Files
tubestation/security/manager/ssl/nsNSSComponent.cpp
Nicholas Nethercote fcd4f4fa21 Bug 1376638 - Minimize uses of prmem.h. r=glandium.
It's silly to use prmem.h within Firefox code given that in our configuration
its functions are just wrappers for malloc() et al. (Indeed, in some places we
mix PR_Malloc() with free(), or malloc() with PR_Free().)

This patch removes all uses, except for the places where we need to use
PR_Free() to free something allocated by another NSPR function; in those cases
I've added a comment explaining which function did the allocation.
2017-06-30 19:05:41 -07:00

2414 lines
82 KiB
C++

/* -*- 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 "ExtendedValidation.h"
#include "NSSCertDBTrustDomain.h"
#include "PKCS11.h"
#include "ScopedNSSTypes.h"
#include "SharedSSLState.h"
#include "cert.h"
#include "certdb.h"
#include "mozilla/ArrayUtils.h"
#include "mozilla/Assertions.h"
#include "mozilla/Casting.h"
#include "mozilla/Preferences.h"
#include "mozilla/PodOperations.h"
#include "mozilla/PublicSSL.h"
#include "mozilla/Services.h"
#include "mozilla/StaticPtr.h"
#include "mozilla/SyncRunnable.h"
#include "mozilla/Telemetry.h"
#include "mozilla/TimeStamp.h"
#include "mozilla/Unused.h"
#include "nsAppDirectoryServiceDefs.h"
#include "nsCRT.h"
#include "nsClientAuthRemember.h"
#include "nsComponentManagerUtils.h"
#include "nsDirectoryServiceDefs.h"
#include "nsICertOverrideService.h"
#include "nsIFile.h"
#include "nsIObserverService.h"
#include "nsIPrompt.h"
#include "nsIProperties.h"
#include "nsISiteSecurityService.h"
#include "nsITokenPasswordDialogs.h"
#include "nsIWindowWatcher.h"
#include "nsIXULRuntime.h"
#include "nsLiteralString.h"
#include "nsNSSCertificateDB.h"
#include "nsNSSHelper.h"
#include "nsNSSShutDown.h"
#include "nsPrintfCString.h"
#include "nsServiceManagerUtils.h"
#include "nsThreadUtils.h"
#include "nsXULAppAPI.h"
#include "nss.h"
#include "p12plcy.h"
#include "pkix/pkixnss.h"
#include "secerr.h"
#include "secmod.h"
#include "ssl.h"
#include "sslerr.h"
#include "sslproto.h"
#include "prmem.h"
#ifndef MOZ_NO_SMART_CARDS
#include "nsSmartCardMonitor.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;
// This function can be called from chrome or content processes
// to ensure that NSS is initialized.
bool EnsureNSSInitializedChromeOrContent()
{
// If this is not the main thread (i.e. probably a worker) then forward this
// call to the main thread.
if (!NS_IsMainThread()) {
static Atomic<bool> initialized(false);
// Cache the result to dispatch to the main thread only once per worker.
if (initialized) {
return true;
}
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,
new SyncRunnable(
NS_NewRunnableFunction("EnsureNSSInitializedChromeOrContent", []() {
initialized = EnsureNSSInitializedChromeOrContent();
})));
return initialized;
}
if (XRE_IsParentProcess()) {
nsCOMPtr<nsISupports> nss = do_GetService(PSM_COMPONENT_CONTRACTID);
if (!nss) {
return false;
}
return true;
}
if (NSS_IsInitialized()) {
return true;
}
if (NSS_NoDB_Init(nullptr) != SECSuccess) {
return false;
}
if (NS_FAILED(mozilla::psm::InitializeCipherSuite())) {
return false;
}
mozilla::psm::DisableMD5();
return true;
}
static const uint32_t OCSP_TIMEOUT_MILLISECONDS_SOFT_DEFAULT = 2000;
static const uint32_t OCSP_TIMEOUT_MILLISECONDS_SOFT_MAX = 5000;
static const uint32_t OCSP_TIMEOUT_MILLISECONDS_HARD_DEFAULT = 10000;
static const uint32_t OCSP_TIMEOUT_MILLISECONDS_HARD_MAX = 20000;
static void
GetRevocationBehaviorFromPrefs(/*out*/ CertVerifier::OcspDownloadConfig* odc,
/*out*/ CertVerifier::OcspStrictConfig* osc,
/*out*/ CertVerifier::OcspGetConfig* ogc,
/*out*/ uint32_t* certShortLifetimeInDays,
/*out*/ TimeDuration& softTimeout,
/*out*/ TimeDuration& hardTimeout,
const MutexAutoLock& /*proofOfLock*/)
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(odc);
MOZ_ASSERT(osc);
MOZ_ASSERT(ogc);
MOZ_ASSERT(certShortLifetimeInDays);
// 0 = disabled
// 1 = enabled for everything (default)
// 2 = enabled for EV certificates only
int32_t ocspLevel = Preferences::GetInt("security.OCSP.enabled", 1);
switch (ocspLevel) {
case 0: *odc = CertVerifier::ocspOff; break;
case 2: *odc = CertVerifier::ocspEVOnly; break;
default: *odc = CertVerifier::ocspOn; break;
}
*osc = Preferences::GetBool("security.OCSP.require", false)
? CertVerifier::ocspStrict
: CertVerifier::ocspRelaxed;
// XXX: Always use POST for OCSP; see bug 871954 for undoing this.
*ogc = Preferences::GetBool("security.OCSP.GET.enabled", false)
? CertVerifier::ocspGetEnabled
: CertVerifier::ocspGetDisabled;
// If we pass in just 0 as the second argument to Preferences::GetUint, there
// are two function signatures that match (given that 0 can be intepreted as
// a null pointer). Thus the compiler will complain without the cast.
*certShortLifetimeInDays =
Preferences::GetUint("security.pki.cert_short_lifetime_in_days",
static_cast<uint32_t>(0));
uint32_t softTimeoutMillis =
Preferences::GetUint("security.OCSP.timeoutMilliseconds.soft",
OCSP_TIMEOUT_MILLISECONDS_SOFT_DEFAULT);
softTimeoutMillis = std::min(softTimeoutMillis,
OCSP_TIMEOUT_MILLISECONDS_SOFT_MAX);
softTimeout = TimeDuration::FromMilliseconds(softTimeoutMillis);
uint32_t hardTimeoutMillis =
Preferences::GetUint("security.OCSP.timeoutMilliseconds.hard",
OCSP_TIMEOUT_MILLISECONDS_HARD_DEFAULT);
hardTimeoutMillis = std::min(hardTimeoutMillis,
OCSP_TIMEOUT_MILLISECONDS_HARD_MAX);
hardTimeout = TimeDuration::FromMilliseconds(hardTimeoutMillis);
SSL_ClearSessionCache();
}
nsNSSComponent::nsNSSComponent()
: mMutex("nsNSSComponent.mMutex")
, mNSSInitialized(false)
#ifndef MOZ_NO_SMART_CARDS
, mThreadList(nullptr)
#endif
{
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
ShutdownNSS();
SharedSSLState::GlobalCleanup();
RememberCertErrorsTable::Cleanup();
--mInstanceCount;
MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsNSSComponent::dtor finished\n"));
}
NS_IMETHODIMP
nsNSSComponent::PIPBundleFormatStringFromName(const char* name,
const char16_t** params,
uint32_t numParams,
nsAString& outString)
{
MutexAutoLock lock(mMutex);
nsresult rv = NS_ERROR_FAILURE;
if (mPIPNSSBundle && name) {
nsXPIDLString result;
rv = mPIPNSSBundle->FormatStringFromName(NS_ConvertASCIItoUTF16(name).get(),
params, numParams,
getter_Copies(result));
if (NS_SUCCEEDED(rv)) {
outString = result;
}
}
return rv;
}
NS_IMETHODIMP
nsNSSComponent::GetPIPNSSBundleString(const char* name, nsAString& outString)
{
MutexAutoLock lock(mMutex);
nsresult rv = NS_ERROR_FAILURE;
outString.SetLength(0);
if (mPIPNSSBundle && name) {
nsXPIDLString result;
rv = mPIPNSSBundle->GetStringFromName(NS_ConvertASCIItoUTF16(name).get(),
getter_Copies(result));
if (NS_SUCCEEDED(rv)) {
outString = result;
rv = NS_OK;
}
}
return rv;
}
NS_IMETHODIMP
nsNSSComponent::GetNSSBundleString(const char* name, nsAString& outString)
{
MutexAutoLock lock(mMutex);
nsresult rv = NS_ERROR_FAILURE;
outString.SetLength(0);
if (mNSSErrorsBundle && name) {
nsXPIDLString result;
rv = mNSSErrorsBundle->GetStringFromName(NS_ConvertASCIItoUTF16(name).get(),
getter_Copies(result));
if (NS_SUCCEEDED(rv)) {
outString = result;
rv = NS_OK;
}
}
return rv;
}
#ifndef MOZ_NO_SMART_CARDS
nsresult
nsNSSComponent::LaunchSmartCardThreads()
{
MOZ_ASSERT(NS_IsMainThread());
if (!NS_IsMainThread()) {
return NS_ERROR_NOT_SAME_THREAD;
}
AutoSECMODListReadLock lock;
SECMODModuleList* list = SECMOD_GetDefaultModuleList();
nsresult rv;
while (list) {
rv = LaunchSmartCardThread(list->module);
if (NS_FAILED(rv)) {
return rv;
}
list = list->next;
}
return NS_OK;
}
NS_IMETHODIMP
nsNSSComponent::LaunchSmartCardThread(SECMODModule* module)
{
MOZ_ASSERT(NS_IsMainThread());
if (!NS_IsMainThread()) {
return NS_ERROR_NOT_SAME_THREAD;
}
SmartCardMonitoringThread* newThread;
if (SECMOD_HasRemovableSlots(module)) {
if (!mThreadList) {
mThreadList = new SmartCardThreadList();
}
newThread = new SmartCardMonitoringThread(module);
// newThread is adopted by the add.
return mThreadList->Add(newThread);
}
return NS_OK;
}
NS_IMETHODIMP
nsNSSComponent::ShutdownSmartCardThread(SECMODModule* module)
{
MOZ_ASSERT(NS_IsMainThread());
if (!NS_IsMainThread()) {
return NS_ERROR_NOT_SAME_THREAD;
}
if (!mThreadList) {
return NS_OK;
}
mThreadList->Remove(module);
return NS_OK;
}
void
nsNSSComponent::ShutdownSmartCardThreads()
{
MOZ_ASSERT(NS_IsMainThread());
if (!NS_IsMainThread()) {
return;
}
delete mThreadList;
mThreadList = nullptr;
}
#endif // MOZ_NO_SMART_CARDS
#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,
wchar_t* optionalChildName,
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;
NS_NAMED_LITERAL_STRING(familySafetyPath,
"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Parental Controls");
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;
}
NS_NAMED_LITERAL_STRING(usersString, "Users");
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'", 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(NS_LITERAL_STRING("Parental Controls On"),
&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;
}
// It would be convenient to just use nsIX509CertDB in the following code.
// However, since nsIX509CertDB depends on nsNSSComponent initialization (and
// since this code runs during that initialization), we can't use it. Instead,
// we can use NSS APIs directly (as long as we're called late enough in
// nsNSSComponent initialization such that those APIs are safe to use).
// Helper function to convert a PCCERT_CONTEXT (i.e. a certificate obtained via
// a Windows API) to a temporary CERTCertificate (i.e. a certificate for use
// with NSS APIs).
static UniqueCERTCertificate
PCCERT_CONTEXTToCERTCertificate(PCCERT_CONTEXT pccert)
{
MOZ_ASSERT(pccert);
if (!pccert) {
return nullptr;
}
SECItem derCert = {
siBuffer,
pccert->pbCertEncoded,
pccert->cbCertEncoded
};
return UniqueCERTCertificate(
CERT_NewTempCertificate(CERT_GetDefaultCertDB(), &derCert,
nullptr, // nickname unnecessary
false, // not permanent
true)); // copy DER
}
static NS_NAMED_LITERAL_CSTRING(kMicrosoftFamilySafetyCN,
"Microsoft Family Safety");
nsresult
nsNSSComponent::MaybeImportFamilySafetyRoot(PCCERT_CONTEXT certificate,
bool& wasFamilySafetyRoot)
{
MutexAutoLock lock(mMutex);
MOZ_ASSERT(NS_IsMainThread());
if (!NS_IsMainThread()) {
return NS_ERROR_NOT_SAME_THREAD;
}
MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("MaybeImportFamilySafetyRoot"));
wasFamilySafetyRoot = false;
UniqueCERTCertificate nssCertificate(
PCCERT_CONTEXTToCERTCertificate(certificate));
if (!nssCertificate) {
MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("couldn't decode certificate"));
return NS_ERROR_FAILURE;
}
// Looking for a certificate with the common name 'Microsoft Family Safety'
UniquePORTString subjectName(CERT_GetCommonName(&nssCertificate->subject));
MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
("subject name is '%s'", subjectName.get()));
if (kMicrosoftFamilySafetyCN.Equals(subjectName.get())) {
wasFamilySafetyRoot = true;
CERTCertTrust trust = {
CERTDB_TRUSTED_CA | CERTDB_VALID_CA | CERTDB_USER,
0,
0
};
if (CERT_ChangeCertTrust(nullptr, nssCertificate.get(), &trust)
!= SECSuccess) {
MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
("couldn't trust certificate for TLS server auth"));
return NS_ERROR_FAILURE;
}
MOZ_ASSERT(!mFamilySafetyRoot);
mFamilySafetyRoot = Move(nssCertificate);
MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("added Family Safety root"));
}
return NS_OK;
}
// Because HCERTSTORE is just a typedef void*, we can't use any of the nice
// scoped or unique pointer templates. To elaborate, any attempt would
// instantiate those templates with T = void. When T gets used in the context
// of T&, this results in void&, which isn't legal.
class ScopedCertStore final
{
public:
explicit ScopedCertStore(HCERTSTORE certstore) : certstore(certstore) {}
~ScopedCertStore()
{
CertCloseStore(certstore, 0);
}
HCERTSTORE get()
{
return certstore;
}
private:
ScopedCertStore(const ScopedCertStore&) = delete;
ScopedCertStore& operator=(const ScopedCertStore&) = delete;
HCERTSTORE certstore;
};
static const wchar_t* kWindowsDefaultRootStoreName = L"ROOT";
nsresult
nsNSSComponent::LoadFamilySafetyRoot()
{
ScopedCertStore certstore(
CertOpenSystemStore(0, kWindowsDefaultRootStoreName));
if (!certstore.get()) {
MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
("couldn't get certstore '%S'", kWindowsDefaultRootStoreName));
return NS_ERROR_FAILURE;
}
// Any resources held by the certificate are released by the next call to
// CertFindCertificateInStore.
PCCERT_CONTEXT certificate = nullptr;
while ((certificate = CertFindCertificateInStore(certstore.get(),
X509_ASN_ENCODING, 0,
CERT_FIND_ANY, nullptr,
certificate))) {
bool wasFamilySafetyRoot = false;
nsresult rv = MaybeImportFamilySafetyRoot(certificate,
wasFamilySafetyRoot);
if (NS_SUCCEEDED(rv) && wasFamilySafetyRoot) {
return NS_OK; // We're done (we're only expecting one root).
}
}
return NS_ERROR_FAILURE;
}
void
nsNSSComponent::UnloadFamilySafetyRoot()
{
MutexAutoLock lock(mMutex);
MOZ_ASSERT(NS_IsMainThread());
if (!NS_IsMainThread()) {
return;
}
MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("UnloadFamilySafetyRoot"));
if (!mFamilySafetyRoot) {
MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("Family Safety Root wasn't present"));
return;
}
// It would be intuitive to set the trust to { 0, 0, 0 } here. However, this
// doesn't work for temporary certificates because CERT_ChangeCertTrust first
// looks up the current trust settings in the permanent cert database, finds
// that such trust doesn't exist, considers the current trust to be
// { 0, 0, 0 }, and decides that it doesn't need to update the trust since
// they're the same. To work around this, we set a non-zero flag to ensure
// that the trust will get updated.
CERTCertTrust trust = { CERTDB_USER, 0, 0 };
if (CERT_ChangeCertTrust(nullptr, mFamilySafetyRoot.get(), &trust)
!= SECSuccess) {
MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
("couldn't untrust certificate for TLS server auth"));
}
mFamilySafetyRoot = nullptr;
}
#endif // XP_WIN
// The supported values of this pref are:
// 0: disable detecting Family Safety mode and importing the root
// 1: only attempt to detect Family Safety mode (don't import the root)
// 2: detect Family Safety mode and import the root
const char* kFamilySafetyModePref = "security.family_safety.mode";
// The telemetry gathered by this function is as follows:
// 0-2: the value of the Family Safety mode pref
// 3: detecting Family Safety mode failed
// 4: Family Safety was not enabled
// 5: Family Safety was enabled
// 6: failed to import the Family Safety root
// 7: successfully imported the root
void
nsNSSComponent::MaybeEnableFamilySafetyCompatibility()
{
#ifdef XP_WIN
UnloadFamilySafetyRoot();
if (!(IsWin8Point1OrLater() && !IsWin10OrLater())) {
return;
}
// Detect but don't import by default.
uint32_t familySafetyMode = Preferences::GetUint(kFamilySafetyModePref, 1);
if (familySafetyMode > 2) {
familySafetyMode = 0;
}
Telemetry::Accumulate(Telemetry::FAMILY_SAFETY, familySafetyMode);
if (familySafetyMode == 0) {
return;
}
bool familySafetyEnabled;
nsresult rv = AccountHasFamilySafetyEnabled(familySafetyEnabled);
if (NS_FAILED(rv)) {
Telemetry::Accumulate(Telemetry::FAMILY_SAFETY, 3);
return;
}
if (!familySafetyEnabled) {
Telemetry::Accumulate(Telemetry::FAMILY_SAFETY, 4);
return;
}
Telemetry::Accumulate(Telemetry::FAMILY_SAFETY, 5);
if (familySafetyMode == 2) {
rv = LoadFamilySafetyRoot();
if (NS_FAILED(rv)) {
Telemetry::Accumulate(Telemetry::FAMILY_SAFETY, 6);
MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
("failed to load Family Safety root"));
} else {
Telemetry::Accumulate(Telemetry::FAMILY_SAFETY, 7);
}
}
#endif // XP_WIN
}
#ifdef XP_WIN
// Helper function to determine if the OS considers the given certificate to be
// a trust anchor for TLS server auth certificates. This is to be used in the
// context of importing what are presumed to be root certificates from the OS.
// If this function returns true but it turns out that the given certificate is
// in some way unsuitable to issue certificates, mozilla::pkix will never build
// a valid chain that includes the certificate, so importing it even if it
// isn't a valid CA poses no risk.
static bool
CertIsTrustAnchorForTLSServerAuth(PCCERT_CONTEXT certificate)
{
MOZ_ASSERT(certificate);
if (!certificate) {
return false;
}
PCCERT_CHAIN_CONTEXT pChainContext = nullptr;
CERT_ENHKEY_USAGE enhkeyUsage;
memset(&enhkeyUsage, 0, sizeof(CERT_ENHKEY_USAGE));
LPSTR identifiers[] = {
"1.3.6.1.5.5.7.3.1", // id-kp-serverAuth
};
enhkeyUsage.cUsageIdentifier = ArrayLength(identifiers);
enhkeyUsage.rgpszUsageIdentifier = identifiers;
CERT_USAGE_MATCH certUsage;
memset(&certUsage, 0, sizeof(CERT_USAGE_MATCH));
certUsage.dwType = USAGE_MATCH_TYPE_AND;
certUsage.Usage = enhkeyUsage;
CERT_CHAIN_PARA chainPara;
memset(&chainPara, 0, sizeof(CERT_CHAIN_PARA));
chainPara.cbSize = sizeof(CERT_CHAIN_PARA);
chainPara.RequestedUsage = certUsage;
if (!CertGetCertificateChain(nullptr, certificate, nullptr, nullptr,
&chainPara, 0, nullptr, &pChainContext)) {
MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("CertGetCertificateChain failed"));
return false;
}
bool trusted = pChainContext->TrustStatus.dwErrorStatus ==
CERT_TRUST_NO_ERROR;
bool isRoot = pChainContext->cChain == 1;
CertFreeCertificateChain(pChainContext);
if (trusted && isRoot) {
MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
("certificate is trust anchor for TLS server auth"));
return true;
}
MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
("certificate not trust anchor for TLS server auth"));
return false;
}
void
nsNSSComponent::UnloadEnterpriseRoots(const MutexAutoLock& /*proof of lock*/)
{
MOZ_ASSERT(NS_IsMainThread());
if (!NS_IsMainThread()) {
return;
}
MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("UnloadEnterpriseRoots"));
if (!mEnterpriseRoots) {
MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("no enterprise roots were present"));
return;
}
// It would be intuitive to set the trust to { 0, 0, 0 } here. However, this
// doesn't work for temporary certificates because CERT_ChangeCertTrust first
// looks up the current trust settings in the permanent cert database, finds
// that such trust doesn't exist, considers the current trust to be
// { 0, 0, 0 }, and decides that it doesn't need to update the trust since
// they're the same. To work around this, we set a non-zero flag to ensure
// that the trust will get updated.
CERTCertTrust trust = { CERTDB_USER, 0, 0 };
for (CERTCertListNode* n = CERT_LIST_HEAD(mEnterpriseRoots.get());
!CERT_LIST_END(n, mEnterpriseRoots.get()); n = CERT_LIST_NEXT(n)) {
if (!n || !n->cert) {
MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
("library failure: CERTCertListNode null or lacks cert"));
continue;
}
if (CERT_ChangeCertTrust(nullptr, n->cert, &trust) != SECSuccess) {
MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
("couldn't untrust certificate for TLS server auth"));
}
}
mEnterpriseRoots = nullptr;
MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("unloaded enterprise roots"));
}
NS_IMETHODIMP
nsNSSComponent::GetEnterpriseRoots(nsIX509CertList** enterpriseRoots)
{
nsNSSShutDownPreventionLock lock;
MutexAutoLock nsNSSComponentLock(mMutex);
MOZ_ASSERT(NS_IsMainThread());
if (!NS_IsMainThread()) {
return NS_ERROR_NOT_SAME_THREAD;
}
NS_ENSURE_ARG_POINTER(enterpriseRoots);
// nsNSSComponent isn't a nsNSSShutDownObject, so we can't check
// isAlreadyShutDown(). However, since mEnterpriseRoots is cleared when NSS
// shuts down, we can use that as a proxy for checking for NSS shutdown.
// (Of course, it may also be the case that no enterprise roots were imported,
// so we should just return a null list and NS_OK in this case.)
if (!mEnterpriseRoots) {
*enterpriseRoots = nullptr;
return NS_OK;
}
UniqueCERTCertList enterpriseRootsCopy(
nsNSSCertList::DupCertList(mEnterpriseRoots, lock));
if (!enterpriseRootsCopy) {
return NS_ERROR_FAILURE;
}
nsCOMPtr<nsIX509CertList> enterpriseRootsCertList(
new nsNSSCertList(Move(enterpriseRootsCopy), lock));
if (!enterpriseRootsCertList) {
return NS_ERROR_FAILURE;
}
enterpriseRootsCertList.forget(enterpriseRoots);
return NS_OK;
}
#endif // XP_WIN
static const char* kEnterpriseRootModePref = "security.enterprise_roots.enabled";
void
nsNSSComponent::MaybeImportEnterpriseRoots()
{
#ifdef XP_WIN
MutexAutoLock lock(mMutex);
MOZ_ASSERT(NS_IsMainThread());
if (!NS_IsMainThread()) {
return;
}
UnloadEnterpriseRoots(lock);
bool importEnterpriseRoots = Preferences::GetBool(kEnterpriseRootModePref,
false);
if (!importEnterpriseRoots) {
return;
}
MOZ_ASSERT(!mEnterpriseRoots);
mEnterpriseRoots.reset(CERT_NewCertList());
if (!mEnterpriseRoots) {
MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
("failed to allocate a new CERTCertList for mEnterpriseRoots"));
return;
}
ImportEnterpriseRootsForLocation(CERT_SYSTEM_STORE_LOCAL_MACHINE, lock);
ImportEnterpriseRootsForLocation(CERT_SYSTEM_STORE_LOCAL_MACHINE_GROUP_POLICY,
lock);
ImportEnterpriseRootsForLocation(CERT_SYSTEM_STORE_LOCAL_MACHINE_ENTERPRISE,
lock);
#endif // XP_WIN
}
#ifdef XP_WIN
// Loads the enterprise roots at the registry location corresponding to the
// given location flag.
// Supported flags are:
// CERT_SYSTEM_STORE_LOCAL_MACHINE
// (for HKLM\SOFTWARE\Microsoft\SystemCertificates)
// CERT_SYSTEM_STORE_LOCAL_MACHINE_GROUP_POLICY
// (for HKLM\SOFTWARE\Policies\Microsoft\SystemCertificates\Root\Certificates)
// CERT_SYSTEM_STORE_LOCAL_MACHINE_ENTERPRISE
// (for HKLM\SOFTWARE\Microsoft\EnterpriseCertificates\Root\Certificates)
void
nsNSSComponent::ImportEnterpriseRootsForLocation(
DWORD locationFlag, const MutexAutoLock& /*proof of lock*/)
{
MOZ_ASSERT(NS_IsMainThread());
if (!NS_IsMainThread()) {
return;
}
MOZ_ASSERT(locationFlag == CERT_SYSTEM_STORE_LOCAL_MACHINE ||
locationFlag == CERT_SYSTEM_STORE_LOCAL_MACHINE_GROUP_POLICY ||
locationFlag == CERT_SYSTEM_STORE_LOCAL_MACHINE_ENTERPRISE,
"unexpected locationFlag for ImportEnterpriseRootsForLocation");
if (!(locationFlag == CERT_SYSTEM_STORE_LOCAL_MACHINE ||
locationFlag == CERT_SYSTEM_STORE_LOCAL_MACHINE_GROUP_POLICY ||
locationFlag == CERT_SYSTEM_STORE_LOCAL_MACHINE_ENTERPRISE)) {
return;
}
DWORD flags = locationFlag |
CERT_STORE_OPEN_EXISTING_FLAG |
CERT_STORE_READONLY_FLAG;
// The certificate store being opened should consist only of certificates
// added by a user or administrator and not any certificates that are part
// of Microsoft's root store program.
// The 3rd parameter to CertOpenStore should be NULL according to
// https://msdn.microsoft.com/en-us/library/windows/desktop/aa376559%28v=vs.85%29.aspx
ScopedCertStore enterpriseRootStore(CertOpenStore(
CERT_STORE_PROV_SYSTEM_REGISTRY_W, 0, NULL, flags,
kWindowsDefaultRootStoreName));
if (!enterpriseRootStore.get()) {
MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("failed to open enterprise root store"));
return;
}
CERTCertTrust trust = {
CERTDB_TRUSTED_CA | CERTDB_VALID_CA | CERTDB_USER,
0,
0
};
PCCERT_CONTEXT certificate = nullptr;
uint32_t numImported = 0;
while ((certificate = CertFindCertificateInStore(enterpriseRootStore.get(),
X509_ASN_ENCODING, 0,
CERT_FIND_ANY, nullptr,
certificate))) {
if (!CertIsTrustAnchorForTLSServerAuth(certificate)) {
MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
("skipping cert not trust anchor for TLS server auth"));
continue;
}
UniqueCERTCertificate nssCertificate(
PCCERT_CONTEXTToCERTCertificate(certificate));
if (!nssCertificate) {
MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("couldn't decode certificate"));
continue;
}
// Don't import the Microsoft Family Safety root (this prevents the
// Enterprise Roots feature from interacting poorly with the Family
// Safety support).
UniquePORTString subjectName(
CERT_GetCommonName(&nssCertificate->subject));
if (kMicrosoftFamilySafetyCN.Equals(subjectName.get())) {
MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("skipping Family Safety Root"));
continue;
}
MOZ_ASSERT(mEnterpriseRoots, "mEnterpriseRoots unexpectedly NULL?");
if (!mEnterpriseRoots) {
return;
}
if (CERT_AddCertToListTail(mEnterpriseRoots.get(), nssCertificate.get())
!= SECSuccess) {
MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("couldn't add cert to list"));
continue;
}
if (CERT_ChangeCertTrust(nullptr, nssCertificate.get(), &trust)
!= SECSuccess) {
MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
("couldn't trust certificate for TLS server auth"));
}
MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("Imported '%s'", subjectName.get()));
numImported++;
// now owned by mEnterpriseRoots
Unused << nssCertificate.release();
}
MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("imported %u roots", numImported));
}
#endif // XP_WIN
void
nsNSSComponent::LoadLoadableRoots()
{
// Find the best Roots module for our purposes.
// Prefer the application's installation directory,
// but also ensure the library is at least the version we expect.
nsAutoString modName;
nsresult rv = GetPIPNSSBundleString("RootCertModuleName", modName);
if (NS_FAILED(rv)) {
// When running Cpp unit tests on Android, this will fail because string
// bundles aren't available (see bug 1311077, bug 1228175 comment 12, and
// bug 929655). Because the module name is really only for display purposes,
// we can just hard-code the value here. Furthermore, if we want to be able
// to stop using string bundles in PSM in this way, we'll have to hard-code
// the string and only use the localized version when displaying it to the
// user, so this is a step in that direction anyway.
modName.AssignLiteral("Builtin Roots Module");
}
nsCOMPtr<nsIProperties> directoryService(do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID));
if (!directoryService)
return;
static const char nss_lib[] = "nss3";
const char* possible_ckbi_locations[] = {
nss_lib, // This special value means: search for ckbi in the directory
// where nss3 is.
NS_XPCOM_CURRENT_PROCESS_DIR,
NS_GRE_DIR,
0 // This special value means:
// search for ckbi in the directories on the shared
// library/DLL search path
};
for (size_t il = 0; il < sizeof(possible_ckbi_locations)/sizeof(const char*); ++il) {
nsAutoCString libDir;
if (possible_ckbi_locations[il]) {
nsCOMPtr<nsIFile> mozFile;
if (possible_ckbi_locations[il] == nss_lib) {
// Get the location of the nss3 library.
char* nss_path = PR_GetLibraryFilePathname(DLL_PREFIX "nss3" DLL_SUFFIX,
(PRFuncPtr) NSS_Initialize);
if (!nss_path) {
continue;
}
// Get the directory containing the nss3 library.
nsCOMPtr<nsIFile> nssLib(do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv));
if (NS_SUCCEEDED(rv)) {
rv = nssLib->InitWithNativePath(nsDependentCString(nss_path));
}
PR_Free(nss_path); // PR_GetLibraryFilePathname() uses PR_Malloc().
if (NS_SUCCEEDED(rv)) {
nsCOMPtr<nsIFile> file;
if (NS_SUCCEEDED(nssLib->GetParent(getter_AddRefs(file)))) {
mozFile = do_QueryInterface(file);
}
}
} else {
directoryService->Get( possible_ckbi_locations[il],
NS_GET_IID(nsIFile),
getter_AddRefs(mozFile));
}
if (!mozFile) {
continue;
}
if (NS_FAILED(mozFile->GetNativePath(libDir))) {
continue;
}
}
NS_ConvertUTF16toUTF8 modNameUTF8(modName);
if (mozilla::psm::LoadLoadableRoots(libDir, modNameUTF8)) {
break;
}
}
}
void
nsNSSComponent::UnloadLoadableRoots()
{
nsresult rv;
nsAutoString modName;
rv = GetPIPNSSBundleString("RootCertModuleName", modName);
if (NS_FAILED(rv)) return;
NS_ConvertUTF16toUTF8 modNameUTF8(modName);
::mozilla::psm::UnloadLoadableRoots(modNameUTF8.get());
}
nsresult
nsNSSComponent::ConfigureInternalPKCS11Token()
{
nsAutoString manufacturerID;
nsAutoString libraryDescription;
nsAutoString tokenDescription;
nsAutoString privateTokenDescription;
nsAutoString slotDescription;
nsAutoString privateSlotDescription;
nsAutoString fips140SlotDescription;
nsAutoString fips140TokenDescription;
nsresult rv;
rv = GetPIPNSSBundleString("ManufacturerID", manufacturerID);
if (NS_FAILED(rv)) return rv;
rv = GetPIPNSSBundleString("LibraryDescription", libraryDescription);
if (NS_FAILED(rv)) return rv;
rv = GetPIPNSSBundleString("TokenDescription", tokenDescription);
if (NS_FAILED(rv)) return rv;
rv = GetPIPNSSBundleString("PrivateTokenDescription", privateTokenDescription);
if (NS_FAILED(rv)) return rv;
rv = GetPIPNSSBundleString("SlotDescription", slotDescription);
if (NS_FAILED(rv)) return rv;
rv = GetPIPNSSBundleString("PrivateSlotDescription", privateSlotDescription);
if (NS_FAILED(rv)) return rv;
rv = GetPIPNSSBundleString("Fips140SlotDescription", fips140SlotDescription);
if (NS_FAILED(rv)) return rv;
rv = GetPIPNSSBundleString("Fips140TokenDescription", fips140TokenDescription);
if (NS_FAILED(rv)) return rv;
PK11_ConfigurePKCS11(NS_ConvertUTF16toUTF8(manufacturerID).get(),
NS_ConvertUTF16toUTF8(libraryDescription).get(),
NS_ConvertUTF16toUTF8(tokenDescription).get(),
NS_ConvertUTF16toUTF8(privateTokenDescription).get(),
NS_ConvertUTF16toUTF8(slotDescription).get(),
NS_ConvertUTF16toUTF8(privateSlotDescription).get(),
NS_ConvertUTF16toUTF8(fips140SlotDescription).get(),
NS_ConvertUTF16toUTF8(fips140TokenDescription).get(),
0, 0);
return NS_OK;
}
nsresult
nsNSSComponent::InitializePIPNSSBundle()
{
MutexAutoLock lock(mMutex);
nsresult rv;
nsCOMPtr<nsIStringBundleService> bundleService(do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv));
if (NS_FAILED(rv) || !bundleService)
return NS_ERROR_FAILURE;
bundleService->CreateBundle("chrome://pipnss/locale/pipnss.properties",
getter_AddRefs(mPIPNSSBundle));
if (!mPIPNSSBundle)
rv = NS_ERROR_FAILURE;
bundleService->CreateBundle("chrome://pipnss/locale/nsserrors.properties",
getter_AddRefs(mNSSErrorsBundle));
if (!mNSSErrorsBundle)
rv = NS_ERROR_FAILURE;
return rv;
}
// Table of pref names and SSL cipher ID
typedef struct {
const char* pref;
long id;
bool enabledByDefault;
} 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, true },
{ "security.ssl3.ecdhe_ecdsa_aes_128_gcm_sha256",
TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, true },
{ "security.ssl3.ecdhe_ecdsa_chacha20_poly1305_sha256",
TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, true },
{ "security.ssl3.ecdhe_rsa_chacha20_poly1305_sha256",
TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, true },
{ "security.ssl3.ecdhe_ecdsa_aes_256_gcm_sha384",
TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, true },
{ "security.ssl3.ecdhe_rsa_aes_256_gcm_sha384",
TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, true },
{ "security.ssl3.ecdhe_rsa_aes_128_sha",
TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, true },
{ "security.ssl3.ecdhe_ecdsa_aes_128_sha",
TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, true },
{ "security.ssl3.ecdhe_rsa_aes_256_sha",
TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, true },
{ "security.ssl3.ecdhe_ecdsa_aes_256_sha",
TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, true },
{ "security.ssl3.dhe_rsa_aes_128_sha",
TLS_DHE_RSA_WITH_AES_128_CBC_SHA, true },
{ "security.ssl3.dhe_rsa_aes_256_sha",
TLS_DHE_RSA_WITH_AES_256_CBC_SHA, true },
{ "security.tls13.aes_128_gcm_sha256",
TLS_AES_128_GCM_SHA256, true },
{ "security.tls13.chacha20_poly1305_sha256",
TLS_CHACHA20_POLY1305_SHA256, true },
{ "security.tls13.aes_256_gcm_sha384",
TLS_AES_256_GCM_SHA384, true },
{ "security.ssl3.rsa_aes_128_sha",
TLS_RSA_WITH_AES_128_CBC_SHA, true }, // deprecated (RSA key exchange)
{ "security.ssl3.rsa_aes_256_sha",
TLS_RSA_WITH_AES_256_CBC_SHA, true }, // deprecated (RSA key exchange)
{ "security.ssl3.rsa_des_ede3_sha",
TLS_RSA_WITH_3DES_EDE_CBC_SHA, true }, // deprecated (RSA key exchange, 3DES)
// All the rest are disabled
{ nullptr, 0 } // end marker
};
// 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 const int32_t OCSP_ENABLED_DEFAULT = 1;
static const bool REQUIRE_SAFE_NEGOTIATION_DEFAULT = false;
static const bool FALSE_START_ENABLED_DEFAULT = true;
static const bool ALPN_ENABLED_DEFAULT = false;
static const bool ENABLED_0RTT_DATA_DEFAULT = false;
static void
ConfigureTLSSessionIdentifiers()
{
bool disableSessionIdentifiers =
Preferences::GetBool("security.ssl.disable_session_identifiers", false);
SSL_OptionSetDefault(SSL_ENABLE_SESSION_TICKETS, !disableSessionIdentifiers);
SSL_OptionSetDefault(SSL_NO_CACHE, disableSessionIdentifiers);
}
namespace {
class CipherSuiteChangeObserver : public nsIObserver
{
public:
NS_DECL_ISUPPORTS
NS_DECL_NSIOBSERVER
static nsresult StartObserve();
protected:
virtual ~CipherSuiteChangeObserver() {}
private:
static StaticRefPtr<CipherSuiteChangeObserver> sObserver;
CipherSuiteChangeObserver() {}
};
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;
}
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
const CipherPref* const cp = sCipherPrefs;
for (size_t i = 0; cp[i].pref; ++i) {
if (prefName.Equals(cp[i].pref)) {
bool cipherEnabled = Preferences::GetBool(cp[i].pref,
cp[i].enabledByDefault);
SSL_CipherPrefSetDefault(cp[i].id, cipherEnabled);
SSL_ClearSessionCache();
break;
}
}
} 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)
{
MutexAutoLock lock(mMutex);
// This preference controls whether we do OCSP fetching and does not affect
// OCSP stapling.
// 0 = disabled, 1 = enabled
int32_t ocspEnabled = Preferences::GetInt("security.OCSP.enabled",
OCSP_ENABLED_DEFAULT);
bool ocspRequired = ocspEnabled &&
Preferences::GetBool("security.OCSP.require", false);
// 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);
}
bool ocspStaplingEnabled = Preferences::GetBool("security.ssl.enable_ocsp_stapling",
true);
PublicSSLState()->SetOCSPStaplingEnabled(ocspStaplingEnabled);
PrivateSSLState()->SetOCSPStaplingEnabled(ocspStaplingEnabled);
bool ocspMustStapleEnabled = Preferences::GetBool("security.ssl.enable_ocsp_must_staple",
true);
PublicSSLState()->SetOCSPMustStapleEnabled(ocspMustStapleEnabled);
PrivateSSLState()->SetOCSPMustStapleEnabled(ocspMustStapleEnabled);
const CertVerifier::CertificateTransparencyMode defaultCTMode =
CertVerifier::CertificateTransparencyMode::TelemetryOnly;
CertVerifier::CertificateTransparencyMode ctMode =
static_cast<CertVerifier::CertificateTransparencyMode>
(Preferences::GetInt("security.pki.certificate_transparency.mode",
static_cast<int32_t>(defaultCTMode)));
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);
CertVerifier::PinningMode pinningMode =
static_cast<CertVerifier::PinningMode>
(Preferences::GetInt("security.cert_pinning.enforcement_level",
CertVerifier::pinningDisabled));
if (pinningMode > CertVerifier::pinningEnforceTestMode) {
pinningMode = CertVerifier::pinningDisabled;
}
CertVerifier::SHA1Mode sha1Mode = static_cast<CertVerifier::SHA1Mode>
(Preferences::GetInt("security.pki.sha1_enforcement_level",
static_cast<int32_t>(CertVerifier::SHA1Mode::Allowed)));
switch (sha1Mode) {
case CertVerifier::SHA1Mode::Allowed:
case CertVerifier::SHA1Mode::Forbidden:
case CertVerifier::SHA1Mode::UsedToBeBefore2016ButNowIsForbidden:
case CertVerifier::SHA1Mode::ImportedRoot:
case CertVerifier::SHA1Mode::ImportedRootOrBefore2016:
break;
default:
sha1Mode = CertVerifier::SHA1Mode::Allowed;
break;
}
// Convert a previously-available setting to a safe one.
if (sha1Mode == CertVerifier::SHA1Mode::UsedToBeBefore2016ButNowIsForbidden) {
sha1Mode = CertVerifier::SHA1Mode::Forbidden;
}
BRNameMatchingPolicy::Mode nameMatchingMode =
static_cast<BRNameMatchingPolicy::Mode>
(Preferences::GetInt("security.pki.name_matching_mode",
static_cast<int32_t>(BRNameMatchingPolicy::Mode::DoNotEnforce)));
switch (nameMatchingMode) {
case BRNameMatchingPolicy::Mode::Enforce:
case BRNameMatchingPolicy::Mode::EnforceAfter23August2015:
case BRNameMatchingPolicy::Mode::EnforceAfter23August2016:
case BRNameMatchingPolicy::Mode::DoNotEnforce:
break;
default:
nameMatchingMode = BRNameMatchingPolicy::Mode::DoNotEnforce;
break;
}
NetscapeStepUpPolicy netscapeStepUpPolicy =
static_cast<NetscapeStepUpPolicy>
(Preferences::GetUint("security.pki.netscape_step_up_policy",
static_cast<uint32_t>(NetscapeStepUpPolicy::AlwaysMatch)));
switch (netscapeStepUpPolicy) {
case NetscapeStepUpPolicy::AlwaysMatch:
case NetscapeStepUpPolicy::MatchBefore23August2016:
case NetscapeStepUpPolicy::MatchBefore23August2015:
case NetscapeStepUpPolicy::NeverMatch:
break;
default:
netscapeStepUpPolicy = NetscapeStepUpPolicy::AlwaysMatch;
break;
}
CertVerifier::OcspDownloadConfig odc;
CertVerifier::OcspStrictConfig osc;
CertVerifier::OcspGetConfig ogc;
uint32_t certShortLifetimeInDays;
TimeDuration softTimeout;
TimeDuration hardTimeout;
GetRevocationBehaviorFromPrefs(&odc, &osc, &ogc, &certShortLifetimeInDays,
softTimeout, hardTimeout, lock);
mDefaultCertVerifier = new SharedCertVerifier(odc, osc, ogc, softTimeout,
hardTimeout,
certShortLifetimeInDays,
pinningMode, sha1Mode,
nameMatchingMode,
netscapeStepUpPolicy,
ctMode);
}
// 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 security-prefs.js
// 1 means TLS 1.0, 2 means TLS 1.1, etc.
static const uint32_t PSM_DEFAULT_MIN_TLS_VERSION = 1;
static const uint32_t PSM_DEFAULT_MAX_TLS_VERSION = 4;
uint32_t minFromPrefs = Preferences::GetUint("security.tls.version.min",
PSM_DEFAULT_MIN_TLS_VERSION);
uint32_t maxFromPrefs = Preferences::GetUint("security.tls.version.max",
PSM_DEFAULT_MAX_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;
}
static nsresult
GetNSSProfilePath(nsAutoCString& aProfilePath)
{
aProfilePath.Truncate();
const char* dbDirOverride = getenv("MOZPSM_NSSDBDIR_OVERRIDE");
if (dbDirOverride && strlen(dbDirOverride) > 0) {
MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
("Using specified MOZPSM_NSSDBDIR_OVERRIDE as NSS DB dir: %s\n",
dbDirOverride));
aProfilePath.Assign(dbDirOverride);
return NS_OK;
}
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)
// Native path will drop Unicode characters that cannot be mapped to system's
// codepage, using short (canonical) path as workaround.
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;
}
rv = profileFileWin->GetNativeCanonicalPath(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
// "<original name>.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.
static nsresult
AttemptToRenamePKCS11ModuleDB(const nsACString& profilePath)
{
// profilePath may come from the environment variable
// MOZPSM_NSSDBDIR_OVERRIDE. If so, the user's NSS DBs are most likely not in
// their profile directory and we shouldn't mess with them.
const char* dbDirOverride = getenv("MOZPSM_NSSDBDIR_OVERRIDE");
if (dbDirOverride && strlen(dbDirOverride) > 0) {
MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
("MOZPSM_NSSDBDIR_OVERRIDE set - not renaming PKCS#11 module DB"));
return NS_OK;
}
NS_NAMED_LITERAL_CSTRING(moduleDBFilename, "secmod.db");
NS_NAMED_LITERAL_CSTRING(destModuleDBFilename, "secmod.db.fips");
nsCOMPtr<nsIFile> dbFile = do_CreateInstance("@mozilla.org/file/local;1");
if (!dbFile) {
return NS_ERROR_FAILURE;
}
nsresult rv = dbFile->InitWithNativePath(profilePath);
if (NS_FAILED(rv)) {
return rv;
}
rv = dbFile->AppendNative(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.get()));
return NS_OK;
}
nsCOMPtr<nsIFile> destDBFile = do_CreateInstance("@mozilla.org/file/local;1");
if (!destDBFile) {
return NS_ERROR_FAILURE;
}
rv = destDBFile->InitWithNativePath(profilePath);
if (NS_FAILED(rv)) {
return rv;
}
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.
nsCOMPtr<nsIFile> profileDir = do_CreateInstance("@mozilla.org/file/local;1");
if (!profileDir) {
return NS_ERROR_FAILURE;
}
rv = profileDir->InitWithNativePath(profilePath);
if (NS_FAILED(rv)) {
return rv;
}
// 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.
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);
return srv == SECSuccess ? NS_OK : NS_ERROR_FAILURE;
}
const char* profilePathCStr = PromiseFlatCString(profilePath).get();
// Try read/write mode. If we're in safeMode, we won't load PKCS#11 modules.
#ifndef ANDROID
PRErrorCode savedPRErrorCode1;
#endif // ifndef ANDROID
SECStatus srv = ::mozilla::psm::InitializeNSS(profilePathCStr, false,
!safeMode);
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(profilePathCStr, true, !safeMode);
if (srv == SECSuccess) {
MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("initialized NSS in r-o mode"));
return NS_OK;
}
#ifndef ANDROID
savedPRErrorCode2 = PR_GetError();
#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)) {
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(profilePathCStr, false, false);
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) {
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)) {
return rv;
}
srv = ::mozilla::psm::InitializeNSS(profilePathCStr, false, true);
if (srv == SECSuccess) {
MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("initialized in r/w mode"));
return NS_OK;
}
srv = ::mozilla::psm::InitializeNSS(profilePathCStr, true, true);
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);
return srv == SECSuccess ? NS_OK : NS_ERROR_FAILURE;
}
nsresult
nsNSSComponent::InitializeNSS()
{
MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsNSSComponent::InitializeNSS\n"));
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"));
// The call to ConfigureInternalPKCS11Token needs to be done before NSS is initialized,
// but affects only static data.
// If we could assume i18n will not change between profiles, one call per application
// run were sufficient. As I can't predict what happens in the future, let's repeat
// this call for every re-init of NSS.
ConfigureInternalPKCS11Token();
nsAutoCString profileStr;
nsresult rv = GetNSSProfilePath(profileStr);
if (NS_FAILED(rv)) {
return NS_ERROR_NOT_AVAILABLE;
}
bool nocertdb = Preferences::GetBool("security.nocertdb", false);
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);
if (NS_FAILED(rv)) {
return rv;
}
}
MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("inSafeMode: %u\n", inSafeMode));
rv = InitializeNSSWithFallbacks(profileStr, nocertdb, inSafeMode);
if (NS_FAILED(rv)) {
MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("failed to initialize NSS"));
return rv;
}
PK11_SetPasswordFunc(PK11PasswordPrompt);
SharedSSLState::GlobalInit();
// Register an observer so we can inform NSS when these prefs change
Preferences::AddStrongObserver(this, "security.");
SSL_OptionSetDefault(SSL_ENABLE_SSL2, false);
SSL_OptionSetDefault(SSL_V2_COMPATIBLE_HELLO, false);
rv = setEnabledTLSVersions();
if (NS_FAILED(rv)) {
return NS_ERROR_UNEXPECTED;
}
DisableMD5();
LoadLoadableRoots();
rv = LoadExtendedValidationInfo();
if (NS_FAILED(rv)) {
MOZ_LOG(gPIPNSSLog, LogLevel::Error, ("failed to load EV info"));
return rv;
}
MaybeEnableFamilySafetyCompatibility();
MaybeImportEnterpriseRoots();
ConfigureTLSSessionIdentifiers();
bool requireSafeNegotiation =
Preferences::GetBool("security.ssl.require_safe_negotiation",
REQUIRE_SAFE_NEGOTIATION_DEFAULT);
SSL_OptionSetDefault(SSL_REQUIRE_SAFE_NEGOTIATION, requireSafeNegotiation);
SSL_OptionSetDefault(SSL_ENABLE_RENEGOTIATION, SSL_RENEGOTIATE_REQUIRES_XTN);
SSL_OptionSetDefault(SSL_ENABLE_EXTENDED_MASTER_SECRET, true);
SSL_OptionSetDefault(SSL_ENABLE_FALSE_START,
Preferences::GetBool("security.ssl.enable_false_start",
FALSE_START_ENABLED_DEFAULT));
// 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,
Preferences::GetBool("security.ssl.enable_alpn",
ALPN_ENABLED_DEFAULT));
SSL_OptionSetDefault(SSL_ENABLE_0RTT_DATA,
Preferences::GetBool("security.tls.enable_0rtt_data",
ENABLED_0RTT_DATA_DEFAULT));
if (NS_FAILED(InitializeCipherSuite())) {
MOZ_LOG(gPIPNSSLog, LogLevel::Error, ("Unable to initialize cipher suite settings\n"));
return NS_ERROR_FAILURE;
}
// TLSServerSocket may be run with the session cache enabled. It is necessary
// to call this once before that can happen. This specifies a maximum of 1000
// cache entries (the default number of cache entries is 10000, which seems a
// little excessive as there probably won't be that many clients connecting to
// any TLSServerSockets the browser runs.)
// Note that this must occur before any calls to SSL_ClearSessionCache
// (otherwise memory will leak).
if (SSL_ConfigServerSessionIDCache(1000, 0, 0, nullptr) != SECSuccess) {
return NS_ERROR_FAILURE;
}
// dynamic options from prefs
setValidationOptions(true);
#ifndef MOZ_NO_SMART_CARDS
rv = LaunchSmartCardThreads();
if (NS_FAILED(rv)) {
MOZ_LOG(gPIPNSSLog, LogLevel::Error,
("failed to start smart card threads"));
return rv;
}
#endif
mozilla::pkix::RegisterErrorTable();
if (PK11_IsFIPS()) {
Telemetry::Accumulate(Telemetry::FIPS_ENABLED, true);
}
{ // Introduce scope for the AutoSECMODListReadLock.
AutoSECMODListReadLock lock;
for (SECMODModuleList* list = SECMOD_GetDefaultModuleList(); list;
list = list->next) {
nsAutoString scalarKey;
GetModuleNameForTelemetry(list->module, scalarKey);
// Scalar keys must be between 0 and 70 characters (exclusive).
// GetModuleNameForTelemetry takes care of keys that are too long. If for
// some reason it couldn't come up with an appropriate name and returned
// an empty result, however, we need to not attempt to record this (it
// wouldn't give us anything useful anyway).
if (scalarKey.Length() > 0) {
Telemetry::ScalarSet(
Telemetry::ScalarID::SECURITY_PKCS11_MODULES_LOADED, scalarKey, true);
}
}
}
MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("NSS Initialization done\n"));
{
MutexAutoLock lock(mMutex);
// ensure we have initial values for various root hashes
#ifdef DEBUG
mTestBuiltInRootHash =
Preferences::GetString("security.test.built_in_root_hash");
#endif
mContentSigningRootHash =
Preferences::GetString("security.content.signature.root_hash");
mNSSInitialized = true;
}
return NS_OK;
}
void
nsNSSComponent::ShutdownNSS()
{
MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsNSSComponent::ShutdownNSS\n"));
MOZ_RELEASE_ASSERT(NS_IsMainThread());
// This is idempotent and can happen as a result of observing
// profile-before-change and being called from nsNSSComponent's destructor.
// We need to do this before other cleanup because we must avoid acquiring
// mMutex and then preventing threads holding nsNSSShutDownPreventionLocks
// from continuing (which is what evaporateAllNSSResourcesAndShutDown does).
MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("evaporating psm resources"));
if (NS_FAILED(nsNSSShutDownList::evaporateAllNSSResourcesAndShutDown())) {
MOZ_LOG(gPIPNSSLog, LogLevel::Error, ("failed to evaporate resources"));
return;
}
// This currently calls GetPIPNSSBundleString, which acquires mMutex, so we
// can't call it while already holding mMutex. This is fine as mMutex doesn't
// actually protect anything that UnloadLoadableRoots is modifying. Also,
// UnloadLoadableRoots is idempotent.
UnloadLoadableRoots();
MutexAutoLock lock(mMutex);
// Other shutdown tasks are not guaranteed to be idempotent and we should
// avoid performing them more than once.
if (!mNSSInitialized) {
return;
}
mNSSInitialized = false;
#ifdef XP_WIN
mFamilySafetyRoot = nullptr;
mEnterpriseRoots = nullptr;
#endif
PK11_SetPasswordFunc((PK11PasswordFunc)nullptr);
Preferences::RemoveObserver(this, "security.");
#ifndef MOZ_NO_SMART_CARDS
ShutdownSmartCardThreads();
#endif
SSL_ClearSessionCache();
// TLSServerSocket may be run with the session cache enabled. This ensures
// those resources are cleaned up.
Unused << SSL_ShutdownServerSessionIDCache();
// Release the default CertVerifier. This will cause any held NSS resources
// to be released (it's not an nsNSSShutDownObject, so we have to do this
// manually).
mDefaultCertVerifier = nullptr;
if (NSS_Shutdown() != SECSuccess) {
MOZ_LOG(gPIPNSSLog, LogLevel::Error, ("NSS SHUTDOWN FAILURE"));
} else {
MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("NSS shutdown =====>> OK <<====="));
}
}
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;
}
MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("Beginning NSS initialization\n"));
nsresult rv = InitializePIPNSSBundle();
if (NS_FAILED(rv)) {
MOZ_LOG(gPIPNSSLog, LogLevel::Error, ("Unable to create pipnss bundle.\n"));
return rv;
}
rv = InitializeNSS();
if (NS_FAILED(rv)) {
MOZ_LOG(gPIPNSSLog, LogLevel::Error,
("nsNSSComponent::InitializeNSS() failed\n"));
return rv;
}
RememberCertErrorsTable::Init();
return RegisterObservers();
}
// 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)
{
if (nsCRT::strcmp(aTopic, PROFILE_BEFORE_CHANGE_TOPIC) == 0) {
MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("receiving profile change topic\n"));
ShutdownNSS();
} else if (nsCRT::strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID) == 0) {
nsNSSShutDownPreventionLock locker;
bool clearSessionCache = true;
NS_ConvertUTF16toUTF8 prefName(someData);
if (prefName.EqualsLiteral("security.tls.version.min") ||
prefName.EqualsLiteral("security.tls.version.max")) {
(void) setEnabledTLSVersions();
} else if (prefName.EqualsLiteral("security.ssl.require_safe_negotiation")) {
bool requireSafeNegotiation =
Preferences::GetBool("security.ssl.require_safe_negotiation",
REQUIRE_SAFE_NEGOTIATION_DEFAULT);
SSL_OptionSetDefault(SSL_REQUIRE_SAFE_NEGOTIATION, requireSafeNegotiation);
} else if (prefName.EqualsLiteral("security.ssl.enable_false_start")) {
SSL_OptionSetDefault(SSL_ENABLE_FALSE_START,
Preferences::GetBool("security.ssl.enable_false_start",
FALSE_START_ENABLED_DEFAULT));
} else if (prefName.EqualsLiteral("security.ssl.enable_alpn")) {
SSL_OptionSetDefault(SSL_ENABLE_ALPN,
Preferences::GetBool("security.ssl.enable_alpn",
ALPN_ENABLED_DEFAULT));
} else if (prefName.EqualsLiteral("security.tls.enable_0rtt_data")) {
SSL_OptionSetDefault(SSL_ENABLE_0RTT_DATA,
Preferences::GetBool("security.tls.enable_0rtt_data",
ENABLED_0RTT_DATA_DEFAULT));
} else if (prefName.Equals("security.ssl.disable_session_identifiers")) {
ConfigureTLSSessionIdentifiers();
} else if (prefName.EqualsLiteral("security.OCSP.enabled") ||
prefName.EqualsLiteral("security.OCSP.require") ||
prefName.EqualsLiteral("security.OCSP.GET.enabled") ||
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.cert_pinning.enforcement_level") ||
prefName.EqualsLiteral("security.pki.sha1_enforcement_level") ||
prefName.EqualsLiteral("security.pki.name_matching_mode") ||
prefName.EqualsLiteral("security.pki.netscape_step_up_policy") ||
prefName.EqualsLiteral("security.OCSP.timeoutMilliseconds.soft") ||
prefName.EqualsLiteral("security.OCSP.timeoutMilliseconds.hard")) {
setValidationOptions(false);
#ifdef DEBUG
} else if (prefName.EqualsLiteral("security.test.built_in_root_hash")) {
MutexAutoLock lock(mMutex);
mTestBuiltInRootHash = Preferences::GetString("security.test.built_in_root_hash");
#endif // DEBUG
} else if (prefName.Equals(kFamilySafetyModePref)) {
MaybeEnableFamilySafetyCompatibility();
} else if (prefName.EqualsLiteral("security.content.signature.root_hash")) {
MutexAutoLock lock(mMutex);
mContentSigningRootHash =
Preferences::GetString("security.content.signature.root_hash");
} else if (prefName.Equals(kEnterpriseRootModePref)) {
MaybeImportEnterpriseRoots();
} else {
clearSessionCache = false;
}
if (clearSessionCache)
SSL_ClearSessionCache();
}
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(
NS_LITERAL_CSTRING("all:temporary-certificates"),
0);
}
nsClientAuthRememberService::ClearAllRememberedDecisions();
return nsNSSShutDownList::doPK11Logout();
}
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);
return NS_OK;
}
#ifdef DEBUG
NS_IMETHODIMP
nsNSSComponent::IsCertTestBuiltInRoot(CERTCertificate* cert, bool& result)
{
result = false;
// Create the nsNSSCertificate and get its hash before acquiring mMutex (we
// must avoid acquiring mMutex and then creating an
// nsNSSShutDownPreventionLock).
RefPtr<nsNSSCertificate> nsc = nsNSSCertificate::Create(cert);
if (!nsc) {
return NS_ERROR_FAILURE;
}
nsAutoString certHash;
nsresult rv = nsc->GetSha256Fingerprint(certHash);
if (NS_FAILED(rv)) {
return rv;
}
MutexAutoLock lock(mMutex);
MOZ_ASSERT(mNSSInitialized);
if (mTestBuiltInRootHash.IsEmpty()) {
return NS_OK;
}
result = mTestBuiltInRootHash.Equals(certHash);
return NS_OK;
}
#endif // DEBUG
NS_IMETHODIMP
nsNSSComponent::IsCertContentSigningRoot(CERTCertificate* cert, bool& result)
{
result = false;
// Create the nsNSSCertificate and get its hash before acquiring mMutex (we
// must avoid acquiring mMutex and then creating an
// nsNSSShutDownPreventionLock).
RefPtr<nsNSSCertificate> nsc = nsNSSCertificate::Create(cert);
if (!nsc) {
MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("creating nsNSSCertificate failed"));
return NS_ERROR_FAILURE;
}
nsAutoString certHash;
nsresult rv = nsc->GetSha256Fingerprint(certHash);
if (NS_FAILED(rv)) {
MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("getting cert fingerprint failed"));
return rv;
}
MutexAutoLock lock(mMutex);
MOZ_ASSERT(mNSSInitialized);
if (mContentSigningRootHash.IsEmpty()) {
MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("mContentSigningRootHash is empty"));
return NS_ERROR_FAILURE;
}
result = mContentSigningRootHash.Equals(certHash);
return NS_OK;
}
SharedCertVerifier::~SharedCertVerifier() { }
already_AddRefed<SharedCertVerifier>
nsNSSComponent::GetDefaultCertVerifier()
{
MutexAutoLock lock(mMutex);
MOZ_ASSERT(mNSSInitialized);
RefPtr<SharedCertVerifier> certVerifier(mDefaultCertVerifier);
return certVerifier.forget();
}
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 nssComponent->GetDefaultCertVerifier();
}
return nullptr;
}
} } // namespace mozilla::psm
NS_IMPL_ISUPPORTS(PipUIContext, nsIInterfaceRequestor)
PipUIContext::PipUIContext()
{
}
PipUIContext::~PipUIContext()
{
}
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,
nsNSSShutDownPreventionLock& /*proofOfLock*/)
{
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;
NS_ConvertUTF8toUTF16 tokenName(PK11_GetTokenName(slot));
rv = dialogs->SetPassword(ctx, tokenName, &canceled);
if (NS_FAILED(rv)) {
return rv;
}
if (canceled) {
return NS_ERROR_NOT_AVAILABLE;
}
}
return NS_OK;
}
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
const CipherPref* const cp = sCipherPrefs;
for (size_t i = 0; cp[i].pref; ++i) {
bool cipherEnabled = Preferences::GetBool(cp[i].pref,
cp[i].enabledByDefault);
SSL_CipherPrefSetDefault(cp[i].id, cipherEnabled);
}
// 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_PKCS12SetPreferredCipher(PKCS12_DES_EDE3_168, 1);
PORT_SetUCS2_ASCIIConversionFunction(pip_ucs2_ascii_conversion_fn);
// 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