michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim: set ts=8 sts=2 et sw=2 tw=80: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "CertVerifier.h" michael@0: michael@0: #include michael@0: michael@0: #include "pkix/pkix.h" michael@0: #include "ExtendedValidation.h" michael@0: #include "NSSCertDBTrustDomain.h" michael@0: #include "PublicKeyPinningService.h" michael@0: #include "cert.h" michael@0: #include "ocsp.h" michael@0: #include "secerr.h" michael@0: #include "pk11pub.h" michael@0: #include "prerror.h" michael@0: #include "sslerr.h" michael@0: michael@0: // ScopedXXX in this file are mozilla::pkix::ScopedXXX, not michael@0: // mozilla::ScopedXXX. michael@0: using namespace mozilla::pkix; michael@0: using namespace mozilla::psm; michael@0: michael@0: #ifdef PR_LOGGING michael@0: PRLogModuleInfo* gCertVerifierLog = nullptr; michael@0: #endif michael@0: michael@0: namespace mozilla { namespace psm { michael@0: michael@0: const CertVerifier::Flags CertVerifier::FLAG_LOCAL_ONLY = 1; michael@0: const CertVerifier::Flags CertVerifier::FLAG_MUST_BE_EV = 2; michael@0: michael@0: CertVerifier::CertVerifier(implementation_config ic, michael@0: #ifndef NSS_NO_LIBPKIX michael@0: missing_cert_download_config mcdc, michael@0: crl_download_config cdc, michael@0: #endif michael@0: ocsp_download_config odc, michael@0: ocsp_strict_config osc, michael@0: ocsp_get_config ogc, michael@0: pinning_enforcement_config pel) michael@0: : mImplementation(ic) michael@0: #ifndef NSS_NO_LIBPKIX michael@0: , mMissingCertDownloadEnabled(mcdc == missing_cert_download_on) michael@0: , mCRLDownloadEnabled(cdc == crl_download_allowed) michael@0: #endif michael@0: , mOCSPDownloadEnabled(odc == ocsp_on) michael@0: , mOCSPStrict(osc == ocsp_strict) michael@0: , mOCSPGETEnabled(ogc == ocsp_get_enabled) michael@0: , mPinningEnforcementLevel(pel) michael@0: { michael@0: } michael@0: michael@0: CertVerifier::~CertVerifier() michael@0: { michael@0: } michael@0: michael@0: void michael@0: InitCertVerifierLog() michael@0: { michael@0: #ifdef PR_LOGGING michael@0: if (!gCertVerifierLog) { michael@0: gCertVerifierLog = PR_NewLogModule("certverifier"); michael@0: } michael@0: #endif michael@0: } michael@0: michael@0: // Once we migrate to mozilla::pkix or change the overridable error michael@0: // logic this will become unnecesary. michael@0: static SECStatus michael@0: insertErrorIntoVerifyLog(CERTCertificate* cert, const PRErrorCode err, michael@0: CERTVerifyLog* verifyLog){ michael@0: CERTVerifyLogNode* node; michael@0: node = (CERTVerifyLogNode *)PORT_ArenaAlloc(verifyLog->arena, michael@0: sizeof(CERTVerifyLogNode)); michael@0: if (!node) { michael@0: PR_SetError(PR_UNKNOWN_ERROR, 0); michael@0: return SECFailure; michael@0: } michael@0: node->cert = CERT_DupCertificate(cert); michael@0: node->error = err; michael@0: node->depth = 0; michael@0: node->arg = nullptr; michael@0: //and at to head! michael@0: node->prev = nullptr; michael@0: node->next = verifyLog->head; michael@0: if (verifyLog->head) { michael@0: verifyLog->head->prev = node; michael@0: } michael@0: verifyLog->head = node; michael@0: if (!verifyLog->tail) { michael@0: verifyLog->tail = node; michael@0: } michael@0: verifyLog->count++; michael@0: michael@0: return SECSuccess; michael@0: } michael@0: michael@0: SECStatus michael@0: IsCertBuiltInRoot(CERTCertificate* cert, bool& result) { michael@0: result = false; michael@0: ScopedPtr slots; michael@0: slots = PK11_GetAllSlotsForCert(cert, nullptr); michael@0: if (!slots) { michael@0: if (PORT_GetError() == SEC_ERROR_NO_TOKEN) { michael@0: // no list michael@0: return SECSuccess; michael@0: } michael@0: return SECFailure; michael@0: } michael@0: for (PK11SlotListElement* le = slots->head; le; le = le->next) { michael@0: char* token = PK11_GetTokenName(le->slot); michael@0: PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, michael@0: ("BuiltInRoot? subject=%s token=%s",cert->subjectName, token)); michael@0: if (strcmp("Builtin Object Token", token) == 0) { michael@0: result = true; michael@0: return SECSuccess; michael@0: } michael@0: } michael@0: return SECSuccess; michael@0: } michael@0: michael@0: struct ChainValidationCallbackState michael@0: { michael@0: const char* hostname; michael@0: const CertVerifier::pinning_enforcement_config pinningEnforcementLevel; michael@0: const SECCertificateUsage usage; michael@0: const PRTime time; michael@0: }; michael@0: michael@0: SECStatus chainValidationCallback(void* state, const CERTCertList* certList, michael@0: PRBool* chainOK) michael@0: { michael@0: ChainValidationCallbackState* callbackState = michael@0: reinterpret_cast(state); michael@0: michael@0: *chainOK = PR_FALSE; michael@0: michael@0: PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, michael@0: ("verifycert: Inside the Callback \n")); michael@0: michael@0: // On sanity failure we fail closed. michael@0: if (!certList) { michael@0: PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, michael@0: ("verifycert: Short circuit, callback, sanity check failed \n")); michael@0: PR_SetError(PR_INVALID_STATE_ERROR, 0); michael@0: return SECFailure; michael@0: } michael@0: if (!callbackState) { michael@0: PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, michael@0: ("verifycert: Short circuit, callback, no state! \n")); michael@0: PR_SetError(PR_INVALID_STATE_ERROR, 0); michael@0: return SECFailure; michael@0: } michael@0: michael@0: if (callbackState->usage != certificateUsageSSLServer || michael@0: callbackState->pinningEnforcementLevel == CertVerifier::pinningDisabled) { michael@0: PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, michael@0: ("verifycert: Callback shortcut pel=%d \n", michael@0: callbackState->pinningEnforcementLevel)); michael@0: *chainOK = PR_TRUE; michael@0: return SECSuccess; michael@0: } michael@0: michael@0: for (CERTCertListNode* node = CERT_LIST_HEAD(certList); michael@0: !CERT_LIST_END(node, certList); michael@0: node = CERT_LIST_NEXT(node)) { michael@0: CERTCertificate* currentCert = node->cert; michael@0: if (CERT_LIST_END(CERT_LIST_NEXT(node), certList)) { michael@0: bool isBuiltInRoot = false; michael@0: SECStatus srv = IsCertBuiltInRoot(currentCert, isBuiltInRoot); michael@0: if (srv != SECSuccess) { michael@0: PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, ("Is BuiltInRoot failure")); michael@0: return srv; michael@0: } michael@0: // If desired, the user can enable "allow user CA MITM mode", in which michael@0: // case key pinning is not enforced for certificates that chain to trust michael@0: // anchors that are not in Mozilla's root program michael@0: if (!isBuiltInRoot && michael@0: (callbackState->pinningEnforcementLevel == michael@0: CertVerifier::pinningAllowUserCAMITM)) { michael@0: *chainOK = PR_TRUE; michael@0: return SECSuccess; michael@0: } michael@0: } michael@0: } michael@0: michael@0: const bool enforceTestMode = (callbackState->pinningEnforcementLevel == michael@0: CertVerifier::pinningEnforceTestMode); michael@0: *chainOK = PublicKeyPinningService:: michael@0: ChainHasValidPins(certList, callbackState->hostname, callbackState->time, michael@0: enforceTestMode); michael@0: michael@0: return SECSuccess; michael@0: } michael@0: michael@0: // This always returns secfailure but its objective is to replate michael@0: // the PR_Error michael@0: static void michael@0: tryWorsenPRErrorInCallback(CERTCertificate* cert, michael@0: ChainValidationCallbackState* callbackState) { michael@0: ScopedCERTCertificate certCopy(CERT_DupCertificate(cert)); michael@0: if (!certCopy) { michael@0: return; michael@0: } michael@0: ScopedCERTCertList certList(CERT_NewCertList()); michael@0: if (!certList) { michael@0: return; michael@0: } michael@0: SECStatus srv = CERT_AddCertToListTail(certList.get(), certCopy.get()); michael@0: if (srv != SECSuccess) { michael@0: return; michael@0: } michael@0: certCopy.release(); // now owned by certList michael@0: PRBool chainOK = false; michael@0: srv = chainValidationCallback(&callbackState, certList.get(), &chainOK); michael@0: if (srv != SECSuccess) { michael@0: return; michael@0: } michael@0: if (!chainOK) { michael@0: PR_SetError(SEC_ERROR_APPLICATION_CALLBACK_ERROR, 0); // same as libpkix michael@0: return ; michael@0: } michael@0: return; // no change in PR_error michael@0: } michael@0: michael@0: static SECStatus michael@0: ClassicVerifyCert(CERTCertificate* cert, michael@0: const SECCertificateUsage usage, michael@0: const PRTime time, michael@0: void* pinArg, michael@0: ChainValidationCallbackState* callbackState, michael@0: /*optional out*/ ScopedCERTCertList* validationChain, michael@0: /*optional out*/ CERTVerifyLog* verifyLog) michael@0: { michael@0: SECStatus rv; michael@0: SECCertUsage enumUsage; michael@0: switch (usage) { michael@0: case certificateUsageSSLClient: michael@0: enumUsage = certUsageSSLClient; michael@0: break; michael@0: case certificateUsageSSLServer: michael@0: enumUsage = certUsageSSLServer; michael@0: break; michael@0: case certificateUsageSSLCA: michael@0: enumUsage = certUsageSSLCA; michael@0: break; michael@0: case certificateUsageEmailSigner: michael@0: enumUsage = certUsageEmailSigner; michael@0: break; michael@0: case certificateUsageEmailRecipient: michael@0: enumUsage = certUsageEmailRecipient; michael@0: break; michael@0: case certificateUsageObjectSigner: michael@0: enumUsage = certUsageObjectSigner; michael@0: break; michael@0: case certificateUsageVerifyCA: michael@0: enumUsage = certUsageVerifyCA; michael@0: break; michael@0: case certificateUsageStatusResponder: michael@0: enumUsage = certUsageStatusResponder; michael@0: break; michael@0: default: michael@0: PR_NOT_REACHED("unexpected usage"); michael@0: PORT_SetError(SEC_ERROR_INVALID_ARGS); michael@0: return SECFailure; michael@0: } michael@0: if (usage == certificateUsageSSLServer) { michael@0: // SSL server cert verification has always used CERT_VerifyCert, so we michael@0: // continue to use it for SSL cert verification to minimize the risk of michael@0: // there being any differnce in results between CERT_VerifyCert and michael@0: // CERT_VerifyCertificate. michael@0: rv = CERT_VerifyCert(CERT_GetDefaultCertDB(), cert, true, michael@0: certUsageSSLServer, time, pinArg, verifyLog); michael@0: } else { michael@0: rv = CERT_VerifyCertificate(CERT_GetDefaultCertDB(), cert, true, michael@0: usage, time, pinArg, verifyLog, nullptr); michael@0: } michael@0: michael@0: if (rv == SECSuccess && michael@0: (validationChain || usage == certificateUsageSSLServer)) { michael@0: PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, michael@0: ("VerifyCert: getting chain in 'classic' \n")); michael@0: ScopedCERTCertList certChain(CERT_GetCertChainFromCert(cert, time, michael@0: enumUsage)); michael@0: if (!certChain) { michael@0: return SECFailure; michael@0: } michael@0: if (usage == certificateUsageSSLServer) { michael@0: PRBool chainOK = PR_FALSE; michael@0: SECStatus srv = chainValidationCallback(callbackState, certChain.get(), michael@0: &chainOK); michael@0: if (srv != SECSuccess) { michael@0: return srv; michael@0: } michael@0: if (chainOK != PR_TRUE) { michael@0: if (verifyLog) { michael@0: insertErrorIntoVerifyLog(cert, michael@0: SEC_ERROR_APPLICATION_CALLBACK_ERROR, michael@0: verifyLog); michael@0: } michael@0: PR_SetError(SEC_ERROR_APPLICATION_CALLBACK_ERROR, 0); // same as libpkix michael@0: return SECFailure; michael@0: } michael@0: } michael@0: michael@0: // If there is an error we may need to worsen to error to be a pinning failure michael@0: if (rv != SECSuccess && usage == certificateUsageSSLServer) { michael@0: tryWorsenPRErrorInCallback(cert, callbackState); michael@0: } michael@0: michael@0: if (rv == SECSuccess && validationChain) { michael@0: *validationChain = certChain.release(); michael@0: } michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: #ifndef NSS_NO_LIBPKIX michael@0: static void michael@0: destroyCertListThatShouldNotExist(CERTCertList** certChain) michael@0: { michael@0: PR_ASSERT(certChain); michael@0: PR_ASSERT(!*certChain); michael@0: if (certChain && *certChain) { michael@0: // There SHOULD not be a validation chain on failure, asserion here for michael@0: // the debug builds AND a fallback for production builds michael@0: CERT_DestroyCertList(*certChain); michael@0: *certChain = nullptr; michael@0: } michael@0: } michael@0: #endif michael@0: michael@0: static SECStatus michael@0: BuildCertChainForOneKeyUsage(TrustDomain& trustDomain, CERTCertificate* cert, michael@0: PRTime time, KeyUsage ku1, KeyUsage ku2, michael@0: KeyUsage ku3, SECOidTag eku, michael@0: SECOidTag requiredPolicy, michael@0: const SECItem* stapledOCSPResponse, michael@0: ScopedCERTCertList& builtChain) michael@0: { michael@0: SECStatus rv = BuildCertChain(trustDomain, cert, time, MustBeEndEntity, michael@0: ku1, eku, requiredPolicy, stapledOCSPResponse, michael@0: builtChain); michael@0: if (rv != SECSuccess && PR_GetError() == SEC_ERROR_INADEQUATE_KEY_USAGE) { michael@0: rv = BuildCertChain(trustDomain, cert, time, MustBeEndEntity, michael@0: ku2, eku, requiredPolicy, stapledOCSPResponse, michael@0: builtChain); michael@0: if (rv != SECSuccess && PR_GetError() == SEC_ERROR_INADEQUATE_KEY_USAGE) { michael@0: rv = BuildCertChain(trustDomain, cert, time, MustBeEndEntity, michael@0: ku3, eku, requiredPolicy, stapledOCSPResponse, michael@0: builtChain); michael@0: if (rv != SECSuccess) { michael@0: PR_SetError(SEC_ERROR_INADEQUATE_KEY_USAGE, 0); michael@0: } michael@0: } michael@0: } michael@0: return rv; michael@0: } michael@0: michael@0: SECStatus michael@0: CertVerifier::MozillaPKIXVerifyCert( michael@0: CERTCertificate* cert, michael@0: const SECCertificateUsage usage, michael@0: const PRTime time, michael@0: void* pinArg, michael@0: const Flags flags, michael@0: ChainValidationCallbackState* callbackState, michael@0: /*optional*/ const SECItem* stapledOCSPResponse, michael@0: /*optional out*/ mozilla::pkix::ScopedCERTCertList* validationChain, michael@0: /*optional out*/ SECOidTag* evOidPolicy) michael@0: { michael@0: PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, ("Top of MozillaPKIXVerifyCert\n")); michael@0: michael@0: PR_ASSERT(cert); michael@0: PR_ASSERT(usage == certificateUsageSSLServer || !(flags & FLAG_MUST_BE_EV)); michael@0: michael@0: if (validationChain) { michael@0: *validationChain = nullptr; michael@0: } michael@0: if (evOidPolicy) { michael@0: *evOidPolicy = SEC_OID_UNKNOWN; michael@0: } michael@0: michael@0: if (!cert || michael@0: (usage != certificateUsageSSLServer && (flags & FLAG_MUST_BE_EV))) { michael@0: PR_SetError(SEC_ERROR_INVALID_ARGS, 0); michael@0: return SECFailure; michael@0: } michael@0: michael@0: CERTChainVerifyCallback callbackContainer; michael@0: callbackContainer.isChainValid = chainValidationCallback; michael@0: callbackContainer.isChainValidArg = callbackState; michael@0: michael@0: NSSCertDBTrustDomain::OCSPFetching ocspFetching michael@0: = !mOCSPDownloadEnabled || michael@0: (flags & FLAG_LOCAL_ONLY) ? NSSCertDBTrustDomain::NeverFetchOCSP michael@0: : !mOCSPStrict ? NSSCertDBTrustDomain::FetchOCSPForDVSoftFail michael@0: : NSSCertDBTrustDomain::FetchOCSPForDVHardFail; michael@0: michael@0: SECStatus rv; michael@0: michael@0: // TODO(bug 970750): anyExtendedKeyUsage michael@0: // TODO: encipherOnly/decipherOnly michael@0: // S/MIME Key Usage: http://tools.ietf.org/html/rfc3850#section-4.4.2 michael@0: // S/MIME EKU: http://tools.ietf.org/html/rfc3850#section-4.4.4 michael@0: michael@0: // TODO(bug 915931): Pass in stapled OCSP response in all calls to michael@0: // BuildCertChain. michael@0: michael@0: mozilla::pkix::ScopedCERTCertList builtChain; michael@0: switch (usage) { michael@0: case certificateUsageSSLClient: { michael@0: // XXX: We don't really have a trust bit for SSL client authentication so michael@0: // just use trustEmail as it is the closest alternative. michael@0: NSSCertDBTrustDomain trustDomain(trustEmail, ocspFetching, mOCSPCache, michael@0: pinArg); michael@0: rv = BuildCertChain(trustDomain, cert, time, MustBeEndEntity, michael@0: KeyUsage::digitalSignature, michael@0: SEC_OID_EXT_KEY_USAGE_CLIENT_AUTH, michael@0: SEC_OID_X509_ANY_POLICY, michael@0: stapledOCSPResponse, builtChain); michael@0: break; michael@0: } michael@0: michael@0: case certificateUsageSSLServer: { michael@0: // TODO: When verifying a certificate in an SSL handshake, we should michael@0: // restrict the acceptable key usage based on the key exchange method michael@0: // chosen by the server. michael@0: michael@0: #ifndef MOZ_NO_EV_CERTS michael@0: // Try to validate for EV first. michael@0: SECOidTag evPolicy = SEC_OID_UNKNOWN; michael@0: rv = GetFirstEVPolicy(cert, evPolicy); michael@0: if (rv == SECSuccess && evPolicy != SEC_OID_UNKNOWN) { michael@0: NSSCertDBTrustDomain michael@0: trustDomain(trustSSL, michael@0: ocspFetching == NSSCertDBTrustDomain::NeverFetchOCSP michael@0: ? NSSCertDBTrustDomain::LocalOnlyOCSPForEV michael@0: : NSSCertDBTrustDomain::FetchOCSPForEV, michael@0: mOCSPCache, pinArg, &callbackContainer); michael@0: rv = BuildCertChainForOneKeyUsage(trustDomain, cert, time, michael@0: KeyUsage::digitalSignature, // ECDHE/DHE michael@0: KeyUsage::keyEncipherment, // RSA michael@0: KeyUsage::keyAgreement, // (EC)DH michael@0: SEC_OID_EXT_KEY_USAGE_SERVER_AUTH, michael@0: evPolicy, stapledOCSPResponse, michael@0: builtChain); michael@0: if (rv == SECSuccess) { michael@0: if (evOidPolicy) { michael@0: *evOidPolicy = evPolicy; michael@0: } michael@0: break; michael@0: } michael@0: builtChain = nullptr; // clear built chain, just in case. michael@0: } michael@0: #endif michael@0: michael@0: if (flags & FLAG_MUST_BE_EV) { michael@0: PR_SetError(SEC_ERROR_POLICY_VALIDATION_FAILED, 0); michael@0: rv = SECFailure; michael@0: break; michael@0: } michael@0: michael@0: // Now try non-EV. michael@0: NSSCertDBTrustDomain trustDomain(trustSSL, ocspFetching, mOCSPCache, michael@0: pinArg, &callbackContainer); michael@0: rv = BuildCertChainForOneKeyUsage(trustDomain, cert, time, michael@0: KeyUsage::digitalSignature, // (EC)DHE michael@0: KeyUsage::keyEncipherment, // RSA michael@0: KeyUsage::keyAgreement, // (EC)DH michael@0: SEC_OID_EXT_KEY_USAGE_SERVER_AUTH, michael@0: SEC_OID_X509_ANY_POLICY, michael@0: stapledOCSPResponse, builtChain); michael@0: break; michael@0: } michael@0: michael@0: case certificateUsageSSLCA: { michael@0: NSSCertDBTrustDomain trustDomain(trustSSL, ocspFetching, mOCSPCache, michael@0: pinArg); michael@0: rv = BuildCertChain(trustDomain, cert, time, MustBeCA, michael@0: KeyUsage::keyCertSign, michael@0: SEC_OID_EXT_KEY_USAGE_SERVER_AUTH, michael@0: SEC_OID_X509_ANY_POLICY, michael@0: stapledOCSPResponse, builtChain); michael@0: break; michael@0: } michael@0: michael@0: case certificateUsageEmailSigner: { michael@0: NSSCertDBTrustDomain trustDomain(trustEmail, ocspFetching, mOCSPCache, michael@0: pinArg); michael@0: rv = BuildCertChain(trustDomain, cert, time, MustBeEndEntity, michael@0: KeyUsage::digitalSignature, michael@0: SEC_OID_EXT_KEY_USAGE_EMAIL_PROTECT, michael@0: SEC_OID_X509_ANY_POLICY, michael@0: stapledOCSPResponse, builtChain); michael@0: break; michael@0: } michael@0: michael@0: case certificateUsageEmailRecipient: { michael@0: // TODO: The higher level S/MIME processing should pass in which key michael@0: // usage it is trying to verify for, and base its algorithm choices michael@0: // based on the result of the verification(s). michael@0: NSSCertDBTrustDomain trustDomain(trustEmail, ocspFetching, mOCSPCache, michael@0: pinArg); michael@0: rv = BuildCertChain(trustDomain, cert, time, MustBeEndEntity, michael@0: KeyUsage::keyEncipherment, // RSA michael@0: SEC_OID_EXT_KEY_USAGE_EMAIL_PROTECT, michael@0: SEC_OID_X509_ANY_POLICY, michael@0: stapledOCSPResponse, builtChain); michael@0: if (rv != SECSuccess && PR_GetError() == SEC_ERROR_INADEQUATE_KEY_USAGE) { michael@0: rv = BuildCertChain(trustDomain, cert, time, MustBeEndEntity, michael@0: KeyUsage::keyAgreement, // ECDH/DH michael@0: SEC_OID_EXT_KEY_USAGE_EMAIL_PROTECT, michael@0: SEC_OID_X509_ANY_POLICY, michael@0: stapledOCSPResponse, builtChain); michael@0: } michael@0: break; michael@0: } michael@0: michael@0: case certificateUsageObjectSigner: { michael@0: NSSCertDBTrustDomain trustDomain(trustObjectSigning, ocspFetching, michael@0: mOCSPCache, pinArg); michael@0: rv = BuildCertChain(trustDomain, cert, time, MustBeEndEntity, michael@0: KeyUsage::digitalSignature, michael@0: SEC_OID_EXT_KEY_USAGE_CODE_SIGN, michael@0: SEC_OID_X509_ANY_POLICY, michael@0: stapledOCSPResponse, builtChain); michael@0: break; michael@0: } michael@0: michael@0: case certificateUsageVerifyCA: michael@0: case certificateUsageStatusResponder: { michael@0: // XXX This is a pretty useless way to verify a certificate. It is used michael@0: // by the implementation of window.crypto.importCertificates and in the michael@0: // certificate viewer UI. Because we don't know what trust bit is michael@0: // interesting, we just try them all. michael@0: mozilla::pkix::EndEntityOrCA endEntityOrCA; michael@0: mozilla::pkix::KeyUsage keyUsage; michael@0: SECOidTag eku; michael@0: if (usage == certificateUsageVerifyCA) { michael@0: endEntityOrCA = MustBeCA; michael@0: keyUsage = KeyUsage::keyCertSign; michael@0: eku = SEC_OID_UNKNOWN; michael@0: } else { michael@0: endEntityOrCA = MustBeEndEntity; michael@0: keyUsage = KeyUsage::digitalSignature; michael@0: eku = SEC_OID_OCSP_RESPONDER; michael@0: } michael@0: michael@0: NSSCertDBTrustDomain sslTrust(trustSSL, ocspFetching, mOCSPCache, michael@0: pinArg); michael@0: rv = BuildCertChain(sslTrust, cert, time, endEntityOrCA, michael@0: keyUsage, eku, SEC_OID_X509_ANY_POLICY, michael@0: stapledOCSPResponse, builtChain); michael@0: if (rv == SECFailure && PR_GetError() == SEC_ERROR_UNKNOWN_ISSUER) { michael@0: NSSCertDBTrustDomain emailTrust(trustEmail, ocspFetching, mOCSPCache, michael@0: pinArg); michael@0: rv = BuildCertChain(emailTrust, cert, time, endEntityOrCA, keyUsage, michael@0: eku, SEC_OID_X509_ANY_POLICY, michael@0: stapledOCSPResponse, builtChain); michael@0: if (rv == SECFailure && PR_GetError() == SEC_ERROR_UNKNOWN_ISSUER) { michael@0: NSSCertDBTrustDomain objectSigningTrust(trustObjectSigning, michael@0: ocspFetching, mOCSPCache, michael@0: pinArg); michael@0: rv = BuildCertChain(objectSigningTrust, cert, time, endEntityOrCA, michael@0: keyUsage, eku, SEC_OID_X509_ANY_POLICY, michael@0: stapledOCSPResponse, builtChain); michael@0: } michael@0: } michael@0: michael@0: break; michael@0: } michael@0: michael@0: default: michael@0: PR_SetError(SEC_ERROR_INVALID_ARGS, 0); michael@0: return SECFailure; michael@0: } michael@0: michael@0: // If there is an error we may need to worsen to error to be a pinning failure michael@0: if (rv != SECSuccess && usage == certificateUsageSSLServer && michael@0: PR_GetError() != SEC_ERROR_APPLICATION_CALLBACK_ERROR) { michael@0: tryWorsenPRErrorInCallback(cert, callbackState); michael@0: } michael@0: michael@0: if (validationChain && rv == SECSuccess) { michael@0: *validationChain = builtChain.release(); michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: SECStatus michael@0: CertVerifier::VerifyCert(CERTCertificate* cert, michael@0: const SECCertificateUsage usage, michael@0: const PRTime time, michael@0: void* pinArg, michael@0: const char* hostname, michael@0: const Flags flags, michael@0: /*optional in*/ const SECItem* stapledOCSPResponse, michael@0: /*optional out*/ ScopedCERTCertList* validationChain, michael@0: /*optional out*/ SECOidTag* evOidPolicy, michael@0: /*optional out*/ CERTVerifyLog* verifyLog) michael@0: { michael@0: ChainValidationCallbackState callbackState = { hostname, michael@0: mPinningEnforcementLevel, michael@0: usage, michael@0: time }; michael@0: michael@0: if (mImplementation == mozillapkix) { michael@0: return MozillaPKIXVerifyCert(cert, usage, time, pinArg, flags, michael@0: &callbackState, stapledOCSPResponse, michael@0: validationChain, evOidPolicy); michael@0: } michael@0: michael@0: if (!cert) michael@0: { michael@0: PR_NOT_REACHED("Invalid arguments to CertVerifier::VerifyCert"); michael@0: PORT_SetError(SEC_ERROR_INVALID_ARGS); michael@0: return SECFailure; michael@0: } michael@0: if (validationChain) { michael@0: *validationChain = nullptr; michael@0: } michael@0: if (evOidPolicy) { michael@0: *evOidPolicy = SEC_OID_UNKNOWN; michael@0: } michael@0: michael@0: switch(usage){ michael@0: case certificateUsageSSLClient: michael@0: case certificateUsageSSLServer: michael@0: case certificateUsageSSLCA: michael@0: case certificateUsageEmailSigner: michael@0: case certificateUsageEmailRecipient: michael@0: case certificateUsageObjectSigner: michael@0: case certificateUsageVerifyCA: michael@0: case certificateUsageStatusResponder: michael@0: break; michael@0: default: michael@0: PORT_SetError(SEC_ERROR_INVALID_ARGS); michael@0: return SECFailure; michael@0: } michael@0: michael@0: if ((flags & FLAG_MUST_BE_EV) && usage != certificateUsageSSLServer) { michael@0: PORT_SetError(SEC_ERROR_INVALID_ARGS); michael@0: return SECFailure; michael@0: } michael@0: michael@0: #ifndef NSS_NO_LIBPKIX michael@0: ScopedCERTCertList trustAnchors; michael@0: SECStatus rv; michael@0: SECOidTag evPolicy = SEC_OID_UNKNOWN; michael@0: michael@0: // Do EV checking only for sslserver usage michael@0: if (usage == certificateUsageSSLServer) { michael@0: SECStatus srv = GetFirstEVPolicy(cert, evPolicy); michael@0: if (srv == SECSuccess) { michael@0: if (evPolicy != SEC_OID_UNKNOWN) { michael@0: trustAnchors = GetRootsForOid(evPolicy); michael@0: } michael@0: if (!trustAnchors) { michael@0: return SECFailure; michael@0: } michael@0: // pkix ignores an empty trustanchors list and michael@0: // decides then to use the whole set of trust in the DB michael@0: // so we set the evPolicy to unkown in this case michael@0: if (CERT_LIST_EMPTY(trustAnchors)) { michael@0: evPolicy = SEC_OID_UNKNOWN; michael@0: } michael@0: } else { michael@0: // No known EV policy found michael@0: if (flags & FLAG_MUST_BE_EV) { michael@0: PORT_SetError(SEC_ERROR_EXTENSION_NOT_FOUND); michael@0: return SECFailure; michael@0: } michael@0: // Do not setup EV verification params michael@0: evPolicy = SEC_OID_UNKNOWN; michael@0: } michael@0: if ((evPolicy == SEC_OID_UNKNOWN) && (flags & FLAG_MUST_BE_EV)) { michael@0: PORT_SetError(SEC_ERROR_UNKNOWN_ISSUER); michael@0: return SECFailure; michael@0: } michael@0: } michael@0: michael@0: PR_ASSERT(evPolicy == SEC_OID_UNKNOWN || trustAnchors); michael@0: michael@0: size_t i = 0; michael@0: size_t validationChainLocation = 0; michael@0: size_t validationTrustAnchorLocation = 0; michael@0: CERTValOutParam cvout[4]; michael@0: if (verifyLog) { michael@0: cvout[i].type = cert_po_errorLog; michael@0: cvout[i].value.pointer.log = verifyLog; michael@0: ++i; michael@0: } michael@0: if (validationChain) { michael@0: PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, ("VerifyCert: setting up validation chain outparam.\n")); michael@0: validationChainLocation = i; michael@0: cvout[i].type = cert_po_certList; michael@0: cvout[i].value.pointer.chain = nullptr; michael@0: ++i; michael@0: validationTrustAnchorLocation = i; michael@0: cvout[i].type = cert_po_trustAnchor; michael@0: cvout[i].value.pointer.cert = nullptr; michael@0: ++i; michael@0: } michael@0: cvout[i].type = cert_po_end; michael@0: michael@0: CERTRevocationFlags rev; michael@0: michael@0: CERTRevocationMethodIndex revPreferredMethods[2]; michael@0: rev.leafTests.preferred_methods = michael@0: rev.chainTests.preferred_methods = revPreferredMethods; michael@0: michael@0: uint64_t revFlagsPerMethod[2]; michael@0: rev.leafTests.cert_rev_flags_per_method = michael@0: rev.chainTests.cert_rev_flags_per_method = revFlagsPerMethod; michael@0: rev.leafTests.number_of_preferred_methods = michael@0: rev.chainTests.number_of_preferred_methods = 1; michael@0: michael@0: rev.leafTests.number_of_defined_methods = michael@0: rev.chainTests.number_of_defined_methods = cert_revocation_method_ocsp + 1; michael@0: michael@0: const bool localOnly = flags & FLAG_LOCAL_ONLY; michael@0: CERTValInParam cvin[7]; michael@0: michael@0: // Parameters for both EV and DV validation michael@0: cvin[0].type = cert_pi_useAIACertFetch; michael@0: cvin[0].value.scalar.b = mMissingCertDownloadEnabled && !localOnly; michael@0: cvin[1].type = cert_pi_revocationFlags; michael@0: cvin[1].value.pointer.revocation = &rev; michael@0: cvin[2].type = cert_pi_date; michael@0: cvin[2].value.scalar.time = time; michael@0: i = 3; michael@0: michael@0: CERTChainVerifyCallback callbackContainer; michael@0: if (usage == certificateUsageSSLServer) { michael@0: callbackContainer.isChainValid = chainValidationCallback; michael@0: callbackContainer.isChainValidArg = &callbackState; michael@0: cvin[i].type = cert_pi_chainVerifyCallback; michael@0: cvin[i].value.pointer.chainVerifyCallback = &callbackContainer; michael@0: ++i; michael@0: } michael@0: michael@0: const size_t evParamLocation = i; michael@0: michael@0: if (evPolicy != SEC_OID_UNKNOWN) { michael@0: // EV setup! michael@0: // XXX 859872 The current flags are not quite correct. (use michael@0: // of ocsp flags for crl preferences). michael@0: uint64_t ocspRevMethodFlags = michael@0: CERT_REV_M_TEST_USING_THIS_METHOD michael@0: | ((mOCSPDownloadEnabled && !localOnly) ? michael@0: CERT_REV_M_ALLOW_NETWORK_FETCHING : CERT_REV_M_FORBID_NETWORK_FETCHING) michael@0: | CERT_REV_M_ALLOW_IMPLICIT_DEFAULT_SOURCE michael@0: | CERT_REV_M_REQUIRE_INFO_ON_MISSING_SOURCE michael@0: | CERT_REV_M_IGNORE_MISSING_FRESH_INFO michael@0: | CERT_REV_M_STOP_TESTING_ON_FRESH_INFO michael@0: | (mOCSPGETEnabled ? 0 : CERT_REV_M_FORCE_POST_METHOD_FOR_OCSP); michael@0: michael@0: rev.leafTests.cert_rev_flags_per_method[cert_revocation_method_crl] = michael@0: rev.chainTests.cert_rev_flags_per_method[cert_revocation_method_crl] michael@0: = CERT_REV_M_DO_NOT_TEST_USING_THIS_METHOD; michael@0: michael@0: rev.leafTests.cert_rev_flags_per_method[cert_revocation_method_ocsp] = michael@0: rev.chainTests.cert_rev_flags_per_method[cert_revocation_method_ocsp] michael@0: = ocspRevMethodFlags; michael@0: michael@0: rev.leafTests.cert_rev_method_independent_flags = michael@0: rev.chainTests.cert_rev_method_independent_flags = michael@0: // avoiding the network is good, let's try local first michael@0: CERT_REV_MI_TEST_ALL_LOCAL_INFORMATION_FIRST michael@0: // is overall revocation requirement strict or relaxed? michael@0: | CERT_REV_MI_REQUIRE_SOME_FRESH_INFO_AVAILABLE michael@0: ; michael@0: michael@0: rev.leafTests.preferred_methods[0] = michael@0: rev.chainTests.preferred_methods[0] = cert_revocation_method_ocsp; michael@0: michael@0: cvin[i].type = cert_pi_policyOID; michael@0: cvin[i].value.arraySize = 1; michael@0: cvin[i].value.array.oids = &evPolicy; michael@0: ++i; michael@0: PR_ASSERT(trustAnchors); michael@0: cvin[i].type = cert_pi_trustAnchors; michael@0: cvin[i].value.pointer.chain = trustAnchors.get(); michael@0: ++i; michael@0: michael@0: cvin[i].type = cert_pi_end; michael@0: michael@0: rv = CERT_PKIXVerifyCert(cert, usage, cvin, cvout, pinArg); michael@0: if (rv == SECSuccess) { michael@0: if (evOidPolicy) { michael@0: *evOidPolicy = evPolicy; michael@0: } michael@0: PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, michael@0: ("VerifyCert: successful CERT_PKIXVerifyCert(ev) \n")); michael@0: goto pkix_done; michael@0: } michael@0: PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, michael@0: ("VerifyCert: failed CERT_PKIXVerifyCert(ev)\n")); michael@0: if (flags & FLAG_MUST_BE_EV) { michael@0: return rv; michael@0: } michael@0: if (validationChain) { michael@0: destroyCertListThatShouldNotExist( michael@0: &cvout[validationChainLocation].value.pointer.chain); michael@0: } michael@0: michael@0: if (verifyLog) { michael@0: // Cleanup the log so that it is ready the the next validation michael@0: CERTVerifyLogNode* i_node; michael@0: for (i_node = verifyLog->head; i_node; i_node = i_node->next) { michael@0: //destroy cert if any. michael@0: if (i_node->cert) { michael@0: CERT_DestroyCertificate(i_node->cert); michael@0: } michael@0: // No need to cleanup the actual nodes in the arena. michael@0: } michael@0: verifyLog->count = 0; michael@0: verifyLog->head = nullptr; michael@0: verifyLog->tail = nullptr; michael@0: } michael@0: michael@0: } michael@0: #endif michael@0: michael@0: // If we're here, PKIX EV verification failed. michael@0: // If requested, don't do DV fallback. michael@0: if (flags & FLAG_MUST_BE_EV) { michael@0: PR_ASSERT(*evOidPolicy == SEC_OID_UNKNOWN); michael@0: #ifdef NSS_NO_LIBPKIX michael@0: PR_SetError(SEC_ERROR_INVALID_ARGS, 0); michael@0: #else michael@0: PR_SetError(PR_INVALID_STATE_ERROR, 0); michael@0: #endif michael@0: return SECFailure; michael@0: } michael@0: michael@0: if (mImplementation == classic) { michael@0: // XXX: we do not care about the localOnly flag (currently) as the michael@0: // caller that wants localOnly should disable and reenable the fetching. michael@0: return ClassicVerifyCert(cert, usage, time, pinArg, &callbackState, michael@0: validationChain, verifyLog); michael@0: } michael@0: michael@0: #ifdef NSS_NO_LIBPKIX michael@0: PR_NOT_REACHED("libpkix implementation chosen but not even compiled in"); michael@0: PR_SetError(PR_INVALID_STATE_ERROR, 0); michael@0: return SECFailure; michael@0: #else michael@0: PR_ASSERT(mImplementation == libpkix); michael@0: michael@0: // The current flags check the chain the same way as the leafs michael@0: rev.leafTests.cert_rev_flags_per_method[cert_revocation_method_crl] = michael@0: rev.chainTests.cert_rev_flags_per_method[cert_revocation_method_crl] = michael@0: // implicit default source - makes no sense for CRLs michael@0: CERT_REV_M_IGNORE_IMPLICIT_DEFAULT_SOURCE michael@0: michael@0: // let's not stop on fresh CRL. If OCSP is enabled, too, let's check it michael@0: | CERT_REV_M_CONTINUE_TESTING_ON_FRESH_INFO michael@0: michael@0: // no fresh CRL? well, let other flag decide whether to fail or not michael@0: | CERT_REV_M_IGNORE_MISSING_FRESH_INFO michael@0: michael@0: // testing using local CRLs is always allowed michael@0: | CERT_REV_M_TEST_USING_THIS_METHOD michael@0: michael@0: // no local crl and don't know where to get it from? ignore michael@0: | CERT_REV_M_SKIP_TEST_ON_MISSING_SOURCE michael@0: michael@0: // crl download based on parameter michael@0: | ((mCRLDownloadEnabled && !localOnly) ? michael@0: CERT_REV_M_ALLOW_NETWORK_FETCHING : CERT_REV_M_FORBID_NETWORK_FETCHING) michael@0: ; michael@0: michael@0: rev.leafTests.cert_rev_flags_per_method[cert_revocation_method_ocsp] = michael@0: rev.chainTests.cert_rev_flags_per_method[cert_revocation_method_ocsp] = michael@0: // use OCSP michael@0: CERT_REV_M_TEST_USING_THIS_METHOD michael@0: michael@0: // if app has a default OCSP responder configured, let's use it michael@0: | CERT_REV_M_ALLOW_IMPLICIT_DEFAULT_SOURCE michael@0: michael@0: // of course OCSP doesn't work without a source. let's accept such certs michael@0: | CERT_REV_M_SKIP_TEST_ON_MISSING_SOURCE michael@0: michael@0: // if ocsp is required stop on lack of freshness michael@0: | (mOCSPStrict ? michael@0: CERT_REV_M_FAIL_ON_MISSING_FRESH_INFO : CERT_REV_M_IGNORE_MISSING_FRESH_INFO) michael@0: michael@0: // ocsp success is sufficient michael@0: | CERT_REV_M_STOP_TESTING_ON_FRESH_INFO michael@0: michael@0: // ocsp enabled controls network fetching, too michael@0: | ((mOCSPDownloadEnabled && !localOnly) ? michael@0: CERT_REV_M_ALLOW_NETWORK_FETCHING : CERT_REV_M_FORBID_NETWORK_FETCHING) michael@0: michael@0: | (mOCSPGETEnabled ? 0 : CERT_REV_M_FORCE_POST_METHOD_FOR_OCSP); michael@0: ; michael@0: michael@0: rev.leafTests.preferred_methods[0] = michael@0: rev.chainTests.preferred_methods[0] = cert_revocation_method_ocsp; michael@0: michael@0: rev.leafTests.cert_rev_method_independent_flags = michael@0: rev.chainTests.cert_rev_method_independent_flags = michael@0: // avoiding the network is good, let's try local first michael@0: CERT_REV_MI_TEST_ALL_LOCAL_INFORMATION_FIRST; michael@0: michael@0: // Skip EV parameters michael@0: cvin[evParamLocation].type = cert_pi_end; michael@0: michael@0: PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, ("VerifyCert: calling CERT_PKIXVerifyCert(dv) \n")); michael@0: rv = CERT_PKIXVerifyCert(cert, usage, cvin, cvout, pinArg); michael@0: michael@0: pkix_done: michael@0: // If there is an error we may need to worsen to error to be a pinning failure michael@0: if (rv != SECSuccess && usage == certificateUsageSSLServer && michael@0: PR_GetError() != SEC_ERROR_APPLICATION_CALLBACK_ERROR) { michael@0: tryWorsenPRErrorInCallback(cert, &callbackState); michael@0: } michael@0: michael@0: if (validationChain) { michael@0: PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, ("VerifyCert: validation chain requested\n")); michael@0: ScopedCERTCertificate trustAnchor(cvout[validationTrustAnchorLocation].value.pointer.cert); michael@0: michael@0: if (rv == SECSuccess) { michael@0: if (! cvout[validationChainLocation].value.pointer.chain) { michael@0: PR_SetError(PR_UNKNOWN_ERROR, 0); michael@0: return SECFailure; michael@0: } michael@0: PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, ("VerifyCert: I have a chain\n")); michael@0: *validationChain = cvout[validationChainLocation].value.pointer.chain; michael@0: if (trustAnchor) { michael@0: // we should only add the issuer to the chain if it is not already michael@0: // present. On CA cert checking, the issuer is the same cert, so in michael@0: // that case we do not add the cert to the chain. michael@0: if (!CERT_CompareCerts(trustAnchor.get(), cert)) { michael@0: PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, ("VerifyCert: adding issuer to tail for display\n")); michael@0: // note: rv is reused to catch errors on cert creation! michael@0: ScopedCERTCertificate tempCert(CERT_DupCertificate(trustAnchor.get())); michael@0: rv = CERT_AddCertToListTail(validationChain->get(), tempCert.get()); michael@0: if (rv == SECSuccess) { michael@0: tempCert.release(); // ownership traferred to validationChain michael@0: } else { michael@0: *validationChain = nullptr; michael@0: } michael@0: } michael@0: } michael@0: } else { michael@0: destroyCertListThatShouldNotExist( michael@0: &cvout[validationChainLocation].value.pointer.chain); michael@0: } michael@0: } michael@0: michael@0: return rv; michael@0: #endif michael@0: } michael@0: michael@0: SECStatus michael@0: CertVerifier::VerifySSLServerCert(CERTCertificate* peerCert, michael@0: /*optional*/ const SECItem* stapledOCSPResponse, michael@0: PRTime time, michael@0: /*optional*/ void* pinarg, michael@0: const char* hostname, michael@0: bool saveIntermediatesInPermanentDatabase, michael@0: /*optional out*/ mozilla::pkix::ScopedCERTCertList* certChainOut, michael@0: /*optional out*/ SECOidTag* evOidPolicy) michael@0: { michael@0: PR_ASSERT(peerCert); michael@0: // XXX: PR_ASSERT(pinarg) michael@0: PR_ASSERT(hostname); michael@0: PR_ASSERT(hostname[0]); michael@0: michael@0: if (certChainOut) { michael@0: *certChainOut = nullptr; michael@0: } michael@0: if (evOidPolicy) { michael@0: *evOidPolicy = SEC_OID_UNKNOWN; michael@0: } michael@0: michael@0: if (!hostname || !hostname[0]) { michael@0: PR_SetError(SSL_ERROR_BAD_CERT_DOMAIN, 0); michael@0: return SECFailure; michael@0: } michael@0: michael@0: // CreateCertErrorRunnable assumes that CERT_VerifyCertName is only called michael@0: // if VerifyCert succeeded. michael@0: ScopedCERTCertList validationChain; michael@0: SECStatus rv = VerifyCert(peerCert, certificateUsageSSLServer, time, pinarg, michael@0: hostname, 0, stapledOCSPResponse, &validationChain, michael@0: evOidPolicy, nullptr); michael@0: if (rv != SECSuccess) { michael@0: return rv; michael@0: } michael@0: michael@0: rv = CERT_VerifyCertName(peerCert, hostname); michael@0: if (rv != SECSuccess) { michael@0: return rv; michael@0: } michael@0: michael@0: if (saveIntermediatesInPermanentDatabase) { michael@0: SaveIntermediateCerts(validationChain); michael@0: } michael@0: michael@0: if (certChainOut) { michael@0: *certChainOut = validationChain.release(); michael@0: } michael@0: michael@0: return SECSuccess; michael@0: } michael@0: michael@0: } } // namespace mozilla::psm