SecOCSPResponse.c   [plain text]


/*
 * Copyright (c) 2008-2009 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@
 */

/*
 * SecOCSPResponse.c - Wrapper to decode ocsp responses.
 */

#include <securityd/SecOCSPResponse.h>
#include <Security/SecCertificateInternal.h>
#include <Security/SecFramework.h>
#include <Security/SecKeyPriv.h>
#include <AssertMacros.h>
#include <security_utilities/debugging.h>
#include <security_asn1/SecAsn1Coder.h>
#include <security_asn1/ocspTemplates.h>
#include <security_asn1/oidsalg.h>
#include <security_asn1/oidsocsp.h>
#include <CommonCrypto/CommonDigest.h>
#include <asl.h>
#include <stdlib.h>
#include "SecInternal.h"

#define ocspdErrorLog(args...)     asl_log(NULL, NULL, ASL_LEVEL_ERR, ## args)
#define ocspdHttpDebug(args...)     secdebug("ocspdHttp", ## args)
#define ocspdDebug(args...)     secdebug("ocsp", ## args)


/*
   OCSPResponse ::= SEQUENCE {
      responseStatus         OCSPResponseStatus,
      responseBytes          [0] EXPLICIT ResponseBytes OPTIONAL }

   OCSPResponseStatus ::= ENUMERATED {
       successful            (0),  --Response has valid confirmations
       malformedRequest      (1),  --Illegal confirmation request
       internalError         (2),  --Internal error in issuer
       tryLater              (3),  --Try again later
                                   --(4) is not used
       sigRequired           (5),  --Must sign the request
       unauthorized          (6)   --Request unauthorized
   }

   ResponseBytes ::=       SEQUENCE {
       responseType   OBJECT IDENTIFIER,
       response       OCTET STRING }

   id-pkix-ocsp           OBJECT IDENTIFIER ::= { id-ad-ocsp }
   id-pkix-ocsp-basic     OBJECT IDENTIFIER ::= { id-pkix-ocsp 1 }

   The value for response SHALL be the DER encoding of
   BasicOCSPResponse.

   BasicOCSPResponse       ::= SEQUENCE {
      tbsResponseData      ResponseData,
      signatureAlgorithm   AlgorithmIdentifier,
      signature            BIT STRING,
      certs                [0] EXPLICIT SEQUENCE OF Certificate OPTIONAL }

   The value for signature SHALL be computed on the hash of the DER
   encoding ResponseData.

   ResponseData ::= SEQUENCE {
      version              [0] EXPLICIT Version DEFAULT v1,
      responderID              ResponderID,
      producedAt               GeneralizedTime,
      responses                SEQUENCE OF SingleResponse,
      responseExtensions   [1] EXPLICIT Extensions OPTIONAL }

   ResponderID ::= CHOICE {
      byName               [1] Name,
      byKey                [2] KeyHash }

   KeyHash ::= OCTET STRING -- SHA-1 hash of responder's public key
   (excluding the tag and length fields)

   SingleResponse ::= SEQUENCE {
      certID                       CertID,
      certStatus                   CertStatus,
      thisUpdate                   GeneralizedTime,
      nextUpdate         [0]       EXPLICIT GeneralizedTime OPTIONAL,
      singleExtensions   [1]       EXPLICIT Extensions OPTIONAL }

   CertStatus ::= CHOICE {
       good        [0]     IMPLICIT NULL,
       revoked     [1]     IMPLICIT RevokedInfo,
       unknown     [2]     IMPLICIT UnknownInfo }

   RevokedInfo ::= SEQUENCE {
       revocationTime              GeneralizedTime,
       revocationReason    [0]     EXPLICIT CRLReason OPTIONAL }

   UnknownInfo ::= NULL -- this can be replaced with an enumeration
*/

static CFAbsoluteTime genTimeToCFAbsTime(const SecAsn1Item *datetime)
{
    return SecAbsoluteTimeFromDateContent(SEC_ASN1_GENERALIZED_TIME,
        datetime->Data, datetime->Length);
}

void SecOCSPSingleResponseDestroy(SecOCSPSingleResponseRef this) {
    free(this);
}

