X-Git-Url: https://git.saurik.com/apple/security.git/blobdiff_plain/80e2389990082500d76eb566d4946be3e786c3ef..d8f41ccd20de16f8ebe2ccc84d47bf1cb2b26bbb:/Security/libsecurity_apple_x509_tp/lib/tpOcspCertVfy.cpp diff --git a/Security/libsecurity_apple_x509_tp/lib/tpOcspCertVfy.cpp b/Security/libsecurity_apple_x509_tp/lib/tpOcspCertVfy.cpp new file mode 100644 index 00000000..6fdb9e69 --- /dev/null +++ b/Security/libsecurity_apple_x509_tp/lib/tpOcspCertVfy.cpp @@ -0,0 +1,592 @@ +/* + * 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 +#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; +}