/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "CertVerifier.h" #include #include "AppTrustDomain.h" #include "CTDiversityPolicy.h" #include "CTKnownLogs.h" #include "CTLogVerifier.h" #include "ExtendedValidation.h" #include "MultiLogCTVerifier.h" #include "NSSCertDBTrustDomain.h" #include "NSSErrorsService.h" #include "cert.h" #include "mozilla/Assertions.h" #include "mozilla/Casting.h" #include "mozilla/IntegerPrintfMacros.h" #include "mozilla/Logging.h" #include "nsNSSComponent.h" #include "mozilla/SyncRunnable.h" #include "nsPromiseFlatString.h" #include "nsServiceManagerUtils.h" #include "pk11pub.h" #include "mozpkix/pkix.h" #include "mozpkix/pkixcheck.h" #include "mozpkix/pkixnss.h" #include "mozpkix/pkixutil.h" #include "secmod.h" #include "nsNetCID.h" using namespace mozilla::ct; using namespace mozilla::pkix; using namespace mozilla::psm; mozilla::LazyLogModule gCertVerifierLog("certverifier"); // Returns the certificate validity period in calendar months (rounded down). // "extern" to allow unit tests in CTPolicyEnforcerTest.cpp. extern mozilla::pkix::Result GetCertLifetimeInFullMonths(Time certNotBefore, Time certNotAfter, size_t& months) { if (certNotBefore >= certNotAfter) { MOZ_ASSERT_UNREACHABLE("Expected notBefore < notAfter"); return mozilla::pkix::Result::FATAL_ERROR_INVALID_ARGS; } uint64_t notBeforeSeconds; Result rv = SecondsSinceEpochFromTime(certNotBefore, ¬BeforeSeconds); if (rv != Success) { return rv; } uint64_t notAfterSeconds; rv = SecondsSinceEpochFromTime(certNotAfter, ¬AfterSeconds); if (rv != Success) { return rv; } // PRTime is microseconds PRTime notBeforePR = static_cast(notBeforeSeconds) * 1000000; PRTime notAfterPR = static_cast(notAfterSeconds) * 1000000; PRExplodedTime explodedNotBefore; PRExplodedTime explodedNotAfter; PR_ExplodeTime(notBeforePR, PR_LocalTimeParameters, &explodedNotBefore); PR_ExplodeTime(notAfterPR, PR_LocalTimeParameters, &explodedNotAfter); PRInt32 signedMonths = (explodedNotAfter.tm_year - explodedNotBefore.tm_year) * 12 + (explodedNotAfter.tm_month - explodedNotBefore.tm_month); if (explodedNotAfter.tm_mday < explodedNotBefore.tm_mday) { --signedMonths; } // Can't use `mozilla::AssertedCast(signedMonths)` below // since it currently generates a warning on Win x64 debug. if (signedMonths < 0) { MOZ_ASSERT_UNREACHABLE("Expected explodedNotBefore < explodedNotAfter"); return mozilla::pkix::Result::FATAL_ERROR_LIBRARY_FAILURE; } months = static_cast(signedMonths); return Success; } namespace mozilla { namespace psm { const CertVerifier::Flags CertVerifier::FLAG_LOCAL_ONLY = 1; const CertVerifier::Flags CertVerifier::FLAG_MUST_BE_EV = 2; const CertVerifier::Flags CertVerifier::FLAG_TLS_IGNORE_STATUS_REQUEST = 4; static const unsigned int MIN_RSA_BITS = 2048; static const unsigned int MIN_RSA_BITS_WEAK = 1024; void CertificateTransparencyInfo::Reset() { enabled = false; verifyResult.Reset(); policyCompliance = CTPolicyCompliance::Unknown; } CertVerifier::CertVerifier(OcspDownloadConfig odc, OcspStrictConfig osc, mozilla::TimeDuration ocspTimeoutSoft, mozilla::TimeDuration ocspTimeoutHard, uint32_t certShortLifetimeInDays, NetscapeStepUpPolicy netscapeStepUpPolicy, CertificateTransparencyMode ctMode, CRLiteMode crliteMode, const Vector& thirdPartyCerts) : mOCSPDownloadConfig(odc), mOCSPStrict(osc == ocspStrict), mOCSPTimeoutSoft(ocspTimeoutSoft), mOCSPTimeoutHard(ocspTimeoutHard), mCertShortLifetimeInDays(certShortLifetimeInDays), mNetscapeStepUpPolicy(netscapeStepUpPolicy), mCTMode(ctMode), mCRLiteMode(crliteMode) { LoadKnownCTLogs(); for (const auto& root : thirdPartyCerts) { EnterpriseCert rootCopy; // Best-effort. If we run out of memory, users might see untrusted issuer // errors, but the browser will probably crash before then. if (NS_SUCCEEDED(rootCopy.Init(root))) { Unused << mThirdPartyCerts.append(std::move(rootCopy)); } } for (const auto& root : mThirdPartyCerts) { Input input; if (root.GetInput(input) == Success) { // mThirdPartyCerts consists of roots and intermediates. if (root.GetIsRoot()) { // Best effort again. Unused << mThirdPartyRootInputs.append(input); } else { Unused << mThirdPartyIntermediateInputs.append(input); } } } } CertVerifier::~CertVerifier() = default; Result IsDelegatedCredentialAcceptable(const DelegatedCredentialInfo& dcInfo) { bool isEcdsa = dcInfo.scheme == ssl_sig_ecdsa_secp256r1_sha256 || dcInfo.scheme == ssl_sig_ecdsa_secp384r1_sha384 || dcInfo.scheme == ssl_sig_ecdsa_secp521r1_sha512; // Firefox currently does not advertise any RSA schemes for use // with Delegated Credentials. As a secondary (on top of NSS) // check, disallow any RSA SPKI here. When ssl_sig_rsa_pss_pss_* // schemes are supported, check the modulus size and allow RSA here. if (!isEcdsa) { return Result::ERROR_INVALID_KEY; } return Result::Success; } // The term "builtin root" traditionally refers to a root CA certificate that // has been added to the NSS trust store, because it has been approved // for inclusion according to the Mozilla CA policy, and might be accepted // by Mozilla applications as an issuer for certificates seen on the public web. Result IsCertBuiltInRoot(Input certInput, bool& result) { result = false; if (NS_FAILED(BlockUntilLoadableCertsLoaded())) { return Result::FATAL_ERROR_LIBRARY_FAILURE; } #ifdef DEBUG nsCOMPtr component(do_GetService(PSM_COMPONENT_CONTRACTID)); if (!component) { return Result::FATAL_ERROR_LIBRARY_FAILURE; } nsTArray certBytes; certBytes.AppendElements(certInput.UnsafeGetData(), certInput.GetLength()); if (NS_FAILED(component->IsCertTestBuiltInRoot(certBytes, &result))) { return Result::FATAL_ERROR_LIBRARY_FAILURE; } if (result) { return Success; } #endif // DEBUG SECItem certItem(UnsafeMapInputToSECItem(certInput)); AutoSECMODListReadLock lock; for (SECMODModuleList* list = SECMOD_GetDefaultModuleList(); list; list = list->next) { for (int i = 0; i < list->module->slotCount; i++) { PK11SlotInfo* slot = list->module->slots[i]; // We're searching for the "builtin root module", which is a module that // contains an object with a CKA_CLASS of CKO_NETSCAPE_BUILTIN_ROOT_LIST. // We use PK11_HasRootCerts() to identify a module with that property. // In the past, we exclusively used the PKCS#11 module named nssckbi, // which is provided by the NSS library. // Nowadays, some distributions use a replacement module, which contains // the builtin roots, but which also contains additional CA certificates, // such as CAs trusted in a local deployment. // We want to be able to distinguish between these two categories, // because a CA, which may issue certificates for the public web, // is expected to comply with additional requirements. // If the certificate has attribute CKA_NSS_MOZILLA_CA_POLICY set to true, // then we treat it as a "builtin root". if (!PK11_IsPresent(slot) || !PK11_HasRootCerts(slot)) { continue; } CK_OBJECT_HANDLE handle = PK11_FindEncodedCertInSlot(slot, &certItem, nullptr); if (handle == CK_INVALID_HANDLE) { continue; } if (PK11_HasAttributeSet(slot, handle, CKA_NSS_MOZILLA_CA_POLICY, false)) { // Attribute was found, and is set to true result = true; break; } } } return Success; } static Result BuildCertChainForOneKeyUsage( NSSCertDBTrustDomain& trustDomain, Input certDER, Time time, KeyUsage ku1, KeyUsage ku2, KeyUsage ku3, KeyPurposeId eku, const CertPolicyId& requiredPolicy, const Input* stapledOCSPResponse, /*optional out*/ CertVerifier::OCSPStaplingStatus* ocspStaplingStatus) { trustDomain.ResetAccumulatedState(); Result rv = BuildCertChain(trustDomain, certDER, time, EndEntityOrCA::MustBeEndEntity, ku1, eku, requiredPolicy, stapledOCSPResponse); if (rv == Result::ERROR_INADEQUATE_KEY_USAGE) { trustDomain.ResetAccumulatedState(); rv = BuildCertChain(trustDomain, certDER, time, EndEntityOrCA::MustBeEndEntity, ku2, eku, requiredPolicy, stapledOCSPResponse); if (rv == Result::ERROR_INADEQUATE_KEY_USAGE) { trustDomain.ResetAccumulatedState(); rv = BuildCertChain(trustDomain, certDER, time, EndEntityOrCA::MustBeEndEntity, ku3, eku, requiredPolicy, stapledOCSPResponse); if (rv != Success) { rv = Result::ERROR_INADEQUATE_KEY_USAGE; } } } if (ocspStaplingStatus) { *ocspStaplingStatus = trustDomain.GetOCSPStaplingStatus(); } return rv; } void CertVerifier::LoadKnownCTLogs() { if (mCTMode == CertificateTransparencyMode::Disabled) { return; } mCTVerifier = MakeUnique(); for (const CTLogInfo& log : kCTLogList) { Input publicKey; Result rv = publicKey.Init( BitwiseCast(log.key), log.keyLength); if (rv != Success) { MOZ_ASSERT_UNREACHABLE("Failed reading a log key for a known CT Log"); continue; } CTLogVerifier logVerifier; const CTLogOperatorInfo& logOperator = kCTLogOperatorList[log.operatorIndex]; rv = logVerifier.Init(publicKey, logOperator.id, log.status, log.disqualificationTime); if (rv != Success) { MOZ_ASSERT_UNREACHABLE("Failed initializing a known CT Log"); continue; } mCTVerifier->AddLog(std::move(logVerifier)); } // TBD: Initialize mCTDiversityPolicy with the CA dependency map // of the known CT logs operators. mCTDiversityPolicy = MakeUnique(); } Result CertVerifier::VerifyCertificateTransparencyPolicy( NSSCertDBTrustDomain& trustDomain, const nsTArray>& builtChain, Input sctsFromTLS, Time time, /*optional out*/ CertificateTransparencyInfo* ctInfo) { if (ctInfo) { ctInfo->Reset(); } if (mCTMode == CertificateTransparencyMode::Disabled) { return Success; } if (ctInfo) { ctInfo->enabled = true; } if (builtChain.IsEmpty()) { return Result::FATAL_ERROR_INVALID_ARGS; } Input embeddedSCTs = trustDomain.GetSCTListFromCertificate(); if (embeddedSCTs.GetLength() > 0) { MOZ_LOG(gCertVerifierLog, LogLevel::Debug, ("Got embedded SCT data of length %zu\n", static_cast(embeddedSCTs.GetLength()))); } Input sctsFromOCSP = trustDomain.GetSCTListFromOCSPStapling(); if (sctsFromOCSP.GetLength() > 0) { MOZ_LOG(gCertVerifierLog, LogLevel::Debug, ("Got OCSP SCT data of length %zu\n", static_cast(sctsFromOCSP.GetLength()))); } if (sctsFromTLS.GetLength() > 0) { MOZ_LOG(gCertVerifierLog, LogLevel::Debug, ("Got TLS SCT data of length %zu\n", static_cast(sctsFromTLS.GetLength()))); } if (builtChain.Length() == 1) { // Issuer certificate is required for SCT verification. // If we've arrived here, we probably have a "trust chain" with only one // certificate (i.e. a self-signed end-entity that has been set as a trust // anchor either by a third party modifying our trust DB or via the // enterprise roots feature). If this is the case, certificate transparency // information will probably not be present, and it certainly won't verify // correctly. To simplify things, we return an empty CTVerifyResult and a // "not enough SCTs" CTPolicyCompliance result. if (ctInfo) { CTVerifyResult emptyResult; ctInfo->verifyResult = std::move(emptyResult); ctInfo->policyCompliance = CTPolicyCompliance::NotEnoughScts; } return Success; } const nsTArray& endEntityBytes = builtChain.ElementAt(0); Input endEntityInput; Result rv = endEntityInput.Init(endEntityBytes.Elements(), endEntityBytes.Length()); if (rv != Success) { return rv; } const nsTArray& issuerBytes = builtChain.ElementAt(1); Input issuerInput; rv = issuerInput.Init(issuerBytes.Elements(), issuerBytes.Length()); if (rv != Success) { return rv; } BackCert issuerBackCert(issuerInput, EndEntityOrCA::MustBeCA, nullptr); rv = issuerBackCert.Init(); if (rv != Success) { return rv; } Input issuerPublicKeyInput = issuerBackCert.GetSubjectPublicKeyInfo(); CTVerifyResult result; rv = mCTVerifier->Verify(endEntityInput, issuerPublicKeyInput, embeddedSCTs, sctsFromOCSP, sctsFromTLS, time, result); if (rv != Success) { MOZ_LOG(gCertVerifierLog, LogLevel::Debug, ("SCT verification failed with fatal error %" PRId32 "\n", static_cast(rv))); return rv; } if (MOZ_LOG_TEST(gCertVerifierLog, LogLevel::Debug)) { size_t validCount = 0; size_t unknownLogCount = 0; size_t disqualifiedLogCount = 0; size_t invalidSignatureCount = 0; size_t invalidTimestampCount = 0; for (const VerifiedSCT& verifiedSct : result.verifiedScts) { switch (verifiedSct.status) { case VerifiedSCT::Status::Valid: validCount++; break; case VerifiedSCT::Status::ValidFromDisqualifiedLog: disqualifiedLogCount++; break; case VerifiedSCT::Status::UnknownLog: unknownLogCount++; break; case VerifiedSCT::Status::InvalidSignature: invalidSignatureCount++; break; case VerifiedSCT::Status::InvalidTimestamp: invalidTimestampCount++; break; case VerifiedSCT::Status::None: default: MOZ_ASSERT_UNREACHABLE("Unexpected SCT verification status"); } } MOZ_LOG( gCertVerifierLog, LogLevel::Debug, ("SCT verification result: " "valid=%zu unknownLog=%zu disqualifiedLog=%zu " "invalidSignature=%zu invalidTimestamp=%zu " "decodingErrors=%zu\n", validCount, unknownLogCount, disqualifiedLogCount, invalidSignatureCount, invalidTimestampCount, result.decodingErrors)); } BackCert endEntityBackCert(endEntityInput, EndEntityOrCA::MustBeEndEntity, nullptr); rv = endEntityBackCert.Init(); if (rv != Success) { return rv; } Time notBefore(Time::uninitialized); Time notAfter(Time::uninitialized); rv = ParseValidity(endEntityBackCert.GetValidity(), ¬Before, ¬After); if (rv != Success) { return rv; } size_t lifetimeInMonths; rv = GetCertLifetimeInFullMonths(notBefore, notAfter, lifetimeInMonths); if (rv != Success) { return rv; } CTLogOperatorList allOperators; GetCTLogOperatorsFromVerifiedSCTList(result.verifiedScts, allOperators); CTLogOperatorList dependentOperators; rv = mCTDiversityPolicy->GetDependentOperators(builtChain, allOperators, dependentOperators); if (rv != Success) { return rv; } CTPolicyEnforcer ctPolicyEnforcer; CTPolicyCompliance ctPolicyCompliance; ctPolicyEnforcer.CheckCompliance(result.verifiedScts, lifetimeInMonths, dependentOperators, ctPolicyCompliance); if (ctInfo) { ctInfo->verifyResult = std::move(result); ctInfo->policyCompliance = ctPolicyCompliance; } return Success; } Result CertVerifier::VerifyCert( const nsTArray& certBytes, SECCertificateUsage usage, Time time, void* pinArg, const char* hostname, /*out*/ nsTArray>& builtChain, /*optional*/ const Flags flags, /*optional*/ const Maybe>>& extraCertificates, /*optional*/ const Maybe>& stapledOCSPResponseArg, /*optional*/ const Maybe>& sctsFromTLS, /*optional*/ const OriginAttributes& originAttributes, /*optional out*/ EVStatus* evStatus, /*optional out*/ OCSPStaplingStatus* ocspStaplingStatus, /*optional out*/ KeySizeStatus* keySizeStatus, /*optional out*/ PinningTelemetryInfo* pinningTelemetryInfo, /*optional out*/ CertificateTransparencyInfo* ctInfo, /*optional out*/ bool* isBuiltChainRootBuiltInRoot, /*optional out*/ bool* madeOCSPRequests) { MOZ_LOG(gCertVerifierLog, LogLevel::Debug, ("Top of VerifyCert\n")); MOZ_ASSERT(usage == certificateUsageSSLServer || !(flags & FLAG_MUST_BE_EV)); MOZ_ASSERT(usage == certificateUsageSSLServer || !keySizeStatus); if (NS_FAILED(BlockUntilLoadableCertsLoaded())) { return Result::FATAL_ERROR_LIBRARY_FAILURE; } if (NS_FAILED(CheckForSmartCardChanges())) { return Result::FATAL_ERROR_LIBRARY_FAILURE; } if (evStatus) { *evStatus = EVStatus::NotEV; } if (ocspStaplingStatus) { if (usage != certificateUsageSSLServer) { return Result::FATAL_ERROR_INVALID_ARGS; } *ocspStaplingStatus = OCSP_STAPLING_NEVER_CHECKED; } if (keySizeStatus) { if (usage != certificateUsageSSLServer) { return Result::FATAL_ERROR_INVALID_ARGS; } *keySizeStatus = KeySizeStatus::NeverChecked; } if (usage != certificateUsageSSLServer && (flags & FLAG_MUST_BE_EV)) { return Result::FATAL_ERROR_INVALID_ARGS; } if (isBuiltChainRootBuiltInRoot) { *isBuiltChainRootBuiltInRoot = false; } if (madeOCSPRequests) { *madeOCSPRequests = false; } Input certDER; Result rv = certDER.Init(certBytes.Elements(), certBytes.Length()); if (rv != Success) { return rv; } // We configure the OCSP fetching modes separately for EV and non-EV // verifications. NSSCertDBTrustDomain::OCSPFetching defaultOCSPFetching = (mOCSPDownloadConfig == ocspOff) || (mOCSPDownloadConfig == ocspEVOnly) || (flags & FLAG_LOCAL_ONLY) ? NSSCertDBTrustDomain::NeverFetchOCSP : !mOCSPStrict ? NSSCertDBTrustDomain::FetchOCSPForDVSoftFail : NSSCertDBTrustDomain::FetchOCSPForDVHardFail; Input stapledOCSPResponseInput; const Input* stapledOCSPResponse = nullptr; if (stapledOCSPResponseArg) { rv = stapledOCSPResponseInput.Init(stapledOCSPResponseArg->Elements(), stapledOCSPResponseArg->Length()); if (rv != Success) { // The stapled OCSP response was too big. return Result::ERROR_OCSP_MALFORMED_RESPONSE; } stapledOCSPResponse = &stapledOCSPResponseInput; } Input sctsFromTLSInput; if (sctsFromTLS) { rv = sctsFromTLSInput.Init(sctsFromTLS->Elements(), sctsFromTLS->Length()); if (rv != Success && sctsFromTLSInput.GetLength() != 0) { return Result::FATAL_ERROR_LIBRARY_FAILURE; } } switch (usage) { case certificateUsageSSLClient: { // XXX: We don't really have a trust bit for SSL client authentication so // just use trustEmail as it is the closest alternative. NSSCertDBTrustDomain trustDomain( trustEmail, defaultOCSPFetching, mOCSPCache, pinArg, mOCSPTimeoutSoft, mOCSPTimeoutHard, mCertShortLifetimeInDays, MIN_RSA_BITS_WEAK, ValidityCheckingMode::CheckingOff, NetscapeStepUpPolicy::NeverMatch, mCRLiteMode, originAttributes, mThirdPartyRootInputs, mThirdPartyIntermediateInputs, extraCertificates, builtChain, nullptr, nullptr); rv = BuildCertChain( trustDomain, certDER, time, EndEntityOrCA::MustBeEndEntity, KeyUsage::digitalSignature, KeyPurposeId::id_kp_clientAuth, CertPolicyId::anyPolicy, stapledOCSPResponse); if (madeOCSPRequests) { *madeOCSPRequests |= trustDomain.GetOCSPFetchStatus() == OCSPFetchStatus::Fetched; } break; } case certificateUsageSSLServer: { // TODO: When verifying a certificate in an SSL handshake, we should // restrict the acceptable key usage based on the key exchange method // chosen by the server. // Try to validate for EV first. NSSCertDBTrustDomain::OCSPFetching evOCSPFetching = (mOCSPDownloadConfig == ocspOff) || (flags & FLAG_LOCAL_ONLY) ? NSSCertDBTrustDomain::LocalOnlyOCSPForEV : NSSCertDBTrustDomain::FetchOCSPForEV; nsTArray evPolicies; GetKnownEVPolicies(certBytes, evPolicies); rv = Result::ERROR_UNKNOWN_ERROR; for (const auto& evPolicy : evPolicies) { NSSCertDBTrustDomain trustDomain( trustSSL, evOCSPFetching, mOCSPCache, pinArg, mOCSPTimeoutSoft, mOCSPTimeoutHard, mCertShortLifetimeInDays, MIN_RSA_BITS, ValidityCheckingMode::CheckForEV, mNetscapeStepUpPolicy, mCRLiteMode, originAttributes, mThirdPartyRootInputs, mThirdPartyIntermediateInputs, extraCertificates, builtChain, pinningTelemetryInfo, hostname); rv = BuildCertChainForOneKeyUsage( trustDomain, certDER, time, KeyUsage::digitalSignature, // (EC)DHE KeyUsage::keyEncipherment, // RSA KeyUsage::keyAgreement, // (EC)DH KeyPurposeId::id_kp_serverAuth, evPolicy, stapledOCSPResponse, ocspStaplingStatus); if (madeOCSPRequests) { *madeOCSPRequests |= trustDomain.GetOCSPFetchStatus() == OCSPFetchStatus::Fetched; } if (rv == Success) { rv = VerifyCertificateTransparencyPolicy( trustDomain, builtChain, sctsFromTLSInput, time, ctInfo); } if (rv == Success) { if (evStatus) { *evStatus = EVStatus::EV; } if (isBuiltChainRootBuiltInRoot) { *isBuiltChainRootBuiltInRoot = trustDomain.GetIsBuiltChainRootBuiltInRoot(); } break; } } if (rv == Success) { break; } if (flags & FLAG_MUST_BE_EV) { rv = Result::ERROR_POLICY_VALIDATION_FAILED; break; } // Now try non-EV. unsigned int keySizeOptions[] = {MIN_RSA_BITS, MIN_RSA_BITS_WEAK}; KeySizeStatus keySizeStatuses[] = {KeySizeStatus::LargeMinimumSucceeded, KeySizeStatus::CompatibilityRisk}; static_assert( MOZ_ARRAY_LENGTH(keySizeOptions) == MOZ_ARRAY_LENGTH(keySizeStatuses), "keySize array lengths differ"); size_t keySizeOptionsCount = MOZ_ARRAY_LENGTH(keySizeStatuses); for (size_t i = 0; i < keySizeOptionsCount && rv != Success; i++) { // invalidate any telemetry info relating to failed chains if (pinningTelemetryInfo) { pinningTelemetryInfo->Reset(); } NSSCertDBTrustDomain trustDomain( trustSSL, defaultOCSPFetching, mOCSPCache, pinArg, mOCSPTimeoutSoft, mOCSPTimeoutHard, mCertShortLifetimeInDays, keySizeOptions[i], ValidityCheckingMode::CheckingOff, mNetscapeStepUpPolicy, mCRLiteMode, originAttributes, mThirdPartyRootInputs, mThirdPartyIntermediateInputs, extraCertificates, builtChain, pinningTelemetryInfo, hostname); rv = BuildCertChainForOneKeyUsage( trustDomain, certDER, time, KeyUsage::digitalSignature, //(EC)DHE KeyUsage::keyEncipherment, // RSA KeyUsage::keyAgreement, //(EC)DH KeyPurposeId::id_kp_serverAuth, CertPolicyId::anyPolicy, stapledOCSPResponse, ocspStaplingStatus); if (madeOCSPRequests) { *madeOCSPRequests |= trustDomain.GetOCSPFetchStatus() == OCSPFetchStatus::Fetched; } if (rv != Success && !IsFatalError(rv) && rv != Result::ERROR_REVOKED_CERTIFICATE && trustDomain.GetIsErrorDueToDistrustedCAPolicy()) { // Bug 1444440 - If there are multiple paths, at least one to a CA // distrusted-by-policy, and none of them ending in a trusted root, // then we might show a different error (UNKNOWN_ISSUER) than we // intend, confusing users. rv = Result::ERROR_ADDITIONAL_POLICY_CONSTRAINT_FAILED; } if (rv == Success) { rv = VerifyCertificateTransparencyPolicy( trustDomain, builtChain, sctsFromTLSInput, time, ctInfo); } if (rv == Success) { if (keySizeStatus) { *keySizeStatus = keySizeStatuses[i]; } if (isBuiltChainRootBuiltInRoot) { *isBuiltChainRootBuiltInRoot = trustDomain.GetIsBuiltChainRootBuiltInRoot(); } break; } } if (rv != Success && keySizeStatus) { *keySizeStatus = KeySizeStatus::AlreadyBad; } break; } case certificateUsageSSLCA: { NSSCertDBTrustDomain trustDomain( trustSSL, defaultOCSPFetching, mOCSPCache, pinArg, mOCSPTimeoutSoft, mOCSPTimeoutHard, mCertShortLifetimeInDays, MIN_RSA_BITS_WEAK, ValidityCheckingMode::CheckingOff, mNetscapeStepUpPolicy, mCRLiteMode, originAttributes, mThirdPartyRootInputs, mThirdPartyIntermediateInputs, extraCertificates, builtChain, nullptr, nullptr); rv = BuildCertChain(trustDomain, certDER, time, EndEntityOrCA::MustBeCA, KeyUsage::keyCertSign, KeyPurposeId::id_kp_serverAuth, CertPolicyId::anyPolicy, stapledOCSPResponse); if (madeOCSPRequests) { *madeOCSPRequests |= trustDomain.GetOCSPFetchStatus() == OCSPFetchStatus::Fetched; } break; } case certificateUsageEmailSigner: { NSSCertDBTrustDomain trustDomain( trustEmail, defaultOCSPFetching, mOCSPCache, pinArg, mOCSPTimeoutSoft, mOCSPTimeoutHard, mCertShortLifetimeInDays, MIN_RSA_BITS_WEAK, ValidityCheckingMode::CheckingOff, NetscapeStepUpPolicy::NeverMatch, mCRLiteMode, originAttributes, mThirdPartyRootInputs, mThirdPartyIntermediateInputs, extraCertificates, builtChain, nullptr, nullptr); rv = BuildCertChain( trustDomain, certDER, time, EndEntityOrCA::MustBeEndEntity, KeyUsage::digitalSignature, KeyPurposeId::id_kp_emailProtection, CertPolicyId::anyPolicy, stapledOCSPResponse); if (rv == Result::ERROR_INADEQUATE_KEY_USAGE) { rv = BuildCertChain( trustDomain, certDER, time, EndEntityOrCA::MustBeEndEntity, KeyUsage::nonRepudiation, KeyPurposeId::id_kp_emailProtection, CertPolicyId::anyPolicy, stapledOCSPResponse); } if (madeOCSPRequests) { *madeOCSPRequests |= trustDomain.GetOCSPFetchStatus() == OCSPFetchStatus::Fetched; } break; } case certificateUsageEmailRecipient: { // TODO: The higher level S/MIME processing should pass in which key // usage it is trying to verify for, and base its algorithm choices // based on the result of the verification(s). NSSCertDBTrustDomain trustDomain( trustEmail, defaultOCSPFetching, mOCSPCache, pinArg, mOCSPTimeoutSoft, mOCSPTimeoutHard, mCertShortLifetimeInDays, MIN_RSA_BITS_WEAK, ValidityCheckingMode::CheckingOff, NetscapeStepUpPolicy::NeverMatch, mCRLiteMode, originAttributes, mThirdPartyRootInputs, mThirdPartyIntermediateInputs, extraCertificates, builtChain, nullptr, nullptr); rv = BuildCertChain(trustDomain, certDER, time, EndEntityOrCA::MustBeEndEntity, KeyUsage::keyEncipherment, // RSA KeyPurposeId::id_kp_emailProtection, CertPolicyId::anyPolicy, stapledOCSPResponse); if (rv == Result::ERROR_INADEQUATE_KEY_USAGE) { rv = BuildCertChain(trustDomain, certDER, time, EndEntityOrCA::MustBeEndEntity, KeyUsage::keyAgreement, // ECDH/DH KeyPurposeId::id_kp_emailProtection, CertPolicyId::anyPolicy, stapledOCSPResponse); } if (madeOCSPRequests) { *madeOCSPRequests |= trustDomain.GetOCSPFetchStatus() == OCSPFetchStatus::Fetched; } break; } default: rv = Result::FATAL_ERROR_INVALID_ARGS; } if (rv != Success) { return rv; } return Success; } static bool CertIsSelfSigned(const BackCert& backCert, void* pinarg) { if (!InputsAreEqual(backCert.GetIssuer(), backCert.GetSubject())) { return false; } nsTArray> emptyCertList; // AppTrustDomain is only used for its signature verification callbacks // (AppTrustDomain::Verify{ECDSA,RSAPKCS1,RSAPSS}SignedData). mozilla::psm::AppTrustDomain trustDomain(std::move(emptyCertList)); Result rv = VerifySignedData(trustDomain, backCert.GetSignedData(), backCert.GetSubjectPublicKeyInfo()); return rv == Success; } static Result CheckCertHostnameHelper(Input peerCertInput, const nsACString& hostname) { Input hostnameInput; Result rv = hostnameInput.Init( BitwiseCast(hostname.BeginReading()), hostname.Length()); if (rv != Success) { return Result::FATAL_ERROR_INVALID_ARGS; } rv = CheckCertHostname(peerCertInput, hostnameInput); // Treat malformed name information as a domain mismatch. if (rv == Result::ERROR_BAD_DER) { return Result::ERROR_BAD_CERT_DOMAIN; } return rv; } Result CertVerifier::VerifySSLServerCert( const nsTArray& peerCertBytes, Time time, /*optional*/ void* pinarg, const nsACString& hostname, /*out*/ nsTArray>& builtChain, /*optional*/ Flags flags, /*optional*/ const Maybe>>& extraCertificates, /*optional*/ const Maybe>& stapledOCSPResponse, /*optional*/ const Maybe>& sctsFromTLS, /*optional*/ const Maybe& dcInfo, /*optional*/ const OriginAttributes& originAttributes, /*optional out*/ EVStatus* evStatus, /*optional out*/ OCSPStaplingStatus* ocspStaplingStatus, /*optional out*/ KeySizeStatus* keySizeStatus, /*optional out*/ PinningTelemetryInfo* pinningTelemetryInfo, /*optional out*/ CertificateTransparencyInfo* ctInfo, /*optional out*/ bool* isBuiltChainRootBuiltInRoot, /*optional out*/ bool* madeOCSPRequests) { // XXX: MOZ_ASSERT(pinarg); MOZ_ASSERT(!hostname.IsEmpty()); if (isBuiltChainRootBuiltInRoot) { *isBuiltChainRootBuiltInRoot = false; } if (evStatus) { *evStatus = EVStatus::NotEV; } if (hostname.IsEmpty()) { return Result::ERROR_BAD_CERT_DOMAIN; } // CreateCertErrorRunnable assumes that CheckCertHostname is only called // if VerifyCert succeeded. Input peerCertInput; Result rv = peerCertInput.Init(peerCertBytes.Elements(), peerCertBytes.Length()); if (rv != Success) { return rv; } bool isBuiltChainRootBuiltInRootLocal; rv = VerifyCert(peerCertBytes, certificateUsageSSLServer, time, pinarg, PromiseFlatCString(hostname).get(), builtChain, flags, extraCertificates, stapledOCSPResponse, sctsFromTLS, originAttributes, evStatus, ocspStaplingStatus, keySizeStatus, pinningTelemetryInfo, ctInfo, &isBuiltChainRootBuiltInRootLocal, madeOCSPRequests); if (rv != Success) { // we don't use the certificate for path building, so this parameter doesn't // matter EndEntityOrCA notUsedForPaths = EndEntityOrCA::MustBeEndEntity; BackCert peerBackCert(peerCertInput, notUsedForPaths, nullptr); if (peerBackCert.Init() != Success) { return rv; } if ((rv == Result::ERROR_UNKNOWN_ISSUER || rv == Result::ERROR_BAD_SIGNATURE || rv == Result::ERROR_INADEQUATE_KEY_USAGE) && CertIsSelfSigned(peerBackCert, pinarg)) { // In this case we didn't find any issuer for the certificate, or we did // find other certificates with the same subject but different keys, and // the certificate is self-signed. return Result::ERROR_SELF_SIGNED_CERT; } if (rv == Result::ERROR_UNKNOWN_ISSUER) { // In this case we didn't get any valid path for the cert. Let's see if // the issuer is the same as the issuer for our canary probe. If yes, this // connection is connecting via a misconfigured proxy. // Note: The MitM canary might not be set. In this case we consider this // an unknown issuer error. nsCOMPtr component( do_GetService(PSM_COMPONENT_CONTRACTID)); if (!component) { return Result::FATAL_ERROR_LIBRARY_FAILURE; } // IssuerMatchesMitmCanary succeeds if the issuer matches the canary and // the feature is enabled. Input issuerNameInput = peerBackCert.GetIssuer(); SECItem issuerNameItem = UnsafeMapInputToSECItem(issuerNameInput); UniquePORTString issuerName(CERT_DerNameToAscii(&issuerNameItem)); if (!issuerName) { return Result::ERROR_BAD_DER; } nsresult rv = component->IssuerMatchesMitmCanary(issuerName.get()); if (NS_SUCCEEDED(rv)) { return Result::ERROR_MITM_DETECTED; } } // If the certificate is expired or not yet valid, first check whether or // not it is valid for the indicated hostname, because that would be a more // serious error. if (rv == Result::ERROR_EXPIRED_CERTIFICATE || rv == Result::ERROR_NOT_YET_VALID_CERTIFICATE || rv == Result::ERROR_INVALID_DER_TIME) { Result hostnameResult = CheckCertHostnameHelper(peerCertInput, hostname); if (hostnameResult != Success) { return hostnameResult; } } return rv; } if (dcInfo) { rv = IsDelegatedCredentialAcceptable(*dcInfo); if (rv != Success) { return rv; } } Input stapledOCSPResponseInput; Input* responseInputPtr = nullptr; if (stapledOCSPResponse) { rv = stapledOCSPResponseInput.Init(stapledOCSPResponse->Elements(), stapledOCSPResponse->Length()); if (rv != Success) { // The stapled OCSP response was too big. return Result::ERROR_OCSP_MALFORMED_RESPONSE; } responseInputPtr = &stapledOCSPResponseInput; } if (!(flags & FLAG_TLS_IGNORE_STATUS_REQUEST)) { rv = CheckTLSFeaturesAreSatisfied(peerCertInput, responseInputPtr); if (rv != Success) { return rv; } } rv = CheckCertHostnameHelper(peerCertInput, hostname); if (rv != Success) { return rv; } if (isBuiltChainRootBuiltInRoot) { *isBuiltChainRootBuiltInRoot = isBuiltChainRootBuiltInRootLocal; } return Success; } } // namespace psm } // namespace mozilla