--- /dev/null
+/*
+ * Copyright (c) 2004,2011-2012,2014 Apple Inc. All Rights Reserved.
+ *
+ * @APPLE_LICENSE_HEADER_START@
+ *
+ * This file contains Original Code and/or Modifications of Original Code
+ * as defined in and that are subject to the Apple Public Source License
+ * Version 2.0 (the 'License'). You may not use this file except in
+ * compliance with the License. Please obtain a copy of the License at
+ * http://www.opensource.apple.com/apsl/ and read it before using this
+ * file.
+ *
+ * The 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.
+ *
+ * @APPLE_LICENSE_HEADER_END@
+ */
+
+/*
+ * tpOcspVerify.cpp - top-level OCSP verification
+ */
+
+#include "tpOcspVerify.h"
+#include "tpdebugging.h"
+#include "ocspRequest.h"
+#include "tpOcspCache.h"
+#include "tpOcspCertVfy.h"
+#include <security_ocspd/ocspResponse.h>
+#include "certGroupUtils.h"
+#include <Security/certextensions.h>
+#include <Security/oidsattr.h>
+#include <Security/oidscert.h>
+#include <security_asn1/SecNssCoder.h>
+#include <security_ocspd/ocspdClient.h>
+#include <security_ocspd/ocspdUtils.h>
+#include "tpTime.h"
+
+#pragma mark ---- private routines ----
+
+/*
+ * Get a smart CertID for specified cert and issuer
+ */
+static CSSM_RETURN tpOcspGetCertId(
+ TPCertInfo &subject,
+ TPCertInfo &issuer,
+ OCSPClientCertID *&certID) /* mallocd by coder and RETURNED */
+{
+ CSSM_RETURN crtn;
+ CSSM_DATA_PTR issuerSubject = NULL;
+ CSSM_DATA_PTR issuerPubKeyData = NULL;
+ CSSM_KEY_PTR issuerPubKey;
+ CSSM_DATA_PTR subjectSerial = NULL;
+
+ crtn = subject.fetchField(&CSSMOID_X509V1SerialNumber, &subjectSerial);
+ if(crtn) {
+ return crtn;
+ }
+ crtn = subject.fetchField(&CSSMOID_X509V1IssuerNameStd, &issuerSubject);
+ if(crtn) {
+ return crtn;
+ }
+ crtn = issuer.fetchField(&CSSMOID_CSSMKeyStruct, &issuerPubKeyData);
+ if(crtn) {
+ return crtn;
+ }
+ assert(issuerPubKeyData->Length == sizeof(CSSM_KEY));
+ issuerPubKey = (CSSM_KEY_PTR)issuerPubKeyData->Data;
+ certID = new OCSPClientCertID(*issuerSubject, issuerPubKey->KeyData, *subjectSerial);
+
+ subject.freeField(&CSSMOID_X509V1SerialNumber, subjectSerial);
+ issuer.freeField(&CSSMOID_X509V1IssuerNameStd, issuerSubject);
+ issuer.freeField(&CSSMOID_CSSMKeyStruct, issuerPubKeyData);
+ return CSSM_OK;
+}
+
+/*
+ * Examine cert, looking for AuthorityInfoAccess, with id-ad-ocsp URIs. Create
+ * an NULL_terminated array of CSSM_DATAs containing the URIs if found.
+ */
+static CSSM_DATA **tpOcspUrlsFromCert(
+ TPCertInfo &subject,
+ SecNssCoder &coder)
+{
+ CSSM_DATA_PTR extField = NULL;
+ CSSM_RETURN crtn;
+
+ crtn = subject.fetchField(&CSSMOID_AuthorityInfoAccess, &extField);
+ if(crtn) {
+ tpOcspDebug("tpOcspUrlsFromCert: no AIA extension");
+ return NULL;
+ }
+ if(extField->Length != sizeof(CSSM_X509_EXTENSION)) {
+ tpErrorLog("tpOcspUrlsFromCert: malformed CSSM_FIELD");
+ return NULL;
+ }
+ CSSM_X509_EXTENSION *cssmExt = (CSSM_X509_EXTENSION *)extField->Data;
+ if(cssmExt->format != CSSM_X509_DATAFORMAT_PARSED) {
+ tpErrorLog("tpOcspUrlsFromCert: malformed CSSM_X509_EXTENSION");
+ return NULL;
+ }
+
+ CE_AuthorityInfoAccess *aia =
+ (CE_AuthorityInfoAccess *)cssmExt->value.parsedValue;
+ CSSM_DATA **urls = NULL;
+ unsigned numUrls = 0;
+ for(unsigned dex=0; dex<aia->numAccessDescriptions; dex++) {
+ CE_AccessDescription *ad = &aia->accessDescriptions[dex];
+ if(!tpCompareCssmData(&ad->accessMethod, &CSSMOID_AD_OCSP)) {
+ continue;
+ }
+ CE_GeneralName *genName = &ad->accessLocation;
+ if(genName->nameType != GNT_URI) {
+ tpErrorLog("tpOcspUrlsFromCert: CSSMOID_AD_OCSP, but not type URI");
+ continue;
+ }
+
+ /* got one */
+ if(urls == NULL) {
+ urls = coder.mallocn<CSSM_DATA_PTR>(2);
+ }
+ else {
+ /* realloc */
+ CSSM_DATA **oldUrls = urls;
+ urls = coder.mallocn<CSSM_DATA_PTR>(numUrls + 2);
+ for(unsigned i=0; i<numUrls; i++) {
+ urls[i] = oldUrls[i];
+ }
+ }
+ urls[numUrls] = coder.mallocn<CSSM_DATA>();
+ coder.allocCopyItem(genName->name, *urls[numUrls++]);
+ urls[numUrls] = NULL;
+ #ifndef NDEBUG
+ {
+ CSSM_DATA urlStr;
+ coder.allocItem(urlStr, genName->name.Length + 1);
+ memmove(urlStr.Data, genName->name.Data, genName->name.Length);
+ urlStr.Data[urlStr.Length-1] = '\0';
+ tpOcspDebug("tpOcspUrlsFromCert: found URI %s", urlStr.Data);
+ }
+ #endif
+ }
+ subject.freeField(&CSSMOID_AuthorityInfoAccess, extField);
+ return urls;
+}
+
+/*
+ * Create an SecAsn1OCSPDRequest for one cert. This consists of:
+ *
+ * -- cooking up an OCSPRequest if net fetch is enabled or a local responder
+ * is configured;
+ * -- extracting URLs from subject cert if net fetch is enabled;
+ * -- creating an SecAsn1OCSPDRequest, mallocd in coder's space;
+ */
+static SecAsn1OCSPDRequest *tpGenOcspdReq(
+ TPVerifyContext &vfyCtx,
+ SecNssCoder &coder,
+ TPCertInfo &subject,
+ TPCertInfo &issuer,
+ OCSPClientCertID &certId,
+ const CSSM_DATA **urls, // from subject's AuthorityInfoAccess
+ CSSM_DATA &nonce) // possibly mallocd in coder's space and RETURNED
+{
+ OCSPRequest *ocspReq = NULL;
+ SecAsn1OCSPDRequest *ocspdReq = NULL; // to return
+ OCSPClientCertID *certID = NULL;
+ CSSM_RETURN crtn;
+ bool deleteCertID = false;
+
+ /* gather options or their defaults */
+ CSSM_APPLE_TP_OCSP_OPT_FLAGS optFlags = 0;
+ const CSSM_APPLE_TP_OCSP_OPTIONS *ocspOpts = vfyCtx.ocspOpts;
+ CSSM_DATA_PTR localResponder = NULL;
+ CSSM_DATA_PTR localResponderCert = NULL;
+ if(ocspOpts != NULL) {
+ optFlags = vfyCtx.ocspOpts->Flags;
+ localResponder = ocspOpts->LocalResponder;
+ localResponderCert = ocspOpts->LocalResponderCert;
+ }
+ bool genNonce = optFlags & CSSM_TP_OCSP_GEN_NONCE ? true : false;
+ bool requireRespNonce = optFlags & CSSM_TP_OCSP_REQUIRE_RESP_NONCE ? true : false;
+
+ /*
+ * One degenerate case in case we can't really do anything.
+ * If no URI and no local responder, only proceed if cache is not disabled
+ * and we're requiring full OCSP per cert.
+ */
+ if( ( (optFlags & CSSM_TP_ACTION_OCSP_CACHE_READ_DISABLE) ||
+ !(optFlags & CSSM_TP_ACTION_OCSP_REQUIRE_PER_CERT)
+ ) &&
+ (localResponder == NULL) &&
+ (urls == NULL)) {
+ tpOcspDebug("tpGenOcspdReq: no route to OCSP; NULL return");
+ return NULL;
+ }
+
+ /* do we need an OCSP request? */
+ if(!(optFlags & CSSM_TP_ACTION_OCSP_DISABLE_NET) || (localResponder != NULL)) {
+ try {
+ ocspReq = new OCSPRequest(subject, issuer, genNonce);
+ certID = ocspReq->certID();
+ }
+ catch(...) {
+ /* not sure how this could even happen but that was a fair amount of code */
+ tpErrorLog("tpGenOcspdReq: error cooking up OCSPRequest\n");
+ if(ocspReq != NULL) {
+ delete ocspReq;
+ return NULL;
+ }
+ }
+ }
+
+ /* certID needed one way or the other */
+ if(certID == NULL) {
+ crtn = tpOcspGetCertId(subject, issuer, certID);
+ if(crtn) {
+ goto errOut;
+ }
+ deleteCertID = true;
+ }
+
+ /*
+ * Create the SecAsn1OCSPDRequest. All fields optional.
+ */
+ ocspdReq = coder.mallocn<SecAsn1OCSPDRequest>();
+ memset(ocspdReq, 0, sizeof(*ocspdReq));
+ if(optFlags & CSSM_TP_ACTION_OCSP_CACHE_WRITE_DISABLE) {
+ ocspdReq->cacheWriteDisable = coder.mallocn<CSSM_DATA>();
+ ocspdReq->cacheWriteDisable->Data = coder.mallocn<uint8>();
+ ocspdReq->cacheWriteDisable->Data[0] = 0xff;
+ ocspdReq->cacheWriteDisable->Length = 1;
+ }
+ /*
+ * Note we're enforcing a not-so-obvious policy here: if nonce match is
+ * required, disk cache reads by ocspd are disabled. In-core cache is
+ * still enabled and hits in that cache do NOT require nonce matches.
+ */
+ if((optFlags & CSSM_TP_ACTION_OCSP_CACHE_READ_DISABLE) || requireRespNonce) {
+ ocspdReq->cacheReadDisable = coder.mallocn<CSSM_DATA>();
+ ocspdReq->cacheReadDisable->Data = coder.mallocn<uint8>();
+ ocspdReq->cacheReadDisable->Data[0] = 0xff;
+ ocspdReq->cacheReadDisable->Length = 1;
+ }
+ /* CertID, only required field */
+ coder.allocCopyItem(*certID->encode(), ocspdReq->certID);
+ if(ocspReq != NULL) {
+ ocspdReq->ocspReq = coder.mallocn<CSSM_DATA>();
+ coder.allocCopyItem(*ocspReq->encode(), *ocspdReq->ocspReq);
+ if(genNonce) {
+ /* nonce not available until encode() called */
+ coder.allocCopyItem(*ocspReq->nonce(), nonce);
+ }
+ }
+ if(localResponder != NULL) {
+ ocspdReq->localRespURI = localResponder;
+ }
+ if(!(optFlags & CSSM_TP_ACTION_OCSP_DISABLE_NET)) {
+ ocspdReq->urls = const_cast<CSSM_DATA **>(urls);
+ }
+
+errOut:
+ delete ocspReq;
+ if(deleteCertID) {
+ delete certID;
+ }
+ return ocspdReq;
+}
+
+static bool revocationTimeAfterVerificationTime(CFAbsoluteTime revokedTime, CSSM_TIMESTRING verifyTimeStr)
+{
+ // Return true if the revocation time is after the specified verification time (i.e. "good")
+ // If verifyTime not specified, use now for the verifyTime
+
+ CFAbsoluteTime verifyTime = 0;
+
+ if (verifyTimeStr)
+ {
+ CFDateRef cfVerifyTime = NULL; // made with CFDateCreate
+ int rtn = timeStringToCfDate((char *)verifyTimeStr, (unsigned)strlen(verifyTimeStr), &cfVerifyTime);
+ if (!rtn)
+ if (cfVerifyTime)
+ {
+ verifyTime = CFDateGetAbsoluteTime(cfVerifyTime);
+ CFRelease(cfVerifyTime);
+ }
+ }
+
+ if (verifyTime == 0)
+ verifyTime = CFAbsoluteTimeGetCurrent();
+
+ return verifyTime < revokedTime;
+}
+
+/*
+ * Apply a verified OCSPSingleResponse to a TPCertInfo.
+ */
+static CSSM_RETURN tpApplySingleResp(
+ OCSPSingleResponse &singleResp,
+ TPCertInfo &cert,
+ unsigned dex, // for debug
+ CSSM_APPLE_TP_OCSP_OPT_FLAGS flags, // for OCSP_SUFFICIENT
+ CSSM_TIMESTRING verifyTime, // Check revocation at specific time
+ bool &processed) // set true iff CS_Good or CS_Revoked found
+{
+ SecAsn1OCSPCertStatusTag certStatus = singleResp.certStatus();
+ CSSM_RETURN crtn = CSSM_OK;
+ if ((certStatus == CS_Revoked) &&
+ revocationTimeAfterVerificationTime(singleResp.revokedTime(), verifyTime))
+ {
+ tpOcspDebug("tpApplySingleResp: CS_Revoked for cert %u, but revoked after verification time", dex);
+ certStatus = CS_Good;
+ }
+
+ switch(certStatus) {
+ case CS_Good:
+ tpOcspDebug("tpApplySingleResp: CS_Good for cert %u", dex);
+ cert.revokeCheckGood(true);
+ if(flags & CSSM_TP_ACTION_OCSP_SUFFICIENT) {
+ /* no more revocation checking necessary for this cert */
+ cert.revokeCheckComplete(true);
+ }
+ processed = true;
+ break;
+ case CS_Revoked:
+ tpOcspDebug("tpApplySingleResp: CS_Revoked for cert %u", dex);
+ switch(singleResp.crlReason()) {
+ case CE_CR_CertificateHold:
+ crtn = CSSMERR_TP_CERT_SUSPENDED;
+ break;
+ default:
+ /* FIXME - may want more detailed CRLReason-specific errors */
+ crtn = CSSMERR_TP_CERT_REVOKED;
+ break;
+ }
+ if(!cert.addStatusCode(crtn)) {
+ crtn = CSSM_OK;
+ }
+ processed = true;
+ break;
+ case CS_Unknown:
+ /* not an error, no per-cert status, nothing here */
+ tpOcspDebug("tpApplySingleResp: CS_Unknown for cert %u", dex);
+ break;
+ default:
+ tpOcspDebug("tpApplySingleResp: BAD certStatus (%d) for cert %u",
+ (int)certStatus, dex);
+ if(cert.addStatusCode(CSSMERR_APPLETP_OCSP_STATUS_UNRECOGNIZED)) {
+ crtn = CSSMERR_APPLETP_OCSP_STATUS_UNRECOGNIZED;
+ }
+ break;
+ }
+ return crtn;
+}
+
+/*
+ * An exceptional case: synchronously flush the OCSPD cache and send a new
+ * resquest for just this one cert.
+ */
+static OCSPResponse *tpOcspFlushAndReFetch(
+ TPVerifyContext &vfyCtx,
+ SecNssCoder &coder,
+ TPCertInfo &subject,
+ TPCertInfo &issuer,
+ OCSPClientCertID &certID)
+{
+ const CSSM_DATA *derCertID = certID.encode();
+ CSSM_RETURN crtn;
+
+ crtn = ocspdCacheFlush(*derCertID);
+ if(crtn) {
+ #ifndef NDEBUG
+ cssmPerror("ocspdCacheFlush", crtn);
+ #endif
+ return NULL;
+ }
+
+ /* Cook up an OCSPDRequests, one request, just for this */
+ /* send it to ocsdp */
+ /* munge reply into an OCSPRsponse and return it */
+ tpOcspDebug("pOcspFlushAndReFetch: Code on demand");
+ return NULL;
+}
+
+class PendingRequest
+{
+public:
+ PendingRequest(
+ TPCertInfo &subject,
+ TPCertInfo &issuer,
+ OCSPClientCertID &cid,
+ CSSM_DATA **u,
+ unsigned dex);
+ ~PendingRequest() {}
+
+ TPCertInfo &subject;
+ TPCertInfo &issuer;
+ OCSPClientCertID &certID; // owned by caller
+ CSSM_DATA **urls; // owner-managed array of URLs obtained from subject's
+ // AuthorityInfoAccess.id-ad-ocsp.
+ CSSM_DATA nonce; // owner-managed copy of this requests' nonce, if it
+ // has one
+ unsigned dex; // in inputCerts, for debug
+ bool processed;
+};
+
+PendingRequest::PendingRequest(
+ TPCertInfo &subj,
+ TPCertInfo &iss,
+ OCSPClientCertID &cid,
+ CSSM_DATA **u,
+ unsigned dx)
+ : subject(subj), issuer(iss), certID(cid),
+ urls(u), dex(dx), processed(false)
+{
+ nonce.Data = NULL;
+ nonce.Length = 0;
+}
+
+#pragma mark ---- public API ----
+
+CSSM_RETURN tpVerifyCertGroupWithOCSP(
+ TPVerifyContext &vfyCtx,
+ TPCertGroup &certGroup) // to be verified
+{
+ assert(vfyCtx.clHand != 0);
+ assert(vfyCtx.policy == kRevokeOcsp);
+
+ CSSM_RETURN ourRtn = CSSM_OK;
+ OcspRespStatus respStat;
+ SecNssCoder coder;
+ CSSM_RETURN crtn;
+ SecAsn1OCSPDRequests ocspdReqs;
+ SecAsn1OCSPReplies ocspdReplies;
+ unsigned numRequests = 0; // in ocspdReqs
+ CSSM_DATA derOcspdRequests = {0, NULL};
+ CSSM_DATA derOcspdReplies = {0, NULL};
+ uint8 version = OCSPD_REQUEST_VERS;
+ unsigned numReplies;
+ unsigned numCerts = certGroup.numCerts();
+ if(numCerts <= 1) {
+ /* Can't verify without an issuer; we're done */
+ return CSSM_OK;
+ }
+ numCerts--;
+
+ /* gather options or their defaults */
+ CSSM_APPLE_TP_OCSP_OPT_FLAGS optFlags = 0;
+ const CSSM_APPLE_TP_OCSP_OPTIONS *ocspOpts = vfyCtx.ocspOpts;
+ CSSM_DATA_PTR localResponder = NULL;
+ CSSM_DATA_PTR localResponderCert = NULL;
+ bool cacheReadDisable = false;
+ bool cacheWriteDisable = false;
+ bool genNonce = false; // in outgoing request
+ bool requireRespNonce = false; // in incoming response
+ PRErrorCode prtn;
+
+ if(ocspOpts != NULL) {
+ optFlags = vfyCtx.ocspOpts->Flags;
+ localResponder = ocspOpts->LocalResponder;
+ localResponderCert = ocspOpts->LocalResponderCert;
+ }
+ if(optFlags & CSSM_TP_ACTION_OCSP_CACHE_READ_DISABLE) {
+ cacheReadDisable = true;
+ }
+ if(optFlags & CSSM_TP_ACTION_OCSP_CACHE_WRITE_DISABLE) {
+ cacheWriteDisable = true;
+ }
+ if(optFlags & CSSM_TP_OCSP_GEN_NONCE) {
+ genNonce = true;
+ }
+ if(optFlags & CSSM_TP_OCSP_REQUIRE_RESP_NONCE) {
+ requireRespNonce = true;
+ }
+ if(requireRespNonce & !genNonce) {
+ /* no can do */
+ tpErrorLog("tpVerifyCertGroupWithOCSP: requireRespNonce, !genNonce\n");
+ return CSSMERR_TP_INVALID_REQUEST_INPUTS;
+ }
+
+ tpOcspDebug("tpVerifyCertGroupWithOCSP numCerts %u optFlags 0x%lx",
+ numCerts, (unsigned long)optFlags);
+
+ /*
+ * create list of pendingRequests parallel to certGroup
+ */
+ PendingRequest **pending = coder.mallocn<PendingRequest *>(numCerts);
+ memset(pending, 0, (numCerts * sizeof(PendingRequest *)));
+
+ for(unsigned dex=0; dex<numCerts; dex++) {
+ OCSPClientCertID *certID = NULL;
+ TPCertInfo *subject = certGroup.certAtIndex(dex);
+
+ if(subject->trustSettingsFound()) {
+ /* functionally equivalent to root - we're done */
+ tpOcspDebug("...tpVerifyCertGroupWithOCSP: terminate per user trust at %u",
+ (unsigned)dex);
+ goto postOcspd;
+ }
+ TPCertInfo *issuer = certGroup.certAtIndex(dex + 1);
+ crtn = tpOcspGetCertId(*subject, *issuer, certID);
+ if(crtn) {
+ tpErrorLog("tpVerifyCertGroupWithOCSP: error extracting cert fields; "
+ "aborting\n");
+ goto errOut;
+ }
+
+ /*
+ * We use the URLs in the subject cert's AuthorityInfoAccess extension
+ * for two things - mainly to get the URL(s) for actual OCSP transactions,
+ * but also for CSSM_TP_ACTION_OCSP_REQUIRE_IF_RESP_PRESENT processing.
+ * So, we do the per-cert processing to get them right now even if we
+ * wind up using a local responder or getting verification from cache.
+ */
+ CSSM_DATA **urls = tpOcspUrlsFromCert(*subject, coder);
+ pending[dex] = new PendingRequest(*subject, *issuer, *certID, urls, dex);
+ }
+ /* subsequent errors to errOut: */
+
+ /*
+ * Create empty SecAsn1OCSPDRequests big enough for all certs
+ */
+ ocspdReqs.requests = coder.mallocn<SecAsn1OCSPDRequest *>(numCerts + 1);
+ memset(ocspdReqs.requests, 0, (numCerts + 1) * sizeof(SecAsn1OCSPDRequest *));
+ ocspdReqs.version.Data = &version;
+ ocspdReqs.version.Length = 1;
+
+ /*
+ * For each cert, either obtain a cached OCSPResponse, or create
+ * a request to get one.
+ *
+ * NOTE: in-core cache reads (via tpOcspCacheLookup() do NOT involve a
+ * nonce check, no matter what the app says. If nonce checking is required by the
+ * app, responses don't get added to cache if the nonce doesn't match, but once
+ * a response is validated and added to cache it's fair game for that task.
+ */
+ for(unsigned dex=0; dex<numCerts; dex++) {
+ PendingRequest *pendReq = pending[dex];
+ OCSPSingleResponse *singleResp = NULL;
+ if(!cacheReadDisable) {
+ singleResp = tpOcspCacheLookup(pendReq->certID, localResponder);
+ }
+ if(singleResp) {
+ tpOcspDebug("...tpVerifyCertGroupWithOCSP: localCache hit (1) dex %u",
+ (unsigned)dex);
+ crtn = tpApplySingleResp(*singleResp, pendReq->subject, dex, optFlags,
+ vfyCtx.verifyTime, pendReq->processed);
+ delete singleResp;
+ if(pendReq->processed) {
+ /* definitely done with this cert one way or the other */
+ if(crtn && (ourRtn == CSSM_OK)) {
+ /* real cert error: first error encountered; we'll keep going */
+ ourRtn = crtn;
+ }
+ continue;
+ }
+ if(crtn) {
+ /*
+ * This indicates a bad cached response. Well that's kinda weird, let's
+ * just flush this out and try a normal transaction.
+ */
+ tpOcspCacheFlush(pendReq->certID);
+ }
+ }
+
+ /*
+ * Prepare a request for ocspd
+ */
+ SecAsn1OCSPDRequest *ocspdReq = tpGenOcspdReq(vfyCtx, coder,
+ pendReq->subject, pendReq->issuer, pendReq->certID,
+ const_cast<const CSSM_DATA **>(pendReq->urls),
+ pendReq->nonce);
+ if(ocspdReq == NULL) {
+ /* tpGenOcspdReq determined there was no route to OCSP responder */
+ tpOcspDebug("tpVerifyCertGroupWithOCSP: no OCSP possible for cert %u", dex);
+ continue;
+ }
+ ocspdReqs.requests[numRequests++] = ocspdReq;
+ }
+ if(numRequests == 0) {
+ /* no candidates for OCSP: almost done */
+ goto postOcspd;
+ }
+
+ /* ship requests off to ocspd, get ocspReplies back */
+ if(coder.encodeItem(&ocspdReqs, kSecAsn1OCSPDRequestsTemplate, derOcspdRequests)) {
+ tpErrorLog("tpVerifyCertGroupWithOCSP: error encoding ocspdReqs\n");
+ ourRtn = CSSMERR_TP_INTERNAL_ERROR;
+ goto errOut;
+ }
+ crtn = ocspdFetch(vfyCtx.alloc, derOcspdRequests, derOcspdReplies);
+ if(crtn) {
+ tpErrorLog("tpVerifyCertGroupWithOCSP: error during ocspd RPC\n");
+ #ifndef NDEBUG
+ cssmPerror("ocspdFetch", crtn);
+ #endif
+ /* But this is not necessarily fatal...update per-cert status and check
+ * caller requirements below */
+ goto postOcspd;
+ }
+ memset(&ocspdReplies, 0, sizeof(ocspdReplies));
+ prtn = coder.decodeItem(derOcspdReplies, kSecAsn1OCSPDRepliesTemplate,
+ &ocspdReplies);
+ /* we're done with this, mallocd in ocspdFetch() */
+ vfyCtx.alloc.free(derOcspdReplies.Data);
+ if(prtn) {
+ /*
+ * This can happen when an OCSP server provides bad data...we cannot
+ * determine which cert is associated with this bad response;
+ * just flag it with the first one and proceed to the loop that
+ * handles CSSM_TP_ACTION_OCSP_REQUIRE_PER_CERT.
+ */
+ tpErrorLog("tpVerifyCertGroupWithOCSP: error decoding ocspd reply\n");
+ pending[0]->subject.addStatusCode(CSSMERR_APPLETP_OCSP_BAD_RESPONSE);
+ goto postOcspd;
+ }
+ if((ocspdReplies.version.Length != 1) ||
+ (ocspdReplies.version.Data[0] != OCSPD_REPLY_VERS)) {
+ tpErrorLog("tpVerifyCertGroupWithOCSP: ocspd reply version mismatch\n");
+ if(ourRtn == CSSM_OK) {
+ ourRtn = CSSMERR_TP_INTERNAL_ERROR; // maybe something better?
+ }
+ goto errOut;
+ }
+
+ /* process each reply */
+ numReplies = ocspdArraySize((const void **)ocspdReplies.replies);
+ for(unsigned dex=0; dex<numReplies; dex++) {
+ SecAsn1OCSPDReply *reply = ocspdReplies.replies[dex];
+
+ /* Cook up our version of an OCSPResponse from the encoded data */
+ OCSPResponse *ocspResp = NULL;
+ try {
+ ocspResp = new OCSPResponse(reply->ocspResp, TP_OCSP_CACHE_TTL);
+ }
+ catch(...) {
+ tpErrorLog("tpVerifyCertGroupWithOCSP: error decoding ocsp response\n");
+ /* what the heck, keep going */
+ continue;
+ }
+
+ /*
+ * Find matching subject cert if possible (it's technically optional for
+ * verification of the response in some cases, e.g., local responder).
+ */
+ PendingRequest *pendReq = NULL; // fully qualified
+ PendingRequest *reqWithIdMatch = NULL; // CertID match only, not nonce
+ for(unsigned pdex=0; pdex<numCerts; pdex++) {
+
+ /* first check ID match; that is required no matter what */
+ if((pending[pdex])->certID.compareToExist(reply->certID)) {
+ reqWithIdMatch = pending[pdex];
+ }
+ if(reqWithIdMatch == NULL) {
+ continue;
+ }
+ if(!genNonce) {
+ /* that's good enough */
+ pendReq = reqWithIdMatch;
+ tpOcspDebug("OCSP processs reply: CertID match, no nonce");
+ break;
+ }
+ if(tpCompareCssmData(&reqWithIdMatch->nonce, ocspResp->nonce())) {
+ tpOcspDebug("OCSP processs reply: nonce MATCH");
+ pendReq = reqWithIdMatch;
+ break;
+ }
+
+ /*
+ * In this case we keep going; if we never find a match, then we can
+ * use reqWithIdMatch if !requireRespNonce.
+ */
+ tpOcspDebug("OCSP processs reply: certID match, nonce MISMATCH");
+ }
+ if(pendReq == NULL) {
+ if(requireRespNonce) {
+ tpOcspDebug("OCSP processs reply: tossing out response due to "
+ "requireRespNonce");
+ delete ocspResp;
+ if(ourRtn == CSSM_OK) {
+ ourRtn = CSSMERR_APPLETP_OCSP_NONCE_MISMATCH;
+ }
+ continue;
+ }
+ if(reqWithIdMatch != NULL) {
+ /*
+ * Nonce mismatch but caller thinks that's OK. Log it and proceed.
+ */
+ assert(genNonce);
+ tpOcspDebug("OCSP processs reply: using bad nonce due to !requireRespNonce");
+ pendReq = reqWithIdMatch;
+ pendReq->subject.addStatusCode(CSSMERR_APPLETP_OCSP_NONCE_MISMATCH);
+ }
+ }
+ TPCertInfo *issuer = NULL;
+ if(pendReq != NULL) {
+ issuer = &pendReq->issuer;
+ }
+
+ /* verify response and either throw out or add to local cache */
+ respStat = tpVerifyOcspResp(vfyCtx, coder, issuer, *ocspResp, crtn);
+ switch(respStat) {
+ case ORS_Good:
+ break;
+ case ORS_Unknown:
+ /* not an error but we can't use it */
+ if((crtn != CSSM_OK) && (pendReq != NULL)) {
+ /* pass this info back to caller here... */
+ pendReq->subject.addStatusCode(crtn);
+ }
+ delete ocspResp;
+ continue;
+ case ORS_Bad:
+ delete ocspResp;
+ /*
+ * An exceptional case: synchronously flush the OCSPD cache and send a
+ * new request for just this one cert.
+ * FIXME: does this really buy us anything? A DOS attacker who managed
+ * to get this bogus response into our cache is likely to be able
+ * to do it again and again.
+ */
+ tpOcspDebug("tpVerifyCertGroupWithOCSP: flush/refetch for cert %u", dex);
+ ocspResp = tpOcspFlushAndReFetch(vfyCtx, coder, pendReq->subject,
+ pendReq->issuer, pendReq->certID);
+ if(ocspResp == NULL) {
+ tpErrorLog("tpVerifyCertGroupWithOCSP: error on flush/refetch\n");
+ ourRtn = CSSMERR_APPLETP_OCSP_BAD_RESPONSE;
+ goto errOut;
+ }
+ respStat = tpVerifyOcspResp(vfyCtx, coder, issuer, *ocspResp, crtn);
+ if(respStat != ORS_Good) {
+ tpErrorLog("tpVerifyCertGroupWithOCSP: verify error after "
+ "flush/refetch\n");
+ if((crtn != CSSM_OK) && (pendReq != NULL)) {
+ /* pass this info back to caller here... */
+ if(pendReq->subject.addStatusCode(crtn)) {
+ ourRtn = CSSMERR_APPLETP_OCSP_BAD_RESPONSE;
+ }
+ }
+ else {
+ ourRtn = CSSMERR_APPLETP_OCSP_BAD_RESPONSE;
+ }
+ goto errOut;
+ }
+ /* Voila! Recovery. Proceed. */
+ tpOcspDebug("tpVerifyCertGroupWithOCSP: refetch for cert %u SUCCEEDED",
+ dex);
+ break;
+ } /* switch response status */
+
+ if(!cacheWriteDisable) {
+ tpOcspCacheAdd(reply->ocspResp, localResponder);
+ }
+
+ /* attempt to apply to pendReq */
+ if(pendReq != NULL) {
+ OCSPSingleResponse *singleResp =
+ ocspResp->singleResponseFor(pendReq->certID);
+ if(singleResp) {
+ crtn = tpApplySingleResp(*singleResp, pendReq->subject, pendReq->dex,
+ optFlags, vfyCtx.verifyTime, pendReq->processed);
+ if(crtn && (ourRtn == CSSM_OK)) {
+ ourRtn = crtn;
+ }
+ delete singleResp;
+ }
+ } /* a reply which matches a pending request */
+
+ /*
+ * Done with this - note local OCSP response cache doesn't store this
+ * object; it stores an encoded copy.
+ */
+ delete ocspResp;
+ } /* for each reply */
+
+postOcspd:
+
+ /*
+ * Now process each cert which hasn't had an OCSP response applied to it.
+ * This can happen if we get back replies which are not strictly in 1-1 sync with
+ * our requests but which nevertheless contain valid info for more than one
+ * cert each.
+ */
+ for(unsigned dex=0; dex<numCerts; dex++) {
+ PendingRequest *pendReq = pending[dex];
+ if(pendReq == NULL) {
+ /* i.e. terminated due to user trust */
+ tpOcspDebug("...tpVerifyCertGroupWithOCSP: NULL pendReq dex %u",
+ (unsigned)dex);
+ break;
+ }
+ if(pendReq->processed) {
+ continue;
+ }
+ OCSPSingleResponse *singleResp = NULL;
+ /* Note this corner case will not work if cache is disabled. */
+ if(!cacheReadDisable) {
+ singleResp = tpOcspCacheLookup(pendReq->certID, localResponder);
+ }
+ if(singleResp) {
+ tpOcspDebug("...tpVerifyCertGroupWithOCSP: localCache (2) hit dex %u",
+ (unsigned)dex);
+ crtn = tpApplySingleResp(*singleResp, pendReq->subject, dex, optFlags,
+ vfyCtx.verifyTime, pendReq->processed);
+ if(crtn) {
+ if(ourRtn == CSSM_OK) {
+ ourRtn = crtn;
+ }
+ }
+ delete singleResp;
+ }
+ if(!pendReq->processed) {
+ /* Couldn't perform OCSP for this cert. */
+ tpOcspDebug("tpVerifyCertGroupWithOCSP: OCSP_UNAVAILABLE for cert %u", dex);
+ bool required = false;
+ CSSM_RETURN responseStatus = CSSM_OK;
+ if(pendReq->subject.numStatusCodes() > 0) {
+ /*
+ * Check whether we got a response for this cert, but it was rejected
+ * due to being improperly signed. That should result in an actual
+ * error, even under Best Attempt processing. (10743149)
+ */
+ if(pendReq->subject.hasStatusCode(CSSMERR_APPLETP_OCSP_BAD_RESPONSE)) {
+// responseStatus = CSSMERR_APPLETP_OCSP_BAD_RESPONSE; <rdar://problem/10831157>
+ } else if(pendReq->subject.hasStatusCode(CSSMERR_APPLETP_OCSP_SIG_ERROR)) {
+ responseStatus = CSSMERR_APPLETP_OCSP_SIG_ERROR;
+ } else if(pendReq->subject.hasStatusCode(CSSMERR_APPLETP_OCSP_NO_SIGNER)) {
+ responseStatus = CSSMERR_APPLETP_OCSP_NO_SIGNER;
+ }
+ }
+ if(responseStatus == CSSM_OK) {
+ /* no response available (as opposed to getting an invalid response) */
+ pendReq->subject.addStatusCode(CSSMERR_APPLETP_OCSP_UNAVAILABLE);
+ }
+ if(optFlags & CSSM_TP_ACTION_OCSP_REQUIRE_PER_CERT) {
+ /* every cert needs OCSP */
+ tpOcspDebug("tpVerifyCertGroupWithOCSP: response required for all certs, missing for cert %u", dex);
+ required = true;
+ }
+ else if(optFlags & CSSM_TP_ACTION_OCSP_REQUIRE_IF_RESP_PRESENT) {
+ /* this cert needs OCSP if it had an AIA extension with an OCSP URI */
+ if(pendReq->urls) {
+ tpOcspDebug("tpVerifyCertGroupWithOCSP: OCSP URI present but no valid response for cert %u", dex);
+ required = true;
+ }
+ }
+ if( (required && pendReq->subject.isStatusFatal(CSSMERR_APPLETP_OCSP_UNAVAILABLE)) ||
+ (responseStatus != CSSM_OK && pendReq->subject.isStatusFatal(responseStatus)) ) {
+ /* fatal error, but we keep on processing */
+ if(ourRtn == CSSM_OK) {
+ ourRtn = (responseStatus != CSSM_OK) ? responseStatus : CSSMERR_APPLETP_OCSP_UNAVAILABLE;
+ }
+ }
+ }
+ }
+errOut:
+ for(unsigned dex=0; dex<numCerts; dex++) {
+ PendingRequest *pendReq = pending[dex];
+ if(pendReq == NULL) {
+ /* i.e. terminated due to user trust */
+ break;
+ }
+ delete &pendReq->certID;
+ delete pendReq;
+ }
+ return ourRtn;
+}