--- /dev/null
+/*
+ * Copyright (c) 2002-2009,2011-2012,2014 Apple Inc. All Rights Reserved.
+ *
+ * The contents of this file constitute Original Code as defined in and are
+ * subject to the Apple Public Source License Version 1.2 (the 'License').
+ * You may not use this file except in compliance with the License. Please obtain
+ * a copy of the License at http://www.apple.com/publicsource and read it before
+ * using this file.
+ *
+ * This Original Code and all software distributed under the License are
+ * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS
+ * OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, INCLUDING WITHOUT
+ * LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+ * PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. Please see the License for the
+ * specific language governing rights and limitations under the License.
+ */
+
+
+/*
+ * TPDatabase.cpp - TP's DL/DB access functions.
+ *
+ */
+
+#include <Security/cssmtype.h>
+#include <Security/cssmapi.h>
+#include <security_cdsa_utilities/Schema.h> /* private API */
+#include <security_keychain/TrustKeychains.h> /* private SecTrustKeychainsGetMutex() */
+#include <Security/SecCertificatePriv.h> /* private SecInferLabelFromX509Name() */
+#include <Security/oidscert.h>
+#include "TPDatabase.h"
+#include "tpdebugging.h"
+#include "certGroupUtils.h"
+#include "TPCertInfo.h"
+#include "TPCrlInfo.h"
+#include "tpCrlVerify.h"
+#include "tpTime.h"
+
+
+/*
+ * Given a DL/DB, look up cert by subject name. Subsequent
+ * certs can be found using the returned result handle.
+ */
+static CSSM_DB_UNIQUE_RECORD_PTR tpCertLookup(
+ CSSM_DL_DB_HANDLE dlDb,
+ const CSSM_DATA *subjectName, // DER-encoded
+ CSSM_HANDLE_PTR resultHand, // RETURNED
+ CSSM_DATA_PTR cert) // RETURNED
+{
+ CSSM_QUERY query;
+ CSSM_SELECTION_PREDICATE predicate;
+ CSSM_DB_UNIQUE_RECORD_PTR record = NULL;
+
+ cert->Data = NULL;
+ cert->Length = 0;
+
+ /* SWAG until cert schema nailed down */
+ predicate.DbOperator = CSSM_DB_EQUAL;
+ predicate.Attribute.Info.AttributeNameFormat =
+ CSSM_DB_ATTRIBUTE_NAME_AS_STRING;
+ predicate.Attribute.Info.Label.AttributeName = (char*) "Subject";
+ predicate.Attribute.Info.AttributeFormat = CSSM_DB_ATTRIBUTE_FORMAT_BLOB;
+ predicate.Attribute.Value = const_cast<CSSM_DATA_PTR>(subjectName);
+ predicate.Attribute.NumberOfValues = 1;
+
+ query.RecordType = CSSM_DL_DB_RECORD_X509_CERTIFICATE;
+ query.Conjunctive = CSSM_DB_NONE;
+ query.NumSelectionPredicates = 1;
+ query.SelectionPredicate = &predicate;
+ query.QueryLimits.TimeLimit = 0; // FIXME - meaningful?
+ query.QueryLimits.SizeLimit = 1; // FIXME - meaningful?
+ query.QueryFlags = 0; // FIXME - used?
+
+ CSSM_DL_DataGetFirst(dlDb,
+ &query,
+ resultHand,
+ NULL, // don't fetch attributes
+ cert,
+ &record);
+
+ return record;
+}
+
+/*
+ * Search a list of DBs for a cert which verifies specified subject item.
+ * Just a boolean return - we found it, or not. If we did, we return
+ * TPCertInfo associated with the raw cert.
+ * A true partialIssuerKey on return indicates that caller must deal
+ * with partial public key processing later.
+ * If verifyCurrent is true, we will not return a cert which is not
+ * temporally valid; else we may well do so.
+ */
+TPCertInfo *tpDbFindIssuerCert(
+ Allocator &alloc,
+ CSSM_CL_HANDLE clHand,
+ CSSM_CSP_HANDLE cspHand,
+ const TPClItemInfo *subjectItem,
+ const CSSM_DL_DB_LIST *dbList,
+ const char *verifyTime, // may be NULL
+ bool &partialIssuerKey, // RETURNED
+ TPCertInfo *oldRoot)
+{
+ StLock<Mutex> _(SecTrustKeychainsGetMutex());
+
+ uint32 dbDex;
+ CSSM_HANDLE resultHand;
+ CSSM_DATA cert;
+ CSSM_DL_DB_HANDLE dlDb;
+ CSSM_DB_UNIQUE_RECORD_PTR record;
+ TPCertInfo *issuerCert = NULL;
+ bool foundIt;
+ TPCertInfo *expiredIssuer = NULL;
+ TPCertInfo *nonRootIssuer = NULL;
+
+ partialIssuerKey = false;
+ if(dbList == NULL) {
+ return NULL;
+ }
+ for(dbDex=0; dbDex<dbList->NumHandles; dbDex++) {
+ dlDb = dbList->DLDBHandle[dbDex];
+ cert.Data = NULL;
+ cert.Length = 0;
+ resultHand = 0;
+ record = tpCertLookup(dlDb,
+ subjectItem->issuerName(),
+ &resultHand,
+ &cert);
+ /* remember we have to:
+ * -- abort this query regardless, and
+ * -- free the CSSM_DATA cert regardless, and
+ * -- free the unique record if we don't use it
+ * (by placing it in issuerCert)...
+ */
+ if(record != NULL) {
+ /* Found one */
+ assert(cert.Data != NULL);
+ tpDbDebug("tpDbFindIssuerCert: found cert record (1) %p", record);
+ issuerCert = NULL;
+ CSSM_RETURN crtn = CSSM_OK;
+ try {
+ issuerCert = new TPCertInfo(clHand, cspHand, &cert, TIC_CopyData, verifyTime);
+ }
+ catch(...) {
+ crtn = CSSMERR_TP_INVALID_CERTIFICATE;
+ }
+
+ /* we're done with raw cert data */
+ tpFreePluginMemory(dlDb.DLHandle, cert.Data);
+ cert.Data = NULL;
+ cert.Length = 0;
+
+ /* Does it verify the subject cert? */
+ if(crtn == CSSM_OK) {
+ crtn = subjectItem->verifyWithIssuer(issuerCert);
+ }
+
+ /*
+ * Handle temporal invalidity - if so and this is the first one
+ * we've seen, hold on to it while we search for better one.
+ */
+ if((crtn == CSSM_OK) && (expiredIssuer == NULL)) {
+ if(issuerCert->isExpired() || issuerCert->isNotValidYet()) {
+ /*
+ * Exact value not important here, this just uniquely identifies
+ * this situation in the switch below.
+ */
+ tpDbDebug("tpDbFindIssuerCert: holding expired cert (1)");
+ crtn = CSSM_CERT_STATUS_EXPIRED;
+ expiredIssuer = issuerCert;
+ expiredIssuer->dlDbHandle(dlDb);
+ expiredIssuer->uniqueRecord(record);
+ }
+ }
+ /*
+ * Prefer a root over an intermediate issuer if we can get one
+ * (in case a cross-signed intermediate and root are both available)
+ */
+ if((crtn == CSSM_OK) && (nonRootIssuer == NULL)) {
+ if(!issuerCert->isSelfSigned()) {
+ /*
+ * Exact value not important here, this just uniquely identifies
+ * this situation in the switch below.
+ */
+ tpDbDebug("tpDbFindIssuerCert: holding non-root cert (1)");
+ crtn = CSSM_CERT_STATUS_IS_ROOT;
+ nonRootIssuer = issuerCert;
+ nonRootIssuer->dlDbHandle(dlDb);
+ nonRootIssuer->uniqueRecord(record);
+ }
+ }
+ switch(crtn) {
+ case CSSMERR_CSP_APPLE_PUBLIC_KEY_INCOMPLETE:
+ partialIssuerKey = true;
+ break;
+ case CSSM_OK:
+ if((oldRoot == NULL) ||
+ !tp_CompareCerts(issuerCert->itemData(), oldRoot->itemData())) {
+ /* We found a new root cert which does not match the old one */
+ break;
+ }
+ /* else fall through to search for a different one */
+ default:
+ if(issuerCert != NULL) {
+ /* either holding onto this cert, or done with it. */
+ if(crtn != CSSM_CERT_STATUS_EXPIRED &&
+ crtn != CSSM_CERT_STATUS_IS_ROOT) {
+ delete issuerCert;
+ CSSM_DL_FreeUniqueRecord(dlDb, record);
+ }
+ issuerCert = NULL;
+ }
+
+ /*
+ * Continue searching this DB. Break on finding the holy
+ * grail or no more records found.
+ */
+ for(;;) {
+ cert.Data = NULL;
+ cert.Length = 0;
+ record = NULL;
+ CSSM_RETURN crtn = CSSM_DL_DataGetNext(dlDb,
+ resultHand,
+ NULL, // no attrs
+ &cert,
+ &record);
+ if(crtn) {
+ /* no more, done with this DB */
+ assert(cert.Data == NULL);
+ break;
+ }
+ assert(cert.Data != NULL);
+ tpDbDebug("tpDbFindIssuerCert: found cert record (2) %p", record);
+
+ /* found one - does it verify subject? */
+ try {
+ issuerCert = new TPCertInfo(clHand, cspHand, &cert, TIC_CopyData,
+ verifyTime);
+ }
+ catch(...) {
+ crtn = CSSMERR_TP_INVALID_CERTIFICATE;
+ }
+ /* we're done with raw cert data */
+ tpFreePluginMemory(dlDb.DLHandle, cert.Data);
+ cert.Data = NULL;
+ cert.Length = 0;
+
+ if(crtn == CSSM_OK) {
+ crtn = subjectItem->verifyWithIssuer(issuerCert);
+ }
+
+ /* temporal validity check, again */
+ if((crtn == CSSM_OK) && (expiredIssuer == NULL)) {
+ if(issuerCert->isExpired() || issuerCert->isNotValidYet()) {
+ tpDbDebug("tpDbFindIssuerCert: holding expired cert (2)");
+ crtn = CSSM_CERT_STATUS_EXPIRED;
+ expiredIssuer = issuerCert;
+ expiredIssuer->dlDbHandle(dlDb);
+ expiredIssuer->uniqueRecord(record);
+ }
+ }
+ /* self-signed check, again */
+ if((crtn == CSSM_OK) && (nonRootIssuer == NULL)) {
+ if(!issuerCert->isSelfSigned()) {
+ tpDbDebug("tpDbFindIssuerCert: holding non-root cert (2)");
+ crtn = CSSM_CERT_STATUS_IS_ROOT;
+ nonRootIssuer = issuerCert;
+ nonRootIssuer->dlDbHandle(dlDb);
+ nonRootIssuer->uniqueRecord(record);
+ }
+ }
+
+ foundIt = false;
+ switch(crtn) {
+ case CSSM_OK:
+ /* duplicate check, again */
+ if((oldRoot == NULL) ||
+ !tp_CompareCerts(issuerCert->itemData(), oldRoot->itemData())) {
+ foundIt = true;
+ }
+ break;
+ case CSSMERR_CSP_APPLE_PUBLIC_KEY_INCOMPLETE:
+ partialIssuerKey = true;
+ foundIt = true;
+ break;
+ default:
+ break;
+ }
+ if(foundIt) {
+ /* yes! */
+ break;
+ }
+ if(issuerCert != NULL) {
+ /* either holding onto this cert, or done with it. */
+ if(crtn != CSSM_CERT_STATUS_EXPIRED &&
+ crtn != CSSM_CERT_STATUS_IS_ROOT) {
+ delete issuerCert;
+ CSSM_DL_FreeUniqueRecord(dlDb, record);
+ }
+ issuerCert = NULL;
+ }
+ } /* searching subsequent records */
+ } /* switch verify */
+
+ if(record != NULL) {
+ /* NULL record --> end of search --> DB auto-aborted */
+ crtn = CSSM_DL_DataAbortQuery(dlDb, resultHand);
+ assert(crtn == CSSM_OK);
+ }
+ if(issuerCert != NULL) {
+ /* successful return */
+ tpDbDebug("tpDbFindIssuer: returning record %p", record);
+ issuerCert->dlDbHandle(dlDb);
+ issuerCert->uniqueRecord(record);
+ if(expiredIssuer != NULL) {
+ /* We found a replacement */
+ tpDbDebug("tpDbFindIssuer: discarding expired cert");
+ expiredIssuer->freeUniqueRecord();
+ delete expiredIssuer;
+ }
+ /* Avoid deleting the non-root cert if same as expired cert */
+ if(nonRootIssuer != NULL && nonRootIssuer != expiredIssuer) {
+ /* We found a replacement */
+ tpDbDebug("tpDbFindIssuer: discarding non-root cert");
+ nonRootIssuer->freeUniqueRecord();
+ delete nonRootIssuer;
+ }
+ return issuerCert;
+ }
+ } /* tpCertLookup, i.e., CSSM_DL_DataGetFirst, succeeded */
+ else {
+ assert(cert.Data == NULL);
+ assert(resultHand == 0);
+ }
+ } /* main loop searching dbList */
+
+ if(nonRootIssuer != NULL) {
+ /* didn't find root issuer, so use this one */
+ tpDbDebug("tpDbFindIssuer: taking non-root issuer cert, record %p",
+ nonRootIssuer->uniqueRecord());
+ if(expiredIssuer != NULL && expiredIssuer != nonRootIssuer) {
+ expiredIssuer->freeUniqueRecord();
+ delete expiredIssuer;
+ }
+ return nonRootIssuer;
+ }
+
+ if(expiredIssuer != NULL) {
+ /* OK, we'll take this one */
+ tpDbDebug("tpDbFindIssuer: taking expired cert after all, record %p",
+ expiredIssuer->uniqueRecord());
+ return expiredIssuer;
+ }
+ /* issuer not found */
+ return NULL;
+}
+
+/*
+ * Given a DL/DB, look up CRL by issuer name and validity time.
+ * Subsequent CRLs can be found using the returned result handle.
+ */
+#define SEARCH_BY_DATE 1
+
+static CSSM_DB_UNIQUE_RECORD_PTR tpCrlLookup(
+ CSSM_DL_DB_HANDLE dlDb,
+ const CSSM_DATA *issuerName, // DER-encoded
+ CSSM_TIMESTRING verifyTime, // may be NULL, implies "now"
+ CSSM_HANDLE_PTR resultHand, // RETURNED
+ CSSM_DATA_PTR crl) // RETURNED
+{
+ CSSM_QUERY query;
+ CSSM_SELECTION_PREDICATE pred[3];
+ CSSM_DB_UNIQUE_RECORD_PTR record = NULL;
+ char timeStr[CSSM_TIME_STRLEN + 1];
+
+ crl->Data = NULL;
+ crl->Length = 0;
+
+ /* Three predicates...first, the issuer name */
+ pred[0].DbOperator = CSSM_DB_EQUAL;
+ pred[0].Attribute.Info.AttributeNameFormat =
+ CSSM_DB_ATTRIBUTE_NAME_AS_STRING;
+ pred[0].Attribute.Info.Label.AttributeName = (char*) "Issuer";
+ pred[0].Attribute.Info.AttributeFormat = CSSM_DB_ATTRIBUTE_FORMAT_BLOB;
+ pred[0].Attribute.Value = const_cast<CSSM_DATA_PTR>(issuerName);
+ pred[0].Attribute.NumberOfValues = 1;
+
+ /* now before/after. Cook up an appropriate time string. */
+ if(verifyTime != NULL) {
+ /* Caller spec'd tolerate any format */
+ int rtn = tpTimeToCssmTimestring(verifyTime, (unsigned)strlen(verifyTime), timeStr);
+ if(rtn) {
+ tpErrorLog("tpCrlLookup: Invalid VerifyTime string\n");
+ return NULL;
+ }
+ }
+ else {
+ /* right now */
+ StLock<Mutex> _(tpTimeLock());
+ timeAtNowPlus(0, TIME_CSSM, timeStr);
+ }
+ CSSM_DATA timeData;
+ timeData.Data = (uint8 *)timeStr;
+ timeData.Length = CSSM_TIME_STRLEN;
+
+ #if SEARCH_BY_DATE
+ pred[1].DbOperator = CSSM_DB_LESS_THAN;
+ pred[1].Attribute.Info.AttributeNameFormat = CSSM_DB_ATTRIBUTE_NAME_AS_STRING;
+ pred[1].Attribute.Info.Label.AttributeName = (char*) "NextUpdate";
+ pred[1].Attribute.Info.AttributeFormat = CSSM_DB_ATTRIBUTE_FORMAT_BLOB;
+ pred[1].Attribute.Value = &timeData;
+ pred[1].Attribute.NumberOfValues = 1;
+
+ pred[2].DbOperator = CSSM_DB_GREATER_THAN;
+ pred[2].Attribute.Info.AttributeNameFormat = CSSM_DB_ATTRIBUTE_NAME_AS_STRING;
+ pred[2].Attribute.Info.Label.AttributeName = (char*) "ThisUpdate";
+ pred[2].Attribute.Info.AttributeFormat = CSSM_DB_ATTRIBUTE_FORMAT_BLOB;
+ pred[2].Attribute.Value = &timeData;
+ pred[2].Attribute.NumberOfValues = 1;
+ #endif
+
+ query.RecordType = CSSM_DL_DB_RECORD_X509_CRL;
+ query.Conjunctive = CSSM_DB_AND;
+ #if SEARCH_BY_DATE
+ query.NumSelectionPredicates = 3;
+ #else
+ query.NumSelectionPredicates = 1;
+ #endif
+ query.SelectionPredicate = pred;
+ query.QueryLimits.TimeLimit = 0; // FIXME - meaningful?
+ query.QueryLimits.SizeLimit = 1; // FIXME - meaningful?
+ query.QueryFlags = 0; // FIXME - used?
+
+ CSSM_DL_DataGetFirst(dlDb,
+ &query,
+ resultHand,
+ NULL, // don't fetch attributes
+ crl,
+ &record);
+ return record;
+}
+
+/*
+ * Search a list of DBs for a CRL from the specified issuer and (optional)
+ * TPVerifyContext.verifyTime.
+ * Just a boolean return - we found it, or not. If we did, we return a
+ * TPCrlInfo which has been verified with the specified TPVerifyContext.
+ */
+TPCrlInfo *tpDbFindIssuerCrl(
+ TPVerifyContext &vfyCtx,
+ const CSSM_DATA &issuer,
+ TPCertInfo &forCert)
+{
+ StLock<Mutex> _(SecTrustKeychainsGetMutex());
+
+ uint32 dbDex;
+ CSSM_HANDLE resultHand;
+ CSSM_DATA crl;
+ CSSM_DL_DB_HANDLE dlDb;
+ CSSM_DB_UNIQUE_RECORD_PTR record;
+ TPCrlInfo *issuerCrl = NULL;
+ CSSM_DL_DB_LIST_PTR dbList = vfyCtx.dbList;
+ CSSM_RETURN crtn;
+
+ if(dbList == NULL) {
+ return NULL;
+ }
+ for(dbDex=0; dbDex<dbList->NumHandles; dbDex++) {
+ dlDb = dbList->DLDBHandle[dbDex];
+ crl.Data = NULL;
+ crl.Length = 0;
+ record = tpCrlLookup(dlDb,
+ &issuer,
+ vfyCtx.verifyTime,
+ &resultHand,
+ &crl);
+ /* remember we have to:
+ * -- abort this query regardless, and
+ * -- free the CSSM_DATA crl regardless, and
+ * -- free the unique record if we don't use it
+ * (by placing it in issuerCert)...
+ */
+ if(record != NULL) {
+ /* Found one */
+ assert(crl.Data != NULL);
+ issuerCrl = new TPCrlInfo(vfyCtx.clHand,
+ vfyCtx.cspHand,
+ &crl,
+ TIC_CopyData,
+ vfyCtx.verifyTime);
+ /* we're done with raw CRL data */
+ /* FIXME this assumes that vfyCtx.alloc is the same as the
+ * allocator associated with DlDB...OK? */
+ tpFreeCssmData(vfyCtx.alloc, &crl, CSSM_FALSE);
+ crl.Data = NULL;
+ crl.Length = 0;
+
+ /* and we're done with the record */
+ CSSM_DL_FreeUniqueRecord(dlDb, record);
+
+ /* Does it verify with specified context? */
+ crtn = issuerCrl->verifyWithContextNow(vfyCtx, &forCert);
+ if(crtn) {
+
+ delete issuerCrl;
+ issuerCrl = NULL;
+
+ /*
+ * Verify fail. Continue searching this DB. Break on
+ * finding the holy grail or no more records found.
+ */
+ for(;;) {
+ crl.Data = NULL;
+ crl.Length = 0;
+ crtn = CSSM_DL_DataGetNext(dlDb,
+ resultHand,
+ NULL, // no attrs
+ &crl,
+ &record);
+ if(crtn) {
+ /* no more, done with this DB */
+ assert(crl.Data == NULL);
+ break;
+ }
+ assert(crl.Data != NULL);
+
+ /* found one - is it any good? */
+ issuerCrl = new TPCrlInfo(vfyCtx.clHand,
+ vfyCtx.cspHand,
+ &crl,
+ TIC_CopyData,
+ vfyCtx.verifyTime);
+ /* we're done with raw CRL data */
+ /* FIXME this assumes that vfyCtx.alloc is the same as the
+ * allocator associated with DlDB...OK? */
+ tpFreeCssmData(vfyCtx.alloc, &crl, CSSM_FALSE);
+ crl.Data = NULL;
+ crl.Length = 0;
+
+ CSSM_DL_FreeUniqueRecord(dlDb, record);
+
+ crtn = issuerCrl->verifyWithContextNow(vfyCtx, &forCert);
+ if(crtn == CSSM_OK) {
+ /* yes! */
+ break;
+ }
+ delete issuerCrl;
+ issuerCrl = NULL;
+ } /* searching subsequent records */
+ } /* verify fail */
+ /* else success! */
+
+ if(issuerCrl != NULL) {
+ /* successful return */
+ CSSM_DL_DataAbortQuery(dlDb, resultHand);
+ tpDebug("tpDbFindIssuerCrl: found CRL record %p", record);
+ return issuerCrl;
+ }
+ } /* tpCrlLookup, i.e., CSSM_DL_DataGetFirst, succeeded */
+ else {
+ assert(crl.Data == NULL);
+ }
+ /* in any case, abort the query for this db */
+ CSSM_DL_DataAbortQuery(dlDb, resultHand);
+
+ } /* main loop searching dbList */
+
+ /* issuer not found */
+ return NULL;
+}
+