bug 1239166 - platform work to support Microsoft Family Safety functionality draft
authorDavid Keeler <dkeeler@mozilla.com>
Tue, 12 Jan 2016 15:39:43 -0800
changeset 322138 f65824e2481ce08b6ff44ea824bf4209b0d41f0b
parent 318937 29258f59e5456a1a518ccce6b473b50c1173477e
child 513037 6ccadec408ba49b444a8d54d92f11fcf53e8777b
push id9534
push userbmo:dkeeler@mozilla.com
push dateFri, 15 Jan 2016 20:28:34 +0000
bugs1239166
milestone46.0a1
bug 1239166 - platform work to support Microsoft Family Safety functionality
security/manager/ssl/nsNSSComponent.cpp
security/manager/ssl/nsNSSComponent.h
--- a/security/manager/ssl/nsNSSComponent.cpp
+++ b/security/manager/ssl/nsNSSComponent.cpp
@@ -45,16 +45,21 @@
 #include "sslproto.h"
 
 #ifndef MOZ_NO_SMART_CARDS
 #include "nsSmartCardMonitor.h"
 #endif
 
 #ifdef XP_WIN
 #include "nsILocalFileWin.h"
+
+#include "windows.h" // this needs to be before the following includes
+#include "nsIWindowsRegKey.h"
+#include "Sddl.h"
+#include "Wincrypt.h"
 #endif
 
 using namespace mozilla;
 using namespace mozilla::psm;
 
 PRLogModuleInfo* gPIPNSSLog = nullptr;
 
 int nsNSSComponent::mInstanceCount = 0;
