Bug 1464828 - DAR API with libsecret and NSS back-end, r=keeler,m_and_m,froydnj
authorFranziskus Kiefer <franziskuskiefer@gmail.com>
Thu, 26 Jul 2018 16:05:35 +0200
changeset 483689 9df44f0fe08280fb29d78485a124b1d837909bd6
parent 483688 3637493053343f40e1d239ab5721893cce9f3d08
child 483690 d895bcb50ce7be0684ebca87e5748e58d7e09278
push id9719
push userffxbld-merge
push dateFri, 24 Aug 2018 17:49:46 +0000
treeherdermozilla-beta@719ec98fba77 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskeeler, m_and_m, froydnj
bugs1464828
milestone63.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 1464828 - DAR API with libsecret and NSS back-end, r=keeler,m_and_m,froydnj This implements an API in `nsIOSKeyStore.idl` and `OSKeyStore.cpp` to encrypt and decrypt bytes with a key that is stored in the OS key store. There are two OS adapters in this patch. Libsecret is used on Linux if available. The NSS key store is used as fallback if no OS specific key store is implemented. Differential Revision: https://phabricator.services.mozilla.com/D1858
config/system-headers.mozbuild
security/manager/ssl/LibSecret.cpp
security/manager/ssl/LibSecret.h
security/manager/ssl/NSSKeyStore.cpp
security/manager/ssl/NSSKeyStore.h
security/manager/ssl/OSKeyStore.cpp
security/manager/ssl/OSKeyStore.h
security/manager/ssl/SSLServerCertVerification.cpp
security/manager/ssl/moz.build
security/manager/ssl/nsIOSKeyStore.idl
security/manager/ssl/nsNSSCertValidity.cpp
security/manager/ssl/nsNSSCertificateDB.cpp
security/manager/ssl/nsNSSModule.cpp
security/manager/ssl/nsPKCS12Blob.h
security/manager/ssl/tests/unit/test_oskeystore.js
security/manager/ssl/tests/unit/xpcshell.ini
security/nss.symbols
toolkit/library/moz.build
toolkit/moz.configure
--- a/config/system-headers.mozbuild
+++ b/config/system-headers.mozbuild
@@ -1341,8 +1341,25 @@ if CONFIG['ENABLE_BIGINT']:
     ]
 
 if CONFIG['MOZ_WAYLAND']:
     system_headers += [
         'xkbcommon/xkbcommon.h',
         'wayland-client.h',
         'wayland-egl.h',
     ]
