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 291434 201173cd688629e57570cf67d1e91c2ae84b3f5f
parent 291433 45de51023c1f88e77dea266467ea2f1a1da526f1
child 291435 6166a6070ec2e78b2994e1670c2fb62db27cb6e2
push id19656
push usergwagner@mozilla.com
push dateMon, 04 Apr 2016 13:43:23 +0000
treeherderb2g-inbound@e99061fde28a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersfroydnj, mgoodwin, mhowell, rbarnes, vladan
bugs1239166
milestone48.0a1
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",