]> git.saurik.com Git - apple/security.git/blob - OSX/libsecurity_apple_x509_tp/lib/TPDatabase.cpp
Security-57740.31.2.tar.gz
[apple/security.git] / OSX / 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) {
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 /* Delete old stashed expired issuer */
169 if (expiredIssuer) {
170 expiredIssuer->freeUniqueRecord();
171 delete expiredIssuer;
172 }
173 expiredIssuer = issuerCert;
174 expiredIssuer->dlDbHandle(dlDb);
175 expiredIssuer->uniqueRecord(record);
176 }
177 }
178 /*
179 * Prefer a root over an intermediate issuer if we can get one
180 * (in case a cross-signed intermediate and root are both available)
181 */
182 if(crtn == CSSM_OK && !issuerCert->isSelfSigned()) {
183 /*
184 * Exact value not important here, this just uniquely identifies
185 * this situation in the switch below.
186 */
187 tpDbDebug("tpDbFindIssuerCert: holding non-root cert (1)");
188 crtn = CSSM_CERT_STATUS_IS_ROOT;
189 /*
190 * If the old intermediate was temporally invalid, replace it.
191 * (Regardless of temporal validity of new one we found, because
192 * as far as this code is concerned they're equivalent.)
193 */
194 if(!nonRootIssuer ||
195 (nonRootIssuer && (nonRootIssuer->isExpired() || nonRootIssuer->isNotValidYet()))) {
196 if(nonRootIssuer) {
197 nonRootIssuer->freeUniqueRecord();
198 delete nonRootIssuer;
199 }
200 nonRootIssuer = issuerCert;
201 nonRootIssuer->dlDbHandle(dlDb);
202 nonRootIssuer->uniqueRecord(record);
203 }
204 else {
205 delete issuerCert;
206 CSSM_DL_FreeUniqueRecord(dlDb, record);
207 issuerCert = NULL;
208 }
209 }
210 switch(crtn) {
211 case CSSMERR_CSP_APPLE_PUBLIC_KEY_INCOMPLETE:
212 partialIssuerKey = true;
213 break;
214 case CSSM_OK:
215 if((oldRoot == NULL) ||
216 !tp_CompareCerts(issuerCert->itemData(), oldRoot->itemData())) {
217 /* We found a new root cert which does not match the old one */
218 break;
219 }
220 /* else fall through to search for a different one */
221 default:
222 if(issuerCert != NULL) {
223 /* either holding onto this cert, or done with it. */
224 if(crtn != CSSM_CERT_STATUS_EXPIRED &&
225 crtn != CSSM_CERT_STATUS_IS_ROOT) {
226 delete issuerCert;
227 CSSM_DL_FreeUniqueRecord(dlDb, record);
228 }
229 issuerCert = NULL;
230 }
231
232 /*
233 * Continue searching this DB. Break on finding the holy
234 * grail or no more records found.
235 */
236 for(;;) {
237 cert.Data = NULL;
238 cert.Length = 0;
239 record = NULL;
240 CSSM_RETURN crtn = CSSM_DL_DataGetNext(dlDb,
241 resultHand,
242 NULL, // no attrs
243 &cert,
244 &record);
245 if(crtn) {
246 /* no more, done with this DB */
247 assert(cert.Data == NULL);
248 break;
249 }
250 assert(cert.Data != NULL);
251 tpDbDebug("tpDbFindIssuerCert: found cert record (2) %p", record);
252
253 /* found one - does it verify subject? */
254 try {
255 issuerCert = new TPCertInfo(clHand, cspHand, &cert, TIC_CopyData,
256 verifyTime);
257 }
258 catch(...) {
259 crtn = CSSMERR_TP_INVALID_CERTIFICATE;
260 }
261 /* we're done with raw cert data */
262 tpFreePluginMemory(dlDb.DLHandle, cert.Data);
263 cert.Data = NULL;
264 cert.Length = 0;
265
266 if(crtn == CSSM_OK) {
267 crtn = subjectItem->verifyWithIssuer(issuerCert);
268 }
269
270 /* temporal validity check, again */
271 if(crtn == CSSM_OK) {
272 if(issuerCert->isExpired() || issuerCert->isNotValidYet()) {
273 tpDbDebug("tpDbFindIssuerCert: holding expired cert (2)");
274 crtn = CSSM_CERT_STATUS_EXPIRED;
275 /* Delete old stashed expired issuer */
276 if (expiredIssuer) {
277 expiredIssuer->freeUniqueRecord();
278 delete expiredIssuer;
279 }
280 expiredIssuer = issuerCert;
281 expiredIssuer->dlDbHandle(dlDb);
282 expiredIssuer->uniqueRecord(record);
283 }
284 }
285 /* self-signed check, again */
286 if(crtn == CSSM_OK && !issuerCert->isSelfSigned()) {
287 tpDbDebug("tpDbFindIssuerCert: holding non-root cert (2)");
288 crtn = CSSM_CERT_STATUS_IS_ROOT;
289 /*
290 * If the old intermediate was temporally invalid, replace it.
291 * (Regardless of temporal validity of new one we found, because
292 * as far as this code is concerned they're equivalent.)
293 */
294 if(!nonRootIssuer ||
295 (nonRootIssuer && (nonRootIssuer->isExpired() || nonRootIssuer->isNotValidYet()))) {
296 if(nonRootIssuer) {
297 nonRootIssuer->freeUniqueRecord();
298 delete nonRootIssuer;
299 }
300 nonRootIssuer = issuerCert;
301 nonRootIssuer->dlDbHandle(dlDb);
302 nonRootIssuer->uniqueRecord(record);
303 }
304 else {
305 delete issuerCert;
306 CSSM_DL_FreeUniqueRecord(dlDb, record);
307 issuerCert = NULL;
308 }
309 }
310
311 foundIt = false;
312 switch(crtn) {
313 case CSSM_OK:
314 /* duplicate check, again */
315 if((oldRoot == NULL) ||
316 !tp_CompareCerts(issuerCert->itemData(), oldRoot->itemData())) {
317 foundIt = true;
318 }
319 break;
320 case CSSMERR_CSP_APPLE_PUBLIC_KEY_INCOMPLETE:
321 partialIssuerKey = true;
322 foundIt = true;
323 break;
324 default:
325 break;
326 }
327 if(foundIt) {
328 /* yes! */
329 break;
330 }
331 if(issuerCert != NULL) {
332 /* either holding onto this cert, or done with it. */
333 if(crtn != CSSM_CERT_STATUS_EXPIRED &&
334 crtn != CSSM_CERT_STATUS_IS_ROOT) {
335 delete issuerCert;
336 CSSM_DL_FreeUniqueRecord(dlDb, record);
337 }
338 issuerCert = NULL;
339 }
340 } /* searching subsequent records */
341 } /* switch verify */
342
343 if(record != NULL) {
344 /* NULL record --> end of search --> DB auto-aborted */
345 crtn = CSSM_DL_DataAbortQuery(dlDb, resultHand);
346 assert(crtn == CSSM_OK);
347 }
348 if(issuerCert != NULL) {
349 /* successful return */
350 tpDbDebug("tpDbFindIssuer: returning record %p", record);
351 issuerCert->dlDbHandle(dlDb);
352 issuerCert->uniqueRecord(record);
353 if(expiredIssuer != NULL) {
354 /* We found a replacement */
355 tpDbDebug("tpDbFindIssuer: discarding expired cert");
356 expiredIssuer->freeUniqueRecord();
357 delete expiredIssuer;
358 }
359 /* Avoid deleting the non-root cert if same as expired cert */
360 if(nonRootIssuer != NULL && nonRootIssuer != expiredIssuer) {
361 /* We found a replacement */
362 tpDbDebug("tpDbFindIssuer: discarding non-root cert");
363 nonRootIssuer->freeUniqueRecord();
364 delete nonRootIssuer;
365 }
366 return issuerCert;
367 }
368 } /* tpCertLookup, i.e., CSSM_DL_DataGetFirst, succeeded */
369 else {
370 assert(cert.Data == NULL);
371 assert(resultHand == 0);
372 }
373 } /* main loop searching dbList */
374
375 if(nonRootIssuer != NULL) {
376 /* didn't find root issuer, so use this one */
377 tpDbDebug("tpDbFindIssuer: taking non-root issuer cert, record %p",
378 nonRootIssuer->uniqueRecord());
379 if(expiredIssuer != NULL && expiredIssuer != nonRootIssuer) {
380 expiredIssuer->freeUniqueRecord();
381 delete expiredIssuer;
382 }
383 return nonRootIssuer;
384 }
385
386 if(expiredIssuer != NULL) {
387 /* OK, we'll take this one */
388 tpDbDebug("tpDbFindIssuer: taking expired cert after all, record %p",
389 expiredIssuer->uniqueRecord());
390 return expiredIssuer;
391 }
392 /* issuer not found */
393 return NULL;
394 }
395
396 /*
397 * Given a DL/DB, look up CRL by issuer name and validity time.
398 * Subsequent CRLs can be found using the returned result handle.
399 */
400 #define SEARCH_BY_DATE 1
401
402 static CSSM_DB_UNIQUE_RECORD_PTR tpCrlLookup(
403 CSSM_DL_DB_HANDLE dlDb,
404 const CSSM_DATA *issuerName, // DER-encoded
405 CSSM_TIMESTRING verifyTime, // may be NULL, implies "now"
406 CSSM_HANDLE_PTR resultHand, // RETURNED
407 CSSM_DATA_PTR crl) // RETURNED
408 {
409 CSSM_QUERY query;
410 CSSM_SELECTION_PREDICATE pred[3];
411 CSSM_DB_UNIQUE_RECORD_PTR record = NULL;
412 char timeStr[CSSM_TIME_STRLEN + 1];
413
414 crl->Data = NULL;
415 crl->Length = 0;
416
417 /* Three predicates...first, the issuer name */
418 pred[0].DbOperator = CSSM_DB_EQUAL;
419 pred[0].Attribute.Info.AttributeNameFormat =
420 CSSM_DB_ATTRIBUTE_NAME_AS_STRING;
421 pred[0].Attribute.Info.Label.AttributeName = (char*) "Issuer";
422 pred[0].Attribute.Info.AttributeFormat = CSSM_DB_ATTRIBUTE_FORMAT_BLOB;
423 pred[0].Attribute.Value = const_cast<CSSM_DATA_PTR>(issuerName);
424 pred[0].Attribute.NumberOfValues = 1;
425
426 /* now before/after. Cook up an appropriate time string. */
427 if(verifyTime != NULL) {
428 /* Caller spec'd tolerate any format */
429 int rtn = tpTimeToCssmTimestring(verifyTime, (unsigned)strlen(verifyTime), timeStr);
430 if(rtn) {
431 tpErrorLog("tpCrlLookup: Invalid VerifyTime string\n");
432 return NULL;
433 }
434 }
435 else {
436 /* right now */
437 StLock<Mutex> _(tpTimeLock());
438 timeAtNowPlus(0, TIME_CSSM, timeStr);
439 }
440 CSSM_DATA timeData;
441 timeData.Data = (uint8 *)timeStr;
442 timeData.Length = CSSM_TIME_STRLEN;
443
444 #if SEARCH_BY_DATE
445 pred[1].DbOperator = CSSM_DB_LESS_THAN;
446 pred[1].Attribute.Info.AttributeNameFormat = CSSM_DB_ATTRIBUTE_NAME_AS_STRING;
447 pred[1].Attribute.Info.Label.AttributeName = (char*) "NextUpdate";
448 pred[1].Attribute.Info.AttributeFormat = CSSM_DB_ATTRIBUTE_FORMAT_BLOB;
449 pred[1].Attribute.Value = &timeData;
450 pred[1].Attribute.NumberOfValues = 1;
451
452 pred[2].DbOperator = CSSM_DB_GREATER_THAN;
453 pred[2].Attribute.Info.AttributeNameFormat = CSSM_DB_ATTRIBUTE_NAME_AS_STRING;
454 pred[2].Attribute.Info.Label.AttributeName = (char*) "ThisUpdate";
455 pred[2].Attribute.Info.AttributeFormat = CSSM_DB_ATTRIBUTE_FORMAT_BLOB;
456 pred[2].Attribute.Value = &timeData;
457 pred[2].Attribute.NumberOfValues = 1;
458 #endif
459
460 query.RecordType = CSSM_DL_DB_RECORD_X509_CRL;
461 query.Conjunctive = CSSM_DB_AND;
462 #if SEARCH_BY_DATE
463 query.NumSelectionPredicates = 3;
464 #else
465 query.NumSelectionPredicates = 1;
466 #endif
467 query.SelectionPredicate = pred;
468 query.QueryLimits.TimeLimit = 0; // FIXME - meaningful?
469 query.QueryLimits.SizeLimit = 1; // FIXME - meaningful?
470 query.QueryFlags = 0; // FIXME - used?
471
472 CSSM_DL_DataGetFirst(dlDb,
473 &query,
474 resultHand,
475 NULL, // don't fetch attributes
476 crl,
477 &record);
478 return record;
479 }
480
481 /*
482 * Search a list of DBs for a CRL from the specified issuer and (optional)
483 * TPVerifyContext.verifyTime.
484 * Just a boolean return - we found it, or not. If we did, we return a
485 * TPCrlInfo which has been verified with the specified TPVerifyContext.
486 */
487 TPCrlInfo *tpDbFindIssuerCrl(
488 TPVerifyContext &vfyCtx,
489 const CSSM_DATA &issuer,
490 TPCertInfo &forCert)
491 {
492 StLock<Mutex> _(SecTrustKeychainsGetMutex());
493
494 uint32 dbDex;
495 CSSM_HANDLE resultHand;
496 CSSM_DATA crl;
497 CSSM_DL_DB_HANDLE dlDb;
498 CSSM_DB_UNIQUE_RECORD_PTR record;
499 TPCrlInfo *issuerCrl = NULL;
500 CSSM_DL_DB_LIST_PTR dbList = vfyCtx.dbList;
501 CSSM_RETURN crtn;
502
503 if(dbList == NULL) {
504 return NULL;
505 }
506 for(dbDex=0; dbDex<dbList->NumHandles; dbDex++) {
507 dlDb = dbList->DLDBHandle[dbDex];
508 crl.Data = NULL;
509 crl.Length = 0;
510 record = tpCrlLookup(dlDb,
511 &issuer,
512 vfyCtx.verifyTime,
513 &resultHand,
514 &crl);
515 /* remember we have to:
516 * -- abort this query regardless, and
517 * -- free the CSSM_DATA crl regardless, and
518 * -- free the unique record if we don't use it
519 * (by placing it in issuerCert)...
520 */
521 if(record != NULL) {
522 /* Found one */
523 assert(crl.Data != NULL);
524 issuerCrl = new TPCrlInfo(vfyCtx.clHand,
525 vfyCtx.cspHand,
526 &crl,
527 TIC_CopyData,
528 vfyCtx.verifyTime);
529 /* we're done with raw CRL data */
530 /* FIXME this assumes that vfyCtx.alloc is the same as the
531 * allocator associated with DlDB...OK? */
532 tpFreeCssmData(vfyCtx.alloc, &crl, CSSM_FALSE);
533 crl.Data = NULL;
534 crl.Length = 0;
535
536 /* and we're done with the record */
537 CSSM_DL_FreeUniqueRecord(dlDb, record);
538
539 /* Does it verify with specified context? */
540 crtn = issuerCrl->verifyWithContextNow(vfyCtx, &forCert);
541 if(crtn) {
542
543 delete issuerCrl;
544 issuerCrl = NULL;
545
546 /*
547 * Verify fail. Continue searching this DB. Break on
548 * finding the holy grail or no more records found.
549 */
550 for(;;) {
551 crl.Data = NULL;
552 crl.Length = 0;
553 crtn = CSSM_DL_DataGetNext(dlDb,
554 resultHand,
555 NULL, // no attrs
556 &crl,
557 &record);
558 if(crtn) {
559 /* no more, done with this DB */
560 assert(crl.Data == NULL);
561 break;
562 }
563 assert(crl.Data != NULL);
564
565 /* found one - is it any good? */
566 issuerCrl = new TPCrlInfo(vfyCtx.clHand,
567 vfyCtx.cspHand,
568 &crl,
569 TIC_CopyData,
570 vfyCtx.verifyTime);
571 /* we're done with raw CRL data */
572 /* FIXME this assumes that vfyCtx.alloc is the same as the
573 * allocator associated with DlDB...OK? */
574 tpFreeCssmData(vfyCtx.alloc, &crl, CSSM_FALSE);
575 crl.Data = NULL;
576 crl.Length = 0;
577
578 CSSM_DL_FreeUniqueRecord(dlDb, record);
579
580 crtn = issuerCrl->verifyWithContextNow(vfyCtx, &forCert);
581 if(crtn == CSSM_OK) {
582 /* yes! */
583 break;
584 }
585 delete issuerCrl;
586 issuerCrl = NULL;
587 } /* searching subsequent records */
588 } /* verify fail */
589 /* else success! */
590
591 if(issuerCrl != NULL) {
592 /* successful return */
593 CSSM_DL_DataAbortQuery(dlDb, resultHand);
594 tpDebug("tpDbFindIssuerCrl: found CRL record %p", record);
595 return issuerCrl;
596 }
597 } /* tpCrlLookup, i.e., CSSM_DL_DataGetFirst, succeeded */
598 else {
599 assert(crl.Data == NULL);
600 }
601 /* in any case, abort the query for this db */
602 CSSM_DL_DataAbortQuery(dlDb, resultHand);
603
604 } /* main loop searching dbList */
605
606 /* issuer not found */
607 return NULL;
608 }
609