ocspResponse.cpp   [plain text]


/*
 * 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 <Security/cssmapple.h>
#include <Security/oidscrl.h>
#include <Security/oidsalg.h>
#include "ocspdDebug.h"
#include <CommonCrypto/CommonDigest.h>
#include <security_cdsa_utilities/cssmerrors.h>

/* 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<OCSPNonce *>(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; dex<numResponses; dex++) {
		SecAsn1OCSPSingleResponse *resp = mResponseData.responses[dex];
		SecAsn1OCSPCertID &certID = resp->certID;
		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, kSecAsn1NameTemplate, 
			&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; dex<numResponses; dex++) {
		SecAsn1OCSPSingleResponse *resp = mResponseData.responses[dex];
		CSSM_DATA certID = {0, NULL};
		if(SecAsn1EncodeItem(mCoder, &resp->certID, 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; dex<numResponses; dex++) {
		SecAsn1OCSPSingleResponse *resp = mResponseData.responses[dex];
		
		/* 
		 * First off, a thisUpdate later than 'now' invalidates the whole response. 
		 */
		CFAbsoluteTime thisUpdate = genTimeToCFAbsTime(&resp->thisUpdate);
		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;
}