tpOcspCertVfy.cpp   [plain text]


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

/*
 * tpOcspCertVfy.cpp - OCSP cert verification routines
 */

#include "tpOcspCertVfy.h"
#include "tpdebugging.h"
#include "certGroupUtils.h"
#include <Security/oidscert.h>
#include <CommonCrypto/CommonDigest.h>
#include <security_ocspd/ocspdUtils.h>
#include "tpTime.h"

#ifndef	NDEBUG
#include <Security/SecCertificate.h>
#endif

/*
 * Is signerCert authorized to sign OCSP responses by issuerCert? IssuerCert is
 * assumed to be (i.e., must, but we don't check that here) the signer of the
 * cert being verified, which is not in the loop for this op. Just a bool returned;
 * it's authorized or it's not.
 */
static bool tpIsAuthorizedOcspSigner(
	TPCertInfo &issuerCert,		// issuer of cert being verified
	TPCertInfo &signerCert)		// potential signer of OCSP response
{
	CSSM_DATA_PTR		fieldValue = NULL;			// mallocd by CL
	CSSM_RETURN			crtn;
	bool				ourRtn = false;
	CE_ExtendedKeyUsage *eku = NULL;
	bool				foundEku = false;

	/*
	 * First see if issuerCert issued signerCert (No signature vfy yet, just
	 * subject/issuer check).
	 */
	if(!issuerCert.isIssuerOf(signerCert)) {
#ifndef	NDEBUG
		SecCertificateRef issuerRef = NULL;
		SecCertificateRef signerRef = NULL;
		const CSSM_DATA *issuerData = issuerCert.itemData();
		const CSSM_DATA *signerData = signerCert.itemData();
		crtn = SecCertificateCreateFromData(issuerData, CSSM_CERT_X_509v3, CSSM_CERT_ENCODING_BER, &issuerRef);
		crtn = SecCertificateCreateFromData(signerData, CSSM_CERT_X_509v3, CSSM_CERT_ENCODING_BER, &signerRef);
		CFStringRef issuerName = SecCertificateCopySubjectSummary(issuerRef);
		CFStringRef signerName = SecCertificateCopySubjectSummary(signerRef);
		if(issuerName) {
			CFIndex maxLength = CFStringGetMaximumSizeForEncoding(CFStringGetLength(issuerName), kCFStringEncodingUTF8) + 1;
			char* buf = (char*) malloc(maxLength);
			if (buf) {
				if (CFStringGetCString(issuerName, buf, (CFIndex)maxLength, kCFStringEncodingUTF8)) {
					tpOcspDebug("tpIsAuthorizedOcspSigner: issuerCert \"%s\"", buf);
				}
				free(buf);
			}
			CFRelease(issuerName);
		}
		if(signerName) {
			CFIndex maxLength = CFStringGetMaximumSizeForEncoding(CFStringGetLength(signerName), kCFStringEncodingUTF8) + 1;
			char* buf = (char*) malloc(maxLength);
			if (buf) {
				if (CFStringGetCString(signerName, buf, (CFIndex)maxLength, kCFStringEncodingUTF8)) {
					tpOcspDebug("tpIsAuthorizedOcspSigner: signerCert \"%s\"", buf);
				}
				free(buf);
			}
			CFRelease(signerName);
		}
		if(issuerRef) {
			CFRelease(issuerRef);
		}
		if(signerRef) {
			CFRelease(signerRef);
		}
#endif
		tpOcspDebug("tpIsAuthorizedOcspSigner: signer is not issued by issuerCert");
		return false;
	}

	/* Fetch ExtendedKeyUse field from signerCert */
	crtn = signerCert.fetchField(&CSSMOID_ExtendedKeyUsage, &fieldValue);
	if(crtn) {
		tpOcspDebug("tpIsAuthorizedOcspSigner: signer is issued by issuer, no EKU");
		return false;
	}
	CSSM_X509_EXTENSION *cssmExt = (CSSM_X509_EXTENSION *)fieldValue->Data;
	if(cssmExt->format != CSSM_X509_DATAFORMAT_PARSED) {
		tpOcspDebug("tpIsAuthorizedOcspSigner: bad extension format");
		goto errOut;
	}
	eku = (CE_ExtendedKeyUsage *)cssmExt->value.parsedValue;

	/* Look for OID_KP_OCSPSigning */
	for(unsigned dex=0; dex<eku->numPurposes; dex++) {
		if(tpCompareCssmData(&eku->purposes[dex], &CSSMOID_OCSPSigning)) {
			foundEku = true;
			break;
		}
	}
	if(!foundEku) {
		tpOcspDebug("tpIsAuthorizedOcspSigner: signer is issued by issuer, no OCSP "
			"signing EKU");
		goto errOut;
	}

	/*
	 * OK, signerCert is authorized by *someone* to sign OCSP requests, and
	 * it claims to be issued by issuer. Sig verify to be sure.
	 * FIXME this is not handling partial public keys, which would be a colossal
	 * mess to handle in this module...so we don't.
	 */
	crtn = signerCert.verifyWithIssuer(&issuerCert, NULL);
	if(crtn == CSSM_OK) {
		tpOcspDebug("tpIsAuthorizedOcspSigner: FOUND authorized signer");
		ourRtn = true;
	}
	else {
		/* This is a highly irregular situation... */
		tpOcspDebug("tpIsAuthorizedOcspSigner: signer sig verify FAIL");
	}
errOut:
	if(fieldValue != NULL) {
		signerCert.freeField(&CSSMOID_ExtendedKeyUsage, fieldValue);
	}
	return ourRtn;
}

