bug 1239166 - platform work to support Microsoft Family Safety functionality r=froydnj,mgoodwin,mhowell,rbarnes,vladan
authorDavid Keeler <dkeeler@mozilla.com>
Tue, 12 Jan 2016 15:39:43 -0800
changeset 329246 201173cd6886
parent 329245 45de51023c1f
child 329247 6166a6070ec2
push id6048
push userkmoir@mozilla.com
push dateMon, 06 Jun 2016 19:02:08 +0000
treeherdermozilla-beta@46d72a56c57d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersfroydnj, mgoodwin, mhowell, rbarnes, vladan
bugs1239166
milestone48.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
bug 1239166 - platform work to support Microsoft Family Safety functionality r=froydnj,mgoodwin,mhowell,rbarnes,vladan MozReview-Commit-ID: GhpJqJB97r9
mfbt/WindowsVersion.h
security/manager/ssl/nsNSSCertificate.cpp
security/manager/ssl/nsNSSCertificate.h
security/manager/ssl/nsNSSCertificateDB.cpp
security/manager/ssl/nsNSSCertificateDB.h
security/manager/ssl/nsNSSComponent.cpp
toolkit/components/telemetry/Histograms.json
--- a/mfbt/WindowsVersion.h
+++ b/mfbt/WindowsVersion.h
@@ -167,16 +167,22 @@ IsWin7SP1OrLater()
 
 MOZ_ALWAYS_INLINE bool
 IsWin8OrLater()
 {
   return IsWindowsVersionOrLater(0x06020000ul);
 }
 
 MOZ_ALWAYS_INLINE bool
