--- /dev/null
+/*
+ * Copyright (c) 2004,2011-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@
+ */
+
+/*
+ * 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.
+ */
+static
+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, (CC_LONG)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 linkage */
+ 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;
+ CSSM_BOOL verifiedViaTrustSetting;
+
+ 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;
+ bool foundLocalResponder = false;
+ uint32 numSignerCerts = ocspResp.numSignerCerts();
+
+ /*
+ * This cert group, allocated 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,
+ * so all 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;
+ foundLocalResponder = true;
+ 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.
+ * OcspCert will 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) {
+ if((issuer != NULL) && !issuer->isStatusFatal(CSSMERR_APPLETP_OCSP_NO_SIGNER)) {
+ /* user wants to proceed without verifying! */
+ tpOcspDebug("tpVerifyOcspResp: no signer found, user allows!");
+ ourRtn = ORS_Good;
+ }
+ else {
+ tpOcspDebug("tpVerifyOcspResp: no signer found");
+ ourRtn = ORS_Unknown;
+ /* caller adds to per-cert status */
+ cssmErr = CSSMERR_APPLETP_OCSP_NO_SIGNER;
+ }
+ goto errOut;
+ }
+
+ if(signerInfo != NULL && !foundLocalResponder) {
+ /*
+ * tpIsOcspIssuer has verified that signerInfo is the signer of the
+ * OCSP response, and that it is either the issuer of the cert being
+ * checked or is a valid authorized responder for that issuer based on
+ * key id linkage and EKU. There is no stipulation in RFC2560 to also
+ * build the chain back to a trusted anchor; however, we'll continue to
+ * enforce this for the local responder case. (10742723)
+ */
+ tpOcspDebug("tpVerifyOcspResp SUCCESS");
+ ourRtn = ORS_Good;
+ 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,
+ vfyCtx.policyOid,
+ vfyCtx.policyStr,
+ vfyCtx.policyStrLen,
+ kSecTrustSettingsKeyUseSignRevocation,
+ verifiedToRoot,
+ verifiedToAnchor,
+ verifiedViaTrustSetting);
+ if(crtn) {
+ tpOcspDebug("tpVerifyOcspResp buildCertGroup failure");
+ cssmErr = crtn;
+ ourRtn = ORS_Unknown;
+ goto errOut;
+ }
+
+ if(!verifiedToAnchor && !verifiedViaTrustSetting) {
+ /* 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;
+ }
+ if((issuer != NULL) && !issuer->isStatusFatal(cssmErr)) {
+ tpOcspDebug("...ignoring last error per trust setting");
+ ourRtn = ORS_Good;
+ }
+ else {
+ ourRtn = ORS_Unknown;
+ }
+ }
+ else {
+ tpOcspDebug("tpVerifyOcspResp SUCCESS; chain verified");
+ ourRtn = ORS_Good;
+ }
+
+ /* FIXME policy verify? */
+
+errOut:
+ delete signerInfoTBD;
+ /* any other cleanup? */
+ return ourRtn;
+}