/*
 * Check ResponderID linkage between an OCSPResponse and a cert we believe to
 * be the issuer of both that response and the cert being verified. Returns
 * true if OK.
 */
static
bool tpOcspResponderIDCheck(
	OCSPResponse	&ocspResp,
	TPCertInfo		&signer)
{
	bool shouldBeSigner = false;
	if(ocspResp.responderIDTag() == RIT_Name) {
		/*
		 * Name inside response must == signer's SubjectName.
		 * Note we can't use signer.subjectName(); that's normalized.
		 */

		const CSSM_DATA *respIdName = ocspResp.encResponderName();
		CSSM_DATA *subjectName = NULL;
		CSSM_RETURN crtn = signer.fetchField(&CSSMOID_X509V1SubjectNameStd,
			&subjectName);
		if(crtn) {
			/* bad cert */
			tpOcspDebug("tpOcspResponderIDCheck: error on fetchField(subjectName");
			return false;
		}
		if(tpCompareCssmData(respIdName, subjectName)) {
			tpOcspDebug("tpOcspResponderIDCheck: good ResponderID.byName");
			shouldBeSigner = true;
		}
		else {
			tpOcspDebug("tpOcspResponderIDCheck: BAD ResponderID.byName");
		}
		signer.freeField(&CSSMOID_X509V1SubjectNameStd, subjectName);
	}
	else {
		/* ResponderID.byKey must == SHA1(signer's public key) */
		const CSSM_KEY_PTR pubKey = signer.pubKey();
		assert(pubKey != NULL);
		uint8 digest[CC_SHA1_DIGEST_LENGTH];
		CSSM_DATA keyHash = {CC_SHA1_DIGEST_LENGTH, digest};
		CSSM_DATA pubKeyBytes = {0, NULL};
		ocspdGetPublicKeyBytes(NULL, pubKey, pubKeyBytes);
		ocspdSha1(pubKeyBytes.Data, (CC_LONG)pubKeyBytes.Length, digest);
		const CSSM_DATA *respKeyHash = &ocspResp.responderID().byKey;
		if(tpCompareCssmData(&keyHash, respKeyHash)) {
			tpOcspDebug("tpOcspResponderIDCheck: good ResponderID.byKey");
			shouldBeSigner = true;
		}
		else {
			tpOcspDebug("tpOcspResponderIDCheck: BAD ResponderID.byKey");
		}
	}
	return shouldBeSigner;
}

/*
 * Verify the signature of an OCSP response. Caller is responsible for all other
 * verification of the response, this is just the crypto.
 * Returns true on success.
 */