@@ -391,16 +396,253 @@ nsNSSComponent::ShutdownSmartCardThread(
 void
 nsNSSComponent::ShutdownSmartCardThreads()
 {
   delete mThreadList;
   mThreadList = nullptr;
 }
 #endif // MOZ_NO_SMART_CARDS
 
+#ifdef XP_WIN
+static bool
+GetUserSid(nsAString& sidString)
+{
+  WCHAR lpAccountName[256]; // TODO: buffer sizes, etc.
+  DWORD lcAccountName = sizeof(lpAccountName) / sizeof(lpAccountName[0]);
+  BOOL success = GetUserName(lpAccountName, &lcAccountName);
+  if (!success) {
+    printf("GetComputerName failed\n");
+    return false;
+  }
+  char sid_buffer[SECURITY_MAX_SID_SIZE];
+  SID* sid = reinterpret_cast<SID*>(sid_buffer);
+  DWORD cbSid = sizeof(sid_buffer);
+  SID_NAME_USE eUse;
+  WCHAR ReferencedDomainName[128];
+  DWORD cchReferencedDomainName = sizeof(ReferencedDomainName) / sizeof(ReferencedDomainName[0]);
+  success = LookupAccountName(nullptr, lpAccountName, sid, &cbSid, ReferencedDomainName, &cchReferencedDomainName, &eUse);
+  if (!success) {
+    printf("LookupAccountName failed\n");
+    return false;
+  }
+  LPTSTR StringSid; // TODO: scoped?
+  success = ConvertSidToStringSid(sid, &StringSid);
+  if (!success) {
+    printf("ConvertSidToStringSid failed\n");
+    return false;
+  }
+  sidString.Assign(StringSid);
+  LocalFree(StringSid);
+  return true;
+}
+
+bool
+nsNSSComponent::AccountHasFamilySafetyEnabled()
+{
+  printf("AccountHasFamilySafetyEnabled\n");
+  nsCOMPtr<nsIWindowsRegKey> regKey(
+    do_CreateInstance("@mozilla.org/windows-registry-key;1"));
+  if (!regKey) {
+    printf("couldn't create nsIWindowsRegKey\n");
+    return false;
+  }
+  // TODO: double-check this on 32-bit windows
+  uint32_t flags = nsIWindowsRegKey::ACCESS_READ | nsIWindowsRegKey::WOW64_64;
+  // TODO: this appears to be Windows-version-specific.
+  NS_NAMED_LITERAL_STRING(familySafetyPath,
+    "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Parental Controls\\Users");
+  nsresult rv = regKey->Open(nsIWindowsRegKey::ROOT_KEY_LOCAL_MACHINE,
+                             familySafetyPath, flags);
+  if (NS_FAILED(rv)) {
+    printf("couldn't open regKey\n");
+    return false;
+  }
+  uint32_t childCount;
+  rv = regKey->GetChildCount(&childCount);
+  if (NS_FAILED(rv)) {
+    printf("couldn't get child count\n");
+    return false;
+  }
+  nsAutoString sid;
+  if (!GetUserSid(sid)) {
+    printf("couldn't get sid\n");
+    return false;
+  }
+  printf("our sid is '%S'\n", sid.get());
+  for (uint32_t i = 0; i < childCount; i++) {
+    nsAutoString childName;
+    rv = regKey->GetChildName(i, childName);
+    if (NS_FAILED(rv)) {
+      printf("couldn't get child name\n");
+      return false;
+    }
+    printf("investigating '%S'\n", childName.get());
+    if (childName.Equals(sid)) {
+      nsCOMPtr<nsIWindowsRegKey> parentalControlsRegKey;
+      rv = regKey->OpenChild(childName, flags,
+                             getter_AddRefs(parentalControlsRegKey));
+      if (NS_FAILED(rv)) {
+        printf("couldn't open child key\n");
+        return false;
+      }
+      uint32_t parentalControlsOn;
+      rv = parentalControlsRegKey->ReadIntValue(
+        NS_LITERAL_STRING("Parental Controls On"), &parentalControlsOn);
+      if (NS_FAILED(rv)) {
+        printf("couldn't read Parental Controls On\n");
+        return false;
+      }
+      printf("successfully read value of parentalControlsOn\n");
+      return parentalControlsOn > 0;
+    }
+  }
+  printf("whelp, I guess we don't have Family Safety enabled\n");
+  return false;
+}
+
+static nsresult
+MaybeImportFamilySafetyRoot(PCCERT_CONTEXT certificate, bool& wasFamilySafetyRoot)
+{
+  wasFamilySafetyRoot = false;
+
+  nsCOMPtr<nsIX509CertDB> certDB(do_GetService(NS_X509CERTDB_CONTRACTID));
+  if (!certDB) {
+    return NS_ERROR_FAILURE;
+  }
+  const char* certificateBytes =
+    reinterpret_cast<const char*>(certificate->pbCertEncoded);
+  uint32_t certificateLength = certificate->cbCertEncoded;
+  nsCOMPtr<nsIX509Cert> xpcomCertificate;
+  nsresult rv = certDB->ConstructX509(certificateBytes, certificateLength,
+                                      getter_AddRefs(xpcomCertificate));
+  if (NS_FAILED(rv)) {
+    MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
+            ("MaybeImportFamilySafetyRoot: couldn't decode certificate"));
+    return rv;
+  }
+  // Looking for 'CN=Microsoft Family Safety'
+  // (TODO: tie it to an expected key hash or whatevs.)
+  nsAutoString subjectName;
+  rv = xpcomCertificate->GetSubjectName(subjectName);
+  if (NS_FAILED(rv)) {
+    MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
+            ("MaybeImportFamilySafetyRoot: couldn't get subject name"));
+    return rv;
+  }
+  MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
+          ("MaybeImportFamilySafetyRoot: subject name is '%s'",
+           NS_ConvertUTF16toUTF8(subjectName).get()));
+  if (subjectName.Equals(L"CN=Microsoft Family Safety")) {
+    wasFamilySafetyRoot = true;
+    // This seems silly, but it's the simplest way to permanently add this root
+    // given the APIs currently available.
+    nsDependentCSubstring certDER(certificateBytes, certificateLength);
+    rv = certDB->AddCert(certDER, "CTu,,", nullptr);
+    if (NS_FAILED(rv)) {
+      MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
+              ("MaybeImportFamilySafetyRoot: couldn't permanently add "
+               "Microsoft Family Safety certificate"));
+      return rv;
+    }
+    char* dbKey = nullptr;
+    rv = xpcomCertificate->GetDbKey(&dbKey); // TODO: this api should be improved
+    if (NS_FAILED(rv)) {
+      printf("GetDbKey failed\n.");
+      return rv;
+    }
+    // TODO: what if this is already set?
+    // TODO: make the pref string a constant
+    Preferences::SetCString("security.family_safety_root.imported_db_key",
+                            dbKey);
+    PR_Free(dbKey);
+  }
+  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";
+
+void
+nsNSSComponent::LoadFamilySafetyRoot()
+{
+  ScopedCertStore certstore(
+    CertOpenSystemStore(0, WindowsDefaultRootStoreName));
+  if (!certstore.get()) {
+    MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
+            ("Couldn't get certstore '%S'", WindowsDefaultRootStoreName));
+    return;
+  }
+  // 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; // We're done (we're only expecting one root).
+    }
+  }
+}
+
+void
+nsNSSComponent::UnloadFamilySafetyRoot()
+{
+  nsCOMPtr<nsIX509CertDB> certDB(do_GetService(NS_X509CERTDB_CONTRACTID));
+  if (!certDB) {
+    printf("UnloadFamilySafetyRoot: couldn't get certDB\n");
+    return; // TODO: log error, etc.?
+  }
+  auto dbKey = Preferences::GetCString(
+    "security.family_safety_root.imported_db_key");
+  if (!dbKey) {
+    printf("UnloadFamilySafetyRoot: Family Safety Root wasn't imported.\n");
+    return; // Family Safety Root wasn't imported
+  }
+  nsCOMPtr<nsIX509Cert> cert;
+  nsresult rv = certDB->FindCertByDBKey(dbKey, nullptr,
+                                        getter_AddRefs(cert));
+  if (NS_FAILED(rv)) {
+    printf("UnloadFamilySafetyRoot: couldn't find Family Safety Root\n");
+    return; // TODO: log error, etc.?
+  }
+  rv = cert->MarkForPermDeletion();
+  if (NS_FAILED(rv)) {
+    printf("UnloadFamilySafetyRoot: couldn't delete Family Safety Root\n");
+    return; // TODO: log error, etc.?
+  }
+  printf("UnloadFamilySafetyRoot: success\n");
+  Preferences::ClearUser("security.family_safety_root.imported_db_key");
+}
+#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.
@@ -1050,16 +1292,24 @@ nsNSSComponent::InitializeNSS()
     return NS_ERROR_UNEXPECTED;
   }
 
   DisableMD5();
   // Initialize the certverifier log before calling any functions that library.
   InitCertVerifierLog();
   LoadLoadableRoots();
 
