gecko-dev/services/crypto/component/IdentityCryptoService.cpp

463 строки
15 KiB
C++

/* -*- 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 (NS_LITERAL_CSTRING("DS160"))
#define RSA_KEY_TYPE_STRING (NS_LITERAL_CSTRING("RS256"))
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
private:
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 = thread.forget();
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) {}
MOZ_MUST_USE 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;
}
MOZ_MUST_USE 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);
}
MOZ_MUST_USE 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;
}