static bool tpOcspResponseSigVerify(
	TPVerifyContext		&vfyCtx,
	OCSPResponse		&ocspResp,		// parsed response
	TPCertInfo			&signer)
{
	/* get signature algorithm in CSSM form from the response */
	const SecAsn1OCSPBasicResponse &basicResp = ocspResp.basicResponse();
	const CSSM_OID *algOid = &basicResp.algId.algorithm;
	CSSM_ALGORITHMS sigAlg;

	if(!cssmOidToAlg(algOid, &sigAlg)) {
		tpOcspDebug("tpOcspResponseSigVerify: unknown signature algorithm");
	}

	/* signer's public key from the cert */
	const CSSM_KEY *pubKey = signer.pubKey();

	/* signature: on decode, length is in BITS */
	CSSM_DATA sig = basicResp.sig;
	sig.Length /= 8;

	CSSM_RETURN crtn;
	CSSM_CC_HANDLE sigHand;
	bool ourRtn = false;
	crtn = CSSM_CSP_CreateSignatureContext(vfyCtx.cspHand, sigAlg, NULL,
		pubKey, &sigHand);
	if(crtn) {
		#ifndef	NDEBUG
		cssmPerror("tpOcspResponseSigVerify, CSSM_CSP_CreateSignatureContext", crtn);
		#endif
		return false;
	}
	crtn = CSSM_VerifyData(sigHand, &basicResp.tbsResponseData, 1,
		CSSM_ALGID_NONE, &sig);
	if(crtn) {
		#ifndef	NDEBUG
		cssmPerror("tpOcspResponseSigVerify, CSSM_VerifyData", crtn);
		#endif
	}
	else {
		ourRtn = true;
	}
	CSSM_DeleteContext(sigHand);
	return ourRtn;
}

/* possible return from tpIsOcspIssuer() */
typedef enum {
	OIS_No,			// not the issuer
	OIS_Good,		// is the issuer and signature matches
	OIS_BadSig,		// appears to be issuer, but signature doesn't match
} OcspIssuerStatus;

/* type of rawCert passed to tpIsOcspIssuer */
typedef enum {
	OCT_Local,		// LocalResponder - no checking other than signature
	OCT_Issuer,		// it's the issuer of the cert being verified
	OCT_Provided,	// came with response, provenance unknown
} OcspCertType;

/*
 * Did specified cert issue the OCSP response?
 *
 * This implements the algorithm described in RFC2560, section 4.2.2.2,
 * "Authorized Responders". It sees if the cert could be the issuer of the
 * OCSP response per that algorithm; then if it could, it performs signature
 * verification.
 */
static OcspIssuerStatus tpIsOcspIssuer(
	TPVerifyContext		&vfyCtx,
	OCSPResponse		&ocspResp,		// parsed response
	/* on input specify at least one of the following two */
	const CSSM_DATA		*signerData,
	TPCertInfo			*signer,
	OcspCertType		certType,		// where rawCert came from
	TPCertInfo			*issuer,		// OPTIONAL, if known
	TPCertInfo			**signerRtn)	// optionally RETURNED if at all possible
{
	assert((signerData != NULL) || (signer != NULL));

	/* get signer as TPCertInfo if caller hasn't provided */
	TPCertInfo *tmpSigner = NULL;
	if(signer == NULL) {
		try {
			tmpSigner = new TPCertInfo(vfyCtx.clHand, vfyCtx.cspHand, signerData,
				TIC_CopyData, vfyCtx.verifyTime);
		}
		catch(...) {
			tpOcspDebug("tpIsOcspIssuer: bad cert");
			return OIS_No;
		}
		signer = tmpSigner;
	}
	if(signer == NULL) {
		return OIS_No;
	}
	if(signerRtn != NULL) {
		*signerRtn = signer;
	}

	/*
	 * Qualification of "this can be the signer" depends on where the
	 * signer came from.
	 */
	bool shouldBeSigner = false;
	OcspIssuerStatus ourRtn = OIS_No;

	switch(certType) {
		case OCT_Local:			// caller trusts this and thinks it's the signer
			shouldBeSigner = true;
			break;
		case OCT_Issuer:		// last resort, the actual issuer
			/* check ResponderID linkage */
			shouldBeSigner = tpOcspResponderIDCheck(ocspResp, *signer);
			break;
		case OCT_Provided:
		{
			/*
			 * This cert came with the response.
			 */
			if(issuer == NULL) {
				/*
				 * careful, might not know the issuer...how would this path ever
				 * work then? I don't think it needs to because you can NOT
				 * do OCSP on a cert without its issuer in hand.
				 */
				break;
			}

			/* check EKU linkage */
			shouldBeSigner = tpIsAuthorizedOcspSigner(*issuer, *signer);
			break;
		}
	}
	if(!shouldBeSigner) {
		goto errOut;
	}

	/* verify the signature */
	if(tpOcspResponseSigVerify(vfyCtx, ocspResp, *signer)) {
		ourRtn = OIS_Good;
	}

errOut:
	if((signerRtn == NULL) && (tmpSigner != NULL)) {
		delete tmpSigner;
	}
	return ourRtn;

}

