summaryrefslogtreecommitdiffstats
path: root/services/crypto/component/IdentityCryptoService.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'services/crypto/component/IdentityCryptoService.cpp')
-rw-r--r--services/crypto/component/IdentityCryptoService.cpp461
1 files changed, 461 insertions, 0 deletions
diff --git a/services/crypto/component/IdentityCryptoService.cpp b/services/crypto/component/IdentityCryptoService.cpp
new file mode 100644
index 0000000000..f2c59eb9d5
--- /dev/null
+++ b/services/crypto/component/IdentityCryptoService.cpp
@@ -0,0 +1,461 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* 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 "nsIIdentityCryptoService.h"
+#include "nsServiceManagerUtils.h"
+#include "nsIThread.h"
+#include "nsThreadUtils.h"
+#include "nsCOMPtr.h"
+#include "nsProxyRelease.h"
+#include "nsString.h"
+#include "mozilla/ArrayUtils.h" // ArrayLength
+#include "mozilla/Base64.h"
+#include "mozilla/Components.h"
+#include "ScopedNSSTypes.h"
+#include "NSSErrorsService.h"
+
+#include "nss.h"
+#include "pk11pub.h"
+#include "secmod.h"
+#include "secerr.h"
+#include "keyhi.h"
+#include "cryptohi.h"
+
+#include <limits.h>
+
+using namespace mozilla;
+
+namespace {
+
+void HexEncode(const SECItem* it, nsACString& result) {
+ static const char digits[] = "0123456789ABCDEF";
+ result.SetLength(it->len * 2);
+ char* p = result.BeginWriting();
+ for (unsigned int i = 0; i < it->len; ++i) {
+ *p++ = digits[it->data[i] >> 4];
+ *p++ = digits[it->data[i] & 0x0f];
+ }
+}
+
+#define DSA_KEY_TYPE_STRING ("DS160"_ns)
+#define RSA_KEY_TYPE_STRING ("RS256"_ns)
+
+class KeyPair : public nsIIdentityKeyPair {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIIDENTITYKEYPAIR
+
+ KeyPair(SECKEYPrivateKey* aPrivateKey, SECKEYPublicKey* aPublicKey,
+ nsIEventTarget* aOperationThread);
+
+ private:
+ virtual ~KeyPair() {
+ if (mPrivateKey) {
+ SECKEY_DestroyPrivateKey(mPrivateKey);
+ }
+ if (mPublicKey) {
+ SECKEY_DestroyPublicKey(mPublicKey);
+ }
+ }
+
+ SECKEYPrivateKey* mPrivateKey;
+ SECKEYPublicKey* mPublicKey;
+ nsCOMPtr<nsIEventTarget> mThread;
+
+ KeyPair(const KeyPair&) = delete;
+ void operator=(const KeyPair&) = delete;
+};
+
+NS_IMPL_ISUPPORTS(KeyPair, nsIIdentityKeyPair)
+
+class KeyGenRunnable : public Runnable {
+ public:
+ NS_DECL_NSIRUNNABLE
+
+ KeyGenRunnable(KeyType keyType, nsIIdentityKeyGenCallback* aCallback,
+ nsIEventTarget* aOperationThread);
+
+ private:
+ const KeyType mKeyType; // in
+ nsMainThreadPtrHandle<nsIIdentityKeyGenCallback> mCallback; // in
+ nsresult mRv; // out
+ nsCOMPtr<nsIIdentityKeyPair> mKeyPair; // out
+ nsCOMPtr<nsIEventTarget> mThread;
+
+ KeyGenRunnable(const KeyGenRunnable&) = delete;
+ void operator=(const KeyGenRunnable&) = delete;
+};
+
+class SignRunnable : public Runnable {
+ public:
+ NS_DECL_NSIRUNNABLE
+
+ SignRunnable(const nsACString& textToSign, SECKEYPrivateKey* privateKey,
+ nsIIdentitySignCallback* aCallback);
+
+ private:
+ ~SignRunnable() override {
+ if (mPrivateKey) {
+ SECKEY_DestroyPrivateKey(mPrivateKey);
+ }
+ }
+
+ const nsCString mTextToSign; // in
+ SECKEYPrivateKey* mPrivateKey; // in
+ nsMainThreadPtrHandle<nsIIdentitySignCallback> mCallback; // in
+ nsresult mRv; // out
+ nsCString mSignature; // out
+
+ SignRunnable(const SignRunnable&) = delete;
+ void operator=(const SignRunnable&) = delete;
+};
+
+class IdentityCryptoService final : public nsIIdentityCryptoService {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIIDENTITYCRYPTOSERVICE
+
+ IdentityCryptoService() = default;
+ nsresult Init() {
+ nsresult rv;
+ nsCOMPtr<nsISupports> dummyUsedToEnsureNSSIsInitialized =
+ do_GetService("@mozilla.org/psm;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIThread> thread;
+ rv = NS_NewNamedThread("IdentityCrypto", getter_AddRefs(thread));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mThread = std::move(thread);
+
+ return NS_OK;
+ }
+
+ private:
+ ~IdentityCryptoService() = default;
+ IdentityCryptoService(const KeyPair&) = delete;
+ void operator=(const IdentityCryptoService&) = delete;
+
+ nsCOMPtr<nsIEventTarget> mThread;
+};
+
+NS_IMPL_ISUPPORTS(IdentityCryptoService, nsIIdentityCryptoService)
+
+NS_IMETHODIMP
+IdentityCryptoService::GenerateKeyPair(const nsACString& keyTypeString,
+ nsIIdentityKeyGenCallback* callback) {
+ KeyType keyType;
+ if (keyTypeString.Equals(RSA_KEY_TYPE_STRING)) {
+ keyType = rsaKey;
+ } else if (keyTypeString.Equals(DSA_KEY_TYPE_STRING)) {
+ keyType = dsaKey;
+ } else {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsCOMPtr<nsIRunnable> r = new KeyGenRunnable(keyType, callback, mThread);
+ nsresult rv = mThread->Dispatch(r.forget(), NS_DISPATCH_NORMAL);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+IdentityCryptoService::Base64UrlEncode(const nsACString& utf8Input,
+ nsACString& result) {
+ return Base64URLEncode(
+ utf8Input.Length(),
+ reinterpret_cast<const uint8_t*>(utf8Input.BeginReading()),
+ Base64URLEncodePaddingPolicy::Include, result);
+}
+
+KeyPair::KeyPair(SECKEYPrivateKey* privateKey, SECKEYPublicKey* publicKey,
+ nsIEventTarget* operationThread)
+ : mPrivateKey(privateKey), mPublicKey(publicKey), mThread(operationThread) {
+ MOZ_ASSERT(!NS_IsMainThread());
+}
+
+NS_IMETHODIMP
+KeyPair::GetHexRSAPublicKeyExponent(nsACString& result) {
+ MOZ_ASSERT(NS_IsMainThread());
+ NS_ENSURE_TRUE(mPublicKey, NS_ERROR_NOT_AVAILABLE);
+ NS_ENSURE_TRUE(mPublicKey->keyType == rsaKey, NS_ERROR_NOT_AVAILABLE);
+ HexEncode(&mPublicKey->u.rsa.publicExponent, result);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+KeyPair::GetHexRSAPublicKeyModulus(nsACString& result) {
+ MOZ_ASSERT(NS_IsMainThread());
+ NS_ENSURE_TRUE(mPublicKey, NS_ERROR_NOT_AVAILABLE);
+ NS_ENSURE_TRUE(mPublicKey->keyType == rsaKey, NS_ERROR_NOT_AVAILABLE);
+ HexEncode(&mPublicKey->u.rsa.modulus, result);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+KeyPair::GetHexDSAPrime(nsACString& result) {
+ MOZ_ASSERT(NS_IsMainThread());
+ NS_ENSURE_TRUE(mPublicKey, NS_ERROR_NOT_AVAILABLE);
+ NS_ENSURE_TRUE(mPublicKey->keyType == dsaKey, NS_ERROR_NOT_AVAILABLE);
+ HexEncode(&mPublicKey->u.dsa.params.prime, result);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+KeyPair::GetHexDSASubPrime(nsACString& result) {
+ MOZ_ASSERT(NS_IsMainThread());
+ NS_ENSURE_TRUE(mPublicKey, NS_ERROR_NOT_AVAILABLE);
+ NS_ENSURE_TRUE(mPublicKey->keyType == dsaKey, NS_ERROR_NOT_AVAILABLE);
+ HexEncode(&mPublicKey->u.dsa.params.subPrime, result);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+KeyPair::GetHexDSAGenerator(nsACString& result) {
+ MOZ_ASSERT(NS_IsMainThread());
+ NS_ENSURE_TRUE(mPublicKey, NS_ERROR_NOT_AVAILABLE);
+ NS_ENSURE_TRUE(mPublicKey->keyType == dsaKey, NS_ERROR_NOT_AVAILABLE);
+ HexEncode(&mPublicKey->u.dsa.params.base, result);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+KeyPair::GetHexDSAPublicValue(nsACString& result) {
+ MOZ_ASSERT(NS_IsMainThread());
+ NS_ENSURE_TRUE(mPublicKey, NS_ERROR_NOT_AVAILABLE);
+ NS_ENSURE_TRUE(mPublicKey->keyType == dsaKey, NS_ERROR_NOT_AVAILABLE);
+ HexEncode(&mPublicKey->u.dsa.publicValue, result);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+KeyPair::GetKeyType(nsACString& result) {
+ MOZ_ASSERT(NS_IsMainThread());
+ NS_ENSURE_TRUE(mPublicKey, NS_ERROR_NOT_AVAILABLE);
+
+ switch (mPublicKey->keyType) {
+ case rsaKey:
+ result = RSA_KEY_TYPE_STRING;
+ return NS_OK;
+ case dsaKey:
+ result = DSA_KEY_TYPE_STRING;
+ return NS_OK;
+ default:
+ return NS_ERROR_UNEXPECTED;
+ }
+}
+
+NS_IMETHODIMP
+KeyPair::Sign(const nsACString& textToSign, nsIIdentitySignCallback* callback) {
+ MOZ_ASSERT(NS_IsMainThread());
+ nsCOMPtr<nsIRunnable> r = new SignRunnable(textToSign, mPrivateKey, callback);
+
+ return mThread->Dispatch(r, NS_DISPATCH_NORMAL);
+}
+
+KeyGenRunnable::KeyGenRunnable(KeyType keyType,
+ nsIIdentityKeyGenCallback* callback,
+ nsIEventTarget* operationThread)
+ : mozilla::Runnable("KeyGenRunnable"),
+ mKeyType(keyType),
+ mCallback(new nsMainThreadPtrHolder<nsIIdentityKeyGenCallback>(
+ "KeyGenRunnable::mCallback", callback)),
+ mRv(NS_ERROR_NOT_INITIALIZED),
+ mThread(operationThread) {}
+
+[[nodiscard]] nsresult GenerateKeyPair(PK11SlotInfo* slot,
+ SECKEYPrivateKey** privateKey,
+ SECKEYPublicKey** publicKey,
+ CK_MECHANISM_TYPE mechanism,
+ void* params) {
+ *publicKey = nullptr;
+ *privateKey = PK11_GenerateKeyPair(
+ slot, mechanism, params, publicKey, PR_FALSE /*isPerm*/,
+ PR_TRUE /*isSensitive*/, nullptr /*&pwdata*/);
+ if (!*privateKey) {
+ MOZ_ASSERT(!*publicKey);
+ return mozilla::psm::GetXPCOMFromNSSError(PR_GetError());
+ }
+ if (!*publicKey) {
+ SECKEY_DestroyPrivateKey(*privateKey);
+ *privateKey = nullptr;
+ MOZ_CRASH("PK11_GnerateKeyPair returned private key without public key");
+ }
+
+ return NS_OK;
+}
+
+[[nodiscard]] nsresult GenerateRSAKeyPair(PK11SlotInfo* slot,
+ SECKEYPrivateKey** privateKey,
+ SECKEYPublicKey** publicKey) {
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ PK11RSAGenParams rsaParams;
+ rsaParams.keySizeInBits = 2048;
+ rsaParams.pe = 0x10001;
+ return GenerateKeyPair(slot, privateKey, publicKey, CKM_RSA_PKCS_KEY_PAIR_GEN,
+ &rsaParams);
+}
+
+[[nodiscard]] nsresult GenerateDSAKeyPair(PK11SlotInfo* slot,
+ SECKEYPrivateKey** privateKey,
+ SECKEYPublicKey** publicKey) {
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ // XXX: These could probably be static const arrays, but this way we avoid
+ // compiler warnings and also we avoid having to worry much about whether the
+ // functions that take these inputs will (unexpectedly) modify them.
+
+ // Using NIST parameters. Some other BrowserID components require that these
+ // exact parameters are used.
+ uint8_t P[] = {
+ 0xFF, 0x60, 0x04, 0x83, 0xDB, 0x6A, 0xBF, 0xC5, 0xB4, 0x5E, 0xAB, 0x78,
+ 0x59, 0x4B, 0x35, 0x33, 0xD5, 0x50, 0xD9, 0xF1, 0xBF, 0x2A, 0x99, 0x2A,
+ 0x7A, 0x8D, 0xAA, 0x6D, 0xC3, 0x4F, 0x80, 0x45, 0xAD, 0x4E, 0x6E, 0x0C,
+ 0x42, 0x9D, 0x33, 0x4E, 0xEE, 0xAA, 0xEF, 0xD7, 0xE2, 0x3D, 0x48, 0x10,
+ 0xBE, 0x00, 0xE4, 0xCC, 0x14, 0x92, 0xCB, 0xA3, 0x25, 0xBA, 0x81, 0xFF,
+ 0x2D, 0x5A, 0x5B, 0x30, 0x5A, 0x8D, 0x17, 0xEB, 0x3B, 0xF4, 0xA0, 0x6A,
+ 0x34, 0x9D, 0x39, 0x2E, 0x00, 0xD3, 0x29, 0x74, 0x4A, 0x51, 0x79, 0x38,
+ 0x03, 0x44, 0xE8, 0x2A, 0x18, 0xC4, 0x79, 0x33, 0x43, 0x8F, 0x89, 0x1E,
+ 0x22, 0xAE, 0xEF, 0x81, 0x2D, 0x69, 0xC8, 0xF7, 0x5E, 0x32, 0x6C, 0xB7,
+ 0x0E, 0xA0, 0x00, 0xC3, 0xF7, 0x76, 0xDF, 0xDB, 0xD6, 0x04, 0x63, 0x8C,
+ 0x2E, 0xF7, 0x17, 0xFC, 0x26, 0xD0, 0x2E, 0x17};
+
+ uint8_t Q[] = {0xE2, 0x1E, 0x04, 0xF9, 0x11, 0xD1, 0xED, 0x79, 0x91, 0x00,
+ 0x8E, 0xCA, 0xAB, 0x3B, 0xF7, 0x75, 0x98, 0x43, 0x09, 0xC3};
+
+ uint8_t G[] = {
+ 0xC5, 0x2A, 0x4A, 0x0F, 0xF3, 0xB7, 0xE6, 0x1F, 0xDF, 0x18, 0x67, 0xCE,
+ 0x84, 0x13, 0x83, 0x69, 0xA6, 0x15, 0x4F, 0x4A, 0xFA, 0x92, 0x96, 0x6E,
+ 0x3C, 0x82, 0x7E, 0x25, 0xCF, 0xA6, 0xCF, 0x50, 0x8B, 0x90, 0xE5, 0xDE,
+ 0x41, 0x9E, 0x13, 0x37, 0xE0, 0x7A, 0x2E, 0x9E, 0x2A, 0x3C, 0xD5, 0xDE,
+ 0xA7, 0x04, 0xD1, 0x75, 0xF8, 0xEB, 0xF6, 0xAF, 0x39, 0x7D, 0x69, 0xE1,
+ 0x10, 0xB9, 0x6A, 0xFB, 0x17, 0xC7, 0xA0, 0x32, 0x59, 0x32, 0x9E, 0x48,
+ 0x29, 0xB0, 0xD0, 0x3B, 0xBC, 0x78, 0x96, 0xB1, 0x5B, 0x4A, 0xDE, 0x53,
+ 0xE1, 0x30, 0x85, 0x8C, 0xC3, 0x4D, 0x96, 0x26, 0x9A, 0xA8, 0x90, 0x41,
+ 0xF4, 0x09, 0x13, 0x6C, 0x72, 0x42, 0xA3, 0x88, 0x95, 0xC9, 0xD5, 0xBC,
+ 0xCA, 0xD4, 0xF3, 0x89, 0xAF, 0x1D, 0x7A, 0x4B, 0xD1, 0x39, 0x8B, 0xD0,
+ 0x72, 0xDF, 0xFA, 0x89, 0x62, 0x33, 0x39, 0x7A};
+
+ static_assert(MOZ_ARRAY_LENGTH(P) == 1024 / CHAR_BIT, "bad DSA P");
+ static_assert(MOZ_ARRAY_LENGTH(Q) == 160 / CHAR_BIT, "bad DSA Q");
+ static_assert(MOZ_ARRAY_LENGTH(G) == 1024 / CHAR_BIT, "bad DSA G");
+
+ PQGParams pqgParams = {
+ nullptr /*arena*/,
+ {siBuffer, P, static_cast<unsigned int>(mozilla::ArrayLength(P))},
+ {siBuffer, Q, static_cast<unsigned int>(mozilla::ArrayLength(Q))},
+ {siBuffer, G, static_cast<unsigned int>(mozilla::ArrayLength(G))}};
+
+ return GenerateKeyPair(slot, privateKey, publicKey, CKM_DSA_KEY_PAIR_GEN,
+ &pqgParams);
+}
+
+NS_IMETHODIMP
+KeyGenRunnable::Run() {
+ if (!NS_IsMainThread()) {
+ // We always want to use the internal slot for BrowserID; in particular,
+ // we want to avoid smartcard slots.
+ PK11SlotInfo* slot = PK11_GetInternalSlot();
+ if (!slot) {
+ mRv = NS_ERROR_UNEXPECTED;
+ } else {
+ SECKEYPrivateKey* privk = nullptr;
+ SECKEYPublicKey* pubk = nullptr;
+
+ switch (mKeyType) {
+ case rsaKey:
+ mRv = GenerateRSAKeyPair(slot, &privk, &pubk);
+ break;
+ case dsaKey:
+ mRv = GenerateDSAKeyPair(slot, &privk, &pubk);
+ break;
+ default:
+ MOZ_CRASH("unknown key type");
+ }
+
+ PK11_FreeSlot(slot);
+
+ if (NS_SUCCEEDED(mRv)) {
+ MOZ_ASSERT(privk);
+ MOZ_ASSERT(pubk);
+ // mKeyPair will take over ownership of privk and pubk
+ mKeyPair = new KeyPair(privk, pubk, mThread);
+ }
+ }
+
+ NS_DispatchToMainThread(this);
+ } else {
+ // Back on Main Thread
+ (void)mCallback->GenerateKeyPairFinished(mRv, mKeyPair);
+ }
+ return NS_OK;
+}
+
+SignRunnable::SignRunnable(const nsACString& aText,
+ SECKEYPrivateKey* privateKey,
+ nsIIdentitySignCallback* aCallback)
+ : mozilla::Runnable("SignRunnable"),
+ mTextToSign(aText),
+ mPrivateKey(SECKEY_CopyPrivateKey(privateKey)),
+ mCallback(new nsMainThreadPtrHolder<nsIIdentitySignCallback>(
+ "SignRunnable::mCallback", aCallback)),
+ mRv(NS_ERROR_NOT_INITIALIZED) {}
+
+NS_IMETHODIMP
+SignRunnable::Run() {
+ if (!NS_IsMainThread()) {
+ // We need the output in PKCS#11 format, not DER encoding, so we must use
+ // PK11_HashBuf and PK11_Sign instead of SEC_SignData.
+
+ SECItem sig = {siBuffer, nullptr, 0};
+ int sigLength = PK11_SignatureLen(mPrivateKey);
+ if (sigLength <= 0) {
+ mRv = mozilla::psm::GetXPCOMFromNSSError(PR_GetError());
+ } else if (!SECITEM_AllocItem(nullptr, &sig, sigLength)) {
+ mRv = mozilla::psm::GetXPCOMFromNSSError(PR_GetError());
+ } else {
+ uint8_t hash[32]; // big enough for SHA-1 or SHA-256
+ SECOidTag hashAlg =
+ mPrivateKey->keyType == dsaKey ? SEC_OID_SHA1 : SEC_OID_SHA256;
+ SECItem hashItem = {siBuffer, hash, hashAlg == SEC_OID_SHA1 ? 20u : 32u};
+
+ mRv = MapSECStatus(
+ PK11_HashBuf(hashAlg, hash,
+ const_cast<uint8_t*>(
+ reinterpret_cast<const uint8_t*>(mTextToSign.get())),
+ mTextToSign.Length()));
+ if (NS_SUCCEEDED(mRv)) {
+ mRv = MapSECStatus(PK11_Sign(mPrivateKey, &sig, &hashItem));
+ }
+ if (NS_SUCCEEDED(mRv)) {
+ mRv =
+ Base64URLEncode(sig.len, sig.data,
+ Base64URLEncodePaddingPolicy::Include, mSignature);
+ }
+ SECITEM_FreeItem(&sig, false);
+ }
+
+ NS_DispatchToMainThread(this);
+ } else {
+ // Back on Main Thread
+ (void)mCallback->SignFinished(mRv, mSignature);
+ }
+
+ return NS_OK;
+}
+} // unnamed namespace
+
+// XPCOM module registration
+
+NS_IMPL_COMPONENT_FACTORY(nsIIdentityCryptoService) {
+ auto inst = MakeRefPtr<IdentityCryptoService>();
+ if (NS_SUCCEEDED(inst->Init())) {
+ return inst.forget().downcast<nsIIdentityCryptoService>();
+ }
+ return nullptr;
+}