tpOcspCertVfy.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@
 */

/*
 * tpOcspCertVfy.cpp - OCSP cert verification routines
 */

#include "tpOcspCertVfy.h"
#include "tpdebugging.h"
#include "certGroupUtils.h"
#include <Security/oidscert.h>
#include <CommonCrypto/CommonDigest.h>
#include <security_ocspd/ocspdUtils.h>

/*
 * Is signerCert authorized to sign OCSP responses by issuerCert? IssuerCert is
 * assumed to be (i.e., must, but we don't check that here) the signer of the 
 * cert being verified, which is not in the loop for this op. Just a bool returned;
 * it's autoritized or it's not. 
 */
static bool tpIsAuthorizedOcspSigner(
	TPCertInfo &issuerCert,		// issuer of cert being verified
	TPCertInfo &signerCert)		// potential signer of OCSP response
{
	CSSM_DATA_PTR		fieldValue = NULL;			// mallocd by CL
	CSSM_RETURN			crtn;
	bool				ourRtn = false;
	CE_ExtendedKeyUsage *eku = NULL;
	bool				foundEku = false;
	
	/* 
	 * First see if issuerCert issued signerCert (No signature vfy yet, just
	 * subject/issuer check).
	 */
	if(!issuerCert.isIssuerOf(signerCert)) {
		return false;
	}
	
	/* Fetch ExtendedKeyUse field from signerCert */
	crtn = signerCert.fetchField(&CSSMOID_ExtendedKeyUsage, &fieldValue);
	if(crtn) {
		tpOcspDebug("tpIsAuthorizedOcspSigner: signer is issued by issuer, no EKU");
		return false;
	}	
	CSSM_X509_EXTENSION *cssmExt = (CSSM_X509_EXTENSION *)fieldValue->Data;
	if(cssmExt->format != CSSM_X509_DATAFORMAT_PARSED) {
		tpOcspDebug("tpIsAuthorizedOcspSigner: bad extension format");
		goto errOut;
	}
	eku = (CE_ExtendedKeyUsage *)cssmExt->value.parsedValue;
	
	/* Look for OID_KP_OCSPSigning */
	for(unsigned dex=0; dex<eku->numPurposes; dex++) {
		if(tpCompareCssmData(&eku->purposes[dex], &CSSMOID_OCSPSigning)) {
			foundEku = true;
			break;
		}
	}
	if(!foundEku) {
		tpOcspDebug("tpIsAuthorizedOcspSigner: signer is issued by issuer, no OCSP "
			"signing EKU");
		goto errOut;
	}

	/* 
	 * OK, signerCert is authorized by *someone* to sign OCSP requests, and 
	 * it claims to be issued by issuer. Sig verify to be sure. 
	 * FIXME this is not handling partial public keys, which would be a colossal
	 * mess to handle in this module...so we don't.
	 */
	crtn = signerCert.verifyWithIssuer(&issuerCert, NULL);
	if(crtn == CSSM_OK) {
		tpOcspDebug("tpIsAuthorizedOcspSigner: FOUND authorized signer");
		ourRtn = true;
	}
	else {
		/* This is a highly irregular situation... */
		tpOcspDebug("tpIsAuthorizedOcspSigner: signer sig verify FAIL");
	}
errOut:
	if(fieldValue != NULL) {
		signerCert.freeField(&CSSMOID_ExtendedKeyUsage, fieldValue);
	}
	return ourRtn;
}

/* 
 * Check ResponderID linkage between an OCSPResponse and a cert we believe to 
 * be the issuer of both that response and the cert being verified. Returns
 * true if OK.
 */
