Source code
Revision control
Copy as Markdown
Other Tools
/* -*- 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
#include "nsNSSComponent.h"
#include "BinaryPath.h"
#include "CryptoTask.h"
#include "EnterpriseRoots.h"
#include "ExtendedValidation.h"
#include "NSSCertDBTrustDomain.h"
#include "PKCS11ModuleDB.h"
#include "SSLTokensCache.h"
#include "ScopedNSSTypes.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/Base64.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 "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 "nsNSSHelper.h"
#include "nsNSSIOLayer.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
using namespace mozilla;
using namespace mozilla::psm;
LazyLogModule gPIPNSSLog("pipnss");
int nsNSSComponent::mInstanceCount = 0;
// Forward declaration.
nsresult CommonInit();
template <const glean::impl::QuantityMetric* metric>
class MOZ_RAII AutoGleanTimer {
public:
explicit AutoGleanTimer(TimeStamp aStart = TimeStamp::Now())
: mStart(aStart) {}
~AutoGleanTimer() {
TimeStamp end = TimeStamp::Now();
uint32_t delta = static_cast<uint32_t>((end - mStart).ToMilliseconds());
metric->Set(delta);
}
private:
const TimeStamp mStart;
};
// 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();
nsSSLIOLayerHelpers::GlobalCleanup();
--mInstanceCount;
MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsNSSComponent::dtor finished\n"));
}
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();
if (importEnterpriseRoots) {
RefPtr<BackgroundImportEnterpriseCertsTask> task =
new BackgroundImportEnterpriseCertsTask(this);
Unused << task->Dispatch();
}
}
void nsNSSComponent::ImportEnterpriseRoots() {
MOZ_ASSERT(!NS_IsMainThread());
if (NS_IsMainThread()) {
return;
}
nsTArray<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;
}
enterpriseCerts.Clear();
MutexAutoLock nsNSSComponentLock(mMutex);
for (const auto& cert : mEnterpriseCerts) {
nsTArray<uint8_t> certCopy;
// mEnterpriseCerts includes both roots and intermediates.
if (cert.GetIsRoot() == getRoots) {
cert.CopyBytes(certCopy);
enterpriseCerts.AppendElement(std::move(certCopy));
}
}
return NS_OK;
}
NS_IMETHODIMP
nsNSSComponent::GetEnterpriseRoots(
nsTArray<nsTArray<uint8_t>>& enterpriseRoots) {
return CommonGetEnterpriseCerts(enterpriseRoots, true);
}
nsresult BytesArrayToPEM(const nsTArray<nsTArray<uint8_t>>& bytesArray,
nsACString& pemArray) {
for (const auto& bytes : bytesArray) {
nsAutoCString base64;
nsresult rv = Base64Encode(reinterpret_cast<const char*>(bytes.Elements()),
bytes.Length(), base64);
if (NS_FAILED(rv)) {
return rv;
}
if (!pemArray.IsEmpty()) {
pemArray.AppendLiteral("\n");
}
pemArray.AppendLiteral("-----BEGIN CERTIFICATE-----\n");
for (size_t i = 0; i < base64.Length() / 64; i++) {
pemArray.Append(Substring(base64, i * 64, 64));
pemArray.AppendLiteral("\n");
}
if (base64.Length() % 64 != 0) {
size_t chunks = base64.Length() / 64;
pemArray.Append(Substring(base64, chunks * 64));
pemArray.AppendLiteral("\n");
}
pemArray.AppendLiteral("-----END CERTIFICATE-----");
}
return NS_OK;
}
NS_IMETHODIMP
nsNSSComponent::GetEnterpriseRootsPEM(nsACString& enterpriseRootsPEM) {
nsTArray<nsTArray<uint8_t>> enterpriseRoots;
nsresult rv = GetEnterpriseRoots(enterpriseRoots);
if (NS_FAILED(rv)) {
return rv;
}
return BytesArrayToPEM(enterpriseRoots, enterpriseRootsPEM);
}
NS_IMETHODIMP
nsNSSComponent::GetEnterpriseIntermediates(
nsTArray<nsTArray<uint8_t>>& enterpriseIntermediates) {
return CommonGetEnterpriseCerts(enterpriseIntermediates, false);
}
NS_IMETHODIMP
nsNSSComponent::GetEnterpriseIntermediatesPEM(
nsACString& enterpriseIntermediatesPEM) {
nsTArray<nsTArray<uint8_t>> enterpriseIntermediates;
nsresult rv = GetEnterpriseIntermediates(enterpriseIntermediates);
if (NS_FAILED(rv)) {
return rv;
}
return BytesArrayToPEM(enterpriseIntermediates, enterpriseIntermediatesPEM);
}
NS_IMETHODIMP
nsNSSComponent::AddEnterpriseIntermediate(
const nsTArray<uint8_t>& intermediateBytes) {
nsresult rv = BlockUntilLoadableCertsLoaded();
if (NS_FAILED(rv)) {
return rv;
}
EnterpriseCert intermediate(intermediateBytes.Elements(),
intermediateBytes.Length(), false);
{
MutexAutoLock nsNSSComponentLock(mMutex);
mEnterpriseCerts.AppendElement(std::move(intermediate));
}
UpdateCertVerifierWithEnterpriseRoots();
return NS_OK;
}
class LoadLoadableCertsTask final : public Runnable {
public:
LoadLoadableCertsTask(nsNSSComponent* nssComponent,
bool importEnterpriseRoots,
Vector<nsCString>&& possibleLoadableRootsLocations,
Maybe<nsCString>&& osClientCertsModuleLocation)
: Runnable("LoadLoadableCertsTask"),
mNSSComponent(nssComponent),
mImportEnterpriseRoots(importEnterpriseRoots),
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;
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() {
AutoGleanTimer<&glean::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 (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.get()));
if (osClientCertsModule) {
SECMOD_UnloadUserModule(osClientCertsModule.get());
}
}
}
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;
MOZ_RUNINIT 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;
nsresult rv = NS_NewNativeLocalFile(nsDependentCString(nss3Path.get()),
getter_AddRefs(nss3File));
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();
nsSSLIOLayerHelpers::GlobalInit();
return NS_OK;
}
void PrepareForShutdownInSocketProcess() {
MOZ_ASSERT(XRE_IsSocketProcess());
nsSSLIOLayerHelpers::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;
}
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);
}
}
}
// static
void SetKyberPolicy() {
if (StaticPrefs::security_tls_enable_kyber()) {
NSS_SetAlgorithmPolicy(SEC_OID_MLKEM768X25519, NSS_USE_ALG_IN_SSL_KX, 0);
} else {
NSS_SetAlgorithmPolicy(SEC_OID_MLKEM768X25519, 0, NSS_USE_ALG_IN_SSL_KX);
}
}
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();
SetKyberPolicy();
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();