static SecOCSPSingleResponseRef SecOCSPSingleResponseCreate(
    SecAsn1OCSPSingleResponse *resp, SecAsn1CoderRef coder) {
	assert(resp != NULL);
    SecOCSPSingleResponseRef this;
    require(this = (SecOCSPSingleResponseRef)
        malloc(sizeof(struct __SecOCSPSingleResponse)), errOut);
    this->certStatus = CS_NotParsed;
	this->thisUpdate = NULL_TIME;
	this->nextUpdate = NULL_TIME;
	this->revokedTime = NULL_TIME;
	this->crlReason = kSecRevocationReasonUndetermined;
	//this->extensions = NULL;

	if ((resp->certStatus.Data == NULL) || (resp->certStatus.Length == 0)) {
		ocspdErrorLog("OCSPSingleResponse: bad certStatus");
        goto errOut;
	}
	this->certStatus = (SecAsn1OCSPCertStatusTag)(resp->certStatus.Data[0] & SEC_ASN1_TAGNUM_MASK);
	if (this->certStatus == CS_Revoked) {
		/* Decode further to get SecAsn1OCSPRevokedInfo */
		SecAsn1OCSPCertStatus certStatus;
		memset(&certStatus, 0, sizeof(certStatus));
		if (SecAsn1DecodeData(coder, &resp->certStatus,
				kSecAsn1OCSPCertStatusRevokedTemplate, &certStatus)) {
			ocspdErrorLog("OCSPSingleResponse: err decoding certStatus");
            goto errOut;
		}
		SecAsn1OCSPRevokedInfo *revokedInfo = certStatus.revokedInfo;
		if (revokedInfo != NULL) {
			/* Treat this as optional even for CS_Revoked */
			this->revokedTime = genTimeToCFAbsTime(&revokedInfo->revocationTime);
			const SecAsn1Item *revReason = revokedInfo->revocationReason;
			if((revReason != NULL) &&
			   (revReason->Data != NULL) &&
			   (revReason->Length != 0)) {
			   this->crlReason = revReason->Data[0];
			}
		}
	}
	this->thisUpdate = genTimeToCFAbsTime(&resp->thisUpdate);
	if (resp->nextUpdate != NULL) {
		this->nextUpdate = genTimeToCFAbsTime(resp->nextUpdate);
	}
	//mExtensions = new OCSPExtensions(resp->singleExtensions);
	ocspdDebug("status %d reason %d", (int)this->certStatus,
		(int)this->crlReason);
    return this;
errOut:
    if (this)
        SecOCSPSingleResponseDestroy(this);
    return NULL;
}

/* Calculate temporal validity; set latestNextUpdate and expireTime. Only
   called from SecOCSPResponseCreate. Returns true if valid, else returns
   false. */