bool tpOcspResponderIDCheck(
	OCSPResponse	&ocspResp,
	TPCertInfo		&signer)
{
	bool shouldBeSigner = false;
	if(ocspResp.responderIDTag() == RIT_Name) {
		/* 
		 * Name inside response must == signer's SubjectName.
		 * Note we can't use signer.subjectName(); that's normalized.
		 */

		const CSSM_DATA *respIdName = ocspResp.encResponderName();
		CSSM_DATA *subjectName = NULL;
		CSSM_RETURN crtn = signer.fetchField(&CSSMOID_X509V1SubjectNameStd, 
			&subjectName);
		if(crtn) {
			/* bad cert */
			tpOcspDebug("tpOcspResponderIDCheck: error on fetchField(subjectName");
			return false;
		}
		if(tpCompareCssmData(respIdName, subjectName)) {
			tpOcspDebug("tpOcspResponderIDCheck: good ResponderID.byName");
			shouldBeSigner = true;
		}
		else {
			tpOcspDebug("tpOcspResponderIDCheck: BAD ResponderID.byName");
		}
		signer.freeField(&CSSMOID_X509V1SubjectNameStd, subjectName);
	}
	else {
		/* ResponderID.byKey must == SHA1(signer's public key) */
		const CSSM_KEY *pubKey = signer.pubKey();
		assert(pubKey != NULL);
		uint8 digest[CC_SHA1_DIGEST_LENGTH];
		CSSM_DATA keyHash = {CC_SHA1_DIGEST_LENGTH, digest};
		ocspdSha1(pubKey->KeyData.Data, pubKey->KeyData.Length, digest);
		const CSSM_DATA *respKeyHash = &ocspResp.responderID().byKey;
		if(tpCompareCssmData(&keyHash, respKeyHash)) {
			tpOcspDebug("tpOcspResponderIDCheck: good ResponderID.byKey");
			shouldBeSigner = true;
		}
		else {
			tpOcspDebug("tpOcspResponderIDCheck: BAD ResponderID.byKey");
		}
	}
	return shouldBeSigner;
}

/*
 * Verify the signature of an OCSP response. Caller is responsible for all other 
 * verification of the response, this is just the crypto.
 * Returns true on success. 
 */
static bool tpOcspResponseSigVerify(
	TPVerifyContext		&vfyCtx,
	OCSPResponse		&ocspResp,		// parsed response
	TPCertInfo			&signer)
{
	/* get signature algorithm in CSSM form from the response */
	const SecAsn1OCSPBasicResponse &basicResp = ocspResp.basicResponse();
	const CSSM_OID *algOid = &basicResp.algId.algorithm;
	CSSM_ALGORITHMS sigAlg;
	
	if(!cssmOidToAlg(algOid, &sigAlg)) {
		tpOcspDebug("tpOcspResponseSigVerify: unknown signature algorithm");
	}
	
	/* signer's public key from the cert */
	const CSSM_KEY *pubKey = signer.pubKey();
	
	/* signature: on decode, length is in BITS */
	CSSM_DATA sig = basicResp.sig;
	sig.Length /= 8;
	
	CSSM_RETURN crtn;
	CSSM_CC_HANDLE sigHand;
	bool ourRtn = false;
	crtn = CSSM_CSP_CreateSignatureContext(vfyCtx.cspHand, sigAlg, NULL,
		pubKey, &sigHand);
	if(crtn) {
		#ifndef	NDEBUG
		cssmPerror("tpOcspResponseSigVerify, CSSM_CSP_CreateSignatureContext", crtn);
		#endif
		return false;
	}
	crtn = CSSM_VerifyData(sigHand, &basicResp.tbsResponseData, 1,
		CSSM_ALGID_NONE, &sig);
	if(crtn) {
		#ifndef	NDEBUG
		cssmPerror("tpOcspResponseSigVerify, CSSM_VerifyData", crtn);
		#endif
	}
	else {
		ourRtn = true;
	}
	CSSM_DeleteContext(sigHand);
	return ourRtn;
}

/* possible return from tpIsOcspIssuer() */
typedef enum {
	OIS_No,			// not the issuer
	OIS_Good,		// is the issuer and signature matches
	OIS_BadSig,		// appears to be issuer, but signature doesn't match
} OcspIssuerStatus;

/* type of rawCert passed to tpIsOcspIssuer */
typedef enum {
	OCT_Local,		// LocalResponder - no checking other than signature
	OCT_Issuer,		// it's the issuer of the cert being verified
	OCT_Provided,	// came with response, provenance unknown 
} OcspCertType;

