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