static bool SecOCSPResponseCalculateValidity(SecOCSPResponseRef this,
    CFTimeInterval maxAge, CFTimeInterval defaultTTL)
{
	this->latestNextUpdate = NULL_TIME;
	CFAbsoluteTime now = this->verifyTime = CFAbsoluteTimeGetCurrent();

    if (this->producedAt > now) {
        ocspdErrorLog("OCSPResponse: producedAt later than current time");
        return false;
    }

    /* Make this->latestNextUpdate be the date farthest in the future
       of any of the singleResponses nextUpdate fields. */
    SecAsn1OCSPSingleResponse **responses;
    for (responses = this->responseData.responses; *responses; ++responses) {
		SecAsn1OCSPSingleResponse *resp = *responses;
		
		/* thisUpdate later than 'now' invalidates the whole response. */
		CFAbsoluteTime thisUpdate = genTimeToCFAbsTime(&resp->thisUpdate);
		if (thisUpdate > now) {
			ocspdErrorLog("OCSPResponse: thisUpdate later than current time");
			return false;
		}

		/* Keep track of latest nextUpdate. */
		if (resp->nextUpdate != NULL) {
			CFAbsoluteTime nextUpdate = genTimeToCFAbsTime(resp->nextUpdate);
			if (nextUpdate > this->latestNextUpdate) {
				this->latestNextUpdate = nextUpdate;
			}
		}
#ifdef STRICT_RFC5019
        else {
            /* RFC 5019 section 2.2.4 states on nextUpdate:
                 Responders MUST always include this value to aid in
                 response caching.  See Section 6 for additional
                 information on caching.
            */
			ocspdErrorLog("OCSPResponse: nextUpdate not present");
			return false;
        }
#endif
	}

    /* Now that we have this->latestNextUpdate, we figure out the latest
       date at which we will expire this response from our cache.  To comply
       with rfc5019s:

6.1.  Caching at the Client

   To minimize bandwidth usage, clients MUST locally cache authoritative
   OCSP responses (i.e., a response with a signature that has been
   successfully validated and that indicate an OCSPResponseStatus of
   'successful').

   Most OCSP clients will send OCSPRequests at or near the nextUpdate
   time (when a cached response expires).  To avoid large spikes in
   responder load that might occur when many clients refresh cached
   responses for a popular certificate, responders MAY indicate when the
   client should fetch an updated OCSP response by using the cache-
   control:max-age directive.  Clients SHOULD fetch the updated OCSP
   Response on or after the max-age time.  To ensure that clients
   receive an updated OCSP response, OCSP responders MUST refresh the
   OCSP response before the max-age time.

6.2 [...]

       we need to take the cache-control:max-age directive into account.

       The way the code below is written we ignore a max-age=0 in the
       http header.  Since a value of 0 (NULL_TIME) also means there
       was no max-age in the header. This seems ok since that would imply
       no-cache so we also ignore negative values for the same reason,
       instead we'll expire whenever this->latestNextUpdate tells us to,
       which is the signed value if max-age is too low, since we don't
       want to refetch multilple times for a single page load in a browser. */
	if (this->latestNextUpdate == NULL_TIME) {
        /* See comment above on RFC 5019 section 2.2.4. */
		/* Absolute expire time = current time plus defaultTTL */
		this->expireTime = now + defaultTTL;
	} else if (this->latestNextUpdate < now) {
			ocspdErrorLog("OCSPResponse: now > latestNextUpdate");
			return false;
    } else if (maxAge > 0) {
        /* Beware of double overflows such as:

               now + maxAge < this->latestNextUpdate

           in the math below since an attacker could create any positive
           value for maxAge. */
        if (maxAge < this->latestNextUpdate - now) {
            /* maxAge header wants us to expire the cache entry sooner than
               nextUpdate would allow, to balance server load. */
            this->expireTime = now + maxAge;
        } else {
            /* maxAge http header attempting to make us cache the response
               longer than it's valid for, bad http header! Ignoring you. */
            ocspdErrorLog("OCSPResponse: now + maxAge > latestNextUpdate,"
                " using latestNextUpdate");
            this->expireTime = this->latestNextUpdate;
        }
	} else {
        /* No maxAge provided, just use latestNextUpdate. */
		this->expireTime = this->latestNextUpdate;
    }

	return true;
}

