summaryrefslogtreecommitdiffstats
path: root/security/manager/ssl/SecretDecoderRing.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'security/manager/ssl/SecretDecoderRing.cpp')
-rw-r--r--security/manager/ssl/SecretDecoderRing.cpp343
1 files changed, 343 insertions, 0 deletions
diff --git a/security/manager/ssl/SecretDecoderRing.cpp b/security/manager/ssl/SecretDecoderRing.cpp
new file mode 100644
index 0000000000..da1750c6ae
--- /dev/null
+++ b/security/manager/ssl/SecretDecoderRing.cpp
@@ -0,0 +1,343 @@
+/* -*- 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 "SecretDecoderRing.h"
+
+#include "ScopedNSSTypes.h"
+#include "mozilla/Base64.h"
+#include "mozilla/Casting.h"
+#include "mozilla/Services.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/dom/Promise.h"
+#include "nsCOMPtr.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIObserverService.h"
+#include "nsITokenPasswordDialogs.h"
+#include "nsNSSComponent.h"
+#include "nsNSSHelper.h"
+#include "nsNetCID.h"
+#include "nsPK11TokenDB.h"
+#include "pk11func.h"
+#include "pk11sdr.h" // For PK11SDR_Encrypt, PK11SDR_Decrypt
+
+static mozilla::LazyLogModule gSDRLog("sdrlog");
+
+using namespace mozilla;
+using dom::Promise;
+
+NS_IMPL_ISUPPORTS(SecretDecoderRing, nsISecretDecoderRing)
+
+void BackgroundSdrEncryptStrings(const nsTArray<nsCString>& plaintexts,
+ RefPtr<Promise>& aPromise) {
+ nsCOMPtr<nsISecretDecoderRing> sdrService =
+ do_GetService(NS_SECRETDECODERRING_CONTRACTID);
+ nsTArray<nsString> cipherTexts(plaintexts.Length());
+
+ nsresult rv = NS_ERROR_FAILURE;
+ for (const auto& plaintext : plaintexts) {
+ nsCString cipherText;
+ rv = sdrService->EncryptString(plaintext, cipherText);
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ break;
+ }
+
+ cipherTexts.AppendElement(NS_ConvertASCIItoUTF16(cipherText));
+ }
+
+ nsCOMPtr<nsIRunnable> runnable(
+ NS_NewRunnableFunction("BackgroundSdrEncryptStringsResolve",
+ [rv, aPromise = std::move(aPromise),
+ cipherTexts = std::move(cipherTexts)]() {
+ if (NS_FAILED(rv)) {
+ aPromise->MaybeReject(rv);
+ } else {
+ aPromise->MaybeResolve(cipherTexts);
+ }
+ }));
+ NS_DispatchToMainThread(runnable.forget());
+}
+
+void BackgroundSdrDecryptStrings(const nsTArray<nsCString>& encryptedStrings,
+ RefPtr<Promise>& aPromise) {
+ nsCOMPtr<nsISecretDecoderRing> sdrService =
+ do_GetService(NS_SECRETDECODERRING_CONTRACTID);
+ nsTArray<nsString> plainTexts(encryptedStrings.Length());
+
+ nsresult rv = NS_ERROR_FAILURE;
+ for (const auto& encryptedString : encryptedStrings) {
+ nsCString plainText;
+ rv = sdrService->DecryptString(encryptedString, plainText);
+
+ if (NS_FAILED(rv)) {
+ if (rv == NS_ERROR_NOT_AVAILABLE) {
+ // Master Password entry was canceled. Don't keep prompting again.
+ break;
+ }
+
+ // NS_ERROR_ILLEGAL_VALUE or NS_ERROR_FAILURE could be due to bad data for
+ // a single string but we still want to decrypt the others.
+ // Callers of `decryptMany` in crypto-SDR.js assume there will be an
+ // equal number of usernames and passwords so use an empty string to keep
+ // this assumption true.
+ MOZ_LOG(gSDRLog, LogLevel::Warning,
+ ("Couldn't decrypt string: %s", encryptedString.get()));
+ plainTexts.AppendElement(nullptr);
+ rv = NS_OK;
+ continue;
+ }
+
+ plainTexts.AppendElement(NS_ConvertUTF8toUTF16(plainText));
+ }
+
+ nsCOMPtr<nsIRunnable> runnable(
+ NS_NewRunnableFunction("BackgroundSdrDecryptStringsResolve",
+ [rv, aPromise = std::move(aPromise),
+ plainTexts = std::move(plainTexts)]() {
+ if (NS_FAILED(rv)) {
+ aPromise->MaybeReject(rv);
+ } else {
+ aPromise->MaybeResolve(plainTexts);
+ }
+ }));
+ NS_DispatchToMainThread(runnable.forget());
+}
+
+nsresult SecretDecoderRing::Encrypt(const nsACString& data,
+ /*out*/ nsACString& result) {
+ UniquePK11SlotInfo slot(PK11_GetInternalKeySlot());
+ if (!slot) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ /* Make sure token is initialized. */
+ nsCOMPtr<nsIInterfaceRequestor> ctx = new PipUIContext();
+ nsresult rv = setPassword(slot.get(), ctx);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ /* Force authentication */
+ if (PK11_Authenticate(slot.get(), true, ctx) != SECSuccess) {
+ return NS_ERROR_FAILURE;
+ }
+
+ /* Use default key id */
+ SECItem keyid;
+ keyid.data = nullptr;
+ keyid.len = 0;
+ SECItem request;
+ request.data = BitwiseCast<unsigned char*, const char*>(data.BeginReading());
+ request.len = data.Length();
+ ScopedAutoSECItem reply;
+ if (PK11SDR_Encrypt(&keyid, &request, &reply, ctx) != SECSuccess) {
+ return NS_ERROR_FAILURE;
+ }
+
+ result.Assign(BitwiseCast<char*, unsigned char*>(reply.data), reply.len);
+ return NS_OK;
+}
+
+nsresult SecretDecoderRing::Decrypt(const nsACString& data,
+ /*out*/ nsACString& result) {
+ /* Find token with SDR key */
+ UniquePK11SlotInfo slot(PK11_GetInternalKeySlot());
+ if (!slot) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ /* Force authentication */
+ nsCOMPtr<nsIInterfaceRequestor> ctx = new PipUIContext();
+ if (PK11_Authenticate(slot.get(), true, ctx) != SECSuccess) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ SECItem request;
+ request.data = BitwiseCast<unsigned char*, const char*>(data.BeginReading());
+ request.len = data.Length();
+ ScopedAutoSECItem reply;
+ if (PK11SDR_Decrypt(&request, &reply, ctx) != SECSuccess) {
+ return NS_ERROR_FAILURE;
+ }
+
+ result.Assign(BitwiseCast<char*, unsigned char*>(reply.data), reply.len);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SecretDecoderRing::EncryptString(const nsACString& text,
+ /*out*/ nsACString& encryptedBase64Text) {
+ nsAutoCString encryptedText;
+ nsresult rv = Encrypt(text, encryptedText);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = Base64Encode(encryptedText, encryptedBase64Text);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SecretDecoderRing::AsyncEncryptStrings(const nsTArray<nsCString>& plaintexts,
+ JSContext* aCx, Promise** aPromise) {
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+ NS_ENSURE_ARG(!plaintexts.IsEmpty());
+ NS_ENSURE_ARG_POINTER(aCx);
+ NS_ENSURE_ARG_POINTER(aPromise);
+
+ nsIGlobalObject* globalObject = xpc::CurrentNativeGlobal(aCx);
+ if (NS_WARN_IF(!globalObject)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ ErrorResult result;
+ RefPtr<Promise> promise = Promise::Create(globalObject, result);
+ if (NS_WARN_IF(result.Failed())) {
+ return result.StealNSResult();
+ }
+
+ // plaintexts are already expected to be UTF-8.
+ nsCOMPtr<nsIRunnable> runnable(NS_NewRunnableFunction(
+ "BackgroundSdrEncryptStrings",
+ [promise, plaintexts = plaintexts.Clone()]() mutable {
+ BackgroundSdrEncryptStrings(plaintexts, promise);
+ }));
+
+ nsCOMPtr<nsIEventTarget> target(
+ do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID));
+ if (!target) {
+ return NS_ERROR_FAILURE;
+ }
+ nsresult rv = target->Dispatch(runnable, NS_DISPATCH_NORMAL);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ promise.forget(aPromise);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SecretDecoderRing::DecryptString(const nsACString& encryptedBase64Text,
+ /*out*/ nsACString& decryptedText) {
+ nsAutoCString encryptedText;
+ nsresult rv = Base64Decode(encryptedBase64Text, encryptedText);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = Decrypt(encryptedText, decryptedText);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SecretDecoderRing::AsyncDecryptStrings(
+ const nsTArray<nsCString>& encryptedStrings, JSContext* aCx,
+ Promise** aPromise) {
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+ NS_ENSURE_ARG(!encryptedStrings.IsEmpty());
+ NS_ENSURE_ARG_POINTER(aCx);
+ NS_ENSURE_ARG_POINTER(aPromise);
+
+ nsIGlobalObject* globalObject = xpc::CurrentNativeGlobal(aCx);
+ if (NS_WARN_IF(!globalObject)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ ErrorResult result;
+ RefPtr<Promise> promise = Promise::Create(globalObject, result);
+ if (NS_WARN_IF(result.Failed())) {
+ return result.StealNSResult();
+ }
+
+ // encryptedStrings are expected to be base64.
+ nsCOMPtr<nsIRunnable> runnable(NS_NewRunnableFunction(
+ "BackgroundSdrDecryptStrings",
+ [promise, encryptedStrings = encryptedStrings.Clone()]() mutable {
+ BackgroundSdrDecryptStrings(encryptedStrings, promise);
+ }));
+
+ nsCOMPtr<nsIEventTarget> target(
+ do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID));
+ if (!target) {
+ return NS_ERROR_FAILURE;
+ }
+ nsresult rv = target->Dispatch(runnable, NS_DISPATCH_NORMAL);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ promise.forget(aPromise);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SecretDecoderRing::ChangePassword() {
+ UniquePK11SlotInfo slot(PK11_GetInternalKeySlot());
+ if (!slot) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // nsPK11Token::nsPK11Token takes its own reference to slot, so we pass a
+ // non-owning pointer here.
+ nsCOMPtr<nsIPK11Token> token = new nsPK11Token(slot.get());
+
+ nsCOMPtr<nsITokenPasswordDialogs> dialogs;
+ nsresult rv = getNSSDialogs(getter_AddRefs(dialogs),
+ NS_GET_IID(nsITokenPasswordDialogs),
+ NS_TOKENPASSWORDSDIALOG_CONTRACTID);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIInterfaceRequestor> ctx = new PipUIContext();
+ bool canceled; // Ignored
+ return dialogs->SetPassword(ctx, token, &canceled);
+}
+
+NS_IMETHODIMP
+SecretDecoderRing::Logout() {
+ PK11_LogoutAll();
+ nsCOMPtr<nsINSSComponent> nssComponent(do_GetService(NS_NSSCOMPONENT_CID));
+ if (!nssComponent) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ return nssComponent->ClearSSLExternalAndInternalSessionCache();
+}
+
+NS_IMETHODIMP
+SecretDecoderRing::LogoutAndTeardown() {
+ PK11_LogoutAll();
+ nsCOMPtr<nsINSSComponent> nssComponent(do_GetService(NS_NSSCOMPONENT_CID));
+ if (!nssComponent) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // LogoutAuthenticatedPK11 also clears the SSL caches.
+ nsresult rv = nssComponent->LogoutAuthenticatedPK11();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // After we just logged out, we need to prune dead connections to make
+ // sure that all connections that should be stopped, are stopped. See
+ // bug 517584.
+ nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+ if (os) {
+ os->NotifyObservers(nullptr, "net:prune-dead-connections", nullptr);
+ }
+
+ return NS_OK;
+}