/*
 * Did specified cert issue the OCSP response? 
 *
 * This implements the algorithm described in RFC2560, section 4.2.2.2,
 * "Authorized Responders". It sees if the cert could be the issuer of the 
 * OCSP response per that algorithm; then if it could, it performs signature
 * verification.
 */
static OcspIssuerStatus tpIsOcspIssuer(
	TPVerifyContext		&vfyCtx,
	OCSPResponse		&ocspResp,		// parsed response
	/* on input specify at least one of the following two */
	const CSSM_DATA		*signerData,
	TPCertInfo			*signer,
	OcspCertType		certType,		// where rawCert came from 
	TPCertInfo			*issuer,		// OPTIONAL, if known
	TPCertInfo			**signerRtn)	// optionally RETURNED if at all possible
{
	assert((signerData != NULL) || (signer != NULL));
	
	/* get signer as TPCertInfo if caller hasn't provided */
	TPCertInfo *tmpSigner = NULL;
	if(signer == NULL) {
		try {
			tmpSigner = new TPCertInfo(vfyCtx.clHand, vfyCtx.cspHand, signerData,
				TIC_CopyData, vfyCtx.verifyTime);
		}
		catch(...) {
			tpOcspDebug("tpIsOcspIssuer: bad cert");
			return OIS_No;
		}
		signer = tmpSigner;
	}
	if(signer == NULL) {
		return OIS_No;
	}
	if(signerRtn != NULL) {
		*signerRtn = signer;
	}
	
	/* 
	 * Qualification of "this can be the signer" depends on where the
	 * signer came from.
	 */
	bool shouldBeSigner = false;
	OcspIssuerStatus ourRtn = OIS_No;
	
	switch(certType) {
		case OCT_Local:			// caller trusts this and thinks it's the signer
			shouldBeSigner = true;
			break;
		case OCT_Issuer:		// last resort, the actual issuer
			/* check ResponderID linkage */
			shouldBeSigner = tpOcspResponderIDCheck(ocspResp, *signer);
			break;
		case OCT_Provided:
		{
			/* 
			 * This cert came with the response.
			 */
			if(issuer == NULL) {
				/* 
				 * careful, might not know the issuer...how would this path ever 
				 * work then? I don't think it needs to because you can NOT
				 * do OCSP on a cert without its issuer in hand.
				 */
				break;
			}

			/* check EKU lingage */
			shouldBeSigner = tpIsAuthorizedOcspSigner(*issuer, *signer);
			break;
		}
	}
	if(!shouldBeSigner) {
		goto errOut;
	}
	
	/* verify the signature */
	if(tpOcspResponseSigVerify(vfyCtx, ocspResp, *signer)) {
		ourRtn = OIS_Good;
	}
	
errOut:
	if((signerRtn == NULL) && (tmpSigner != NULL)) {
		delete tmpSigner;
	}
	return ourRtn;

}

