From 36d22d82aa202bb199967e9512281e9a53db42c9 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 21:33:14 +0200 Subject: Adding upstream version 115.7.0esr. Signed-off-by: Daniel Baumann --- security/certverifier/NSSCertDBTrustDomain.cpp | 2029 ++++++++++++++++++++++++ 1 file changed, 2029 insertions(+) create mode 100644 security/certverifier/NSSCertDBTrustDomain.cpp (limited to 'security/certverifier/NSSCertDBTrustDomain.cpp') diff --git a/security/certverifier/NSSCertDBTrustDomain.cpp b/security/certverifier/NSSCertDBTrustDomain.cpp new file mode 100644 index 0000000000..0c128eabf2 --- /dev/null +++ b/security/certverifier/NSSCertDBTrustDomain.cpp @@ -0,0 +1,2029 @@ +/* -*- 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 "NSSCertDBTrustDomain.h" + +#include +#include + +#include "CRLiteTimestamp.h" +#include "ExtendedValidation.h" +#include "MultiLogCTVerifier.h" +#include "NSSErrorsService.h" +#include "PublicKeyPinningService.h" +#include "cert.h" +#include "cert_storage/src/cert_storage.h" +#include "certdb.h" +#include "mozilla/AppShutdown.h" +#include "mozilla/Assertions.h" +#include "mozilla/Casting.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/Logging.h" +#include "mozilla/PodOperations.h" +#include "mozilla/Services.h" +#include "mozilla/StaticPrefs_security.h" +#include "mozilla/SyncRunnable.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/Unused.h" +#include "mozpkix/Result.h" +#include "mozpkix/pkix.h" +#include "mozpkix/pkixnss.h" +#include "mozpkix/pkixutil.h" +#include "nsCRTGlue.h" +#include "nsIObserverService.h" +#include "nsNetCID.h" +#include "nsNSSCallbacks.h" +#include "nsNSSCertHelper.h" +#include "nsNSSCertificate.h" +#include "nsNSSCertificateDB.h" +#include "nsNSSIOLayer.h" +#include "nsPrintfCString.h" +#include "nsServiceManagerUtils.h" +#include "nsThreadUtils.h" +#include "nss.h" +#include "pk11pub.h" +#include "prerror.h" +#include "secder.h" +#include "secerr.h" + +#ifdef MOZ_WIDGET_COCOA +# include "nsCocoaFeatures.h" +#endif + +#include "TrustOverrideUtils.h" +#include "TrustOverride-AppleGoogleDigiCertData.inc" +#include "TrustOverride-SymantecData.inc" + +using namespace mozilla; +using namespace mozilla::ct; +using namespace mozilla::pkix; + +extern LazyLogModule gCertVerifierLog; + +static const uint64_t ServerFailureDelaySeconds = 5 * 60; + +namespace mozilla { +namespace psm { + +NSSCertDBTrustDomain::NSSCertDBTrustDomain( + SECTrustType certDBTrustType, OCSPFetching ocspFetching, + OCSPCache& ocspCache, + /*optional but shouldn't be*/ void* pinArg, TimeDuration ocspTimeoutSoft, + TimeDuration ocspTimeoutHard, uint32_t certShortLifetimeInDays, + unsigned int minRSABits, ValidityCheckingMode validityCheckingMode, + NetscapeStepUpPolicy netscapeStepUpPolicy, CRLiteMode crliteMode, + const OriginAttributes& originAttributes, + const Vector& thirdPartyRootInputs, + const Vector& thirdPartyIntermediateInputs, + const Maybe>>& extraCertificates, + /*out*/ nsTArray>& builtChain, + /*optional*/ PinningTelemetryInfo* pinningTelemetryInfo, + /*optional*/ const char* hostname) + : mCertDBTrustType(certDBTrustType), + mOCSPFetching(ocspFetching), + mOCSPCache(ocspCache), + mPinArg(pinArg), + mOCSPTimeoutSoft(ocspTimeoutSoft), + mOCSPTimeoutHard(ocspTimeoutHard), + mCertShortLifetimeInDays(certShortLifetimeInDays), + mMinRSABits(minRSABits), + mValidityCheckingMode(validityCheckingMode), + mNetscapeStepUpPolicy(netscapeStepUpPolicy), + mCRLiteMode(crliteMode), + mSawDistrustedCAByPolicyError(false), + mOriginAttributes(originAttributes), + mThirdPartyRootInputs(thirdPartyRootInputs), + mThirdPartyIntermediateInputs(thirdPartyIntermediateInputs), + mExtraCertificates(extraCertificates), + mBuiltChain(builtChain), + mIsBuiltChainRootBuiltInRoot(false), + mPinningTelemetryInfo(pinningTelemetryInfo), + mHostname(hostname), + mCertStorage(do_GetService(NS_CERT_STORAGE_CID)), + mOCSPStaplingStatus(CertVerifier::OCSP_STAPLING_NEVER_CHECKED), + mSCTListFromCertificate(), + mSCTListFromOCSPStapling(), + mBuiltInRootsModule(SECMOD_FindModule(kRootModuleName)), + mOCSPFetchStatus(OCSPFetchStatus::NotFetched) {} + +static void FindRootsWithSubject(UniqueSECMODModule& rootsModule, + SECItem subject, + /*out*/ nsTArray>& roots) { + MOZ_ASSERT(rootsModule); + AutoSECMODListReadLock lock; + for (int slotIndex = 0; slotIndex < rootsModule->slotCount; slotIndex++) { + CERTCertificateList* rawResults = nullptr; + if (PK11_FindRawCertsWithSubject(rootsModule->slots[slotIndex], &subject, + &rawResults) != SECSuccess) { + continue; + } + // rawResults == nullptr means we didn't find any matching certificates + if (!rawResults) { + continue; + } + UniqueCERTCertificateList results(rawResults); + for (int certIndex = 0; certIndex < results->len; certIndex++) { + nsTArray root; + root.AppendElements(results->certs[certIndex].data, + results->certs[certIndex].len); + roots.AppendElement(std::move(root)); + } + } +} + +// A self-signed issuer certificate should never be necessary in order to build +// a trusted certificate chain unless it is a trust anchor. This is because if +// it were necessary, there would exist another certificate with the same +// subject and public key that is also a valid issing certificate. Given this +// certificate, it is possible to build another chain using just it instead of +// it and the self-signed certificate. This is only true as long as the +// certificate extensions we support are restrictive rather than additive in +// terms of the rest of the chain (for example, we don't support policy mapping +// and we ignore any SCT information in intermediates). +static bool ShouldSkipSelfSignedNonTrustAnchor(TrustDomain& trustDomain, + Input certDER) { + BackCert cert(certDER, EndEntityOrCA::MustBeCA, nullptr); + if (cert.Init() != Success) { + return false; // turn any failures into "don't skip trying this cert" + } + // If subject != issuer, this isn't a self-signed cert. + if (!InputsAreEqual(cert.GetSubject(), cert.GetIssuer())) { + return false; + } + TrustLevel trust; + if (trustDomain.GetCertTrust(EndEntityOrCA::MustBeCA, CertPolicyId::anyPolicy, + certDER, trust) != Success) { + return false; + } + // If the trust for this certificate is anything other than "inherit", we want + // to process it like normal. + if (trust != TrustLevel::InheritsTrust) { + return false; + } + if (VerifySignedData(trustDomain, cert.GetSignedData(), + cert.GetSubjectPublicKeyInfo()) != Success) { + return false; + } + // This is a self-signed, non-trust-anchor certificate, so we shouldn't use it + // for path building. See bug 1056341. + return true; +} + +static Result CheckCandidates(TrustDomain& trustDomain, + TrustDomain::IssuerChecker& checker, + nsTArray& candidates, + Input* nameConstraintsInputPtr, bool& keepGoing) { + for (Input candidate : candidates) { + // Stop path building if the program is shutting down. + if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) { + keepGoing = false; + return Success; + } + if (ShouldSkipSelfSignedNonTrustAnchor(trustDomain, candidate)) { + continue; + } + Result rv = checker.Check(candidate, nameConstraintsInputPtr, keepGoing); + if (rv != Success) { + return rv; + } + if (!keepGoing) { + return Success; + } + } + + return Success; +} + +Result NSSCertDBTrustDomain::FindIssuer(Input encodedIssuerName, + IssuerChecker& checker, Time) { + SECItem encodedIssuerNameItem = UnsafeMapInputToSECItem(encodedIssuerName); + // Handle imposed name constraints, if any. + ScopedAutoSECItem nameConstraints; + Input nameConstraintsInput; + Input* nameConstraintsInputPtr = nullptr; + SECStatus srv = + CERT_GetImposedNameConstraints(&encodedIssuerNameItem, &nameConstraints); + if (srv == SECSuccess) { + if (nameConstraintsInput.Init(nameConstraints.data, nameConstraints.len) != + Success) { + return Result::FATAL_ERROR_LIBRARY_FAILURE; + } + nameConstraintsInputPtr = &nameConstraintsInput; + } else if (PR_GetError() != SEC_ERROR_EXTENSION_NOT_FOUND) { + return Result::FATAL_ERROR_LIBRARY_FAILURE; + } + + // First try all relevant certificates known to Gecko, which avoids calling + // CERT_CreateSubjectCertList, because that can be expensive. + nsTArray geckoRootCandidates; + nsTArray geckoIntermediateCandidates; + + if (!mCertStorage) { + return Result::FATAL_ERROR_LIBRARY_FAILURE; + } + nsTArray subject; + subject.AppendElements(encodedIssuerName.UnsafeGetData(), + encodedIssuerName.GetLength()); + nsTArray> certs; + nsresult rv = mCertStorage->FindCertsBySubject(subject, certs); + if (NS_FAILED(rv)) { + return Result::FATAL_ERROR_LIBRARY_FAILURE; + } + for (auto& cert : certs) { + Input certDER; + Result rv = certDER.Init(cert.Elements(), cert.Length()); + if (rv != Success) { + continue; // probably too big + } + // Currently we're only expecting intermediate certificates in cert storage. + geckoIntermediateCandidates.AppendElement(std::move(certDER)); + } + + // We might not have this module if e.g. we're on a Linux distribution that + // does something unexpected. + nsTArray> builtInRoots; + if (mBuiltInRootsModule) { + FindRootsWithSubject(mBuiltInRootsModule, encodedIssuerNameItem, + builtInRoots); + for (const auto& root : builtInRoots) { + Input rootInput; + Result rv = rootInput.Init(root.Elements(), root.Length()); + if (rv != Success) { + continue; // probably too big + } + geckoRootCandidates.AppendElement(rootInput); + } + } else { + MOZ_LOG(gCertVerifierLog, LogLevel::Debug, + ("NSSCertDBTrustDomain::FindIssuer: no built-in roots module")); + } + + for (const auto& thirdPartyRootInput : mThirdPartyRootInputs) { + BackCert root(thirdPartyRootInput, EndEntityOrCA::MustBeCA, nullptr); + Result rv = root.Init(); + if (rv != Success) { + continue; + } + // Filter out 3rd party roots that can't be issuers we're looking for + // because the subject distinguished name doesn't match. This prevents + // mozilla::pkix from accumulating spurious errors during path building. + if (!InputsAreEqual(encodedIssuerName, root.GetSubject())) { + continue; + } + geckoRootCandidates.AppendElement(thirdPartyRootInput); + } + + for (const auto& thirdPartyIntermediateInput : + mThirdPartyIntermediateInputs) { + BackCert intermediate(thirdPartyIntermediateInput, EndEntityOrCA::MustBeCA, + nullptr); + Result rv = intermediate.Init(); + if (rv != Success) { + continue; + } + // Filter out 3rd party intermediates that can't be issuers we're looking + // for because the subject distinguished name doesn't match. This prevents + // mozilla::pkix from accumulating spurious errors during path building. + if (!InputsAreEqual(encodedIssuerName, intermediate.GetSubject())) { + continue; + } + geckoIntermediateCandidates.AppendElement(thirdPartyIntermediateInput); + } + + if (mExtraCertificates.isSome()) { + for (const auto& extraCert : *mExtraCertificates) { + Input certInput; + Result rv = certInput.Init(extraCert.Elements(), extraCert.Length()); + if (rv != Success) { + continue; + } + BackCert cert(certInput, EndEntityOrCA::MustBeCA, nullptr); + rv = cert.Init(); + if (rv != Success) { + continue; + } + // Filter out certificates that can't be issuers we're looking for because + // the subject distinguished name doesn't match. This prevents + // mozilla::pkix from accumulating spurious errors during path building. + if (!InputsAreEqual(encodedIssuerName, cert.GetSubject())) { + continue; + } + // We assume that extra certificates (presumably from the TLS handshake) + // are intermediates, since sending trust anchors would be superfluous. + geckoIntermediateCandidates.AppendElement(certInput); + } + } + + // Try all root certs first and then all (presumably) intermediates. + geckoRootCandidates.AppendElements(std::move(geckoIntermediateCandidates)); + + bool keepGoing = true; + Result result = CheckCandidates(*this, checker, geckoRootCandidates, + nameConstraintsInputPtr, keepGoing); + if (result != Success) { + return result; + } + if (!keepGoing) { + return Success; + } + + // Synchronously dispatch a task to the socket thread to find + // CERTCertificates with the given subject. This involves querying NSS + // structures and databases, so it should be done on the socket thread. + nsTArray> nssRootCandidates; + nsTArray> nssIntermediateCandidates; + RefPtr getCandidatesTask = + NS_NewRunnableFunction("NSSCertDBTrustDomain::FindIssuer", [&]() { + if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) { + return; + } + // NSS seems not to differentiate between "no potential issuers found" + // and "there was an error trying to retrieve the potential issuers." We + // assume there was no error if CERT_CreateSubjectCertList returns + // nullptr. + UniqueCERTCertList candidates( + CERT_CreateSubjectCertList(nullptr, CERT_GetDefaultCertDB(), + &encodedIssuerNameItem, 0, false)); + if (candidates) { + for (CERTCertListNode* n = CERT_LIST_HEAD(candidates); + !CERT_LIST_END(n, candidates); n = CERT_LIST_NEXT(n)) { + nsTArray candidate; + candidate.AppendElements(n->cert->derCert.data, + n->cert->derCert.len); + if (n->cert->isRoot) { + nssRootCandidates.AppendElement(std::move(candidate)); + } else { + nssIntermediateCandidates.AppendElement(std::move(candidate)); + } + } + } + }); + nsCOMPtr socketThread( + do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID)); + if (!socketThread) { + return Result::FATAL_ERROR_LIBRARY_FAILURE; + } + rv = SyncRunnable::DispatchToThread(socketThread, getCandidatesTask); + if (NS_FAILED(rv)) { + return Result::FATAL_ERROR_LIBRARY_FAILURE; + } + + nsTArray nssCandidates; + for (const auto& rootCandidate : nssRootCandidates) { + Input certDER; + Result rv = certDER.Init(rootCandidate.Elements(), rootCandidate.Length()); + if (rv != Success) { + continue; // probably too big + } + nssCandidates.AppendElement(std::move(certDER)); + } + for (const auto& intermediateCandidate : nssIntermediateCandidates) { + Input certDER; + Result rv = certDER.Init(intermediateCandidate.Elements(), + intermediateCandidate.Length()); + if (rv != Success) { + continue; // probably too big + } + nssCandidates.AppendElement(std::move(certDER)); + } + + return CheckCandidates(*this, checker, nssCandidates, nameConstraintsInputPtr, + keepGoing); +} + +Result NSSCertDBTrustDomain::GetCertTrust(EndEntityOrCA endEntityOrCA, + const CertPolicyId& policy, + Input candidateCertDER, + /*out*/ TrustLevel& trustLevel) { + // Check the certificate against the OneCRL cert blocklist + if (!mCertStorage) { + return Result::FATAL_ERROR_LIBRARY_FAILURE; + } + + // The certificate blocklist currently only applies to TLS server + // certificates. + if (mCertDBTrustType == trustSSL) { + int16_t revocationState; + + nsTArray issuerBytes; + nsTArray serialBytes; + nsTArray subjectBytes; + nsTArray pubKeyBytes; + + Result result = + BuildRevocationCheckArrays(candidateCertDER, endEntityOrCA, issuerBytes, + serialBytes, subjectBytes, pubKeyBytes); + if (result != Success) { + return result; + } + + nsresult nsrv = mCertStorage->GetRevocationState( + issuerBytes, serialBytes, subjectBytes, pubKeyBytes, &revocationState); + if (NS_FAILED(nsrv)) { + return Result::FATAL_ERROR_LIBRARY_FAILURE; + } + + if (revocationState == nsICertStorage::STATE_ENFORCE) { + MOZ_LOG(gCertVerifierLog, LogLevel::Debug, + ("NSSCertDBTrustDomain: certificate is in blocklist")); + Telemetry::AccumulateCategorical( + Telemetry::LABELS_CERT_REVOCATION_MECHANISMS::OneCRL); + return Result::ERROR_REVOKED_CERTIFICATE; + } + } + + // This may be a third-party root. + for (const auto& thirdPartyRootInput : mThirdPartyRootInputs) { + if (InputsAreEqual(candidateCertDER, thirdPartyRootInput)) { + trustLevel = TrustLevel::TrustAnchor; + return Success; + } + } + + // This may be a third-party intermediate. + for (const auto& thirdPartyIntermediateInput : + mThirdPartyIntermediateInputs) { + if (InputsAreEqual(candidateCertDER, thirdPartyIntermediateInput)) { + trustLevel = TrustLevel::InheritsTrust; + return Success; + } + } + + // Synchronously dispatch a task to the socket thread to construct a + // CERTCertificate and get its trust from NSS. This involves querying NSS + // structures and databases, so it should be done on the socket thread. + Result result = Result::FATAL_ERROR_LIBRARY_FAILURE; + RefPtr getTrustTask = + NS_NewRunnableFunction("NSSCertDBTrustDomain::GetCertTrust", [&]() { + if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) { + result = Result::FATAL_ERROR_LIBRARY_FAILURE; + return; + } + // This would be cleaner and more efficient if we could get the trust + // information without constructing a CERTCertificate here, but NSS + // doesn't expose it in any other easy-to-use fashion. The use of + // CERT_NewTempCertificate to get a CERTCertificate shouldn't be a + // performance problem for certificates already known to NSS because NSS + // will just find the existing CERTCertificate in its in-memory cache + // and return it. For certificates not already in NSS (namely + // third-party roots and intermediates), we want to avoid calling + // CERT_NewTempCertificate repeatedly, so we've already checked if the + // candidate certificate is a third-party certificate, above. + SECItem candidateCertDERSECItem = + UnsafeMapInputToSECItem(candidateCertDER); + UniqueCERTCertificate candidateCert(CERT_NewTempCertificate( + CERT_GetDefaultCertDB(), &candidateCertDERSECItem, nullptr, false, + true)); + if (!candidateCert) { + result = MapPRErrorCodeToResult(PR_GetError()); + return; + } + // NB: CERT_GetCertTrust seems to be abusing SECStatus as a boolean, + // where SECSuccess means that there is a trust record and SECFailure + // means there is not a trust record. I looked at NSS's internal uses of + // CERT_GetCertTrust, and all that code uses the result as a boolean + // meaning "We have a trust record." + CERTCertTrust trust; + if (CERT_GetCertTrust(candidateCert.get(), &trust) == SECSuccess) { + uint32_t flags = SEC_GET_TRUST_FLAGS(&trust, mCertDBTrustType); + + // For DISTRUST, we use the CERTDB_TRUSTED or CERTDB_TRUSTED_CA bit, + // because we can have active distrust for either type of cert. Note + // that CERTDB_TERMINAL_RECORD means "stop trying to inherit trust" so + // if the relevant trust bit isn't set then that means the cert must + // be considered distrusted. + uint32_t relevantTrustBit = endEntityOrCA == EndEntityOrCA::MustBeCA + ? CERTDB_TRUSTED_CA + : CERTDB_TRUSTED; + if (((flags & (relevantTrustBit | CERTDB_TERMINAL_RECORD))) == + CERTDB_TERMINAL_RECORD) { + trustLevel = TrustLevel::ActivelyDistrusted; + result = Success; + return; + } + + // For TRUST, we use the CERTDB_TRUSTED_CA bit. + if (flags & CERTDB_TRUSTED_CA) { + if (policy.IsAnyPolicy()) { + trustLevel = TrustLevel::TrustAnchor; + result = Success; + return; + } + + nsTArray certBytes(candidateCert->derCert.data, + candidateCert->derCert.len); + if (CertIsAuthoritativeForEVPolicy(certBytes, policy)) { + trustLevel = TrustLevel::TrustAnchor; + result = Success; + return; + } + } + } + trustLevel = TrustLevel::InheritsTrust; + result = Success; + }); + nsCOMPtr socketThread( + do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID)); + if (!socketThread) { + return Result::FATAL_ERROR_LIBRARY_FAILURE; + } + nsresult rv = SyncRunnable::DispatchToThread(socketThread, getTrustTask); + if (NS_FAILED(rv)) { + return Result::FATAL_ERROR_LIBRARY_FAILURE; + } + return result; +} + +Result NSSCertDBTrustDomain::DigestBuf(Input item, DigestAlgorithm digestAlg, + /*out*/ uint8_t* digestBuf, + size_t digestBufLen) { + return DigestBufNSS(item, digestAlg, digestBuf, digestBufLen); +} + +TimeDuration NSSCertDBTrustDomain::GetOCSPTimeout() const { + switch (mOCSPFetching) { + case NSSCertDBTrustDomain::FetchOCSPForDVSoftFail: + return mOCSPTimeoutSoft; + case NSSCertDBTrustDomain::FetchOCSPForEV: + case NSSCertDBTrustDomain::FetchOCSPForDVHardFail: + return mOCSPTimeoutHard; + // The rest of these are error cases. Assert in debug builds, but return + // the soft timeout value in release builds. + case NSSCertDBTrustDomain::NeverFetchOCSP: + case NSSCertDBTrustDomain::LocalOnlyOCSPForEV: + MOZ_ASSERT_UNREACHABLE("we should never see this OCSPFetching type here"); + break; + } + + MOZ_ASSERT_UNREACHABLE("we're not handling every OCSPFetching type"); + return mOCSPTimeoutSoft; +} + +// Copied and modified from CERT_GetOCSPAuthorityInfoAccessLocation and +// CERT_GetGeneralNameByType. Returns a non-Result::Success result on error, +// Success with result.IsVoid() == true when an OCSP URI was not found, and +// Success with result.IsVoid() == false when an OCSP URI was found. +static Result GetOCSPAuthorityInfoAccessLocation(const UniquePLArenaPool& arena, + Input aiaExtension, + /*out*/ nsCString& result) { + MOZ_ASSERT(arena.get()); + if (!arena.get()) { + return Result::FATAL_ERROR_INVALID_ARGS; + } + + result.Assign(VoidCString()); + SECItem aiaExtensionSECItem = UnsafeMapInputToSECItem(aiaExtension); + CERTAuthInfoAccess** aia = + CERT_DecodeAuthInfoAccessExtension(arena.get(), &aiaExtensionSECItem); + if (!aia) { + return Result::ERROR_CERT_BAD_ACCESS_LOCATION; + } + for (size_t i = 0; aia[i]; ++i) { + if (SECOID_FindOIDTag(&aia[i]->method) == SEC_OID_PKIX_OCSP) { + // NSS chooses the **last** OCSP URL; we choose the **first** + CERTGeneralName* current = aia[i]->location; + if (!current) { + continue; + } + do { + if (current->type == certURI) { + const SECItem& location = current->name.other; + // (location.len + 1) must be small enough to fit into a uint32_t, + // but we limit it to a smaller bound to reduce OOM risk. + if (location.len > 1024 || memchr(location.data, 0, location.len)) { + // Reject embedded nulls. (NSS doesn't do this) + return Result::ERROR_CERT_BAD_ACCESS_LOCATION; + } + result.Assign(nsDependentCSubstring( + reinterpret_cast(location.data), location.len)); + return Success; + } + current = CERT_GetNextGeneralName(current); + } while (current != aia[i]->location); + } + } + + return Success; +} + +NS_IMPL_ISUPPORTS(CRLiteTimestamp, nsICRLiteTimestamp) + +NS_IMETHODIMP +CRLiteTimestamp::GetLogID(nsTArray& aLogID) { + aLogID.Clear(); + aLogID.AppendElements(mLogID); + return NS_OK; +} + +NS_IMETHODIMP +CRLiteTimestamp::GetTimestamp(uint64_t* aTimestamp) { + *aTimestamp = mTimestamp; + return NS_OK; +} + +Result BuildCRLiteTimestampArray( + Input sctExtension, + /*out*/ nsTArray>& timestamps) { + Input sctList; + Result rv = + ExtractSignedCertificateTimestampListFromExtension(sctExtension, sctList); + if (rv != Success) { + return rv; + } + std::vector decodedSCTs; + size_t decodingErrors; + DecodeSCTs(sctList, decodedSCTs, decodingErrors); + Unused << decodingErrors; + + for (const auto& sct : decodedSCTs) { + timestamps.AppendElement(new CRLiteTimestamp(sct)); + } + return Success; +} + +Result NSSCertDBTrustDomain::CheckCRLiteStash( + const nsTArray& issuerSubjectPublicKeyInfoBytes, + const nsTArray& serialNumberBytes) { + // This information is deterministic and has already been validated by our + // infrastructure (it comes from signed CRLs), so if the stash says a + // certificate is revoked, it is. + bool isRevokedByStash = false; + nsresult rv = mCertStorage->IsCertRevokedByStash( + issuerSubjectPublicKeyInfoBytes, serialNumberBytes, &isRevokedByStash); + if (NS_FAILED(rv)) { + MOZ_LOG(gCertVerifierLog, LogLevel::Debug, + ("NSSCertDBTrustDomain::CheckCRLiteStash: IsCertRevokedByStash " + "failed")); + return Result::FATAL_ERROR_LIBRARY_FAILURE; + } + if (isRevokedByStash) { + MOZ_LOG(gCertVerifierLog, LogLevel::Debug, + ("NSSCertDBTrustDomain::CheckCRLiteStash: IsCertRevokedByStash " + "returned true")); + return Result::ERROR_REVOKED_CERTIFICATE; + } + return Success; +} + +Result NSSCertDBTrustDomain::CheckCRLite( + const nsTArray& issuerBytes, + const nsTArray& issuerSubjectPublicKeyInfoBytes, + const nsTArray& serialNumberBytes, + const nsTArray>& timestamps, + /*out*/ bool& filterCoversCertificate) { + filterCoversCertificate = false; + int16_t crliteRevocationState; + nsresult rv = mCertStorage->GetCRLiteRevocationState( + issuerBytes, issuerSubjectPublicKeyInfoBytes, serialNumberBytes, + timestamps, &crliteRevocationState); + if (NS_FAILED(rv)) { + MOZ_LOG(gCertVerifierLog, LogLevel::Debug, + ("NSSCertDBTrustDomain::CheckCRLite: CRLite call failed")); + return Result::FATAL_ERROR_LIBRARY_FAILURE; + } + MOZ_LOG(gCertVerifierLog, LogLevel::Debug, + ("NSSCertDBTrustDomain::CheckCRLite: CRLite check returned " + "state=%hd", + crliteRevocationState)); + + switch (crliteRevocationState) { + case nsICertStorage::STATE_ENFORCE: + filterCoversCertificate = true; + return Result::ERROR_REVOKED_CERTIFICATE; + case nsICertStorage::STATE_UNSET: + filterCoversCertificate = true; + return Success; + case nsICertStorage::STATE_NOT_ENROLLED: + filterCoversCertificate = false; + return Success; + case nsICertStorage::STATE_NOT_COVERED: + filterCoversCertificate = false; + return Success; + case nsICertStorage::STATE_NO_FILTER: + filterCoversCertificate = false; + return Success; + default: + MOZ_LOG(gCertVerifierLog, LogLevel::Debug, + ("NSSCertDBTrustDomain::CheckCRLite: Unknown CRLite revocation " + "state")); + return Result::FATAL_ERROR_LIBRARY_FAILURE; + } +} + +Result NSSCertDBTrustDomain::CheckRevocation( + EndEntityOrCA endEntityOrCA, const CertID& certID, Time time, + Duration validityDuration, + /*optional*/ const Input* stapledOCSPResponse, + /*optional*/ const Input* aiaExtension, + /*optional*/ const Input* sctExtension) { + // Actively distrusted certificates will have already been blocked by + // GetCertTrust. + + MOZ_LOG(gCertVerifierLog, LogLevel::Debug, + ("NSSCertDBTrustDomain: Top of CheckRevocation\n")); + + // None of the revocation methods in this function are consulted for CA + // certificates. Revocation for CAs is handled by GetCertTrust. + if (endEntityOrCA == EndEntityOrCA::MustBeCA) { + return Success; + } + + // Look for an OCSP Authority Information Access URL. Our behavior in + // ConfirmRevocations mode depends on whether a synchronous OCSP + // request is possible. + nsCString aiaLocation(VoidCString()); + if (aiaExtension) { + UniquePLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE)); + if (!arena) { + return Result::FATAL_ERROR_NO_MEMORY; + } + Result rv = + GetOCSPAuthorityInfoAccessLocation(arena, *aiaExtension, aiaLocation); + if (rv != Success) { + return rv; + } + } + + bool crliteCoversCertificate = false; + Result crliteResult = Success; + if (mCRLiteMode != CRLiteMode::Disabled && sctExtension) { + crliteResult = + CheckRevocationByCRLite(certID, *sctExtension, crliteCoversCertificate); + + // If CheckCRLite returned an error other than "revoked certificate", + // propagate that error. + if (crliteResult != Success && + crliteResult != Result::ERROR_REVOKED_CERTIFICATE) { + return crliteResult; + } + + if (crliteCoversCertificate) { + Telemetry::AccumulateCategorical( + Telemetry::LABELS_CERT_REVOCATION_MECHANISMS::CRLite); + // If we don't return here we will consult OCSP. + // In Enforce CRLite mode we can return "Revoked" or "Not Revoked" + // without consulting OCSP. + if (mCRLiteMode == CRLiteMode::Enforce) { + return crliteResult; + } + // If we don't have a URL for an OCSP responder, then we can return any + // result ConfirmRevocations mode. Note that we might have a + // stapled or cached OCSP response which we ignore in this case. + if (mCRLiteMode == CRLiteMode::ConfirmRevocations && + aiaLocation.IsVoid()) { + return crliteResult; + } + // In ConfirmRevocations mode we can return "Not Revoked" + // without consulting OCSP. + if (mCRLiteMode == CRLiteMode::ConfirmRevocations && + crliteResult == Success) { + return Success; + } + } + } + + bool ocspSoftFailure = false; + Result ocspResult = CheckRevocationByOCSP( + certID, time, validityDuration, aiaLocation, crliteCoversCertificate, + crliteResult, stapledOCSPResponse, ocspSoftFailure); + + // In ConfirmRevocations mode we treat any OCSP failure as confirmation + // of a CRLite revoked result. + if (crliteCoversCertificate && + crliteResult == Result::ERROR_REVOKED_CERTIFICATE && + mCRLiteMode == CRLiteMode::ConfirmRevocations && + (ocspResult != Success || ocspSoftFailure)) { + return Result::ERROR_REVOKED_CERTIFICATE; + } + + MOZ_LOG(gCertVerifierLog, LogLevel::Debug, + ("NSSCertDBTrustDomain: end of CheckRevocation")); + + return ocspResult; +} + +Result NSSCertDBTrustDomain::CheckRevocationByCRLite( + const CertID& certID, const Input& sctExtension, + /*out*/ bool& crliteCoversCertificate) { + crliteCoversCertificate = false; + MOZ_LOG(gCertVerifierLog, LogLevel::Debug, + ("NSSCertDBTrustDomain::CheckRevocation: checking CRLite")); + nsTArray issuerSubjectPublicKeyInfoBytes; + issuerSubjectPublicKeyInfoBytes.AppendElements( + certID.issuerSubjectPublicKeyInfo.UnsafeGetData(), + certID.issuerSubjectPublicKeyInfo.GetLength()); + nsTArray serialNumberBytes; + serialNumberBytes.AppendElements(certID.serialNumber.UnsafeGetData(), + certID.serialNumber.GetLength()); + // The CRLite stash is essentially a subset of a collection of CRLs, so if + // it says a certificate is revoked, it is. + Result rv = + CheckCRLiteStash(issuerSubjectPublicKeyInfoBytes, serialNumberBytes); + if (rv != Success) { + crliteCoversCertificate = (rv == Result::ERROR_REVOKED_CERTIFICATE); + return rv; + } + + nsTArray issuerBytes; + issuerBytes.AppendElements(certID.issuer.UnsafeGetData(), + certID.issuer.GetLength()); + + nsTArray> timestamps; + rv = BuildCRLiteTimestampArray(sctExtension, timestamps); + if (rv != Success) { + MOZ_LOG(gCertVerifierLog, LogLevel::Debug, + ("decoding SCT extension failed - CRLite will be not be " + "consulted")); + return Success; + } + return CheckCRLite(issuerBytes, issuerSubjectPublicKeyInfoBytes, + serialNumberBytes, timestamps, crliteCoversCertificate); +} + +Result NSSCertDBTrustDomain::CheckRevocationByOCSP( + const CertID& certID, Time time, Duration validityDuration, + const nsCString& aiaLocation, const bool crliteCoversCertificate, + const Result crliteResult, + /*optional*/ const Input* stapledOCSPResponse, + /*out*/ bool& softFailure) { + softFailure = false; + const uint16_t maxOCSPLifetimeInDays = 10; + // If we have a stapled OCSP response then the verification of that response + // determines the result unless the OCSP response is expired. We make an + // exception for expired responses because some servers, nginx in particular, + // are known to serve expired responses due to bugs. + // We keep track of the result of verifying the stapled response but don't + // immediately return failure if the response has expired. + Result stapledOCSPResponseResult = Success; + if (stapledOCSPResponse) { + bool expired; + uint32_t ageInHours; + stapledOCSPResponseResult = VerifyAndMaybeCacheEncodedOCSPResponse( + certID, time, maxOCSPLifetimeInDays, *stapledOCSPResponse, + ResponseWasStapled, expired, ageInHours); + Telemetry::AccumulateCategorical( + Telemetry::LABELS_CERT_REVOCATION_MECHANISMS::StapledOCSP); + if (stapledOCSPResponseResult == Success) { + // stapled OCSP response present and good + mOCSPStaplingStatus = CertVerifier::OCSP_STAPLING_GOOD; + MOZ_LOG(gCertVerifierLog, LogLevel::Debug, + ("NSSCertDBTrustDomain: stapled OCSP response: good")); + return Success; + } + if (stapledOCSPResponseResult == Result::ERROR_OCSP_OLD_RESPONSE || + expired) { + // stapled OCSP response present but expired + mOCSPStaplingStatus = CertVerifier::OCSP_STAPLING_EXPIRED; + MOZ_LOG(gCertVerifierLog, LogLevel::Debug, + ("NSSCertDBTrustDomain: expired stapled OCSP response")); + } else if (stapledOCSPResponseResult == + Result::ERROR_OCSP_TRY_SERVER_LATER || + stapledOCSPResponseResult == + Result::ERROR_OCSP_INVALID_SIGNING_CERT) { + // Stapled OCSP response present but invalid for a small number of reasons + // CAs/servers commonly get wrong. This will be treated similarly to an + // expired stapled response. + mOCSPStaplingStatus = CertVerifier::OCSP_STAPLING_INVALID; + MOZ_LOG(gCertVerifierLog, LogLevel::Debug, + ("NSSCertDBTrustDomain: stapled OCSP response: " + "failure (allowed for compatibility)")); + } else { + // stapled OCSP response present but invalid for some reason + mOCSPStaplingStatus = CertVerifier::OCSP_STAPLING_INVALID; + MOZ_LOG(gCertVerifierLog, LogLevel::Debug, + ("NSSCertDBTrustDomain: stapled OCSP response: failure")); + return stapledOCSPResponseResult; + } + } else { + // no stapled OCSP response + mOCSPStaplingStatus = CertVerifier::OCSP_STAPLING_NONE; + MOZ_LOG(gCertVerifierLog, LogLevel::Debug, + ("NSSCertDBTrustDomain: no stapled OCSP response")); + } + + Result cachedResponseResult = Success; + Time cachedResponseValidThrough(Time::uninitialized); + bool cachedResponsePresent = + mOCSPCache.Get(certID, mOriginAttributes, cachedResponseResult, + cachedResponseValidThrough); + if (cachedResponsePresent) { + Telemetry::AccumulateCategorical( + Telemetry::LABELS_CERT_REVOCATION_MECHANISMS::CachedOCSP); + if (cachedResponseResult == Success && cachedResponseValidThrough >= time) { + MOZ_LOG(gCertVerifierLog, LogLevel::Debug, + ("NSSCertDBTrustDomain: cached OCSP response: good")); + return Success; + } + // If we have a cached revoked response, use it. + if (cachedResponseResult == Result::ERROR_REVOKED_CERTIFICATE) { + MOZ_LOG(gCertVerifierLog, LogLevel::Debug, + ("NSSCertDBTrustDomain: cached OCSP response: revoked")); + return Result::ERROR_REVOKED_CERTIFICATE; + } + // The cached response may indicate an unknown certificate or it may be + // expired. Don't return with either of these statuses yet - we may be + // able to fetch a more recent one. + MOZ_LOG(gCertVerifierLog, LogLevel::Debug, + ("NSSCertDBTrustDomain: cached OCSP response: error %d", + static_cast(cachedResponseResult))); + // When a good cached response has expired, it is more convenient + // to convert that to an error code and just deal with + // cachedResponseResult from here on out. + if (cachedResponseResult == Success && cachedResponseValidThrough < time) { + cachedResponseResult = Result::ERROR_OCSP_OLD_RESPONSE; + } + // We may have a cached indication of server failure. Ignore it if + // it has expired. + if (cachedResponseResult != Success && + cachedResponseResult != Result::ERROR_OCSP_UNKNOWN_CERT && + cachedResponseResult != Result::ERROR_OCSP_OLD_RESPONSE && + cachedResponseValidThrough < time) { + cachedResponseResult = Success; + cachedResponsePresent = false; + } + } else { + MOZ_LOG(gCertVerifierLog, LogLevel::Debug, + ("NSSCertDBTrustDomain: no cached OCSP response")); + } + // At this point, if and only if cachedErrorResult is Success, there was no + // cached response. + MOZ_ASSERT((!cachedResponsePresent && cachedResponseResult == Success) || + (cachedResponsePresent && cachedResponseResult != Success)); + + // TODO: We still need to handle the fallback for invalid stapled responses. + // But, if/when we disable OCSP fetching by default, it would be ambiguous + // whether security.OCSP.enable==0 means "I want the default" or "I really + // never want you to ever fetch OCSP." + // Additionally, this doesn't properly handle OCSP-must-staple when OCSP + // fetching is disabled. + Duration shortLifetime(mCertShortLifetimeInDays * Time::ONE_DAY_IN_SECONDS); + if (validityDuration < shortLifetime) { + Telemetry::AccumulateCategorical( + Telemetry::LABELS_CERT_REVOCATION_MECHANISMS::ShortValidity); + } + if ((mOCSPFetching == NeverFetchOCSP) || (validityDuration < shortLifetime)) { + // We're not going to be doing any fetching, so if there was a cached + // "unknown" response, say so. + if (cachedResponseResult == Result::ERROR_OCSP_UNKNOWN_CERT) { + return Result::ERROR_OCSP_UNKNOWN_CERT; + } + // If we're doing hard-fail, we want to know if we have a cached response + // that has expired. + if (mOCSPFetching == FetchOCSPForDVHardFail && + cachedResponseResult == Result::ERROR_OCSP_OLD_RESPONSE) { + return Result::ERROR_OCSP_OLD_RESPONSE; + } + + softFailure = true; + return Success; + } + + if (mOCSPFetching == LocalOnlyOCSPForEV) { + if (cachedResponseResult != Success) { + return cachedResponseResult; + } + return Result::ERROR_OCSP_UNKNOWN_CERT; + } + + if (aiaLocation.IsVoid()) { + if (mOCSPFetching == FetchOCSPForEV || + cachedResponseResult == Result::ERROR_OCSP_UNKNOWN_CERT) { + return Result::ERROR_OCSP_UNKNOWN_CERT; + } + if (cachedResponseResult == Result::ERROR_OCSP_OLD_RESPONSE) { + return Result::ERROR_OCSP_OLD_RESPONSE; + } + if (stapledOCSPResponseResult != Success) { + return stapledOCSPResponseResult; + } + + // Nothing to do if we don't have an OCSP responder URI for the cert; just + // assume it is good. Note that this is the confusing, but intended, + // interpretation of "strict" revocation checking in the face of a + // certificate that lacks an OCSP responder URI. There's no need to set + // softFailure here---we check for the presence of an AIA before attempting + // OCSP when CRLite is configured in confirm revocations mode. + return Success; + } + + if (cachedResponseResult == Success || + cachedResponseResult == Result::ERROR_OCSP_UNKNOWN_CERT || + cachedResponseResult == Result::ERROR_OCSP_OLD_RESPONSE) { + // Only send a request to, and process a response from, the server if we + // didn't have a cached indication of failure. Also, don't keep requesting + // responses from a failing server. + return SynchronousCheckRevocationWithServer( + certID, aiaLocation, time, maxOCSPLifetimeInDays, cachedResponseResult, + stapledOCSPResponseResult, crliteCoversCertificate, crliteResult, + softFailure); + } + + return HandleOCSPFailure(cachedResponseResult, stapledOCSPResponseResult, + cachedResponseResult, softFailure); +} + +Result NSSCertDBTrustDomain::SynchronousCheckRevocationWithServer( + const CertID& certID, const nsCString& aiaLocation, Time time, + uint16_t maxOCSPLifetimeInDays, const Result cachedResponseResult, + const Result stapledOCSPResponseResult, const bool crliteCoversCertificate, + const Result crliteResult, /*out*/ bool& softFailure) { + if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) { + return Result::FATAL_ERROR_LIBRARY_FAILURE; + } + + uint8_t ocspRequestBytes[OCSP_REQUEST_MAX_LENGTH]; + size_t ocspRequestLength; + Result rv = CreateEncodedOCSPRequest(*this, certID, ocspRequestBytes, + ocspRequestLength); + if (rv != Success) { + return rv; + } + + Vector ocspResponse; + Input response; + mOCSPFetchStatus = OCSPFetchStatus::Fetched; + rv = DoOCSPRequest(aiaLocation, mOriginAttributes, ocspRequestBytes, + ocspRequestLength, GetOCSPTimeout(), ocspResponse); + Telemetry::AccumulateCategorical( + Telemetry::LABELS_CERT_REVOCATION_MECHANISMS::OCSP); + if (rv == Success && + response.Init(ocspResponse.begin(), ocspResponse.length()) != Success) { + rv = Result::ERROR_OCSP_MALFORMED_RESPONSE; // too big + } + + if (rv != Success) { + Time timeout(time); + if (timeout.AddSeconds(ServerFailureDelaySeconds) != Success) { + return Result::FATAL_ERROR_LIBRARY_FAILURE; // integer overflow + } + + Result cacheRV = + mOCSPCache.Put(certID, mOriginAttributes, rv, time, timeout); + if (cacheRV != Success) { + return cacheRV; + } + + if (crliteCoversCertificate) { + if (crliteResult == Success) { + // CRLite says the certificate is OK, but OCSP fetching failed. + Telemetry::AccumulateCategorical( + Telemetry::LABELS_CRLITE_VS_OCSP_RESULT::CRLiteOkOCSPFail); + } else { + // CRLite says the certificate is revoked, but OCSP fetching failed. + Telemetry::AccumulateCategorical( + Telemetry::LABELS_CRLITE_VS_OCSP_RESULT::CRLiteRevOCSPFail); + } + } + + return HandleOCSPFailure(cachedResponseResult, stapledOCSPResponseResult, + rv, softFailure); + } + + // If the response from the network has expired but indicates a revoked + // or unknown certificate, PR_GetError() will return the appropriate error. + // We actually ignore expired here. + bool expired; + uint32_t ageInHours; + rv = VerifyAndMaybeCacheEncodedOCSPResponse( + certID, time, maxOCSPLifetimeInDays, response, ResponseIsFromNetwork, + expired, ageInHours); + + // If the CRLite filter covers the certificate, compare the CRLite result + // with the OCSP fetching result. OCSP may have succeeded, said the + // certificate is revoked, said the certificate doesn't exist, or it may have + // failed for a reason that results in a "soft fail" (i.e. there is no + // indication that the certificate is either definitely revoked or definitely + // not revoked, so for usability, revocation checking says the certificate is + // valid by default). + if (crliteCoversCertificate) { + if (rv == Success) { + if (crliteResult == Success) { + // CRLite and OCSP fetching agree the certificate is OK. + Telemetry::AccumulateCategorical( + Telemetry::LABELS_CRLITE_VS_OCSP_RESULT::CRLiteOkOCSPOk); + } else { + // CRLite says the certificate is revoked, but OCSP says it is OK. + Telemetry::AccumulateCategorical( + Telemetry::LABELS_CRLITE_VS_OCSP_RESULT::CRLiteRevOCSPOk); + + if (mCRLiteMode == CRLiteMode::ConfirmRevocations) { + Telemetry::Accumulate(Telemetry::OCSP_AGE_AT_CRLITE_OVERRIDE, + ageInHours); + } + } + } else if (rv == Result::ERROR_REVOKED_CERTIFICATE) { + if (crliteResult == Success) { + // CRLite says the certificate is OK, but OCSP says it is revoked. + Telemetry::AccumulateCategorical( + Telemetry::LABELS_CRLITE_VS_OCSP_RESULT::CRLiteOkOCSPRev); + } else { + // CRLite and OCSP fetching agree the certificate is revoked. + Telemetry::AccumulateCategorical( + Telemetry::LABELS_CRLITE_VS_OCSP_RESULT::CRLiteRevOCSPRev); + } + } else if (rv == Result::ERROR_OCSP_UNKNOWN_CERT) { + if (crliteResult == Success) { + // CRLite says the certificate is OK, but OCSP says it doesn't exist. + Telemetry::AccumulateCategorical( + Telemetry::LABELS_CRLITE_VS_OCSP_RESULT::CRLiteOkOCSPUnk); + } else { + // CRLite says the certificate is revoked, but OCSP says it doesn't + // exist. + Telemetry::AccumulateCategorical( + Telemetry::LABELS_CRLITE_VS_OCSP_RESULT::CRLiteRevOCSPUnk); + } + } else { + if (crliteResult == Success) { + // CRLite says the certificate is OK, but OCSP encountered a soft-fail + // error. + Telemetry::AccumulateCategorical( + Telemetry::LABELS_CRLITE_VS_OCSP_RESULT::CRLiteOkOCSPSoft); + } else { + // CRLite says the certificate is revoked, but OCSP encountered a + // soft-fail error. + Telemetry::AccumulateCategorical( + Telemetry::LABELS_CRLITE_VS_OCSP_RESULT::CRLiteRevOCSPSoft); + } + } + } + + if (rv == Success || mOCSPFetching != FetchOCSPForDVSoftFail) { + MOZ_LOG(gCertVerifierLog, LogLevel::Debug, + ("NSSCertDBTrustDomain: returning after " + "VerifyEncodedOCSPResponse")); + return rv; + } + + if (rv == Result::ERROR_OCSP_UNKNOWN_CERT || + rv == Result::ERROR_REVOKED_CERTIFICATE) { + return rv; + } + + if (stapledOCSPResponseResult != Success) { + MOZ_LOG(gCertVerifierLog, LogLevel::Debug, + ("NSSCertDBTrustDomain: returning SECFailure from expired/invalid " + "stapled response after OCSP request verification failure")); + return stapledOCSPResponseResult; + } + + softFailure = true; + return Success; // Soft fail -> success :( +} + +Result NSSCertDBTrustDomain::HandleOCSPFailure( + const Result cachedResponseResult, const Result stapledOCSPResponseResult, + const Result error, /*out*/ bool& softFailure) { + if (mOCSPFetching != FetchOCSPForDVSoftFail) { + MOZ_LOG(gCertVerifierLog, LogLevel::Debug, + ("NSSCertDBTrustDomain: returning SECFailure after OCSP request " + "failure")); + return error; + } + + if (cachedResponseResult == Result::ERROR_OCSP_UNKNOWN_CERT) { + MOZ_LOG(gCertVerifierLog, LogLevel::Debug, + ("NSSCertDBTrustDomain: returning SECFailure from cached response " + "after OCSP request failure")); + return cachedResponseResult; + } + + if (stapledOCSPResponseResult != Success) { + MOZ_LOG(gCertVerifierLog, LogLevel::Debug, + ("NSSCertDBTrustDomain: returning SECFailure from expired/invalid " + "stapled response after OCSP request failure")); + return stapledOCSPResponseResult; + } + + MOZ_LOG(gCertVerifierLog, LogLevel::Debug, + ("NSSCertDBTrustDomain: returning SECSuccess after OCSP request " + "failure")); + + softFailure = true; + return Success; // Soft fail -> success :( +} + +Result NSSCertDBTrustDomain::VerifyAndMaybeCacheEncodedOCSPResponse( + const CertID& certID, Time time, uint16_t maxLifetimeInDays, + Input encodedResponse, EncodedResponseSource responseSource, + /*out*/ bool& expired, + /*out*/ uint32_t& ageInHours) { + Time thisUpdate(Time::uninitialized); + Time validThrough(Time::uninitialized); + + Result rv = VerifyEncodedOCSPResponse(*this, certID, time, maxLifetimeInDays, + encodedResponse, expired, &thisUpdate, + &validThrough); + // If a response was stapled and expired, we don't want to cache it. Return + // early to simplify the logic here. + if (responseSource == ResponseWasStapled && expired) { + MOZ_ASSERT(rv != Success); + return rv; + } + // validThrough is only trustworthy if the response successfully verifies + // or it indicates a revoked or unknown certificate. + // If this isn't the case, store an indication of failure (to prevent + // repeatedly requesting a response from a failing server). + if (rv != Success && rv != Result::ERROR_REVOKED_CERTIFICATE && + rv != Result::ERROR_OCSP_UNKNOWN_CERT) { + validThrough = time; + if (validThrough.AddSeconds(ServerFailureDelaySeconds) != Success) { + return Result::FATAL_ERROR_LIBRARY_FAILURE; // integer overflow + } + } + // The `thisUpdate` field holds the latest time at which the server knew the + // response was correct. The age of the response is the time that has elapsed + // since. We only use this for the telemetry defined in Bug 1794479. + uint64_t timeInSeconds; + uint64_t thisUpdateInSeconds; + uint64_t ageInSeconds; + SecondsSinceEpochFromTime(time, &timeInSeconds); + SecondsSinceEpochFromTime(thisUpdate, &thisUpdateInSeconds); + if (timeInSeconds >= thisUpdateInSeconds) { + ageInSeconds = timeInSeconds - thisUpdateInSeconds; + // ageInHours is 32 bits because of the telemetry api. + if (ageInSeconds > UINT32_MAX) { + // We could divide by 3600 before checking the UINT32_MAX bound, but if + // ageInSeconds is more than UINT32_MAX then there's been some sort of + // error. + ageInHours = UINT32_MAX; + } else { + // We start at 1 and divide with truncation to reserve ageInHours=0 for + // the case where `thisUpdate` is in the future. + ageInHours = 1 + ageInSeconds / (60 * 60); + } + } else { + ageInHours = 0; + } + if (responseSource == ResponseIsFromNetwork || rv == Success || + rv == Result::ERROR_REVOKED_CERTIFICATE || + rv == Result::ERROR_OCSP_UNKNOWN_CERT) { + MOZ_LOG(gCertVerifierLog, LogLevel::Debug, + ("NSSCertDBTrustDomain: caching OCSP response")); + Result putRV = + mOCSPCache.Put(certID, mOriginAttributes, rv, thisUpdate, validThrough); + if (putRV != Success) { + return putRV; + } + } + + return rv; +} + +SECStatus GetCertDistrustAfterValue(const SECItem* distrustItem, + PRTime& distrustTime) { + if (!distrustItem || !distrustItem->data || distrustItem->len != 13) { + PR_SetError(SEC_ERROR_INVALID_ARGS, 0); + return SECFailure; + } + return DER_DecodeTimeChoice(&distrustTime, distrustItem); +} + +SECStatus GetCertNotBeforeValue(const CERTCertificate* cert, + PRTime& distrustTime) { + return DER_DecodeTimeChoice(&distrustTime, &cert->validity.notBefore); +} + +nsresult isDistrustedCertificateChain( + const nsTArray>& certArray, + const SECTrustType certDBTrustType, bool& isDistrusted) { + if (certArray.Length() == 0) { + return NS_ERROR_FAILURE; + } + + // Set the default result to be distrusted. + isDistrusted = true; + + // There is no distrust to set if the certDBTrustType is not SSL or Email. + if (certDBTrustType != trustSSL && certDBTrustType != trustEmail) { + isDistrusted = false; + return NS_OK; + } + + SECStatus runnableRV = SECFailure; + + RefPtr isDistrustedChainTask = + NS_NewRunnableFunction("isDistrustedCertificateChain", [&]() { + if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) { + runnableRV = SECFailure; + return; + } + // Allocate objects and retreive the root and end-entity certificates. + CERTCertDBHandle* certDB(CERT_GetDefaultCertDB()); + const nsTArray& certRootDER = certArray.LastElement(); + SECItem certRootDERItem = { + siBuffer, const_cast(certRootDER.Elements()), + AssertedCast(certRootDER.Length())}; + UniqueCERTCertificate certRoot(CERT_NewTempCertificate( + certDB, &certRootDERItem, nullptr, false, true)); + if (!certRoot) { + runnableRV = SECFailure; + return; + } + const nsTArray& certLeafDER = certArray.ElementAt(0); + SECItem certLeafDERItem = { + siBuffer, const_cast(certLeafDER.Elements()), + AssertedCast(certLeafDER.Length())}; + UniqueCERTCertificate certLeaf(CERT_NewTempCertificate( + certDB, &certLeafDERItem, nullptr, false, true)); + if (!certLeaf) { + runnableRV = SECFailure; + return; + } + + // Set isDistrusted to false if there is no distrust for the root. + if (!certRoot->distrust) { + isDistrusted = false; + runnableRV = SECSuccess; + return; + } + + // Create a pointer to refer to the selected distrust struct. + SECItem* distrustPtr = nullptr; + if (certDBTrustType == trustSSL) { + distrustPtr = &certRoot->distrust->serverDistrustAfter; + } + if (certDBTrustType == trustEmail) { + distrustPtr = &certRoot->distrust->emailDistrustAfter; + } + + // Get validity for the current end-entity certificate + // and get the distrust field for the root certificate. + PRTime certRootDistrustAfter; + PRTime certLeafNotBefore; + + runnableRV = + GetCertDistrustAfterValue(distrustPtr, certRootDistrustAfter); + if (runnableRV != SECSuccess) { + return; + } + + runnableRV = GetCertNotBeforeValue(certLeaf.get(), certLeafNotBefore); + if (runnableRV != SECSuccess) { + return; + } + + // Compare the validity of the end-entity certificate with + // the distrust value of the root. + if (certLeafNotBefore <= certRootDistrustAfter) { + isDistrusted = false; + } + + runnableRV = SECSuccess; + }); + nsCOMPtr socketThread( + do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID)); + if (!socketThread) { + return NS_ERROR_FAILURE; + } + nsresult rv = + SyncRunnable::DispatchToThread(socketThread, isDistrustedChainTask); + if (NS_FAILED(rv) || runnableRV != SECSuccess) { + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +Result NSSCertDBTrustDomain::IsChainValid(const DERArray& reversedDERArray, + Time time, + const CertPolicyId& requiredPolicy) { + MOZ_LOG(gCertVerifierLog, LogLevel::Debug, + ("NSSCertDBTrustDomain: IsChainValid")); + + size_t numCerts = reversedDERArray.GetLength(); + if (numCerts < 1) { + return Result::FATAL_ERROR_LIBRARY_FAILURE; + } + nsTArray> certArray; + for (size_t i = numCerts; i > 0; --i) { + const Input* derInput = reversedDERArray.GetDER(i - 1); + certArray.EmplaceBack(derInput->UnsafeGetData(), derInput->GetLength()); + } + + const nsTArray& rootBytes = certArray.LastElement(); + Input rootInput; + Result rv = rootInput.Init(rootBytes.Elements(), rootBytes.Length()); + if (rv != Success) { + return rv; + } + rv = IsCertBuiltInRoot(rootInput, mIsBuiltChainRootBuiltInRoot); + if (rv != Result::Success) { + return rv; + } + nsresult nsrv; + // If mHostname isn't set, we're not verifying in the context of a TLS + // handshake, so don't verify key pinning in those cases. + if (mHostname) { + nsTArray> derCertSpanList; + for (const auto& certDER : certArray) { + derCertSpanList.EmplaceBack(certDER.Elements(), certDER.Length()); + } + + bool chainHasValidPins; + nsrv = PublicKeyPinningService::ChainHasValidPins( + derCertSpanList, mHostname, time, mIsBuiltChainRootBuiltInRoot, + chainHasValidPins, mPinningTelemetryInfo); + if (NS_FAILED(nsrv)) { + return Result::FATAL_ERROR_LIBRARY_FAILURE; + } + if (!chainHasValidPins) { + return Result::ERROR_KEY_PINNING_FAILURE; + } + } + + // Check that the childs' certificate NotBefore date is anterior to + // the NotAfter value of the parent when the root is a builtin. + if (mIsBuiltChainRootBuiltInRoot) { + bool isDistrusted; + nsrv = + isDistrustedCertificateChain(certArray, mCertDBTrustType, isDistrusted); + if (NS_FAILED(nsrv)) { + return Result::FATAL_ERROR_LIBRARY_FAILURE; + } + if (isDistrusted) { + return Result::ERROR_UNTRUSTED_ISSUER; + } + } + + // See bug 1434300. If the root is a Symantec root, see if we distrust this + // path. Since we already have the root available, we can check that cheaply + // here before proceeding with the rest of the algorithm. + + // This algorithm only applies if we are verifying in the context of a TLS + // handshake. To determine this, we check mHostname: If it isn't set, this is + // not TLS, so don't run the algorithm. + const nsTArray& rootCertDER = certArray.LastElement(); + if (mHostname && CertDNIsInList(rootCertDER, RootSymantecDNs)) { + if (numCerts <= 1) { + // This chain is supposed to be complete, so this is an error. + return Result::ERROR_ADDITIONAL_POLICY_CONSTRAINT_FAILED; + } + nsTArray intCerts; + + for (size_t i = 1; i < certArray.Length() - 1; ++i) { + const nsTArray& certBytes = certArray.ElementAt(i); + Input certInput; + rv = certInput.Init(certBytes.Elements(), certBytes.Length()); + if (rv != Success) { + return Result::FATAL_ERROR_LIBRARY_FAILURE; + } + + intCerts.EmplaceBack(certInput); + } + + bool isDistrusted = false; + nsrv = CheckForSymantecDistrust(intCerts, RootAppleAndGoogleSPKIs, + isDistrusted); + if (NS_FAILED(nsrv)) { + return Result::FATAL_ERROR_LIBRARY_FAILURE; + } + if (isDistrusted) { + mSawDistrustedCAByPolicyError = true; + return Result::ERROR_ADDITIONAL_POLICY_CONSTRAINT_FAILED; + } + } + + mBuiltChain = std::move(certArray); + + return Success; +} + +Result NSSCertDBTrustDomain::CheckSignatureDigestAlgorithm( + DigestAlgorithm aAlg, EndEntityOrCA /*endEntityOrCA*/, Time /*notBefore*/) { + switch (aAlg) { + case DigestAlgorithm::sha256: // fall through + case DigestAlgorithm::sha384: // fall through + case DigestAlgorithm::sha512: + return Success; + case DigestAlgorithm::sha1: + return Result::ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED; + } + return Result::FATAL_ERROR_LIBRARY_FAILURE; +} + +Result NSSCertDBTrustDomain::CheckRSAPublicKeyModulusSizeInBits( + EndEntityOrCA /*endEntityOrCA*/, unsigned int modulusSizeInBits) { + if (modulusSizeInBits < mMinRSABits) { + return Result::ERROR_INADEQUATE_KEY_SIZE; + } + return Success; +} + +Result NSSCertDBTrustDomain::VerifyRSAPKCS1SignedData( + Input data, DigestAlgorithm digestAlgorithm, Input signature, + Input subjectPublicKeyInfo) { + return VerifyRSAPKCS1SignedDataNSS(data, digestAlgorithm, signature, + subjectPublicKeyInfo, mPinArg); +} + +Result NSSCertDBTrustDomain::VerifyRSAPSSSignedData( + Input data, DigestAlgorithm digestAlgorithm, Input signature, + Input subjectPublicKeyInfo) { + return VerifyRSAPSSSignedDataNSS(data, digestAlgorithm, signature, + subjectPublicKeyInfo, mPinArg); +} + +Result NSSCertDBTrustDomain::CheckECDSACurveIsAcceptable( + EndEntityOrCA /*endEntityOrCA*/, NamedCurve curve) { + switch (curve) { + case NamedCurve::secp256r1: // fall through + case NamedCurve::secp384r1: // fall through + case NamedCurve::secp521r1: + return Success; + } + + return Result::ERROR_UNSUPPORTED_ELLIPTIC_CURVE; +} + +Result NSSCertDBTrustDomain::VerifyECDSASignedData( + Input data, DigestAlgorithm digestAlgorithm, Input signature, + Input subjectPublicKeyInfo) { + return VerifyECDSASignedDataNSS(data, digestAlgorithm, signature, + subjectPublicKeyInfo, mPinArg); +} + +Result NSSCertDBTrustDomain::CheckValidityIsAcceptable( + Time notBefore, Time notAfter, EndEntityOrCA endEntityOrCA, + KeyPurposeId keyPurpose) { + if (endEntityOrCA != EndEntityOrCA::MustBeEndEntity) { + return Success; + } + if (keyPurpose == KeyPurposeId::id_kp_OCSPSigning) { + return Success; + } + + Duration DURATION_27_MONTHS_PLUS_SLOP((2 * 365 + 3 * 31 + 7) * + Time::ONE_DAY_IN_SECONDS); + Duration maxValidityDuration(UINT64_MAX); + Duration validityDuration(notBefore, notAfter); + + switch (mValidityCheckingMode) { + case ValidityCheckingMode::CheckingOff: + return Success; + case ValidityCheckingMode::CheckForEV: + // The EV Guidelines say the maximum is 27 months, but we use a slightly + // higher limit here to (hopefully) minimize compatibility breakage. + maxValidityDuration = DURATION_27_MONTHS_PLUS_SLOP; + break; + default: + MOZ_ASSERT_UNREACHABLE( + "We're not handling every ValidityCheckingMode type"); + } + + if (validityDuration > maxValidityDuration) { + return Result::ERROR_VALIDITY_TOO_LONG; + } + + return Success; +} + +Result NSSCertDBTrustDomain::NetscapeStepUpMatchesServerAuth( + Time notBefore, + /*out*/ bool& matches) { + // (new Date("2015-08-23T00:00:00Z")).getTime() / 1000 + static const Time AUGUST_23_2015 = TimeFromEpochInSeconds(1440288000); + // (new Date("2016-08-23T00:00:00Z")).getTime() / 1000 + static const Time AUGUST_23_2016 = TimeFromEpochInSeconds(1471910400); + + switch (mNetscapeStepUpPolicy) { + case NetscapeStepUpPolicy::AlwaysMatch: + matches = true; + return Success; + case NetscapeStepUpPolicy::MatchBefore23August2016: + matches = notBefore < AUGUST_23_2016; + return Success; + case NetscapeStepUpPolicy::MatchBefore23August2015: + matches = notBefore < AUGUST_23_2015; + return Success; + case NetscapeStepUpPolicy::NeverMatch: + matches = false; + return Success; + default: + MOZ_ASSERT_UNREACHABLE("unhandled NetscapeStepUpPolicy type"); + } + return Result::FATAL_ERROR_LIBRARY_FAILURE; +} + +void NSSCertDBTrustDomain::ResetAccumulatedState() { + mOCSPStaplingStatus = CertVerifier::OCSP_STAPLING_NEVER_CHECKED; + mSCTListFromOCSPStapling = nullptr; + mSCTListFromCertificate = nullptr; + mSawDistrustedCAByPolicyError = false; + mIsBuiltChainRootBuiltInRoot = false; +} + +static Input SECItemToInput(const UniqueSECItem& item) { + Input result; + if (item) { + MOZ_ASSERT(item->type == siBuffer); + Result rv = result.Init(item->data, item->len); + // As used here, |item| originally comes from an Input, + // so there should be no issues converting it back. + MOZ_ASSERT(rv == Success); + Unused << rv; // suppresses warnings in release builds + } + return result; +} + +Input NSSCertDBTrustDomain::GetSCTListFromCertificate() const { + return SECItemToInput(mSCTListFromCertificate); +} + +Input NSSCertDBTrustDomain::GetSCTListFromOCSPStapling() const { + return SECItemToInput(mSCTListFromOCSPStapling); +} + +bool NSSCertDBTrustDomain::GetIsBuiltChainRootBuiltInRoot() const { + return mIsBuiltChainRootBuiltInRoot; +} + +bool NSSCertDBTrustDomain::GetIsErrorDueToDistrustedCAPolicy() const { + return mSawDistrustedCAByPolicyError; +} + +void NSSCertDBTrustDomain::NoteAuxiliaryExtension(AuxiliaryExtension extension, + Input extensionData) { + UniqueSECItem* out = nullptr; + switch (extension) { + case AuxiliaryExtension::EmbeddedSCTList: + out = &mSCTListFromCertificate; + break; + case AuxiliaryExtension::SCTListFromOCSPResponse: + out = &mSCTListFromOCSPStapling; + break; + default: + MOZ_ASSERT_UNREACHABLE("unhandled AuxiliaryExtension"); + } + if (out) { + SECItem extensionDataItem = UnsafeMapInputToSECItem(extensionData); + out->reset(SECITEM_DupItem(&extensionDataItem)); + } +} + +SECStatus InitializeNSS(const nsACString& dir, NSSDBConfig nssDbConfig, + PKCS11DBConfig pkcs11DbConfig) { + MOZ_ASSERT(NS_IsMainThread()); + + // The NSS_INIT_NOROOTINIT flag turns off the loading of the root certs + // module by NSS_Initialize because we will load it in LoadLoadableRoots + // later. It also allows us to work around a bug in the system NSS in + // Ubuntu 8.04, which loads any nonexistent "/libnssckbi.so" as + // "/usr/lib/nss/libnssckbi.so". + uint32_t flags = NSS_INIT_NOROOTINIT | NSS_INIT_OPTIMIZESPACE; + if (nssDbConfig == NSSDBConfig::ReadOnly) { + flags |= NSS_INIT_READONLY; + } + if (pkcs11DbConfig == PKCS11DBConfig::DoNotLoadModules) { + flags |= NSS_INIT_NOMODDB; + } + nsAutoCString dbTypeAndDirectory("sql:"); + dbTypeAndDirectory.Append(dir); + MOZ_LOG(gCertVerifierLog, LogLevel::Debug, + ("InitializeNSS(%s, %d, %d)", dbTypeAndDirectory.get(), + (int)nssDbConfig, (int)pkcs11DbConfig)); + SECStatus srv = + NSS_Initialize(dbTypeAndDirectory.get(), "", "", SECMOD_DB, flags); + if (srv != SECSuccess) { + return srv; + } + + if (nssDbConfig == NSSDBConfig::ReadWrite) { + UniquePK11SlotInfo slot(PK11_GetInternalKeySlot()); + if (!slot) { + return SECFailure; + } + // If the key DB doesn't have a password set, PK11_NeedUserInit will return + // true. For the SQL DB, we need to set a password or we won't be able to + // import any certificates or change trust settings. + if (PK11_NeedUserInit(slot.get())) { + srv = PK11_InitPin(slot.get(), nullptr, nullptr); + MOZ_ASSERT(srv == SECSuccess); + Unused << srv; + } + } + + return SECSuccess; +} + +void DisableMD5() { + NSS_SetAlgorithmPolicy( + SEC_OID_MD5, 0, + NSS_USE_ALG_IN_CERT_SIGNATURE | NSS_USE_ALG_IN_CMS_SIGNATURE); + NSS_SetAlgorithmPolicy( + SEC_OID_PKCS1_MD5_WITH_RSA_ENCRYPTION, 0, + NSS_USE_ALG_IN_CERT_SIGNATURE | NSS_USE_ALG_IN_CMS_SIGNATURE); + NSS_SetAlgorithmPolicy( + SEC_OID_PKCS5_PBE_WITH_MD5_AND_DES_CBC, 0, + NSS_USE_ALG_IN_CERT_SIGNATURE | NSS_USE_ALG_IN_CMS_SIGNATURE); +} + +// Load a given PKCS#11 module located in the given directory. It will be named +// the given module name. Optionally pass some string parameters to it via +// 'params'. This argument will be provided to C_Initialize when called on the +// module. +// |libraryName| and |dir| are encoded in UTF-8. +bool LoadUserModuleAt(const char* moduleName, const char* libraryName, + const nsCString& dir, /* optional */ const char* params) { + // If a module exists with the same name, make a best effort attempt to delete + // it. Note that it isn't possible to delete the internal module, so checking + // the return value would be detrimental in that case. + int unusedModType; + Unused << SECMOD_DeleteModule(moduleName, &unusedModType); + + nsAutoCString fullLibraryPath; + if (!dir.IsEmpty()) { + fullLibraryPath.Assign(dir); + fullLibraryPath.AppendLiteral(FILE_PATH_SEPARATOR); + } + fullLibraryPath.Append(MOZ_DLL_PREFIX); + fullLibraryPath.Append(libraryName); + fullLibraryPath.Append(MOZ_DLL_SUFFIX); + // Escape the \ and " characters. + fullLibraryPath.ReplaceSubstring("\\", "\\\\"); + fullLibraryPath.ReplaceSubstring("\"", "\\\""); + + nsAutoCString pkcs11ModuleSpec("name=\""); + pkcs11ModuleSpec.Append(moduleName); + pkcs11ModuleSpec.AppendLiteral("\" library=\""); + pkcs11ModuleSpec.Append(fullLibraryPath); + pkcs11ModuleSpec.AppendLiteral("\""); + if (params) { + pkcs11ModuleSpec.AppendLiteral("\" parameters=\""); + pkcs11ModuleSpec.Append(params); + pkcs11ModuleSpec.AppendLiteral("\""); + } + + UniqueSECMODModule userModule(SECMOD_LoadUserModule( + const_cast(pkcs11ModuleSpec.get()), nullptr, false)); + if (!userModule) { + return false; + } + + if (!userModule->loaded) { + return false; + } + + return true; +} + +const char* kIPCClientCertsModuleName = "IPC Client Cert Module"; + +bool LoadIPCClientCertsModule(const nsCString& dir) { + // The IPC client certs module needs to be able to call back into gecko to be + // able to communicate with the parent process over IPC. This is achieved by + // serializing the addresses of the relevant functions and passing them as an + // extra string parameter that will be available when C_Initialize is called + // on IPC client certs. + nsPrintfCString addrs("%p,%p", DoFindObjects, DoSign); + if (!LoadUserModuleAt(kIPCClientCertsModuleName, "ipcclientcerts", dir, + addrs.get())) { + return false; + } + RunOnShutdown( + []() { + UniqueSECMODModule ipcClientCertsModule( + SECMOD_FindModule(kIPCClientCertsModuleName)); + if (ipcClientCertsModule) { + SECMOD_UnloadUserModule(ipcClientCertsModule.get()); + } + }, + ShutdownPhase::XPCOMWillShutdown); + return true; +} + +const char* kOSClientCertsModuleName = "OS Client Cert Module"; + +bool LoadOSClientCertsModule(const nsCString& dir) { +#ifdef MOZ_WIDGET_COCOA + // osclientcerts requires macOS 10.14 or later + if (!nsCocoaFeatures::OnMojaveOrLater()) { + return false; + } +#endif + nsLiteralCString params = + StaticPrefs::security_osclientcerts_assume_rsa_pss_support() + ? "RSA-PSS"_ns + : ""_ns; + return LoadUserModuleAt(kOSClientCertsModuleName, "osclientcerts", dir, + params.get()); +} + +bool LoadLoadableRoots(const nsCString& dir) { + // Some NSS command-line utilities will load a roots module under the name + // "Root Certs" if there happens to be a `MOZ_DLL_PREFIX "nssckbi" + // MOZ_DLL_SUFFIX` file in the directory being operated on. In some cases this + // can cause us to fail to load our roots module. In these cases, deleting the + // "Root Certs" module allows us to load the correct one. See bug 1406396. + int unusedModType; + Unused << SECMOD_DeleteModule("Root Certs", &unusedModType); + return LoadUserModuleAt(kRootModuleName, "nssckbi", dir, nullptr); +} + +nsresult DefaultServerNicknameForCert(const CERTCertificate* cert, + /*out*/ nsCString& nickname) { + MOZ_ASSERT(cert); + NS_ENSURE_ARG_POINTER(cert); + + UniquePORTString baseName(CERT_GetCommonName(&cert->subject)); + if (!baseName) { + baseName = UniquePORTString(CERT_GetOrgUnitName(&cert->subject)); + } + if (!baseName) { + baseName = UniquePORTString(CERT_GetOrgName(&cert->subject)); + } + if (!baseName) { + baseName = UniquePORTString(CERT_GetLocalityName(&cert->subject)); + } + if (!baseName) { + baseName = UniquePORTString(CERT_GetStateName(&cert->subject)); + } + if (!baseName) { + baseName = UniquePORTString(CERT_GetCountryName(&cert->subject)); + } + if (!baseName) { + return NS_ERROR_FAILURE; + } + + // This function is only used in contexts where a failure to find a suitable + // nickname does not block the overall task from succeeding. + // As such, we use an arbitrary limit to prevent this nickname searching + // process from taking forever. + static const uint32_t ARBITRARY_LIMIT = 500; + for (uint32_t count = 1; count < ARBITRARY_LIMIT; count++) { + nickname = baseName.get(); + if (count != 1) { + nickname.AppendPrintf(" #%u", count); + } + if (nickname.IsEmpty()) { + return NS_ERROR_FAILURE; + } + + bool conflict = SEC_CertNicknameConflict(nickname.get(), &cert->derSubject, + cert->dbhandle); + if (!conflict) { + return NS_OK; + } + } + + return NS_ERROR_FAILURE; +} + +Result BuildRevocationCheckArrays(Input certDER, EndEntityOrCA endEntityOrCA, + /*out*/ nsTArray& issuerBytes, + /*out*/ nsTArray& serialBytes, + /*out*/ nsTArray& subjectBytes, + /*out*/ nsTArray& pubKeyBytes) { + BackCert cert(certDER, endEntityOrCA, nullptr); + Result rv = cert.Init(); + if (rv != Success) { + return rv; + } + issuerBytes.Clear(); + Input issuer(cert.GetIssuer()); + issuerBytes.AppendElements(issuer.UnsafeGetData(), issuer.GetLength()); + serialBytes.Clear(); + Input serial(cert.GetSerialNumber()); + serialBytes.AppendElements(serial.UnsafeGetData(), serial.GetLength()); + subjectBytes.Clear(); + Input subject(cert.GetSubject()); + subjectBytes.AppendElements(subject.UnsafeGetData(), subject.GetLength()); + pubKeyBytes.Clear(); + Input pubKey(cert.GetSubjectPublicKeyInfo()); + pubKeyBytes.AppendElements(pubKey.UnsafeGetData(), pubKey.GetLength()); + + return Success; +} + +bool CertIsInCertStorage(const nsTArray& certDER, + nsICertStorage* certStorage) { + MOZ_ASSERT(certStorage); + if (!certStorage) { + return false; + } + Input certInput; + Result rv = certInput.Init(certDER.Elements(), certDER.Length()); + if (rv != Success) { + return false; + } + BackCert cert(certInput, EndEntityOrCA::MustBeCA, nullptr); + rv = cert.Init(); + if (rv != Success) { + return false; + } + nsTArray subject; + subject.AppendElements(cert.GetSubject().UnsafeGetData(), + cert.GetSubject().GetLength()); + nsTArray> certStorageCerts; + if (NS_FAILED(certStorage->FindCertsBySubject(subject, certStorageCerts))) { + return false; + } + for (const auto& certStorageCert : certStorageCerts) { + if (certStorageCert.Length() != certDER.Length()) { + continue; + } + if (memcmp(certStorageCert.Elements(), certDER.Elements(), + certStorageCert.Length()) == 0) { + return true; + } + } + return false; +} + +/** + * Given a list of certificates representing a verified certificate path from an + * end-entity certificate to a trust anchor, imports the intermediate + * certificates into the permanent certificate database. This is an attempt to + * cope with misconfigured servers that don't include the appropriate + * intermediate certificates in the TLS handshake. + * + * @param certList the verified certificate list + */ +void SaveIntermediateCerts(const nsTArray>& certList) { + if (certList.IsEmpty()) { + return; + } + nsTArray> intermediates; + // Skip the end-entity; we only want to store intermediates. Similarly, + // there's no need to save the trust anchor - it's either already a permanent + // certificate or it's the Microsoft Family Safety root or an enterprise root + // temporarily imported via the child mode or enterprise root features. We + // don't want to import these because they're intended to be temporary (and + // because importing them happens to reset their trust settings, which breaks + // these features). + for (size_t index = 1; index < certList.Length() - 1; index++) { + intermediates.AppendElement(certList.ElementAt(index).Clone()); + } + nsCOMPtr importCertsRunnable(NS_NewRunnableFunction( + "IdleSaveIntermediateCerts", + [intermediates = std::move(intermediates)]() -> void { + if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) { + return; + } + UniquePK11SlotInfo slot(PK11_GetInternalKeySlot()); + if (!slot) { + return; + } + size_t numCertsImported = 0; + nsCOMPtr certStorage( + do_GetService(NS_CERT_STORAGE_CID)); + for (const auto& certDER : intermediates) { + if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) { + return; + } + if (CertIsInCertStorage(certDER, certStorage)) { + continue; + } + SECItem certDERItem = {siBuffer, + const_cast(certDER.Elements()), + AssertedCast(certDER.Length())}; + UniqueCERTCertificate cert(CERT_NewTempCertificate( + CERT_GetDefaultCertDB(), &certDERItem, nullptr, false, true)); + if (!cert) { + continue; + } + if (cert->slot) { + // This cert was found on a token; no need to remember it in the + // permanent database. + continue; + } + PRBool isperm; + if (CERT_GetCertIsPerm(cert.get(), &isperm) != SECSuccess) { + continue; + } + if (isperm) { + // We don't need to remember certs already stored in perm db. + continue; + } + // This is a best-effort attempt at avoiding unknown issuer errors + // in the future, so ignore failures here. + nsAutoCString nickname; + if (NS_FAILED(DefaultServerNicknameForCert(cert.get(), nickname))) { + continue; + } + Unused << PK11_ImportCert(slot.get(), cert.get(), CK_INVALID_HANDLE, + nickname.get(), false); + numCertsImported++; + } + + nsCOMPtr runnable(NS_NewRunnableFunction( + "IdleSaveIntermediateCertsDone", [numCertsImported]() -> void { + nsCOMPtr observerService = + mozilla::services::GetObserverService(); + if (observerService) { + NS_ConvertUTF8toUTF16 numCertsImportedString( + nsPrintfCString("%zu", numCertsImported)); + observerService->NotifyObservers( + nullptr, "psm:intermediate-certs-cached", + numCertsImportedString.get()); + } + })); + Unused << NS_DispatchToMainThread(runnable.forget()); + })); + Unused << NS_DispatchToCurrentThreadQueue(importCertsRunnable.forget(), + EventQueuePriority::Idle); +} + +} // namespace psm +} // namespace mozilla -- cgit v1.2.3