#include "trust/trustd/SecOCSPResponse.h"
#include <asl.h>
#include <AssertMacros.h>
#include <CommonCrypto/CommonDigest.h>
#include <Security/SecCertificateInternal.h>
#include <Security/SecFramework.h>
#include <Security/SecKeyPriv.h>
#include <security_asn1/SecAsn1Coder.h>
#include <security_asn1/SecAsn1Templates.h>
#include <security_asn1/ocspTemplates.h>
#include <security_asn1/oidsocsp.h>
#include <stdlib.h>
#include "SecInternal.h"
#include <utilities/SecCFWrappers.h>
#include <utilities/SecSCTUtils.h>
#define ocspdErrorLog(args, ...) secerror(args, ## __VA_ARGS__)
#define ocspdHttpDebug(args...) secdebug("ocspdHttp", ## args)
#define ocspdDebug(args...) secdebug("ocsp", ## args)
static CFAbsoluteTime genTimeToCFAbsTime(const SecAsn1Item *datetime)
{
return SecAbsoluteTimeFromDateContent(SEC_ASN1_GENERALIZED_TIME,
datetime->Data, datetime->Length);
}
void SecOCSPSingleResponseDestroy(SecOCSPSingleResponseRef this) {
CFReleaseSafe(this->scts);
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) {
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) {
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;
}
}
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,};
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)
bool SecOCSPResponseCalculateValidity(SecOCSPResponseRef this,
CFTimeInterval maxAge, CFTimeInterval defaultTTL, CFAbsoluteTime verifyTime)
{
bool ok = false;
this->latestNextUpdate = NULL_TIME;
if (this->producedAt > verifyTime + LEEWAY) {
secnotice("ocsp", "OCSPResponse: producedAt more than 1:15 from now");
goto exit;
}
SecAsn1OCSPSingleResponse **responses;
for (responses = this->responseData.responses; *responses; ++responses) {
SecAsn1OCSPSingleResponse *resp = *responses;
CFAbsoluteTime thisUpdate = genTimeToCFAbsTime(&resp->thisUpdate);
if (thisUpdate > verifyTime + LEEWAY) {
secnotice("ocsp","OCSPResponse: thisUpdate more than 1:15 from now");
goto exit;
}
if (resp->nextUpdate != NULL) {
CFAbsoluteTime nextUpdate = genTimeToCFAbsTime(resp->nextUpdate);
if (nextUpdate > this->latestNextUpdate) {
this->latestNextUpdate = nextUpdate;
}
}
else {
secnotice("ocsp", "OCSPResponse: nextUpdate not present");
#ifdef STRICT_RFC5019
goto exit;
#endif
}
}
if (this->latestNextUpdate == NULL_TIME) {
this->expireTime = verifyTime + defaultTTL;
} else if (this->latestNextUpdate < verifyTime - LEEWAY) {
secnotice("ocsp", "OCSPResponse: latestNextUpdate more than 1:15 ago");
goto exit;
} else if (maxAge > 0) {
if (maxAge < this->latestNextUpdate - verifyTime) {
this->expireTime = verifyTime + maxAge;
} else {
#ifdef DEBUG
CFStringRef hexResp = CFDataCopyHexString(this->data);
ocspdDebug("OCSPResponse: now + maxAge > latestNextUpdate,"
" using latestNextUpdate %@", hexResp);
CFReleaseSafe(hexResp);
#endif
this->expireTime = this->latestNextUpdate;
}
} else {
this->expireTime = this->latestNextUpdate;
}
ok = true;
exit:
return ok;
}
SecOCSPResponseRef SecOCSPResponseCreateWithID(CFDataRef ocspResponse, int64_t responseID) {
SecAsn1OCSPResponse topResp = {};
SecOCSPResponseRef this = NULL;
require(ocspResponse, errOut);
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");
}
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) {
#ifdef DEBUG
CFStringRef hexResp = CFDataCopyHexString(this->data);
secdebug("ocsp", "OCSPResponse: status: %d %@", this->responseStatus, hexResp);
CFReleaseNull(hexResp);
#endif
goto fini;
}
if (topResp.responseBytes == NULL) {
ocspdErrorLog("OCSPResponse: empty responseBytes");
goto errOut;
}
if (!SecAsn1OidCompare(&topResp.responseBytes->responseType,
&OID_PKIX_OCSP_BASIC)) {
ocspdErrorLog("OCSPResponse: unknown responseType");
goto errOut;
}
if (SecAsn1DecodeData(this->coder, &topResp.responseBytes->response,
kSecAsn1OCSPBasicResponseTemplate, &this->basicResponse)) {
ocspdErrorLog("OCSPResponse: decode failure at SecAsn1OCSPBasicResponse");
goto errOut;
}
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;
}
this->responderIdTag = (SecAsn1OCSPResponderIDTag)
(this->responseData.responderID.Data[0] & SEC_ASN1_TAGNUM_MASK);
const SecAsn1Template *templ;
switch(this->responderIdTag) {
case RIT_Name:
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;
}
fini:
return this;
errOut:
#ifdef DEBUG
{
CFStringRef hexResp = (this) ? CFDataCopyHexString(this->data) : NULL;
secdebug("ocsp", "bad ocsp response: %@", hexResp);
CFReleaseSafe(hexResp);
}
#endif
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) {
CFMutableArrayRef result = NULL;
result = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
if (!result) {
return NULL;
}
SecAsn1Item **certs;
for (certs = this->basicResponse.certs; certs && *certs; ++certs) {
SecCertificateRef cert = NULL;
cert = SecCertificateCreateWithBytes(kCFAllocatorDefault, (*certs)->Data, (*certs)->Length);
if (cert) {
CFArrayAppendValue(result, cert);
CFReleaseNull(cert);
}
}
return result;
}
void SecOCSPResponseFinalize(SecOCSPResponseRef this) {
CFReleaseSafe(this->data);
CFReleaseSafe(this->nonce);
SecAsn1CoderRelease(this->coder);
free(this);
}
static CFAbsoluteTime SecOCSPSingleResponseComputedNextUpdate(SecOCSPSingleResponseRef this, CFTimeInterval defaultTTL) {
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;
if (!request) { return sr; }
CFDataRef issuer = SecCertificateCopyIssuerSequence(request->certificate);
const DERItem *publicKey = SecCertificateGetPublicKeyData(request->issuer);
CFDataRef serial = SecCertificateCopySerialNumberData(request->certificate, NULL);
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;
if (!serial || certId->serialNumber.Length != (size_t)CFDataGetLength(serial) ||
memcmp(CFDataGetBytePtr(serial), certId->serialNumber.Data,
certId->serialNumber.Length)) {
continue;
}
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) {
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)) {
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) {
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,
SecCertificateRef issuer) {
bool shouldBeSigner = false;
if (this->responderIdTag == RIT_Name) {
CFDataRef subject = SecCertificateCopySubjectSequence(issuer);
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 {
CFDataRef pubKeyDigest = SecCertificateCopyPublicKeySHA1Digest(issuer);
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 = SecCertificateCopyKey(issuer);
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;
}
SecCertificateRef SecOCSPResponseCopySigner(SecOCSPResponseRef this, SecCertificateRef issuer) {
SecAsn1Item **certs;
for (certs = this->basicResponse.certs; certs && *certs; ++certs) {
SecCertificateRef cert = SecCertificateCreateWithBytes(
kCFAllocatorDefault, (*certs)->Data, (*certs)->Length);
if (cert) {
if (SecOCSPResponseIsIssuer(this, cert)) {
return cert;
} else {
CFRelease(cert);
}
} else {
ocspdErrorLog("ocsp response cert failed to parse");
}
}
ocspdDebug("ocsp response did not contain a signer cert.");
if (issuer && SecOCSPResponseIsIssuer(this, issuer)) {
CFRetain(issuer);
return issuer;
}
return NULL;
}