From 9825c57bc32c275bec813481468aded9e513d37f Mon Sep 17 00:00:00 2001 From: David Keeler Date: Tue, 12 Jan 2016 15:39:43 -0800 Subject: [PATCH] bug 1239166 - platform work to support Microsoft Family Safety functionality r=froydnj,mgoodwin,mhowell,rbarnes,vladan MozReview-Commit-ID: GhpJqJB97r9 --HG-- extra : rebase_source : e943c1e4d0f008ffd6b6bb4bb63e1daf27ae2c96 --- mfbt/WindowsVersion.h | 6 + security/manager/ssl/nsNSSCertificate.cpp | 17 +- security/manager/ssl/nsNSSCertificate.h | 4 + security/manager/ssl/nsNSSCertificateDB.cpp | 39 +- security/manager/ssl/nsNSSCertificateDB.h | 5 + security/manager/ssl/nsNSSComponent.cpp | 445 ++++++++++++++++++- toolkit/components/telemetry/Histograms.json | 8 + 7 files changed, 504 insertions(+), 20 deletions(-) diff --git a/mfbt/WindowsVersion.h b/mfbt/WindowsVersion.h index 72eda2c48ded..4156f4f64baa 100644 --- a/mfbt/WindowsVersion.h +++ b/mfbt/WindowsVersion.h @@ -171,6 +171,12 @@ IsWin8OrLater() return IsWindowsVersionOrLater(0x06020000ul); } +MOZ_ALWAYS_INLINE bool +IsWin8Point1OrLater() +{ + return IsWindowsVersionOrLater(0x06030000ul); +} + MOZ_ALWAYS_INLINE bool IsWin10OrLater() { diff --git a/security/manager/ssl/nsNSSCertificate.cpp b/security/manager/ssl/nsNSSCertificate.cpp index 3346aadf73de..f584cc223d49 100644 --- a/security/manager/ssl/nsNSSCertificate.cpp +++ b/security/manager/ssl/nsNSSCertificate.cpp @@ -513,7 +513,12 @@ nsNSSCertificate::GetDbKey(nsACString& aDbKey) if (isAlreadyShutDown()) { return NS_ERROR_NOT_AVAILABLE; } + return GetDbKey(mCert, aDbKey); +} +nsresult +nsNSSCertificate::GetDbKey(CERTCertificate* cert, nsACString& aDbKey) +{ static_assert(sizeof(uint64_t) == 8, "type size sanity check"); static_assert(sizeof(uint32_t) == 4, "type size sanity check"); // The format of the key is the base64 encoding of the following: @@ -528,14 +533,14 @@ nsNSSCertificate::GetDbKey(nsACString& aDbKey) nsAutoCString buf; const char leadingZeroes[] = {0, 0, 0, 0, 0, 0, 0, 0}; buf.Append(leadingZeroes, sizeof(leadingZeroes)); - uint32_t serialNumberLen = htonl(mCert->serialNumber.len); + uint32_t serialNumberLen = htonl(cert->serialNumber.len); buf.Append(reinterpret_cast(&serialNumberLen), sizeof(uint32_t)); - uint32_t issuerLen = htonl(mCert->derIssuer.len); + uint32_t issuerLen = htonl(cert->derIssuer.len); buf.Append(reinterpret_cast(&issuerLen), sizeof(uint32_t)); - buf.Append(reinterpret_cast(mCert->serialNumber.data), - mCert->serialNumber.len); - buf.Append(reinterpret_cast(mCert->derIssuer.data), - mCert->derIssuer.len); + buf.Append(reinterpret_cast(cert->serialNumber.data), + cert->serialNumber.len); + buf.Append(reinterpret_cast(cert->derIssuer.data), + cert->derIssuer.len); return Base64Encode(buf, aDbKey); } diff --git a/security/manager/ssl/nsNSSCertificate.h b/security/manager/ssl/nsNSSCertificate.h index 621df9a3fbdc..519f26327826 100644 --- a/security/manager/ssl/nsNSSCertificate.h +++ b/security/manager/ssl/nsNSSCertificate.h @@ -53,6 +53,10 @@ public: ev_status_unknown = 2 }; + // This is a separate static method so nsNSSComponent can use it during NSS + // initialization. Other code should probably not use it. + static nsresult GetDbKey(CERTCertificate* cert, nsACString& aDbKey); + private: virtual ~nsNSSCertificate(); diff --git a/security/manager/ssl/nsNSSCertificateDB.cpp b/security/manager/ssl/nsNSSCertificateDB.cpp index 2a31ba5932cb..c45bb45a2b7e 100644 --- a/security/manager/ssl/nsNSSCertificateDB.cpp +++ b/security/manager/ssl/nsNSSCertificateDB.cpp @@ -128,10 +128,10 @@ nsNSSCertificateDB::FindCertByNickname(const nsAString& nickname, } NS_IMETHODIMP -nsNSSCertificateDB::FindCertByDBKey(const char* aDBkey,nsIX509Cert** _cert) +nsNSSCertificateDB::FindCertByDBKey(const char* aDBKey,nsIX509Cert** _cert) { - NS_ENSURE_ARG_POINTER(aDBkey); - NS_ENSURE_ARG(aDBkey[0]); + NS_ENSURE_ARG_POINTER(aDBKey); + NS_ENSURE_ARG(aDBKey[0]); NS_ENSURE_ARG_POINTER(_cert); *_cert = nullptr; @@ -140,6 +140,27 @@ nsNSSCertificateDB::FindCertByDBKey(const char* aDBkey,nsIX509Cert** _cert) return NS_ERROR_NOT_AVAILABLE; } + UniqueCERTCertificate cert; + nsresult rv = FindCertByDBKey(aDBKey, cert); + if (NS_FAILED(rv)) { + return rv; + } + // If we can't find the certificate, that's not an error. Just return null. + if (!cert) { + return NS_OK; + } + nsCOMPtr nssCert = nsNSSCertificate::Create(cert.get()); + if (!nssCert) { + return NS_ERROR_OUT_OF_MEMORY; + } + nssCert.forget(_cert); + return NS_OK; +} + +nsresult +nsNSSCertificateDB::FindCertByDBKey(const char* aDBKey, + UniqueCERTCertificate& cert) +{ static_assert(sizeof(uint64_t) == 8, "type size sanity check"); static_assert(sizeof(uint32_t) == 4, "type size sanity check"); // (From nsNSSCertificate::GetDbKey) @@ -153,7 +174,7 @@ nsNSSCertificateDB::FindCertByDBKey(const char* aDBkey,nsIX509Cert** _cert) // n bytes: // m bytes: nsAutoCString decoded; - nsAutoCString tmpDBKey(aDBkey); + nsAutoCString tmpDBKey(aDBKey); // Filter out any whitespace for backwards compatibility. tmpDBKey.StripWhitespace(); nsresult rv = Base64Decode(tmpDBKey, decoded); @@ -185,15 +206,7 @@ nsNSSCertificateDB::FindCertByDBKey(const char* aDBkey,nsIX509Cert** _cert) reader += issuerLen; MOZ_ASSERT(reader == decoded.EndReading()); - ScopedCERTCertificate cert( - CERT_FindCertByIssuerAndSN(CERT_GetDefaultCertDB(), &issuerSN)); - if (cert) { - nsCOMPtr nssCert = nsNSSCertificate::Create(cert.get()); - if (!nssCert) { - return NS_ERROR_OUT_OF_MEMORY; - } - nssCert.forget(_cert); - } + cert.reset(CERT_FindCertByIssuerAndSN(CERT_GetDefaultCertDB(), &issuerSN)); return NS_OK; } diff --git a/security/manager/ssl/nsNSSCertificateDB.h b/security/manager/ssl/nsNSSCertificateDB.h index 5a9588b190f6..4258a153bfd6 100644 --- a/security/manager/ssl/nsNSSCertificateDB.h +++ b/security/manager/ssl/nsNSSCertificateDB.h @@ -35,6 +35,11 @@ public: ImportValidCACerts(int numCACerts, SECItem *CACerts, nsIInterfaceRequestor *ctx, const nsNSSShutDownPreventionLock &proofOfLock); + // This is a separate static method so nsNSSComponent can use it during NSS + // initialization. Other code should probably not use it. + static nsresult + FindCertByDBKey(const char* aDBKey, mozilla::UniqueCERTCertificate& cert); + protected: virtual ~nsNSSCertificateDB(); diff --git a/security/manager/ssl/nsNSSComponent.cpp b/security/manager/ssl/nsNSSComponent.cpp index 4b92283e420d..cebbf3170e79 100644 --- a/security/manager/ssl/nsNSSComponent.cpp +++ b/security/manager/ssl/nsNSSComponent.cpp @@ -4,16 +4,21 @@ * 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/. */ +#define CERT_AddTempCertToPerm __CERT_AddTempCertToPerm + #include "nsNSSComponent.h" #include "ExtendedValidation.h" #include "NSSCertDBTrustDomain.h" #include "SharedSSLState.h" +#include "cert.h" +#include "certdb.h" #include "mozilla/Preferences.h" #include "mozilla/PublicSSL.h" #include "mozilla/Services.h" #include "mozilla/StaticPtr.h" #include "mozilla/Telemetry.h" +#include "mozilla/UniquePtr.h" #include "nsAppDirectoryServiceDefs.h" #include "nsCRT.h" #include "nsCertVerificationThread.h" @@ -30,6 +35,7 @@ #include "nsITokenPasswordDialogs.h" #include "nsIWindowWatcher.h" #include "nsIXULRuntime.h" +#include "nsNSSCertificateDB.h" #include "nsNSSHelper.h" #include "nsNSSShutDown.h" #include "nsServiceManagerUtils.h" @@ -49,7 +55,14 @@ #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; @@ -394,6 +407,433 @@ nsNSSComponent::ShutdownSmartCardThreads() } #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 = reinterpret_cast(sid_buffer); + DWORD cbSid = MOZ_ARRAY_LENGTH(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(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(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 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 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 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 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 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; +} + +const char* kImportedFamilySafetyRootPref = + "security.family_safety.imported_root.db_key"; + +static nsresult +MaybeImportFamilySafetyRoot(PCCERT_CONTEXT certificate, + bool& wasFamilySafetyRoot) +{ + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("MaybeImportFamilySafetyRoot")); + wasFamilySafetyRoot = false; + + // It would be convenient to just use nsIX509CertDB here. However, since + // nsIX509CertDB depends on nsNSSComponent 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). + + SECItem derCert = { + siBuffer, + certificate->pbCertEncoded, + certificate->cbCertEncoded + }; + UniqueCERTCertificate nssCertificate( + CERT_NewTempCertificate(CERT_GetDefaultCertDB(), &derCert, + nullptr, // nickname unnecessary + false, // not permanent + true)); // copy DER + 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' + UniquePtr subjectName( + CERT_GetCommonName(&nssCertificate->subject), PORT_Free); + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, + ("subject name is '%s'", subjectName.get())); + if (nsCRT::strcmp(subjectName.get(), "Microsoft Family Safety") == 0) { + wasFamilySafetyRoot = true; + CERTCertTrust trust = { + CERTDB_TRUSTED_CA | CERTDB_VALID_CA | CERTDB_USER, + 0, + 0 + }; + SECStatus srv = __CERT_AddTempCertToPerm( + nssCertificate.get(), "Microsoft Family Safety", &trust); + if (srv != SECSuccess) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, + ("couldn't permanently add certificate")); + return NS_ERROR_FAILURE; + } + nsAutoCString dbKey; + nsresult rv = nsNSSCertificate::GetDbKey(nssCertificate.get(), dbKey); + if (NS_FAILED(rv)) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("GetDbKey failed")); + return rv; + } + Preferences::SetCString(kImportedFamilySafetyRootPref, dbKey); + 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 pointer templates. +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* WindowsDefaultRootStoreName = L"ROOT"; + +static nsresult +LoadFamilySafetyRoot() +{ + ScopedCertStore certstore( + CertOpenSystemStore(0, WindowsDefaultRootStoreName)); + if (!certstore.get()) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, + ("couldn't get certstore '%S'", WindowsDefaultRootStoreName)); + 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; +} + +static void +UnloadFamilySafetyRoot() +{ + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("UnloadFamilySafetyRoot")); + nsAdoptingCString dbKey = Preferences::GetCString( + kImportedFamilySafetyRootPref); + if (!dbKey || dbKey.IsEmpty()) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, + ("Family Safety root wasn't previously imported")); + return; + } + UniqueCERTCertificate cert; + nsresult rv = nsNSSCertificateDB::FindCertByDBKey(dbKey, cert); + if (NS_FAILED(rv)) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, + ("finding previously-imported Family Safety root failed")); + return; + } + if (!cert) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, + ("previously-imported Family Safety root not found")); + return; + } + SECStatus srv = SEC_DeletePermCertificate(cert.get()); + if (srv != SECSuccess) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, + ("couldn't delete previously-imported Family Safety root")); + return; + } + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, + ("deleted previously-imported Family Safety root")); + Preferences::ClearUser(kImportedFamilySafetyRootPref); +} + +#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 +static void +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 +} + void nsNSSComponent::LoadLoadableRoots() { @@ -1079,6 +1519,8 @@ nsNSSComponent::InitializeNSS() InitCertVerifierLog(); LoadLoadableRoots(); + MaybeEnableFamilySafetyCompatibility(); + ConfigureTLSSessionIdentifiers(); bool requireSafeNegotiation = @@ -1144,7 +1586,6 @@ nsNSSComponent::InitializeNSS() if (PK11_IsFIPS()) { Telemetry::Accumulate(Telemetry::FIPS_ENABLED, true); } - MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("NSS Initialization done\n")); return NS_OK; } @@ -1346,6 +1787,8 @@ nsNSSComponent::Observe(nsISupports* aSubject, const char* aTopic, MutexAutoLock lock(mutex); mTestBuiltInRootHash = Preferences::GetString("security.test.built_in_root_hash"); #endif // DEBUG + } else if (prefName.Equals(kFamilySafetyModePref)) { + MaybeEnableFamilySafetyCompatibility(); } else { clearSessionCache = false; } diff --git a/toolkit/components/telemetry/Histograms.json b/toolkit/components/telemetry/Histograms.json index dd67b9328768..5ee7643533a5 100644 --- a/toolkit/components/telemetry/Histograms.json +++ b/toolkit/components/telemetry/Histograms.json @@ -291,6 +291,14 @@ "n_values": 10, "description": "GPU Device Reset Reason (ok, hung, removed, reset, internal error, invalid call, out of memory)" }, + "FAMILY_SAFETY": { + "alert_emails": ["seceng@mozilla.org"], + "expires_in_version": "55", + "kind": "enumerated", + "n_values": 16, + "bug_numbers": [1239166], + "description": "Status of Family Safety detection and remediation. See nsNSSComponent.cpp." + }, "FETCH_IS_MAINTHREAD": { "expires_in_version": "50", "kind": "boolean",