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
) && (expiredIssuer
== NULL
)) {
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 expiredIssuer
= issuerCert
;
169 expiredIssuer
->dlDbHandle(dlDb
);
170 expiredIssuer
->uniqueRecord(record
);
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)
177 if((crtn
== CSSM_OK
) && (nonRootIssuer
== NULL
)) {
178 if(!issuerCert
->isSelfSigned()) {
180 * Exact value not important here, this just uniquely identifies
181 * this situation in the switch below.
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
);
191 case CSSMERR_CSP_APPLE_PUBLIC_KEY_INCOMPLETE
:
192 partialIssuerKey
= true;
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 */
200 /* else fall through to search for a different one */
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
) {
207 CSSM_DL_FreeUniqueRecord(dlDb
, record
);
213 * Continue searching this DB. Break on finding the holy
214 * grail or no more records found.
220 CSSM_RETURN crtn
= CSSM_DL_DataGetNext(dlDb
,
226 /* no more, done with this DB */
227 assert(cert
.Data
== NULL
);
230 assert(cert
.Data
!= NULL
);
231 tpDbDebug("tpDbFindIssuerCert: found cert record (2) %p", record
);
233 /* found one - does it verify subject? */
235 issuerCert
= new TPCertInfo(clHand
, cspHand
, &cert
, TIC_CopyData
,
239 crtn
= CSSMERR_TP_INVALID_CERTIFICATE
;
241 /* we're done with raw cert data */
242 tpFreePluginMemory(dlDb
.DLHandle
, cert
.Data
);
246 if(crtn
== CSSM_OK
) {
247 crtn
= subjectItem
->verifyWithIssuer(issuerCert
);
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
);
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
);
274 /* duplicate check, again */
275 if((oldRoot
== NULL
) ||
276 !tp_CompareCerts(issuerCert
->itemData(), oldRoot
->itemData())) {
280 case CSSMERR_CSP_APPLE_PUBLIC_KEY_INCOMPLETE
:
281 partialIssuerKey
= true;
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
) {
296 CSSM_DL_FreeUniqueRecord(dlDb
, record
);
300 } /* searching subsequent records */
301 } /* switch verify */
304 /* NULL record --> end of search --> DB auto-aborted */
305 crtn
= CSSM_DL_DataAbortQuery(dlDb
, resultHand
);
306 assert(crtn
== CSSM_OK
);
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
;
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
;
328 } /* tpCertLookup, i.e., CSSM_DL_DataGetFirst, succeeded */
330 assert(cert
.Data
== NULL
);
331 assert(resultHand
== 0);
333 } /* main loop searching dbList */
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
;
343 return nonRootIssuer
;
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
;
352 /* issuer not found */
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.
360 #define SEARCH_BY_DATE 1
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
370 CSSM_SELECTION_PREDICATE pred
[3];
371 CSSM_DB_UNIQUE_RECORD_PTR record
= NULL
;
372 char timeStr
[CSSM_TIME_STRLEN
+ 1];
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;
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
);
391 tpErrorLog("tpCrlLookup: Invalid VerifyTime string\n");
397 StLock
<Mutex
> _(tpTimeLock());
398 timeAtNowPlus(0, TIME_CSSM
, timeStr
);
401 timeData
.Data
= (uint8
*)timeStr
;
402 timeData
.Length
= CSSM_TIME_STRLEN
;
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;
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;
420 query
.RecordType
= CSSM_DL_DB_RECORD_X509_CRL
;
421 query
.Conjunctive
= CSSM_DB_AND
;
423 query
.NumSelectionPredicates
= 3;
425 query
.NumSelectionPredicates
= 1;
427 query
.SelectionPredicate
= pred
;
428 query
.QueryLimits
.TimeLimit
= 0; // FIXME - meaningful?
429 query
.QueryLimits
.SizeLimit
= 1; // FIXME - meaningful?
430 query
.QueryFlags
= 0; // FIXME - used?
432 CSSM_DL_DataGetFirst(dlDb
,
435 NULL
, // don't fetch attributes
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.
447 TPCrlInfo
*tpDbFindIssuerCrl(
448 TPVerifyContext
&vfyCtx
,
449 const CSSM_DATA
&issuer
,
452 StLock
<Mutex
> _(SecTrustKeychainsGetMutex());
455 CSSM_HANDLE resultHand
;
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
;
466 for(dbDex
=0; dbDex
<dbList
->NumHandles
; dbDex
++) {
467 dlDb
= dbList
->DLDBHandle
[dbDex
];
470 record
= tpCrlLookup(dlDb
,
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)...
483 assert(crl
.Data
!= NULL
);
484 issuerCrl
= new TPCrlInfo(vfyCtx
.clHand
,
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
);
496 /* and we're done with the record */
497 CSSM_DL_FreeUniqueRecord(dlDb
, record
);
499 /* Does it verify with specified context? */
500 crtn
= issuerCrl
->verifyWithContextNow(vfyCtx
, &forCert
);
507 * Verify fail. Continue searching this DB. Break on
508 * finding the holy grail or no more records found.
513 crtn
= CSSM_DL_DataGetNext(dlDb
,
519 /* no more, done with this DB */
520 assert(crl
.Data
== NULL
);
523 assert(crl
.Data
!= NULL
);
525 /* found one - is it any good? */
526 issuerCrl
= new TPCrlInfo(vfyCtx
.clHand
,
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
);
538 CSSM_DL_FreeUniqueRecord(dlDb
, record
);
540 crtn
= issuerCrl
->verifyWithContextNow(vfyCtx
, &forCert
);
541 if(crtn
== CSSM_OK
) {
547 } /* searching subsequent records */
551 if(issuerCrl
!= NULL
) {
552 /* successful return */
553 CSSM_DL_DataAbortQuery(dlDb
, resultHand
);
554 tpDebug("tpDbFindIssuerCrl: found CRL record %p", record
);
557 } /* tpCrlLookup, i.e., CSSM_DL_DataGetFirst, succeeded */
559 assert(crl
.Data
== NULL
);
561 /* in any case, abort the query for this db */
562 CSSM_DL_DataAbortQuery(dlDb
, resultHand
);
564 } /* main loop searching dbList */
566 /* issuer not found */