X-Git-Url: https://git.saurik.com/apple/security.git/blobdiff_plain/5dd5f9ec28f304ca377c42fd7f711d6cf12b90e1..5c19dc3ae3bd8e40a9c028b0deddd50ff337692c:/OSX/sec/securityd/SecOCSPResponse.c diff --git a/OSX/sec/securityd/SecOCSPResponse.c b/OSX/sec/securityd/SecOCSPResponse.c new file mode 100644 index 00000000..138f4836 --- /dev/null +++ b/OSX/sec/securityd/SecOCSPResponse.c @@ -0,0 +1,683 @@ +/* + * Copyright (c) 2008-2009,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@ + */ + +/* + * SecOCSPResponse.c - Wrapper to decode ocsp responses. + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "SecInternal.h" +#include +#include + + +#define ocspdErrorLog(args, ...) secerror(args, ## __VA_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) + calloc(1, sizeof(struct __SecOCSPSingleResponse)), errOut); + this->certStatus = CS_NotParsed; + this->thisUpdate = NULL_TIME; + this->nextUpdate = NULL_TIME; + this->revokedTime = NULL_TIME; + this->crlReason = kSecRevocationReasonUndetermined; + this->scts = 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 (this->thisUpdate == NULL_TIME) { + ocspdErrorLog("OCSPResponse: bad thisUpdate DER"); + goto errOut; + } + + if (resp->nextUpdate != NULL) { + this->nextUpdate = genTimeToCFAbsTime(resp->nextUpdate); + if (this->nextUpdate == NULL_TIME) { + ocspdErrorLog("OCSPResponse: bad nextUpdate DER"); + goto errOut; + } + } + + /* Lookup through extensions to find SCTs */ + if(resp->singleExtensions) { + ocspdErrorLog("OCSPResponse: single response has extension(s)."); + int i = 0; + NSS_CertExtension *extn; + while ((extn = resp->singleExtensions[i])) { + if(SecAsn1OidCompare(&extn->extnId, &OID_GOOGLE_OCSP_SCT )) { + ocspdErrorLog("OCSPResponse: single response has an SCT extension."); + SecAsn1Item sct_data = {0,}; + + // Note: if there are more that one valid SCT extension, we just use the first one that successfully decoded + if((this->scts == NULL) && (SecAsn1DecodeData(coder, &extn->value, kSecAsn1OctetStringTemplate, &sct_data) == 0)) { + this->scts = SecCreateSignedCertificateTimestampsArrayFromSerializedSCTList(sct_data.Data, sct_data.Length); + ocspdErrorLog("OCSPResponse: single response has an SCT extension, parsed = %p.", this->scts); + } + } + i++; + } + } + + ocspdDebug("status %d reason %d", (int)this->certStatus, + (int)this->crlReason); + return this; +errOut: + if (this) + SecOCSPSingleResponseDestroy(this); + return NULL; +} + +#define LEEWAY (4500.0) + +/* Calculate temporal validity; set latestNextUpdate and expireTime. + Returns true if valid, else returns false. */ +bool SecOCSPResponseCalculateValidity(SecOCSPResponseRef this, + CFTimeInterval maxAge, CFTimeInterval defaultTTL, CFAbsoluteTime verifyTime) +{ + bool ok = false; + this->latestNextUpdate = NULL_TIME; + CFStringRef hexResp = CFDataCopyHexString(this->data); + + if (this->producedAt > verifyTime + LEEWAY) { + ocspdErrorLog("OCSPResponse: producedAt more than 1:15 from now %@", hexResp); + goto exit; + } + + /* 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 > verifyTime + LEEWAY) { + ocspdErrorLog("OCSPResponse: thisUpdate more than 1:15 from now %@", hexResp); + goto exit; + } + + /* Keep track of latest nextUpdate. */ + if (resp->nextUpdate != NULL) { + CFAbsoluteTime nextUpdate = genTimeToCFAbsTime(resp->nextUpdate); + if (nextUpdate > this->latestNextUpdate) { + this->latestNextUpdate = nextUpdate; + } + } + 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 %@", hexResp); +#ifdef STRICT_RFC5019 + goto exit; +#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 = verifyTime + defaultTTL; + } else if (this->latestNextUpdate < verifyTime - LEEWAY) { + ocspdErrorLog("OCSPResponse: latestNextUpdate more than 1:15 ago %@", hexResp); + goto exit; + } 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 - verifyTime) { + /* maxAge header wants us to expire the cache entry sooner than + nextUpdate would allow, to balance server load. */ + this->expireTime = verifyTime + maxAge; + } else { + /* maxAge http header attempting to make us cache the response + longer than it's valid for, bad http header! Ignoring you. */ + ocspdDebug("OCSPResponse: now + maxAge > latestNextUpdate," + " using latestNextUpdate %@", hexResp); + this->expireTime = this->latestNextUpdate; + } + } else { + /* No maxAge provided, just use latestNextUpdate. */ + this->expireTime = this->latestNextUpdate; + } + + ok = true; +exit: + CFReleaseSafe(hexResp); + return ok; +} + +SecOCSPResponseRef SecOCSPResponseCreateWithID(CFDataRef ocspResponse, int64_t responseID) { + CFStringRef hexResp = CFDataCopyHexString(ocspResponse); + SecAsn1OCSPResponse topResp = {}; + SecOCSPResponseRef this; + + require(this = (SecOCSPResponseRef)calloc(1, sizeof(struct __SecOCSPResponse)), + errOut); + require_noerr(SecAsn1CoderCreate(&this->coder), errOut); + + this->data = ocspResponse; + this->responseID = responseID; + 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 %@", hexResp); + } + /* remainder is valid only on RS_Success */ + if ((topResp.responseStatus.Data == NULL) || + (topResp.responseStatus.Length == 0)) { + ocspdErrorLog("OCSPResponse: no responseStatus %@", hexResp); + goto errOut; + } + this->responseStatus = topResp.responseStatus.Data[0]; + if (this->responseStatus != kSecOCSPSuccess) { + secdebug("ocsp", "OCSPResponse: status: %d %@", this->responseStatus, hexResp); + /* not a failure of our constructor; this object is now useful, but + * only for this one byte of status info */ + goto fini; + } + if (topResp.responseBytes == NULL) { + /* I don't see how this can be legal on RS_Success */ + ocspdErrorLog("OCSPResponse: empty responseBytes %@", hexResp); + goto errOut; + } + if (!SecAsn1OidCompare(&topResp.responseBytes->responseType, + &OID_PKIX_OCSP_BASIC)) { + ocspdErrorLog("OCSPResponse: unknown responseType %@", hexResp); + goto errOut; + + } + + /* decode the SecAsn1OCSPBasicResponse */ + if (SecAsn1DecodeData(this->coder, &topResp.responseBytes->response, + kSecAsn1OCSPBasicResponseTemplate, &this->basicResponse)) { + ocspdErrorLog("OCSPResponse: decode failure at SecAsn1OCSPBasicResponse %@", hexResp); + 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 %@", hexResp); + goto errOut; + } + this->producedAt = genTimeToCFAbsTime(&this->responseData.producedAt); + if (this->producedAt == NULL_TIME) { + ocspdErrorLog("OCSPResponse: bad producedAt %@", hexResp); + goto errOut; + } + + if (this->responseData.responderID.Data == NULL) { + ocspdErrorLog("OCSPResponse: bad responderID %@", hexResp); + 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 %@", hexResp); + goto errOut; + } + if (SecAsn1DecodeData(this->coder, &this->responseData.responderID, templ, + &this->responderID)) { + ocspdErrorLog("OCSPResponse: decode failure at responderID %@", hexResp); + goto errOut; + } + +fini: + CFReleaseSafe(hexResp); + return this; +errOut: + CFReleaseSafe(hexResp); + if (this) { + SecOCSPResponseFinalize(this); + } + return NULL; +} + +SecOCSPResponseRef SecOCSPResponseCreate(CFDataRef this) { + return SecOCSPResponseCreateWithID(this, -1); +} + +int64_t SecOCSPResponseGetID(SecOCSPResponseRef this) { + return this->responseID; +} + +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; +} + +CFArrayRef SecOCSPResponseCopySigners(SecOCSPResponseRef this) { + return NULL; +} + +void SecOCSPResponseFinalize(SecOCSPResponseRef this) { + CFReleaseSafe(this->data); + CFReleaseSafe(this->nonce); + SecAsn1CoderRelease(this->coder); + free(this); +} + +static CFAbsoluteTime SecOCSPSingleResponseComputedNextUpdate(SecOCSPSingleResponseRef this, CFTimeInterval defaultTTL) { + /* 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. */ + return this->nextUpdate == NULL_TIME ? this->thisUpdate + defaultTTL : this->nextUpdate; +} + +bool SecOCSPSingleResponseCalculateValidity(SecOCSPSingleResponseRef this, CFTimeInterval defaultTTL, CFAbsoluteTime verifyTime) { + if (this->thisUpdate > verifyTime + LEEWAY) { + ocspdErrorLog("OCSPSingleResponse: thisUpdate more than 1:15 from now"); + return false; + } + + CFAbsoluteTime cnu = SecOCSPSingleResponseComputedNextUpdate(this, defaultTTL); + if (verifyTime - LEEWAY > cnu) { + ocspdErrorLog("OCSPSingleResponse: %s %.2f days ago", this->nextUpdate ? "nextUpdate" : "thisUpdate + defaultTTL", (verifyTime - cnu) / 86400); + return false; + } + + return true; +} + +CFArrayRef SecOCSPSingleResponseCopySCTs(SecOCSPSingleResponseRef this) +{ + return CFRetainSafe(this->scts); +} + + +SecOCSPSingleResponseRef SecOCSPResponseCopySingleResponse( + SecOCSPResponseRef this, SecOCSPRequestRef request) { + SecOCSPSingleResponseRef sr = NULL; + + CFDataRef issuer = SecCertificateCopyIssuerSequence(request->certificate); + const DERItem *publicKey = SecCertificateGetPublicKeyData(request->issuer); +#if (TARGET_OS_MAC && !(TARGET_OS_EMBEDDED || TARGET_OS_IPHONE)) + CFDataRef serial = SecCertificateCopySerialNumber(request->certificate, NULL); +#else + CFDataRef serial = SecCertificateCopySerialNumber(request->certificate); +#endif + CFDataRef issuerNameHash = NULL; + CFDataRef issuerPubKeyHash = NULL; + SecAsn1Oid *algorithm = NULL; + SecAsn1Item *parameters = NULL; + + 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 (!issuerNameHash || !issuerPubKeyHash) { + /* This can happen when the hash algorithm is not supported, shoudl be really rare */ + /* See also: CrashTracer: securityd at securityd: SecOCSPResponseCopySingleResponse */ + ocspdErrorLog("Unknown hash algorithm in singleResponse"); + algorithm = NULL; + parameters = NULL; + continue; + } + + 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)) { + + /* 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) == errSecSuccess; +} + +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 whose 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 (certIssuer && 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; +}