From 26a029d407be480d791972afb5975cf62c9360a6 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 02:47:55 +0200 Subject: Adding upstream version 124.0.1. Signed-off-by: Daniel Baumann --- security/manager/ssl/OSKeyStore.cpp | 628 ++++++++++++++++++++++++++++++++++++ 1 file changed, 628 insertions(+) create mode 100644 security/manager/ssl/OSKeyStore.cpp (limited to 'security/manager/ssl/OSKeyStore.cpp') diff --git a/security/manager/ssl/OSKeyStore.cpp b/security/manager/ssl/OSKeyStore.cpp new file mode 100644 index 0000000000..5ab289b0dd --- /dev/null +++ b/security/manager/ssl/OSKeyStore.cpp @@ -0,0 +1,628 @@ +/* -*- 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 "nsThreadUtils.h" +#include "nsXPCOM.h" +#include "pk11pub.h" + +#if defined(XP_MACOSX) +# include "KeychainSecret.h" +#elif defined(XP_WIN) +# include "CredentialManagerSecret.h" +#elif defined(MOZ_WIDGET_GTK) +# include "LibSecret.h" +# include "NSSKeyStore.h" +#else +# include "NSSKeyStore.h" +#endif + +NS_IMPL_ISUPPORTS(OSKeyStore, nsIOSKeyStore) + +using namespace mozilla; +using dom::Promise; + +OSKeyStore::OSKeyStore() : mKs(nullptr) { + MOZ_ASSERT(NS_IsMainThread()); + if (NS_WARN_IF(!NS_IsMainThread())) { + return; + } + +#if defined(XP_MACOSX) + mKs.reset(new KeychainSecret()); +#elif defined(XP_WIN) + mKs.reset(new CredentialManagerSecret()); +#elif defined(MOZ_WIDGET_GTK) + if (NS_SUCCEEDED(MaybeLoadLibSecret())) { + mKs.reset(new LibSecret()); + } else { + mKs.reset(new NSSKeyStore()); + } +#else + mKs.reset(new NSSKeyStore()); +#endif +} + +static nsresult GenerateRandom(std::vector& r) { + if (r.empty()) { + 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) { + NS_ENSURE_STATE(mKs); + *aAvailable = mKs->SecretAvailable(aLabel); + return NS_OK; +} + +nsresult OSKeyStore::GenerateSecret(const nsACString& aLabel, + /* out */ nsACString& aRecoveryPhrase) { + NS_ENSURE_STATE(mKs); + size_t keyByteLength = mKs->GetKeyByteLength(); + std::vector secret(keyByteLength); + nsresult rv = GenerateRandom(secret); + if (NS_FAILED(rv) || secret.size() != keyByteLength) { + return NS_ERROR_FAILURE; + } + nsAutoCString secretString; + secretString.Assign(BitwiseCast(secret.data()), + secret.size()); + + nsCString base64; + rv = Base64Encode(secretString, base64); + if (NS_FAILED(rv)) { + return rv; + } + + rv = mKs->StoreSecret(secretString, aLabel); + if (NS_FAILED(rv)) { + return rv; + } + + aRecoveryPhrase = std::move(base64); + return NS_OK; +} + +nsresult OSKeyStore::RecoverSecret(const nsACString& aLabel, + const nsACString& aRecoveryPhrase) { + NS_ENSURE_STATE(mKs); + nsAutoCString secret; + nsresult rv = Base64Decode(aRecoveryPhrase, secret); + if (NS_FAILED(rv)) { + return rv; + } + if (secret.Length() != mKs->GetKeyByteLength()) { + return NS_ERROR_INVALID_ARG; + } + rv = mKs->StoreSecret(secret, aLabel); + if (NS_FAILED(rv)) { + return rv; + } + + return NS_OK; +} + +nsresult OSKeyStore::DeleteSecret(const nsACString& aLabel) { + NS_ENSURE_STATE(mKs); + return mKs->DeleteSecret(aLabel); +} + +enum Cipher { Encrypt = true, Decrypt = false }; + +nsresult OSKeyStore::EncryptBytes(const nsACString& aLabel, + const std::vector& aInBytes, + /*out*/ nsACString& aEncryptedBase64Text) { + NS_ENSURE_STATE(mKs); + + aEncryptedBase64Text.Truncate(); + std::vector outBytes; + nsresult rv = + mKs->EncryptDecrypt(aLabel, aInBytes, outBytes, Cipher::Encrypt); + if (NS_FAILED(rv)) { + return rv; + } + nsAutoCString ciphertext; + ciphertext.Assign(BitwiseCast(outBytes.data()), + outBytes.size()); + + nsCString base64ciphertext; + rv = Base64Encode(ciphertext, base64ciphertext); + if (NS_FAILED(rv)) { + return rv; + } + aEncryptedBase64Text = std::move(base64ciphertext); + return NS_OK; +} + +nsresult OSKeyStore::DecryptBytes(const nsACString& aLabel, + const nsACString& aEncryptedBase64Text, + /*out*/ uint32_t* outLen, + /*out*/ uint8_t** outBytes) { + 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; + } + uint8_t* tmp = BitwiseCast(ciphertext.BeginReading()); + const std::vector ciphertextBytes(tmp, tmp + ciphertext.Length()); + std::vector plaintextBytes; + rv = mKs->EncryptDecrypt(aLabel, ciphertextBytes, plaintextBytes, + Cipher::Decrypt); + if (NS_FAILED(rv)) { + return rv; + } + + *outBytes = (uint8_t*)moz_xmalloc(plaintextBytes.size()); + memcpy(*outBytes, plaintextBytes.data(), plaintextBytes.size()); + *outLen = plaintextBytes.size(); + return NS_OK; +} + +// Async interfaces that return promises because the key store implementation +// might block, e.g. asking for a password. + +nsresult GetPromise(JSContext* aCx, /* out */ RefPtr& 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; +} + +void BackgroundGenerateSecret(const nsACString& aLabel, + RefPtr& aPromise, + RefPtr self) { + nsAutoCString recovery; + nsresult rv = self->GenerateSecret(aLabel, recovery); + nsAutoString recoveryString; + if (NS_SUCCEEDED(rv)) { + CopyUTF8toUTF16(recovery, recoveryString); + } + nsCOMPtr 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) { + MOZ_ASSERT(NS_IsMainThread()); + if (!NS_IsMainThread()) { + return NS_ERROR_NOT_SAME_THREAD; + } + + NS_ENSURE_ARG_POINTER(aCx); + + RefPtr promiseHandle; + nsresult rv = GetPromise(aCx, promiseHandle); + if (NS_FAILED(rv)) { + return rv; + } + + RefPtr self = this; + nsCOMPtr runnable(NS_NewRunnableFunction( + "BackgroundGenerateSecret", + [self, promiseHandle, aLabel = nsAutoCString(aLabel)]() mutable { + BackgroundGenerateSecret(aLabel, promiseHandle, self); + })); + + promiseHandle.forget(promiseOut); + return NS_DispatchBackgroundTask(runnable.forget(), + NS_DISPATCH_EVENT_MAY_BLOCK); +} + +void BackgroundSecretAvailable(const nsACString& aLabel, + RefPtr& aPromise, + RefPtr self) { + bool available = false; + nsresult rv = self->SecretAvailable(aLabel, &available); + nsCOMPtr 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) { + MOZ_ASSERT(NS_IsMainThread()); + if (!NS_IsMainThread()) { + return NS_ERROR_NOT_SAME_THREAD; + } + + NS_ENSURE_ARG_POINTER(aCx); + + RefPtr promiseHandle; + nsresult rv = GetPromise(aCx, promiseHandle); + if (NS_FAILED(rv)) { + return rv; + } + + RefPtr self = this; + nsCOMPtr runnable(NS_NewRunnableFunction( + "BackgroundSecretAvailable", + [self, promiseHandle, aLabel = nsAutoCString(aLabel)]() mutable { + BackgroundSecretAvailable(aLabel, promiseHandle, self); + })); + + promiseHandle.forget(promiseOut); + return NS_DispatchBackgroundTask(runnable.forget(), + NS_DISPATCH_EVENT_MAY_BLOCK); +} + +void BackgroundRecoverSecret(const nsACString& aLabel, + const nsACString& aRecoveryPhrase, + RefPtr& aPromise, + RefPtr self) { + nsresult rv = self->RecoverSecret(aLabel, aRecoveryPhrase); + nsCOMPtr 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) { + MOZ_ASSERT(NS_IsMainThread()); + if (!NS_IsMainThread()) { + return NS_ERROR_NOT_SAME_THREAD; + } + + NS_ENSURE_ARG_POINTER(aCx); + + RefPtr promiseHandle; + nsresult rv = GetPromise(aCx, promiseHandle); + if (NS_FAILED(rv)) { + return rv; + } + + RefPtr self = this; + nsCOMPtr runnable(NS_NewRunnableFunction( + "BackgroundRecoverSecret", + [self, promiseHandle, aLabel = nsAutoCString(aLabel), + aRecoveryPhrase = nsAutoCString(aRecoveryPhrase)]() mutable { + BackgroundRecoverSecret(aLabel, aRecoveryPhrase, promiseHandle, self); + })); + + promiseHandle.forget(promiseOut); + return NS_DispatchBackgroundTask(runnable.forget(), + NS_DISPATCH_EVENT_MAY_BLOCK); +} + +void BackgroundDeleteSecret(const nsACString& aLabel, RefPtr& aPromise, + RefPtr self) { + nsresult rv = self->DeleteSecret(aLabel); + nsCOMPtr 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) { + MOZ_ASSERT(NS_IsMainThread()); + if (!NS_IsMainThread()) { + return NS_ERROR_NOT_SAME_THREAD; + } + + NS_ENSURE_ARG_POINTER(aCx); + + RefPtr promiseHandle; + nsresult rv = GetPromise(aCx, promiseHandle); + if (NS_FAILED(rv)) { + return rv; + } + + RefPtr self = this; + nsCOMPtr runnable(NS_NewRunnableFunction( + "BackgroundDeleteSecret", + [self, promiseHandle, aLabel = nsAutoCString(aLabel)]() mutable { + BackgroundDeleteSecret(aLabel, promiseHandle, self); + })); + + promiseHandle.forget(promiseOut); + return NS_DispatchBackgroundTask(runnable.forget(), + NS_DISPATCH_EVENT_MAY_BLOCK); +} + +static void BackgroundEncryptBytes(const nsACString& aLabel, + const std::vector& aInBytes, + RefPtr& aPromise, + RefPtr self) { + nsAutoCString ciphertext; + nsresult rv = self->EncryptBytes(aLabel, aInBytes, ciphertext); + nsAutoString ctext; + CopyUTF8toUTF16(ciphertext, ctext); + + nsCOMPtr 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, + const nsTArray& inBytes, JSContext* aCx, + Promise** promiseOut) { + MOZ_ASSERT(NS_IsMainThread()); + if (!NS_IsMainThread()) { + return NS_ERROR_NOT_SAME_THREAD; + } + + NS_ENSURE_ARG_POINTER(aCx); + + RefPtr promiseHandle; + nsresult rv = GetPromise(aCx, promiseHandle); + if (NS_FAILED(rv)) { + return rv; + } + + RefPtr self = this; + nsCOMPtr runnable(NS_NewRunnableFunction( + "BackgroundEncryptBytes", + [promiseHandle, + inBytes = std::vector(inBytes.Elements(), + inBytes.Elements() + inBytes.Length()), + aLabel = nsAutoCString(aLabel), self]() mutable { + BackgroundEncryptBytes(aLabel, inBytes, promiseHandle, self); + })); + + promiseHandle.forget(promiseOut); + return NS_DispatchBackgroundTask(runnable.forget(), + NS_DISPATCH_EVENT_MAY_BLOCK); +} + +void BackgroundDecryptBytes(const nsACString& aLabel, + const nsACString& aEncryptedBase64Text, + RefPtr& aPromise, + RefPtr self) { + uint8_t* plaintext = nullptr; + uint32_t plaintextLen = 0; + nsresult rv = self->DecryptBytes(aLabel, aEncryptedBase64Text, &plaintextLen, + &plaintext); + nsTArray plain; + if (plaintext) { + MOZ_ASSERT(plaintextLen > 0); + plain.AppendElements(plaintext, plaintextLen); + free(plaintext); + } + + nsCOMPtr 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) { + MOZ_ASSERT(NS_IsMainThread()); + if (!NS_IsMainThread()) { + return NS_ERROR_NOT_SAME_THREAD; + } + + NS_ENSURE_ARG_POINTER(aCx); + + RefPtr promiseHandle; + nsresult rv = GetPromise(aCx, promiseHandle); + if (NS_FAILED(rv)) { + return rv; + } + + RefPtr self = this; + nsCOMPtr runnable(NS_NewRunnableFunction( + "BackgroundDecryptBytes", + [promiseHandle, self, + aEncryptedBase64Text = nsAutoCString(aEncryptedBase64Text), + aLabel = nsAutoCString(aLabel)]() mutable { + BackgroundDecryptBytes(aLabel, aEncryptedBase64Text, promiseHandle, + self); + })); + + promiseHandle.forget(promiseOut); + return NS_DispatchBackgroundTask(runnable.forget(), + NS_DISPATCH_EVENT_MAY_BLOCK); +} + +// Generic AES-GCM cipher wrapper for NSS functions. + +nsresult AbstractOSKeyStore::BuildAesGcmKey(std::vector 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& inBytes, + std::vector& 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.empty())) { + return NS_ERROR_INVALID_ARG; + } + + const uint8_t* ivp = nullptr; + std::vector 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(ivp); + gcm_params.ulIvLen = mIVLength; + gcm_params.ulIvBits = gcm_params.ulIvLen * 8; + gcm_params.ulTagBits = 128; + gcm_params.pAAD = nullptr; + gcm_params.ulAADLen = 0; + + SECItem paramsItem = {siBuffer, reinterpret_cast(&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, ¶msItem, 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 input(inBytes); + input.erase(input.begin(), input.begin() + mIVLength); + srv = PK11_Decrypt(aSymKey.get(), CKM_AES_GCM, ¶msItem, 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::SecretAvailable(const nsACString& aLabel) { + nsAutoCString secret; + nsresult rv = RetrieveSecret(aLabel, secret); + if (NS_FAILED(rv) || secret.Length() == 0) { + return false; + } + return true; +} + +nsresult AbstractOSKeyStore::EncryptDecrypt(const nsACString& aLabel, + const std::vector& inBytes, + std::vector& 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(secret.BeginReading()); + std::vector 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); +} -- cgit v1.2.3