+#ifdef XP_WIN
+  if (AccountHasFamilySafetyEnabled()) {
+    LoadFamilySafetyRoot();
+  } else {
+    UnloadFamilySafetyRoot();
+  }
+#endif
+
   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);
@@ -1106,17 +1356,16 @@ nsNSSComponent::InitializeNSS()
   // Initialize the site security service
   nsCOMPtr<nsISiteSecurityService> sssService =
     do_GetService(NS_SSSERVICE_CONTRACTID);
   if (!sssService) {
     MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("Cannot initialize site security service\n"));
     return NS_ERROR_FAILURE;
   }
 
-
   MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("NSS Initialization done\n"));
   return NS_OK;
 }
 
 void
 nsNSSComponent::ShutdownNSS()
 {
   // Can be called both during init and profile change,
--- a/security/manager/ssl/nsNSSComponent.h
+++ b/security/manager/ssl/nsNSSComponent.h
@@ -148,16 +148,21 @@ protected:
   virtual ~nsNSSComponent();
 
 private:
   nsresult InitializeNSS();
   void ShutdownNSS();
 
   void LoadLoadableRoots();
   void UnloadLoadableRoots();
+#ifdef XP_WIN
+  bool AccountHasFamilySafetyEnabled();
+  void LoadFamilySafetyRoot();
+  void UnloadFamilySafetyRoot();
+#endif
   void setValidationOptions(bool isInitialSetting,
                             const mozilla::MutexAutoLock& lock);
   nsresult setEnabledTLSVersions();
   nsresult InitializePIPNSSBundle();
   nsresult ConfigureInternalPKCS11Token();
   nsresult RegisterObservers();
 
   void DoProfileBeforeChange();