OcspRespStatus tpVerifyOcspResp(
	TPVerifyContext		&vfyCtx,
	SecNssCoder			&coder,
	TPCertInfo			*issuer,		// issuer of the related cert, may be issuer of
										//   reply, may not be known
	OCSPResponse		&ocspResp,
	CSSM_RETURN			&cssmErr)		// possible per-cert error
{
	OcspRespStatus	ourRtn = ORS_Unknown;
	CSSM_RETURN		crtn;
    char *ocspResp_genTime = NULL;
    char *ocspResp_cssmTime = NULL;
    CSSM_TIMESTRING cssmTimeStr = NULL;

	tpOcspDebug("tpVerifyOcspResp top");

	switch(ocspResp.responseStatus()) {
		case RS_Success:
			crtn = CSSM_OK;
			break;
		case RS_MalformedRequest:
			crtn = CSSMERR_APPLETP_OCSP_RESP_MALFORMED_REQ;
			break;
		case RS_InternalError:
			crtn = CSSMERR_APPLETP_OCSP_RESP_INTERNAL_ERR;
			break;
		case RS_TryLater:
			crtn = CSSMERR_APPLETP_OCSP_RESP_TRY_LATER;
			break;
		case RS_SigRequired:
			crtn = CSSMERR_APPLETP_OCSP_RESP_SIG_REQUIRED;
			break;
		case RS_Unauthorized:
			crtn = CSSMERR_APPLETP_OCSP_RESP_UNAUTHORIZED;
			break;
		default:
			crtn = CSSMERR_APPLETP_OCSP_BAD_RESPONSE;
			break;
	}
	if(crtn) {
		tpOcspDebug("tpVerifyOcspResp aborting due to response status %d",
			(int)(ocspResp.responseStatus()));
		cssmErr = crtn;
		return ORS_Unknown;
	}
	cssmErr = CSSM_OK;

	/* one of our main jobs is to locate the signer of the response, here */
	TPCertInfo *signerInfo = NULL;
	TPCertInfo *signerInfoTBD = NULL;		// if non NULL at end, we delete
	/* we'll be verifying into this cert group */
	TPCertGroup	ocspCerts(vfyCtx.alloc, TGO_Caller);
	CSSM_BOOL verifiedToRoot;
	CSSM_BOOL verifiedToAnchor;
	CSSM_BOOL verifiedViaTrustSetting;

	const CSSM_APPLE_TP_OCSP_OPTIONS *ocspOpts = vfyCtx.ocspOpts;
	OcspIssuerStatus issuerStat;

    /* determine verification time for signer */
    ocspResp_cssmTime = (char *)malloc(CSSM_TIME_STRLEN + 1);
    ocspResp_genTime = (char *)malloc(GENERAL_TIME_STRLEN + 1);
    if (ocspResp_cssmTime && ocspResp_genTime) {
        cssmTimeStr = vfyCtx.verifyTime;        // store old verify time for other ctx users
        cfAbsTimeToGgenTime(ocspResp.producedAt(), ocspResp_genTime);
        tpTimeToCssmTimestring(ocspResp_genTime,GENERAL_TIME_STRLEN,ocspResp_cssmTime);
        vfyCtx.verifyTime = ocspResp_cssmTime;
    }

	/*
	 * Set true if we ever find an apparent issuer which does not correctly
	 * pass signature verify. If true and we never success, that's a XXX error.
	 */
	bool foundBadIssuer = false;
	bool foundLocalResponder = false;
	uint32 numSignerCerts = ocspResp.numSignerCerts();

	/*
	 * This cert group, allocated by AppleTPSession::CertGroupVerify(),
	 * serves two functions here:
	 *
	 * -- it accumulates certs we get from the net (as parts of OCSP responses)
	 *    for user in verifying OCSPResponse-related certs.
	 *    TPCertGroup::buildCertGroup() uses this group as one of the many
	 *    sources of certs when building a cert chain.
	 *
	 * -- it provides a container into which to stash TPCertInfos which
	 *    persist at least as long as the TPVerifyContext; it's of type TGO_Group,
	 *    so all of the certs added to it get freed when the group does.
	 */
	assert(vfyCtx.signerCerts != NULL);

	TPCertGroup &gatheredCerts = vfyCtx.gatheredCerts;

	/* set up for disposal of TPCertInfos created by TPCertGroup::buildCertGroup() */
	TPCertGroup	certsToBeFreed(vfyCtx.alloc, TGO_Group);

	/*
	 * First job is to find the cert which signed this response.
	 * Give priority to caller's LocalResponderCert.
	 */
	if((ocspOpts != NULL) && (ocspOpts->LocalResponderCert != NULL)) {
		TPCertInfo *responderInfo = NULL;
		issuerStat = tpIsOcspIssuer(vfyCtx, ocspResp,
			ocspOpts->LocalResponderCert, NULL,
			OCT_Local, issuer, &responderInfo);
		switch(issuerStat) {
			case OIS_BadSig:
				foundBadIssuer = true;
				/* drop thru */
			case OIS_No:
				if(responderInfo != NULL) {
					/* can't use it - should this be an immediate error? */
					delete responderInfo;
				}
				break;
			case OIS_Good:
				assert(responderInfo != NULL);
				signerInfo = signerInfoTBD = responderInfo;
				foundLocalResponder = true;
				tpOcspDebug("tpVerifyOcspResp: signer := LocalResponderCert");
				break;
		}
	}

	if((signerInfo == NULL) && (numSignerCerts != 0)) {
		/*
		 * App did not specify a local responder (or provided a bad one)
		 * and the response came with some certs. Try those.
		 */
		TPCertInfo *respCert = NULL;
		for(unsigned dex=0; dex<numSignerCerts; dex++) {
			const CSSM_DATA *certData = ocspResp.signerCert(dex);
			if(signerInfo == NULL) {
				/* stop trying this after we succeed... */
				issuerStat = tpIsOcspIssuer(vfyCtx, ocspResp,
					certData, NULL,
					OCT_Provided, issuer, &respCert);
				switch(issuerStat) {
					case OIS_No:
						break;
					case OIS_Good:
						assert(respCert != NULL);
						signerInfo = signerInfoTBD = respCert;
						tpOcspDebug("tpVerifyOcspResp: signer := signerCert[%u]", dex);
						break;
					case OIS_BadSig:
						foundBadIssuer = true;
						break;
				}
			}
			else {
				/*
				 * At least add this cert to certGroup for verification.
				 * OcspCert will own the TPCertInfo.
				 */
				try {
					respCert = new TPCertInfo(vfyCtx.clHand, vfyCtx.cspHand, certData,
						TIC_CopyData, vfyCtx.verifyTime);
				}
				catch(...) {
					tpOcspDebug("tpVerifyOcspResp: BAD signerCert[%u]", dex);
				}
			}
			/* if we got a TPCertInfo, and it's not the signer, add it to certGroup */
			if((respCert != NULL) && (respCert != signerInfo)) {
				gatheredCerts.appendCert(respCert);
			}
		}
	}

	if((signerInfo == NULL) && (issuer != NULL)) {
		/*
		 * Haven't found it yet, try the actual issuer
		 */
		issuerStat = tpIsOcspIssuer(vfyCtx, ocspResp,
			NULL, issuer,
			OCT_Issuer, issuer, NULL);
		switch(issuerStat) {
			case OIS_BadSig:
				ourRtn = ORS_Unknown;
				cssmErr = CSSMERR_APPLETP_OCSP_SIG_ERROR;
				goto errOut;
			case OIS_No:
				break;
			case OIS_Good:
				signerInfo = issuer;
				tpOcspDebug("tpVerifyOcspResp: signer := issuer");
				break;
		}
	}

	if(signerInfo == NULL) {
		if((issuer != NULL) && !issuer->isStatusFatal(CSSMERR_APPLETP_OCSP_NO_SIGNER)) {
			/* user wants to proceed without verifying! */
			tpOcspDebug("tpVerifyOcspResp: no signer found, user allows!");
			ourRtn = ORS_Good;
		}
		else {
			tpOcspDebug("tpVerifyOcspResp: no signer found");
			ourRtn = ORS_Unknown;
			/* caller adds to per-cert status */
			cssmErr = CSSMERR_APPLETP_OCSP_NO_SIGNER;
		}
		goto errOut;
	}

	if(signerInfo != NULL && !foundLocalResponder) {
		/*
		 * tpIsOcspIssuer has verified that signerInfo is the signer of the
		 * OCSP response, and that it is either the issuer of the cert being
		 * checked or is a valid authorized responder for that issuer based on
		 * key id linkage and EKU. There is no stipulation in RFC2560 to also
		 * build the chain back to a trusted anchor; however, we'll continue to
		 * enforce this for the local responder case. (10742723)
		 */
        /* check that the cert we found is temporally valid */
        if (signerInfo->isExpired() || signerInfo->isNotValidYet()) {
            tpOcspDebug("tpVerifyOcspResp ocspSigner expired or not yet valid");
            ourRtn = ORS_Unknown;
            cssmErr = CSSMERR_APPLETP_OCSP_NO_SIGNER;
            goto errOut;
        }
        else {
            tpOcspDebug("tpVerifyOcspResp SUCCESS");
            ourRtn = ORS_Good;
            goto errOut;
        }
	}

	/*
	 * Last remaining task is to verify the signer, and all the certs back to
	 * an anchor
	 */

	/* start from scratch with both of these groups */
	gatheredCerts.setAllUnused();
	vfyCtx.signerCerts->setAllUnused();
	crtn = ocspCerts.buildCertGroup(
			*signerInfo,			// subject item
			vfyCtx.signerCerts,		// inCertGroup the original group-to-be-verified
			vfyCtx.dbList,			// optional
			vfyCtx.clHand,
			vfyCtx.cspHand,
			vfyCtx.verifyTime,
			vfyCtx.numAnchorCerts,
			vfyCtx.anchorCerts,
			certsToBeFreed,			// local to-be-freed right now
			&gatheredCerts,			// accumulate gathered certs here
			CSSM_FALSE,				// subjectIsInGroup
			vfyCtx.actionFlags,
			vfyCtx.policyOid,
			vfyCtx.policyStr,
			vfyCtx.policyStrLen,
			kSecTrustSettingsKeyUseSignRevocation,
			verifiedToRoot,
			verifiedToAnchor,
			verifiedViaTrustSetting);
	if(crtn) {
		tpOcspDebug("tpVerifyOcspResp buildCertGroup failure");
		cssmErr = crtn;
		ourRtn = ORS_Unknown;
		goto errOut;
	}

    /* Verify temporal validity of local OCSP signer chain */
    crtn = ocspCerts.getReturnCode(crtn, NULL, vfyCtx.actionFlags);
    if ((crtn == CSSMERR_TP_CERT_EXPIRED) || (crtn == CSSMERR_TP_CERT_NOT_VALID_YET)) {
        tpOcspDebug("tpVerifyOcspResp ocspSigner expired or not yet valid");
        ourRtn = ORS_Unknown;
        cssmErr = CSSMERR_APPLETP_OCSP_NO_SIGNER;
        goto errOut;
    }

	if(!verifiedToAnchor && !verifiedViaTrustSetting) {
		/* required */
		ourRtn = ORS_Unknown;
		if(verifiedToRoot) {
			/* verified to root which is not an anchor */
			tpOcspDebug("tpVerifyOcspResp root, no anchor");
			cssmErr = CSSMERR_APPLETP_OCSP_INVALID_ANCHOR_CERT;
		}
		else {
			/* partial chain, no root, not verifiable by anchor */
			tpOcspDebug("tpVerifyOcspResp no root, no anchor");
			cssmErr = CSSMERR_APPLETP_OCSP_NOT_TRUSTED;
		}
		if((issuer != NULL) && !issuer->isStatusFatal(cssmErr)) {
			tpOcspDebug("...ignoring last error per trust setting");
			ourRtn = ORS_Good;
		}
		else {
			ourRtn = ORS_Unknown;
		}
	}
	else {
		tpOcspDebug("tpVerifyOcspResp SUCCESS; chain verified");
		ourRtn = ORS_Good;
	}

    /* FIXME policy verify? */

errOut:
	delete signerInfoTBD;
    if (ocspResp_genTime && ocspResp_cssmTime) vfyCtx.verifyTime = cssmTimeStr;
    if (ocspResp_genTime) free(ocspResp_genTime);
    if (ocspResp_cssmTime) free(ocspResp_cssmTime);
	/* any other cleanup? */
	return ourRtn;
}