2 * Copyright (c) 2002-2009,2011-2012,2014 Apple Inc. All Rights Reserved.
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
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.
20 * TPDatabase.cpp - TP's DL/DB access functions.
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"
40 * Given a DL/DB, look up cert by subject name. Subsequent
41 * certs can be found using the returned result handle.
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
50 CSSM_SELECTION_PREDICATE predicate
;
51 CSSM_DB_UNIQUE_RECORD_PTR record
= NULL
;
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;
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?
73 CSSM_DL_DataGetFirst(dlDb
,
76 NULL
, // don't fetch attributes
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.
92 TPCertInfo
*tpDbFindIssuerCert(
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
102 StLock
<Mutex
> _(SecTrustKeychainsGetMutex());
105 CSSM_HANDLE resultHand
;
107 CSSM_DL_DB_HANDLE dlDb
;
108 CSSM_DB_UNIQUE_RECORD_PTR record
;
109 TPCertInfo
*issuerCert
= NULL
;
111 TPCertInfo
*expiredIssuer
= NULL
;
112 TPCertInfo
*nonRootIssuer
= NULL
;
114 partialIssuerKey
= false;
118 for(dbDex
=0; dbDex
<dbList
->NumHandles
; dbDex
++) {
119 dlDb
= dbList
->DLDBHandle
[dbDex
];
123 record
= tpCertLookup(dlDb
,
124 subjectItem
->issuerName(),
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)...
135 assert(cert
.Data
!= NULL
);
136 tpDbDebug("tpDbFindIssuerCert: found cert record (1) %p", record
);
138 CSSM_RETURN crtn
= CSSM_OK
;
140 issuerCert
= new TPCertInfo(clHand
, cspHand
, &cert
, TIC_CopyData
, verifyTime
);
143 crtn
= CSSMERR_TP_INVALID_CERTIFICATE
;
146 /* we're done with raw cert data */
147 tpFreePluginMemory(dlDb
.DLHandle
, cert
.Data
);
151 /* Does it verify the subject cert? */
152 if(crtn
== CSSM_OK
) {
153 crtn
= subjectItem
->verifyWithIssuer(issuerCert
);
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.
160 if(crtn
== CSSM_OK
) {
161 if(issuerCert
->isExpired() || issuerCert
->isNotValidYet()) {
163 * Exact value not important here, this just uniquely identifies
164 * this situation in the switch below.
166 tpDbDebug("tpDbFindIssuerCert: holding expired cert (1)");
167 crtn
= CSSM_CERT_STATUS_EXPIRED
;
168 /* Delete old stashed expired issuer */
170 expiredIssuer
->freeUniqueRecord();
171 delete expiredIssuer
;
173 expiredIssuer
= issuerCert
;
174 expiredIssuer
->dlDbHandle(dlDb
);
175 expiredIssuer
->uniqueRecord(record
);
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)
182 if(crtn
== CSSM_OK
&& !issuerCert
->isSelfSigned()) {
184 * Exact value not important here, this just uniquely identifies
185 * this situation in the switch below.
187 tpDbDebug("tpDbFindIssuerCert: holding non-root cert (1)");
188 crtn
= CSSM_CERT_STATUS_IS_ROOT
;
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.)
195 (nonRootIssuer
&& (nonRootIssuer
->isExpired() || nonRootIssuer
->isNotValidYet()))) {
197 nonRootIssuer
->freeUniqueRecord();
198 delete nonRootIssuer
;
200 nonRootIssuer
= issuerCert
;
201 nonRootIssuer
->dlDbHandle(dlDb
);
202 nonRootIssuer
->uniqueRecord(record
);
206 CSSM_DL_FreeUniqueRecord(dlDb
, record
);
211 case CSSMERR_CSP_APPLE_PUBLIC_KEY_INCOMPLETE
:
212 partialIssuerKey
= true;
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 */
220 /* else fall through to search for a different one */
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
) {
227 CSSM_DL_FreeUniqueRecord(dlDb
, record
);
233 * Continue searching this DB. Break on finding the holy
234 * grail or no more records found.
240 CSSM_RETURN crtn
= CSSM_DL_DataGetNext(dlDb
,
246 /* no more, done with this DB */
247 assert(cert
.Data
== NULL
);
250 assert(cert
.Data
!= NULL
);
251 tpDbDebug("tpDbFindIssuerCert: found cert record (2) %p", record
);
253 /* found one - does it verify subject? */
255 issuerCert
= new TPCertInfo(clHand
, cspHand
, &cert
, TIC_CopyData
,
259 crtn
= CSSMERR_TP_INVALID_CERTIFICATE
;
261 /* we're done with raw cert data */
262 tpFreePluginMemory(dlDb
.DLHandle
, cert
.Data
);
266 if(crtn
== CSSM_OK
) {
267 crtn
= subjectItem
->verifyWithIssuer(issuerCert
);
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 */
277 expiredIssuer
->freeUniqueRecord();
278 delete expiredIssuer
;
280 expiredIssuer
= issuerCert
;
281 expiredIssuer
->dlDbHandle(dlDb
);
282 expiredIssuer
->uniqueRecord(record
);
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
;
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.)
295 (nonRootIssuer
&& (nonRootIssuer
->isExpired() || nonRootIssuer
->isNotValidYet()))) {
297 nonRootIssuer
->freeUniqueRecord();
298 delete nonRootIssuer
;
300 nonRootIssuer
= issuerCert
;
301 nonRootIssuer
->dlDbHandle(dlDb
);
302 nonRootIssuer
->uniqueRecord(record
);
306 CSSM_DL_FreeUniqueRecord(dlDb
, record
);
314 /* duplicate check, again */
315 if((oldRoot
== NULL
) ||
316 !tp_CompareCerts(issuerCert
->itemData(), oldRoot
->itemData())) {
320 case CSSMERR_CSP_APPLE_PUBLIC_KEY_INCOMPLETE
:
321 partialIssuerKey
= true;
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
) {
336 CSSM_DL_FreeUniqueRecord(dlDb
, record
);
340 } /* searching subsequent records */
341 } /* switch verify */
344 /* NULL record --> end of search --> DB auto-aborted */
345 crtn
= CSSM_DL_DataAbortQuery(dlDb
, resultHand
);
346 assert(crtn
== CSSM_OK
);
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
;
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
;
368 } /* tpCertLookup, i.e., CSSM_DL_DataGetFirst, succeeded */
370 assert(cert
.Data
== NULL
);
371 assert(resultHand
== 0);
373 } /* main loop searching dbList */
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
;
383 return nonRootIssuer
;
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
;
392 /* issuer not found */
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.
400 #define SEARCH_BY_DATE 1
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
410 CSSM_SELECTION_PREDICATE pred
[3];
411 CSSM_DB_UNIQUE_RECORD_PTR record
= NULL
;
412 char timeStr
[CSSM_TIME_STRLEN
+ 1];
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;
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
);
431 tpErrorLog("tpCrlLookup: Invalid VerifyTime string\n");
437 StLock
<Mutex
> _(tpTimeLock());
438 timeAtNowPlus(0, TIME_CSSM
, timeStr
);
441 timeData
.Data
= (uint8
*)timeStr
;
442 timeData
.Length
= CSSM_TIME_STRLEN
;
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;
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;
460 query
.RecordType
= CSSM_DL_DB_RECORD_X509_CRL
;
461 query
.Conjunctive
= CSSM_DB_AND
;
463 query
.NumSelectionPredicates
= 3;
465 query
.NumSelectionPredicates
= 1;
467 query
.SelectionPredicate
= pred
;
468 query
.QueryLimits
.TimeLimit
= 0; // FIXME - meaningful?
469 query
.QueryLimits
.SizeLimit
= 1; // FIXME - meaningful?
470 query
.QueryFlags
= 0; // FIXME - used?
472 CSSM_DL_DataGetFirst(dlDb
,
475 NULL
, // don't fetch attributes
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.
487 TPCrlInfo
*tpDbFindIssuerCrl(
488 TPVerifyContext
&vfyCtx
,
489 const CSSM_DATA
&issuer
,
492 StLock
<Mutex
> _(SecTrustKeychainsGetMutex());
495 CSSM_HANDLE resultHand
;
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
;
506 for(dbDex
=0; dbDex
<dbList
->NumHandles
; dbDex
++) {
507 dlDb
= dbList
->DLDBHandle
[dbDex
];
510 record
= tpCrlLookup(dlDb
,
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)...
523 assert(crl
.Data
!= NULL
);
524 issuerCrl
= new TPCrlInfo(vfyCtx
.clHand
,
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
);
536 /* and we're done with the record */
537 CSSM_DL_FreeUniqueRecord(dlDb
, record
);
539 /* Does it verify with specified context? */
540 crtn
= issuerCrl
->verifyWithContextNow(vfyCtx
, &forCert
);
547 * Verify fail. Continue searching this DB. Break on
548 * finding the holy grail or no more records found.
553 crtn
= CSSM_DL_DataGetNext(dlDb
,
559 /* no more, done with this DB */
560 assert(crl
.Data
== NULL
);
563 assert(crl
.Data
!= NULL
);
565 /* found one - is it any good? */
566 issuerCrl
= new TPCrlInfo(vfyCtx
.clHand
,
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
);
578 CSSM_DL_FreeUniqueRecord(dlDb
, record
);
580 crtn
= issuerCrl
->verifyWithContextNow(vfyCtx
, &forCert
);
581 if(crtn
== CSSM_OK
) {
587 } /* searching subsequent records */
591 if(issuerCrl
!= NULL
) {
592 /* successful return */
593 CSSM_DL_DataAbortQuery(dlDb
, resultHand
);
594 tpDebug("tpDbFindIssuerCrl: found CRL record %p", record
);
597 } /* tpCrlLookup, i.e., CSSM_DL_DataGetFirst, succeeded */
599 assert(crl
.Data
== NULL
);
601 /* in any case, abort the query for this db */
602 CSSM_DL_DataAbortQuery(dlDb
, resultHand
);
604 } /* main loop searching dbList */
606 /* issuer not found */