X-Git-Url: https://git.saurik.com/apple/security.git/blobdiff_plain/80e2389990082500d76eb566d4946be3e786c3ef..d8f41ccd20de16f8ebe2ccc84d47bf1cb2b26bbb:/Security/libsecurity_apple_x509_tp/lib/tpCrlVerify.cpp diff --git a/Security/libsecurity_apple_x509_tp/lib/tpCrlVerify.cpp b/Security/libsecurity_apple_x509_tp/lib/tpCrlVerify.cpp new file mode 100644 index 00000000..346b97c7 --- /dev/null +++ b/Security/libsecurity_apple_x509_tp/lib/tpCrlVerify.cpp @@ -0,0 +1,470 @@ +/* + * Copyright (c) 2002-2012 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. + */ + + +/* + * tpCrlVerify.cpp - routines to verify CRLs and to verify certs against CRLs. + */ + +#include "tpCrlVerify.h" +#include "TPCertInfo.h" +#include "TPCrlInfo.h" +#include "tpOcspVerify.h" +#include "tpdebugging.h" +#include "TPNetwork.h" +#include "TPDatabase.h" +#include +#include +#include +#include +#include +#include +#include + +/* general purpose, switch to policy-specific code based on TPVerifyContext.policy */ +CSSM_RETURN tpRevocationPolicyVerify( + TPVerifyContext &tpVerifyContext, + TPCertGroup &certGroup) +{ + switch(tpVerifyContext.policy) { + case kRevokeNone: + return CSSM_OK; + case kRevokeCrlBasic: + return tpVerifyCertGroupWithCrls(tpVerifyContext, certGroup); + case kRevokeOcsp: + return tpVerifyCertGroupWithOCSP(tpVerifyContext, certGroup); + default: + assert(0); + return CSSMERR_TP_INTERNAL_ERROR; + } +} + +/* + * For now, a process-wide memory resident CRL cache. + * We are responsible for deleting the CRLs which get added to this + * cache. Currently the only time we add a CRL to this cache is + * when we fetch one from the net. We ref count CRLs in this cache + * to allow multi-threaded access. + * Entries do not persist past the tpVerifyCertGroupWithCrls() in + * which they were created unless another thread in the same + * process snags a refcount (also from tpVerifyCertGroupWithCrls()). + * I.e. when cert verification is complete the cache will be empty. + * This is a change from Tiger and previous. CRLs get pretty big, + * up to a megabyte or so, and it's just not worth it to keep those + * around in memory. (OCSP responses, which are much smaller than + * CRLs, are indeed cached in memory. See tpOcspCache.cpp.) + */ +class TPCRLCache : private TPCrlGroup +{ +public: + TPCRLCache(); + ~TPCRLCache() { } + TPCrlInfo *search( + TPCertInfo &cert, + TPVerifyContext &vfyCtx); + void add( + TPCrlInfo &crl); + void remove( + TPCrlInfo &crl); + void release( + TPCrlInfo &crl); + +private: + /* Protects ref count of all members of the cache */ + Mutex mLock; +}; + +TPCRLCache::TPCRLCache() + : TPCrlGroup(Allocator::standard(), TGO_Group) +{ + +} + +TPCrlInfo *TPCRLCache::search( + TPCertInfo &cert, + TPVerifyContext &vfyCtx) +{ + StLock _(mLock); + TPCrlInfo *crl = findCrlForCert(cert); + if(crl) { + /* reevaluate validity */ + crl->calculateCurrent(vfyCtx.verifyTime); + crl->mRefCount++; + tpCrlDebug("TPCRLCache hit"); + } + else { + tpCrlDebug("TPCRLCache miss"); + } + return crl; +} + +/* bumps ref count - caller is going to be using the CRL */ +void TPCRLCache::add( + TPCrlInfo &crl) +{ + StLock _(mLock); + tpCrlDebug("TPCRLCache add"); + crl.mRefCount++; + appendCrl(crl); +} + +/* delete and remove from cache if refCount zero */ +void TPCRLCache::release( + TPCrlInfo &crl) +{ + StLock _(mLock); + assert(crl.mRefCount > 0); + crl.mRefCount--; + if(crl.mRefCount == 0) { + tpCrlDebug("TPCRLCache release; deleting"); + removeCrl(crl); + delete &crl; + } + else { + tpCrlDebug("TPCRLCache release; in use"); + } +} + +static ModuleNexus tpGlobalCrlCache; + +/* + * Find CRL for specified cert. Only returns a fully verified CRL. + * Cert-specific errors such as CSSMERR_APPLETP_CRL_NOT_FOUND will be added + * to cert's return codes. + */ +static CSSM_RETURN tpFindCrlForCert( + TPCertInfo &subject, + TPCrlInfo *&foundCrl, // RETURNED + TPVerifyContext &vfyCtx) +{ + + tpCrlDebug("tpFindCrlForCert top"); + TPCrlInfo *crl = NULL; + foundCrl = NULL; + CSSM_APPLE_TP_CRL_OPT_FLAGS crlOptFlags = 0; + + if(vfyCtx.crlOpts) { + crlOptFlags = vfyCtx.crlOpts->CrlFlags; + } + + /* Search inputCrls for a CRL for subject cert */ + if(vfyCtx.inputCrls != NULL) { + crl = vfyCtx.inputCrls->findCrlForCert(subject); + if(crl && (crl->verifyWithContextNow(vfyCtx, &subject) == CSSM_OK)) { + foundCrl = crl; + crl->mFromWhere = CFW_InGroup; + tpCrlDebug(" ...CRL found in CrlGroup"); + return CSSM_OK; + } + } + + /* local process-wide cache */ + crl = tpGlobalCrlCache().search(subject, vfyCtx); + if(crl) { + tpCrlDebug("...tpFindCrlForCert found CRL in cache, calling verifyWithContext"); + if(crl->verifyWithContextNow(vfyCtx, &subject) == CSSM_OK) { + foundCrl = crl; + crl->mFromWhere = CFW_LocalCache; + tpCrlDebug(" ...CRL found in local cache"); + return CSSM_OK; + } + else { + tpGlobalCrlCache().release(*crl); + } + } + + /* + * Try DL/DB. + * Note tpDbFindIssuerCrl() returns a verified CRL. + */ + crl = tpDbFindIssuerCrl(vfyCtx, *subject.issuerName(), subject); + if(crl) { + foundCrl = crl; + crl->mFromWhere = CFW_DlDb; + tpCrlDebug(" ...CRL found in DlDb"); + return CSSM_OK; + } + + /* Last resort: try net if enabled */ + CSSM_RETURN crtn = CSSMERR_APPLETP_CRL_NOT_FOUND; + crl = NULL; + if(crlOptFlags & CSSM_TP_ACTION_FETCH_CRL_FROM_NET) { + crtn = tpFetchCrlFromNet(subject, vfyCtx, crl); + } + + if(crtn) { + tpCrlDebug(" ...tpFindCrlForCert: CRL not found"); + if(subject.addStatusCode(crtn)) { + return crtn; + } + else { + return CSSM_OK; + } + } + + /* got one from net - add to global cache */ + assert(crl != NULL); + tpGlobalCrlCache().add(*crl); + crl->mFromWhere = CFW_Net; + tpCrlDebug(" ...CRL found from net"); + + foundCrl = crl; + return CSSM_OK; +} + +/* + * Dispose of a CRL obtained from tpFindCrlForCert(). + */ +static void tpDisposeCrl( + TPCrlInfo &crl, + TPVerifyContext &vfyCtx) +{ + switch(crl.mFromWhere) { + case CFW_Nowhere: + default: + assert(0); + CssmError::throwMe(CSSMERR_TP_INTERNAL_ERROR); + case CFW_InGroup: + /* nothing to do, handled by TPCrlGroup */ + return; + case CFW_DlDb: + /* cooked up specially for this call */ + delete &crl; + return; + case CFW_LocalCache: // cache hit + case CFW_Net: // fetched from net & added to cache + tpGlobalCrlCache().release(crl); + return; + /* probably others */ + } +} + +/* + * Does this cert have a CrlDistributionPoints extension? We don't parse it, we + * just tell the caller whether or not it has one. + */ +static bool tpCertHasCrlDistPt( + TPCertInfo &cert) +{ + CSSM_DATA_PTR fieldValue; + CSSM_RETURN crtn = cert.fetchField(&CSSMOID_CrlDistributionPoints, &fieldValue); + if(crtn) { + return false; + } + else { + cert.freeField(&CSSMOID_CrlDistributionPoints, fieldValue); + return true; + } +} + +/* + * Get current CRL status for a certificate and its issuers. + * + * Possible results: + * + * CSSM_OK (we have a valid CRL; certificate is not revoked) + * CSSMERR_TP_CERT_REVOKED (we have a valid CRL; certificate is revoked) + * CSSMERR_APPLETP_NETWORK_FAILURE (CRL not available, download in progress) + * CSSMERR_APPLETP_CRL_NOT_FOUND (CRL not available, and not being fetched) + * CSSMERR_TP_INTERNAL_ERROR (unexpected error) + * + * Note that ocspdCRLStatus does NOT wait for the CRL to be downloaded before + * returning, nor does it initiate a CRL download. + */ +static +CSSM_RETURN tpGetCrlStatusForCert( + TPCertInfo &subject, + const CSSM_DATA &issuers) +{ + CSSM_DATA *serialNumber=NULL; + CSSM_RETURN crtn = subject.fetchField(&CSSMOID_X509V1SerialNumber, &serialNumber); + if(crtn || !serialNumber) { + return CSSMERR_TP_INTERNAL_ERROR; + } + crtn = ocspdCRLStatus(*serialNumber, issuers, subject.issuerName(), NULL); + subject.freeField(&CSSMOID_X509V1SerialNumber, serialNumber); + return crtn; +} + +/* + * Perform CRL verification on a cert group. + * The cert group has already passed basic issuer/subject and signature + * verification. The status of the incoming CRLs is completely unknown. + * + * FIXME - No mechanism to get CRLs from net with non-NULL verifyTime. + * How are we supposed to get the CRL which was valid at a specified + * time in the past? + */ +CSSM_RETURN tpVerifyCertGroupWithCrls( + TPVerifyContext &vfyCtx, + TPCertGroup &certGroup) // to be verified +{ + CSSM_RETURN crtn; + CSSM_RETURN ourRtn = CSSM_OK; + + assert(vfyCtx.clHand != 0); + assert(vfyCtx.policy == kRevokeCrlBasic); + tpCrlDebug("tpVerifyCertGroupWithCrls numCerts %u", certGroup.numCerts()); + CSSM_DATA issuers = { 0, NULL }; + CSSM_APPLE_TP_CRL_OPT_FLAGS optFlags = 0; + if(vfyCtx.crlOpts != NULL) { + optFlags = vfyCtx.crlOpts->CrlFlags; + } + + /* found & verified CRLs we need to release */ + TPCrlGroup foundCrls(vfyCtx.alloc, TGO_Caller); + + try { + + unsigned certDex; + TPCrlInfo *crl = NULL; + + /* get issuers as PEM-encoded data blob; we need to release */ + certGroup.encodeIssuers(issuers); + + /* main loop, verify each cert */ + for(certDex=0; certDexisAnchor() ? "anchor " : "", cert->index()); + if(cert->isSelfSigned() || cert->trustSettingsFound()) { + /* CRL meaningless for a root or trusted cert */ + continue; + } + if(cert->revokeCheckComplete()) { + /* Another revocation policy claimed that this cert is good to go */ + tpCrlDebug(" ...cert at index %u revokeCheckComplete; skipping", + cert->index()); + continue; + } + crl = NULL; + do { + /* first, see if we have CRL status available for this cert */ + crtn = tpGetCrlStatusForCert(*cert, issuers); + tpCrlDebug("...tpGetCrlStatusForCert: %u", crtn); + if(crtn == CSSM_OK) { + tpCrlDebug("tpVerifyCertGroupWithCrls: cert %u verified by local .crl\n", + cert->index()); + cert->revokeCheckGood(true); + if(optFlags & CSSM_TP_ACTION_CRL_SUFFICIENT) { + /* no more revocation checking necessary for this cert */ + cert->revokeCheckComplete(true); + } + break; + } + if(crtn == CSSMERR_TP_CERT_REVOKED) { + tpCrlDebug("tpVerifyCertGroupWithCrls: cert %u revoked in local .crl\n", + cert->index()); + cert->addStatusCode(crtn); + break; + } + if(crtn == CSSMERR_APPLETP_NETWORK_FAILURE) { + /* crl is being fetched from net, but we don't have it yet */ + if((optFlags & CSSM_TP_ACTION_REQUIRE_CRL_IF_PRESENT) && + tpCertHasCrlDistPt(*cert)) { + /* crl is required; we don't have it yet, so we fail */ + tpCrlDebug(" ...cert %u: REQUIRE_CRL_IF_PRESENT abort", + cert->index()); + break; + } + /* "Best Attempt" case, so give the cert a pass for now */ + tpCrlDebug(" ...cert %u: no CRL; tolerating", cert->index()); + crtn = CSSM_OK; + break; + } + /* all other CRL status results: try to fetch the CRL */ + + /* find a CRL for this cert by hook or crook */ + crtn = tpFindCrlForCert(*cert, crl, vfyCtx); + if(crtn) { + /* tpFindCrlForCert may have simply caused ocspd to start + * downloading a CRL asynchronously; depending on the speed + * of the network and the CRL size, this may return 0 bytes + * of data with a CSSMERR_APPLETP_NETWORK_FAILURE result. + * We won't know the actual revocation result until the + * next time we call tpGetCrlStatusForCert after the full + * CRL has been downloaded successfully. + */ + if(optFlags & CSSM_TP_ACTION_REQUIRE_CRL_PER_CERT) { + tpCrlDebug(" ...cert %u: REQUIRE_CRL_PER_CERT abort", + cert->index()); + break; + } + if((optFlags & CSSM_TP_ACTION_REQUIRE_CRL_IF_PRESENT) && + tpCertHasCrlDistPt(*cert)) { + tpCrlDebug(" ...cert %u: REQUIRE_CRL_IF_PRESENT abort", + cert->index()); + break; + } + /* + * This is the only place where "Best Attempt" tolerates an error + */ + tpCrlDebug(" ...cert %u: no CRL; tolerating", cert->index()); + crtn = CSSM_OK; + assert(crl == NULL); + break; + } + + /* Keep track; we'll release all when done. */ + assert(crl != NULL); + foundCrls.appendCrl(*crl); + + /* revoked? */ + crtn = crl->isCertRevoked(*cert, vfyCtx.verifyTime); + if(crtn) { + break; + } + tpCrlDebug(" ...cert %u VERIFIED by CRL", cert->index()); + cert->revokeCheckGood(true); + if(optFlags & CSSM_TP_ACTION_CRL_SUFFICIENT) { + /* no more revocation checking necessary for this cert */ + cert->revokeCheckComplete(true); + } + } while(0); + + /* done processing one cert */ + if(crtn) { + tpCrlDebug(" ...cert at index %u FAILED crl vfy", + cert->index()); + if(ourRtn == CSSM_OK) { + ourRtn = crtn; + } + /* continue on to next cert */ + } /* error on one cert */ + } /* for each cert */ + } + catch(const CssmError &cerr) { + if(ourRtn == CSSM_OK) { + ourRtn = cerr.error; + } + } + /* other exceptions fatal */ + + /* release all found CRLs */ + for(unsigned dex=0; dex