X-Git-Url: https://git.saurik.com/apple/security.git/blobdiff_plain/80e2389990082500d76eb566d4946be3e786c3ef..d8f41ccd20de16f8ebe2ccc84d47bf1cb2b26bbb:/Security/libsecurity_apple_x509_tp/lib/TPDatabase.cpp?ds=sidebyside diff --git a/Security/libsecurity_apple_x509_tp/lib/TPDatabase.cpp b/Security/libsecurity_apple_x509_tp/lib/TPDatabase.cpp new file mode 100644 index 00000000..583b7c10 --- /dev/null +++ b/Security/libsecurity_apple_x509_tp/lib/TPDatabase.cpp @@ -0,0 +1,559 @@ +/* + * 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 +#include +#include /* private API */ +#include /* private SecTrustKeychainsGetMutex() */ +#include /* private SecInferLabelFromX509Name() */ +#include +#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(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 +{ + StLock _(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; dbDexNumHandles; 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 CSSM_OK: + break; + case CSSMERR_CSP_APPLE_PUBLIC_KEY_INCOMPLETE: + partialIssuerKey = true; + break; + 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: + 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(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 _(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 _(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; dbDexNumHandles; 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; +} +