summaryrefslogtreecommitdiffstats
path: root/security/certverifier/NSSCertDBTrustDomain.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /security/certverifier/NSSCertDBTrustDomain.cpp
parentInitial commit. (diff)
downloadfirefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz
firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'security/certverifier/NSSCertDBTrustDomain.cpp')
-rw-r--r--security/certverifier/NSSCertDBTrustDomain.cpp2029
1 files changed, 2029 insertions, 0 deletions
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 <stdint.h>
+#include <utility>
+
+#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<Input>& thirdPartyRootInputs,
+ const Vector<Input>& thirdPartyIntermediateInputs,
+ const Maybe<nsTArray<nsTArray<uint8_t>>>& extraCertificates,
+ /*out*/ nsTArray<nsTArray<uint8_t>>& 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<nsTArray<uint8_t>>& 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<uint8_t> 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<Input>& 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<Input> geckoRootCandidates;
+ nsTArray<Input> geckoIntermediateCandidates;
+
+ if (!mCertStorage) {
+ return Result::FATAL_ERROR_LIBRARY_FAILURE;
+ }
+ nsTArray<uint8_t> subject;
+ subject.AppendElements(encodedIssuerName.UnsafeGetData(),
+ encodedIssuerName.GetLength());
+ nsTArray<nsTArray<uint8_t>> 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<nsTArray<uint8_t>> 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<nsTArray<uint8_t>> nssRootCandidates;
+ nsTArray<nsTArray<uint8_t>> nssIntermediateCandidates;
+ RefPtr<Runnable> 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<uint8_t> 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<nsIEventTarget> 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<Input> 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<uint8_t> issuerBytes;
+ nsTArray<uint8_t> serialBytes;
+ nsTArray<uint8_t> subjectBytes;
+ nsTArray<uint8_t> 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<Runnable> 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<uint8_t> certBytes(candidateCert->derCert.data,
+ candidateCert->derCert.len);
+ if (CertIsAuthoritativeForEVPolicy(certBytes, policy)) {
+ trustLevel = TrustLevel::TrustAnchor;
+ result = Success;
+ return;
+ }
+ }
+ }
+ trustLevel = TrustLevel::InheritsTrust;
+ result = Success;
+ });
+ nsCOMPtr<nsIEventTarget> 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<const char*>(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<uint8_t>& 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<RefPtr<nsICRLiteTimestamp>>& timestamps) {
+ Input sctList;
+ Result rv =
+ ExtractSignedCertificateTimestampListFromExtension(sctExtension, sctList);
+ if (rv != Success) {
+ return rv;
+ }
+ std::vector<SignedCertificateTimestamp> 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<uint8_t>& issuerSubjectPublicKeyInfoBytes,
+ const nsTArray<uint8_t>& 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<uint8_t>& issuerBytes,
+ const nsTArray<uint8_t>& issuerSubjectPublicKeyInfoBytes,
+ const nsTArray<uint8_t>& serialNumberBytes,
+ const nsTArray<RefPtr<nsICRLiteTimestamp>>& 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<uint8_t> issuerSubjectPublicKeyInfoBytes;
+ issuerSubjectPublicKeyInfoBytes.AppendElements(
+ certID.issuerSubjectPublicKeyInfo.UnsafeGetData(),
+ certID.issuerSubjectPublicKeyInfo.GetLength());
+ nsTArray<uint8_t> 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<uint8_t> issuerBytes;
+ issuerBytes.AppendElements(certID.issuer.UnsafeGetData(),
+ certID.issuer.GetLength());
+
+ nsTArray<RefPtr<nsICRLiteTimestamp>> 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<int>(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<uint8_t> 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<nsTArray<uint8_t>>& 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<Runnable> 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<uint8_t>& certRootDER = certArray.LastElement();
+ SECItem certRootDERItem = {
+ siBuffer, const_cast<unsigned char*>(certRootDER.Elements()),
+ AssertedCast<unsigned int>(certRootDER.Length())};
+ UniqueCERTCertificate certRoot(CERT_NewTempCertificate(
+ certDB, &certRootDERItem, nullptr, false, true));
+ if (!certRoot) {
+ runnableRV = SECFailure;
+ return;
+ }
+ const nsTArray<uint8_t>& certLeafDER = certArray.ElementAt(0);
+ SECItem certLeafDERItem = {
+ siBuffer, const_cast<unsigned char*>(certLeafDER.Elements()),
+ AssertedCast<unsigned int>(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<nsIEventTarget> 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<nsTArray<uint8_t>> certArray;
+ for (size_t i = numCerts; i > 0; --i) {
+ const Input* derInput = reversedDERArray.GetDER(i - 1);
+ certArray.EmplaceBack(derInput->UnsafeGetData(), derInput->GetLength());
+ }
+
+ const nsTArray<uint8_t>& 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<Span<const uint8_t>> 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<uint8_t>& 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<Input> intCerts;
+
+ for (size_t i = 1; i < certArray.Length() - 1; ++i) {
+ const nsTArray<uint8_t>& 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 "<configdir>/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<char*>(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<uint8_t>& issuerBytes,
+ /*out*/ nsTArray<uint8_t>& serialBytes,
+ /*out*/ nsTArray<uint8_t>& subjectBytes,
+ /*out*/ nsTArray<uint8_t>& 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<uint8_t>& 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<uint8_t> subject;
+ subject.AppendElements(cert.GetSubject().UnsafeGetData(),
+ cert.GetSubject().GetLength());
+ nsTArray<nsTArray<uint8_t>> 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<nsTArray<uint8_t>>& certList) {
+ if (certList.IsEmpty()) {
+ return;
+ }
+ nsTArray<nsTArray<uint8_t>> 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<nsIRunnable> 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<nsICertStorage> 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<unsigned char*>(certDER.Elements()),
+ AssertedCast<unsigned int>(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<nsIRunnable> runnable(NS_NewRunnableFunction(
+ "IdleSaveIntermediateCertsDone", [numCertsImported]() -> void {
+ nsCOMPtr<nsIObserverService> 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