X-Git-Url: https://git.saurik.com/apple/security.git/blobdiff_plain/72a12576750f52947eb043106ba5c12c0d07decf..b1ab9ed8d0e0f1c3b66d7daa8fd5564444c56195:/libsecurity_ocspd/common/ocspResponse.cpp diff --git a/libsecurity_ocspd/common/ocspResponse.cpp b/libsecurity_ocspd/common/ocspResponse.cpp new file mode 100644 index 00000000..8b4cb16f --- /dev/null +++ b/libsecurity_ocspd/common/ocspResponse.cpp @@ -0,0 +1,559 @@ +/* + * Copyright (c) 2004 Apple Computer, 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@ + */ + +/* + * ocspResponse.cpp - OCSP Response class + */ +#include "ocspResponse.h" +#include "ocspdUtils.h" +#include +#include +#include +#include "ocspdDebug.h" +#include +#include +#include + +/* malloc & copy CSSM_DATA using std malloc */ +static void allocCopyData( + const CSSM_DATA &src, + CSSM_DATA &dst) +{ + if(src.Length == 0) { + dst.Data = NULL; + dst.Length = 0; + return; + } + dst.Data = (uint8 *)malloc(src.Length); + memmove(dst.Data, src.Data, src.Length); + dst.Length = src.Length; +} + +/* std free() of a CSSM_DATA */ +static void freeData( + CSSM_DATA &d) +{ + if(d.Data) { + free(d.Data); + d.Data = NULL; + } + d.Length = 0; +} + +#pragma mark ---- OCSPClientCertID ---- + +/* + * Basic constructor given issuer's public key and name, and subject's + * serial number. + */ +OCSPClientCertID::OCSPClientCertID( + const CSSM_DATA &issuerName, + const CSSM_DATA &issuerPubKey, + const CSSM_DATA &subjectSerial) +{ + mEncoded.Data = NULL; + mEncoded.Length = 0; + allocCopyData(issuerName, mIssuerName); + allocCopyData(issuerPubKey, mIssuerPubKey); + allocCopyData(subjectSerial, mSubjectSerial); +} + +OCSPClientCertID::~OCSPClientCertID() +{ + freeData(mIssuerName); + freeData(mIssuerPubKey); + freeData(mSubjectSerial); + freeData(mEncoded); +} + +/* preencoded DER NULL */ +static uint8 nullParam[2] = {5, 0}; + +/* + * DER encode in specified coder's memory. + */ +const CSSM_DATA *OCSPClientCertID::encode() +{ + if(mEncoded.Data != NULL) { + return &mEncoded; + } + + SecAsn1OCSPCertID certID; + uint8 issuerNameHash[CC_SHA1_DIGEST_LENGTH]; + uint8 pubKeyHash[CC_SHA1_DIGEST_LENGTH]; + + /* algId refers to the hash we'll perform in issuer name and key */ + certID.algId.algorithm = CSSMOID_SHA1; + certID.algId.parameters.Data = nullParam; + certID.algId.parameters.Length = sizeof(nullParam); + + /* SHA1(issuerName) */ + ocspdSha1(mIssuerName.Data, mIssuerName.Length, issuerNameHash); + /* SHA1(issuer public key) */ + ocspdSha1(mIssuerPubKey.Data, mIssuerPubKey.Length, pubKeyHash); + + /* build the CertID from those components */ + certID.issuerNameHash.Data = issuerNameHash; + certID.issuerNameHash.Length = CC_SHA1_DIGEST_LENGTH; + certID.issuerPubKeyHash.Data = pubKeyHash; + certID.issuerPubKeyHash.Length = CC_SHA1_DIGEST_LENGTH; + certID.serialNumber = mSubjectSerial; + + /* encode */ + SecAsn1CoderRef coder; + SecAsn1CoderCreate(&coder); + + CSSM_DATA tmp = {0, NULL}; + SecAsn1EncodeItem(coder, &certID, kSecAsn1OCSPCertIDTemplate, &tmp); + allocCopyData(tmp, mEncoded); + SecAsn1CoderRelease(coder); + return &mEncoded; +} + +/* + * Does this object refer to the same cert as specified SecAsn1OCSPCertID? + * This is the main purpose of this class's existence; this function works + * even if specified SecAsn1OCSPCertID uses a different hash algorithm + * than we do, since we keep copies of our basic components. + * + * Returns true if compare successful. + */ +typedef void (*hashFcn)(const void *data, CC_LONG len, unsigned char *md); + +bool OCSPClientCertID::compareToExist( + const SecAsn1OCSPCertID &exist) +{ + /* easy part */ + if(!ocspdCompareCssmData(&mSubjectSerial, &exist.serialNumber)) { + return false; + } + + hashFcn hf = NULL; + const CSSM_OID *alg = &exist.algId.algorithm; + uint8 digest[OCSPD_MAX_DIGEST_LEN]; + CSSM_DATA digestData = {0, digest}; + + if(ocspdCompareCssmData(alg, &CSSMOID_SHA1)) { + hf = ocspdSha1; + digestData.Length = CC_SHA1_DIGEST_LENGTH; + } + else if(ocspdCompareCssmData(alg, &CSSMOID_MD5)) { + hf = ocspdMD5; + digestData.Length = CC_MD5_DIGEST_LENGTH; + } + else if(ocspdCompareCssmData(alg, &CSSMOID_MD4)) { + hf = ocspdMD4; + digestData.Length = CC_MD4_DIGEST_LENGTH; + } + /* an OID for SHA256? */ + else { + return false; + } + + /* generate digests using exist's hash algorithm */ + hf(mIssuerName.Data, mIssuerName.Length, digest); + if(!ocspdCompareCssmData(&digestData, &exist.issuerNameHash)) { + return false; + } + hf(mIssuerPubKey.Data, mIssuerPubKey.Length, digest); + if(!ocspdCompareCssmData(&digestData, &exist.issuerPubKeyHash)) { + return false; + } + + return true; +} + +bool OCSPClientCertID::compareToExist( + const CSSM_DATA &exist) +{ + SecAsn1CoderRef coder; + SecAsn1OCSPCertID certID; + bool brtn = false; + + SecAsn1CoderCreate(&coder); + memset(&certID, 0, sizeof(certID)); + if(SecAsn1DecodeData(coder, &exist, kSecAsn1OCSPCertIDTemplate, &certID)) { + goto errOut; + } + brtn = compareToExist(certID); +errOut: + SecAsn1CoderRelease(coder); + return brtn; +} + +#pragma mark ---- OCSPSingleResponse ---- + +/* + * Constructor, called by OCSPResponse. + */ +OCSPSingleResponse::OCSPSingleResponse( + SecAsn1OCSPSingleResponse *resp) + : mCertStatus(CS_NotParsed), + mThisUpdate(NULL_TIME), + mNextUpdate(NULL_TIME), + mRevokedTime(NULL_TIME), + mCrlReason(CrlReason_NONE), + mExtensions(NULL) +{ + assert(resp != NULL); + + SecAsn1CoderCreate(&mCoder); + if((resp->certStatus.Data == NULL) || (resp->certStatus.Length == 0)) { + ocspdErrorLog("OCSPSingleResponse: bad certStatus\n"); + CssmError::throwMe(CSSMERR_APPLETP_OCSP_BAD_RESPONSE); + } + mCertStatus = (SecAsn1OCSPCertStatusTag)(resp->certStatus.Data[0] & SEC_ASN1_TAGNUM_MASK); + if(mCertStatus == CS_Revoked) { + /* decode further to get SecAsn1OCSPRevokedInfo */ + SecAsn1OCSPCertStatus certStatus; + memset(&certStatus, 0, sizeof(certStatus)); + if(SecAsn1DecodeData(mCoder, &resp->certStatus, + kSecAsn1OCSPCertStatusRevokedTemplate, &certStatus)) { + ocspdErrorLog("OCSPSingleResponse: err decoding certStatus\n"); + CssmError::throwMe(CSSMERR_APPLETP_OCSP_BAD_RESPONSE); + } + SecAsn1OCSPRevokedInfo *revokedInfo = certStatus.revokedInfo; + if(revokedInfo != NULL) { + /* Treat this as optional even for CS_Revoked */ + mRevokedTime = genTimeToCFAbsTime(&revokedInfo->revocationTime); + const CSSM_DATA *revReason = revokedInfo->revocationReason; + if((revReason != NULL) && + (revReason->Data != NULL) && + (revReason->Length != 0)) { + mCrlReason = revReason->Data[0]; + } + } + } + mThisUpdate = genTimeToCFAbsTime(&resp->thisUpdate); + if(resp->nextUpdate != NULL) { + mNextUpdate = genTimeToCFAbsTime(resp->nextUpdate); + } + mExtensions = new OCSPExtensions(resp->singleExtensions); + ocspdDebug("OCSPSingleResponse: status %d reason %d", (int)mCertStatus, + (int)mCrlReason); +} + +OCSPSingleResponse::~OCSPSingleResponse() +{ + delete mExtensions; + SecAsn1CoderRelease(mCoder); +} + +/*** Extensions-specific accessors ***/ +const CSSM_DATA *OCSPSingleResponse::*crlUrl() +{ + /* TBD */ + return NULL; +} + +const CSSM_DATA *OCSPSingleResponse::crlNum() +{ + /* TBD */ + return NULL; + +} + +CFAbsoluteTime OCSPSingleResponse::crlTime() /* may be NULL_TIME */ +{ + /* TBD */ + return NULL_TIME; +} + +/* archive cutoff */ +CFAbsoluteTime OCSPSingleResponse::archiveCutoff() +{ + /* TBD */ + return NULL_TIME; +} + +#pragma mark ---- OCSPResponse ---- + +OCSPResponse::OCSPResponse( + const CSSM_DATA &resp, + CFTimeInterval defaultTTL) // default time-to-live in seconds + : mLatestNextUpdate(NULL_TIME), + mExpireTime(NULL_TIME), + mExtensions(NULL) +{ + SecAsn1CoderCreate(&mCoder); + memset(&mTopResp, 0, sizeof(mTopResp)); + memset(&mBasicResponse, 0, sizeof(mBasicResponse)); + memset(&mResponseData, 0, sizeof(mResponseData)); + memset(&mResponderId, 0, sizeof(mResponderId)); + mResponderIdTag = (SecAsn1OCSPResponderIDTag)0; // invalid + mEncResponderName.Data = NULL; + mEncResponderName.Length = 0; + + if(SecAsn1DecodeData(mCoder, &resp, kSecAsn1OCSPResponseTemplate, &mTopResp)) { + ocspdErrorLog("OCSPResponse: decode failure at top level\n"); + CssmError::throwMe(CSSMERR_APPLETP_OCSP_BAD_RESPONSE); + } + + /* remainder is valid only on RS_Success */ + if((mTopResp.responseStatus.Data == NULL) || + (mTopResp.responseStatus.Length == 0)) { + ocspdErrorLog("OCSPResponse: no responseStatus\n"); + CssmError::throwMe(CSSMERR_APPLETP_OCSP_BAD_RESPONSE); + } + if(mTopResp.responseStatus.Data[0] != RS_Success) { + /* not a failure of our constructor; this object is now useful, but + * only for this one byte of status info */ + return; + } + if(mTopResp.responseBytes == NULL) { + /* I don't see how this can be legal on RS_Success */ + ocspdErrorLog("OCSPResponse: empty responseBytes\n"); + CssmError::throwMe(CSSMERR_APPLETP_OCSP_BAD_RESPONSE); + } + if(!ocspdCompareCssmData(&mTopResp.responseBytes->responseType, + &CSSMOID_PKIX_OCSP_BASIC)) { + ocspdErrorLog("OCSPResponse: unknown responseType\n"); + CssmError::throwMe(CSSMERR_APPLETP_OCSP_BAD_RESPONSE); + } + + /* decode the SecAsn1OCSPBasicResponse */ + if(SecAsn1DecodeData(mCoder, &mTopResp.responseBytes->response, + kSecAsn1OCSPBasicResponseTemplate, &mBasicResponse)) { + ocspdErrorLog("OCSPResponse: decode failure at SecAsn1OCSPBasicResponse\n"); + CssmError::throwMe(CSSMERR_APPLETP_OCSP_BAD_RESPONSE); + } + + /* signature and cert evaluation done externally */ + + /* decode the SecAsn1OCSPResponseData */ + if(SecAsn1DecodeData(mCoder, &mBasicResponse.tbsResponseData, + kSecAsn1OCSPResponseDataTemplate, &mResponseData)) { + ocspdErrorLog("OCSPResponse: decode failure at SecAsn1OCSPResponseData\n"); + CssmError::throwMe(CSSMERR_APPLETP_OCSP_BAD_RESPONSE); + } + if(mResponseData.responderID.Data == NULL) { + ocspdErrorLog("OCSPResponse: bad responderID\n"); + CssmError::throwMe(CSSMERR_APPLETP_OCSP_BAD_RESPONSE); + } + + /* choice processing for ResponderID */ + mResponderIdTag = (SecAsn1OCSPResponderIDTag) + (mResponseData.responderID.Data[0] & SEC_ASN1_TAGNUM_MASK); + const SecAsn1Template *templ; + switch(mResponderIdTag) { + case RIT_Name: + templ = kSecAsn1OCSPResponderIDAsNameTemplate; + break; + case RIT_Key: + templ = kSecAsn1OCSPResponderIDAsKeyTemplate; + break; + default: + ocspdErrorLog("OCSPResponse: bad responderID tag\n"); + CssmError::throwMe(CSSMERR_APPLETP_OCSP_BAD_RESPONSE); + } + if(SecAsn1DecodeData(mCoder, &mResponseData.responderID, templ, &mResponderId)) { + ocspdErrorLog("OCSPResponse: decode failure at responderID\n"); + CssmError::throwMe(CSSMERR_APPLETP_OCSP_BAD_RESPONSE); + } + + /* check temporal validity */ + if(!calculateValidity(defaultTTL)) { + /* Whoops, abort */ + CssmError::throwMe(CSSMERR_APPLETP_OCSP_BAD_RESPONSE); + } + + /* + * Individual responses looked into when we're asked for a specific one + * via singleResponse() + */ + mExtensions = new OCSPExtensions(mResponseData.responseExtensions); +} + +OCSPResponse::~OCSPResponse() +{ + delete mExtensions; + SecAsn1CoderRelease(mCoder); +} + +SecAsn1OCSPResponseStatus OCSPResponse::responseStatus() +{ + assert(mTopResp.responseStatus.Data != NULL); /* else constructor should have failed */ + return (SecAsn1OCSPResponseStatus)(mTopResp.responseStatus.Data[0]); +} + +const CSSM_DATA *OCSPResponse::nonce() /* NULL means not present */ +{ + OCSPExtension *ext = mExtensions->findExtension(CSSMOID_PKIX_OCSP_NONCE); + if(ext == NULL) { + return NULL; + } + OCSPNonce *nonceExt = dynamic_cast(ext); + return &(nonceExt->nonce()); +} + +CFAbsoluteTime OCSPResponse::producedAt() +{ + return genTimeToCFAbsTime(&mResponseData.producedAt); +} + +uint32 OCSPResponse::numSignerCerts() +{ + return ocspdArraySize((const void **)mBasicResponse.certs); +} + +const CSSM_DATA *OCSPResponse::signerCert(uint32 dex) +{ + uint32 numCerts = numSignerCerts(); + if(dex >= numCerts) { + ocspdErrorLog("OCSPResponse::signerCert: numCerts overflow\n"); + CssmError::throwMe(CSSMERR_TP_INTERNAL_ERROR); + } + return mBasicResponse.certs[dex]; +} + +/* + * Obtain a OCSPSingleResponse for a given "smart" CertID. + */ +OCSPSingleResponse *OCSPResponse::singleResponseFor(OCSPClientCertID &matchCertID) +{ + unsigned numResponses = ocspdArraySize((const void **)mResponseData.responses); + for(unsigned dex=0; dexcertID; + if(matchCertID.compareToExist(certID)) { + try { + OCSPSingleResponse *singleResp = new OCSPSingleResponse(resp); + return singleResp; + } + catch(...) { + /* try to find another... */ + continue; + } + } + } + ocspdDebug("OCSPResponse::singleResponse: certID not found"); + return NULL; +} + +/* + * If responderID is of form RIT_Name, return the encoded version of the + * NSS_Name (for comparison with issuer's subjectName). Evaluated lazily, + * once, in mCoder space. + */ +const CSSM_DATA *OCSPResponse::encResponderName() +{ + if(mResponderIdTag != RIT_Name) { + assert(0); + return NULL; + } + if(mEncResponderName.Data != NULL) { + return &mEncResponderName; + } + if(SecAsn1EncodeItem(mCoder, &mResponderId.byName, kSecAsn1AnyTemplate, + &mEncResponderName)) { + ocspdDebug("OCSPResponse::encResponderName: error encoding ResponderId!"); + return NULL; + } + return &mEncResponderName; +} + +/* + * Obtain a OCSPSingleResponse for a given raw encoded CertID. + */ +OCSPSingleResponse *OCSPResponse::singleResponseFor(const CSSM_DATA &matchCertID) +{ + unsigned numResponses = ocspdArraySize((const void **)mResponseData.responses); + for(unsigned dex=0; dexcertID, kSecAsn1OCSPCertIDTemplate, + &certID)) { + ocspdDebug("OCSPResponse::singleResponse: error encoding certID!"); + return NULL; + } + if(!ocspdCompareCssmData(&matchCertID, &certID)) { + /* try to find another */ + continue; + } + try { + OCSPSingleResponse *singleResp = new OCSPSingleResponse(resp); + return singleResp; + } + catch(...) { + /* try to find another... */ + continue; + } + } + ocspdDebug("OCSPResponse::singleResponse: certID not found"); + return NULL; + +} + +/* + * Calculate temporal validity; set mLatestNextUpdate and mExpireTime. Only + * called from constructor. Returns true if valid, else returns false. + */ +bool OCSPResponse::calculateValidity(CFTimeInterval defaultTTL) +{ + mLatestNextUpdate = NULL_TIME; + CFAbsoluteTime now = CFAbsoluteTimeGetCurrent(); + + unsigned numResponses = ocspdArraySize((const void **)mResponseData.responses); + for(unsigned dex=0; dexthisUpdate); + if(thisUpdate > now) { + ocspdErrorLog("OCSPResponse::calculateValidity: thisUpdate not passed\n"); + return false; + } + + /* + * Accumulate latest nextUpdate + */ + if(resp->nextUpdate != NULL) { + CFAbsoluteTime nextUpdate = genTimeToCFAbsTime(resp->nextUpdate); + if(nextUpdate > mLatestNextUpdate) { + mLatestNextUpdate = nextUpdate; + } + } + } + + CFAbsoluteTime defaultExpire = now + defaultTTL; + if(mLatestNextUpdate == NULL_TIME) { + /* absolute expire time = current time plus default TTL */ + mExpireTime = defaultExpire; + } + else if(defaultExpire < mLatestNextUpdate) { + /* caller more stringent than response */ + mExpireTime = defaultExpire; + } + else { + /* response more stringent than caller */ + if(mLatestNextUpdate < now) { + ocspdErrorLog("OCSPResponse::calculateValidity: now > mLatestNextUpdate\n"); + return false; + } + mExpireTime = mLatestNextUpdate; + } + return true; +} +