OcspRespStatus tpVerifyOcspResp(
	TPVerifyContext		&vfyCtx,
	SecNssCoder			&coder,
	TPCertInfo			*issuer,		// issuer of the related cert, may be issuer of 
										//   reply, may not be known 
	OCSPResponse		&ocspResp,
	CSSM_RETURN			&cssmErr)		// possible per-cert error 
{
	OcspRespStatus	ourRtn = ORS_Unknown;
	CSSM_RETURN		crtn;
		
	tpOcspDebug("tpVerifyOcspResp top");
	
	switch(ocspResp.responseStatus()) {
		case RS_Success: 
			crtn = CSSM_OK; 
			break;
		case RS_MalformedRequest:
			crtn = CSSMERR_APPLETP_OCSP_RESP_MALFORMED_REQ;
			break;
		case RS_InternalError:
			crtn = CSSMERR_APPLETP_OCSP_RESP_INTERNAL_ERR;
			break;
		case RS_TryLater:
			crtn = CSSMERR_APPLETP_OCSP_RESP_TRY_LATER;
			break;
		case RS_SigRequired:
			crtn = CSSMERR_APPLETP_OCSP_RESP_SIG_REQUIRED;
			break;
		case RS_Unauthorized:
			crtn = CSSMERR_APPLETP_OCSP_RESP_UNAUTHORIZED;
			break;
		default:
			crtn = CSSMERR_APPLETP_OCSP_BAD_RESPONSE;
			break;
	}
	if(crtn) {
		tpOcspDebug("tpVerifyOcspResp aborting due to response status %d",
			(int)(ocspResp.responseStatus()));
		cssmErr = crtn;
		return ORS_Unknown;
	}
	cssmErr = CSSM_OK;
	
	/* one of our main jobs is to locate the signer of the response, here */
	TPCertInfo *signerInfo = NULL;
	TPCertInfo *signerInfoTBD = NULL;		// if non NULL at end, we delete
	/* we'll be verifying into this cert group */
	TPCertGroup	ocspCerts(vfyCtx.alloc, TGO_Caller);
	CSSM_BOOL verifiedToRoot;
	CSSM_BOOL verifiedToAnchor;
	
	const CSSM_APPLE_TP_OCSP_OPTIONS *ocspOpts = vfyCtx.ocspOpts;
	OcspIssuerStatus issuerStat;
	
	/* 
	 * Set true if we ever find an apparent issuer which does not correctly
	 * pass signature verify. If true and we never success, that's a XXX error.
	 */
	bool foundBadIssuer = false;
	uint32 numSignerCerts = ocspResp.numSignerCerts();
	
	/*
	 * This cert group, alocated by AppleTPSession::CertGroupVerify(), 
	 * serves two functions here:
	 *
	 * -- it accumulates certs we get from the net (as parts of OCSP responses)
	 *    for user in verifying OCSPResponse-related certs.
	 *    TPCertGroup::buildCertGroup() uses this group as one of the many 
	 *    sources of certs when building a cert chain.
	 *
	 * -- it provides a container into which to stash TPCertInfos which 
	 *    persist at least as long as the TPVerifyContext; it's of type TGO_Group,
	 *    soall of the certs added to it get freed when the group does. 
	 */
	assert(vfyCtx.signerCerts != NULL);
	
	TPCertGroup &gatheredCerts = vfyCtx.gatheredCerts;
	
	/* set up for disposal of TPCertInfos created by TPCertGroup::buildCertGroup() */
	TPCertGroup	certsToBeFreed(vfyCtx.alloc, TGO_Group);
	
	/* 
	 * First job is to find the cert which signed this response.
	 * Give priority to caller's LocalResponderCert.
	 */
	if((ocspOpts != NULL) && (ocspOpts->LocalResponderCert != NULL)) {
		TPCertInfo *responderInfo = NULL;
		issuerStat = tpIsOcspIssuer(vfyCtx, ocspResp, 
			ocspOpts->LocalResponderCert, NULL,
			OCT_Local, issuer, &responderInfo);
		switch(issuerStat) {
			case OIS_BadSig:
				foundBadIssuer = true;
				/* drop thru */
			case OIS_No:
				if(responderInfo != NULL) {
					/* can't use it - should this be an immediate error? */
					delete responderInfo;
				}
				break;
			case OIS_Good:
				assert(responderInfo != NULL);
				signerInfo = signerInfoTBD = responderInfo;
				tpOcspDebug("tpVerifyOcspResp: signer := LocalResponderCert");
				break;
		}
	}
	
	if((signerInfo == NULL) && (numSignerCerts != 0)) {
		/* 
		 * App did not specify a local responder (or provided a bad one)
		 * and the response came with some certs. Try those.
		 */
		TPCertInfo *respCert = NULL;
		for(unsigned dex=0; dex<numSignerCerts; dex++) {
			const CSSM_DATA *certData = ocspResp.signerCert(dex);
			if(signerInfo == NULL) {
				/* stop trying this after we succeed... */
				issuerStat = tpIsOcspIssuer(vfyCtx, ocspResp, 
					certData, NULL,
					OCT_Provided, issuer, &respCert);
				switch(issuerStat) {
					case OIS_No:
						break;
					case OIS_Good:
						assert(respCert != NULL);
						signerInfo = signerInfoTBD = respCert;
						tpOcspDebug("tpVerifyOcspResp: signer := signerCert[%u]", dex);
						break;
					case OIS_BadSig:
						foundBadIssuer = true;
						break;
				}
			}
			else {
				/* 
				 * At least add this cert to certGroup for verification.
				 * OcspCertwill own the TPCertInfo .
				 */
				try {
					respCert = new TPCertInfo(vfyCtx.clHand, vfyCtx.cspHand, certData,
						TIC_CopyData, vfyCtx.verifyTime);
				}
				catch(...) {
					tpOcspDebug("tpVerifyOcspResp: BAD signerCert[%u]", dex);
				}
			}
			/* if we got a TPCertInfo, and it's not the signer, add it to certGroup */
			if((respCert != NULL) && (respCert != signerInfo)) {
				gatheredCerts.appendCert(respCert);
			}
		}
	}
	
	if((signerInfo == NULL) && (issuer != NULL)) {
		/* 
		 * Haven't found it yet, try the actual issuer
		 */
		issuerStat = tpIsOcspIssuer(vfyCtx, ocspResp, 
			NULL, issuer, 
			OCT_Issuer, issuer, NULL);
		switch(issuerStat) {
			case OIS_BadSig:
				ourRtn = ORS_Unknown;
				cssmErr = CSSMERR_APPLETP_OCSP_SIG_ERROR;
				goto errOut;
			case OIS_No:
				break;
			case OIS_Good:
				signerInfo = issuer;
				tpOcspDebug("tpVerifyOcspResp: signer := issuer");
				break;
		}
	}
		
	if(signerInfo == NULL) {
		tpOcspDebug("tpVerifyOcspResp: no signer found");
		ourRtn = ORS_Unknown;
		cssmErr = CSSMERR_APPLETP_OCSP_NO_SIGNER;
		goto errOut;
	}
	
	/* 
	 * Last remaining task is to verify the signer, and all the certs back to 
	 * an anchor 
	 */

	/* start from scratch with both of these groups */
	gatheredCerts.setAllUnused();
	vfyCtx.signerCerts->setAllUnused();
	crtn = ocspCerts.buildCertGroup(
			*signerInfo,			// subject item
			vfyCtx.signerCerts,		// inCertGroup the original group-to-be-verified
			vfyCtx.dbList,			// optional
			vfyCtx.clHand,
			vfyCtx.cspHand,
			vfyCtx.verifyTime,
			vfyCtx.numAnchorCerts,
			vfyCtx.anchorCerts,
			certsToBeFreed,			// local to-be-freed right now
			&gatheredCerts,			// accumulate gathered certs here
			CSSM_FALSE,				// subjectIsInGroup
			vfyCtx.actionFlags,
			verifiedToRoot,	
			verifiedToAnchor);
	if(crtn) {
		tpOcspDebug("tpVerifyOcspResp buildCertGroup failure");
		cssmErr = crtn;
		ourRtn = ORS_Unknown;
		goto errOut;
	}

	if(!verifiedToAnchor) {
		/* required */
		ourRtn = ORS_Unknown;
		if(verifiedToRoot) {
			/* verified to root which is not an anchor */
			tpOcspDebug("tpVerifyOcspResp root, no anchor");
			cssmErr = CSSMERR_APPLETP_OCSP_INVALID_ANCHOR_CERT;
		}
		else {
			/* partial chain, no root, not verifiable by anchor */
			tpOcspDebug("tpVerifyOcspResp no root, no anchor");
			cssmErr = CSSMERR_APPLETP_OCSP_NOT_TRUSTED;
		}
		ourRtn = ORS_Unknown;
	}
	else {
		tpOcspDebug("tpVerifyOcspResp SUCCESS");
		ourRtn = ORS_Good;
	}
	
	/* FIXME policy verify? */
	
errOut:
	delete signerInfoTBD;
	/* any other cleanup? */
	return ourRtn;
}