diff options
Diffstat (limited to 'security/certverifier/CertVerifier.cpp')
-rw-r--r-- | security/certverifier/CertVerifier.cpp | 903 |
1 files changed, 903 insertions, 0 deletions
diff --git a/security/certverifier/CertVerifier.cpp b/security/certverifier/CertVerifier.cpp new file mode 100644 index 000000000..61d8fcdb8 --- /dev/null +++ b/security/certverifier/CertVerifier.cpp @@ -0,0 +1,903 @@ +/* -*- 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 <stdint.h> + +#include "CTKnownLogs.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 "nsNSSComponent.h" +#include "nsServiceManagerUtils.h" +#include "pk11pub.h" +#include "pkix/pkix.h" +#include "pkix/pkixnss.h" +#include "prerror.h" +#include "secerr.h" +#include "secmod.h" +#include "sslerr.h" + +using namespace mozilla::ct; +using namespace mozilla::pkix; +using namespace mozilla::psm; + +mozilla::LazyLogModule gCertVerifierLog("certverifier"); + +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; + +CertVerifier::CertVerifier(OcspDownloadConfig odc, + OcspStrictConfig osc, + OcspGetConfig ogc, + uint32_t certShortLifetimeInDays, + PinningMode pinningMode, + SHA1Mode sha1Mode, + BRNameMatchingPolicy::Mode nameMatchingMode, + NetscapeStepUpPolicy netscapeStepUpPolicy, + CertificateTransparencyMode ctMode) + : mOCSPDownloadConfig(odc) + , mOCSPStrict(osc == ocspStrict) + , mOCSPGETEnabled(ogc == ocspGetEnabled) + , mCertShortLifetimeInDays(certShortLifetimeInDays) + , mPinningMode(pinningMode) + , mSHA1Mode(sha1Mode) + , mNameMatchingMode(nameMatchingMode) + , mNetscapeStepUpPolicy(netscapeStepUpPolicy) + , mCTMode(ctMode) +{ + LoadKnownCTLogs(); +} + +CertVerifier::~CertVerifier() +{ +} + +Result +IsCertChainRootBuiltInRoot(const UniqueCERTCertList& chain, bool& result) +{ + if (!chain || CERT_LIST_EMPTY(chain)) { + return Result::FATAL_ERROR_LIBRARY_FAILURE; + } + CERTCertListNode* rootNode = CERT_LIST_TAIL(chain); + if (!rootNode) { + return Result::FATAL_ERROR_LIBRARY_FAILURE; + } + CERTCertificate* root = rootNode->cert; + if (!root) { + return Result::FATAL_ERROR_LIBRARY_FAILURE; + } + return IsCertBuiltInRoot(root, result); +} + +Result +IsCertBuiltInRoot(CERTCertificate* cert, bool& result) +{ + result = false; +#ifdef DEBUG + nsCOMPtr<nsINSSComponent> component(do_GetService(PSM_COMPONENT_CONTRACTID)); + if (!component) { + return Result::FATAL_ERROR_LIBRARY_FAILURE; + } + nsresult rv = component->IsCertTestBuiltInRoot(cert, result); + if (NS_FAILED(rv)) { + return Result::FATAL_ERROR_LIBRARY_FAILURE; + } + if (result) { + return Success; + } +#endif // DEBUG + 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]; + // PK11_HasRootCerts should return true if and only if the given slot has + // an object with a CKA_CLASS of CKO_NETSCAPE_BUILTIN_ROOT_LIST, which + // should be true only of the builtin root list. + // If we can find a copy of the given certificate on the slot with the + // builtin root list, that certificate must be a builtin. + if (PK11_IsPresent(slot) && PK11_HasRootCerts(slot) && + PK11_FindCertInSlot(slot, cert, nullptr) != CK_INVALID_HANDLE) { + result = true; + return Success; + } + } + } + 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() +{ + mCTVerifier = MakeUnique<MultiLogCTVerifier>(); + for (const CTLogInfo& log : kCTLogList) { + Input publicKey; + Result rv = publicKey.Init( + BitwiseCast<const uint8_t*, const char*>(log.logKey), log.logKeyLength); + if (rv != Success) { + MOZ_ASSERT_UNREACHABLE("Failed reading a log key for a known CT Log"); + continue; + } + rv = mCTVerifier->AddLog(publicKey); + if (rv != Success) { + MOZ_ASSERT_UNREACHABLE("Failed initializing a known CT Log"); + continue; + } + } +} + +Result +CertVerifier::VerifySignedCertificateTimestamps( + NSSCertDBTrustDomain& trustDomain, const UniqueCERTCertList& 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 || CERT_LIST_EMPTY(builtChain)) { + return Result::FATAL_ERROR_INVALID_ARGS; + } + + bool gotScts = false; + Input embeddedSCTs = trustDomain.GetSCTListFromCertificate(); + if (embeddedSCTs.GetLength() > 0) { + gotScts = true; + MOZ_LOG(gCertVerifierLog, LogLevel::Debug, + ("Got embedded SCT data of length %zu\n", + static_cast<size_t>(embeddedSCTs.GetLength()))); + } + Input sctsFromOCSP = trustDomain.GetSCTListFromOCSPStapling(); + if (sctsFromOCSP.GetLength() > 0) { + gotScts = true; + MOZ_LOG(gCertVerifierLog, LogLevel::Debug, + ("Got OCSP SCT data of length %zu\n", + static_cast<size_t>(sctsFromOCSP.GetLength()))); + } + if (sctsFromTLS.GetLength() > 0) { + gotScts = true; + MOZ_LOG(gCertVerifierLog, LogLevel::Debug, + ("Got TLS SCT data of length %zu\n", + static_cast<size_t>(sctsFromTLS.GetLength()))); + } + if (!gotScts) { + return Success; + } + + CERTCertListNode* endEntityNode = CERT_LIST_HEAD(builtChain); + if (!endEntityNode || CERT_LIST_END(endEntityNode, builtChain)) { + return Result::FATAL_ERROR_INVALID_ARGS; + } + CERTCertListNode* issuerNode = CERT_LIST_NEXT(endEntityNode); + if (!issuerNode || CERT_LIST_END(issuerNode, builtChain)) { + // Issuer certificate is required for SCT verification. + // TODO(bug 1294580): change this to Result::FATAL_ERROR_INVALID_ARGS + return Success; + } + + CERTCertificate* endEntity = endEntityNode->cert; + CERTCertificate* issuer = issuerNode->cert; + if (!endEntity || !issuer) { + return Result::FATAL_ERROR_INVALID_ARGS; + } + + Input endEntityDER; + Result rv = endEntityDER.Init(endEntity->derCert.data, + endEntity->derCert.len); + if (rv != Success) { + return rv; + } + + Input issuerPublicKeyDER; + rv = issuerPublicKeyDER.Init(issuer->derPublicKey.data, + issuer->derPublicKey.len); + if (rv != Success) { + return rv; + } + + CTVerifyResult result; + rv = mCTVerifier->Verify(endEntityDER, issuerPublicKeyDER, + embeddedSCTs, sctsFromOCSP, sctsFromTLS, time, + result); + if (rv != Success) { + MOZ_LOG(gCertVerifierLog, LogLevel::Debug, + ("SCT verification failed with fatal error %i\n", rv)); + return rv; + } + + if (MOZ_LOG_TEST(gCertVerifierLog, LogLevel::Debug)) { + size_t verifiedCount = 0; + size_t unknownLogCount = 0; + size_t invalidSignatureCount = 0; + size_t invalidTimestampCount = 0; + for (const SignedCertificateTimestamp& sct : result.scts) { + switch (sct.verificationStatus) { + case SignedCertificateTimestamp::VerificationStatus::OK: + verifiedCount++; + break; + case SignedCertificateTimestamp::VerificationStatus::UnknownLog: + unknownLogCount++; + break; + case SignedCertificateTimestamp::VerificationStatus::InvalidSignature: + invalidSignatureCount++; + break; + case SignedCertificateTimestamp::VerificationStatus::InvalidTimestamp: + invalidTimestampCount++; + break; + case SignedCertificateTimestamp::VerificationStatus::None: + default: + MOZ_ASSERT_UNREACHABLE("Unexpected SCT verificationStatus"); + } + } + MOZ_LOG(gCertVerifierLog, LogLevel::Debug, + ("SCT verification result: " + "verified=%zu unknownLog=%zu " + "invalidSignature=%zu invalidTimestamp=%zu " + "decodingErrors=%zu\n", + verifiedCount, unknownLogCount, + invalidSignatureCount, invalidTimestampCount, + result.decodingErrors)); + } + + if (ctInfo) { + ctInfo->processedSCTs = true; + ctInfo->verifyResult = Move(result); + } + return Success; +} + +bool +CertVerifier::SHA1ModeMoreRestrictiveThanGivenMode(SHA1Mode mode) +{ + switch (mSHA1Mode) { + case SHA1Mode::Forbidden: + return mode != SHA1Mode::Forbidden; + case SHA1Mode::ImportedRoot: + return mode != SHA1Mode::Forbidden && mode != SHA1Mode::ImportedRoot; + case SHA1Mode::ImportedRootOrBefore2016: + return mode == SHA1Mode::Allowed; + case SHA1Mode::Allowed: + return false; + // MSVC warns unless we explicitly handle this now-unused option. + case SHA1Mode::UsedToBeBefore2016ButNowIsForbidden: + default: + MOZ_ASSERT(false, "unexpected SHA1Mode type"); + return true; + } +} + +static const unsigned int MIN_RSA_BITS = 2048; +static const unsigned int MIN_RSA_BITS_WEAK = 1024; + +Result +CertVerifier::VerifyCert(CERTCertificate* cert, SECCertificateUsage usage, + Time time, void* pinArg, const char* hostname, + /*out*/ UniqueCERTCertList& builtChain, + /*optional*/ const Flags flags, + /*optional*/ const SECItem* stapledOCSPResponseSECItem, + /*optional*/ const SECItem* sctsFromTLSSECItem, + /*optional*/ const NeckoOriginAttributes& originAttributes, + /*optional out*/ SECOidTag* evOidPolicy, + /*optional out*/ OCSPStaplingStatus* ocspStaplingStatus, + /*optional out*/ KeySizeStatus* keySizeStatus, + /*optional out*/ SHA1ModeResult* sha1ModeResult, + /*optional out*/ PinningTelemetryInfo* pinningTelemetryInfo, + /*optional out*/ CertificateTransparencyInfo* ctInfo) +{ + MOZ_LOG(gCertVerifierLog, LogLevel::Debug, ("Top of VerifyCert\n")); + + PR_ASSERT(cert); + PR_ASSERT(usage == certificateUsageSSLServer || !(flags & FLAG_MUST_BE_EV)); + PR_ASSERT(usage == certificateUsageSSLServer || !keySizeStatus); + PR_ASSERT(usage == certificateUsageSSLServer || !sha1ModeResult); + + if (evOidPolicy) { + *evOidPolicy = SEC_OID_UNKNOWN; + } + 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 (sha1ModeResult) { + if (usage != certificateUsageSSLServer) { + return Result::FATAL_ERROR_INVALID_ARGS; + } + *sha1ModeResult = SHA1ModeResult::NeverChecked; + } + + if (!cert || + (usage != certificateUsageSSLServer && (flags & FLAG_MUST_BE_EV))) { + return Result::FATAL_ERROR_INVALID_ARGS; + } + + Input certDER; + Result rv = certDER.Init(cert->derCert.data, cert->derCert.len); + 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; + + OcspGetConfig ocspGETConfig = mOCSPGETEnabled ? ocspGetEnabled + : ocspGetDisabled; + + Input stapledOCSPResponseInput; + const Input* stapledOCSPResponse = nullptr; + if (stapledOCSPResponseSECItem) { + rv = stapledOCSPResponseInput.Init(stapledOCSPResponseSECItem->data, + stapledOCSPResponseSECItem->len); + if (rv != Success) { + // The stapled OCSP response was too big. + return Result::ERROR_OCSP_MALFORMED_RESPONSE; + } + stapledOCSPResponse = &stapledOCSPResponseInput; + } + + Input sctsFromTLSInput; + if (sctsFromTLSSECItem) { + rv = sctsFromTLSInput.Init(sctsFromTLSSECItem->data, + sctsFromTLSSECItem->len); + // Silently discard the error of the extension being too big, + // do not fail the verification. + MOZ_ASSERT(rv == Success); + } + + 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, ocspGETConfig, + mCertShortLifetimeInDays, + pinningDisabled, MIN_RSA_BITS_WEAK, + ValidityCheckingMode::CheckingOff, + SHA1Mode::Allowed, + NetscapeStepUpPolicy::NeverMatch, + originAttributes, + builtChain, nullptr, nullptr); + rv = BuildCertChain(trustDomain, certDER, time, + EndEntityOrCA::MustBeEndEntity, + KeyUsage::digitalSignature, + KeyPurposeId::id_kp_clientAuth, + CertPolicyId::anyPolicy, stapledOCSPResponse); + 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. + + // These configurations are in order of most restrictive to least + // restrictive. This enables us to gather telemetry on the expected + // results of setting the default policy to a particular configuration. + SHA1Mode sha1ModeConfigurations[] = { + SHA1Mode::Forbidden, + SHA1Mode::ImportedRoot, + SHA1Mode::ImportedRootOrBefore2016, + SHA1Mode::Allowed, + }; + + SHA1ModeResult sha1ModeResults[] = { + SHA1ModeResult::SucceededWithoutSHA1, + SHA1ModeResult::SucceededWithImportedRoot, + SHA1ModeResult::SucceededWithImportedRootOrSHA1Before2016, + SHA1ModeResult::SucceededWithSHA1, + }; + + size_t sha1ModeConfigurationsCount = MOZ_ARRAY_LENGTH(sha1ModeConfigurations); + + static_assert(MOZ_ARRAY_LENGTH(sha1ModeConfigurations) == + MOZ_ARRAY_LENGTH(sha1ModeResults), + "digestAlgorithm array lengths differ"); + + rv = Result::ERROR_UNKNOWN_ERROR; + + // Try to validate for EV first. + NSSCertDBTrustDomain::OCSPFetching evOCSPFetching + = (mOCSPDownloadConfig == ocspOff) || + (flags & FLAG_LOCAL_ONLY) ? NSSCertDBTrustDomain::LocalOnlyOCSPForEV + : NSSCertDBTrustDomain::FetchOCSPForEV; + + CertPolicyId evPolicy; + SECOidTag evPolicyOidTag; + SECStatus srv = GetFirstEVPolicy(cert, evPolicy, evPolicyOidTag); + for (size_t i = 0; + i < sha1ModeConfigurationsCount && rv != Success && srv == SECSuccess; + i++) { + // Don't attempt verification if the SHA1 mode set by preferences + // (mSHA1Mode) is more restrictive than the SHA1 mode option we're on. + // (To put it another way, only attempt verification if the SHA1 mode + // option we're on is as restrictive or more restrictive than + // mSHA1Mode.) This allows us to gather telemetry information while + // still enforcing the mode set by preferences. + if (SHA1ModeMoreRestrictiveThanGivenMode(sha1ModeConfigurations[i])) { + continue; + } + + // Because of the try-strict and fallback approach, we have to clear any + // previously noted telemetry information + if (pinningTelemetryInfo) { + pinningTelemetryInfo->Reset(); + } + + NSSCertDBTrustDomain + trustDomain(trustSSL, evOCSPFetching, + mOCSPCache, pinArg, ocspGETConfig, + mCertShortLifetimeInDays, mPinningMode, MIN_RSA_BITS, + ValidityCheckingMode::CheckForEV, + sha1ModeConfigurations[i], mNetscapeStepUpPolicy, + originAttributes, 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 (rv == Success && + sha1ModeConfigurations[i] == SHA1Mode::ImportedRoot) { + bool isBuiltInRoot = false; + rv = IsCertChainRootBuiltInRoot(builtChain, isBuiltInRoot); + if (rv != Success) { + break; + } + if (isBuiltInRoot) { + rv = Result::ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED; + } + } + if (rv == Success) { + MOZ_LOG(gCertVerifierLog, LogLevel::Debug, + ("cert is EV with status %i\n", sha1ModeResults[i])); + if (evOidPolicy) { + *evOidPolicy = evPolicyOidTag; + } + if (sha1ModeResult) { + *sha1ModeResult = sha1ModeResults[i]; + } + rv = VerifySignedCertificateTimestamps(trustDomain, builtChain, + sctsFromTLSInput, time, + ctInfo); + if (rv != Success) { + 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++) { + for (size_t j = 0; j < sha1ModeConfigurationsCount && rv != Success; + j++) { + // Don't attempt verification if the SHA1 mode set by preferences + // (mSHA1Mode) is more restrictive than the SHA1 mode option we're on. + // (To put it another way, only attempt verification if the SHA1 mode + // option we're on is as restrictive or more restrictive than + // mSHA1Mode.) This allows us to gather telemetry information while + // still enforcing the mode set by preferences. + if (SHA1ModeMoreRestrictiveThanGivenMode(sha1ModeConfigurations[j])) { + continue; + } + + // invalidate any telemetry info relating to failed chains + if (pinningTelemetryInfo) { + pinningTelemetryInfo->Reset(); + } + + NSSCertDBTrustDomain trustDomain(trustSSL, defaultOCSPFetching, + mOCSPCache, pinArg, ocspGETConfig, + mCertShortLifetimeInDays, + mPinningMode, keySizeOptions[i], + ValidityCheckingMode::CheckingOff, + sha1ModeConfigurations[j], + mNetscapeStepUpPolicy, + originAttributes, 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 (rv == Success && + sha1ModeConfigurations[j] == SHA1Mode::ImportedRoot) { + bool isBuiltInRoot = false; + rv = IsCertChainRootBuiltInRoot(builtChain, isBuiltInRoot); + if (rv != Success) { + break; + } + if (isBuiltInRoot) { + rv = Result::ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED; + } + } + if (rv == Success) { + if (keySizeStatus) { + *keySizeStatus = keySizeStatuses[i]; + } + if (sha1ModeResult) { + *sha1ModeResult = sha1ModeResults[j]; + } + rv = VerifySignedCertificateTimestamps(trustDomain, builtChain, + sctsFromTLSInput, time, + ctInfo); + if (rv != Success) { + break; + } + } + } + } + + if (rv == Success) { + break; + } + + if (keySizeStatus) { + *keySizeStatus = KeySizeStatus::AlreadyBad; + } + // The telemetry probe CERT_CHAIN_SHA1_POLICY_STATUS gives us feedback on + // the result of setting a specific policy. However, we don't want noise + // from users who have manually set the policy to something other than the + // default, so we only collect for ImportedRoot (which is the default). + if (sha1ModeResult && mSHA1Mode == SHA1Mode::ImportedRoot) { + *sha1ModeResult = SHA1ModeResult::Failed; + } + + break; + } + + case certificateUsageSSLCA: { + NSSCertDBTrustDomain trustDomain(trustSSL, defaultOCSPFetching, + mOCSPCache, pinArg, ocspGETConfig, + mCertShortLifetimeInDays, + pinningDisabled, MIN_RSA_BITS_WEAK, + ValidityCheckingMode::CheckingOff, + SHA1Mode::Allowed, mNetscapeStepUpPolicy, + originAttributes, builtChain, nullptr, + nullptr); + rv = BuildCertChain(trustDomain, certDER, time, + EndEntityOrCA::MustBeCA, KeyUsage::keyCertSign, + KeyPurposeId::id_kp_serverAuth, + CertPolicyId::anyPolicy, stapledOCSPResponse); + break; + } + + case certificateUsageEmailSigner: { + NSSCertDBTrustDomain trustDomain(trustEmail, defaultOCSPFetching, + mOCSPCache, pinArg, ocspGETConfig, + mCertShortLifetimeInDays, + pinningDisabled, MIN_RSA_BITS_WEAK, + ValidityCheckingMode::CheckingOff, + SHA1Mode::Allowed, + NetscapeStepUpPolicy::NeverMatch, + originAttributes, 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); + } + 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, ocspGETConfig, + mCertShortLifetimeInDays, + pinningDisabled, MIN_RSA_BITS_WEAK, + ValidityCheckingMode::CheckingOff, + SHA1Mode::Allowed, + NetscapeStepUpPolicy::NeverMatch, + originAttributes, 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); + } + break; + } + + case certificateUsageObjectSigner: { + NSSCertDBTrustDomain trustDomain(trustObjectSigning, defaultOCSPFetching, + mOCSPCache, pinArg, ocspGETConfig, + mCertShortLifetimeInDays, + pinningDisabled, MIN_RSA_BITS_WEAK, + ValidityCheckingMode::CheckingOff, + SHA1Mode::Allowed, + NetscapeStepUpPolicy::NeverMatch, + originAttributes, builtChain, nullptr, + nullptr); + rv = BuildCertChain(trustDomain, certDER, time, + EndEntityOrCA::MustBeEndEntity, + KeyUsage::digitalSignature, + KeyPurposeId::id_kp_codeSigning, + CertPolicyId::anyPolicy, stapledOCSPResponse); + break; + } + + case certificateUsageVerifyCA: + case certificateUsageStatusResponder: { + // XXX This is a pretty useless way to verify a certificate. It is used + // by the certificate viewer UI. Because we don't know what trust bit is + // interesting, we just try them all. + mozilla::pkix::EndEntityOrCA endEntityOrCA; + mozilla::pkix::KeyUsage keyUsage; + KeyPurposeId eku; + if (usage == certificateUsageVerifyCA) { + endEntityOrCA = EndEntityOrCA::MustBeCA; + keyUsage = KeyUsage::keyCertSign; + eku = KeyPurposeId::anyExtendedKeyUsage; + } else { + endEntityOrCA = EndEntityOrCA::MustBeEndEntity; + keyUsage = KeyUsage::digitalSignature; + eku = KeyPurposeId::id_kp_OCSPSigning; + } + + NSSCertDBTrustDomain sslTrust(trustSSL, defaultOCSPFetching, mOCSPCache, + pinArg, ocspGETConfig, mCertShortLifetimeInDays, + pinningDisabled, MIN_RSA_BITS_WEAK, + ValidityCheckingMode::CheckingOff, + SHA1Mode::Allowed, + NetscapeStepUpPolicy::NeverMatch, + originAttributes, builtChain, nullptr, + nullptr); + rv = BuildCertChain(sslTrust, certDER, time, endEntityOrCA, + keyUsage, eku, CertPolicyId::anyPolicy, + stapledOCSPResponse); + if (rv == Result::ERROR_UNKNOWN_ISSUER) { + NSSCertDBTrustDomain emailTrust(trustEmail, defaultOCSPFetching, + mOCSPCache, pinArg, ocspGETConfig, + mCertShortLifetimeInDays, + pinningDisabled, MIN_RSA_BITS_WEAK, + ValidityCheckingMode::CheckingOff, + SHA1Mode::Allowed, + NetscapeStepUpPolicy::NeverMatch, + originAttributes, builtChain, nullptr, + nullptr); + rv = BuildCertChain(emailTrust, certDER, time, endEntityOrCA, + keyUsage, eku, CertPolicyId::anyPolicy, + stapledOCSPResponse); + if (rv == Result::ERROR_UNKNOWN_ISSUER) { + NSSCertDBTrustDomain objectSigningTrust(trustObjectSigning, + defaultOCSPFetching, mOCSPCache, + pinArg, ocspGETConfig, + mCertShortLifetimeInDays, + pinningDisabled, + MIN_RSA_BITS_WEAK, + ValidityCheckingMode::CheckingOff, + SHA1Mode::Allowed, + NetscapeStepUpPolicy::NeverMatch, + originAttributes, builtChain, + nullptr, nullptr); + rv = BuildCertChain(objectSigningTrust, certDER, time, + endEntityOrCA, keyUsage, eku, + CertPolicyId::anyPolicy, stapledOCSPResponse); + } + } + + break; + } + + default: + rv = Result::FATAL_ERROR_INVALID_ARGS; + } + + if (rv != Success) { + return rv; + } + + return Success; +} + +Result +CertVerifier::VerifySSLServerCert(const UniqueCERTCertificate& peerCert, + /*optional*/ const SECItem* stapledOCSPResponse, + /*optional*/ const SECItem* sctsFromTLS, + Time time, + /*optional*/ void* pinarg, + const char* hostname, + /*out*/ UniqueCERTCertList& builtChain, + /*optional*/ bool saveIntermediatesInPermanentDatabase, + /*optional*/ Flags flags, + /*optional*/ const NeckoOriginAttributes& originAttributes, + /*optional out*/ SECOidTag* evOidPolicy, + /*optional out*/ OCSPStaplingStatus* ocspStaplingStatus, + /*optional out*/ KeySizeStatus* keySizeStatus, + /*optional out*/ SHA1ModeResult* sha1ModeResult, + /*optional out*/ PinningTelemetryInfo* pinningTelemetryInfo, + /*optional out*/ CertificateTransparencyInfo* ctInfo) +{ + PR_ASSERT(peerCert); + // XXX: PR_ASSERT(pinarg) + PR_ASSERT(hostname); + PR_ASSERT(hostname[0]); + + if (evOidPolicy) { + *evOidPolicy = SEC_OID_UNKNOWN; + } + + if (!hostname || !hostname[0]) { + return Result::ERROR_BAD_CERT_DOMAIN; + } + + // CreateCertErrorRunnable assumes that CheckCertHostname is only called + // if VerifyCert succeeded. + Result rv = VerifyCert(peerCert.get(), certificateUsageSSLServer, time, + pinarg, hostname, builtChain, flags, + stapledOCSPResponse, sctsFromTLS, originAttributes, + evOidPolicy, ocspStaplingStatus, keySizeStatus, + sha1ModeResult, pinningTelemetryInfo, ctInfo); + if (rv != Success) { + return rv; + } + + Input peerCertInput; + rv = peerCertInput.Init(peerCert->derCert.data, peerCert->derCert.len); + if (rv != Success) { + return rv; + } + + Input stapledOCSPResponseInput; + Input* responseInputPtr = nullptr; + if (stapledOCSPResponse) { + rv = stapledOCSPResponseInput.Init(stapledOCSPResponse->data, + stapledOCSPResponse->len); + 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; + } + } + + Input hostnameInput; + rv = hostnameInput.Init(BitwiseCast<const uint8_t*, const char*>(hostname), + strlen(hostname)); + if (rv != Success) { + return Result::FATAL_ERROR_INVALID_ARGS; + } + bool isBuiltInRoot; + rv = IsCertChainRootBuiltInRoot(builtChain, isBuiltInRoot); + if (rv != Success) { + return rv; + } + BRNameMatchingPolicy nameMatchingPolicy( + isBuiltInRoot ? mNameMatchingMode + : BRNameMatchingPolicy::Mode::DoNotEnforce); + rv = CheckCertHostname(peerCertInput, hostnameInput, nameMatchingPolicy); + if (rv != Success) { + // Treat malformed name information as a domain mismatch. + if (rv == Result::ERROR_BAD_DER) { + return Result::ERROR_BAD_CERT_DOMAIN; + } + + return rv; + } + + if (saveIntermediatesInPermanentDatabase) { + SaveIntermediateCerts(builtChain); + } + + return Success; +} + +} } // namespace mozilla::psm |