/* * Copyright (c) 2002-2009 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. * * Created 10/9/2002 by Doug Mitchell. */ #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; 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 %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); } } switch(crtn) { case CSSM_OK: break; case CSSMERR_CSP_APPLE_PUBLIC_KEY_INCOMPLETE: partialIssuerKey = true; break; default: issuerCert = NULL; if(crtn != CSSM_CERT_STATUS_EXPIRED) { delete issuerCert; CSSM_DL_FreeUniqueRecord(dlDb, record); } /* * 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 %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); } } 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(crtn != CSSM_CERT_STATUS_EXPIRED) { 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; } return issuerCert; } } /* tpCertLookup, i.e., CSSM_DL_DataGetFirst, succeeded */ else { assert(cert.Data == NULL); assert(resultHand == 0); } } /* main loop searching dbList */ 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, 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; }