+
+if CONFIG['MOZ_LIB_SECRET']:
+    system_headers += [
+        'libsecret/secret.h',
+        'libsecret/secret-attributes.h',
+        'libsecret/secret-collection.h',
+        'libsecret/secret-enum-types.h',
+        'libsecret/secret-item.h',
+        'libsecret/secret-password.h',
+        'libsecret/secret-paths.h',
+        'libsecret/secret-prompt.h',
+        'libsecret/secret-schema.h',
+        'libsecret/secret-schemas.h',
+        'libsecret/secret-types.h',
+        'libsecret/secret-value.h',
+        'libsecret/secret-service.h',
+    ]
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/LibSecret.cpp
@@ -0,0 +1,196 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "LibSecret.h"
+
+#include <gmodule.h>
+
+#include "mozilla/Base64.h"
+
+// This is the implementation of LibSecret, an instantiation of OSKeyStore for
+// Linux.
+
+using namespace mozilla;
+
+LazyLogModule gLibSecretLog("libsecret");
+
+LibSecret::LibSecret() {}
+
+LibSecret::~LibSecret() {}
+
+static const SecretSchema kSchema = {
+  "mozilla.firefox",
+  SECRET_SCHEMA_NONE,
+  { { "string", SECRET_SCHEMA_ATTRIBUTE_STRING }, /* the label */
+    { "NULL", SECRET_SCHEMA_ATTRIBUTE_STRING } }
+};
+
+nsresult
+GetScopedServices(ScopedSecretService& aSs, ScopedSecretCollection& aSc)
+{
+  GError* raw_error = nullptr;
+  aSs = ScopedSecretService(secret_service_get_sync(
+    static_cast<SecretServiceFlags>(
+      SECRET_SERVICE_OPEN_SESSION), // SecretServiceFlags
+    nullptr,                        // GCancellable
+    &raw_error));
+  ScopedGError error(raw_error);
+  if (error || !aSs) {
+    MOZ_LOG(gLibSecretLog, LogLevel::Debug, ("Couldn't get a secret service"));
+    return NS_ERROR_FAILURE;
+  }
+
+  aSc = ScopedSecretCollection(
+    secret_collection_for_alias_sync(aSs.get(),
+                                     "default",
+                                     static_cast<SecretCollectionFlags>(0),
+                                     nullptr, // GCancellable
+                                     &raw_error));
+  error.reset(raw_error);
+  if (!aSc) {
+    MOZ_LOG(
+      gLibSecretLog, LogLevel::Debug, ("Couldn't get a secret collection"));
+    return NS_ERROR_FAILURE;
+  }
+  return NS_OK;
+}
+
+nsresult
+LibSecret::Lock()
+{
+  ScopedSecretService ss;
+  ScopedSecretCollection sc;
+  if (NS_FAILED(GetScopedServices(ss, sc))) {
+    return NS_ERROR_FAILURE;
+  }
+
+  GError* raw_error = nullptr;
+  GList* collections = nullptr;
+  ScopedGList collectionList(g_list_append(collections, sc.get()));
+  int numLocked = secret_service_lock_sync(ss.get(),
+                                           collectionList.get(),
+                                           nullptr, // GCancellable
+                                           nullptr, // list of locked items
+                                           &raw_error);
+  ScopedGError error(raw_error);
+  if (numLocked != 1) {
+    MOZ_LOG(
+      gLibSecretLog, LogLevel::Debug, ("Couldn't lock secret collection"));
+    return NS_ERROR_FAILURE;
+  }
+  return NS_OK;
+}
+
+nsresult
+LibSecret::Unlock()
+{
+  // Accessing the secret service unlocks it. So calling this separately isn't
+  // actually necessary.
+  ScopedSecretService ss;
+  ScopedSecretCollection sc;
+  if (NS_FAILED(GetScopedServices(ss, sc))) {
+    return NS_ERROR_FAILURE;
+  }
+  return NS_OK;
+}
+
+nsresult
+LibSecret::StoreSecret(const nsACString& aSecret, const nsACString& aLabel)
+{
+  GError* raw_error = nullptr;
+  bool stored = secret_password_store_sync(&kSchema,
+                                           SECRET_COLLECTION_DEFAULT,
+                                           PromiseFlatCString(aLabel).get(),
+                                           PromiseFlatCString(aSecret).get(),
+                                           nullptr, // GCancellable
+                                           &raw_error,
+                                           "string",
+                                           PromiseFlatCString(aLabel).get(),
+                                           nullptr);
+  ScopedGError error(raw_error);
+  if (raw_error) {
+    MOZ_LOG(gLibSecretLog, LogLevel::Debug, ("Error storing secret"));
+    return NS_ERROR_FAILURE;
+  }
+
+  return stored ? NS_OK : NS_ERROR_FAILURE;
+}
+
+nsresult
+LibSecret::DeleteSecret(const nsACString& aLabel)
+{
+  GError* raw_error = nullptr;
+  bool r = secret_password_clear_sync(&kSchema,
+                                      nullptr, // GCancellable
+                                      &raw_error,
+                                      "string",
+                                      PromiseFlatCString(aLabel).get(),
+                                      nullptr);
+  ScopedGError error(raw_error);
+  if (raw_error) {
+    MOZ_LOG(gLibSecretLog, LogLevel::Debug, ("Error deleting secret"));
+    return NS_ERROR_FAILURE;
+  }
+
+  return r ? NS_OK : NS_ERROR_FAILURE;
+}
+
+nsresult
+LibSecret::RetrieveSecret(const nsACString& aLabel,
+                          /* out */ nsACString& aSecret)
+{
+  GError* raw_error = nullptr;
+  aSecret.Truncate();
+  ScopedPassword s(secret_password_lookup_sync(&kSchema,
+                                               nullptr, // GCancellable
+                                               &raw_error,
+                                               "string",
+                                               PromiseFlatCString(aLabel).get(),
+                                               nullptr));
+  ScopedGError error(raw_error);
+  if (raw_error || !s) {
+    MOZ_LOG(gLibSecretLog,
+            LogLevel::Debug,
+            ("Error retrieving secret or didn't find it"));
+    return NS_ERROR_FAILURE;
+  }
+  aSecret.Assign(s.get(), strlen(s.get()));
+
+  return NS_OK;
+}
+
+bool
+LibSecret::SecretAvailable(const nsACString& aLabel)
+{
+  nsAutoCString secret;
+  nsresult rv = RetrieveSecret(aLabel, secret);
+  if (NS_FAILED(rv) || secret.Length() == 0) {
+    return false;
+  }
+  return true;
+}
+
+nsresult
+LibSecret::EncryptDecrypt(const nsACString& aLabel,
+                          const std::vector<uint8_t>& inBytes,
+                          std::vector<uint8_t>& outBytes,
+                          bool encrypt)
+{
+  nsAutoCString secret;
+  nsresult rv = RetrieveSecret(aLabel, secret);
+  if (NS_FAILED(rv) || secret.Length() == 0) {
+    return NS_ERROR_FAILURE;
+  }
+
+  uint8_t* p = BitwiseCast<uint8_t*, const char*>(secret.BeginReading());
+  std::vector<uint8_t> buf(p, p + secret.Length());
+  UniquePK11SymKey symKey;
+  rv = BuildAesGcmKey(buf, symKey);
+  if (NS_FAILED(rv)) {
+    return NS_ERROR_FAILURE;
+  }
+  return DoCipher(symKey, inBytes, outBytes, encrypt);
+}
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/LibSecret.h
@@ -0,0 +1,77 @@
+/* -*- 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/. */
+
+#ifndef MOZ_LIB_SECRET
+#error LibSecret OSKeyStore included when MOZ_LIB_SECRET is not defined!
+#endif
+
+#ifndef LibSecret_h
+#define LibSecret_h
+
+#include "OSKeyStore.h"
+
+#include <libsecret/secret.h>
+#include <memory>
+#include <vector>
+
+#include "nsString.h"
+
+struct ScopedDelete
+{
+  void operator()(SecretService* ss) { if (ss) g_object_unref(ss); }
+  void operator()(SecretCollection* sc) { if (sc) g_object_unref(sc); }
+  void operator()(GError* error) { if (error) g_error_free(error); }
+  void operator()(GList* list) { if (list) g_list_free(list); }
+  void operator()(SecretValue* val) { if (val) secret_value_unref(val); }
+  void operator()(SecretItem* val) { if (val) g_object_unref(val); }
+  void operator()(char* val) { if (val) secret_password_free(val); }
+};
+
+template<class T>
+struct ScopedMaybeDelete
+{
+  void operator()(T* ptr)
+  {
+    if (ptr) {
+      ScopedDelete del;
+      del(ptr);
+    }
+  }
+};
+
+#define SCOPED(x) typedef std::unique_ptr<x, ScopedMaybeDelete<x>> Scoped##x
+
+SCOPED(SecretService);
+SCOPED(SecretCollection);
+SCOPED(GError);
+SCOPED(GList);
+SCOPED(SecretValue);
+SCOPED(SecretItem);
+typedef std::unique_ptr<char, ScopedMaybeDelete<char>> ScopedPassword;
+
+#undef SCOPED
+
+class LibSecret final : public AbstractOSKeyStore
+{
+public:
+  LibSecret();
+  virtual nsresult StoreSecret(const nsACString& secret,
+                               const nsACString& label) override;
+  virtual nsresult DeleteSecret(const nsACString& label) override;
+  virtual nsresult Lock() override;
+  virtual nsresult Unlock() override;
+  virtual nsresult EncryptDecrypt(const nsACString& label,
+                                  const std::vector<uint8_t>& inBytes,
+                                  std::vector<uint8_t>& outBytes,
+                                  bool encrypt) override;
+  virtual bool SecretAvailable(const nsACString& label) override;
+
+  nsresult RetrieveSecret(const nsACString& label,
+                          /* out */ nsACString& secret);
+  virtual ~LibSecret();
+};
+
+#endif // LibSecret_h
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/NSSKeyStore.cpp
@@ -0,0 +1,228 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "NSSKeyStore.h"
+
+#include "mozilla/Base64.h"
+#include "mozilla/SyncRunnable.h"
+#include "nsPK11TokenDB.h"
+
+/* Implementing OSKeyStore when there is no platform specific one.
+ * This key store instead puts the keys into the NSS DB.
+ */
+
+using namespace mozilla;
+using mozilla::SyncRunnable;
+
+LazyLogModule gNSSKeyStoreLog("nsskeystore");
+
+NSSKeyStore::NSSKeyStore()
+{
+  MOZ_ASSERT(XRE_IsParentProcess());
+  if (!XRE_IsParentProcess()) {
+    // This shouldn't happen as this is only initialised when creating the
+    // OSKeyStore, which is ParentProcessOnly.
+    return;
+  }
+  Unused << EnsureNSSInitializedChromeOrContent();
+  Unused << InitToken();
+}
+NSSKeyStore::~NSSKeyStore() {}
+
+bool
+NSSKeyStore::IsNSSKeyStore()
+{
+  return true;
+}
+
+nsresult
+NSSKeyStore::InitToken()
+{
+  if (!mSlot) {
+    mSlot = UniquePK11SlotInfo(PK11_GetInternalKeySlot());
+    if (!mSlot) {
+      MOZ_LOG(
+        gNSSKeyStoreLog, LogLevel::Debug, ("Error getting internal key slot"));
+      return NS_ERROR_NOT_AVAILABLE;
+    }
+  }
+  return NS_OK;
+}
+
+nsresult
+NSSKeyStoreMainThreadLock(PK11SlotInfo* aSlot)
+{
+  nsCOMPtr<nsIPK11Token> token = new nsPK11Token(aSlot);
+  return token->LogoutSimple();
+}
+
+nsresult
+NSSKeyStore::Lock()
+{
+  NS_ENSURE_STATE(mSlot);
+
+  if (!NS_IsMainThread()) {
+    nsCOMPtr<nsIThread> mainThread;
+    nsresult rv = NS_GetMainThread(getter_AddRefs(mainThread));
+    if (NS_FAILED(rv)) {
+      return NS_ERROR_FAILURE;
+    }
+
+    // Forward to the main thread synchronously.
+    SyncRunnable::DispatchToThread(
+      mainThread,
+      new SyncRunnable(NS_NewRunnableFunction(
+        "NSSKeyStoreMainThreadLock",
+        [slot = mSlot.get()]() { NSSKeyStoreMainThreadLock(slot); })));
+
+    return NS_OK;
+  }
+
+  return NSSKeyStoreMainThreadLock(mSlot.get());
+}
+
+nsresult
+NSSKeyStoreMainThreadUnlock(PK11SlotInfo* aSlot)
+{
+  nsCOMPtr<nsIPK11Token> token = new nsPK11Token(aSlot);
+  return token->Login(false /* force */);
+}
+
+nsresult
+NSSKeyStore::Unlock()
+{
+  NS_ENSURE_STATE(mSlot);
+
+  if (!NS_IsMainThread()) {
+    nsCOMPtr<nsIThread> mainThread;
+    nsresult rv = NS_GetMainThread(getter_AddRefs(mainThread));
+    if (NS_FAILED(rv)) {
+      return NS_ERROR_FAILURE;
+    }
+
+    // Forward to the main thread synchronously.
+    SyncRunnable::DispatchToThread(
+      mainThread,
+      new SyncRunnable(NS_NewRunnableFunction(
+        "NSSKeyStoreMainThreadUnlock",
+        [slot = mSlot.get()]() { NSSKeyStoreMainThreadUnlock(slot); })));
+
+    return NS_OK;
+  }
+
+  return NSSKeyStoreMainThreadUnlock(mSlot.get());
+}
+
+nsresult
+NSSKeyStore::StoreSecret(const nsACString& aSecret, const nsACString& aLabel)
+{
+  NS_ENSURE_STATE(mSlot);
+  if (NS_FAILED(Unlock())) {
+    MOZ_LOG(gNSSKeyStoreLog, LogLevel::Debug, ("Error unlocking NSS key db"));
+    return NS_ERROR_FAILURE;
+  }
+
+  uint8_t* p = BitwiseCast<uint8_t*, const char*>(aSecret.BeginReading());
+  UniqueSECItem key(SECITEM_AllocItem(nullptr, nullptr, aSecret.Length()));
+  if (!key) {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+  key->type = siBuffer;
+  memcpy(key->data, p, aSecret.Length());
+  key->len = aSecret.Length();
+  UniquePK11SymKey symKey(PK11_ImportSymKey(mSlot.get(),
+                                            CKM_AES_GCM,
+                                            PK11_OriginUnwrap,
+                                            CKA_DECRYPT | CKA_ENCRYPT,
+                                            key.get(),
+                                            nullptr));
+  if (!symKey) {
+    MOZ_LOG(gNSSKeyStoreLog, LogLevel::Debug, ("Error creating NSS SymKey"));
+    return NS_ERROR_FAILURE;
+  }
+  UniquePK11SymKey storedKey(
+    PK11_ConvertSessionSymKeyToTokenSymKey(symKey.get(), nullptr));
+  if (!storedKey) {
+    MOZ_LOG(
+      gNSSKeyStoreLog, LogLevel::Debug, ("Error storing NSS SymKey in DB"));
+    return NS_ERROR_FAILURE;
+  }
+  SECStatus srv =
+    PK11_SetSymKeyNickname(storedKey.get(), PromiseFlatCString(aLabel).get());
+  if (srv != SECSuccess) {
+    MOZ_LOG(gNSSKeyStoreLog, LogLevel::Debug, ("Error naming NSS SymKey"));
+    (void)PK11_DeleteTokenSymKey(storedKey.get());
+    return NS_ERROR_FAILURE;
+  }
+
+  return NS_OK;
+}
+
+nsresult
+NSSKeyStore::DeleteSecret(const nsACString& aLabel)
+{
+  NS_ENSURE_STATE(mSlot);
+  if (NS_FAILED(Unlock())) {
+    MOZ_LOG(gNSSKeyStoreLog, LogLevel::Debug, ("Error unlocking NSS key db"));
+    return NS_ERROR_FAILURE;
+  }
+
+  UniquePK11SymKey symKey(PK11_ListFixedKeysInSlot(
+    mSlot.get(), const_cast<char*>(PromiseFlatCString(aLabel).get()), nullptr));
+  if (!symKey) {
+    // Couldn't find the key or something is wrong. Be nice.
+    return NS_OK;
+  }
+  for (PK11SymKey* tmp = symKey.get(); tmp; tmp = PK11_GetNextSymKey(tmp)) {
+    SECStatus srv = PK11_DeleteTokenSymKey(tmp);
+    if (srv != SECSuccess) {
+      MOZ_LOG(gNSSKeyStoreLog, LogLevel::Debug, ("Error deleting NSS SymKey"));
+      return NS_ERROR_FAILURE;
+    }
+  }
+  return NS_OK;
+}
+
+bool
+NSSKeyStore::SecretAvailable(const nsACString& aLabel)
+{
+  if (!mSlot) {
+    return false;
+  }
+  if (NS_FAILED(Unlock())) {
+    MOZ_LOG(gNSSKeyStoreLog, LogLevel::Debug, ("Error unlocking NSS key db"));
+    return false;
+  }
+
+  UniquePK11SymKey symKey(PK11_ListFixedKeysInSlot(
+    mSlot.get(), const_cast<char*>(PromiseFlatCString(aLabel).get()), nullptr));
+  if (!symKey) {
+    return false;
+  }
+  return true;
+}
+
+nsresult
+NSSKeyStore::EncryptDecrypt(const nsACString& aLabel,
+                            const std::vector<uint8_t>& inBytes,
+                            std::vector<uint8_t>& outBytes,
+                            bool encrypt)
+{
+  NS_ENSURE_STATE(mSlot);
+  if (NS_FAILED(Unlock())) {
+    MOZ_LOG(gNSSKeyStoreLog, LogLevel::Debug, ("Error unlocking NSS key db"));
+    return NS_ERROR_FAILURE;
+  }
+
+  UniquePK11SymKey symKey(PK11_ListFixedKeysInSlot(
+    mSlot.get(), const_cast<char*>(PromiseFlatCString(aLabel).get()), nullptr));
+  if (!symKey) {
+    MOZ_LOG(
+      gNSSKeyStoreLog, LogLevel::Debug, ("Error finding key for given label"));
+    return NS_ERROR_FAILURE;
+  }
+  return DoCipher(symKey, inBytes, outBytes, encrypt);
+}
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/NSSKeyStore.h
@@ -0,0 +1,35 @@
+/* -*- 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/. */
+
+#ifndef NSSKeyStore_h
+#define NSSKeyStore_h
+
+#include "OSKeyStore.h"
+#include "nsString.h"
+
+class NSSKeyStore final : public AbstractOSKeyStore
+{
+public:
+  NSSKeyStore();
+  virtual nsresult StoreSecret(const nsACString& secret,
+                               const nsACString& label) override;
+  virtual nsresult DeleteSecret(const nsACString& label) override;
+  virtual nsresult Lock() override;
+  virtual nsresult Unlock() override;
+  virtual nsresult EncryptDecrypt(const nsACString& label,
+                                  const std::vector<uint8_t>& inBytes,
+                                  std::vector<uint8_t>& outBytes,
+                                  bool encrypt) override;
+  virtual bool SecretAvailable(const nsACString& label) override;
+  virtual bool IsNSSKeyStore() override;
+  virtual ~NSSKeyStore();
+
+private:
+  nsresult InitToken();
+  UniquePK11SlotInfo mSlot = nullptr;
+};
+
+#endif // NSSKeyStore_h
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/OSKeyStore.cpp
@@ -0,0 +1,757 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "OSKeyStore.h"
+
+#include "mozilla/Base64.h"
+#include "mozilla/dom/Promise.h"
+#include "nsIRandomGenerator.h"
+#include "pk11pub.h"
+
+#ifdef MOZ_LIB_SECRET
+#include "LibSecret.h"
+#else
+#include "NSSKeyStore.h"
+#endif
+
+NS_IMPL_ISUPPORTS(OSKeyStore, nsIOSKeyStore)
+
+using namespace mozilla;
+using dom::Promise;
+
+mozilla::LazyLogModule gOSKeyStoreLog("oskeystore");
+
+OSKeyStore::OSKeyStore()
+  : mMutex("OSKeyStore-mutex")
+{
+#ifdef MOZ_LIB_SECRET
+  mKs.reset(new LibSecret());
+#else
+  mKs.reset(new NSSKeyStore());
+#endif
+}
+OSKeyStore::~OSKeyStore() {}
+
+static nsresult
+GenerateRandom(std::vector<uint8_t>& r)
+{
+  if (r.size() < 1) {
+    return NS_ERROR_INVALID_ARG;
+  }
+  UniquePK11SlotInfo slot(PK11_GetInternalSlot());
+  if (!slot) {
+    return NS_ERROR_FAILURE;
+  }
+
+  SECStatus srv = PK11_GenerateRandomOnSlot(slot.get(), r.data(), r.size());
+  if (srv != SECSuccess) {
+    r.clear();
+    return NS_ERROR_FAILURE;
+  }
+
+  return NS_OK;
+}
+
+nsresult
+OSKeyStore::SecretAvailable(const nsACString& aLabel,
+                            /* out */ bool* aAvailable)
+{
+  MutexAutoLock lock(mMutex);
+  NS_ENSURE_STATE(mKs);
+  nsAutoCString label = mLabelPrefix + aLabel;
+  *aAvailable = mKs->SecretAvailable(label);
+  return NS_OK;
+}
+
+nsresult
+OSKeyStore::GenerateSecret(const nsACString& aLabel,
+                           /* out */ nsACString& aRecoveryPhrase)
+{
+  MutexAutoLock lock(mMutex);
+  NS_ENSURE_STATE(mKs);
+  size_t keyByteLength = mKs->GetKeyByteLength();
+  std::vector<uint8_t> secret(keyByteLength);
+  nsresult rv = GenerateRandom(secret);
+  if (NS_FAILED(rv) || secret.size() != keyByteLength) {
+    return NS_ERROR_FAILURE;
+  }
+  nsAutoCString secretString;
+  secretString.Assign(BitwiseCast<char*, uint8_t*>(secret.data()),
+                      secret.size());
+
+  nsAutoCString base64;
+  rv = Base64Encode(secretString, base64);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  nsAutoCString label = mLabelPrefix + aLabel;
+  rv = mKs->StoreSecret(secretString, label);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  aRecoveryPhrase = base64;
+  return NS_OK;
+}
+
+nsresult
+OSKeyStore::RecoverSecret(const nsACString& aLabel,
+                          const nsACString& aRecoveryPhrase)
+{
+  MutexAutoLock lock(mMutex);
+  NS_ENSURE_STATE(mKs);
+  nsAutoCString secret;
+  nsresult rv = Base64Decode(aRecoveryPhrase, secret);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  nsAutoCString label = mLabelPrefix + aLabel;
+  rv = mKs->StoreSecret(secret, label);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  return NS_OK;
+}
+
+nsresult
+OSKeyStore::DeleteSecret(const nsACString& aLabel)
+{
+  MutexAutoLock lock(mMutex);
+  NS_ENSURE_STATE(mKs);
+  nsAutoCString label = mLabelPrefix + aLabel;
+  return mKs->DeleteSecret(label);
+}
+
+enum Cipher
+{
+  Encrypt = true,
+  Decrypt = false
+};
+
+nsresult
+OSKeyStore::EncryptBytes(const nsACString& aLabel,
+                         uint32_t inLen,
+                         uint8_t* inBytes,
+                         /*out*/ nsACString& aEncryptedBase64Text)
+{
+  MutexAutoLock lock(mMutex);
+  NS_ENSURE_STATE(mKs);
+  NS_ENSURE_ARG_POINTER(inBytes);
+
+  nsAutoCString label = mLabelPrefix + aLabel;
+  aEncryptedBase64Text.Truncate();
+  const std::vector<uint8_t> in(inBytes, inBytes + inLen);
+  std::vector<uint8_t> outBytes;
+  nsresult rv = mKs->EncryptDecrypt(label, in, outBytes, Cipher::Encrypt);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  nsAutoCString ciphertext;
+  ciphertext.Assign(BitwiseCast<char*, uint8_t*>(outBytes.data()),
+                    outBytes.size());
+
+  nsAutoCString base64ciphertext;
+  rv = Base64Encode(ciphertext, base64ciphertext);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  aEncryptedBase64Text.Assign(base64ciphertext);
+  return NS_OK;
+}
+
+nsresult
+OSKeyStore::DecryptBytes(const nsACString& aLabel,
+                         const nsACString& aEncryptedBase64Text,
+                         /*out*/ uint32_t* outLen,
+                         /*out*/ uint8_t** outBytes)
+{
+  MutexAutoLock lock(mMutex);
+  NS_ENSURE_STATE(mKs);
+  NS_ENSURE_ARG_POINTER(outLen);
+  NS_ENSURE_ARG_POINTER(outBytes);
+  *outLen = 0;
+  *outBytes = nullptr;
+
+  nsAutoCString ciphertext;
+  nsresult rv = Base64Decode(aEncryptedBase64Text, ciphertext);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  nsAutoCString label = mLabelPrefix + aLabel;
+  uint8_t* tmp = BitwiseCast<uint8_t*, const char*>(ciphertext.BeginReading());
+  const std::vector<uint8_t> ciphertextBytes(tmp, tmp + ciphertext.Length());
+  std::vector<uint8_t> plaintextBytes;
+  rv = mKs->EncryptDecrypt(
+    label, ciphertextBytes, plaintextBytes, Cipher::Decrypt);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  *outBytes = (uint8_t*)moz_xmalloc(plaintextBytes.size());
+  if (!*outBytes) {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+  memcpy(*outBytes, plaintextBytes.data(), plaintextBytes.size());
+  *outLen = plaintextBytes.size();
+  return NS_OK;
+}
+
+nsresult
+OSKeyStore::Lock()
+{
+  MutexAutoLock lock(mMutex);
+  NS_ENSURE_STATE(mKs);
+  return mKs->Lock();
+}
+
+nsresult
+OSKeyStore::Unlock()
+{
+  MutexAutoLock lock(mMutex);
+  NS_ENSURE_STATE(mKs);
+  return mKs->Unlock();
+}
+
+NS_IMETHODIMP
+OSKeyStore::GetIsNSSKeyStore(bool* aNSSKeyStore)
+{
+  MutexAutoLock lock(mMutex);
+  NS_ENSURE_ARG_POINTER(aNSSKeyStore);
+  NS_ENSURE_STATE(mKs);
+  *aNSSKeyStore = mKs->IsNSSKeyStore();
+  return NS_OK;
+}
+
+// Async interfaces that return promises because the key store implementation
+// might block, e.g. asking for a password.
+
+static nsresult
+GetPromise(JSContext* aCx, /* out */ RefPtr<Promise>& aPromise)
+{
+  nsIGlobalObject* globalObject = xpc::CurrentNativeGlobal(aCx);
+  if (NS_WARN_IF(!globalObject)) {
+    return NS_ERROR_UNEXPECTED;
+  }
+  ErrorResult result;
+  aPromise = Promise::Create(globalObject, result);
+  if (NS_WARN_IF(result.Failed())) {
+    return result.StealNSResult();
+  }
+  return NS_OK;
+}
+
+nsresult
+OSKeyStore::FinishAsync(RefPtr<Promise>& aPromiseHandle,
+                        /* out*/ Promise** promiseOut,
+                        const nsACString& aName,
+                        nsCOMPtr<nsIRunnable> aRunnable)
+{
+  // Note that if the NSS PK11 token is handling the key store, locking and
+  // unlocking functions will be pushed to the main thread again.
+  nsCOMPtr<nsIThread> thread;
+  nsresult rv = NS_NewNamedThread(aName, getter_AddRefs(thread), aRunnable);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  aPromiseHandle.forget(promiseOut);
+  return NS_OK;
+}
+
+void
+BackgroundUnlock(RefPtr<Promise>& aPromise, RefPtr<OSKeyStore> self)
+{
+  nsAutoCString recovery;
+  nsresult rv = self->Unlock();
+  nsCOMPtr<nsIRunnable> runnable(NS_NewRunnableFunction(
+    "BackgroundUnlockOSKSResolve", [rv, aPromise = std::move(aPromise)]() {
+      if (NS_FAILED(rv)) {
+        aPromise->MaybeReject(rv);
+      } else {
+        aPromise->MaybeResolveWithUndefined();
+      }
+    }));
+  NS_DispatchToMainThread(runnable.forget());
+}
+
+NS_IMETHODIMP
+OSKeyStore::AsyncUnlock(JSContext* aCx, Promise** promiseOut)
+{
+  NS_ENSURE_ARG_POINTER(aCx);
+
+  RefPtr<Promise> promiseHandle;
+  nsresult rv = GetPromise(aCx, promiseHandle);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  RefPtr<OSKeyStore> self = this;
+  nsCOMPtr<nsIRunnable> runnable(
+    NS_NewRunnableFunction("BackgroundUnlock", [self, promiseHandle]() mutable {
+      BackgroundUnlock(promiseHandle, self);
+    }));
+
+  return FinishAsync(
+    promiseHandle, promiseOut, NS_LITERAL_CSTRING("UnlockKSThread"), runnable);
+}
+
+void
+BackgroundLock(RefPtr<Promise>& aPromise, RefPtr<OSKeyStore> self)
+{
+  nsresult rv = self->Lock();
+  nsCOMPtr<nsIRunnable> runnable(NS_NewRunnableFunction(
+    "BackgroundLockOSKSResolve", [rv, aPromise = std::move(aPromise)]() {
+      if (NS_FAILED(rv)) {
+        aPromise->MaybeReject(rv);
+      } else {
+        aPromise->MaybeResolveWithUndefined();
+      }
+    }));
+  NS_DispatchToMainThread(runnable.forget());
+}
+
+NS_IMETHODIMP
+OSKeyStore::AsyncLock(JSContext* aCx, Promise** promiseOut)
+{
+  NS_ENSURE_ARG_POINTER(aCx);
+
+  RefPtr<Promise> promiseHandle;
+  nsresult rv = GetPromise(aCx, promiseHandle);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  RefPtr<OSKeyStore> self = this;
+  nsCOMPtr<nsIRunnable> runnable(
+    NS_NewRunnableFunction("BackgroundLock", [self, promiseHandle]() mutable {
+      BackgroundLock(promiseHandle, self);
+    }));
+
+  return FinishAsync(
+    promiseHandle, promiseOut, NS_LITERAL_CSTRING("LockKSThread"), runnable);
+}
+
+void
+BackgroundGenerateSecret(const nsACString& aLabel,
+                         RefPtr<Promise>& aPromise,
+                         RefPtr<OSKeyStore> self)
+{
+  nsAutoCString recovery;
+  nsresult rv = self->GenerateSecret(aLabel, recovery);
+  nsAutoString recoveryString;
+  if (NS_SUCCEEDED(rv)){
+    CopyUTF8toUTF16(recovery, recoveryString);
+  }
+  nsCOMPtr<nsIRunnable> runnable(NS_NewRunnableFunction(
+    "BackgroundGenerateSecreteOSKSResolve",
+    [rv, aPromise = std::move(aPromise), recoveryString]() {
+      if (NS_FAILED(rv)) {
+        aPromise->MaybeReject(rv);
+      } else {
+        aPromise->MaybeResolve(recoveryString);
+      }
+    }));
+  NS_DispatchToMainThread(runnable.forget());
+}
+
+NS_IMETHODIMP
+OSKeyStore::AsyncGenerateSecret(const nsACString& aLabel,
+                                JSContext* aCx,
+                                Promise** promiseOut)
+{
+  NS_ENSURE_ARG_POINTER(aCx);
+
+  RefPtr<Promise> promiseHandle;
+  nsresult rv = GetPromise(aCx, promiseHandle);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  RefPtr<OSKeyStore> self = this;
+  nsCOMPtr<nsIRunnable> runnable(NS_NewRunnableFunction(
+    "BackgroundGenerateSecret",
+    [self, promiseHandle, aLabel = nsAutoCString(aLabel)]() mutable {
+      BackgroundGenerateSecret(aLabel, promiseHandle, self);
+    }));
+
+  return FinishAsync(promiseHandle,
+                     promiseOut,
+                     NS_LITERAL_CSTRING("GenerateKSThread"),
+                     runnable);
+}
+
+void
+BackgroundSecretAvailable(const nsACString& aLabel,
+                          RefPtr<Promise>& aPromise,
+                          RefPtr<OSKeyStore> self)
+{
+  bool available = false;
+  nsresult rv = self->SecretAvailable(aLabel, &available);
+  nsCOMPtr<nsIRunnable> runnable(NS_NewRunnableFunction(
+    "BackgroundSecreteAvailableOSKSResolve",
+    [rv, aPromise = std::move(aPromise), available = available]() {
+      if (NS_FAILED(rv)) {
+        aPromise->MaybeReject(rv);
+      } else {
+        aPromise->MaybeResolve(available);
+      }
+    }));
+  NS_DispatchToMainThread(runnable.forget());
+}
+
+NS_IMETHODIMP
+OSKeyStore::AsyncSecretAvailable(const nsACString& aLabel,
+                                 JSContext* aCx,
+                                 Promise** promiseOut)
+{
+  NS_ENSURE_ARG_POINTER(aCx);
+
+  RefPtr<Promise> promiseHandle;
+  nsresult rv = GetPromise(aCx, promiseHandle);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  RefPtr<OSKeyStore> self = this;
+  nsCOMPtr<nsIRunnable> runnable(NS_NewRunnableFunction(
+    "BackgroundSecretAvailable",
+    [self, promiseHandle, aLabel = nsAutoCString(aLabel)]() mutable {
+      BackgroundSecretAvailable(aLabel, promiseHandle, self);
+    }));
+
+  return FinishAsync(promiseHandle,
+                     promiseOut,
+                     NS_LITERAL_CSTRING("AvaiableKSThread"),
+                     runnable);
+}
+
+void
+BackgroundRecoverSecret(const nsACString& aLabel,
+                        const nsACString& aRecoveryPhrase,
+                        RefPtr<Promise>& aPromise,
+                        RefPtr<OSKeyStore> self)
+{
+  nsresult rv = self->RecoverSecret(aLabel, aRecoveryPhrase);
+  nsCOMPtr<nsIRunnable> runnable(
+    NS_NewRunnableFunction("BackgroundRecoverSecreteOSKSResolve",
+                           [rv, aPromise = std::move(aPromise)]() {
+                             if (NS_FAILED(rv)) {
+                               aPromise->MaybeReject(rv);
+                             } else {
+                               aPromise->MaybeResolveWithUndefined();
+                             }
+                           }));
+  NS_DispatchToMainThread(runnable.forget());
+}
+
+NS_IMETHODIMP
+OSKeyStore::AsyncRecoverSecret(const nsACString& aLabel,
+                               const nsACString& aRecoveryPhrase,
+                               JSContext* aCx,
+                               Promise** promiseOut)
+{
+  NS_ENSURE_ARG_POINTER(aCx);
+
+  RefPtr<Promise> promiseHandle;
+  nsresult rv = GetPromise(aCx, promiseHandle);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  RefPtr<OSKeyStore> self = this;
+  nsCOMPtr<nsIRunnable> runnable(NS_NewRunnableFunction(
+    "BackgroundRecoverSecret",
+    [self,
+     promiseHandle,
+     aLabel = nsAutoCString(aLabel),
+     aRecoveryPhrase = nsAutoCString(aRecoveryPhrase)]() mutable {
+      BackgroundRecoverSecret(aLabel, aRecoveryPhrase, promiseHandle, self);
+    }));
+
+  return FinishAsync(
+    promiseHandle, promiseOut, NS_LITERAL_CSTRING("RecoverKSThread"), runnable);
+}
+
+void
+BackgroundDeleteSecret(const nsACString& aLabel,
+                       RefPtr<Promise>& aPromise,
+                       RefPtr<OSKeyStore> self)
+{
+  nsresult rv = self->DeleteSecret(aLabel);
+  nsCOMPtr<nsIRunnable> runnable(
+    NS_NewRunnableFunction("BackgroundDeleteSecreteOSKSResolve",
+                           [rv, aPromise = std::move(aPromise)]() {
+                             if (NS_FAILED(rv)) {
+                               aPromise->MaybeReject(rv);
+                             } else {
+                               aPromise->MaybeResolveWithUndefined();
+                             }
+                           }));
+  NS_DispatchToMainThread(runnable.forget());
+}
+
+NS_IMETHODIMP
+OSKeyStore::AsyncDeleteSecret(const nsACString& aLabel,
+                              JSContext* aCx,
+                              Promise** promiseOut)
+{
+  NS_ENSURE_ARG_POINTER(aCx);
+
+  RefPtr<Promise> promiseHandle;
+  nsresult rv = GetPromise(aCx, promiseHandle);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  RefPtr<OSKeyStore> self = this;
+  nsCOMPtr<nsIRunnable> runnable(NS_NewRunnableFunction(
+    "BackgroundDeleteSecret",
+    [self, promiseHandle, aLabel = nsAutoCString(aLabel)]() mutable {
+      BackgroundDeleteSecret(aLabel, promiseHandle, self);
+    }));
+
+  return FinishAsync(
+    promiseHandle, promiseOut, NS_LITERAL_CSTRING("DeleteKSThread"), runnable);
+}
+
+void
+BackgroundEncryptBytes(const nsACString& aLabel,
+                       std::vector<uint8_t> inBytes,
+                       RefPtr<Promise>& aPromise,
+                       RefPtr<OSKeyStore> self)
+{
+  nsAutoCString ciphertext;
+  nsresult rv =
+    self->EncryptBytes(aLabel, inBytes.size(), inBytes.data(), ciphertext);
+  nsAutoString ctext;
+  CopyUTF8toUTF16(ciphertext, ctext);
+
+  nsCOMPtr<nsIRunnable> runnable(
+    NS_NewRunnableFunction("BackgroundEncryptOSKSResolve",
+                           [rv, aPromise = std::move(aPromise), ctext]() {
+                             if (NS_FAILED(rv)) {
+                               aPromise->MaybeReject(rv);
+                             } else {
+                               aPromise->MaybeResolve(ctext);
+                             }
+                           }));
+  NS_DispatchToMainThread(runnable.forget());
+}
+
+NS_IMETHODIMP
+OSKeyStore::AsyncEncryptBytes(const nsACString& aLabel,
+                              uint32_t inLen,
+                              uint8_t* inBytes,
+                              JSContext* aCx,
+                              Promise** promiseOut)
+{
+  NS_ENSURE_ARG_POINTER(aCx);
+  NS_ENSURE_ARG_POINTER(inBytes);
+
+  RefPtr<Promise> promiseHandle;
+  nsresult rv = GetPromise(aCx, promiseHandle);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  RefPtr<OSKeyStore> self = this;
+  nsCOMPtr<nsIRunnable> runnable(NS_NewRunnableFunction(
+    "BackgroundEncryptBytes",
+    [promiseHandle,
+     inBytes = std::vector<uint8_t>(inBytes, inBytes + inLen),
+     aLabel = nsAutoCString(aLabel),
+     self]() mutable {
+      BackgroundEncryptBytes(aLabel, inBytes, promiseHandle, self);
+    }));
+
+  return FinishAsync(
+    promiseHandle, promiseOut, NS_LITERAL_CSTRING("EncryptKSThread"), runnable);
+}
+
+void
+BackgroundDecryptBytes(const nsACString& aLabel,
+                       const nsACString& aEncryptedBase64Text,
+                       RefPtr<Promise>& aPromise,
+                       RefPtr<OSKeyStore> self)
+{
+  uint8_t* plaintext = nullptr;
+  uint32_t plaintextLen = 0;
+  nsresult rv =
+    self->DecryptBytes(aLabel, aEncryptedBase64Text, &plaintextLen, &plaintext);
+  nsTArray<uint8_t> plain;
+  if (plaintext) {
+    MOZ_ASSERT(plaintextLen > 0);
+    plain.AppendElements(plaintext, plaintextLen);
+    free(plaintext);
+  }
+
+  nsCOMPtr<nsIRunnable> runnable(NS_NewRunnableFunction(
+    "BackgroundDecryptOSKSResolve",
+    [rv, aPromise = std::move(aPromise), plain = std::move(plain)]() {
+      if (NS_FAILED(rv)) {
+        aPromise->MaybeReject(rv);
+      } else {
+        aPromise->MaybeResolve(plain);
+      }
+    }));
+  NS_DispatchToMainThread(runnable.forget());
+}
+
+NS_IMETHODIMP
+OSKeyStore::AsyncDecryptBytes(const nsACString& aLabel,
+                              const nsACString& aEncryptedBase64Text,
+                              JSContext* aCx,
+                              Promise** promiseOut)
+{
+  NS_ENSURE_ARG_POINTER(aCx);
+
+  RefPtr<Promise> promiseHandle;
+  nsresult rv = GetPromise(aCx, promiseHandle);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  RefPtr<OSKeyStore> self = this;
+  nsCOMPtr<nsIRunnable> runnable(NS_NewRunnableFunction(
+    "BackgroundDecryptBytes",
+    [promiseHandle,
+     self,
+     aEncryptedBase64Text = nsAutoCString(aEncryptedBase64Text),
+     aLabel = nsAutoCString(aLabel)]() mutable {
+      BackgroundDecryptBytes(aLabel, aEncryptedBase64Text, promiseHandle, self);
+    }));
+
+  return FinishAsync(
+    promiseHandle, promiseOut, NS_LITERAL_CSTRING("DecryptKSThread"), runnable);
+}
+
+// Generic AES-GCM cipher wrapper for NSS functions.
+
+nsresult
+AbstractOSKeyStore::BuildAesGcmKey(std::vector<uint8_t> aKeyBytes,
+                                   /* out */ UniquePK11SymKey& aKey)
+{
+  if (aKeyBytes.size() != mKeyByteLength) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  UniquePK11SlotInfo slot(PK11_GetInternalSlot());
+  if (!slot) {
+    return NS_ERROR_FAILURE;
+  }
+
+  UniqueSECItem key =
+    UniqueSECItem(SECITEM_AllocItem(nullptr, nullptr, mKeyByteLength));
+  if (!key) {
+    return NS_ERROR_FAILURE;
+  }
+  key->type = siBuffer;
+  memcpy(key->data, aKeyBytes.data(), mKeyByteLength);
+  key->len = mKeyByteLength;
+
+  UniquePK11SymKey symKey(PK11_ImportSymKey(slot.get(),
+                                            CKM_AES_GCM,
+                                            PK11_OriginUnwrap,
+                                            CKA_DECRYPT | CKA_ENCRYPT,
+                                            key.get(),
+                                            nullptr));
+
+  if (!symKey) {
+    return NS_ERROR_FAILURE;
+  }
+  aKey.swap(symKey);
+
+  return NS_OK;
+}
+
+nsresult
+AbstractOSKeyStore::DoCipher(const UniquePK11SymKey& aSymKey,
+                             const std::vector<uint8_t>& inBytes,
+                             std::vector<uint8_t>& outBytes,
+                             bool encrypt)
+{
+  NS_ENSURE_ARG_POINTER(aSymKey);
+  outBytes.clear();
+
+  // Build params.
+  // We need to get the IV from inBytes if we decrypt.
+  if (!encrypt && (inBytes.size() < mIVLength || inBytes.size() == 0)) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  const uint8_t* ivp = nullptr;
+  std::vector<uint8_t> ivBuf;
+  if (encrypt) {
+    // Generate a new IV.
+    ivBuf.resize(mIVLength);
+    nsresult rv = GenerateRandom(ivBuf);
+    if (NS_FAILED(rv) || ivBuf.size() != mIVLength) {
+      return NS_ERROR_FAILURE;
+    }
+    ivp = ivBuf.data();
+  } else {
+    // An IV was passed in. Use the first mIVLength bytes from inBytes as IV.
+    ivp = inBytes.data();
+  }
+
+  CK_GCM_PARAMS gcm_params;
+  gcm_params.pIv = const_cast<unsigned char*>(ivp);
+  gcm_params.ulIvLen = mIVLength;
+  gcm_params.ulTagBits = 128;
+  gcm_params.pAAD = nullptr;
+  gcm_params.ulAADLen = 0;
+
+  SECItem paramsItem = { siBuffer,
+                         reinterpret_cast<unsigned char*>(&gcm_params),
+                         sizeof(CK_GCM_PARAMS) };
+
+  size_t blockLength = 16;
+  outBytes.resize(inBytes.size() + blockLength);
+  unsigned int outLen = 0;
+  SECStatus srv = SECFailure;
+  if (encrypt) {
+    srv = PK11_Encrypt(aSymKey.get(),
+                       CKM_AES_GCM,
+                       &paramsItem,
+                       outBytes.data(),
+                       &outLen,
+                       inBytes.size() + blockLength,
+                       inBytes.data(),
+                       inBytes.size());
+    // Prepend the used IV to the ciphertext.
+    Unused << outBytes.insert(outBytes.begin(), ivp, ivp + mIVLength);
+    outLen += mIVLength;
+  } else {
+    // Remove the IV from the input.
+    std::vector<uint8_t> input(inBytes);
+    input.erase(input.begin(), input.begin() + mIVLength);
+    srv = PK11_Decrypt(aSymKey.get(),
+                       CKM_AES_GCM,
+                       &paramsItem,
+                       outBytes.data(),
+                       &outLen,
+                       input.size() + blockLength,
+                       input.data(),
+                       input.size());
+  }
+  if (srv != SECSuccess || outLen > outBytes.size()) {
+    outBytes.clear();
+    return NS_ERROR_FAILURE;
+  }
+  if (outLen < outBytes.size()) {
+    outBytes.resize(outLen);
+  }
+
+  return NS_OK;
+}
+
+bool
+AbstractOSKeyStore::IsNSSKeyStore()
+{
+  return false;
+}
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/OSKeyStore.h
@@ -0,0 +1,107 @@
+/* -*- 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/. */
+
+// Generic key store implementation for platforms that we don't support with OS
+// specific implementations.
+
+#ifndef OSKeyStore_h
+#define OSKeyStore_h
+
+#include "nsIOSKeyStore.h"
+#include "nsString.h"
+
+#include <memory>
+
+class AbstractOSKeyStore
+{
+public:
+  // Store a new secret with the given label.
+  virtual nsresult StoreSecret(const nsACString& secret,
+                               const nsACString& label) = 0;
+  // Returns true if the secret with the given label is available in the key
+  // store, false otherwise.
+  virtual bool SecretAvailable(const nsACString& label) = 0;
+  // Delete the secret with the given label.
+  virtual nsresult DeleteSecret(const nsACString& label) = 0;
+  // Lock the key store.
+  virtual nsresult Lock() = 0;
+  // Unlock the key store.
+  virtual nsresult Unlock() = 0;
+  // Identify the fallback NSS key store.
+  virtual bool IsNSSKeyStore();
+  virtual ~AbstractOSKeyStore() {}
+
+  // Perform encryption or decryption operation with the given secret and input
+  // bytes. The output is written in outBytes. This function can make use of the
+  // AesGcm class to use NSS for encryption and decryption.
+  virtual nsresult EncryptDecrypt(const nsACString& label,
+                                  const std::vector<uint8_t>& inBytes,
+                                  std::vector<uint8_t>& outBytes,
+                                  bool encrypt) = 0;
+
+  size_t GetKeyByteLength() { return mKeyByteLength; }
+
+protected:
+  /* These helper functions are implemented in OSKeyStore.cpp and implement
+   * common functionality of the abstract key store to encrypt and decrypt.
+   */
+  nsresult DoCipher(const UniquePK11SymKey& aSymKey,
+                    const std::vector<uint8_t>& inBytes,
+                    std::vector<uint8_t>& outBytes,
+                    bool aEncrypt);
+  nsresult BuildAesGcmKey(std::vector<uint8_t> keyBytes,
+                          /* out */ UniquePK11SymKey& aKey);
+
+private:
+  const size_t mKeyByteLength = 16;
+  const size_t mIVLength = 12;
+};
+
+#define NS_OSKEYSTORE_CONTRACTID "@mozilla.org/security/oskeystore;1"
+#define NS_OSKEYSTORE_CID \
+  { 0x57972956, 0x5718, 0x42d2, { 0x80, 0x70, 0xb3, 0xfc, 0x72, 0x21, 0x2e, 0xaf } }
+
+class OSKeyStore : public nsIOSKeyStore
+{
+public:
+  NS_DECL_THREADSAFE_ISUPPORTS
+  NS_DECL_NSIOSKEYSTORE
+
+  OSKeyStore();
+  nsresult GenerateSecret(const nsACString& aLabel,
+                          /* out */ nsACString& aRecoveryPhrase);
+  nsresult SecretAvailable(const nsACString& aLabel,
+                           /* out */ bool* aAvailable);
+  nsresult RecoverSecret(const nsACString& aLabel,
+                         const nsACString& aRecoveryPhrase);
+  nsresult DeleteSecret(const nsACString& aLabel);
+  nsresult EncryptBytes(const nsACString& aLabel,
+                        uint32_t inLen,
+                        uint8_t* inBytes,
+                        /*out*/ nsACString& aEncryptedBase64Text);
+  nsresult DecryptBytes(const nsACString& aLabel,
+                        const nsACString& aEncryptedBase64Text,
+                        /*out*/ uint32_t* outLen,
+                        /*out*/ uint8_t** outBytes);
+  nsresult Lock();
+  nsresult Unlock();
+
+protected:
+  virtual ~OSKeyStore();
+
+private:
+  nsresult FinishAsync(RefPtr<mozilla::dom::Promise>& aPromise,
+                       /* out*/ mozilla::dom::Promise** anotherPromise,
+                       const nsACString& aName,
+                       nsCOMPtr<nsIRunnable> aRunnable);
+
+  std::unique_ptr<AbstractOSKeyStore> mKs = nullptr;
+  Mutex mMutex;
+  const nsCString mLabelPrefix =
+    NS_LITERAL_CSTRING("org.mozilla.nss.keystore.");
+};
+
+#endif // OSKeyStore_h
--- a/security/manager/ssl/SSLServerCertVerification.cpp
+++ b/security/manager/ssl/SSLServerCertVerification.cpp
@@ -117,16 +117,17 @@
 #include "mozilla/net/DNS.h"
 #include "nsComponentManagerUtils.h"
 #include "nsContentUtils.h"
 #include "nsIBadCertListener2.h"
 #include "nsICertOverrideService.h"
 #include "nsISiteSecurityService.h"
 #include "nsISocketProvider.h"
 #include "nsIThreadPool.h"
+#include "nsNetUtil.h"
 #include "nsNSSCertificate.h"
 #include "nsNSSComponent.h"
 #include "nsNSSIOLayer.h"
 #include "nsSSLStatus.h"
 #include "nsServiceManagerUtils.h"
 #include "nsString.h"
 #include "nsURLHelper.h"
 #include "nsXPCOMCIDInternal.h"
--- a/security/manager/ssl/moz.build
+++ b/security/manager/ssl/moz.build
@@ -21,16 +21,17 @@ XPIDL_SOURCES += [
     'nsICryptoHMAC.idl',
     'nsIGenKeypairInfoDlg.idl',
     'nsIKeygenThread.idl',
     'nsIKeyModule.idl',
     'nsILocalCertService.idl',
     'nsINSSComponent.idl',
     'nsINSSErrorsService.idl',
     'nsINSSVersion.idl',
+    'nsIOSKeyStore.idl',
     'nsIPK11Token.idl',
     'nsIPK11TokenDB.idl',
     'nsIPKCS11Module.idl',
     'nsIPKCS11ModuleDB.idl',
     'nsIPKCS11Slot.idl',
     'nsIProtectedAuthThread.idl',
     'nsISecretDecoderRing.idl',
     'nsISecurityUITelemetry.idl',
@@ -118,30 +119,39 @@ UNIFIED_SOURCES += [
     'nsPKCS11Slot.cpp',
     'nsPKCS12Blob.cpp',
     'nsProtectedAuthThread.cpp',
     'nsRandomGenerator.cpp',
     'nsSecureBrowserUIImpl.cpp',
     'nsSecurityHeaderParser.cpp',
     'NSSErrorsService.cpp',
     'nsSiteSecurityService.cpp',
+    'NSSKeyStore.cpp',
     'nsSSLSocketProvider.cpp',
     'nsSSLStatus.cpp',
     'nsTLSSocketProvider.cpp',
+    'OSKeyStore.cpp',
     'PKCS11ModuleDB.cpp',
     'PSMContentListener.cpp',
     'PSMRunnable.cpp',
     'PublicKeyPinningService.cpp',
     'RootCertificateTelemetryUtils.cpp',
     'SecretDecoderRing.cpp',
     'SharedSSLState.cpp',
     'SSLServerCertVerification.cpp',
     'TransportSecurityInfo.cpp',
 ]
 
+if CONFIG['MOZ_LIB_SECRET']:
+    UNIFIED_SOURCES += [
+        'LibSecret.cpp',
+    ]
+    CFLAGS += CONFIG['MOZ_LIB_SECRET_CFLAGS']
+    CXXFLAGS += CONFIG['MOZ_LIB_SECRET_CFLAGS']
+
 if CONFIG['OS_ARCH'] == 'Darwin':
     OS_LIBS += [
         '-framework Security'
     ]
 
 IPDL_SOURCES += [
     'PPSMContentDownloader.ipdl',
 ]
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/nsIOSKeyStore.idl
@@ -0,0 +1,146 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(57972956-5718-42d2-8070-b3fc72212eaf)]
+interface nsIOSKeyStore: nsISupports {
+  /**
+   * This interface provides encryption and decryption operations for data at
+   * rest. The key used to encrypt and decrypt the data is stored in the OS
+   * key store.
+   *
+   * Usage:
+   *
+   * // obtain the singleton OSKeyStore instance
+   * const oskeystore = Cc["@mozilla.org/oskeystore;1"].getService(Ci.nsIOSKeyStore);
+   *
+   * const PASSWORD_LABEL = "mylabel1";
+   * const COOKIE_LABEL = "mylabel2";
+   *
+   * // Unlock the key store.
+   * // Note that this is not necesssary. The key store will be unlocked
+   * // automatically when an operation is performed on it.
+   * await oskeystore.asyncUnlock();
+   *
+   * // Check if there's a secret for your label already.
+   * if (!await oskeystore.asyncSecretAvailable(PASSWORD_LABEL)) {
+   *   // Fail or generate a new secret for your label.
+   *   // If you want to generate a new secret, do.
+   *   // Hold onto `recoveryPhrase` to present to the user.
+   *   let recoveryPhrase = await oskeystore.asyncGenerateSecret(PASSWORD_LABEL);
+   * }
+   *
+   * // Assuming there's a secret with your label. Encrypt/Decrypt as follows.
+   * let encryptedPasswordBytes = await oskeystore.asyncEncryptBytes(PASSWORD_LABEL, passwordBytes);
+   * let newPasswordBytes = await oskeystore.asyncDecryptBytes(PASSWORD_LABEL, encryptedPasswordBytes);
+   *
+   * // Delete the secret from the key store.
+   * await oskeystore.asyncDeleteSecret(PASSWORD_LABEL);
+   *
+   * // Recover a secret from a recovery code.
+   * await oskeystore.asyncRecoverSecret(PASSWORD_LABEL, recoveryPhrase);
+   *
+   * // Lock the key store to prompt the user to log into her OS key store again.
+   * await oskeystore.asyncLock();
+   */
+
+  /**
+   * Generate a new secret and store it in the OS key store with the given label.
+   * The caller should make sure that no other secrets with the same label are
+   * present before calling this function.
+   * This invalidates all previous ciphertexts created with the key
+   * corresponding to the given label.
+   *
+   * @param label The label to use for the secret.
+   * @return Promise that resolves to the recoveryPhrase string used to generate
+   *         the secret.
+   */
+  [implicit_jscontext, must_use]
+  Promise asyncGenerateSecret(in ACString label);
+
+  /**
+   * Check whether a secret for a given label exists.
+   *
+   * @param label The label to lookup.
+   * @return Promise that resolves to a bool (whether a secret with label is
+   *         known or not) or an error.
+   */
+  [implicit_jscontext, must_use]
+  Promise asyncSecretAvailable(in ACString label);
+
+  /**
+   * Set a secret from a given recovery phrase.
+   * This might not be implemented on all platforms.
+   * This invalidates all previous ciphertexts.
+   *
+   * @param label The label to use for the secret.
+   * @param recoveryPhrase The recovery phrase that's used to generate the secret.
+   * @return Promise that resolves to undefined or an error.
+   */
+  [implicit_jscontext, must_use]
+  Promise asyncRecoverSecret(in ACString label, in ACString recoveryPhrase);
+
+  /**
+   * Delete secret with a given label. If there is no secret with the given
+   * label, no action is taken.
+   *
+   * @param label The label of the secret to delete.
+   * @return Promise that resolves to undefined or an error.
+   */
+  [implicit_jscontext, must_use]
+  Promise asyncDeleteSecret(in ACString label);
+
+
+  /**
+   * Encrypt the given data and then return the result as a base64-encoded
+   * string.
+   *
+   * @param label The label of the key to use to encrypt.
+   * @param inLen The length of the bytes to encrypt.
+   * @param inBytes The bytes to encrypt of length inLen.
+   * @return Promise resolving to the encrypted text, encoded as Base64, or an
+   *         error.
+   */
+  [implicit_jscontext, must_use]
+  Promise asyncEncryptBytes(in ACString label, in unsigned long inLen,
+                            [array, size_is(inLen)] in uint8_t inBytes);
+
+  /**
+   * Decode and then decrypt the given base64-encoded string.
+   *
+   * @param label The label of the key to use to decrypt.
+   * @param encryptedBase64Text Encrypted input text, encoded as Base64.
+   * @return Promise resolving to the plaintext bytes or an error.
+   */
+  [implicit_jscontext, must_use]
+  Promise asyncDecryptBytes(in ACString label, in ACString encryptedBase64Text);
+
+  /**
+   * Lock the key store.
+   * The actual behaviour of this depends on the OS.
+   *
+   * @return Promise resolving to undefined or an error.
+   */
+  [implicit_jscontext, must_use]
+  Promise asyncLock();
+
+  /**
+   * Unlock the key store.
+   * The actual behaviour of this depends on the OS.
+   *
+   * @return Promise resolving to undefined or an error.
+   */
+  [implicit_jscontext, must_use]
+  Promise asyncUnlock();
+
+
+  /**
+   * Check if the implementation is using the NSS key store.
+   * This is a special case because Firefox has to handle the locking and
+   * unlocking.
+   */
+  readonly attribute bool isNSSKeyStore;
+};
--- a/security/manager/ssl/nsNSSCertValidity.cpp
+++ b/security/manager/ssl/nsNSSCertValidity.cpp
@@ -7,16 +7,18 @@
 #include "cert.h"
 #include "mozilla/Assertions.h"
 #include "nsCOMPtr.h"
 #include "nsComponentManagerUtils.h"
 #include "nsReadableUtils.h"
 
 NS_IMPL_ISUPPORTS(nsX509CertValidity, nsIX509CertValidity)
 
+using namespace mozilla;
+
 nsX509CertValidity::nsX509CertValidity(const mozilla::UniqueCERTCertificate& cert)
   : mNotBefore(0)
   , mNotAfter(0)
   , mTimesInitialized(false)
 {
   MOZ_ASSERT(cert);
   if (!cert) {
     return;
--- a/security/manager/ssl/nsNSSCertificateDB.cpp
+++ b/security/manager/ssl/nsNSSCertificateDB.cpp
@@ -8,16 +8,17 @@
 #include "CryptoTask.h"
 #include "ExtendedValidation.h"
 #include "NSSCertDBTrustDomain.h"
 #include "SharedSSLState.h"
 #include "certdb.h"
 #include "mozilla/Assertions.h"
 #include "mozilla/Base64.h"
 #include "mozilla/Casting.h"
+#include "mozilla/Services.h"
 #include "mozilla/Unused.h"
 #include "nsArray.h"
 #include "nsArrayUtils.h"
 #include "nsCOMPtr.h"
 #include "nsComponentManagerUtils.h"
 #include "nsICertificateDialogs.h"
 #include "nsIFile.h"
 #include "nsIMutableArray.h"
--- a/security/manager/ssl/nsNSSModule.cpp
+++ b/security/manager/ssl/nsNSSModule.cpp
@@ -29,16 +29,17 @@
 #include "nsPKCS11Slot.h"
 #include "nsRandomGenerator.h"
 #include "nsSSLSocketProvider.h"
 #include "nsSSLStatus.h"
 #include "nsSecureBrowserUIImpl.h"
 #include "nsSiteSecurityService.h"
 #include "nsTLSSocketProvider.h"
 #include "nsXULAppAPI.h"
+#include "OSKeyStore.h"
 
 #ifdef MOZ_XUL
 #include "nsCertTree.h"
 #endif
 
 namespace mozilla { namespace psm {
 
 // Many of the implementations in this module call NSS functions and as a result
@@ -155,16 +156,17 @@ NS_DEFINE_NAMED_CID(NS_CERTOVERRIDE_CID)
 NS_DEFINE_NAMED_CID(NS_RANDOMGENERATOR_CID);
 NS_DEFINE_NAMED_CID(NS_SSLSTATUS_CID);
 NS_DEFINE_NAMED_CID(TRANSPORTSECURITYINFO_CID);
 NS_DEFINE_NAMED_CID(NS_NSSERRORSSERVICE_CID);
 NS_DEFINE_NAMED_CID(NS_NSSVERSION_CID);
 NS_DEFINE_NAMED_CID(NS_SECURE_BROWSER_UI_CID);
 NS_DEFINE_NAMED_CID(NS_SITE_SECURITY_SERVICE_CID);
 NS_DEFINE_NAMED_CID(NS_CERT_BLOCKLIST_CID);
+NS_DEFINE_NAMED_CID(NS_OSKEYSTORE_CID);
 
 // Components that require main thread initialization could cause a deadlock
 // in necko code (bug 1418752). To prevent it we initialize all such components
 // on main thread in advance in net_EnsurePSMInit(). Update that function when
 // new component with ThreadRestriction::MainThreadOnly is added.
 static const mozilla::Module::CIDEntry kNSSCIDs[] = {
   { &kNS_NSSCOMPONENT_CID, false, nullptr, nsNSSComponentConstructor },
   { &kNS_SSLSOCKETPROVIDER_CID, false, nullptr,
@@ -214,16 +216,20 @@ static const mozilla::Module::CIDEntry k
   { &kNS_SITE_SECURITY_SERVICE_CID, false, nullptr,
     Constructor<nsSiteSecurityService, &nsSiteSecurityService::Init,
                 ProcessRestriction::AnyProcess,
                 ThreadRestriction::MainThreadOnly> },
   { &kNS_CERT_BLOCKLIST_CID, false, nullptr,
     Constructor<CertBlocklist, &CertBlocklist::Init,
                 ProcessRestriction::ParentProcessOnly,
                 ThreadRestriction::MainThreadOnly> },
+  { &kNS_OSKEYSTORE_CID, false, nullptr, Constructor<OSKeyStore,
+                nullptr,
+                ProcessRestriction::ParentProcessOnly,
+                ThreadRestriction::MainThreadOnly> },
   { nullptr }
 };
 
 static const mozilla::Module::ContractIDEntry kNSSContracts[] = {
   { PSM_COMPONENT_CONTRACTID, &kNS_NSSCOMPONENT_CID },
   { NS_NSS_ERRORS_SERVICE_CONTRACTID, &kNS_NSSERRORSSERVICE_CID },
   { NS_NSSVERSION_CONTRACTID, &kNS_NSSVERSION_CID },
   { NS_SSLSOCKETPROVIDER_CONTRACTID, &kNS_SSLSOCKETPROVIDER_CID },
@@ -245,16 +251,17 @@ static const mozilla::Module::ContractID
   { NS_KEYMODULEOBJECT_CONTRACTID, &kNS_KEYMODULEOBJECT_CID },
   { NS_KEYMODULEOBJECTFACTORY_CONTRACTID, &kNS_KEYMODULEOBJECTFACTORY_CID },
   { NS_CONTENTSIGNATUREVERIFIER_CONTRACTID, &kNS_CONTENTSIGNATUREVERIFIER_CID },
   { NS_CERTOVERRIDE_CONTRACTID, &kNS_CERTOVERRIDE_CID },
   { NS_RANDOMGENERATOR_CONTRACTID, &kNS_RANDOMGENERATOR_CID },
   { NS_SECURE_BROWSER_UI_CONTRACTID, &kNS_SECURE_BROWSER_UI_CID },
   { NS_SSSERVICE_CONTRACTID, &kNS_SITE_SECURITY_SERVICE_CID },
   { NS_CERTBLOCKLIST_CONTRACTID, &kNS_CERT_BLOCKLIST_CID },
+  { NS_OSKEYSTORE_CONTRACTID, &kNS_OSKEYSTORE_CID},
   { nullptr }
 };
 
 static const mozilla::Module::CategoryEntry kNSSCategories[] = {
   { NS_CONTENT_LISTENER_CATEGORYMANAGER_ENTRY, "application/x-x509-ca-cert", "@mozilla.org/uriloader/psm-external-content-listener;1" },
   { NS_CONTENT_LISTENER_CATEGORYMANAGER_ENTRY, "application/x-x509-server-cert", "@mozilla.org/uriloader/psm-external-content-listener;1" },
   { NS_CONTENT_LISTENER_CATEGORYMANAGER_ENTRY, "application/x-x509-user-cert", "@mozilla.org/uriloader/psm-external-content-listener;1" },
   { NS_CONTENT_LISTENER_CATEGORYMANAGER_ENTRY, "application/x-x509-email-cert", "@mozilla.org/uriloader/psm-external-content-listener;1" },
--- a/security/manager/ssl/nsPKCS12Blob.h
+++ b/security/manager/ssl/nsPKCS12Blob.h
@@ -29,23 +29,24 @@ public:
   // PKCS#12 Export
   nsresult ExportToFile(nsIFile* file, nsIX509Cert** certs, int numCerts);
 
 private:
   nsCOMPtr<nsIInterfaceRequestor> mUIContext;
 
   // local helper functions
   nsresult getPKCS12FilePassword(uint32_t& passwordBufferLength,
-                                 UniquePtr<uint8_t[]>& passwordBuffer);
+                                 mozilla::UniquePtr<uint8_t[]>& passwordBuffer);
   nsresult newPKCS12FilePassword(uint32_t& passwordBufferLength,
-                                 UniquePtr<uint8_t[]>& passwordBuffer);
-  nsresult inputToDecoder(UniqueSEC_PKCS12DecoderContext& dcx, nsIFile* file,
+                                 mozilla::UniquePtr<uint8_t[]>& passwordBuffer);
+  nsresult inputToDecoder(mozilla::UniqueSEC_PKCS12DecoderContext& dcx,
+                          nsIFile* file,
                           PRErrorCode& nssError);
-  UniquePtr<uint8_t[]> stringToBigEndianBytes(const nsString& uni,
-                                              uint32_t& bytesLength);
+  mozilla::UniquePtr<uint8_t[]> stringToBigEndianBytes(const nsString& uni,
+                                                       uint32_t& bytesLength);
   void handleError(int myerr, PRErrorCode prerr);
 
   // RetryReason and ImportMode are used when importing a PKCS12 file.
   // There are two reasons that cause us to retry:
   // - When the password entered by the user is incorrect.
   //   The user will be prompted to try again.
   // - When the user entered a zero length password.
   //   An empty password should be represented as an empty string (a SECItem
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/tests/unit/test_oskeystore.js
@@ -0,0 +1,135 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+// Any copyright is dedicated to the Public Domain.
+// http://creativecommons.org/publicdomain/zero/1.0/
+"use strict";
+
+// Tests the methods and attributes for interfacing with nsIOSKeyStore.
+
+// Ensure that the appropriate initialization has happened.
+do_get_profile();
+
+const LABELS = ["mylabel1",
+                "mylabel2",
+                "mylabel3"];
+
+async function delete_all_secrets() {
+  let keystore = Cc["@mozilla.org/security/oskeystore;1"]
+                   .getService(Ci.nsIOSKeyStore);
+  for (let i in LABELS) {
+    let label = LABELS[i];
+    if (await keystore.asyncSecretAvailable(label)) {
+      await keystore.asyncDeleteSecret(label);
+      ok(!await keystore.asyncSecretAvailable(label), label + " should be deleted now.");
+    }
+  }
+}
+
+// Test that Firefox handles locking and unlocking of the OSKeyStore properly.
+// Does so by mocking out the actual dialog and "filling in" the
+// password. Also tests that providing an incorrect password will fail (well,
+// technically the user will just get prompted again, but if they then cancel
+// the dialog the overall operation will fail).
+
+var gMockPrompter = {
+  passwordToTry: null,
+  numPrompts: 0,
+
+  // This intentionally does not use arrow function syntax to avoid an issue
+  // where in the context of the arrow function, |this != gMockPrompter| due to
+  // how objects get wrapped when going across xpcom boundaries.
+  promptPassword(dialogTitle, text, password, checkMsg, checkValue) {
+    this.numPrompts++;
+    if (this.numPrompts > 1) { // don't keep retrying a bad password
+      return false;
+    }
+    equal(text,
+          "Please enter your master password.",
+          "password prompt text should be as expected");
+    equal(checkMsg, null, "checkMsg should be null");
+    ok(this.passwordToTry, "passwordToTry should be non-null");
+    password.value = this.passwordToTry;
+    return true;
+  },
+
+  QueryInterface: ChromeUtils.generateQI([Ci.nsIPrompt]),
+};
+
+// Mock nsIWindowWatcher. PSM calls getNewPrompter on this to get an nsIPrompt
+// to call promptPassword. We return the mock one, above.
+var gWindowWatcher = {
+  getNewPrompter: () => gMockPrompter,
+  QueryInterface: ChromeUtils.generateQI([Ci.nsIWindowWatcher]),
+};
+
+async function encrypt_decrypt_test() {
+  let keystore = Cc["@mozilla.org/security/oskeystore;1"]
+                   .getService(Ci.nsIOSKeyStore);
+  ok(!await keystore.asyncSecretAvailable(LABELS[0]), "The secret should not be available yet.");
+
+  let recoveryPhrase = await keystore.asyncGenerateSecret(LABELS[0]);
+  ok(recoveryPhrase, "A recovery phrase should've been created.");
+  let recoveryPhrase2 = await keystore.asyncGenerateSecret(LABELS[1]);
+  ok(recoveryPhrase2, "A recovery phrase should've been created.");
+
+  let text = new Uint8Array([0x01, 0x00, 0x01]);
+  let ciphertext = "";
+  try {
+    ciphertext = await keystore.asyncEncryptBytes(LABELS[0], text.length, text);
+    ok(ciphertext, "We should have a ciphertext now.");
+  } catch (e) {
+    ok(false, "Error encrypting " + e);
+  }
+
+  // Decrypting should give us the plaintext bytes again.
+  try {
+    let plaintext = await keystore.asyncDecryptBytes(LABELS[0], ciphertext);
+    ok(plaintext.toString() == text.toString(), "Decrypted plaintext should be the same as text.");
+  } catch (e) {
+    ok(false, "Error decrypting ciphertext " + e);
+  }
+
+  // Decrypting with a wrong key should throw an error.
+  try {
+    await keystore.asyncDecryptBytes(LABELS[1], ciphertext);
+    ok(false, "Decrypting with the wrong key should fail.");
+  } catch (e) {
+    ok(true, "Decrypting with the wrong key should fail " + e);
+  }
+}
+
+add_task(async function() {
+  let keystore = Cc["@mozilla.org/security/oskeystore;1"]
+                   .getService(Ci.nsIOSKeyStore);
+  let windowWatcherCID;
+  if (keystore.isNSSKeyStore) {
+    windowWatcherCID =
+      MockRegistrar.register("@mozilla.org/embedcomp/window-watcher;1",
+                             gWindowWatcher);
+    registerCleanupFunction(() => {
+      MockRegistrar.unregister(windowWatcherCID);
+    });
+  }
+
+  await delete_all_secrets();
+  await encrypt_decrypt_test();
+  await delete_all_secrets();
+
+  if (keystore.isNSSKeyStore) {
+    // If we use the NSS key store implementation test that everything works
+    // when a master password is set.
+    // Set an initial password.
+    let tokenDB = Cc["@mozilla.org/security/pk11tokendb;1"]
+                    .getService(Ci.nsIPK11TokenDB);
+    let token = tokenDB.getInternalKeyToken();
+    token.initPassword("hunter2");
+
+    // Lock the key store. This should be equivalent to token.logoutSimple()
+    await keystore.asyncLock();
+
+    // Set the correct password so that the test operations should succeed.
+    gMockPrompter.passwordToTry = "hunter2";
+    await encrypt_decrypt_test();
+    ok(gMockPrompter.numPrompts == 1, "There should've been one password prompt.");
+    await delete_all_secrets();
+  }
+});
--- a/security/manager/ssl/tests/unit/xpcshell.ini
+++ b/security/manager/ssl/tests/unit/xpcshell.ini
@@ -135,16 +135,17 @@ run-sequentially = hardcoded ports
 [test_ocsp_stapling_expired.js]
 run-sequentially = hardcoded ports
 [test_ocsp_stapling_with_intermediate.js]
 run-sequentially = hardcoded ports
 [test_ocsp_timeout.js]
 run-sequentially = hardcoded ports
 [test_ocsp_url.js]
 run-sequentially = hardcoded ports
+[test_oskeystore.js]
 [test_password_prompt.js]
 [test_pinning.js]
 run-sequentially = hardcoded ports
 # This test can take longer than 300 seconds on B2G emulator debug builds, so
 # give it enough time to finish. See bug 1081128.
 requesttimeoutfactor = 2
 [test_pinning_dynamic.js]
 [test_pinning_header_parsing.js]
--- a/security/nss.symbols
+++ b/security/nss.symbols
@@ -304,25 +304,27 @@ NSSUTIL_Quote
 _NSSUTIL_UTF8ToWide
 #endif
 PK11_AlgtagToMechanism
 PK11_Authenticate
 PK11_ChangePW
 PK11_CheckUserPassword
 PK11_CipherOp
 PK11_ConfigurePKCS11
+PK11_ConvertSessionSymKeyToTokenSymKey
 PK11_CreateContextBySymKey
 PK11_CreateDigestContext
 PK11_CreateGenericObject
 PK11_CreateMergeLog
 PK11_CreatePBEV2AlgorithmID
 PK11_Decrypt
 PK11_DeleteTokenCertAndKey
 PK11_DeleteTokenPrivateKey
 PK11_DeleteTokenPublicKey
+PK11_DeleteTokenSymKey
 PK11_DEREncodePublicKey
 PK11_Derive
 PK11_DeriveWithTemplate
 PK11_DestroyContext
 PK11_DestroyGenericObject
 PK11_DestroyMergeLog
 PK11_DestroyObject
 PK11_DestroyTokenObject
--- a/toolkit/library/moz.build
+++ b/toolkit/library/moz.build
@@ -209,16 +209,19 @@ if CONFIG['MOZ_ANDROID_GOOGLE_VR']:
     OS_LIBS += [
         '-L%s' % CONFIG['MOZ_ANDROID_GOOGLE_VR_LIBS'],
         '-lgvr',
     ]
 
 OS_LIBS += CONFIG['MOZ_CAIRO_OSLIBS']
 OS_LIBS += CONFIG['MOZ_WEBRTC_X11_LIBS']
 
+if CONFIG['MOZ_LIB_SECRET']:
+    OS_LIBS += CONFIG['MOZ_LIB_SECRET_LIBS']
+
 if CONFIG['MOZ_SYSTEM_JPEG']:
     OS_LIBS += CONFIG['MOZ_JPEG_LIBS']
 
 if CONFIG['MOZ_SYSTEM_PNG']:
     OS_LIBS += CONFIG['MOZ_PNG_LIBS']
 
 if CONFIG['MOZ_SYSTEM_LIBEVENT']:
     OS_LIBS += CONFIG['MOZ_LIBEVENT_LIBS']
--- a/toolkit/moz.configure
+++ b/toolkit/moz.configure
@@ -919,16 +919,28 @@ set_define('MOZ_WEBSPEECH_TEST_BACKEND',
 # ==============================================================
 option('--enable-ipdl-tests', help='Enable expensive IPDL tests')
 
 set_config('MOZ_IPDL_TESTS',
            depends_if('--enable-ipdl-tests')(lambda _: True))
 
 include('nss.configure')
 
+# LibSecret
+# ==============================================================
+# To access the OS key store on Linux systems we use libsecret.
+# https://developer.gnome.org/libsecret/
+
+if target.os == 'Linux':
+    libsecret = pkg_check_modules('MOZ_LIB_SECRET', 'libsecret-1',
+                                  allow_missing=True)
+
+    set_config('MOZ_LIB_SECRET', depends_if(libsecret)(lambda _: True))
+    set_define('MOZ_LIB_SECRET', depends_if(libsecret)(lambda _: True))
+
 # Graphics
 # ==============================================================
 option('--disable-skia', help='Disable use of Skia')
 
 @depends('--disable-skia')
 def skia(value):
     if not value:
         die('--disable-skia is not supported anymore')