SecOCSPResponseRef SecOCSPResponseCreate(CFDataRef ocspResponse,
    CFTimeInterval maxAge) {
	SecAsn1OCSPResponse topResp = {};
    SecOCSPResponseRef this;

    require(this = (SecOCSPResponseRef)calloc(1, sizeof(struct __SecOCSPResponse)),
        errOut);
    require_noerr(SecAsn1CoderCreate(&this->coder), errOut);

    this->data = ocspResponse;
    CFRetain(ocspResponse);

    SecAsn1Item resp;
    resp.Length = CFDataGetLength(ocspResponse);
    resp.Data = (uint8_t *)CFDataGetBytePtr(ocspResponse);
	if (SecAsn1DecodeData(this->coder, &resp, kSecAsn1OCSPResponseTemplate,
        &topResp)) {
		ocspdErrorLog("OCSPResponse: decode failure at top level");
	}
	/* remainder is valid only on RS_Success */
	if ((topResp.responseStatus.Data == NULL) ||
	   (topResp.responseStatus.Length == 0)) {
		ocspdErrorLog("OCSPResponse: no responseStatus");
        goto errOut;
	}
    this->responseStatus = topResp.responseStatus.Data[0];
	if (this->responseStatus != kSecOCSPSuccess) {
		secdebug("ocsp", "OCSPResponse: status: %d", this->responseStatus);
		/* not a failure of our constructor; this object is now useful, but
		 * only for this one byte of status info */
		return this;
	}
	if (topResp.responseBytes == NULL) {
		/* I don't see how this can be legal on RS_Success */
		ocspdErrorLog("OCSPResponse: empty responseBytes");
        goto errOut;
	}
    if (!SecAsn1OidCompare(&topResp.responseBytes->responseType,
			&OID_PKIX_OCSP_BASIC)) {
		ocspdErrorLog("OCSPResponse: unknown responseType");
        goto errOut;

	}

	/* decode the SecAsn1OCSPBasicResponse */
	if (SecAsn1DecodeData(this->coder, &topResp.responseBytes->response,
			kSecAsn1OCSPBasicResponseTemplate, &this->basicResponse)) {
		ocspdErrorLog("OCSPResponse: decode failure at SecAsn1OCSPBasicResponse");
        goto errOut;
	}

	/* signature and cert evaluation done externally */

	/* decode the SecAsn1OCSPResponseData */
	if (SecAsn1DecodeData(this->coder, &this->basicResponse.tbsResponseData,
			kSecAsn1OCSPResponseDataTemplate, &this->responseData)) {
		ocspdErrorLog("OCSPResponse: decode failure at SecAsn1OCSPResponseData");
        goto errOut;
	}
    this->producedAt = genTimeToCFAbsTime(&this->responseData.producedAt);
    if (this->producedAt == NULL_TIME) {
		ocspdErrorLog("OCSPResponse: bad producedAt");
        goto errOut;
    }

	if (this->responseData.responderID.Data == NULL) {
		ocspdErrorLog("OCSPResponse: bad responderID");
        goto errOut;
	}

	/* Choice processing for ResponderID */
    this->responderIdTag = (SecAsn1OCSPResponderIDTag)
		(this->responseData.responderID.Data[0] & SEC_ASN1_TAGNUM_MASK);
	const SecAsn1Template *templ;
	switch(this->responderIdTag) {
		case RIT_Name:
            /* @@@ Since we don't use the decoded byName value we could skip
               decoding it but we do it anyway for validation. */
			templ = kSecAsn1OCSPResponderIDAsNameTemplate;
			break;
		case RIT_Key:
			templ = kSecAsn1OCSPResponderIDAsKeyTemplate;
			break;
		default:
			ocspdErrorLog("OCSPResponse: bad responderID tag");
            goto errOut;
	}
	if (SecAsn1DecodeData(this->coder, &this->responseData.responderID, templ,
        &this->responderID)) {
		ocspdErrorLog("OCSPResponse: decode failure at responderID");
        goto errOut;
	}

    /* We should probably get the defaultTTL from the policy.
       For now defaultTTL is hardcoded to 24 hours. */
    CFTimeInterval defaultTTL = 24 * 60 * 60;
	/* Check temporal validity, default TTL 24 hours. */
    require_quiet(SecOCSPResponseCalculateValidity(this, maxAge, defaultTTL), errOut);

#if 0
	/* Individual responses looked into when we're asked for a specific one
	   via SecOCSPResponseCopySingleResponse(). */
	mExtensions = new OCSPExtensions(mResponseData.responseExtensions);
#endif

    return this;
errOut:
    if (this) {
        SecOCSPResponseFinalize(this);
    }
    return NULL;
}

CFDataRef SecOCSPResponseGetData(SecOCSPResponseRef this) {
    return this->data;
}

SecOCSPResponseStatus SecOCSPGetResponseStatus(SecOCSPResponseRef this) {
    return this->responseStatus;
}

CFAbsoluteTime SecOCSPResponseGetExpirationTime(SecOCSPResponseRef this) {
    return this->expireTime;
}

CFDataRef SecOCSPResponseGetNonce(SecOCSPResponseRef this) {
    return this->nonce;
}

CFAbsoluteTime SecOCSPResponseProducedAt(SecOCSPResponseRef this) {
    return this->producedAt;
}

CFAbsoluteTime SecOCSPResponseVerifyTime(SecOCSPResponseRef this) {
    return this->verifyTime;
}

CFArrayRef SecOCSPResponseCopySigners(SecOCSPResponseRef this) {
    return NULL;
}

void SecOCSPResponseFinalize(SecOCSPResponseRef this) {
    CFReleaseSafe(this->data);
    SecAsn1CoderRelease(this->coder);
    free(this);
}

