]> git.saurik.com Git - apple/security.git/blobdiff - Security/libsecurity_apple_x509_tp/lib/tpOcspVerify.cpp
Security-57031.1.35.tar.gz
[apple/security.git] / Security / libsecurity_apple_x509_tp / lib / tpOcspVerify.cpp
diff --git a/Security/libsecurity_apple_x509_tp/lib/tpOcspVerify.cpp b/Security/libsecurity_apple_x509_tp/lib/tpOcspVerify.cpp
new file mode 100644 (file)
index 0000000..e17e7cf
--- /dev/null
@@ -0,0 +1,870 @@
+/*
+ * 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;
+}