diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
commit | 43a97878ce14b72f0981164f87f2e35e14151312 (patch) | |
tree | 620249daf56c0258faa40cbdcf9cfba06de2a846 /security/manager/ssl/nsNSSComponent.cpp | |
parent | Initial commit. (diff) | |
download | firefox-upstream.tar.xz firefox-upstream.zip |
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'security/manager/ssl/nsNSSComponent.cpp')
-rw-r--r-- | security/manager/ssl/nsNSSComponent.cpp | 2809 |
1 files changed, 2809 insertions, 0 deletions
diff --git a/security/manager/ssl/nsNSSComponent.cpp b/security/manager/ssl/nsNSSComponent.cpp new file mode 100644 index 0000000000..5844ffecfd --- /dev/null +++ b/security/manager/ssl/nsNSSComponent.cpp @@ -0,0 +1,2809 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * 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 "nsNSSComponent.h" + +#include "BinaryPath.h" +#include "CryptoTask.h" +#include "EnterpriseRoots.h" +#include "ExtendedValidation.h" +#include "NSSCertDBTrustDomain.h" +#include "SSLTokensCache.h" +#include "ScopedNSSTypes.h" +#include "SharedSSLState.h" +#include "cert.h" +#include "cert_storage/src/cert_storage.h" +#include "certdb.h" +#include "mozilla/AppShutdown.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/Assertions.h" +#include "mozilla/Casting.h" +#include "mozilla/EndianUtils.h" +#include "mozilla/FilePreferences.h" +#include "mozilla/PodOperations.h" +#include "mozilla/Preferences.h" +#include "mozilla/ProfilerLabels.h" +#include "mozilla/ProfilerMarkers.h" +#include "mozilla/PublicSSL.h" +#include "mozilla/ScopeExit.h" +#include "mozilla/Services.h" +#include "mozilla/StaticMutex.h" +#include "mozilla/StaticPrefs_security.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/SyncRunnable.h" +#include "mozilla/Telemetry.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/Unused.h" +#include "mozilla/Vector.h" +#include "mozilla/dom/Promise.h" +#include "mozilla/net/SocketProcessParent.h" +#include "mozpkix/pkixnss.h" +#include "nsAppDirectoryServiceDefs.h" +#include "nsCRT.h" +#include "nsClientAuthRemember.h" +#include "nsComponentManagerUtils.h" +#include "nsDirectoryServiceDefs.h" +#include "nsICertOverrideService.h" +#include "nsIFile.h" +#include "nsILocalFileWin.h" +#include "nsIOService.h" +#include "nsIObserverService.h" +#include "nsIPrompt.h" +#include "nsIProperties.h" +#include "nsISerialEventTarget.h" +#include "nsISiteSecurityService.h" +#include "nsITimer.h" +#include "nsITokenPasswordDialogs.h" +#include "nsIWindowWatcher.h" +#include "nsIXULRuntime.h" +#include "nsLiteralString.h" +#include "nsNSSCertificateDB.h" +#include "nsNSSHelper.h" +#include "nsNetCID.h" +#include "nsPK11TokenDB.h" +#include "nsPrintfCString.h" +#include "nsServiceManagerUtils.h" +#include "nsThreadUtils.h" +#include "nsXULAppAPI.h" +#include "nss.h" +#include "p12plcy.h" +#include "pk11pub.h" +#include "prmem.h" +#include "secerr.h" +#include "secmod.h" +#include "ssl.h" +#include "sslerr.h" +#include "sslproto.h" + +#if defined(XP_LINUX) && !defined(ANDROID) +# include <linux/magic.h> +# include <sys/vfs.h> +#endif + +#ifdef XP_WIN +# include "mozilla/WindowsVersion.h" +# include "nsILocalFileWin.h" + +# include "windows.h" // this needs to be before the following includes +# include "lmcons.h" +# include "sddl.h" +# include "wincrypt.h" +# include "nsIWindowsRegKey.h" +#endif + +using namespace mozilla; +using namespace mozilla::psm; + +LazyLogModule gPIPNSSLog("pipnss"); + +int nsNSSComponent::mInstanceCount = 0; + +// Forward declaration. +nsresult CommonInit(); + +// Take an nsIFile and get a UTF-8-encoded c-string representation of the +// location of that file (encapsulated in an nsACString). +// This operation is generally to be avoided, except when interacting with +// third-party or legacy libraries that cannot handle `nsIFile`s (such as NSS). +// |result| is encoded in UTF-8. +nsresult FileToCString(const nsCOMPtr<nsIFile>& file, nsACString& result) { +#ifdef XP_WIN + nsAutoString path; + nsresult rv = file->GetPath(path); + if (NS_SUCCEEDED(rv)) { + CopyUTF16toUTF8(path, result); + } + return rv; +#else + return file->GetNativePath(result); +#endif +} + +void TruncateFromLastDirectorySeparator(nsCString& path) { + static const nsAutoCString kSeparatorString( + mozilla::FilePreferences::kPathSeparator); + int32_t index = path.RFind(kSeparatorString); + if (index == kNotFound) { + return; + } + path.Truncate(index); +} + +bool LoadIPCClientCerts() { + // This returns the path to the binary currently running, which in most + // cases is "plugin-container". + UniqueFreePtr<char> pluginContainerPath(BinaryPath::Get()); + if (!pluginContainerPath) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, + ("failed to get get plugin-container path")); + return false; + } + nsAutoCString ipcClientCertsDirString(pluginContainerPath.get()); + // On most platforms, ipcclientcerts is in the same directory as + // plugin-container. To obtain the path to that directory, truncate from + // the last directory separator. + // On macOS, plugin-container is in + // Firefox.app/Contents/MacOS/plugin-container.app/Contents/MacOS/, + // whereas ipcclientcerts is in Firefox.app/Contents/MacOS/. Consequently, + // this truncation from the last directory separator has to happen 4 times + // total. Normally this would be done using nsIFile APIs, but due to when + // this is initialized in the socket process, those aren't available. + TruncateFromLastDirectorySeparator(ipcClientCertsDirString); +#ifdef XP_MACOSX + TruncateFromLastDirectorySeparator(ipcClientCertsDirString); + TruncateFromLastDirectorySeparator(ipcClientCertsDirString); + TruncateFromLastDirectorySeparator(ipcClientCertsDirString); +#endif + if (!LoadIPCClientCertsModule(ipcClientCertsDirString)) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, + ("failed to load ipcclientcerts from '%s'", + ipcClientCertsDirString.get())); + return false; + } + return true; +} + +// This function can be called from chrome or content or socket processes +// to ensure that NSS is initialized. +bool EnsureNSSInitializedChromeOrContent() { + static Atomic<bool> initialized(false); + + if (initialized) { + return true; + } + + // If this is not the main thread (i.e. probably a worker) then forward this + // call to the main thread. + if (!NS_IsMainThread()) { + nsCOMPtr<nsIThread> mainThread; + nsresult rv = NS_GetMainThread(getter_AddRefs(mainThread)); + if (NS_FAILED(rv)) { + return false; + } + + // Forward to the main thread synchronously. + mozilla::SyncRunnable::DispatchToThread( + mainThread, + NS_NewRunnableFunction("EnsureNSSInitializedChromeOrContent", []() { + EnsureNSSInitializedChromeOrContent(); + })); + + return initialized; + } + + if (XRE_IsParentProcess()) { + nsCOMPtr<nsISupports> nss = do_GetService(PSM_COMPONENT_CONTRACTID); + if (!nss) { + return false; + } + initialized = true; + return true; + } + + if (NSS_IsInitialized()) { + initialized = true; + return true; + } + + if (NSS_NoDB_Init(nullptr) != SECSuccess) { + return false; + } + + if (XRE_IsSocketProcess()) { + if (NS_FAILED(CommonInit())) { + return false; + } + // If ipcclientcerts fails to load, client certificate authentication won't + // work (if networking is done on the socket process). This is preferable + // to stopping the program entirely, so treat this as best-effort. + Unused << NS_WARN_IF(!LoadIPCClientCerts()); + initialized = true; + return true; + } + + if (NS_FAILED(mozilla::psm::InitializeCipherSuite())) { + return false; + } + + mozilla::psm::DisableMD5(); + mozilla::pkix::RegisterErrorTable(); + initialized = true; + return true; +} + +static const uint32_t OCSP_TIMEOUT_MILLISECONDS_SOFT_MAX = 5000; +static const uint32_t OCSP_TIMEOUT_MILLISECONDS_HARD_MAX = 20000; + +void nsNSSComponent::GetRevocationBehaviorFromPrefs( + /*out*/ CertVerifier::OcspDownloadConfig* odc, + /*out*/ CertVerifier::OcspStrictConfig* osc, + /*out*/ uint32_t* certShortLifetimeInDays, + /*out*/ TimeDuration& softTimeout, + /*out*/ TimeDuration& hardTimeout) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(odc); + MOZ_ASSERT(osc); + MOZ_ASSERT(certShortLifetimeInDays); + + // 0 = disabled + // 1 = enabled for everything (default) + // 2 = enabled for EV certificates only + uint32_t ocspLevel = StaticPrefs::security_OCSP_enabled(); + switch (ocspLevel) { + case 0: + *odc = CertVerifier::ocspOff; + break; + case 2: + *odc = CertVerifier::ocspEVOnly; + break; + default: + *odc = CertVerifier::ocspOn; + break; + } + + *osc = StaticPrefs::security_OCSP_require() ? CertVerifier::ocspStrict + : CertVerifier::ocspRelaxed; + + *certShortLifetimeInDays = + StaticPrefs::security_pki_cert_short_lifetime_in_days(); + + uint32_t softTimeoutMillis = + StaticPrefs::security_OCSP_timeoutMilliseconds_soft(); + softTimeoutMillis = + std::min(softTimeoutMillis, OCSP_TIMEOUT_MILLISECONDS_SOFT_MAX); + softTimeout = TimeDuration::FromMilliseconds(softTimeoutMillis); + + uint32_t hardTimeoutMillis = + StaticPrefs::security_OCSP_timeoutMilliseconds_hard(); + hardTimeoutMillis = + std::min(hardTimeoutMillis, OCSP_TIMEOUT_MILLISECONDS_HARD_MAX); + hardTimeout = TimeDuration::FromMilliseconds(hardTimeoutMillis); +} + +nsNSSComponent::nsNSSComponent() + : mLoadableCertsLoadedMonitor("nsNSSComponent.mLoadableCertsLoadedMonitor"), + mLoadableCertsLoaded(false), + mLoadableCertsLoadedResult(NS_ERROR_FAILURE), + mMutex("nsNSSComponent.mMutex"), + mMitmDetecionEnabled(false) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsNSSComponent::ctor\n")); + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + + MOZ_ASSERT(mInstanceCount == 0, + "nsNSSComponent is a singleton, but instantiated multiple times!"); + ++mInstanceCount; +} + +nsNSSComponent::~nsNSSComponent() { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsNSSComponent::dtor\n")); + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + + // All cleanup code requiring services needs to happen in xpcom_shutdown + + PrepareForShutdown(); + SharedSSLState::GlobalCleanup(); + --mInstanceCount; + + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsNSSComponent::dtor finished\n")); +} + +#ifdef XP_WIN +static bool GetUserSid(nsAString& sidString) { + // UNLEN is the maximum user name length (see Lmcons.h). +1 for the null + // terminator. + WCHAR lpAccountName[UNLEN + 1]; + DWORD lcAccountName = sizeof(lpAccountName) / sizeof(lpAccountName[0]); + BOOL success = GetUserName(lpAccountName, &lcAccountName); + if (!success) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("GetUserName failed")); + return false; + } + char sid_buffer[SECURITY_MAX_SID_SIZE]; + SID* sid = BitwiseCast<SID*, char*>(sid_buffer); + DWORD cbSid = ArrayLength(sid_buffer); + SID_NAME_USE eUse; + // There doesn't appear to be a defined maximum length for the domain name + // here. To deal with this, we start with a reasonable buffer length and + // see if that works. If it fails and the error indicates insufficient length, + // we use the indicated required length and try again. + DWORD cchReferencedDomainName = 128; + auto ReferencedDomainName(MakeUnique<WCHAR[]>(cchReferencedDomainName)); + success = LookupAccountName(nullptr, lpAccountName, sid, &cbSid, + ReferencedDomainName.get(), + &cchReferencedDomainName, &eUse); + if (!success && GetLastError() != ERROR_INSUFFICIENT_BUFFER) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("LookupAccountName failed")); + return false; + } + if (!success) { + ReferencedDomainName = MakeUnique<WCHAR[]>(cchReferencedDomainName); + success = LookupAccountName(nullptr, lpAccountName, sid, &cbSid, + ReferencedDomainName.get(), + &cchReferencedDomainName, &eUse); + } + if (!success) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("LookupAccountName failed")); + return false; + } + LPTSTR StringSid; + success = ConvertSidToStringSid(sid, &StringSid); + if (!success) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("ConvertSidToStringSid failed")); + return false; + } + sidString.Assign(StringSid); + LocalFree(StringSid); + return true; +} + +// This is a specialized helper function to read the value of a registry key +// that might not be present. If it is present, returns (via the output +// parameter) its value. Otherwise, returns the given default value. +// This function handles one level of nesting. That is, if the desired value +// is actually in a direct child of the given registry key (where the child +// and/or the value being sought may not actually be present), this function +// will handle that. In the normal case, though, optionalChildName will be +// null. +static nsresult ReadRegKeyValueWithDefault(nsCOMPtr<nsIWindowsRegKey> regKey, + uint32_t flags, + const wchar_t* optionalChildName, + const wchar_t* valueName, + uint32_t defaultValue, + uint32_t& valueOut) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("ReadRegKeyValueWithDefault")); + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, + ("attempting to read '%S%s%S' with default '%u'", + optionalChildName ? optionalChildName : L"", + optionalChildName ? "\\" : "", valueName, defaultValue)); + if (optionalChildName) { + nsDependentString childNameString(optionalChildName); + bool hasChild; + nsresult rv = regKey->HasChild(childNameString, &hasChild); + if (NS_FAILED(rv)) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, + ("failed to determine if child key is present")); + return rv; + } + if (!hasChild) { + valueOut = defaultValue; + return NS_OK; + } + nsCOMPtr<nsIWindowsRegKey> childRegKey; + rv = regKey->OpenChild(childNameString, flags, getter_AddRefs(childRegKey)); + if (NS_FAILED(rv)) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("couldn't open child key")); + return rv; + } + return ReadRegKeyValueWithDefault(childRegKey, flags, nullptr, valueName, + defaultValue, valueOut); + } + nsDependentString valueNameString(valueName); + bool hasValue; + nsresult rv = regKey->HasValue(valueNameString, &hasValue); + if (NS_FAILED(rv)) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, + ("failed to determine if value is present")); + return rv; + } + if (!hasValue) { + valueOut = defaultValue; + return NS_OK; + } + rv = regKey->ReadIntValue(valueNameString, &valueOut); + if (NS_FAILED(rv)) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("failed to read value")); + return rv; + } + return NS_OK; +} + +static nsresult AccountHasFamilySafetyEnabled(bool& enabled) { + enabled = false; + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("AccountHasFamilySafetyEnabled?")); + nsCOMPtr<nsIWindowsRegKey> parentalControlsKey( + do_CreateInstance("@mozilla.org/windows-registry-key;1")); + if (!parentalControlsKey) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("couldn't create nsIWindowsRegKey")); + return NS_ERROR_FAILURE; + } + uint32_t flags = nsIWindowsRegKey::ACCESS_READ | nsIWindowsRegKey::WOW64_64; + constexpr auto familySafetyPath = + u"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Parental Controls"_ns; + nsresult rv = parentalControlsKey->Open( + nsIWindowsRegKey::ROOT_KEY_LOCAL_MACHINE, familySafetyPath, flags); + if (NS_FAILED(rv)) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("couldn't open parentalControlsKey")); + return rv; + } + constexpr auto usersString = u"Users"_ns; + bool hasUsers; + rv = parentalControlsKey->HasChild(usersString, &hasUsers); + if (NS_FAILED(rv)) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("HasChild(Users) failed")); + return rv; + } + if (!hasUsers) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, + ("Users subkey not present - Parental Controls not enabled")); + return NS_OK; + } + nsCOMPtr<nsIWindowsRegKey> usersKey; + rv = parentalControlsKey->OpenChild(usersString, flags, + getter_AddRefs(usersKey)); + if (NS_FAILED(rv)) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("failed to open Users subkey")); + return rv; + } + nsAutoString sid; + if (!GetUserSid(sid)) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("couldn't get sid")); + return NS_ERROR_FAILURE; + } + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, + ("our sid is '%S'", static_cast<const wchar_t*>(sid.get()))); + bool hasSid; + rv = usersKey->HasChild(sid, &hasSid); + if (NS_FAILED(rv)) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("HasChild(sid) failed")); + return rv; + } + if (!hasSid) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, + ("sid not present in Family Safety Users")); + return NS_OK; + } + nsCOMPtr<nsIWindowsRegKey> sidKey; + rv = usersKey->OpenChild(sid, flags, getter_AddRefs(sidKey)); + if (NS_FAILED(rv)) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("couldn't open sid key")); + return rv; + } + // There are three keys we're interested in: "Parental Controls On", + // "Logging Required", and "Web\\Filter On". These keys will have value 0 + // or 1, indicating a particular feature is disabled or enabled, + // respectively. So, if "Parental Controls On" is not 1, Family Safety is + // disabled and we don't care about anything else. If both "Logging + // Required" and "Web\\Filter On" are 0, the proxy will not be running, + // so for our purposes we can consider Family Safety disabled in that + // case. + // By default, "Logging Required" is 1 and "Web\\Filter On" is 0, + // reflecting the initial settings when Family Safety is enabled for an + // account for the first time, However, these sub-keys are not created + // unless they are switched away from the default value. + uint32_t parentalControlsOn; + rv = sidKey->ReadIntValue(u"Parental Controls On"_ns, &parentalControlsOn); + if (NS_FAILED(rv)) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, + ("couldn't read Parental Controls On")); + return rv; + } + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, + ("Parental Controls On: %u", parentalControlsOn)); + if (parentalControlsOn != 1) { + return NS_OK; + } + uint32_t loggingRequired; + rv = ReadRegKeyValueWithDefault(sidKey, flags, nullptr, L"Logging Required", + 1, loggingRequired); + if (NS_FAILED(rv)) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, + ("failed to read value of Logging Required")); + return rv; + } + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, + ("Logging Required: %u", loggingRequired)); + uint32_t webFilterOn; + rv = ReadRegKeyValueWithDefault(sidKey, flags, L"Web", L"Filter On", 0, + webFilterOn); + if (NS_FAILED(rv)) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, + ("failed to read value of Web\\Filter On")); + return rv; + } + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("Web\\Filter On: %u", webFilterOn)); + enabled = loggingRequired == 1 || webFilterOn == 1; + return NS_OK; +} +#endif // XP_WIN + +bool nsNSSComponent::ShouldEnableEnterpriseRootsForFamilySafety( + uint32_t familySafetyMode) { +#ifdef XP_WIN + if (!(IsWin8Point1OrLater() && !IsWin10OrLater())) { + return false; + } + if (familySafetyMode != 2) { + return false; + } + bool familySafetyEnabled; + nsresult rv = AccountHasFamilySafetyEnabled(familySafetyEnabled); + if (NS_FAILED(rv)) { + return false; + } + return familySafetyEnabled; +#else + return false; +#endif // XP_WIN +} + +void nsNSSComponent::UnloadEnterpriseRoots() { + MOZ_ASSERT(NS_IsMainThread()); + if (!NS_IsMainThread()) { + return; + } + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("UnloadEnterpriseRoots")); + MutexAutoLock lock(mMutex); + mEnterpriseCerts.clear(); + setValidationOptions(false, lock); + ClearSSLExternalAndInternalSessionCache(); +} + +class BackgroundImportEnterpriseCertsTask final : public CryptoTask { + public: + explicit BackgroundImportEnterpriseCertsTask(nsNSSComponent* nssComponent) + : mNSSComponent(nssComponent) {} + + private: + virtual nsresult CalculateResult() override { + mNSSComponent->ImportEnterpriseRoots(); + mNSSComponent->UpdateCertVerifierWithEnterpriseRoots(); + return NS_OK; + } + + virtual void CallCallback(nsresult rv) override { + nsCOMPtr<nsIObserverService> observerService = + mozilla::services::GetObserverService(); + if (observerService) { + observerService->NotifyObservers(nullptr, "psm:enterprise-certs-imported", + nullptr); + } + } + + RefPtr<nsNSSComponent> mNSSComponent; +}; + +void nsNSSComponent::MaybeImportEnterpriseRoots() { + MOZ_ASSERT(NS_IsMainThread()); + if (!NS_IsMainThread()) { + return; + } + bool importEnterpriseRoots = StaticPrefs::security_enterprise_roots_enabled(); + uint32_t familySafetyMode = StaticPrefs::security_family_safety_mode(); + // If we've been configured to detect the Family Safety TLS interception + // feature, see if it's enabled. If so, we want to import enterprise roots. + if (ShouldEnableEnterpriseRootsForFamilySafety(familySafetyMode)) { + importEnterpriseRoots = true; + } + if (importEnterpriseRoots) { + RefPtr<BackgroundImportEnterpriseCertsTask> task = + new BackgroundImportEnterpriseCertsTask(this); + Unused << task->Dispatch(); + } +} + +void nsNSSComponent::ImportEnterpriseRoots() { + MOZ_ASSERT(!NS_IsMainThread()); + if (NS_IsMainThread()) { + return; + } + + Vector<EnterpriseCert> enterpriseCerts; + nsresult rv = GatherEnterpriseCerts(enterpriseCerts); + if (NS_SUCCEEDED(rv)) { + MutexAutoLock lock(mMutex); + mEnterpriseCerts = std::move(enterpriseCerts); + } else { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("failed gathering enterprise roots")); + } +} + +nsresult nsNSSComponent::CommonGetEnterpriseCerts( + nsTArray<nsTArray<uint8_t>>& enterpriseCerts, bool getRoots) { + nsresult rv = BlockUntilLoadableCertsLoaded(); + if (NS_FAILED(rv)) { + return rv; + } + + MutexAutoLock nsNSSComponentLock(mMutex); + enterpriseCerts.Clear(); + for (const auto& cert : mEnterpriseCerts) { + nsTArray<uint8_t> certCopy; + // mEnterpriseCerts includes both roots and intermediates. + if (cert.GetIsRoot() == getRoots) { + nsresult rv = cert.CopyBytes(certCopy); + if (NS_FAILED(rv)) { + return rv; + } + // XXX(Bug 1631371) Check if this should use a fallible operation as it + // pretended earlier. + enterpriseCerts.AppendElement(std::move(certCopy)); + } + } + return NS_OK; +} + +NS_IMETHODIMP +nsNSSComponent::GetEnterpriseRoots( + nsTArray<nsTArray<uint8_t>>& enterpriseRoots) { + return CommonGetEnterpriseCerts(enterpriseRoots, true); +} + +NS_IMETHODIMP +nsNSSComponent::GetEnterpriseIntermediates( + nsTArray<nsTArray<uint8_t>>& enterpriseIntermediates) { + return CommonGetEnterpriseCerts(enterpriseIntermediates, false); +} + +NS_IMETHODIMP +nsNSSComponent::AddEnterpriseIntermediate( + const nsTArray<uint8_t>& intermediateBytes) { + nsresult rv = BlockUntilLoadableCertsLoaded(); + if (NS_FAILED(rv)) { + return rv; + } + EnterpriseCert intermediate; + rv = intermediate.Init(intermediateBytes.Elements(), + intermediateBytes.Length(), false); + if (NS_FAILED(rv)) { + return rv; + } + + { + MutexAutoLock nsNSSComponentLock(mMutex); + if (!mEnterpriseCerts.append(std::move(intermediate))) { + return NS_ERROR_OUT_OF_MEMORY; + } + } + + UpdateCertVerifierWithEnterpriseRoots(); + return NS_OK; +} + +class LoadLoadableCertsTask final : public Runnable { + public: + LoadLoadableCertsTask(nsNSSComponent* nssComponent, + bool importEnterpriseRoots, uint32_t familySafetyMode, + Vector<nsCString>&& possibleLoadableRootsLocations, + Maybe<nsCString>&& osClientCertsModuleLocation) + : Runnable("LoadLoadableCertsTask"), + mNSSComponent(nssComponent), + mImportEnterpriseRoots(importEnterpriseRoots), + mFamilySafetyMode(familySafetyMode), + mPossibleLoadableRootsLocations( + std::move(possibleLoadableRootsLocations)), + mOSClientCertsModuleLocation(std::move(osClientCertsModuleLocation)) { + MOZ_ASSERT(nssComponent); + } + + ~LoadLoadableCertsTask() = default; + + nsresult Dispatch(); + + private: + NS_IMETHOD Run() override; + nsresult LoadLoadableRoots(); + RefPtr<nsNSSComponent> mNSSComponent; + bool mImportEnterpriseRoots; + uint32_t mFamilySafetyMode; + Vector<nsCString> mPossibleLoadableRootsLocations; // encoded in UTF-8 + Maybe<nsCString> mOSClientCertsModuleLocation; // encoded in UTF-8 +}; + +nsresult LoadLoadableCertsTask::Dispatch() { + // The stream transport service (note: not the socket transport service) can + // be used to perform background tasks or I/O that would otherwise block the + // main thread. + nsCOMPtr<nsIEventTarget> target( + do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID)); + if (!target) { + return NS_ERROR_FAILURE; + } + return target->Dispatch(this, NS_DISPATCH_NORMAL); +} + +NS_IMETHODIMP +LoadLoadableCertsTask::Run() { + Telemetry::AutoScalarTimer<Telemetry::ScalarID::NETWORKING_LOADING_CERTS_TASK> + timer; + + nsresult loadLoadableRootsResult = LoadLoadableRoots(); + if (NS_WARN_IF(NS_FAILED(loadLoadableRootsResult))) { + MOZ_LOG(gPIPNSSLog, LogLevel::Error, ("LoadLoadableRoots failed")); + // We don't return loadLoadableRootsResult here because then + // BlockUntilLoadableCertsLoaded will just wait forever. Instead we'll save + // its value (below) so we can inform code that relies on the roots module + // being present that loading it failed. + } + + // Loading EV information will only succeed if we've successfully loaded the + // loadable roots module. + if (NS_SUCCEEDED(loadLoadableRootsResult)) { + if (NS_FAILED(LoadExtendedValidationInfo())) { + // This isn't a show-stopper in the same way that failing to load the + // roots module is. + MOZ_LOG(gPIPNSSLog, LogLevel::Error, ("failed to load EV info")); + } + } + + // If we've been configured to detect the Family Safety TLS interception + // feature, see if it's enabled. If so, we want to import enterprise roots. + if (mNSSComponent->ShouldEnableEnterpriseRootsForFamilySafety( + mFamilySafetyMode)) { + mImportEnterpriseRoots = true; + } + if (mImportEnterpriseRoots) { + mNSSComponent->ImportEnterpriseRoots(); + mNSSComponent->UpdateCertVerifierWithEnterpriseRoots(); + } + if (mOSClientCertsModuleLocation.isSome()) { + bool success = LoadOSClientCertsModule(*mOSClientCertsModuleLocation); + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, + ("loading OS client certs module %s", + success ? "succeeded" : "failed")); + } + { + MonitorAutoLock rootsLoadedLock(mNSSComponent->mLoadableCertsLoadedMonitor); + mNSSComponent->mLoadableCertsLoaded = true; + // Cache the result of LoadLoadableRoots so BlockUntilLoadableCertsLoaded + // can return it to all callers later (we use that particular result because + // if that operation fails, it's unlikely that any TLS connection will + // succeed whereas the browser may still be able to operate if the other + // tasks fail). + mNSSComponent->mLoadableCertsLoadedResult = loadLoadableRootsResult; + mNSSComponent->mLoadableCertsLoadedMonitor.NotifyAll(); + } + return NS_OK; +} + +// Returns by reference the path to the desired directory, based on the current +// settings in the directory service. +// |result| is encoded in UTF-8. +static nsresult GetDirectoryPath(const char* directoryKey, nsCString& result) { + MOZ_ASSERT(NS_IsMainThread()); + + nsCOMPtr<nsIProperties> directoryService( + do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID)); + if (!directoryService) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("could not get directory service")); + return NS_ERROR_FAILURE; + } + nsCOMPtr<nsIFile> directory; + nsresult rv = directoryService->Get(directoryKey, NS_GET_IID(nsIFile), + getter_AddRefs(directory)); + if (NS_FAILED(rv)) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, + ("could not get '%s' from directory service", directoryKey)); + return rv; + } + return FileToCString(directory, result); +} + +class BackgroundLoadOSClientCertsModuleTask final : public CryptoTask { + public: + explicit BackgroundLoadOSClientCertsModuleTask(const nsCString&& libraryDir) + : mLibraryDir(std::move(libraryDir)) {} + + private: + virtual nsresult CalculateResult() override { + bool success = LoadOSClientCertsModule(mLibraryDir); + return success ? NS_OK : NS_ERROR_FAILURE; + } + + virtual void CallCallback(nsresult rv) override { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, + ("loading OS client certs module %s", + NS_SUCCEEDED(rv) ? "succeeded" : "failed")); + nsCOMPtr<nsIObserverService> observerService = + mozilla::services::GetObserverService(); + if (observerService) { + observerService->NotifyObservers( + nullptr, "psm:load-os-client-certs-module-task-ran", nullptr); + } + } + + nsCString mLibraryDir; +}; + +void AsyncLoadOrUnloadOSClientCertsModule(bool load) { + if (load) { + nsCString libraryDir; + nsresult rv = GetDirectoryPath(NS_GRE_BIN_DIR, libraryDir); + if (NS_FAILED(rv)) { + return; + } + RefPtr<BackgroundLoadOSClientCertsModuleTask> task = + new BackgroundLoadOSClientCertsModuleTask(std::move(libraryDir)); + Unused << task->Dispatch(); + } else { + UniqueSECMODModule osClientCertsModule( + SECMOD_FindModule(kOSClientCertsModuleName)); + if (osClientCertsModule) { + SECMOD_UnloadUserModule(osClientCertsModule.get()); + } + } +} + +NS_IMETHODIMP +nsNSSComponent::HasActiveSmartCards(bool* result) { + NS_ENSURE_ARG_POINTER(result); + + BlockUntilLoadableCertsLoaded(); + +#ifndef MOZ_NO_SMART_CARDS + AutoSECMODListReadLock secmodLock; + SECMODModuleList* list = SECMOD_GetDefaultModuleList(); + while (list) { + SECMODModule* module = list->module; + if (SECMOD_LockedModuleHasRemovableSlots(module)) { + *result = true; + return NS_OK; + } + for (int i = 0; i < module->slotCount; i++) { + if (!PK11_IsFriendly(module->slots[i])) { + *result = true; + return NS_OK; + } + } + list = list->next; + } +#endif + *result = false; + return NS_OK; +} + +NS_IMETHODIMP +nsNSSComponent::HasUserCertsInstalled(bool* result) { + NS_ENSURE_ARG_POINTER(result); + + // FindClientCertificatesWithPrivateKeys won't ever return an empty list, so + // all we need to do is check if this is null or not. + UniqueCERTCertList certList(FindClientCertificatesWithPrivateKeys()); + *result = !!certList; + + return NS_OK; +} + +nsresult nsNSSComponent::BlockUntilLoadableCertsLoaded() { + MonitorAutoLock rootsLoadedLock(mLoadableCertsLoadedMonitor); + while (!mLoadableCertsLoaded) { + rootsLoadedLock.Wait(); + } + MOZ_ASSERT(mLoadableCertsLoaded); + + return mLoadableCertsLoadedResult; +} + +#ifndef MOZ_NO_SMART_CARDS +static StaticMutex sCheckForSmartCardChangesMutex MOZ_UNANNOTATED; +static TimeStamp sLastCheckedForSmartCardChanges = TimeStamp::Now(); +#endif + +nsresult nsNSSComponent::CheckForSmartCardChanges() { +#ifndef MOZ_NO_SMART_CARDS + { + StaticMutexAutoLock lock(sCheckForSmartCardChangesMutex); + // Do this at most once every 3 seconds. + TimeStamp now = TimeStamp::Now(); + if (now - sLastCheckedForSmartCardChanges < + TimeDuration::FromSeconds(3.0)) { + return NS_OK; + } + sLastCheckedForSmartCardChanges = now; + } + + // SECMOD_UpdateSlotList attempts to acquire the list lock as well, so we + // have to do this in three steps. + Vector<UniqueSECMODModule> modulesWithRemovableSlots; + { + AutoSECMODListReadLock secmodLock; + SECMODModuleList* list = SECMOD_GetDefaultModuleList(); + while (list) { + if (SECMOD_LockedModuleHasRemovableSlots(list->module)) { + UniqueSECMODModule module(SECMOD_ReferenceModule(list->module)); + if (!modulesWithRemovableSlots.append(std::move(module))) { + return NS_ERROR_OUT_OF_MEMORY; + } + } + list = list->next; + } + } + for (auto& module : modulesWithRemovableSlots) { + // Best-effort. + Unused << SECMOD_UpdateSlotList(module.get()); + } + AutoSECMODListReadLock secmodLock; + for (auto& module : modulesWithRemovableSlots) { + for (int i = 0; i < module->slotCount; i++) { + // We actually don't care about the return value here - we just need to + // call this to get NSS to update its view of this slot. + Unused << PK11_IsPresent(module->slots[i]); + } + } +#endif + + return NS_OK; +} + +// Returns by reference the path to the directory containing the file that has +// been loaded as MOZ_DLL_PREFIX nss3 MOZ_DLL_SUFFIX. +// |result| is encoded in UTF-8. +static nsresult GetNSS3Directory(nsCString& result) { + MOZ_ASSERT(NS_IsMainThread()); + + UniquePRString nss3Path( + PR_GetLibraryFilePathname(MOZ_DLL_PREFIX "nss3" MOZ_DLL_SUFFIX, + reinterpret_cast<PRFuncPtr>(NSS_Initialize))); + if (!nss3Path) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nss not loaded?")); + return NS_ERROR_FAILURE; + } + nsCOMPtr<nsIFile> nss3File(do_CreateInstance(NS_LOCAL_FILE_CONTRACTID)); + if (!nss3File) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("couldn't create a file?")); + return NS_ERROR_FAILURE; + } + nsAutoCString nss3PathAsString(nss3Path.get()); + nsresult rv = nss3File->InitWithNativePath(nss3PathAsString); + if (NS_FAILED(rv)) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, + ("couldn't initialize file with path '%s'", nss3Path.get())); + return rv; + } + nsCOMPtr<nsIFile> nss3Directory; + rv = nss3File->GetParent(getter_AddRefs(nss3Directory)); + if (NS_FAILED(rv)) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("couldn't get parent directory?")); + return rv; + } + return FileToCString(nss3Directory, result); +} + +// The loadable roots library is probably in the same directory we loaded the +// NSS shared library from, but in some cases it may be elsewhere. This function +// enumerates and returns the possible locations as nsCStrings. +// |possibleLoadableRootsLocations| is encoded in UTF-8. +static nsresult ListPossibleLoadableRootsLocations( + Vector<nsCString>& possibleLoadableRootsLocations) { + MOZ_ASSERT(NS_IsMainThread()); + if (!NS_IsMainThread()) { + return NS_ERROR_NOT_SAME_THREAD; + } + + // First try in the directory where we've already loaded + // MOZ_DLL_PREFIX nss3 MOZ_DLL_SUFFIX, since that's likely to be correct. + nsAutoCString nss3Dir; + nsresult rv = GetNSS3Directory(nss3Dir); + if (NS_SUCCEEDED(rv)) { + if (!possibleLoadableRootsLocations.append(std::move(nss3Dir))) { + return NS_ERROR_OUT_OF_MEMORY; + } + } else { + // For some reason this fails on android. In any case, we should try with + // the other potential locations we have. + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, + ("could not determine where nss was loaded from")); + } + nsAutoCString currentProcessDir; + rv = GetDirectoryPath(NS_XPCOM_CURRENT_PROCESS_DIR, currentProcessDir); + if (NS_SUCCEEDED(rv)) { + if (!possibleLoadableRootsLocations.append(std::move(currentProcessDir))) { + return NS_ERROR_OUT_OF_MEMORY; + } + } else { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, + ("could not get current process directory")); + } + nsAutoCString greDir; + rv = GetDirectoryPath(NS_GRE_DIR, greDir); + if (NS_SUCCEEDED(rv)) { + if (!possibleLoadableRootsLocations.append(std::move(greDir))) { + return NS_ERROR_OUT_OF_MEMORY; + } + } else { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("could not get gre directory")); + } + // As a last resort, this will cause the library loading code to use the OS' + // default library search path. + nsAutoCString emptyString; + if (!possibleLoadableRootsLocations.append(std::move(emptyString))) { + return NS_ERROR_OUT_OF_MEMORY; + } + + return NS_OK; +} + +nsresult LoadLoadableCertsTask::LoadLoadableRoots() { + for (const auto& possibleLocation : mPossibleLoadableRootsLocations) { + if (mozilla::psm::LoadLoadableRoots(possibleLocation)) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, + ("loaded CKBI from %s", possibleLocation.get())); + return NS_OK; + } + } + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("could not load loadable roots")); + return NS_ERROR_FAILURE; +} + +// Table of pref names and SSL cipher ID +typedef struct { + const char* pref; + int32_t id; + bool (*prefGetter)(); +} CipherPref; + +// Update the switch statement in AccumulateCipherSuite in nsNSSCallbacks.cpp +// when you add/remove cipher suites here. +static const CipherPref sCipherPrefs[] = { + {"security.ssl3.ecdhe_rsa_aes_128_gcm_sha256", + TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + StaticPrefs::security_ssl3_ecdhe_rsa_aes_128_gcm_sha256}, + {"security.ssl3.ecdhe_ecdsa_aes_128_gcm_sha256", + TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + StaticPrefs::security_ssl3_ecdhe_ecdsa_aes_128_gcm_sha256}, + {"security.ssl3.ecdhe_ecdsa_chacha20_poly1305_sha256", + TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, + StaticPrefs::security_ssl3_ecdhe_ecdsa_chacha20_poly1305_sha256}, + {"security.ssl3.ecdhe_rsa_chacha20_poly1305_sha256", + TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, + StaticPrefs::security_ssl3_ecdhe_rsa_chacha20_poly1305_sha256}, + {"security.ssl3.ecdhe_ecdsa_aes_256_gcm_sha384", + TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, + StaticPrefs::security_ssl3_ecdhe_ecdsa_aes_256_gcm_sha384}, + {"security.ssl3.ecdhe_rsa_aes_256_gcm_sha384", + TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + StaticPrefs::security_ssl3_ecdhe_rsa_aes_256_gcm_sha384}, + {"security.ssl3.ecdhe_rsa_aes_128_sha", TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, + StaticPrefs::security_ssl3_ecdhe_rsa_aes_128_sha}, + {"security.ssl3.ecdhe_ecdsa_aes_128_sha", + TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, + StaticPrefs::security_ssl3_ecdhe_ecdsa_aes_128_sha}, + {"security.ssl3.ecdhe_rsa_aes_256_sha", TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, + StaticPrefs::security_ssl3_ecdhe_rsa_aes_256_sha}, + {"security.ssl3.ecdhe_ecdsa_aes_256_sha", + TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, + StaticPrefs::security_ssl3_ecdhe_ecdsa_aes_256_sha}, + {"security.ssl3.dhe_rsa_aes_128_sha", TLS_DHE_RSA_WITH_AES_128_CBC_SHA, + StaticPrefs::security_ssl3_dhe_rsa_aes_128_sha}, + {"security.ssl3.dhe_rsa_aes_256_sha", TLS_DHE_RSA_WITH_AES_256_CBC_SHA, + StaticPrefs::security_ssl3_dhe_rsa_aes_256_sha}, + {"security.tls13.aes_128_gcm_sha256", TLS_AES_128_GCM_SHA256, + StaticPrefs::security_tls13_aes_128_gcm_sha256}, + {"security.tls13.chacha20_poly1305_sha256", TLS_CHACHA20_POLY1305_SHA256, + StaticPrefs::security_tls13_chacha20_poly1305_sha256}, + {"security.tls13.aes_256_gcm_sha384", TLS_AES_256_GCM_SHA384, + StaticPrefs::security_tls13_aes_256_gcm_sha384}, + {"security.ssl3.rsa_aes_128_gcm_sha256", TLS_RSA_WITH_AES_128_GCM_SHA256, + StaticPrefs::security_ssl3_rsa_aes_128_gcm_sha256}, + {"security.ssl3.rsa_aes_256_gcm_sha384", TLS_RSA_WITH_AES_256_GCM_SHA384, + StaticPrefs::security_ssl3_rsa_aes_256_gcm_sha384}, + {"security.ssl3.rsa_aes_128_sha", TLS_RSA_WITH_AES_128_CBC_SHA, + StaticPrefs::security_ssl3_rsa_aes_128_sha}, + {"security.ssl3.rsa_aes_256_sha", TLS_RSA_WITH_AES_256_CBC_SHA, + StaticPrefs::security_ssl3_rsa_aes_256_sha}, +}; + +// These ciphersuites can only be enabled if deprecated versions of TLS are +// also enabled (via the preference "security.tls.version.enable-deprecated"). +static const CipherPref sDeprecatedTLS1CipherPrefs[] = { + {"security.ssl3.deprecated.rsa_des_ede3_sha", TLS_RSA_WITH_3DES_EDE_CBC_SHA, + StaticPrefs::security_ssl3_deprecated_rsa_des_ede3_sha}, +}; + +// This function will convert from pref values like 1, 2, ... +// to the internal values of SSL_LIBRARY_VERSION_TLS_1_0, +// SSL_LIBRARY_VERSION_TLS_1_1, ... +/*static*/ +void nsNSSComponent::FillTLSVersionRange(SSLVersionRange& rangeOut, + uint32_t minFromPrefs, + uint32_t maxFromPrefs, + SSLVersionRange defaults) { + rangeOut = defaults; + // determine what versions are supported + SSLVersionRange supported; + if (SSL_VersionRangeGetSupported(ssl_variant_stream, &supported) != + SECSuccess) { + return; + } + + // Clip the defaults by what NSS actually supports to enable + // working with a system NSS with different ranges. + rangeOut.min = std::max(rangeOut.min, supported.min); + rangeOut.max = std::min(rangeOut.max, supported.max); + + // convert min/maxFromPrefs to the internal representation + minFromPrefs += SSL_LIBRARY_VERSION_3_0; + maxFromPrefs += SSL_LIBRARY_VERSION_3_0; + // if min/maxFromPrefs are invalid, use defaults + if (minFromPrefs > maxFromPrefs || minFromPrefs < supported.min || + maxFromPrefs > supported.max || + minFromPrefs < SSL_LIBRARY_VERSION_TLS_1_0) { + return; + } + + // fill out rangeOut + rangeOut.min = (uint16_t)minFromPrefs; + rangeOut.max = (uint16_t)maxFromPrefs; +} + +static void ConfigureTLSSessionIdentifiers() { + bool disableSessionIdentifiers = + StaticPrefs::security_ssl_disable_session_identifiers(); + SSL_OptionSetDefault(SSL_ENABLE_SESSION_TICKETS, !disableSessionIdentifiers); + SSL_OptionSetDefault(SSL_NO_CACHE, disableSessionIdentifiers); +} + +nsresult CommonInit() { + SSL_OptionSetDefault(SSL_ENABLE_SSL2, false); + SSL_OptionSetDefault(SSL_V2_COMPATIBLE_HELLO, false); + + nsresult rv = nsNSSComponent::SetEnabledTLSVersions(); + if (NS_FAILED(rv)) { + return rv; + } + + ConfigureTLSSessionIdentifiers(); + + SSL_OptionSetDefault(SSL_REQUIRE_SAFE_NEGOTIATION, + StaticPrefs::security_ssl_require_safe_negotiation()); + SSL_OptionSetDefault(SSL_ENABLE_RENEGOTIATION, SSL_RENEGOTIATE_REQUIRES_XTN); + SSL_OptionSetDefault(SSL_ENABLE_EXTENDED_MASTER_SECRET, true); + SSL_OptionSetDefault(SSL_ENABLE_HELLO_DOWNGRADE_CHECK, + StaticPrefs::security_tls_hello_downgrade_check()); + SSL_OptionSetDefault(SSL_ENABLE_FALSE_START, + StaticPrefs::security_ssl_enable_false_start()); + // SSL_ENABLE_ALPN also requires calling SSL_SetNextProtoNego in order for + // the extensions to be negotiated. + // WebRTC does not do that so it will not use ALPN even when this preference + // is true. + SSL_OptionSetDefault(SSL_ENABLE_ALPN, + StaticPrefs::security_ssl_enable_alpn()); + SSL_OptionSetDefault(SSL_ENABLE_0RTT_DATA, + StaticPrefs::security_tls_enable_0rtt_data()); + SSL_OptionSetDefault(SSL_ENABLE_POST_HANDSHAKE_AUTH, + StaticPrefs::security_tls_enable_post_handshake_auth()); + SSL_OptionSetDefault( + SSL_ENABLE_DELEGATED_CREDENTIALS, + StaticPrefs::security_tls_enable_delegated_credentials()); + + rv = InitializeCipherSuite(); + if (NS_FAILED(rv)) { + MOZ_LOG(gPIPNSSLog, LogLevel::Error, + ("Unable to initialize cipher suite settings\n")); + return rv; + } + + DisableMD5(); + + mozilla::pkix::RegisterErrorTable(); + SharedSSLState::GlobalInit(); + SetValidationOptionsCommon(); + + return NS_OK; +} + +void PrepareForShutdownInSocketProcess() { + MOZ_ASSERT(XRE_IsSocketProcess()); + SharedSSLState::GlobalCleanup(); +} + +bool HandleTLSPrefChange(const nsCString& prefName) { + // Note that the code in this function should be kept in sync with + // gCallbackSecurityPrefs in nsIOService.cpp. + bool prefFound = true; + if (prefName.EqualsLiteral("security.tls.version.min") || + prefName.EqualsLiteral("security.tls.version.max") || + prefName.EqualsLiteral("security.tls.version.enable-deprecated")) { + Unused << nsNSSComponent::SetEnabledTLSVersions(); + } else if (prefName.EqualsLiteral("security.tls.hello_downgrade_check")) { + SSL_OptionSetDefault(SSL_ENABLE_HELLO_DOWNGRADE_CHECK, + StaticPrefs::security_tls_hello_downgrade_check()); + } else if (prefName.EqualsLiteral("security.ssl.require_safe_negotiation")) { + SSL_OptionSetDefault(SSL_REQUIRE_SAFE_NEGOTIATION, + StaticPrefs::security_ssl_require_safe_negotiation()); + } else if (prefName.EqualsLiteral("security.ssl.enable_false_start")) { + SSL_OptionSetDefault(SSL_ENABLE_FALSE_START, + StaticPrefs::security_ssl_enable_false_start()); + } else if (prefName.EqualsLiteral("security.ssl.enable_alpn")) { + SSL_OptionSetDefault(SSL_ENABLE_ALPN, + StaticPrefs::security_ssl_enable_alpn()); + } else if (prefName.EqualsLiteral("security.tls.enable_0rtt_data")) { + SSL_OptionSetDefault(SSL_ENABLE_0RTT_DATA, + StaticPrefs::security_tls_enable_0rtt_data()); + } else if (prefName.EqualsLiteral( + "security.tls.enable_post_handshake_auth")) { + SSL_OptionSetDefault( + SSL_ENABLE_POST_HANDSHAKE_AUTH, + StaticPrefs::security_tls_enable_post_handshake_auth()); + } else if (prefName.EqualsLiteral( + "security.tls.enable_delegated_credentials")) { + SSL_OptionSetDefault( + SSL_ENABLE_DELEGATED_CREDENTIALS, + StaticPrefs::security_tls_enable_delegated_credentials()); + } else if (prefName.EqualsLiteral( + "security.ssl.disable_session_identifiers")) { + ConfigureTLSSessionIdentifiers(); + } else { + prefFound = false; + } + return prefFound; +} + +void SetValidationOptionsCommon() { + // Note that the code in this function should be kept in sync with + // gCallbackSecurityPrefs in nsIOService.cpp. + bool ocspStaplingEnabled = StaticPrefs::security_ssl_enable_ocsp_stapling(); + PublicSSLState()->SetOCSPStaplingEnabled(ocspStaplingEnabled); + PrivateSSLState()->SetOCSPStaplingEnabled(ocspStaplingEnabled); + + bool ocspMustStapleEnabled = + StaticPrefs::security_ssl_enable_ocsp_must_staple(); + PublicSSLState()->SetOCSPMustStapleEnabled(ocspMustStapleEnabled); + PrivateSSLState()->SetOCSPMustStapleEnabled(ocspMustStapleEnabled); + + const CertVerifier::CertificateTransparencyMode defaultCTMode = + CertVerifier::CertificateTransparencyMode::TelemetryOnly; + CertVerifier::CertificateTransparencyMode ctMode = + static_cast<CertVerifier::CertificateTransparencyMode>( + StaticPrefs::security_pki_certificate_transparency_mode()); + switch (ctMode) { + case CertVerifier::CertificateTransparencyMode::Disabled: + case CertVerifier::CertificateTransparencyMode::TelemetryOnly: + break; + default: + ctMode = defaultCTMode; + break; + } + bool sctsEnabled = + ctMode != CertVerifier::CertificateTransparencyMode::Disabled; + PublicSSLState()->SetSignedCertTimestampsEnabled(sctsEnabled); + PrivateSSLState()->SetSignedCertTimestampsEnabled(sctsEnabled); +} + +namespace { + +class CipherSuiteChangeObserver : public nsIObserver { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + + static nsresult StartObserve(); + + protected: + virtual ~CipherSuiteChangeObserver() = default; + + private: + static StaticRefPtr<CipherSuiteChangeObserver> sObserver; + CipherSuiteChangeObserver() = default; +}; + +NS_IMPL_ISUPPORTS(CipherSuiteChangeObserver, nsIObserver) + +// static +StaticRefPtr<CipherSuiteChangeObserver> CipherSuiteChangeObserver::sObserver; + +// static +nsresult CipherSuiteChangeObserver::StartObserve() { + MOZ_ASSERT(NS_IsMainThread(), + "CipherSuiteChangeObserver::StartObserve() can only be accessed " + "on the main thread"); + if (!sObserver) { + RefPtr<CipherSuiteChangeObserver> observer = + new CipherSuiteChangeObserver(); + nsresult rv = Preferences::AddStrongObserver(observer.get(), "security."); + if (NS_FAILED(rv)) { + sObserver = nullptr; + return rv; + } + + nsCOMPtr<nsIObserverService> observerService = + mozilla::services::GetObserverService(); + observerService->AddObserver(observer, NS_XPCOM_SHUTDOWN_OBSERVER_ID, + false); + + sObserver = observer; + } + return NS_OK; +} + +// Enables or disabled ciphersuites from deprecated versions of TLS as +// appropriate. If security.tls.version.enable-deprecated is true, these +// ciphersuites may be enabled, if the corresponding preference is true. +// Otherwise, these ciphersuites will be disabled. +void SetDeprecatedTLS1CipherPrefs() { + if (StaticPrefs::security_tls_version_enable_deprecated()) { + for (const auto& deprecatedTLS1CipherPref : sDeprecatedTLS1CipherPrefs) { + SSL_CipherPrefSetDefault(deprecatedTLS1CipherPref.id, + deprecatedTLS1CipherPref.prefGetter()); + } + } else { + for (const auto& deprecatedTLS1CipherPref : sDeprecatedTLS1CipherPrefs) { + SSL_CipherPrefSetDefault(deprecatedTLS1CipherPref.id, false); + } + } +} + +nsresult CipherSuiteChangeObserver::Observe(nsISupports* /*aSubject*/, + const char* aTopic, + const char16_t* someData) { + MOZ_ASSERT(NS_IsMainThread(), + "CipherSuiteChangeObserver::Observe can only be accessed on main " + "thread"); + if (nsCRT::strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID) == 0) { + NS_ConvertUTF16toUTF8 prefName(someData); + // Look through the cipher table and set according to pref setting + for (const auto& cipherPref : sCipherPrefs) { + if (prefName.Equals(cipherPref.pref)) { + SSL_CipherPrefSetDefault(cipherPref.id, cipherPref.prefGetter()); + break; + } + } + SetDeprecatedTLS1CipherPrefs(); + nsNSSComponent::DoClearSSLExternalAndInternalSessionCache(); + } else if (nsCRT::strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) { + Preferences::RemoveObserver(this, "security."); + MOZ_ASSERT(sObserver.get() == this); + sObserver = nullptr; + nsCOMPtr<nsIObserverService> observerService = + mozilla::services::GetObserverService(); + observerService->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID); + } + return NS_OK; +} + +} // namespace + +void nsNSSComponent::setValidationOptions( + bool isInitialSetting, const mozilla::MutexAutoLock& proofOfLock) { + // We access prefs so this must be done on the main thread. + mMutex.AssertCurrentThreadOwns(); + MOZ_ASSERT(NS_IsMainThread()); + if (NS_WARN_IF(!NS_IsMainThread())) { + return; + } + + SetValidationOptionsCommon(); + + const CertVerifier::CertificateTransparencyMode defaultCTMode = + CertVerifier::CertificateTransparencyMode::TelemetryOnly; + CertVerifier::CertificateTransparencyMode ctMode = + static_cast<CertVerifier::CertificateTransparencyMode>( + StaticPrefs::security_pki_certificate_transparency_mode()); + switch (ctMode) { + case CertVerifier::CertificateTransparencyMode::Disabled: + case CertVerifier::CertificateTransparencyMode::TelemetryOnly: + break; + default: + ctMode = defaultCTMode; + break; + } + + // This preference controls whether we do OCSP fetching and does not affect + // OCSP stapling. + // 0 = disabled, 1 = enabled, 2 = only enabled for EV + uint32_t ocspEnabled = StaticPrefs::security_OCSP_enabled(); + + bool ocspRequired = ocspEnabled > 0 && StaticPrefs::security_OCSP_require(); + + // We measure the setting of the pref at startup only to minimize noise by + // addons that may muck with the settings, though it probably doesn't matter. + if (isInitialSetting) { + Telemetry::Accumulate(Telemetry::CERT_OCSP_ENABLED, ocspEnabled); + Telemetry::Accumulate(Telemetry::CERT_OCSP_REQUIRED, ocspRequired); + } + + NetscapeStepUpPolicy netscapeStepUpPolicy = static_cast<NetscapeStepUpPolicy>( + StaticPrefs::security_pki_netscape_step_up_policy()); + switch (netscapeStepUpPolicy) { + case NetscapeStepUpPolicy::AlwaysMatch: + case NetscapeStepUpPolicy::MatchBefore23August2016: + case NetscapeStepUpPolicy::MatchBefore23August2015: + case NetscapeStepUpPolicy::NeverMatch: + break; + default: + netscapeStepUpPolicy = NetscapeStepUpPolicy::AlwaysMatch; + break; + } + + CRLiteMode defaultCRLiteMode = CRLiteMode::Disabled; + CRLiteMode crliteMode = + static_cast<CRLiteMode>(StaticPrefs::security_pki_crlite_mode()); + switch (crliteMode) { + case CRLiteMode::Disabled: + case CRLiteMode::TelemetryOnly: + case CRLiteMode::Enforce: + case CRLiteMode::ConfirmRevocations: + break; + default: + crliteMode = defaultCRLiteMode; + break; + } + + CertVerifier::OcspDownloadConfig odc; + CertVerifier::OcspStrictConfig osc; + uint32_t certShortLifetimeInDays; + TimeDuration softTimeout; + TimeDuration hardTimeout; + + GetRevocationBehaviorFromPrefs(&odc, &osc, &certShortLifetimeInDays, + softTimeout, hardTimeout); + + mDefaultCertVerifier = new SharedCertVerifier( + odc, osc, softTimeout, hardTimeout, certShortLifetimeInDays, + netscapeStepUpPolicy, ctMode, crliteMode, mEnterpriseCerts); +} + +void nsNSSComponent::UpdateCertVerifierWithEnterpriseRoots() { + MutexAutoLock lock(mMutex); + MOZ_ASSERT(mDefaultCertVerifier); + if (NS_WARN_IF(!mDefaultCertVerifier)) { + return; + } + + RefPtr<SharedCertVerifier> oldCertVerifier = mDefaultCertVerifier; + mDefaultCertVerifier = new SharedCertVerifier( + oldCertVerifier->mOCSPDownloadConfig, + oldCertVerifier->mOCSPStrict ? CertVerifier::ocspStrict + : CertVerifier::ocspRelaxed, + oldCertVerifier->mOCSPTimeoutSoft, oldCertVerifier->mOCSPTimeoutHard, + oldCertVerifier->mCertShortLifetimeInDays, + oldCertVerifier->mNetscapeStepUpPolicy, oldCertVerifier->mCTMode, + oldCertVerifier->mCRLiteMode, mEnterpriseCerts); +} + +// Enable the TLS versions given in the prefs, defaulting to TLS 1.0 (min) and +// TLS 1.2 (max) when the prefs aren't set or set to invalid values. +nsresult nsNSSComponent::SetEnabledTLSVersions() { + // Keep these values in sync with all.js. + // 1 means TLS 1.0, 2 means TLS 1.1, etc. + static const uint32_t PSM_DEFAULT_MIN_TLS_VERSION = 3; + static const uint32_t PSM_DEFAULT_MAX_TLS_VERSION = 4; + static const uint32_t PSM_DEPRECATED_TLS_VERSION = 1; + + uint32_t minFromPrefs = StaticPrefs::security_tls_version_min(); + uint32_t maxFromPrefs = StaticPrefs::security_tls_version_max(); + + // This override should be removed some time after + // PSM_DEFAULT_MIN_TLS_VERSION is increased to 3. + bool enableDeprecated = StaticPrefs::security_tls_version_enable_deprecated(); + if (enableDeprecated) { + minFromPrefs = std::min(minFromPrefs, PSM_DEPRECATED_TLS_VERSION); + } + + SSLVersionRange defaults = { + SSL_LIBRARY_VERSION_3_0 + PSM_DEFAULT_MIN_TLS_VERSION, + SSL_LIBRARY_VERSION_3_0 + PSM_DEFAULT_MAX_TLS_VERSION}; + SSLVersionRange filledInRange; + FillTLSVersionRange(filledInRange, minFromPrefs, maxFromPrefs, defaults); + + SECStatus srv = + SSL_VersionRangeSetDefault(ssl_variant_stream, &filledInRange); + if (srv != SECSuccess) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +#if defined(XP_WIN) || (defined(XP_LINUX) && !defined(ANDROID)) +// If the profile directory is on a networked drive, we want to set the +// environment variable NSS_SDB_USE_CACHE to yes (as long as it hasn't been set +// before). +static void SetNSSDatabaseCacheModeAsAppropriate() { + MOZ_ASSERT(NS_IsMainThread()); + + nsCOMPtr<nsIFile> profileFile; + nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, + getter_AddRefs(profileFile)); + if (NS_FAILED(rv)) { + // We're probably running without a profile directory, so this is + // irrelevant. + return; + } + + static const char sNSS_SDB_USE_CACHE[] = "NSS_SDB_USE_CACHE"; + static const char sNSS_SDB_USE_CACHE_WITH_VALUE[] = "NSS_SDB_USE_CACHE=yes"; + auto profilePath = profileFile->NativePath(); + +# if defined(XP_LINUX) && !defined(ANDROID) + struct statfs statfs_s; + if (statfs(profilePath.get(), &statfs_s) == 0 && + statfs_s.f_type == NFS_SUPER_MAGIC && !PR_GetEnv(sNSS_SDB_USE_CACHE)) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, + ("profile is remote (and NSS_SDB_USE_CACHE wasn't set): " + "setting NSS_SDB_USE_CACHE")); + PR_SetEnv(sNSS_SDB_USE_CACHE_WITH_VALUE); + } else { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("not setting NSS_SDB_USE_CACHE")); + } +# endif // defined(XP_LINUX) && !defined(ANDROID) + +# ifdef XP_WIN + wchar_t volPath[MAX_PATH]; + if (::GetVolumePathNameW(profilePath.get(), volPath, MAX_PATH) && + ::GetDriveTypeW(volPath) == DRIVE_REMOTE && + !PR_GetEnv(sNSS_SDB_USE_CACHE)) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, + ("profile is remote (and NSS_SDB_USE_CACHE wasn't set): " + "setting NSS_SDB_USE_CACHE")); + PR_SetEnv(sNSS_SDB_USE_CACHE_WITH_VALUE); + } else { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("not setting NSS_SDB_USE_CACHE")); + } +# endif // XP_WIN +} +#endif // defined(XP_WIN) || (defined(XP_LINUX) && !defined(ANDROID)) + +static nsresult GetNSSProfilePath(nsAutoCString& aProfilePath) { + aProfilePath.Truncate(); + nsCOMPtr<nsIFile> profileFile; + nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, + getter_AddRefs(profileFile)); + if (NS_FAILED(rv)) { + NS_WARNING( + "NSS will be initialized without a profile directory. " + "Some things may not work as expected."); + return NS_OK; + } + +#if defined(XP_WIN) + // SQLite always takes UTF-8 file paths regardless of the current system + // code page. + nsCOMPtr<nsILocalFileWin> profileFileWin(do_QueryInterface(profileFile)); + if (!profileFileWin) { + MOZ_LOG(gPIPNSSLog, LogLevel::Error, + ("Could not get nsILocalFileWin for profile directory.\n")); + return NS_ERROR_FAILURE; + } + nsAutoString u16ProfilePath; + rv = profileFileWin->GetPath(u16ProfilePath); + CopyUTF16toUTF8(u16ProfilePath, aProfilePath); +#else + rv = profileFile->GetNativePath(aProfilePath); +#endif + if (NS_FAILED(rv)) { + MOZ_LOG(gPIPNSSLog, LogLevel::Error, + ("Could not get native path for profile directory.\n")); + return rv; + } + + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, + ("NSS profile at '%s'\n", aProfilePath.get())); + return NS_OK; +} + +#ifndef ANDROID +// Given a profile path, attempt to rename the PKCS#11 module DB to +// "pkcs11.txt.fips". In the case of a catastrophic failure (e.g. out of +// memory), returns a failing nsresult. If execution could conceivably proceed, +// returns NS_OK even if renaming the file didn't work. This simplifies the +// logic of the calling code. +// |profilePath| is encoded in UTF-8. +static nsresult AttemptToRenamePKCS11ModuleDB(const nsACString& profilePath) { + nsCOMPtr<nsIFile> profileDir = do_CreateInstance("@mozilla.org/file/local;1"); + if (!profileDir) { + return NS_ERROR_FAILURE; + } +# ifdef XP_WIN + // |profilePath| is encoded in UTF-8 because SQLite always takes UTF-8 file + // paths regardless of the current system code page. + nsresult rv = profileDir->InitWithPath(NS_ConvertUTF8toUTF16(profilePath)); +# else + nsresult rv = profileDir->InitWithNativePath(profilePath); +# endif + if (NS_FAILED(rv)) { + return rv; + } + const char* moduleDBFilename = "pkcs11.txt"; + nsAutoCString destModuleDBFilename(moduleDBFilename); + destModuleDBFilename.Append(".fips"); + nsCOMPtr<nsIFile> dbFile; + rv = profileDir->Clone(getter_AddRefs(dbFile)); + if (NS_FAILED(rv) || !dbFile) { + return NS_ERROR_FAILURE; + } + rv = dbFile->AppendNative(nsAutoCString(moduleDBFilename)); + if (NS_FAILED(rv)) { + return rv; + } + // If the PKCS#11 module DB doesn't exist, renaming it won't help. + bool exists; + rv = dbFile->Exists(&exists); + if (NS_FAILED(rv)) { + return rv; + } + // This is strange, but not a catastrophic failure. + if (!exists) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, + ("%s doesn't exist?", moduleDBFilename)); + return NS_OK; + } + nsCOMPtr<nsIFile> destDBFile; + rv = profileDir->Clone(getter_AddRefs(destDBFile)); + if (NS_FAILED(rv) || !destDBFile) { + return NS_ERROR_FAILURE; + } + rv = destDBFile->AppendNative(destModuleDBFilename); + if (NS_FAILED(rv)) { + return rv; + } + // If the destination exists, presumably we've already tried this. Doing it + // again won't help. + rv = destDBFile->Exists(&exists); + if (NS_FAILED(rv)) { + return rv; + } + // Unfortunate, but not a catastrophic failure. + if (exists) { + MOZ_LOG( + gPIPNSSLog, LogLevel::Debug, + ("%s already exists - not overwriting", destModuleDBFilename.get())); + return NS_OK; + } + // Now do the actual move. + // This may fail on, e.g., a read-only file system. This would be unfortunate, + // but again it isn't catastropic and we would want to fall back to + // initializing NSS in no-DB mode. + Unused << dbFile->MoveToNative(profileDir, destModuleDBFilename); + return NS_OK; +} +#endif // ifndef ANDROID + +// Given a profile directory, attempt to initialize NSS. If nocertdb is true, +// (or if we don't have a profile directory) simply initialize NSS in no DB mode +// and return. Otherwise, first attempt to initialize in read/write mode, and +// then read-only mode if that fails. If both attempts fail, we may be failing +// to initialize an NSS DB collection that has FIPS mode enabled. Attempt to +// ascertain if this is the case, and if so, rename the offending PKCS#11 module +// DB so we can (hopefully) initialize NSS in read-write mode. Again attempt +// read-only mode if that fails. Finally, fall back to no DB mode. On Android +// we can skip the FIPS workaround since it was never possible to enable FIPS +// there anyway. +// |profilePath| is encoded in UTF-8. +static nsresult InitializeNSSWithFallbacks(const nsACString& profilePath, + bool nocertdb, bool safeMode) { + if (nocertdb || profilePath.IsEmpty()) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, + ("nocertdb mode or empty profile path -> NSS_NoDB_Init")); + SECStatus srv = NSS_NoDB_Init(nullptr); +#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED + if (srv != SECSuccess) { + MOZ_CRASH_UNSAFE_PRINTF("InitializeNSSWithFallbacks failed: %d", + PR_GetError()); + } +#endif + return srv == SECSuccess ? NS_OK : NS_ERROR_FAILURE; + } + + // Try read/write mode. If we're in safeMode, we won't load PKCS#11 modules. +#ifndef ANDROID + PRErrorCode savedPRErrorCode1; +#endif // ifndef ANDROID + PKCS11DBConfig safeModeDBConfig = + safeMode ? PKCS11DBConfig::DoNotLoadModules : PKCS11DBConfig::LoadModules; + SECStatus srv = ::mozilla::psm::InitializeNSS( + profilePath, NSSDBConfig::ReadWrite, safeModeDBConfig); + if (srv == SECSuccess) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("initialized NSS in r/w mode")); + return NS_OK; + } +#ifndef ANDROID + savedPRErrorCode1 = PR_GetError(); + PRErrorCode savedPRErrorCode2; +#endif // ifndef ANDROID + // That failed. Try read-only mode. + srv = ::mozilla::psm::InitializeNSS(profilePath, NSSDBConfig::ReadOnly, + safeModeDBConfig); + if (srv == SECSuccess) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("initialized NSS in r-o mode")); + return NS_OK; + } +#ifndef ANDROID + savedPRErrorCode2 = PR_GetError(); + + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, + ("failed to initialize NSS with codes %d %d", savedPRErrorCode1, + savedPRErrorCode2)); +#endif // ifndef ANDROID + +#ifndef ANDROID + // That failed as well. Maybe we're trying to load a PKCS#11 module DB that is + // in FIPS mode, but we don't support FIPS? Test load NSS without PKCS#11 + // modules. If that succeeds, that's probably what's going on. + if (!safeMode && (savedPRErrorCode1 == SEC_ERROR_LEGACY_DATABASE || + savedPRErrorCode2 == SEC_ERROR_LEGACY_DATABASE || + savedPRErrorCode1 == SEC_ERROR_PKCS11_DEVICE_ERROR || + savedPRErrorCode2 == SEC_ERROR_PKCS11_DEVICE_ERROR)) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("attempting no-module db init")); + // It would make sense to initialize NSS in read-only mode here since this + // is just a test to see if the PKCS#11 module DB being in FIPS mode is the + // problem, but for some reason the combination of read-only and no-moddb + // flags causes NSS initialization to fail, so unfortunately we have to use + // read-write mode. + srv = ::mozilla::psm::InitializeNSS(profilePath, NSSDBConfig::ReadWrite, + PKCS11DBConfig::DoNotLoadModules); + if (srv == SECSuccess) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("FIPS may be the problem")); + // Unload NSS so we can attempt to fix this situation for the user. + srv = NSS_Shutdown(); + if (srv != SECSuccess) { +# ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED + MOZ_CRASH_UNSAFE_PRINTF("InitializeNSSWithFallbacks failed: %d", + PR_GetError()); +# endif + return NS_ERROR_FAILURE; + } + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("trying to rename module db")); + // If this fails non-catastrophically, we'll attempt to initialize NSS + // again in r/w then r-o mode (both of which will fail), and then we'll + // fall back to NSS_NoDB_Init, which is the behavior we want. + nsresult rv = AttemptToRenamePKCS11ModuleDB(profilePath); + if (NS_FAILED(rv)) { +# ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED + // An nsresult is a uint32_t, but at least one of our compilers doesn't + // like this format string unless we include the cast. <shruggie emoji> + MOZ_CRASH_UNSAFE_PRINTF("InitializeNSSWithFallbacks failed: %u", + (uint32_t)rv); +# endif + return rv; + } + srv = ::mozilla::psm::InitializeNSS(profilePath, NSSDBConfig::ReadWrite, + PKCS11DBConfig::LoadModules); + if (srv == SECSuccess) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("initialized in r/w mode")); + return NS_OK; + } + srv = ::mozilla::psm::InitializeNSS(profilePath, NSSDBConfig::ReadOnly, + PKCS11DBConfig::LoadModules); + if (srv == SECSuccess) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("initialized in r-o mode")); + return NS_OK; + } + } + } +#endif + + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("last-resort NSS_NoDB_Init")); + srv = NSS_NoDB_Init(nullptr); +#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED + if (srv != SECSuccess) { + MOZ_CRASH_UNSAFE_PRINTF("InitializeNSSWithFallbacks failed: %d", + PR_GetError()); + } +#endif + return srv == SECSuccess ? NS_OK : NS_ERROR_FAILURE; +} + +#if defined(NIGHTLY_BUILD) && !defined(ANDROID) +// dbType is either "cert9.db" or "key4.db" +void UnmigrateOneCertDB(const nsCOMPtr<nsIFile>& profileDirectory, + const nsACString& dbType) { + nsCOMPtr<nsIFile> dbFile; + nsresult rv = profileDirectory->Clone(getter_AddRefs(dbFile)); + if (NS_FAILED(rv)) { + return; + } + rv = dbFile->AppendNative(dbType); + if (NS_FAILED(rv)) { + return; + } + bool exists; + rv = dbFile->Exists(&exists); + if (NS_FAILED(rv)) { + return; + } + // If the unprefixed DB already exists, don't overwrite it. + if (exists) { + return; + } + nsCOMPtr<nsIFile> prefixedDBFile; + rv = profileDirectory->Clone(getter_AddRefs(prefixedDBFile)); + if (NS_FAILED(rv)) { + return; + } + nsAutoCString prefixedDBName("gecko-no-share-"); + prefixedDBName.Append(dbType); + rv = prefixedDBFile->AppendNative(prefixedDBName); + if (NS_FAILED(rv)) { + return; + } + Unused << prefixedDBFile->MoveToNative(nullptr, dbType); +} + +void UnmigrateFromPrefixedCertDBs() { + nsCOMPtr<nsIFile> profileDirectory; + nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, + getter_AddRefs(profileDirectory)); + if (NS_FAILED(rv)) { + return; + } + UnmigrateOneCertDB(profileDirectory, "cert9.db"_ns); + UnmigrateOneCertDB(profileDirectory, "key4.db"_ns); +} +#endif // defined(NIGHTLY_BUILD) && !defined(ANDROID) + +nsresult nsNSSComponent::InitializeNSS() { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsNSSComponent::InitializeNSS\n")); + AUTO_PROFILER_LABEL("nsNSSComponent::InitializeNSS", OTHER); + AUTO_PROFILER_TRACING_MARKER("NSS", "nsNSSComponent::InitializeNSS", OTHER); + + static_assert( + nsINSSErrorsService::NSS_SEC_ERROR_BASE == SEC_ERROR_BASE && + nsINSSErrorsService::NSS_SEC_ERROR_LIMIT == SEC_ERROR_LIMIT && + nsINSSErrorsService::NSS_SSL_ERROR_BASE == SSL_ERROR_BASE && + nsINSSErrorsService::NSS_SSL_ERROR_LIMIT == SSL_ERROR_LIMIT, + "You must update the values in nsINSSErrorsService.idl"); + + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("NSS Initialization beginning\n")); + + nsAutoCString profileStr; + nsresult rv = GetNSSProfilePath(profileStr); + MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv)); + if (NS_FAILED(rv)) { + return NS_ERROR_NOT_AVAILABLE; + } + +#if defined(NIGHTLY_BUILD) && !defined(ANDROID) + if (!profileStr.IsEmpty()) { + UnmigrateFromPrefixedCertDBs(); + } +#endif + +#if defined(XP_WIN) || (defined(XP_LINUX) && !defined(ANDROID)) + SetNSSDatabaseCacheModeAsAppropriate(); +#endif + + bool nocertdb = StaticPrefs::security_nocertdb_AtStartup(); + bool inSafeMode = true; + nsCOMPtr<nsIXULRuntime> runtime(do_GetService("@mozilla.org/xre/runtime;1")); + // There might not be an nsIXULRuntime in embedded situations. This will + // default to assuming we are in safe mode (as a result, no external PKCS11 + // modules will be loaded). + if (runtime) { + rv = runtime->GetInSafeMode(&inSafeMode); + MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv)); + if (NS_FAILED(rv)) { + return rv; + } + } + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("inSafeMode: %u\n", inSafeMode)); + + rv = InitializeNSSWithFallbacks(profileStr, nocertdb, inSafeMode); + MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv)); + if (NS_FAILED(rv)) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("failed to initialize NSS")); + return rv; + } + + PK11_SetPasswordFunc(PK11PasswordPrompt); + + // Register an observer so we can inform NSS when these prefs change + Preferences::AddStrongObserver(this, "security."); + + rv = CommonInit(); + + MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv)); + if (NS_FAILED(rv)) { + return NS_ERROR_UNEXPECTED; + } + + nsCOMPtr<nsICertOverrideService> certOverrideService( + do_GetService(NS_CERTOVERRIDE_CONTRACTID)); + nsCOMPtr<nsIClientAuthRememberService> clientAuthRememberService( + do_GetService(NS_CLIENTAUTHREMEMBERSERVICE_CONTRACTID)); + nsCOMPtr<nsISiteSecurityService> siteSecurityService( + do_GetService(NS_SSSERVICE_CONTRACTID)); + nsCOMPtr<nsICertStorage> certStorage(do_GetService(NS_CERT_STORAGE_CID)); + + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("NSS Initialization done\n")); + + { + MutexAutoLock lock(mMutex); + + // ensure we have initial values for various root hashes +#ifdef DEBUG + mTestBuiltInRootHash.Truncate(); + Preferences::GetCString("security.test.built_in_root_hash", + mTestBuiltInRootHash); +#endif + mMitmCanaryIssuer.Truncate(); + Preferences::GetString("security.pki.mitm_canary_issuer", + mMitmCanaryIssuer); + mMitmDetecionEnabled = + Preferences::GetBool("security.pki.mitm_canary_issuer.enabled", true); + + // Set dynamic options from prefs. + setValidationOptions(true, lock); + + bool importEnterpriseRoots = + StaticPrefs::security_enterprise_roots_enabled(); + uint32_t familySafetyMode = StaticPrefs::security_family_safety_mode(); + Vector<nsCString> possibleLoadableRootsLocations; + rv = ListPossibleLoadableRootsLocations(possibleLoadableRootsLocations); + MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv)); + if (NS_FAILED(rv)) { + return rv; + } + + bool loadOSClientCertsModule = + StaticPrefs::security_osclientcerts_autoload(); + Maybe<nsCString> maybeOSClientCertsModuleLocation; + if (loadOSClientCertsModule) { + nsAutoCString libraryDir; + if (NS_SUCCEEDED(GetDirectoryPath(NS_GRE_BIN_DIR, libraryDir))) { + maybeOSClientCertsModuleLocation.emplace(libraryDir); + } + } + RefPtr<LoadLoadableCertsTask> loadLoadableCertsTask( + new LoadLoadableCertsTask(this, importEnterpriseRoots, familySafetyMode, + std::move(possibleLoadableRootsLocations), + std::move(maybeOSClientCertsModuleLocation))); + rv = loadLoadableCertsTask->Dispatch(); + MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv)); + if (NS_FAILED(rv)) { + return rv; + } + + return NS_OK; + } +} + +void nsNSSComponent::PrepareForShutdown() { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsNSSComponent::PrepareForShutdown")); + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + + PK11_SetPasswordFunc((PK11PasswordFunc) nullptr); + + Preferences::RemoveObserver(this, "security."); + + if (mIntermediatePreloadingHealerTimer) { + mIntermediatePreloadingHealerTimer->Cancel(); + mIntermediatePreloadingHealerTimer = nullptr; + } + + // Release the default CertVerifier. This will cause any held NSS resources + // to be released. + MutexAutoLock lock(mMutex); + mDefaultCertVerifier = nullptr; + // We don't actually shut down NSS - XPCOM does, after all threads have been + // joined and the component manager has been shut down (and so there shouldn't + // be any XPCOM objects holding NSS resources). +} + +// The aim of the intermediate preloading healer is to remove intermediates +// that were previously cached by PSM in the NSS certdb that are now preloaded +// in cert_storage. When cached by PSM, these certificates will have no +// particular trust set - they are intended to inherit their trust. If, upon +// examination, these certificates do have trust bits set that affect +// certificate validation, they must have been modified by the user, so we want +// to leave them alone. +bool CertHasDefaultTrust(CERTCertificate* cert) { + CERTCertTrust trust; + if (CERT_GetCertTrust(cert, &trust) != SECSuccess) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("CERT_GetCertTrust failed")); + return false; + } + // This is the active distrust test for CA certificates (this is expected to + // be an intermediate). + if ((trust.sslFlags & (CERTDB_TRUSTED_CA | CERTDB_TERMINAL_RECORD)) == + CERTDB_TERMINAL_RECORD) { + return false; + } + // This is the trust anchor test. + if (trust.sslFlags & CERTDB_TRUSTED_CA) { + return false; + } + // This is the active distrust test for CA certificates (this is expected to + // be an intermediate). + if ((trust.emailFlags & (CERTDB_TRUSTED_CA | CERTDB_TERMINAL_RECORD)) == + CERTDB_TERMINAL_RECORD) { + return false; + } + // This is the trust anchor test. + if (trust.emailFlags & CERTDB_TRUSTED_CA) { + return false; + } + return true; +} + +void IntermediatePreloadingHealerCallback(nsITimer*, void*) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, + ("IntermediatePreloadingHealerCallback")); + + if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, + ("Exiting healer due to app shutdown")); + return; + } + + // Get the slot corresponding to the NSS certdb. + UniquePK11SlotInfo softokenSlot(PK11_GetInternalKeySlot()); + if (!softokenSlot) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("PK11_GetInternalKeySlot failed")); + return; + } + // List the certificates in the NSS certdb. + UniqueCERTCertList softokenCertificates( + PK11_ListCertsInSlot(softokenSlot.get())); + if (!softokenCertificates) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("PK11_ListCertsInSlot failed")); + return; + } + nsCOMPtr<nsICertStorage> certStorage(do_GetService(NS_CERT_STORAGE_CID)); + if (!certStorage) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("couldn't get cert_storage")); + return; + } + Vector<UniqueCERTCertificate> certsToDelete; + // For each certificate, look it up in cert_storage. If there's a match, this + // is a preloaded intermediate. + for (CERTCertListNode* n = CERT_LIST_HEAD(softokenCertificates); + !CERT_LIST_END(n, softokenCertificates); n = CERT_LIST_NEXT(n)) { + if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, + ("Exiting healer due to app shutdown")); + return; + } + + nsTArray<uint8_t> subject; + subject.AppendElements(n->cert->derSubject.data, n->cert->derSubject.len); + nsTArray<nsTArray<uint8_t>> certs; + nsresult rv = certStorage->FindCertsBySubject(subject, certs); + if (NS_FAILED(rv)) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("FindCertsBySubject failed")); + break; + } + for (const auto& encodedCert : certs) { + if (encodedCert.Length() != n->cert->derCert.len) { + continue; + } + if (memcmp(encodedCert.Elements(), n->cert->derCert.data, + encodedCert.Length()) != 0) { + continue; + } + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, + ("found preloaded intermediate in certdb")); + if (!CertHasDefaultTrust(n->cert)) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, + ("certificate doesn't have default trust - skipping")); + continue; + } + UniqueCERTCertificate certCopy(CERT_DupCertificate(n->cert)); + if (!certCopy) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("CERT_DupCertificate failed")); + continue; + } + // Note that we want to remove this certificate from the NSS certdb + // because it also exists in preloaded intermediate storage and is thus + // superfluous. + if (!certsToDelete.append(std::move(certCopy))) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, + ("append failed - out of memory?")); + return; + } + break; + } + // Only delete 20 at a time. + if (certsToDelete.length() >= 20) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, + ("found limit of 20 preloaded intermediates in certdb")); + break; + } + } + for (const auto& certToDelete : certsToDelete) { + if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, + ("Exiting healer due to app shutdown")); + return; + } + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, + ("attempting to delete preloaded intermediate '%s'", + certToDelete->subjectName)); + if (SEC_DeletePermCertificate(certToDelete.get()) != SECSuccess) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, + ("SEC_DeletePermCertificate failed")); + } + } + + // This is for tests - notify that this ran. + nsCOMPtr<nsIRunnable> runnable(NS_NewRunnableFunction( + "IntermediatePreloadingHealerCallbackDone", []() -> void { + nsCOMPtr<nsIObserverService> observerService = + mozilla::services::GetObserverService(); + if (observerService) { + observerService->NotifyObservers( + nullptr, "psm:intermediate-preloading-healer-ran", nullptr); + } + })); + Unused << NS_DispatchToMainThread(runnable.forget()); +} + +nsresult nsNSSComponent::Init() { + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + if (!NS_IsMainThread()) { + return NS_ERROR_NOT_SAME_THREAD; + } + + MOZ_ASSERT(XRE_IsParentProcess()); + if (!XRE_IsParentProcess()) { + return NS_ERROR_NOT_AVAILABLE; + } + + Telemetry::AutoScalarTimer<Telemetry::ScalarID::NETWORKING_NSS_INITIALIZATION> + timer; + uint32_t zero = 0; // Directly using 0 makes the call to ScalarSet ambiguous. + Telemetry::ScalarSet(Telemetry::ScalarID::SECURITY_CLIENT_AUTH_CERT_USAGE, + u"requested"_ns, zero); + Telemetry::ScalarSet(Telemetry::ScalarID::SECURITY_CLIENT_AUTH_CERT_USAGE, + u"sent"_ns, zero); + + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("Beginning NSS initialization\n")); + + nsresult rv = InitializeNSS(); + if (NS_FAILED(rv)) { + MOZ_LOG(gPIPNSSLog, LogLevel::Error, + ("nsNSSComponent::InitializeNSS() failed\n")); + return rv; + } + + rv = RegisterObservers(); + if (NS_FAILED(rv)) { + return rv; + } + + rv = MaybeEnableIntermediatePreloadingHealer(); + if (NS_FAILED(rv)) { + return rv; + } + + return NS_OK; +} + +nsresult nsNSSComponent::MaybeEnableIntermediatePreloadingHealer() { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, + ("nsNSSComponent::MaybeEnableIntermediatePreloadingHealer")); + MOZ_ASSERT(NS_IsMainThread()); + if (!NS_IsMainThread()) { + return NS_ERROR_NOT_SAME_THREAD; + } + + if (mIntermediatePreloadingHealerTimer) { + mIntermediatePreloadingHealerTimer->Cancel(); + mIntermediatePreloadingHealerTimer = nullptr; + } + + if (!StaticPrefs::security_intermediate_preloading_healer_enabled()) { + return NS_OK; + } + + if (!mIntermediatePreloadingHealerTaskQueue) { + nsresult rv = NS_CreateBackgroundTaskQueue( + "IntermediatePreloadingHealer", + getter_AddRefs(mIntermediatePreloadingHealerTaskQueue)); + if (NS_FAILED(rv)) { + MOZ_LOG(gPIPNSSLog, LogLevel::Error, + ("NS_CreateBackgroundTaskQueue failed")); + return rv; + } + } + uint32_t timerDelayMS = + StaticPrefs::security_intermediate_preloading_healer_timer_interval_ms(); + nsresult rv = NS_NewTimerWithFuncCallback( + getter_AddRefs(mIntermediatePreloadingHealerTimer), + IntermediatePreloadingHealerCallback, nullptr, timerDelayMS, + nsITimer::TYPE_REPEATING_SLACK_LOW_PRIORITY, + "IntermediatePreloadingHealer", mIntermediatePreloadingHealerTaskQueue); + if (NS_FAILED(rv)) { + MOZ_LOG(gPIPNSSLog, LogLevel::Error, + ("NS_NewTimerWithFuncCallback failed")); + return rv; + } + return NS_OK; +} + +// nsISupports Implementation for the class +NS_IMPL_ISUPPORTS(nsNSSComponent, nsINSSComponent, nsIObserver) + +static const char* const PROFILE_BEFORE_CHANGE_TOPIC = "profile-before-change"; + +NS_IMETHODIMP +nsNSSComponent::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* someData) { + // In some tests, we don't receive a "profile-before-change" topic. However, + // we still have to shut down before the storage service shuts down, because + // closing the sql-backed softoken requires sqlite still be available. Thus, + // we observe "xpcom-shutdown" just in case. + if (nsCRT::strcmp(aTopic, PROFILE_BEFORE_CHANGE_TOPIC) == 0 || + nsCRT::strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, + ("receiving profile change or XPCOM shutdown notification")); + PrepareForShutdown(); + } else if (nsCRT::strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID) == 0) { + bool clearSessionCache = true; + NS_ConvertUTF16toUTF8 prefName(someData); + + if (HandleTLSPrefChange(prefName)) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("HandleTLSPrefChange done")); + } else if (prefName.EqualsLiteral("security.OCSP.enabled") || + prefName.EqualsLiteral("security.OCSP.require") || + prefName.EqualsLiteral( + "security.pki.cert_short_lifetime_in_days") || + prefName.EqualsLiteral("security.ssl.enable_ocsp_stapling") || + prefName.EqualsLiteral("security.ssl.enable_ocsp_must_staple") || + prefName.EqualsLiteral( + "security.pki.certificate_transparency.mode") || + prefName.EqualsLiteral("security.pki.netscape_step_up_policy") || + prefName.EqualsLiteral( + "security.OCSP.timeoutMilliseconds.soft") || + prefName.EqualsLiteral( + "security.OCSP.timeoutMilliseconds.hard") || + prefName.EqualsLiteral("security.pki.crlite_mode")) { + MutexAutoLock lock(mMutex); + setValidationOptions(false, lock); +#ifdef DEBUG + } else if (prefName.EqualsLiteral("security.test.built_in_root_hash")) { + MutexAutoLock lock(mMutex); + mTestBuiltInRootHash.Truncate(); + Preferences::GetCString("security.test.built_in_root_hash", + mTestBuiltInRootHash); +#endif // DEBUG + } else if (prefName.Equals("security.enterprise_roots.enabled") || + prefName.Equals("security.family_safety.mode")) { + UnloadEnterpriseRoots(); + MaybeImportEnterpriseRoots(); + } else if (prefName.Equals("security.osclientcerts.autoload")) { + bool loadOSClientCertsModule = + StaticPrefs::security_osclientcerts_autoload(); + AsyncLoadOrUnloadOSClientCertsModule(loadOSClientCertsModule); + } else if (prefName.EqualsLiteral("security.pki.mitm_canary_issuer")) { + MutexAutoLock lock(mMutex); + mMitmCanaryIssuer.Truncate(); + Preferences::GetString("security.pki.mitm_canary_issuer", + mMitmCanaryIssuer); + } else if (prefName.EqualsLiteral( + "security.pki.mitm_canary_issuer.enabled")) { + MutexAutoLock lock(mMutex); + mMitmDetecionEnabled = + Preferences::GetBool("security.pki.mitm_canary_issuer.enabled", true); + } else { + clearSessionCache = false; + } + if (clearSessionCache) { + ClearSSLExternalAndInternalSessionCache(); + } + + // Preferences that don't affect certificate verification. + if (prefName.Equals("security.intermediate_preloading_healer.enabled") || + prefName.Equals( + "security.intermediate_preloading_healer.timer_interval_ms")) { + MaybeEnableIntermediatePreloadingHealer(); + } + } + + return NS_OK; +} + +/*static*/ +nsresult nsNSSComponent::GetNewPrompter(nsIPrompt** result) { + NS_ENSURE_ARG_POINTER(result); + *result = nullptr; + + if (!NS_IsMainThread()) { + NS_ERROR("nsSDRContext::GetNewPrompter called off the main thread"); + return NS_ERROR_NOT_SAME_THREAD; + } + + nsresult rv; + nsCOMPtr<nsIWindowWatcher> wwatch( + do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = wwatch->GetNewPrompter(0, result); + NS_ENSURE_SUCCESS(rv, rv); + + return rv; +} + +nsresult nsNSSComponent::LogoutAuthenticatedPK11() { + nsCOMPtr<nsICertOverrideService> icos = + do_GetService("@mozilla.org/security/certoverride;1"); + if (icos) { + icos->ClearValidityOverride("all:temporary-certificates"_ns, 0, + OriginAttributes()); + } + + ClearSSLExternalAndInternalSessionCache(); + + nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService(); + if (os) { + os->NotifyObservers(nullptr, "net:cancel-all-connections", nullptr); + } + + return NS_OK; +} + +nsresult nsNSSComponent::RegisterObservers() { + nsCOMPtr<nsIObserverService> observerService( + do_GetService("@mozilla.org/observer-service;1")); + if (!observerService) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, + ("nsNSSComponent: couldn't get observer service\n")); + return NS_ERROR_FAILURE; + } + + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsNSSComponent: adding observers\n")); + // Using false for the ownsweak parameter means the observer service will + // keep a strong reference to this component. As a result, this will live at + // least as long as the observer service. + observerService->AddObserver(this, PROFILE_BEFORE_CHANGE_TOPIC, false); + observerService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false); + + return NS_OK; +} + +nsresult DoesCertMatchFingerprint(const nsTArray<uint8_t>& cert, + const nsCString& fingerprint, bool& result) { + result = false; + + if (cert.Length() > std::numeric_limits<uint32_t>::max()) { + return NS_ERROR_INVALID_ARG; + } + nsTArray<uint8_t> digestArray; + nsresult rv = Digest::DigestBuf(SEC_OID_SHA256, cert.Elements(), + cert.Length(), digestArray); + if (NS_FAILED(rv)) { + return rv; + } + SECItem digestItem = {siBuffer, digestArray.Elements(), + static_cast<unsigned int>(digestArray.Length())}; + UniquePORTString certFingerprint( + CERT_Hexify(&digestItem, true /* use colon delimiters */)); + if (!certFingerprint) { + return NS_ERROR_FAILURE; + } + + result = fingerprint.Equals(certFingerprint.get()); + return NS_OK; +} + +NS_IMETHODIMP +nsNSSComponent::IsCertTestBuiltInRoot(const nsTArray<uint8_t>& cert, + bool* result) { + NS_ENSURE_ARG_POINTER(result); + *result = false; + +#ifdef DEBUG + MutexAutoLock lock(mMutex); + nsresult rv = DoesCertMatchFingerprint(cert, mTestBuiltInRootHash, *result); + if (NS_FAILED(rv)) { + return rv; + } +#endif // DEBUG + + return NS_OK; +} + +NS_IMETHODIMP +nsNSSComponent::IssuerMatchesMitmCanary(const char* aCertIssuer) { + MutexAutoLock lock(mMutex); + if (mMitmDetecionEnabled && !mMitmCanaryIssuer.IsEmpty()) { + nsString certIssuer = NS_ConvertUTF8toUTF16(aCertIssuer); + if (mMitmCanaryIssuer.Equals(certIssuer)) { + return NS_OK; + } + } + + return NS_ERROR_FAILURE; +} + +SharedCertVerifier::~SharedCertVerifier() = default; + +NS_IMETHODIMP +nsNSSComponent::GetDefaultCertVerifier(SharedCertVerifier** result) { + MutexAutoLock lock(mMutex); + NS_ENSURE_ARG_POINTER(result); + RefPtr<SharedCertVerifier> certVerifier(mDefaultCertVerifier); + certVerifier.forget(result); + return NS_OK; +} + +// static +void nsNSSComponent::DoClearSSLExternalAndInternalSessionCache() { + SSL_ClearSessionCache(); + mozilla::net::SSLTokensCache::Clear(); +} + +NS_IMETHODIMP +nsNSSComponent::ClearSSLExternalAndInternalSessionCache() { + MOZ_ASSERT(XRE_IsParentProcess()); + if (!XRE_IsParentProcess()) { + return NS_ERROR_NOT_AVAILABLE; + } + + if (mozilla::net::nsIOService::UseSocketProcess()) { + if (mozilla::net::gIOService) { + mozilla::net::gIOService->CallOrWaitForSocketProcess([]() { + Unused << mozilla::net::SocketProcessParent::GetSingleton() + ->SendClearSessionCache(); + }); + } + } + DoClearSSLExternalAndInternalSessionCache(); + return NS_OK; +} + +NS_IMETHODIMP +nsNSSComponent::AsyncClearSSLExternalAndInternalSessionCache( + JSContext* aCx, ::mozilla::dom::Promise** aPromise) { + MOZ_ASSERT(XRE_IsParentProcess()); + if (!XRE_IsParentProcess()) { + return NS_ERROR_NOT_AVAILABLE; + } + + nsIGlobalObject* globalObject = xpc::CurrentNativeGlobal(aCx); + if (NS_WARN_IF(!globalObject)) { + return NS_ERROR_FAILURE; + } + + ErrorResult result; + RefPtr<mozilla::dom::Promise> promise = + mozilla::dom::Promise::Create(globalObject, result); + if (NS_WARN_IF(result.Failed())) { + return result.StealNSResult(); + } + + if (mozilla::net::nsIOService::UseSocketProcess() && + mozilla::net::gIOService) { + mozilla::net::gIOService->CallOrWaitForSocketProcess( + [p = RefPtr{promise}]() { + Unused << mozilla::net::SocketProcessParent::GetSingleton() + ->SendClearSessionCache() + ->Then( + GetCurrentSerialEventTarget(), __func__, + [promise = RefPtr{p}] { + promise->MaybeResolveWithUndefined(); + }, + [promise = RefPtr{p}] { + promise->MaybeReject(NS_ERROR_UNEXPECTED); + }); + }); + } else { + promise->MaybeResolveWithUndefined(); + } + DoClearSSLExternalAndInternalSessionCache(); + promise.forget(aPromise); + return NS_OK; +} + +namespace mozilla { +namespace psm { + +already_AddRefed<SharedCertVerifier> GetDefaultCertVerifier() { + static NS_DEFINE_CID(kNSSComponentCID, NS_NSSCOMPONENT_CID); + + nsCOMPtr<nsINSSComponent> nssComponent(do_GetService(kNSSComponentCID)); + if (!nssComponent) { + return nullptr; + } + nsresult rv = nssComponent->BlockUntilLoadableCertsLoaded(); + if (NS_FAILED(rv)) { + return nullptr; + } + RefPtr<SharedCertVerifier> result; + rv = nssComponent->GetDefaultCertVerifier(getter_AddRefs(result)); + if (NS_FAILED(rv)) { + return nullptr; + } + return result.forget(); +} + +// Helper for FindClientCertificatesWithPrivateKeys. Copies all +// CERTCertificates from `from` to `to`. +static inline void CopyCertificatesTo(UniqueCERTCertList& from, + UniqueCERTCertList& to) { + MOZ_ASSERT(from); + MOZ_ASSERT(to); + for (CERTCertListNode* n = CERT_LIST_HEAD(from.get()); + !CERT_LIST_END(n, from.get()); n = CERT_LIST_NEXT(n)) { + UniqueCERTCertificate cert(CERT_DupCertificate(n->cert)); + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, + (" provisionally adding '%s'", n->cert->subjectName)); + if (CERT_AddCertToListTail(to.get(), cert.get()) == SECSuccess) { + Unused << cert.release(); + } + } +} + +// Lists all private keys on all modules and returns a list of any corresponding +// client certificates. Returns null if no such certificates can be found. Also +// returns null if an error is encountered, because this is called as part of +// the client auth data callback, and NSS ignores any errors returned by the +// callback. +UniqueCERTCertList FindClientCertificatesWithPrivateKeys() { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, + ("FindClientCertificatesWithPrivateKeys")); + + BlockUntilLoadableCertsLoaded(); + + UniqueCERTCertList certsWithPrivateKeys(CERT_NewCertList()); + if (!certsWithPrivateKeys) { + return nullptr; + } + + UniquePK11SlotInfo internalSlot(PK11_GetInternalKeySlot()); + + AutoSECMODListReadLock secmodLock; + SECMODModuleList* list = SECMOD_GetDefaultModuleList(); + while (list) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, + (" module '%s'", list->module->commonName)); + for (int i = 0; i < list->module->slotCount; i++) { + PK11SlotInfo* slot = list->module->slots[i]; + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, + (" slot '%s'", PK11_GetSlotName(slot))); + // If this is the internal certificate/key slot or the slot on the + // builtin roots module, there may be many more certificates than private + // keys, so search by private keys (PK11_HasRootCerts will be true if the + // slot contains an object with the vendor-specific CK_CLASS + // CKO_NSS_BUILTIN_ROOT_LIST, which should only be the case for the NSS + // builtin roots module). + if (internalSlot.get() == slot || PK11_HasRootCerts(slot)) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, + (" (looking at internal/builtin slot)")); + if (PK11_Authenticate(slot, true, nullptr) != SECSuccess) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, (" (couldn't authenticate)")); + continue; + } + UniqueSECKEYPrivateKeyList privateKeys( + PK11_ListPrivKeysInSlot(slot, nullptr, nullptr)); + if (!privateKeys) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, (" (no private keys)")); + continue; + } + for (SECKEYPrivateKeyListNode* node = PRIVKEY_LIST_HEAD(privateKeys); + !PRIVKEY_LIST_END(node, privateKeys); + node = PRIVKEY_LIST_NEXT(node)) { + UniqueCERTCertList certs(PK11_GetCertsMatchingPrivateKey(node->key)); + if (!certs) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, + (" PK11_GetCertsMatchingPrivateKey encountered an " + "error ")); + continue; + } + if (CERT_LIST_EMPTY(certs)) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, (" (no certs for key)")); + continue; + } + CopyCertificatesTo(certs, certsWithPrivateKeys); + } + } else { + // ... otherwise, optimistically assume that searching by certificate + // won't take too much time. Since "friendly" slots expose certificates + // without needing to be authenticated to, this results in fewer PIN + // dialogs shown to the user. + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, + (" (looking at non-internal slot)")); + + if (!PK11_IsPresent(slot)) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, (" (not present)")); + continue; + } + // If this isn't a "friendly" slot, authenticate to expose certificates. + if (!PK11_IsFriendly(slot) && + PK11_Authenticate(slot, true, nullptr) != SECSuccess) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, (" (couldn't authenticate)")); + continue; + } + UniqueCERTCertList certsInSlot(PK11_ListCertsInSlot(slot)); + if (!certsInSlot) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, + (" (couldn't list certs in slot)")); + continue; + } + // When NSS decodes a certificate, if that certificate has a + // corresponding private key (or public key, if the slot it's on hasn't + // been logged into), it notes it as a "user cert". + if (CERT_FilterCertListForUserCerts(certsInSlot.get()) != SECSuccess) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, + (" (couldn't filter certs)")); + continue; + } + CopyCertificatesTo(certsInSlot, certsWithPrivateKeys); + } + } + list = list->next; + } + + if (CERT_FilterCertListByUsage(certsWithPrivateKeys.get(), certUsageSSLClient, + false) != SECSuccess) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, + (" CERT_FilterCertListByUsage encountered an error - returning")); + return nullptr; + } + + if (MOZ_UNLIKELY(MOZ_LOG_TEST(gPIPNSSLog, LogLevel::Debug))) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, (" returning:")); + for (CERTCertListNode* n = CERT_LIST_HEAD(certsWithPrivateKeys); + !CERT_LIST_END(n, certsWithPrivateKeys); n = CERT_LIST_NEXT(n)) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, (" %s", n->cert->subjectName)); + } + } + + if (CERT_LIST_EMPTY(certsWithPrivateKeys)) { + return nullptr; + } + + return certsWithPrivateKeys; +} + +} // namespace psm +} // namespace mozilla + +NS_IMPL_ISUPPORTS(PipUIContext, nsIInterfaceRequestor) + +PipUIContext::PipUIContext() = default; + +PipUIContext::~PipUIContext() = default; + +NS_IMETHODIMP +PipUIContext::GetInterface(const nsIID& uuid, void** result) { + NS_ENSURE_ARG_POINTER(result); + *result = nullptr; + + if (!NS_IsMainThread()) { + NS_ERROR("PipUIContext::GetInterface called off the main thread"); + return NS_ERROR_NOT_SAME_THREAD; + } + + if (!uuid.Equals(NS_GET_IID(nsIPrompt))) return NS_ERROR_NO_INTERFACE; + + nsIPrompt* prompt = nullptr; + nsresult rv = nsNSSComponent::GetNewPrompter(&prompt); + *result = prompt; + return rv; +} + +nsresult getNSSDialogs(void** _result, REFNSIID aIID, const char* contract) { + if (!NS_IsMainThread()) { + NS_ERROR("getNSSDialogs called off the main thread"); + return NS_ERROR_NOT_SAME_THREAD; + } + + nsresult rv; + + nsCOMPtr<nsISupports> svc = do_GetService(contract, &rv); + if (NS_FAILED(rv)) { + return rv; + } + + rv = svc->QueryInterface(aIID, _result); + + return rv; +} + +nsresult setPassword(PK11SlotInfo* slot, nsIInterfaceRequestor* ctx) { + MOZ_ASSERT(slot); + MOZ_ASSERT(ctx); + NS_ENSURE_ARG_POINTER(slot); + NS_ENSURE_ARG_POINTER(ctx); + + if (PK11_NeedUserInit(slot)) { + nsCOMPtr<nsITokenPasswordDialogs> dialogs; + nsresult rv = getNSSDialogs(getter_AddRefs(dialogs), + NS_GET_IID(nsITokenPasswordDialogs), + NS_TOKENPASSWORDSDIALOG_CONTRACTID); + if (NS_FAILED(rv)) { + return rv; + } + + bool canceled; + nsCOMPtr<nsIPK11Token> token = new nsPK11Token(slot); + rv = dialogs->SetPassword(ctx, token, &canceled); + if (NS_FAILED(rv)) { + return rv; + } + + if (canceled) { + return NS_ERROR_NOT_AVAILABLE; + } + } + + return NS_OK; +} + +static PRBool ConvertBetweenUCS2andASCII(PRBool toUnicode, unsigned char* inBuf, + unsigned int inBufLen, + unsigned char* outBuf, + unsigned int maxOutBufLen, + unsigned int* outBufLen, + PRBool swapBytes) { + std::unique_ptr<unsigned char[]> inBufDup(new unsigned char[inBufLen]); + if (!inBufDup) { + return PR_FALSE; + } + std::memcpy(inBufDup.get(), inBuf, inBufLen * sizeof(unsigned char)); + + // If converting Unicode to ASCII, swap bytes before conversion as neccessary. + if (!toUnicode && swapBytes) { + if (inBufLen % 2 != 0) { + return PR_FALSE; + } + mozilla::NativeEndian::swapFromLittleEndianInPlace( + reinterpret_cast<char16_t*>(inBufDup.get()), inBufLen / 2); + } + return PORT_UCS2_UTF8Conversion(toUnicode, inBufDup.get(), inBufLen, outBuf, + maxOutBufLen, outBufLen); +} + +namespace mozilla { +namespace psm { + +nsresult InitializeCipherSuite() { + MOZ_ASSERT(NS_IsMainThread(), + "InitializeCipherSuite() can only be accessed on the main thread"); + + if (NSS_SetDomesticPolicy() != SECSuccess) { + return NS_ERROR_FAILURE; + } + + // Disable any ciphers that NSS might have enabled by default + for (uint16_t i = 0; i < SSL_NumImplementedCiphers; ++i) { + uint16_t cipher_id = SSL_ImplementedCiphers[i]; + SSL_CipherPrefSetDefault(cipher_id, false); + } + + // Now only set SSL/TLS ciphers we knew about at compile time + for (const auto& cipherPref : sCipherPrefs) { + SSL_CipherPrefSetDefault(cipherPref.id, cipherPref.prefGetter()); + } + + SetDeprecatedTLS1CipherPrefs(); + + // Enable ciphers for PKCS#12 + SEC_PKCS12EnableCipher(PKCS12_RC4_40, 1); + SEC_PKCS12EnableCipher(PKCS12_RC4_128, 1); + SEC_PKCS12EnableCipher(PKCS12_RC2_CBC_40, 1); + SEC_PKCS12EnableCipher(PKCS12_RC2_CBC_128, 1); + SEC_PKCS12EnableCipher(PKCS12_DES_56, 1); + SEC_PKCS12EnableCipher(PKCS12_DES_EDE3_168, 1); + SEC_PKCS12EnableCipher(PKCS12_AES_CBC_128, 1); + SEC_PKCS12EnableCipher(PKCS12_AES_CBC_192, 1); + SEC_PKCS12EnableCipher(PKCS12_AES_CBC_256, 1); + SEC_PKCS12SetPreferredCipher(PKCS12_DES_EDE3_168, 1); + PORT_SetUCS2_ASCIIConversionFunction(ConvertBetweenUCS2andASCII); + + // PSM enforces a minimum RSA key size of 1024 bits, which is overridable. + // NSS has its own minimum, which is not overridable (the default is 1023 + // bits). This sets the NSS minimum to 512 bits so users can still connect to + // devices like wifi routers with woefully small keys (they would have to add + // an override to do so, but they already do for such devices). + NSS_OptionSet(NSS_RSA_MIN_KEY_SIZE, 512); + + // Observe preference change around cipher suite setting. + return CipherSuiteChangeObserver::StartObserve(); +} + +} // namespace psm +} // namespace mozilla |