+IsWin8Point1OrLater()
+{
+  return IsWindowsVersionOrLater(0x06030000ul);
+}
+
+MOZ_ALWAYS_INLINE bool
 IsWin10OrLater()
 {
   return IsWindowsVersionOrLater(0x0a000000ul);
 }
 
 MOZ_ALWAYS_INLINE bool
 IsNotWin7PreRTM()
 {
--- a/security/manager/ssl/nsNSSCertificate.cpp
+++ b/security/manager/ssl/nsNSSCertificate.cpp
@@ -508,39 +508,44 @@ nsNSSCertificate::FormatUIStrings(const 
 
 NS_IMETHODIMP
 nsNSSCertificate::GetDbKey(nsACString& aDbKey)
 {
   nsNSSShutDownPreventionLock locker;
   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:
   // 4 bytes: {0, 0, 0, 0} (this was intended to be the module ID, but it was
   //                        never implemented)
   // 4 bytes: {0, 0, 0, 0} (this was intended to be the slot ID, but it was
   //                        never implemented)
   // 4 bytes: <serial number length in big-endian order>
   // 4 bytes: <DER-encoded issuer distinguished name length in big-endian order>
   // n bytes: <bytes of serial number>
   // m bytes: <DER-encoded issuer distinguished name>
   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<const char*>(&serialNumberLen), sizeof(uint32_t));
-  uint32_t issuerLen = htonl(mCert->derIssuer.len);
+  uint32_t issuerLen = htonl(cert->derIssuer.len);
   buf.Append(reinterpret_cast<const char*>(&issuerLen), sizeof(uint32_t));
-  buf.Append(reinterpret_cast<const char*>(mCert->serialNumber.data),
-             mCert->serialNumber.len);
-  buf.Append(reinterpret_cast<const char*>(mCert->derIssuer.data),
-             mCert->derIssuer.len);
+  buf.Append(reinterpret_cast<const char*>(cert->serialNumber.data),
+             cert->serialNumber.len);
+  buf.Append(reinterpret_cast<const char*>(cert->derIssuer.data),
+             cert->derIssuer.len);
 
   return Base64Encode(buf, aDbKey);
 }
 
 NS_IMETHODIMP
 nsNSSCertificate::GetWindowTitle(nsAString& aWindowTitle)
 {
   nsNSSShutDownPreventionLock locker;
--- a/security/manager/ssl/nsNSSCertificate.h
+++ b/security/manager/ssl/nsNSSCertificate.h
@@ -48,16 +48,20 @@ public:
   nsresult GetIsExtendedValidation(bool* aIsEV);
 
   enum EVStatus {
     ev_status_invalid = 0,
     ev_status_valid = 1,
     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();
 
   mozilla::ScopedCERTCertificate mCert;
   bool             mPermDelete;
   uint32_t         mCertType;
   nsresult CreateASN1Struct(nsIASN1Object** aRetVal);
   nsresult CreateTBSCertificateASN1Struct(nsIASN1Sequence** retSequence,
--- a/security/manager/ssl/nsNSSCertificateDB.cpp
+++ b/security/manager/ssl/nsNSSCertificateDB.cpp
@@ -123,42 +123,63 @@ nsNSSCertificateDB::FindCertByNickname(c
       pCert.forget(_rvCert);
       return NS_OK;
     }
   }
   return NS_ERROR_FAILURE;
 }
 
 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;
 
   nsNSSShutDownPreventionLock locker;
   if (isAlreadyShutDown()) {
     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<nsIX509Cert> 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)
   // The format of the key is the base64 encoding of the following:
   // 4 bytes: {0, 0, 0, 0} (this was intended to be the module ID, but it was
   //                        never implemented)
   // 4 bytes: {0, 0, 0, 0} (this was intended to be the slot ID, but it was
   //                        never implemented)
   // 4 bytes: <serial number length in big-endian order>
   // 4 bytes: <DER-encoded issuer distinguished name length in big-endian order>
   // n bytes: <bytes of serial number>
   // m bytes: <DER-encoded issuer distinguished name>
   nsAutoCString decoded;
-  nsAutoCString tmpDBKey(aDBkey);
+  nsAutoCString tmpDBKey(aDBKey);
   // Filter out any whitespace for backwards compatibility.
   tmpDBKey.StripWhitespace();
   nsresult rv = Base64Decode(tmpDBKey, decoded);
   if (NS_FAILED(rv)) {
     return rv;
   }
   if (decoded.Length() < 16) {
     return NS_ERROR_ILLEGAL_INPUT;
@@ -180,25 +201,17 @@ nsNSSCertificateDB::FindCertByDBKey(cons
   issuerSN.serialNumber.len = serialNumberLen;
   issuerSN.serialNumber.data = (unsigned char*)reader;
   reader += serialNumberLen;
   issuerSN.derIssuer.len = issuerLen;
   issuerSN.derIssuer.data = (unsigned char*)reader;
   reader += issuerLen;
   MOZ_ASSERT(reader == decoded.EndReading());
 
-  ScopedCERTCertificate cert(
-    CERT_FindCertByIssuerAndSN(CERT_GetDefaultCertDB(), &issuerSN));
-  if (cert) {
-    nsCOMPtr<nsIX509Cert> 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;
 }
 
 SECStatus
 collect_certs(void *arg, SECItem **certs, int numcerts)
 {
   CERTDERCerts *collectArgs;
   SECItem *cert;
--- a/security/manager/ssl/nsNSSCertificateDB.h
+++ b/security/manager/ssl/nsNSSCertificateDB.h
@@ -30,16 +30,21 @@ public:
   get_default_nickname(CERTCertificate *cert, nsIInterfaceRequestor* ctx,
                        nsCString &nickname,
                        const nsNSSShutDownPreventionLock &proofOfLock);
 
   static nsresult 
   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();
 
 private:
 
   static nsresult
   ImportValidCACertsInList(const mozilla::UniqueCERTCertList& filteredCerts,
                            nsIInterfaceRequestor* ctx,
--- a/security/manager/ssl/nsNSSComponent.cpp
+++ b/security/manager/ssl/nsNSSComponent.cpp
@@ -1,40 +1,46 @@
 /* -*- 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/. */
 
+#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"
 #include "nsClientAuthRemember.h"
 #include "nsComponentManagerUtils.h"
 #include "nsDirectoryServiceDefs.h"
 #include "nsIBufEntropyCollector.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 "nsNSSCertificateDB.h"
 #include "nsNSSHelper.h"
 #include "nsNSSShutDown.h"
 #include "nsServiceManagerUtils.h"
 #include "nsThreadUtils.h"
 #include "nsXULAppAPI.h"
 #include "nss.h"
 #include "p12plcy.h"
 #include "pkix/pkixnss.h"
@@ -44,17 +50,24 @@
 #include "sslerr.h"
 #include "sslproto.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;
@@ -389,16 +402,443 @@ nsNSSComponent::ShutdownSmartCardThread(
 void
 nsNSSComponent::ShutdownSmartCardThreads()
 {
   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 = reinterpret_cast<SID*>(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<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;
+}
+
+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<char, void(&)(void*)> 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()
 {
   nsNSSShutDownPreventionLock locker;
   SECMODModule* RootsModule = nullptr;
 
   // In the past we used SECMOD_AddNewModule to load our module containing
   // root CA certificates. This caused problems, refer to bug 176501.
@@ -1074,16 +1514,18 @@ nsNSSComponent::InitializeNSS()
     return NS_ERROR_UNEXPECTED;
   }
 
   DisableMD5();
   // Initialize the certverifier log before calling any functions that library.
   InitCertVerifierLog();
   LoadLoadableRoots();
 
+  MaybeEnableFamilySafetyCompatibility();
+
   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);
@@ -1139,17 +1581,16 @@ nsNSSComponent::InitializeNSS()
   if (!coService) {
     MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("Cannot initialize cert override service\n"));
     return NS_ERROR_FAILURE;
   }
 
   if (PK11_IsFIPS()) {
     Telemetry::Accumulate(Telemetry::FIPS_ENABLED, true);
   }
-
   MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("NSS Initialization done\n"));
   return NS_OK;
 }
 
 void
 nsNSSComponent::ShutdownNSS()
 {
   // Can be called both during init and profile change,
@@ -1341,16 +1782,18 @@ nsNSSComponent::Observe(nsISupports* aSu
                prefName.EqualsLiteral("security.pki.name_matching_mode")) {
       MutexAutoLock lock(mutex);
       setValidationOptions(false, lock);
 #ifdef DEBUG
     } else if (prefName.EqualsLiteral("security.test.built_in_root_hash")) {
       MutexAutoLock lock(mutex);
       mTestBuiltInRootHash = Preferences::GetString("security.test.built_in_root_hash");
 #endif // DEBUG
+    } else if (prefName.Equals(kFamilySafetyModePref)) {
+      MaybeEnableFamilySafetyCompatibility();
     } else {
       clearSessionCache = false;
     }
     if (clearSessionCache)
       SSL_ClearSessionCache();
   }
 
   return NS_OK;
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -286,16 +286,24 @@
     "description": "Pause time for asynchronous deferred finalization (ms)"
   },
   "DEVICE_RESET_REASON": {
     "expires_in_version": "never",
     "kind": "enumerated",
     "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",
     "description": "Was Fetch request initiated from the main thread?"
   },
   "FORCED_DEVICE_RESET_REASON": {
     "expires_in_version": "never",
     "kind": "enumerated",