/* * 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 #include #include /* * 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; dexnumPurposes; 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; dexisStatusFatal(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; }