X-Git-Url: https://git.saurik.com/apple/security.git/blobdiff_plain/80e2389990082500d76eb566d4946be3e786c3ef..d8f41ccd20de16f8ebe2ccc84d47bf1cb2b26bbb:/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 index 00000000..e17e7cf0 --- /dev/null +++ b/Security/libsecurity_apple_x509_tp/lib/tpOcspVerify.cpp @@ -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 +#include "certGroupUtils.h" +#include +#include +#include +#include +#include +#include +#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; dexnumAccessDescriptions; 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(2); + } + else { + /* realloc */ + CSSM_DATA **oldUrls = urls; + urls = coder.mallocn(numUrls + 2); + for(unsigned i=0; i(); + 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(); + memset(ocspdReq, 0, sizeof(*ocspdReq)); + if(optFlags & CSSM_TP_ACTION_OCSP_CACHE_WRITE_DISABLE) { + ocspdReq->cacheWriteDisable = coder.mallocn(); + ocspdReq->cacheWriteDisable->Data = coder.mallocn(); + 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(); + ocspdReq->cacheReadDisable->Data = coder.mallocn(); + 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(); + 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(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(numCerts); + memset(pending, 0, (numCerts * sizeof(PendingRequest *))); + + for(unsigned dex=0; dextrustSettingsFound()) { + /* 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(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; dexcertID, 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(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; dexocspResp, 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; pdexcertID.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; dexprocessed) { + 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; + } 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; dexcertID; + delete pendReq; + } + return ourRtn; +}