SecOCSPSingleResponseRef SecOCSPResponseCopySingleResponse(
    SecOCSPResponseRef this, SecOCSPRequestRef request) {
    SecOCSPSingleResponseRef sr = NULL;

    CFDataRef issuer = SecCertificateCopyIssuerSequence(request->certificate);
    const DERItem *publicKey = SecCertificateGetPublicKeyData(request->issuer);
    CFDataRef serial = SecCertificateCopySerialNumber(request->certificate);
    CFDataRef issuerNameHash = NULL;
    CFDataRef issuerPubKeyHash = NULL;
    SecAsn1Oid *algorithm = NULL;
    SecAsn1Item *parameters = NULL;

    /* We should probably get the defaultTTL from the policy.
       For now defaultTTL is hardcoded to 24 hours. This is how long we trust
       a response without a nextUpdate field.  */
    CFTimeInterval defaultTTL = 24 * 60 * 60;

    SecAsn1OCSPSingleResponse **responses;
    for (responses = this->responseData.responses; *responses; ++responses) {
		SecAsn1OCSPSingleResponse *resp = *responses;
        SecAsn1OCSPCertID *certId = &resp->certID;
        /* First check the easy part, serial number should match. */
        if (certId->serialNumber.Length != (size_t)CFDataGetLength(serial) ||
            memcmp(CFDataGetBytePtr(serial), certId->serialNumber.Data,
                certId->serialNumber.Length)) {
            /* Serial # mismatch, skip this singleResponse. */
            continue;
        }

        /* Calcluate the issuerKey and issuerName digests using the
           hashAlgorithm and parameters specified in the certId, if
           they differ from the ones we already computed. */
        if (!SecAsn1OidCompare(algorithm, &certId->algId.algorithm) ||
            !SecAsn1OidCompare(parameters, &certId->algId.parameters)) {
            algorithm = &certId->algId.algorithm;
            parameters = &certId->algId.parameters;
            CFReleaseSafe(issuerNameHash);
            CFReleaseSafe(issuerPubKeyHash);
            issuerNameHash = SecDigestCreate(kCFAllocatorDefault, algorithm,
                parameters, CFDataGetBytePtr(issuer), CFDataGetLength(issuer));
            issuerPubKeyHash = SecDigestCreate(kCFAllocatorDefault, algorithm,
                parameters, publicKey->data, publicKey->length);
        }

        if (certId->issuerNameHash.Length == (size_t)CFDataGetLength(issuerNameHash)
            && !memcmp(CFDataGetBytePtr(issuerNameHash),
                certId->issuerNameHash.Data, certId->issuerNameHash.Length)
            && certId->issuerPubKeyHash.Length == (size_t)CFDataGetLength(issuerPubKeyHash)
            && !memcmp(CFDataGetBytePtr(issuerPubKeyHash),
                certId->issuerPubKeyHash.Data, certId->issuerPubKeyHash.Length)) {

            CFAbsoluteTime thisUpdate = genTimeToCFAbsTime(&resp->thisUpdate);
            if (thisUpdate > this->verifyTime) {
                ocspdErrorLog("OCSPSingleResponse: thisUpdate > now");
                continue;
            }

            if (resp->nextUpdate == NULL) {
                /* rfc2560 section 2.4 states: "If nextUpdate is not set, the
                   responder is indicating that newer revocation information
                   is available all the time".
                   Let's ensure that thisUpdate isn't more than defaultTTL in
                   the past then. */
                if (this->verifyTime > thisUpdate + defaultTTL) {
                    ocspdErrorLog("OCSPSingleResponse: no nextUpdate present "
                        "and now > thisUpdate + defaultTTL");
                    continue;
                }
            } else {
                CFAbsoluteTime nextUpdate = genTimeToCFAbsTime(resp->nextUpdate);
                if (this->verifyTime > nextUpdate) {
                    ocspdErrorLog("OCSPSingleResponse: now > nextUpdate");
                    continue;
                }
            }

            /* resp matches the certificate in request, so let's use it. */
            sr = SecOCSPSingleResponseCreate(resp, this->coder);
            if (sr) {
                ocspdDebug("found matching singleResponse");
                break;
            }
        }
	}

    CFReleaseSafe(issuerPubKeyHash);
    CFReleaseSafe(issuerNameHash);
    CFReleaseSafe(serial);
    CFReleaseSafe(issuer);

    if (!sr) {
        ocspdDebug("certID not found");
    }

	return sr;
}

