--- /dev/null
+/*
+ * 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 <CommonCrypto/CommonDigest.h>
+#include <Security/oidscert.h>
+#include <security_ocspd/ocspdClient.h>
+#include <security_utilities/globalizer.h>
+#include <security_utilities/threading.h>
+#include <security_cdsa_utilities/cssmerrors.h>
+#include <sys/stat.h>
+
+/* 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<Mutex> _(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<Mutex> _(mLock);
+ tpCrlDebug("TPCRLCache add");
+ crl.mRefCount++;
+ appendCrl(crl);
+}
+
+/* delete and remove from cache if refCount zero */
+void TPCRLCache::release(
+ TPCrlInfo &crl)
+{
+ StLock<Mutex> _(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<TPCRLCache> 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; certDex<certGroup.numCerts(); certDex++) {
+ TPCertInfo *cert = certGroup.certAtIndex(certDex);
+
+ tpCrlDebug("...verifying %s cert %u",
+ cert->isAnchor() ? "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<foundCrls.numCrls(); dex++) {
+ TPCrlInfo *crl = foundCrls.crlAtIndex(dex);
+ assert(crl != NULL);
+ tpDisposeCrl(*crl, vfyCtx);
+ }
+ /* release issuers */
+ if(issuers.Data) {
+ free(issuers.Data);
+ }
+ return ourRtn;
+}
+