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
101 StLock
<Mutex
> _(SecTrustKeychainsGetMutex());
104 CSSM_HANDLE resultHand
;
106 CSSM_DL_DB_HANDLE dlDb
;
107 CSSM_DB_UNIQUE_RECORD_PTR record
;
108 TPCertInfo
*issuerCert
= NULL
;
110 TPCertInfo
*expiredIssuer
= NULL
;
111 TPCertInfo
*nonRootIssuer
= NULL
;
113 partialIssuerKey
= false;
117 for(dbDex
=0; dbDex
<dbList
->NumHandles
; dbDex
++) {
118 dlDb
= dbList
->DLDBHandle
[dbDex
];
122 record
= tpCertLookup(dlDb
,
123 subjectItem
->issuerName(),
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)...
134 assert(cert
.Data
!= NULL
);
135 tpDbDebug("tpDbFindIssuerCert: found cert record (1) %p", record
);
137 CSSM_RETURN crtn
= CSSM_OK
;
139 issuerCert
= new TPCertInfo(clHand
, cspHand
, &cert
, TIC_CopyData
, verifyTime
);
142 crtn
= CSSMERR_TP_INVALID_CERTIFICATE
;
145 /* we're done with raw cert data */
146 tpFreePluginMemory(dlDb
.DLHandle
, cert
.Data
);
150 /* Does it verify the subject cert? */
151 if(crtn
== CSSM_OK
) {
152 crtn
= subjectItem
->verifyWithIssuer(issuerCert
);
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.
159 if((crtn
== CSSM_OK
) && (expiredIssuer
== NULL
)) {
160 if(issuerCert
->isExpired() || issuerCert
->isNotValidYet()) {
162 * Exact value not important here, this just uniquely identifies
163 * this situation in the switch below.
165 tpDbDebug("tpDbFindIssuerCert: holding expired cert (1)");
166 crtn
= CSSM_CERT_STATUS_EXPIRED
;
167 expiredIssuer
= issuerCert
;
168 expiredIssuer
->dlDbHandle(dlDb
);
169 expiredIssuer
->uniqueRecord(record
);
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)
176 if((crtn
== CSSM_OK
) && (nonRootIssuer
== NULL
)) {
177 if(!issuerCert
->isSelfSigned()) {
179 * Exact value not important here, this just uniquely identifies
180 * this situation in the switch below.
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
);
192 case CSSMERR_CSP_APPLE_PUBLIC_KEY_INCOMPLETE
:
193 partialIssuerKey
= true;
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
) {
201 CSSM_DL_FreeUniqueRecord(dlDb
, record
);
207 * Continue searching this DB. Break on finding the holy
208 * grail or no more records found.
214 CSSM_RETURN crtn
= CSSM_DL_DataGetNext(dlDb
,
220 /* no more, done with this DB */
221 assert(cert
.Data
== NULL
);
224 assert(cert
.Data
!= NULL
);
225 tpDbDebug("tpDbFindIssuerCert: found cert record (2) %p", record
);
227 /* found one - does it verify subject? */
229 issuerCert
= new TPCertInfo(clHand
, cspHand
, &cert
, TIC_CopyData
,
233 crtn
= CSSMERR_TP_INVALID_CERTIFICATE
;
235 /* we're done with raw cert data */
236 tpFreePluginMemory(dlDb
.DLHandle
, cert
.Data
);
240 if(crtn
== CSSM_OK
) {
241 crtn
= subjectItem
->verifyWithIssuer(issuerCert
);
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
);
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
);
270 case CSSMERR_CSP_APPLE_PUBLIC_KEY_INCOMPLETE
:
271 partialIssuerKey
= true;
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
) {
286 CSSM_DL_FreeUniqueRecord(dlDb
, record
);
290 } /* searching subsequent records */
291 } /* switch verify */
294 /* NULL record --> end of search --> DB auto-aborted */
295 crtn
= CSSM_DL_DataAbortQuery(dlDb
, resultHand
);
296 assert(crtn
== CSSM_OK
);
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
;
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
;
318 } /* tpCertLookup, i.e., CSSM_DL_DataGetFirst, succeeded */
320 assert(cert
.Data
== NULL
);
321 assert(resultHand
== 0);
323 } /* main loop searching dbList */
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
;
333 return nonRootIssuer
;
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
;
342 /* issuer not found */
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.
350 #define SEARCH_BY_DATE 1
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
360 CSSM_SELECTION_PREDICATE pred
[3];
361 CSSM_DB_UNIQUE_RECORD_PTR record
= NULL
;
362 char timeStr
[CSSM_TIME_STRLEN
+ 1];
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;
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
);
381 tpErrorLog("tpCrlLookup: Invalid VerifyTime string\n");
387 StLock
<Mutex
> _(tpTimeLock());
388 timeAtNowPlus(0, TIME_CSSM
, timeStr
);
391 timeData
.Data
= (uint8
*)timeStr
;
392 timeData
.Length
= CSSM_TIME_STRLEN
;
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;
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;
410 query
.RecordType
= CSSM_DL_DB_RECORD_X509_CRL
;
411 query
.Conjunctive
= CSSM_DB_AND
;
413 query
.NumSelectionPredicates
= 3;
415 query
.NumSelectionPredicates
= 1;
417 query
.SelectionPredicate
= pred
;
418 query
.QueryLimits
.TimeLimit
= 0; // FIXME - meaningful?
419 query
.QueryLimits
.SizeLimit
= 1; // FIXME - meaningful?
420 query
.QueryFlags
= 0; // FIXME - used?
422 CSSM_DL_DataGetFirst(dlDb
,
425 NULL
, // don't fetch attributes
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.
437 TPCrlInfo
*tpDbFindIssuerCrl(
438 TPVerifyContext
&vfyCtx
,
439 const CSSM_DATA
&issuer
,
442 StLock
<Mutex
> _(SecTrustKeychainsGetMutex());
445 CSSM_HANDLE resultHand
;
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
;
456 for(dbDex
=0; dbDex
<dbList
->NumHandles
; dbDex
++) {
457 dlDb
= dbList
->DLDBHandle
[dbDex
];
460 record
= tpCrlLookup(dlDb
,
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)...
473 assert(crl
.Data
!= NULL
);
474 issuerCrl
= new TPCrlInfo(vfyCtx
.clHand
,
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
);
486 /* and we're done with the record */
487 CSSM_DL_FreeUniqueRecord(dlDb
, record
);
489 /* Does it verify with specified context? */
490 crtn
= issuerCrl
->verifyWithContextNow(vfyCtx
, &forCert
);
497 * Verify fail. Continue searching this DB. Break on
498 * finding the holy grail or no more records found.
503 crtn
= CSSM_DL_DataGetNext(dlDb
,
509 /* no more, done with this DB */
510 assert(crl
.Data
== NULL
);
513 assert(crl
.Data
!= NULL
);
515 /* found one - is it any good? */
516 issuerCrl
= new TPCrlInfo(vfyCtx
.clHand
,
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
);
528 CSSM_DL_FreeUniqueRecord(dlDb
, record
);
530 crtn
= issuerCrl
->verifyWithContextNow(vfyCtx
, &forCert
);
531 if(crtn
== CSSM_OK
) {
537 } /* searching subsequent records */
541 if(issuerCrl
!= NULL
) {
542 /* successful return */
543 CSSM_DL_DataAbortQuery(dlDb
, resultHand
);
544 tpDebug("tpDbFindIssuerCrl: found CRL record %p", record
);
547 } /* tpCrlLookup, i.e., CSSM_DL_DataGetFirst, succeeded */
549 assert(crl
.Data
== NULL
);
551 /* in any case, abort the query for this db */
552 CSSM_DL_DataAbortQuery(dlDb
, resultHand
);
554 } /* main loop searching dbList */
556 /* issuer not found */