static bool SecOCSPResponseVerifySignature(SecOCSPResponseRef this,
    SecKeyRef key) {
	/* Beware this->basicResponse.sig: on decode, length is in BITS */
    return SecKeyDigestAndVerify(key, &this->basicResponse.algId,
        this->basicResponse.tbsResponseData.Data,
        this->basicResponse.tbsResponseData.Length,
        this->basicResponse.sig.Data,
        this->basicResponse.sig.Length / 8) == noErr;
}

static bool SecOCSPResponseIsIssuer(SecOCSPResponseRef this,
    SecCertificatePathRef issuer) {
    bool shouldBeSigner = false;
    SecCertificateRef signer = SecCertificatePathGetCertificateAtIndex(issuer, 0);
	if (this->responderIdTag == RIT_Name) {
		/* Name inside response must == signer's SubjectName. */
        CFDataRef subject = SecCertificateCopySubjectSequence(signer);
        if (!subject) {
			ocspdDebug("error on SecCertificateCopySubjectSequence");
			return false;
		}
        if ((size_t)CFDataGetLength(subject) == this->responderID.byName.Length &&
            !memcmp(this->responderID.byName.Data, CFDataGetBytePtr(subject),
                this->responderID.byName.Length)) {
			ocspdDebug("good ResponderID.byName");
			shouldBeSigner = true;
        } else {
			ocspdDebug("BAD ResponderID.byName");
		}
        CFRelease(subject);
    } else /* if (this->responderIdTag == RIT_Key) */ {
		/* ResponderID.byKey must == SHA1(signer's public key) */
        CFDataRef pubKeyDigest = SecCertificateCopyPublicKeySHA1Digest(signer);
        if ((size_t)CFDataGetLength(pubKeyDigest) == this->responderID.byKey.Length &&
            !memcmp(this->responderID.byKey.Data, CFDataGetBytePtr(pubKeyDigest),
                this->responderID.byKey.Length)) {
			ocspdDebug("good ResponderID.byKey");
			shouldBeSigner = true;
		} else {
			ocspdDebug("BAD ResponderID.byKey");
		}
        CFRelease(pubKeyDigest);
    }

    if (shouldBeSigner) {
        SecKeyRef key = SecCertificatePathCopyPublicKeyAtIndex(issuer, 0);
        if (key) {
            shouldBeSigner = SecOCSPResponseVerifySignature(this, key);
            ocspdDebug("ocsp response signature %sok", shouldBeSigner ? "" : "not ");
            CFRelease(key);
        } else {
			ocspdDebug("Failed to extract key from leaf certificate");
            shouldBeSigner = false;
        }
    }

    return shouldBeSigner;
}

/* Returns the SecCertificatePathRef who's leaf signed this ocspResponse if
   we can find one and NULL if we can't find a valid signer. */
SecCertificatePathRef SecOCSPResponseCopySigner(SecOCSPResponseRef this,
    SecCertificatePathRef issuer) {
    SecCertificateRef issuerCert = SecCertificatePathGetCertificateAtIndex(issuer, 0);
    CFDataRef issuerSubject = SecCertificateGetNormalizedSubjectContent(issuerCert);
    /* Look though any certs that came with the response and see if they were
       both issued by the issuerPath and signed the response. */
    SecAsn1Item **certs;
    for (certs = this->basicResponse.certs; certs && *certs; ++certs) {
        SecCertificateRef cert = SecCertificateCreateWithBytes(
            kCFAllocatorDefault, (*certs)->Data, (*certs)->Length);
        if (cert) {
            CFDataRef certIssuer = SecCertificateGetNormalizedIssuerContent(cert);
            if (CFEqual(issuerSubject, certIssuer)) {
                SecCertificatePathRef signer = SecCertificatePathCopyAddingLeaf(issuer, cert);
                CFRelease(cert);
                if (signer) {
                    if (SecOCSPResponseIsIssuer(this, signer)) {
                        return signer;
                    } else {
                        ocspdErrorLog("ocsp response cert not signed by issuer.");
                        CFRelease(signer);
                    }
                }
            } else {
                ocspdErrorLog("ocsp response cert issuer doesn't match issuer subject.");
            }
        } else {
			ocspdErrorLog("ocsp response cert failed to parse");
        }
	}

    /* If none of the returned certs work, try the issuer of the certificate
       being checked directly. */
    if (SecOCSPResponseIsIssuer(this, issuer)) {
        CFRetain(issuer);
        return issuer;
    }

    /* We couldn't find who signed this ocspResponse, give up. */
    return NULL;
}