/* * 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