]> git.saurik.com Git - apple/security.git/blob - Security/libsecurity_apple_x509_tp/lib/TPDatabase.cpp
Security-57031.40.6.tar.gz
[apple/security.git] / Security / libsecurity_apple_x509_tp / lib / TPDatabase.cpp
1 /*
2 * Copyright (c) 2002-2009,2011-2012,2014 Apple Inc. All Rights Reserved.
3 *
4 * The contents of this file constitute Original Code as defined in and are
5 * subject to the Apple Public Source License Version 1.2 (the 'License').
6 * You may not use this file except in compliance with the License. Please obtain
7 * a copy of the License at http://www.apple.com/publicsource and read it before
8 * using this file.
9 *
10 * This Original Code and all software distributed under the License are
11 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS
12 * OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, INCLUDING WITHOUT
13 * LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
14 * PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. Please see the License for the
15 * specific language governing rights and limitations under the License.
16 */
17
18
19 /*
20 * TPDatabase.cpp - TP's DL/DB access functions.
21 *
22 */
23
24 #include <Security/cssmtype.h>
25 #include <Security/cssmapi.h>
26 #include <security_cdsa_utilities/Schema.h> /* private API */
27 #include <security_keychain/TrustKeychains.h> /* private SecTrustKeychainsGetMutex() */
28 #include <Security/SecCertificatePriv.h> /* private SecInferLabelFromX509Name() */
29 #include <Security/oidscert.h>
30 #include "TPDatabase.h"
31 #include "tpdebugging.h"
32 #include "certGroupUtils.h"
33 #include "TPCertInfo.h"
34 #include "TPCrlInfo.h"
35 #include "tpCrlVerify.h"
36 #include "tpTime.h"
37
38
39 /*
40 * Given a DL/DB, look up cert by subject name. Subsequent
41 * certs can be found using the returned result handle.
42 */
43 static CSSM_DB_UNIQUE_RECORD_PTR tpCertLookup(
44 CSSM_DL_DB_HANDLE dlDb,
45 const CSSM_DATA *subjectName, // DER-encoded
46 CSSM_HANDLE_PTR resultHand, // RETURNED
47 CSSM_DATA_PTR cert) // RETURNED
48 {
49 CSSM_QUERY query;
50 CSSM_SELECTION_PREDICATE predicate;
51 CSSM_DB_UNIQUE_RECORD_PTR record = NULL;
52
53 cert->Data = NULL;
54 cert->Length = 0;
55
56 /* SWAG until cert schema nailed down */
57 predicate.DbOperator = CSSM_DB_EQUAL;
58 predicate.Attribute.Info.AttributeNameFormat =
59 CSSM_DB_ATTRIBUTE_NAME_AS_STRING;
60 predicate.Attribute.Info.Label.AttributeName = (char*) "Subject";
61 predicate.Attribute.Info.AttributeFormat = CSSM_DB_ATTRIBUTE_FORMAT_BLOB;
62 predicate.Attribute.Value = const_cast<CSSM_DATA_PTR>(subjectName);
63 predicate.Attribute.NumberOfValues = 1;
64
65 query.RecordType = CSSM_DL_DB_RECORD_X509_CERTIFICATE;
66 query.Conjunctive = CSSM_DB_NONE;
67 query.NumSelectionPredicates = 1;
68 query.SelectionPredicate = &predicate;
69 query.QueryLimits.TimeLimit = 0; // FIXME - meaningful?
70 query.QueryLimits.SizeLimit = 1; // FIXME - meaningful?
71 query.QueryFlags = 0; // FIXME - used?
72
73 CSSM_DL_DataGetFirst(dlDb,
74 &query,
75 resultHand,
76 NULL, // don't fetch attributes
77 cert,
78 &record);
79
80 return record;
81 }
82
83 /*
84 * Search a list of DBs for a cert which verifies specified subject item.
85 * Just a boolean return - we found it, or not. If we did, we return
86 * TPCertInfo associated with the raw cert.
87 * A true partialIssuerKey on return indicates that caller must deal
88 * with partial public key processing later.
89 * If verifyCurrent is true, we will not return a cert which is not
90 * temporally valid; else we may well do so.
91 */
92 TPCertInfo *tpDbFindIssuerCert(
93 Allocator &alloc,
94 CSSM_CL_HANDLE clHand,
95 CSSM_CSP_HANDLE cspHand,
96 const TPClItemInfo *subjectItem,
97 const CSSM_DL_DB_LIST *dbList,
98 const char *verifyTime, // may be NULL
99 bool &partialIssuerKey, // RETURNED
100 TPCertInfo *oldRoot)
101 {
102 StLock<Mutex> _(SecTrustKeychainsGetMutex());
103
104 uint32 dbDex;
105 CSSM_HANDLE resultHand;
106 CSSM_DATA cert;
107 CSSM_DL_DB_HANDLE dlDb;
108 CSSM_DB_UNIQUE_RECORD_PTR record;
109 TPCertInfo *issuerCert = NULL;
110 bool foundIt;
111 TPCertInfo *expiredIssuer = NULL;
112 TPCertInfo *nonRootIssuer = NULL;
113
114 partialIssuerKey = false;
115 if(dbList == NULL) {
116 return NULL;
117 }
118 for(dbDex=0; dbDex<dbList->NumHandles; dbDex++) {
119 dlDb = dbList->DLDBHandle[dbDex];
120 cert.Data = NULL;
121 cert.Length = 0;
122 resultHand = 0;
123 record = tpCertLookup(dlDb,
124 subjectItem->issuerName(),
125 &resultHand,
126 &cert);
127 /* remember we have to:
128 * -- abort this query regardless, and
129 * -- free the CSSM_DATA cert regardless, and
130 * -- free the unique record if we don't use it
131 * (by placing it in issuerCert)...
132 */
133 if(record != NULL) {
134 /* Found one */
135 assert(cert.Data != NULL);
136 tpDbDebug("tpDbFindIssuerCert: found cert record (1) %p", record);
137 issuerCert = NULL;
138 CSSM_RETURN crtn = CSSM_OK;
139 try {
140 issuerCert = new TPCertInfo(clHand, cspHand, &cert, TIC_CopyData, verifyTime);
141 }
142 catch(...) {
143 crtn = CSSMERR_TP_INVALID_CERTIFICATE;
144 }
145
146 /* we're done with raw cert data */
147 tpFreePluginMemory(dlDb.DLHandle, cert.Data);
148 cert.Data = NULL;
149 cert.Length = 0;
150
151 /* Does it verify the subject cert? */
152 if(crtn == CSSM_OK) {
153 crtn = subjectItem->verifyWithIssuer(issuerCert);
154 }
155
156 /*
157 * Handle temporal invalidity - if so and this is the first one
158 * we've seen, hold on to it while we search for better one.
159 */
160 if((crtn == CSSM_OK) && (expiredIssuer == NULL)) {
161 if(issuerCert->isExpired() || issuerCert->isNotValidYet()) {
162 /*
163 * Exact value not important here, this just uniquely identifies
164 * this situation in the switch below.
165 */
166 tpDbDebug("tpDbFindIssuerCert: holding expired cert (1)");
167 crtn = CSSM_CERT_STATUS_EXPIRED;
168 expiredIssuer = issuerCert;
169 expiredIssuer->dlDbHandle(dlDb);
170 expiredIssuer->uniqueRecord(record);
171 }
172 }
173 /*
174 * Prefer a root over an intermediate issuer if we can get one
175 * (in case a cross-signed intermediate and root are both available)
176 */
177 if((crtn == CSSM_OK) && (nonRootIssuer == NULL)) {
178 if(!issuerCert->isSelfSigned()) {
179 /*
180 * Exact value not important here, this just uniquely identifies
181 * this situation in the switch below.
182 */
183 tpDbDebug("tpDbFindIssuerCert: holding non-root cert (1)");
184 crtn = CSSM_CERT_STATUS_IS_ROOT;
185 nonRootIssuer = issuerCert;
186 nonRootIssuer->dlDbHandle(dlDb);
187 nonRootIssuer->uniqueRecord(record);
188 }
189 }
190 switch(crtn) {
191 case CSSMERR_CSP_APPLE_PUBLIC_KEY_INCOMPLETE:
192 partialIssuerKey = true;
193 break;
194 case CSSM_OK:
195 if((oldRoot == NULL) ||
196 !tp_CompareCerts(issuerCert->itemData(), oldRoot->itemData())) {
197 /* We found a new root cert which does not match the old one */
198 break;
199 }
200 /* else fall through to search for a different one */
201 default:
202 if(issuerCert != NULL) {
203 /* either holding onto this cert, or done with it. */
204 if(crtn != CSSM_CERT_STATUS_EXPIRED &&
205 crtn != CSSM_CERT_STATUS_IS_ROOT) {
206 delete issuerCert;
207 CSSM_DL_FreeUniqueRecord(dlDb, record);
208 }
209 issuerCert = NULL;
210 }
211
212 /*
213 * Continue searching this DB. Break on finding the holy
214 * grail or no more records found.
215 */
216 for(;;) {
217 cert.Data = NULL;
218 cert.Length = 0;
219 record = NULL;
220 CSSM_RETURN crtn = CSSM_DL_DataGetNext(dlDb,
221 resultHand,
222 NULL, // no attrs
223 &cert,
224 &record);
225 if(crtn) {
226 /* no more, done with this DB */
227 assert(cert.Data == NULL);
228 break;
229 }
230 assert(cert.Data != NULL);
231 tpDbDebug("tpDbFindIssuerCert: found cert record (2) %p", record);
232
233 /* found one - does it verify subject? */
234 try {
235 issuerCert = new TPCertInfo(clHand, cspHand, &cert, TIC_CopyData,
236 verifyTime);
237 }
238 catch(...) {
239 crtn = CSSMERR_TP_INVALID_CERTIFICATE;
240 }
241 /* we're done with raw cert data */
242 tpFreePluginMemory(dlDb.DLHandle, cert.Data);
243 cert.Data = NULL;
244 cert.Length = 0;
245
246 if(crtn == CSSM_OK) {
247 crtn = subjectItem->verifyWithIssuer(issuerCert);
248 }
249
250 /* temporal validity check, again */
251 if((crtn == CSSM_OK) && (expiredIssuer == NULL)) {
252 if(issuerCert->isExpired() || issuerCert->isNotValidYet()) {
253 tpDbDebug("tpDbFindIssuerCert: holding expired cert (2)");
254 crtn = CSSM_CERT_STATUS_EXPIRED;
255 expiredIssuer = issuerCert;
256 expiredIssuer->dlDbHandle(dlDb);
257 expiredIssuer->uniqueRecord(record);
258 }
259 }
260 /* self-signed check, again */
261 if((crtn == CSSM_OK) && (nonRootIssuer == NULL)) {
262 if(!issuerCert->isSelfSigned()) {
263 tpDbDebug("tpDbFindIssuerCert: holding non-root cert (2)");
264 crtn = CSSM_CERT_STATUS_IS_ROOT;
265 nonRootIssuer = issuerCert;
266 nonRootIssuer->dlDbHandle(dlDb);
267 nonRootIssuer->uniqueRecord(record);
268 }
269 }
270
271 foundIt = false;
272 switch(crtn) {
273 case CSSM_OK:
274 /* duplicate check, again */
275 if((oldRoot == NULL) ||
276 !tp_CompareCerts(issuerCert->itemData(), oldRoot->itemData())) {
277 foundIt = true;
278 }
279 break;
280 case CSSMERR_CSP_APPLE_PUBLIC_KEY_INCOMPLETE:
281 partialIssuerKey = true;
282 foundIt = true;
283 break;
284 default:
285 break;
286 }
287 if(foundIt) {
288 /* yes! */
289 break;
290 }
291 if(issuerCert != NULL) {
292 /* either holding onto this cert, or done with it. */
293 if(crtn != CSSM_CERT_STATUS_EXPIRED &&
294 crtn != CSSM_CERT_STATUS_IS_ROOT) {
295 delete issuerCert;
296 CSSM_DL_FreeUniqueRecord(dlDb, record);
297 }
298 issuerCert = NULL;
299 }
300 } /* searching subsequent records */
301 } /* switch verify */
302
303 if(record != NULL) {
304 /* NULL record --> end of search --> DB auto-aborted */
305 crtn = CSSM_DL_DataAbortQuery(dlDb, resultHand);
306 assert(crtn == CSSM_OK);
307 }
308 if(issuerCert != NULL) {
309 /* successful return */
310 tpDbDebug("tpDbFindIssuer: returning record %p", record);
311 issuerCert->dlDbHandle(dlDb);
312 issuerCert->uniqueRecord(record);
313 if(expiredIssuer != NULL) {
314 /* We found a replacement */
315 tpDbDebug("tpDbFindIssuer: discarding expired cert");
316 expiredIssuer->freeUniqueRecord();
317 delete expiredIssuer;
318 }
319 /* Avoid deleting the non-root cert if same as expired cert */
320 if(nonRootIssuer != NULL && nonRootIssuer != expiredIssuer) {
321 /* We found a replacement */
322 tpDbDebug("tpDbFindIssuer: discarding non-root cert");
323 nonRootIssuer->freeUniqueRecord();
324 delete nonRootIssuer;
325 }
326 return issuerCert;
327 }
328 } /* tpCertLookup, i.e., CSSM_DL_DataGetFirst, succeeded */
329 else {
330 assert(cert.Data == NULL);
331 assert(resultHand == 0);
332 }
333 } /* main loop searching dbList */
334
335 if(nonRootIssuer != NULL) {
336 /* didn't find root issuer, so use this one */
337 tpDbDebug("tpDbFindIssuer: taking non-root issuer cert, record %p",
338 nonRootIssuer->uniqueRecord());
339 if(expiredIssuer != NULL && expiredIssuer != nonRootIssuer) {
340 expiredIssuer->freeUniqueRecord();
341 delete expiredIssuer;
342 }
343 return nonRootIssuer;
344 }
345
346 if(expiredIssuer != NULL) {
347 /* OK, we'll take this one */
348 tpDbDebug("tpDbFindIssuer: taking expired cert after all, record %p",
349 expiredIssuer->uniqueRecord());
350 return expiredIssuer;
351 }
352 /* issuer not found */
353 return NULL;
354 }
355
356 /*
357 * Given a DL/DB, look up CRL by issuer name and validity time.
358 * Subsequent CRLs can be found using the returned result handle.
359 */
360 #define SEARCH_BY_DATE 1
361
362 static CSSM_DB_UNIQUE_RECORD_PTR tpCrlLookup(
363 CSSM_DL_DB_HANDLE dlDb,
364 const CSSM_DATA *issuerName, // DER-encoded
365 CSSM_TIMESTRING verifyTime, // may be NULL, implies "now"
366 CSSM_HANDLE_PTR resultHand, // RETURNED
367 CSSM_DATA_PTR crl) // RETURNED
368 {
369 CSSM_QUERY query;
370 CSSM_SELECTION_PREDICATE pred[3];
371 CSSM_DB_UNIQUE_RECORD_PTR record = NULL;
372 char timeStr[CSSM_TIME_STRLEN + 1];
373
374 crl->Data = NULL;
375 crl->Length = 0;
376
377 /* Three predicates...first, the issuer name */
378 pred[0].DbOperator = CSSM_DB_EQUAL;
379 pred[0].Attribute.Info.AttributeNameFormat =
380 CSSM_DB_ATTRIBUTE_NAME_AS_STRING;
381 pred[0].Attribute.Info.Label.AttributeName = (char*) "Issuer";
382 pred[0].Attribute.Info.AttributeFormat = CSSM_DB_ATTRIBUTE_FORMAT_BLOB;
383 pred[0].Attribute.Value = const_cast<CSSM_DATA_PTR>(issuerName);
384 pred[0].Attribute.NumberOfValues = 1;
385
386 /* now before/after. Cook up an appropriate time string. */
387 if(verifyTime != NULL) {
388 /* Caller spec'd tolerate any format */
389 int rtn = tpTimeToCssmTimestring(verifyTime, (unsigned)strlen(verifyTime), timeStr);
390 if(rtn) {
391 tpErrorLog("tpCrlLookup: Invalid VerifyTime string\n");
392 return NULL;
393 }
394 }
395 else {
396 /* right now */
397 StLock<Mutex> _(tpTimeLock());
398 timeAtNowPlus(0, TIME_CSSM, timeStr);
399 }
400 CSSM_DATA timeData;
401 timeData.Data = (uint8 *)timeStr;
402 timeData.Length = CSSM_TIME_STRLEN;
403
404 #if SEARCH_BY_DATE
405 pred[1].DbOperator = CSSM_DB_LESS_THAN;
406 pred[1].Attribute.Info.AttributeNameFormat = CSSM_DB_ATTRIBUTE_NAME_AS_STRING;
407 pred[1].Attribute.Info.Label.AttributeName = (char*) "NextUpdate";
408 pred[1].Attribute.Info.AttributeFormat = CSSM_DB_ATTRIBUTE_FORMAT_BLOB;
409 pred[1].Attribute.Value = &timeData;
410 pred[1].Attribute.NumberOfValues = 1;
411
412 pred[2].DbOperator = CSSM_DB_GREATER_THAN;
413 pred[2].Attribute.Info.AttributeNameFormat = CSSM_DB_ATTRIBUTE_NAME_AS_STRING;
414 pred[2].Attribute.Info.Label.AttributeName = (char*) "ThisUpdate";
415 pred[2].Attribute.Info.AttributeFormat = CSSM_DB_ATTRIBUTE_FORMAT_BLOB;
416 pred[2].Attribute.Value = &timeData;
417 pred[2].Attribute.NumberOfValues = 1;
418 #endif
419
420 query.RecordType = CSSM_DL_DB_RECORD_X509_CRL;
421 query.Conjunctive = CSSM_DB_AND;
422 #if SEARCH_BY_DATE
423 query.NumSelectionPredicates = 3;
424 #else
425 query.NumSelectionPredicates = 1;
426 #endif
427 query.SelectionPredicate = pred;
428 query.QueryLimits.TimeLimit = 0; // FIXME - meaningful?
429 query.QueryLimits.SizeLimit = 1; // FIXME - meaningful?
430 query.QueryFlags = 0; // FIXME - used?
431
432 CSSM_DL_DataGetFirst(dlDb,
433 &query,
434 resultHand,
435 NULL, // don't fetch attributes
436 crl,
437 &record);
438 return record;
439 }
440
441 /*
442 * Search a list of DBs for a CRL from the specified issuer and (optional)
443 * TPVerifyContext.verifyTime.
444 * Just a boolean return - we found it, or not. If we did, we return a
445 * TPCrlInfo which has been verified with the specified TPVerifyContext.
446 */
447 TPCrlInfo *tpDbFindIssuerCrl(
448 TPVerifyContext &vfyCtx,
449 const CSSM_DATA &issuer,
450 TPCertInfo &forCert)
451 {
452 StLock<Mutex> _(SecTrustKeychainsGetMutex());
453
454 uint32 dbDex;
455 CSSM_HANDLE resultHand;
456 CSSM_DATA crl;
457 CSSM_DL_DB_HANDLE dlDb;
458 CSSM_DB_UNIQUE_RECORD_PTR record;
459 TPCrlInfo *issuerCrl = NULL;
460 CSSM_DL_DB_LIST_PTR dbList = vfyCtx.dbList;
461 CSSM_RETURN crtn;
462
463 if(dbList == NULL) {
464 return NULL;
465 }
466 for(dbDex=0; dbDex<dbList->NumHandles; dbDex++) {
467 dlDb = dbList->DLDBHandle[dbDex];
468 crl.Data = NULL;
469 crl.Length = 0;
470 record = tpCrlLookup(dlDb,
471 &issuer,
472 vfyCtx.verifyTime,
473 &resultHand,
474 &crl);
475 /* remember we have to:
476 * -- abort this query regardless, and
477 * -- free the CSSM_DATA crl regardless, and
478 * -- free the unique record if we don't use it
479 * (by placing it in issuerCert)...
480 */
481 if(record != NULL) {
482 /* Found one */
483 assert(crl.Data != NULL);
484 issuerCrl = new TPCrlInfo(vfyCtx.clHand,
485 vfyCtx.cspHand,
486 &crl,
487 TIC_CopyData,
488 vfyCtx.verifyTime);
489 /* we're done with raw CRL data */
490 /* FIXME this assumes that vfyCtx.alloc is the same as the
491 * allocator associated with DlDB...OK? */
492 tpFreeCssmData(vfyCtx.alloc, &crl, CSSM_FALSE);
493 crl.Data = NULL;
494 crl.Length = 0;
495
496 /* and we're done with the record */
497 CSSM_DL_FreeUniqueRecord(dlDb, record);
498
499 /* Does it verify with specified context? */
500 crtn = issuerCrl->verifyWithContextNow(vfyCtx, &forCert);
501 if(crtn) {
502
503 delete issuerCrl;
504 issuerCrl = NULL;
505
506 /*
507 * Verify fail. Continue searching this DB. Break on
508 * finding the holy grail or no more records found.
509 */
510 for(;;) {
511 crl.Data = NULL;
512 crl.Length = 0;
513 crtn = CSSM_DL_DataGetNext(dlDb,
514 resultHand,
515 NULL, // no attrs
516 &crl,
517 &record);
518 if(crtn) {
519 /* no more, done with this DB */
520 assert(crl.Data == NULL);
521 break;
522 }
523 assert(crl.Data != NULL);
524
525 /* found one - is it any good? */
526 issuerCrl = new TPCrlInfo(vfyCtx.clHand,
527 vfyCtx.cspHand,
528 &crl,
529 TIC_CopyData,
530 vfyCtx.verifyTime);
531 /* we're done with raw CRL data */
532 /* FIXME this assumes that vfyCtx.alloc is the same as the
533 * allocator associated with DlDB...OK? */
534 tpFreeCssmData(vfyCtx.alloc, &crl, CSSM_FALSE);
535 crl.Data = NULL;
536 crl.Length = 0;
537
538 CSSM_DL_FreeUniqueRecord(dlDb, record);
539
540 crtn = issuerCrl->verifyWithContextNow(vfyCtx, &forCert);
541 if(crtn == CSSM_OK) {
542 /* yes! */
543 break;
544 }
545 delete issuerCrl;
546 issuerCrl = NULL;
547 } /* searching subsequent records */
548 } /* verify fail */
549 /* else success! */
550
551 if(issuerCrl != NULL) {
552 /* successful return */
553 CSSM_DL_DataAbortQuery(dlDb, resultHand);
554 tpDebug("tpDbFindIssuerCrl: found CRL record %p", record);
555 return issuerCrl;
556 }
557 } /* tpCrlLookup, i.e., CSSM_DL_DataGetFirst, succeeded */
558 else {
559 assert(crl.Data == NULL);
560 }
561 /* in any case, abort the query for this db */
562 CSSM_DL_DataAbortQuery(dlDb, resultHand);
563
564 } /* main loop searching dbList */
565
566 /* issuer not found */
567 return NULL;
568 }
569