mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-28 23:31:56 +00:00
28a88f8f35
The std::unique_ptr based UniqueX types provide better safety over managing raw pointers. MozReview-Commit-ID: EwwOfs6RHqy --HG-- extra : rebase_source : 7fbfca837c09b641bfffcba854d46b3f79645c0d
1346 lines
40 KiB
C++
1346 lines
40 KiB
C++
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* vim: set ts=8 sts=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 "CryptoKey.h"
|
|
|
|
#include "cryptohi.h"
|
|
#include "mozilla/ArrayUtils.h"
|
|
#include "mozilla/dom/SubtleCryptoBinding.h"
|
|
#include "mozilla/dom/ToJSValue.h"
|
|
#include "nsNSSComponent.h"
|
|
#include "pk11pub.h"
|
|
|
|
// Templates taken from security/nss/lib/cryptohi/seckey.c
|
|
// These would ideally be exported by NSS and until that
|
|
// happens we have to keep our own copies.
|
|
const SEC_ASN1Template SECKEY_DHPublicKeyTemplate[] = {
|
|
{ SEC_ASN1_INTEGER, offsetof(SECKEYPublicKey,u.dh.publicValue), },
|
|
{ 0, }
|
|
};
|
|
const SEC_ASN1Template SECKEY_DHParamKeyTemplate[] = {
|
|
{ SEC_ASN1_SEQUENCE, 0, NULL, sizeof(SECKEYPublicKey) },
|
|
{ SEC_ASN1_INTEGER, offsetof(SECKEYPublicKey,u.dh.prime), },
|
|
{ SEC_ASN1_INTEGER, offsetof(SECKEYPublicKey,u.dh.base), },
|
|
{ SEC_ASN1_SKIP_REST },
|
|
{ 0, }
|
|
};
|
|
|
|
namespace mozilla {
|
|
namespace dom {
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(CryptoKey, mGlobal)
|
|
NS_IMPL_CYCLE_COLLECTING_ADDREF(CryptoKey)
|
|
NS_IMPL_CYCLE_COLLECTING_RELEASE(CryptoKey)
|
|
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CryptoKey)
|
|
NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
|
|
NS_INTERFACE_MAP_ENTRY(nsISupports)
|
|
NS_INTERFACE_MAP_END
|
|
|
|
nsresult
|
|
StringToUsage(const nsString& aUsage, CryptoKey::KeyUsage& aUsageOut)
|
|
{
|
|
if (aUsage.EqualsLiteral(WEBCRYPTO_KEY_USAGE_ENCRYPT)) {
|
|
aUsageOut = CryptoKey::ENCRYPT;
|
|
} else if (aUsage.EqualsLiteral(WEBCRYPTO_KEY_USAGE_DECRYPT)) {
|
|
aUsageOut = CryptoKey::DECRYPT;
|
|
} else if (aUsage.EqualsLiteral(WEBCRYPTO_KEY_USAGE_SIGN)) {
|
|
aUsageOut = CryptoKey::SIGN;
|
|
} else if (aUsage.EqualsLiteral(WEBCRYPTO_KEY_USAGE_VERIFY)) {
|
|
aUsageOut = CryptoKey::VERIFY;
|
|
} else if (aUsage.EqualsLiteral(WEBCRYPTO_KEY_USAGE_DERIVEKEY)) {
|
|
aUsageOut = CryptoKey::DERIVEKEY;
|
|
} else if (aUsage.EqualsLiteral(WEBCRYPTO_KEY_USAGE_DERIVEBITS)) {
|
|
aUsageOut = CryptoKey::DERIVEBITS;
|
|
} else if (aUsage.EqualsLiteral(WEBCRYPTO_KEY_USAGE_WRAPKEY)) {
|
|
aUsageOut = CryptoKey::WRAPKEY;
|
|
} else if (aUsage.EqualsLiteral(WEBCRYPTO_KEY_USAGE_UNWRAPKEY)) {
|
|
aUsageOut = CryptoKey::UNWRAPKEY;
|
|
} else {
|
|
return NS_ERROR_DOM_SYNTAX_ERR;
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
// This helper function will release the memory backing a SECKEYPrivateKey and
|
|
// any resources acquired in its creation. It will leave the backing PKCS#11
|
|
// object untouched, however. This should only be called from
|
|
// PrivateKeyFromPrivateKeyTemplate.
|
|
static void
|
|
DestroyPrivateKeyWithoutDestroyingPKCS11Object(SECKEYPrivateKey* key)
|
|
{
|
|
PK11_FreeSlot(key->pkcs11Slot);
|
|
PORT_FreeArena(key->arena, PR_TRUE);
|
|
}
|
|
|
|
// To protect against key ID collisions, PrivateKeyFromPrivateKeyTemplate
|
|
// generates a random ID for each key. The given template must contain an
|
|
// attribute slot for a key ID, but it must consist of a null pointer and have a
|
|
// length of 0.
|
|
UniqueSECKEYPrivateKey
|
|
PrivateKeyFromPrivateKeyTemplate(CK_ATTRIBUTE* aTemplate,
|
|
CK_ULONG aTemplateSize)
|
|
{
|
|
// Create a generic object with the contents of the key
|
|
UniquePK11SlotInfo slot(PK11_GetInternalSlot());
|
|
if (!slot) {
|
|
return nullptr;
|
|
}
|
|
|
|
// Generate a random 160-bit object ID. This ID must be unique.
|
|
UniqueSECItem objID(::SECITEM_AllocItem(nullptr, nullptr, 20));
|
|
SECStatus rv = PK11_GenerateRandomOnSlot(slot.get(), objID->data, objID->len);
|
|
if (rv != SECSuccess) {
|
|
return nullptr;
|
|
}
|
|
// Check if something is already using this ID.
|
|
SECKEYPrivateKey* preexistingKey = PK11_FindKeyByKeyID(slot.get(),
|
|
objID.get(),
|
|
nullptr);
|
|
if (preexistingKey) {
|
|
// Note that we can't just call SECKEY_DestroyPrivateKey here because that
|
|
// will destroy the PKCS#11 object that is backing a preexisting key (that
|
|
// we still have a handle on somewhere else in memory). If that object were
|
|
// destroyed, cryptographic operations performed by that other key would
|
|
// fail.
|
|
DestroyPrivateKeyWithoutDestroyingPKCS11Object(preexistingKey);
|
|
// Try again with a new ID (but only once - collisions are very unlikely).
|
|
rv = PK11_GenerateRandomOnSlot(slot.get(), objID->data, objID->len);
|
|
if (rv != SECSuccess) {
|
|
return nullptr;
|
|
}
|
|
preexistingKey = PK11_FindKeyByKeyID(slot.get(), objID.get(), nullptr);
|
|
if (preexistingKey) {
|
|
DestroyPrivateKeyWithoutDestroyingPKCS11Object(preexistingKey);
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
CK_ATTRIBUTE* idAttributeSlot = nullptr;
|
|
for (CK_ULONG i = 0; i < aTemplateSize; i++) {
|
|
if (aTemplate[i].type == CKA_ID) {
|
|
if (aTemplate[i].pValue != nullptr || aTemplate[i].ulValueLen != 0) {
|
|
return nullptr;
|
|
}
|
|
idAttributeSlot = aTemplate + i;
|
|
break;
|
|
}
|
|
}
|
|
if (!idAttributeSlot) {
|
|
return nullptr;
|
|
}
|
|
|
|
idAttributeSlot->pValue = objID->data;
|
|
idAttributeSlot->ulValueLen = objID->len;
|
|
UniquePK11GenericObject obj(PK11_CreateGenericObject(slot.get(),
|
|
aTemplate,
|
|
aTemplateSize,
|
|
PR_FALSE));
|
|
// Unset the ID attribute slot's pointer and length so that data that only
|
|
// lives for the scope of this function doesn't escape.
|
|
idAttributeSlot->pValue = nullptr;
|
|
idAttributeSlot->ulValueLen = 0;
|
|
if (!obj) {
|
|
return nullptr;
|
|
}
|
|
|
|
// Have NSS translate the object to a private key.
|
|
return UniqueSECKEYPrivateKey(
|
|
PK11_FindKeyByKeyID(slot.get(), objID.get(), nullptr));
|
|
}
|
|
|
|
CryptoKey::CryptoKey(nsIGlobalObject* aGlobal)
|
|
: mGlobal(aGlobal)
|
|
, mAttributes(0)
|
|
, mSymKey()
|
|
, mPrivateKey(nullptr)
|
|
, mPublicKey(nullptr)
|
|
{
|
|
}
|
|
|
|
CryptoKey::~CryptoKey()
|
|
{
|
|
nsNSSShutDownPreventionLock locker;
|
|
if (isAlreadyShutDown()) {
|
|
return;
|
|
}
|
|
destructorSafeDestroyNSSReference();
|
|
shutdown(ShutdownCalledFrom::Object);
|
|
}
|
|
|
|
JSObject*
|
|
CryptoKey::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
|
|
{
|
|
return CryptoKeyBinding::Wrap(aCx, this, aGivenProto);
|
|
}
|
|
|
|
void
|
|
CryptoKey::GetType(nsString& aRetVal) const
|
|
{
|
|
uint32_t type = mAttributes & TYPE_MASK;
|
|
switch (type) {
|
|
case PUBLIC: aRetVal.AssignLiteral(WEBCRYPTO_KEY_TYPE_PUBLIC); break;
|
|
case PRIVATE: aRetVal.AssignLiteral(WEBCRYPTO_KEY_TYPE_PRIVATE); break;
|
|
case SECRET: aRetVal.AssignLiteral(WEBCRYPTO_KEY_TYPE_SECRET); break;
|
|
}
|
|
}
|
|
|
|
bool
|
|
CryptoKey::Extractable() const
|
|
{
|
|
return (mAttributes & EXTRACTABLE);
|
|
}
|
|
|
|
void
|
|
CryptoKey::GetAlgorithm(JSContext* cx, JS::MutableHandle<JSObject*> aRetVal,
|
|
ErrorResult& aRv) const
|
|
{
|
|
bool converted = false;
|
|
JS::RootedValue val(cx);
|
|
switch (mAlgorithm.mType) {
|
|
case KeyAlgorithmProxy::AES:
|
|
converted = ToJSValue(cx, mAlgorithm.mAes, &val);
|
|
break;
|
|
case KeyAlgorithmProxy::HMAC:
|
|
converted = ToJSValue(cx, mAlgorithm.mHmac, &val);
|
|
break;
|
|
case KeyAlgorithmProxy::RSA: {
|
|
RootedDictionary<RsaHashedKeyAlgorithm> rsa(cx);
|
|
converted = mAlgorithm.mRsa.ToKeyAlgorithm(cx, rsa);
|
|
if (converted) {
|
|
converted = ToJSValue(cx, rsa, &val);
|
|
}
|
|
break;
|
|
}
|
|
case KeyAlgorithmProxy::EC:
|
|
converted = ToJSValue(cx, mAlgorithm.mEc, &val);
|
|
break;
|
|
case KeyAlgorithmProxy::DH: {
|
|
RootedDictionary<DhKeyAlgorithm> dh(cx);
|
|
converted = mAlgorithm.mDh.ToKeyAlgorithm(cx, dh);
|
|
if (converted) {
|
|
converted = ToJSValue(cx, dh, &val);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
if (!converted) {
|
|
aRv.Throw(NS_ERROR_DOM_OPERATION_ERR);
|
|
return;
|
|
}
|
|
|
|
aRetVal.set(&val.toObject());
|
|
}
|
|
|
|
void
|
|
CryptoKey::GetUsages(nsTArray<nsString>& aRetVal) const
|
|
{
|
|
if (mAttributes & ENCRYPT) {
|
|
aRetVal.AppendElement(NS_LITERAL_STRING(WEBCRYPTO_KEY_USAGE_ENCRYPT));
|
|
}
|
|
if (mAttributes & DECRYPT) {
|
|
aRetVal.AppendElement(NS_LITERAL_STRING(WEBCRYPTO_KEY_USAGE_DECRYPT));
|
|
}
|
|
if (mAttributes & SIGN) {
|
|
aRetVal.AppendElement(NS_LITERAL_STRING(WEBCRYPTO_KEY_USAGE_SIGN));
|
|
}
|
|
if (mAttributes & VERIFY) {
|
|
aRetVal.AppendElement(NS_LITERAL_STRING(WEBCRYPTO_KEY_USAGE_VERIFY));
|
|
}
|
|
if (mAttributes & DERIVEKEY) {
|
|
aRetVal.AppendElement(NS_LITERAL_STRING(WEBCRYPTO_KEY_USAGE_DERIVEKEY));
|
|
}
|
|
if (mAttributes & DERIVEBITS) {
|
|
aRetVal.AppendElement(NS_LITERAL_STRING(WEBCRYPTO_KEY_USAGE_DERIVEBITS));
|
|
}
|
|
if (mAttributes & WRAPKEY) {
|
|
aRetVal.AppendElement(NS_LITERAL_STRING(WEBCRYPTO_KEY_USAGE_WRAPKEY));
|
|
}
|
|
if (mAttributes & UNWRAPKEY) {
|
|
aRetVal.AppendElement(NS_LITERAL_STRING(WEBCRYPTO_KEY_USAGE_UNWRAPKEY));
|
|
}
|
|
}
|
|
|
|
KeyAlgorithmProxy&
|
|
CryptoKey::Algorithm()
|
|
{
|
|
return mAlgorithm;
|
|
}
|
|
|
|
const KeyAlgorithmProxy&
|
|
CryptoKey::Algorithm() const
|
|
{
|
|
return mAlgorithm;
|
|
}
|
|
|
|
CryptoKey::KeyType
|
|
CryptoKey::GetKeyType() const
|
|
{
|
|
return static_cast<CryptoKey::KeyType>(mAttributes & TYPE_MASK);
|
|
}
|
|
|
|
nsresult
|
|
CryptoKey::SetType(const nsString& aType)
|
|
{
|
|
mAttributes &= CLEAR_TYPE;
|
|
if (aType.EqualsLiteral(WEBCRYPTO_KEY_TYPE_SECRET)) {
|
|
mAttributes |= SECRET;
|
|
} else if (aType.EqualsLiteral(WEBCRYPTO_KEY_TYPE_PUBLIC)) {
|
|
mAttributes |= PUBLIC;
|
|
} else if (aType.EqualsLiteral(WEBCRYPTO_KEY_TYPE_PRIVATE)) {
|
|
mAttributes |= PRIVATE;
|
|
} else {
|
|
mAttributes |= UNKNOWN;
|
|
return NS_ERROR_DOM_SYNTAX_ERR;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
void
|
|
CryptoKey::SetType(CryptoKey::KeyType aType)
|
|
{
|
|
mAttributes &= CLEAR_TYPE;
|
|
mAttributes |= aType;
|
|
}
|
|
|
|
void
|
|
CryptoKey::SetExtractable(bool aExtractable)
|
|
{
|
|
mAttributes &= CLEAR_EXTRACTABLE;
|
|
if (aExtractable) {
|
|
mAttributes |= EXTRACTABLE;
|
|
}
|
|
}
|
|
|
|
// NSS exports private EC keys without the CKA_EC_POINT attribute, i.e. the
|
|
// public value. To properly export the private key to JWK or PKCS #8 we need
|
|
// the public key data though and so we use this method to augment a private
|
|
// key with data from the given public key.
|
|
nsresult
|
|
CryptoKey::AddPublicKeyData(SECKEYPublicKey* aPublicKey)
|
|
{
|
|
// This should be a private key.
|
|
MOZ_ASSERT(GetKeyType() == PRIVATE);
|
|
// There should be a private NSS key with type 'EC'.
|
|
MOZ_ASSERT(mPrivateKey && mPrivateKey->keyType == ecKey);
|
|
// The given public key should have the same key type.
|
|
MOZ_ASSERT(aPublicKey->keyType == mPrivateKey->keyType);
|
|
|
|
nsNSSShutDownPreventionLock locker;
|
|
|
|
// Read EC params.
|
|
ScopedAutoSECItem params;
|
|
SECStatus rv = PK11_ReadRawAttribute(PK11_TypePrivKey, mPrivateKey.get(),
|
|
CKA_EC_PARAMS, ¶ms);
|
|
if (rv != SECSuccess) {
|
|
return NS_ERROR_DOM_OPERATION_ERR;
|
|
}
|
|
|
|
// Read private value.
|
|
ScopedAutoSECItem value;
|
|
rv = PK11_ReadRawAttribute(PK11_TypePrivKey, mPrivateKey.get(), CKA_VALUE,
|
|
&value);
|
|
if (rv != SECSuccess) {
|
|
return NS_ERROR_DOM_OPERATION_ERR;
|
|
}
|
|
|
|
SECItem* point = &aPublicKey->u.ec.publicValue;
|
|
CK_OBJECT_CLASS privateKeyValue = CKO_PRIVATE_KEY;
|
|
CK_BBOOL falseValue = CK_FALSE;
|
|
CK_KEY_TYPE ecValue = CKK_EC;
|
|
|
|
CK_ATTRIBUTE keyTemplate[9] = {
|
|
{ CKA_CLASS, &privateKeyValue, sizeof(privateKeyValue) },
|
|
{ CKA_KEY_TYPE, &ecValue, sizeof(ecValue) },
|
|
{ CKA_TOKEN, &falseValue, sizeof(falseValue) },
|
|
{ CKA_SENSITIVE, &falseValue, sizeof(falseValue) },
|
|
{ CKA_PRIVATE, &falseValue, sizeof(falseValue) },
|
|
// PrivateKeyFromPrivateKeyTemplate sets the ID.
|
|
{ CKA_ID, nullptr, 0 },
|
|
{ CKA_EC_PARAMS, params.data, params.len },
|
|
{ CKA_EC_POINT, point->data, point->len },
|
|
{ CKA_VALUE, value.data, value.len },
|
|
};
|
|
|
|
mPrivateKey = PrivateKeyFromPrivateKeyTemplate(keyTemplate,
|
|
ArrayLength(keyTemplate));
|
|
NS_ENSURE_TRUE(mPrivateKey, NS_ERROR_DOM_OPERATION_ERR);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
void
|
|
CryptoKey::ClearUsages()
|
|
{
|
|
mAttributes &= CLEAR_USAGES;
|
|
}
|
|
|
|
nsresult
|
|
CryptoKey::AddUsage(const nsString& aUsage)
|
|
{
|
|
return AddUsageIntersecting(aUsage, USAGES_MASK);
|
|
}
|
|
|
|
nsresult
|
|
CryptoKey::AddUsageIntersecting(const nsString& aUsage, uint32_t aUsageMask)
|
|
{
|
|
KeyUsage usage;
|
|
if (NS_FAILED(StringToUsage(aUsage, usage))) {
|
|
return NS_ERROR_DOM_SYNTAX_ERR;
|
|
}
|
|
|
|
if (usage & aUsageMask) {
|
|
AddUsage(usage);
|
|
return NS_OK;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
void
|
|
CryptoKey::AddUsage(CryptoKey::KeyUsage aUsage)
|
|
{
|
|
mAttributes |= aUsage;
|
|
}
|
|
|
|
bool
|
|
CryptoKey::HasAnyUsage()
|
|
{
|
|
return !!(mAttributes & USAGES_MASK);
|
|
}
|
|
|
|
bool
|
|
CryptoKey::HasUsage(CryptoKey::KeyUsage aUsage)
|
|
{
|
|
return !!(mAttributes & aUsage);
|
|
}
|
|
|
|
bool
|
|
CryptoKey::HasUsageOtherThan(uint32_t aUsages)
|
|
{
|
|
return !!(mAttributes & USAGES_MASK & ~aUsages);
|
|
}
|
|
|
|
bool
|
|
CryptoKey::IsRecognizedUsage(const nsString& aUsage)
|
|
{
|
|
KeyUsage dummy;
|
|
nsresult rv = StringToUsage(aUsage, dummy);
|
|
return NS_SUCCEEDED(rv);
|
|
}
|
|
|
|
bool
|
|
CryptoKey::AllUsagesRecognized(const Sequence<nsString>& aUsages)
|
|
{
|
|
for (uint32_t i = 0; i < aUsages.Length(); ++i) {
|
|
if (!IsRecognizedUsage(aUsages[i])) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
nsresult CryptoKey::SetSymKey(const CryptoBuffer& aSymKey)
|
|
{
|
|
if (!mSymKey.Assign(aSymKey)) {
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
CryptoKey::SetPrivateKey(SECKEYPrivateKey* aPrivateKey)
|
|
{
|
|
nsNSSShutDownPreventionLock locker;
|
|
|
|
if (!aPrivateKey || isAlreadyShutDown()) {
|
|
mPrivateKey = nullptr;
|
|
return NS_OK;
|
|
}
|
|
|
|
mPrivateKey = UniqueSECKEYPrivateKey(SECKEY_CopyPrivateKey(aPrivateKey));
|
|
return mPrivateKey ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
|
|
nsresult
|
|
CryptoKey::SetPublicKey(SECKEYPublicKey* aPublicKey)
|
|
{
|
|
nsNSSShutDownPreventionLock locker;
|
|
|
|
if (!aPublicKey || isAlreadyShutDown()) {
|
|
mPublicKey = nullptr;
|
|
return NS_OK;
|
|
}
|
|
|
|
mPublicKey = UniqueSECKEYPublicKey(SECKEY_CopyPublicKey(aPublicKey));
|
|
return mPublicKey ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
|
|
const CryptoBuffer&
|
|
CryptoKey::GetSymKey() const
|
|
{
|
|
return mSymKey;
|
|
}
|
|
|
|
UniqueSECKEYPrivateKey
|
|
CryptoKey::GetPrivateKey() const
|
|
{
|
|
nsNSSShutDownPreventionLock locker;
|
|
if (!mPrivateKey || isAlreadyShutDown()) {
|
|
return nullptr;
|
|
}
|
|
return UniqueSECKEYPrivateKey(SECKEY_CopyPrivateKey(mPrivateKey.get()));
|
|
}
|
|
|
|
UniqueSECKEYPublicKey
|
|
CryptoKey::GetPublicKey() const
|
|
{
|
|
nsNSSShutDownPreventionLock locker;
|
|
if (!mPublicKey || isAlreadyShutDown()) {
|
|
return nullptr;
|
|
}
|
|
return UniqueSECKEYPublicKey(SECKEY_CopyPublicKey(mPublicKey.get()));
|
|
}
|
|
|
|
void CryptoKey::virtualDestroyNSSReference()
|
|
{
|
|
destructorSafeDestroyNSSReference();
|
|
}
|
|
|
|
void CryptoKey::destructorSafeDestroyNSSReference()
|
|
{
|
|
mPrivateKey = nullptr;
|
|
mPublicKey = nullptr;
|
|
}
|
|
|
|
|
|
// Serialization and deserialization convenience methods
|
|
|
|
UniqueSECKEYPrivateKey
|
|
CryptoKey::PrivateKeyFromPkcs8(CryptoBuffer& aKeyData,
|
|
const nsNSSShutDownPreventionLock& /*proofOfLock*/)
|
|
{
|
|
UniquePK11SlotInfo slot(PK11_GetInternalSlot());
|
|
if (!slot) {
|
|
return nullptr;
|
|
}
|
|
|
|
UniquePLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE));
|
|
if (!arena) {
|
|
return nullptr;
|
|
}
|
|
|
|
SECItem pkcs8Item = { siBuffer, nullptr, 0 };
|
|
if (!aKeyData.ToSECItem(arena.get(), &pkcs8Item)) {
|
|
return nullptr;
|
|
}
|
|
|
|
// Allow everything, we enforce usage ourselves
|
|
unsigned int usage = KU_ALL;
|
|
|
|
SECKEYPrivateKey* privKey;
|
|
SECStatus rv = PK11_ImportDERPrivateKeyInfoAndReturnKey(
|
|
slot.get(), &pkcs8Item, nullptr, nullptr, false, false,
|
|
usage, &privKey, nullptr);
|
|
|
|
if (rv == SECFailure) {
|
|
return nullptr;
|
|
}
|
|
|
|
return UniqueSECKEYPrivateKey(privKey);
|
|
}
|
|
|
|
UniqueSECKEYPublicKey
|
|
CryptoKey::PublicKeyFromSpki(CryptoBuffer& aKeyData,
|
|
const nsNSSShutDownPreventionLock& /*proofOfLock*/)
|
|
{
|
|
UniquePLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE));
|
|
if (!arena) {
|
|
return nullptr;
|
|
}
|
|
|
|
SECItem spkiItem = { siBuffer, nullptr, 0 };
|
|
if (!aKeyData.ToSECItem(arena.get(), &spkiItem)) {
|
|
return nullptr;
|
|
}
|
|
|
|
UniqueCERTSubjectPublicKeyInfo spki(
|
|
SECKEY_DecodeDERSubjectPublicKeyInfo(&spkiItem));
|
|
if (!spki) {
|
|
return nullptr;
|
|
}
|
|
|
|
bool isECDHAlgorithm = SECITEM_ItemsAreEqual(&SEC_OID_DATA_EC_DH,
|
|
&spki->algorithm.algorithm);
|
|
bool isDHAlgorithm = SECITEM_ItemsAreEqual(&SEC_OID_DATA_DH_KEY_AGREEMENT,
|
|
&spki->algorithm.algorithm);
|
|
|
|
// Check for |id-ecDH| and |dhKeyAgreement|. Per the WebCrypto spec we must
|
|
// support these OIDs but NSS does unfortunately not know about them. Let's
|
|
// change the algorithm to |id-ecPublicKey| or |dhPublicKey| to make NSS happy.
|
|
if (isECDHAlgorithm || isDHAlgorithm) {
|
|
SECOidTag oid = SEC_OID_UNKNOWN;
|
|
if (isECDHAlgorithm) {
|
|
oid = SEC_OID_ANSIX962_EC_PUBLIC_KEY;
|
|
} else if (isDHAlgorithm) {
|
|
oid = SEC_OID_X942_DIFFIE_HELMAN_KEY;
|
|
} else {
|
|
MOZ_ASSERT(false);
|
|
}
|
|
|
|
SECOidData* oidData = SECOID_FindOIDByTag(oid);
|
|
if (!oidData) {
|
|
return nullptr;
|
|
}
|
|
|
|
SECStatus rv = SECITEM_CopyItem(spki->arena, &spki->algorithm.algorithm,
|
|
&oidData->oid);
|
|
if (rv != SECSuccess) {
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
UniqueSECKEYPublicKey tmp(SECKEY_ExtractPublicKey(spki.get()));
|
|
if (!tmp.get() || !PublicKeyValid(tmp.get())) {
|
|
return nullptr;
|
|
}
|
|
|
|
return UniqueSECKEYPublicKey(SECKEY_CopyPublicKey(tmp.get()));
|
|
}
|
|
|
|
nsresult
|
|
CryptoKey::PrivateKeyToPkcs8(SECKEYPrivateKey* aPrivKey,
|
|
CryptoBuffer& aRetVal,
|
|
const nsNSSShutDownPreventionLock& /*proofOfLock*/)
|
|
{
|
|
UniqueSECItem pkcs8Item(PK11_ExportDERPrivateKeyInfo(aPrivKey, nullptr));
|
|
if (!pkcs8Item.get()) {
|
|
return NS_ERROR_DOM_INVALID_ACCESS_ERR;
|
|
}
|
|
if (!aRetVal.Assign(pkcs8Item.get())) {
|
|
return NS_ERROR_DOM_OPERATION_ERR;
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
PublicDhKeyToSpki(SECKEYPublicKey* aPubKey,
|
|
CERTSubjectPublicKeyInfo* aSpki)
|
|
{
|
|
SECItem* params = ::SECITEM_AllocItem(aSpki->arena, nullptr, 0);
|
|
if (!params) {
|
|
return NS_ERROR_DOM_OPERATION_ERR;
|
|
}
|
|
|
|
SECItem* rvItem = SEC_ASN1EncodeItem(aSpki->arena, params, aPubKey,
|
|
SECKEY_DHParamKeyTemplate);
|
|
if (!rvItem) {
|
|
return NS_ERROR_DOM_OPERATION_ERR;
|
|
}
|
|
|
|
SECStatus rv = SECOID_SetAlgorithmID(aSpki->arena, &aSpki->algorithm,
|
|
SEC_OID_X942_DIFFIE_HELMAN_KEY, params);
|
|
if (rv != SECSuccess) {
|
|
return NS_ERROR_DOM_OPERATION_ERR;
|
|
}
|
|
|
|
rvItem = SEC_ASN1EncodeItem(aSpki->arena, &aSpki->subjectPublicKey, aPubKey,
|
|
SECKEY_DHPublicKeyTemplate);
|
|
if (!rvItem) {
|
|
return NS_ERROR_DOM_OPERATION_ERR;
|
|
}
|
|
|
|
// The public value is a BIT_STRING encoded as an INTEGER. After encoding
|
|
// an INT we need to adjust the length to reflect the number of bits.
|
|
aSpki->subjectPublicKey.len <<= 3;
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
CryptoKey::PublicKeyToSpki(SECKEYPublicKey* aPubKey,
|
|
CryptoBuffer& aRetVal,
|
|
const nsNSSShutDownPreventionLock& /*proofOfLock*/)
|
|
{
|
|
UniqueCERTSubjectPublicKeyInfo spki;
|
|
|
|
// NSS doesn't support exporting DH public keys.
|
|
if (aPubKey->keyType == dhKey) {
|
|
// Mimic the behavior of SECKEY_CreateSubjectPublicKeyInfo() and create
|
|
// a new arena for the SPKI object.
|
|
UniquePLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE));
|
|
if (!arena) {
|
|
return NS_ERROR_DOM_OPERATION_ERR;
|
|
}
|
|
|
|
spki.reset(PORT_ArenaZNew(arena.get(), CERTSubjectPublicKeyInfo));
|
|
if (!spki) {
|
|
return NS_ERROR_DOM_OPERATION_ERR;
|
|
}
|
|
|
|
// Assign |arena| to |spki| and null the variable afterwards so that the
|
|
// arena created above that holds the SPKI object is free'd when |spki|
|
|
// goes out of scope, not when |arena| does.
|
|
spki->arena = arena.release();
|
|
|
|
nsresult rv = PublicDhKeyToSpki(aPubKey, spki.get());
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
} else {
|
|
spki.reset(SECKEY_CreateSubjectPublicKeyInfo(aPubKey));
|
|
if (!spki) {
|
|
return NS_ERROR_DOM_OPERATION_ERR;
|
|
}
|
|
}
|
|
|
|
// Per WebCrypto spec we must export ECDH SPKIs with the algorithm OID
|
|
// id-ecDH (1.3.132.112) and DH SPKIs with OID dhKeyAgreement
|
|
// (1.2.840.113549.1.3.1). NSS doesn't know about these OIDs and there is
|
|
// no way to specify the algorithm to use when exporting a public key.
|
|
if (aPubKey->keyType == ecKey || aPubKey->keyType == dhKey) {
|
|
const SECItem* oidData = nullptr;
|
|
if (aPubKey->keyType == ecKey) {
|
|
oidData = &SEC_OID_DATA_EC_DH;
|
|
} else if (aPubKey->keyType == dhKey) {
|
|
oidData = &SEC_OID_DATA_DH_KEY_AGREEMENT;
|
|
} else {
|
|
MOZ_ASSERT(false);
|
|
}
|
|
|
|
SECStatus rv = SECITEM_CopyItem(spki->arena, &spki->algorithm.algorithm,
|
|
oidData);
|
|
if (rv != SECSuccess) {
|
|
return NS_ERROR_DOM_OPERATION_ERR;
|
|
}
|
|
}
|
|
|
|
const SEC_ASN1Template* tpl = SEC_ASN1_GET(CERT_SubjectPublicKeyInfoTemplate);
|
|
UniqueSECItem spkiItem(SEC_ASN1EncodeItem(nullptr, nullptr, spki.get(), tpl));
|
|
|
|
if (!aRetVal.Assign(spkiItem.get())) {
|
|
return NS_ERROR_DOM_OPERATION_ERR;
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
SECItem*
|
|
CreateECPointForCoordinates(const CryptoBuffer& aX,
|
|
const CryptoBuffer& aY,
|
|
PLArenaPool* aArena)
|
|
{
|
|
// Check that both points have the same length.
|
|
if (aX.Length() != aY.Length()) {
|
|
return nullptr;
|
|
}
|
|
|
|
// Create point.
|
|
SECItem* point = ::SECITEM_AllocItem(aArena, nullptr, aX.Length() + aY.Length() + 1);
|
|
if (!point) {
|
|
return nullptr;
|
|
}
|
|
|
|
// Set point data.
|
|
point->data[0] = EC_POINT_FORM_UNCOMPRESSED;
|
|
memcpy(point->data + 1, aX.Elements(), aX.Length());
|
|
memcpy(point->data + 1 + aX.Length(), aY.Elements(), aY.Length());
|
|
|
|
return point;
|
|
}
|
|
|
|
UniqueSECKEYPrivateKey
|
|
CryptoKey::PrivateKeyFromJwk(const JsonWebKey& aJwk,
|
|
const nsNSSShutDownPreventionLock& /*proofOfLock*/)
|
|
{
|
|
CK_OBJECT_CLASS privateKeyValue = CKO_PRIVATE_KEY;
|
|
CK_BBOOL falseValue = CK_FALSE;
|
|
|
|
if (aJwk.mKty.EqualsLiteral(JWK_TYPE_EC)) {
|
|
// Verify that all of the required parameters are present
|
|
CryptoBuffer x, y, d;
|
|
if (!aJwk.mCrv.WasPassed() ||
|
|
!aJwk.mX.WasPassed() || NS_FAILED(x.FromJwkBase64(aJwk.mX.Value())) ||
|
|
!aJwk.mY.WasPassed() || NS_FAILED(y.FromJwkBase64(aJwk.mY.Value())) ||
|
|
!aJwk.mD.WasPassed() || NS_FAILED(d.FromJwkBase64(aJwk.mD.Value()))) {
|
|
return nullptr;
|
|
}
|
|
|
|
nsString namedCurve;
|
|
if (!NormalizeToken(aJwk.mCrv.Value(), namedCurve)) {
|
|
return nullptr;
|
|
}
|
|
|
|
UniquePLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE));
|
|
if (!arena) {
|
|
return nullptr;
|
|
}
|
|
|
|
// Create parameters.
|
|
SECItem* params = CreateECParamsForCurve(namedCurve, arena.get());
|
|
if (!params) {
|
|
return nullptr;
|
|
}
|
|
|
|
SECItem* ecPoint = CreateECPointForCoordinates(x, y, arena.get());
|
|
if (!ecPoint) {
|
|
return nullptr;
|
|
}
|
|
|
|
// Populate template from parameters
|
|
CK_KEY_TYPE ecValue = CKK_EC;
|
|
CK_ATTRIBUTE keyTemplate[9] = {
|
|
{ CKA_CLASS, &privateKeyValue, sizeof(privateKeyValue) },
|
|
{ CKA_KEY_TYPE, &ecValue, sizeof(ecValue) },
|
|
{ CKA_TOKEN, &falseValue, sizeof(falseValue) },
|
|
{ CKA_SENSITIVE, &falseValue, sizeof(falseValue) },
|
|
{ CKA_PRIVATE, &falseValue, sizeof(falseValue) },
|
|
// PrivateKeyFromPrivateKeyTemplate sets the ID.
|
|
{ CKA_ID, nullptr, 0 },
|
|
{ CKA_EC_PARAMS, params->data, params->len },
|
|
{ CKA_EC_POINT, ecPoint->data, ecPoint->len },
|
|
{ CKA_VALUE, (void*) d.Elements(), (CK_ULONG) d.Length() },
|
|
};
|
|
|
|
return PrivateKeyFromPrivateKeyTemplate(keyTemplate,
|
|
ArrayLength(keyTemplate));
|
|
}
|
|
|
|
if (aJwk.mKty.EqualsLiteral(JWK_TYPE_RSA)) {
|
|
// Verify that all of the required parameters are present
|
|
CryptoBuffer n, e, d, p, q, dp, dq, qi;
|
|
if (!aJwk.mN.WasPassed() || NS_FAILED(n.FromJwkBase64(aJwk.mN.Value())) ||
|
|
!aJwk.mE.WasPassed() || NS_FAILED(e.FromJwkBase64(aJwk.mE.Value())) ||
|
|
!aJwk.mD.WasPassed() || NS_FAILED(d.FromJwkBase64(aJwk.mD.Value())) ||
|
|
!aJwk.mP.WasPassed() || NS_FAILED(p.FromJwkBase64(aJwk.mP.Value())) ||
|
|
!aJwk.mQ.WasPassed() || NS_FAILED(q.FromJwkBase64(aJwk.mQ.Value())) ||
|
|
!aJwk.mDp.WasPassed() || NS_FAILED(dp.FromJwkBase64(aJwk.mDp.Value())) ||
|
|
!aJwk.mDq.WasPassed() || NS_FAILED(dq.FromJwkBase64(aJwk.mDq.Value())) ||
|
|
!aJwk.mQi.WasPassed() || NS_FAILED(qi.FromJwkBase64(aJwk.mQi.Value()))) {
|
|
return nullptr;
|
|
}
|
|
|
|
// Populate template from parameters
|
|
CK_KEY_TYPE rsaValue = CKK_RSA;
|
|
CK_ATTRIBUTE keyTemplate[14] = {
|
|
{ CKA_CLASS, &privateKeyValue, sizeof(privateKeyValue) },
|
|
{ CKA_KEY_TYPE, &rsaValue, sizeof(rsaValue) },
|
|
{ CKA_TOKEN, &falseValue, sizeof(falseValue) },
|
|
{ CKA_SENSITIVE, &falseValue, sizeof(falseValue) },
|
|
{ CKA_PRIVATE, &falseValue, sizeof(falseValue) },
|
|
// PrivateKeyFromPrivateKeyTemplate sets the ID.
|
|
{ CKA_ID, nullptr, 0 },
|
|
{ CKA_MODULUS, (void*) n.Elements(), (CK_ULONG) n.Length() },
|
|
{ CKA_PUBLIC_EXPONENT, (void*) e.Elements(), (CK_ULONG) e.Length() },
|
|
{ CKA_PRIVATE_EXPONENT, (void*) d.Elements(), (CK_ULONG) d.Length() },
|
|
{ CKA_PRIME_1, (void*) p.Elements(), (CK_ULONG) p.Length() },
|
|
{ CKA_PRIME_2, (void*) q.Elements(), (CK_ULONG) q.Length() },
|
|
{ CKA_EXPONENT_1, (void*) dp.Elements(), (CK_ULONG) dp.Length() },
|
|
{ CKA_EXPONENT_2, (void*) dq.Elements(), (CK_ULONG) dq.Length() },
|
|
{ CKA_COEFFICIENT, (void*) qi.Elements(), (CK_ULONG) qi.Length() },
|
|
};
|
|
|
|
return PrivateKeyFromPrivateKeyTemplate(keyTemplate,
|
|
ArrayLength(keyTemplate));
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
bool ReadAndEncodeAttribute(SECKEYPrivateKey* aKey,
|
|
CK_ATTRIBUTE_TYPE aAttribute,
|
|
Optional<nsString>& aDst)
|
|
{
|
|
ScopedAutoSECItem item;
|
|
if (PK11_ReadRawAttribute(PK11_TypePrivKey, aKey, aAttribute, &item)
|
|
!= SECSuccess) {
|
|
return false;
|
|
}
|
|
|
|
CryptoBuffer buffer;
|
|
if (!buffer.Assign(&item)) {
|
|
return false;
|
|
}
|
|
|
|
if (NS_FAILED(buffer.ToJwkBase64(aDst.Value()))) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
ECKeyToJwk(const PK11ObjectType aKeyType, void* aKey, const SECItem* aEcParams,
|
|
const SECItem* aPublicValue, JsonWebKey& aRetVal)
|
|
{
|
|
aRetVal.mX.Construct();
|
|
aRetVal.mY.Construct();
|
|
|
|
// Check that the given EC parameters are valid.
|
|
if (!CheckEncodedECParameters(aEcParams)) {
|
|
return false;
|
|
}
|
|
|
|
// Construct the OID tag.
|
|
SECItem oid = { siBuffer, nullptr, 0 };
|
|
oid.len = aEcParams->data[1];
|
|
oid.data = aEcParams->data + 2;
|
|
|
|
uint32_t flen;
|
|
switch (SECOID_FindOIDTag(&oid)) {
|
|
case SEC_OID_SECG_EC_SECP256R1:
|
|
flen = 32; // bytes
|
|
aRetVal.mCrv.Construct(NS_LITERAL_STRING(WEBCRYPTO_NAMED_CURVE_P256));
|
|
break;
|
|
case SEC_OID_SECG_EC_SECP384R1:
|
|
flen = 48; // bytes
|
|
aRetVal.mCrv.Construct(NS_LITERAL_STRING(WEBCRYPTO_NAMED_CURVE_P384));
|
|
break;
|
|
case SEC_OID_SECG_EC_SECP521R1:
|
|
flen = 66; // bytes
|
|
aRetVal.mCrv.Construct(NS_LITERAL_STRING(WEBCRYPTO_NAMED_CURVE_P521));
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
|
|
// No support for compressed points.
|
|
if (aPublicValue->data[0] != EC_POINT_FORM_UNCOMPRESSED) {
|
|
return false;
|
|
}
|
|
|
|
// Check length of uncompressed point coordinates.
|
|
if (aPublicValue->len != (2 * flen + 1)) {
|
|
return false;
|
|
}
|
|
|
|
UniqueSECItem ecPointX(::SECITEM_AllocItem(nullptr, nullptr, flen));
|
|
UniqueSECItem ecPointY(::SECITEM_AllocItem(nullptr, nullptr, flen));
|
|
if (!ecPointX || !ecPointY) {
|
|
return false;
|
|
}
|
|
|
|
// Extract point data.
|
|
memcpy(ecPointX->data, aPublicValue->data + 1, flen);
|
|
memcpy(ecPointY->data, aPublicValue->data + 1 + flen, flen);
|
|
|
|
CryptoBuffer x, y;
|
|
if (!x.Assign(ecPointX.get()) ||
|
|
NS_FAILED(x.ToJwkBase64(aRetVal.mX.Value())) ||
|
|
!y.Assign(ecPointY.get()) ||
|
|
NS_FAILED(y.ToJwkBase64(aRetVal.mY.Value()))) {
|
|
return false;
|
|
}
|
|
|
|
aRetVal.mKty = NS_LITERAL_STRING(JWK_TYPE_EC);
|
|
return true;
|
|
}
|
|
|
|
nsresult
|
|
CryptoKey::PrivateKeyToJwk(SECKEYPrivateKey* aPrivKey,
|
|
JsonWebKey& aRetVal,
|
|
const nsNSSShutDownPreventionLock& /*proofOfLock*/)
|
|
{
|
|
switch (aPrivKey->keyType) {
|
|
case rsaKey: {
|
|
aRetVal.mN.Construct();
|
|
aRetVal.mE.Construct();
|
|
aRetVal.mD.Construct();
|
|
aRetVal.mP.Construct();
|
|
aRetVal.mQ.Construct();
|
|
aRetVal.mDp.Construct();
|
|
aRetVal.mDq.Construct();
|
|
aRetVal.mQi.Construct();
|
|
|
|
if (!ReadAndEncodeAttribute(aPrivKey, CKA_MODULUS, aRetVal.mN) ||
|
|
!ReadAndEncodeAttribute(aPrivKey, CKA_PUBLIC_EXPONENT, aRetVal.mE) ||
|
|
!ReadAndEncodeAttribute(aPrivKey, CKA_PRIVATE_EXPONENT, aRetVal.mD) ||
|
|
!ReadAndEncodeAttribute(aPrivKey, CKA_PRIME_1, aRetVal.mP) ||
|
|
!ReadAndEncodeAttribute(aPrivKey, CKA_PRIME_2, aRetVal.mQ) ||
|
|
!ReadAndEncodeAttribute(aPrivKey, CKA_EXPONENT_1, aRetVal.mDp) ||
|
|
!ReadAndEncodeAttribute(aPrivKey, CKA_EXPONENT_2, aRetVal.mDq) ||
|
|
!ReadAndEncodeAttribute(aPrivKey, CKA_COEFFICIENT, aRetVal.mQi)) {
|
|
return NS_ERROR_DOM_OPERATION_ERR;
|
|
}
|
|
|
|
aRetVal.mKty = NS_LITERAL_STRING(JWK_TYPE_RSA);
|
|
return NS_OK;
|
|
}
|
|
case ecKey: {
|
|
// Read EC params.
|
|
ScopedAutoSECItem params;
|
|
SECStatus rv = PK11_ReadRawAttribute(PK11_TypePrivKey, aPrivKey,
|
|
CKA_EC_PARAMS, ¶ms);
|
|
if (rv != SECSuccess) {
|
|
return NS_ERROR_DOM_OPERATION_ERR;
|
|
}
|
|
|
|
// Read public point Q.
|
|
ScopedAutoSECItem ecPoint;
|
|
rv = PK11_ReadRawAttribute(PK11_TypePrivKey, aPrivKey, CKA_EC_POINT,
|
|
&ecPoint);
|
|
if (rv != SECSuccess) {
|
|
return NS_ERROR_DOM_OPERATION_ERR;
|
|
}
|
|
|
|
if (!ECKeyToJwk(PK11_TypePrivKey, aPrivKey, ¶ms, &ecPoint, aRetVal)) {
|
|
return NS_ERROR_DOM_OPERATION_ERR;
|
|
}
|
|
|
|
aRetVal.mD.Construct();
|
|
|
|
// Read private value.
|
|
if (!ReadAndEncodeAttribute(aPrivKey, CKA_VALUE, aRetVal.mD)) {
|
|
return NS_ERROR_DOM_OPERATION_ERR;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
default:
|
|
return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
|
|
}
|
|
}
|
|
|
|
UniqueSECKEYPublicKey
|
|
CreateECPublicKey(const SECItem* aKeyData, const nsString& aNamedCurve)
|
|
{
|
|
UniquePLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE));
|
|
if (!arena) {
|
|
return nullptr;
|
|
}
|
|
|
|
// It's important that this be a UniqueSECKEYPublicKey, as this ensures that
|
|
// SECKEY_DestroyPublicKey will be called on it. If this doesn't happen, when
|
|
// CryptoKey::PublicKeyValid is called on it and it gets moved to the internal
|
|
// PKCS#11 slot, it will leak a reference to the slot.
|
|
UniqueSECKEYPublicKey key(PORT_ArenaZNew(arena.get(), SECKEYPublicKey));
|
|
if (!key) {
|
|
return nullptr;
|
|
}
|
|
|
|
key->arena = nullptr; // key doesn't own the arena; it won't get double-freed
|
|
key->keyType = ecKey;
|
|
key->pkcs11Slot = nullptr;
|
|
key->pkcs11ID = CK_INVALID_HANDLE;
|
|
|
|
// Create curve parameters.
|
|
SECItem* params = CreateECParamsForCurve(aNamedCurve, arena.get());
|
|
if (!params) {
|
|
return nullptr;
|
|
}
|
|
key->u.ec.DEREncodedParams = *params;
|
|
|
|
// Set public point.
|
|
key->u.ec.publicValue = *aKeyData;
|
|
|
|
// Ensure the given point is on the curve.
|
|
if (!CryptoKey::PublicKeyValid(key.get())) {
|
|
return nullptr;
|
|
}
|
|
|
|
return UniqueSECKEYPublicKey(SECKEY_CopyPublicKey(key.get()));
|
|
}
|
|
|
|
UniqueSECKEYPublicKey
|
|
CryptoKey::PublicKeyFromJwk(const JsonWebKey& aJwk,
|
|
const nsNSSShutDownPreventionLock& /*proofOfLock*/)
|
|
{
|
|
if (aJwk.mKty.EqualsLiteral(JWK_TYPE_RSA)) {
|
|
// Verify that all of the required parameters are present
|
|
CryptoBuffer n, e;
|
|
if (!aJwk.mN.WasPassed() || NS_FAILED(n.FromJwkBase64(aJwk.mN.Value())) ||
|
|
!aJwk.mE.WasPassed() || NS_FAILED(e.FromJwkBase64(aJwk.mE.Value()))) {
|
|
return nullptr;
|
|
}
|
|
|
|
// Transcode to a DER RSAPublicKey structure
|
|
struct RSAPublicKeyData {
|
|
SECItem n;
|
|
SECItem e;
|
|
};
|
|
const RSAPublicKeyData input = {
|
|
{ siUnsignedInteger, n.Elements(), (unsigned int) n.Length() },
|
|
{ siUnsignedInteger, e.Elements(), (unsigned int) e.Length() }
|
|
};
|
|
const SEC_ASN1Template rsaPublicKeyTemplate[] = {
|
|
{SEC_ASN1_SEQUENCE, 0, nullptr, sizeof(RSAPublicKeyData)},
|
|
{SEC_ASN1_INTEGER, offsetof(RSAPublicKeyData, n),},
|
|
{SEC_ASN1_INTEGER, offsetof(RSAPublicKeyData, e),},
|
|
{0,}
|
|
};
|
|
|
|
UniqueSECItem pkDer(SEC_ASN1EncodeItem(nullptr, nullptr, &input,
|
|
rsaPublicKeyTemplate));
|
|
if (!pkDer.get()) {
|
|
return nullptr;
|
|
}
|
|
|
|
return UniqueSECKEYPublicKey(SECKEY_ImportDERPublicKey(pkDer.get(), CKK_RSA));
|
|
}
|
|
|
|
if (aJwk.mKty.EqualsLiteral(JWK_TYPE_EC)) {
|
|
// Verify that all of the required parameters are present
|
|
CryptoBuffer x, y;
|
|
if (!aJwk.mCrv.WasPassed() ||
|
|
!aJwk.mX.WasPassed() || NS_FAILED(x.FromJwkBase64(aJwk.mX.Value())) ||
|
|
!aJwk.mY.WasPassed() || NS_FAILED(y.FromJwkBase64(aJwk.mY.Value()))) {
|
|
return nullptr;
|
|
}
|
|
|
|
UniquePLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE));
|
|
if (!arena) {
|
|
return nullptr;
|
|
}
|
|
|
|
// Create point.
|
|
SECItem* point = CreateECPointForCoordinates(x, y, arena.get());
|
|
if (!point) {
|
|
return nullptr;
|
|
}
|
|
|
|
nsString namedCurve;
|
|
if (!NormalizeToken(aJwk.mCrv.Value(), namedCurve)) {
|
|
return nullptr;
|
|
}
|
|
|
|
return CreateECPublicKey(point, namedCurve);
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
nsresult
|
|
CryptoKey::PublicKeyToJwk(SECKEYPublicKey* aPubKey,
|
|
JsonWebKey& aRetVal,
|
|
const nsNSSShutDownPreventionLock& /*proofOfLock*/)
|
|
{
|
|
switch (aPubKey->keyType) {
|
|
case rsaKey: {
|
|
CryptoBuffer n, e;
|
|
aRetVal.mN.Construct();
|
|
aRetVal.mE.Construct();
|
|
|
|
if (!n.Assign(&aPubKey->u.rsa.modulus) ||
|
|
!e.Assign(&aPubKey->u.rsa.publicExponent) ||
|
|
NS_FAILED(n.ToJwkBase64(aRetVal.mN.Value())) ||
|
|
NS_FAILED(e.ToJwkBase64(aRetVal.mE.Value()))) {
|
|
return NS_ERROR_DOM_OPERATION_ERR;
|
|
}
|
|
|
|
aRetVal.mKty = NS_LITERAL_STRING(JWK_TYPE_RSA);
|
|
return NS_OK;
|
|
}
|
|
case ecKey:
|
|
if (!ECKeyToJwk(PK11_TypePubKey, aPubKey, &aPubKey->u.ec.DEREncodedParams,
|
|
&aPubKey->u.ec.publicValue, aRetVal)) {
|
|
return NS_ERROR_DOM_OPERATION_ERR;
|
|
}
|
|
return NS_OK;
|
|
default:
|
|
return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
|
|
}
|
|
}
|
|
|
|
UniqueSECKEYPublicKey
|
|
CryptoKey::PublicDhKeyFromRaw(CryptoBuffer& aKeyData,
|
|
const CryptoBuffer& aPrime,
|
|
const CryptoBuffer& aGenerator,
|
|
const nsNSSShutDownPreventionLock& /*proofOfLock*/)
|
|
{
|
|
UniquePLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE));
|
|
if (!arena) {
|
|
return nullptr;
|
|
}
|
|
|
|
SECKEYPublicKey* key = PORT_ArenaZNew(arena.get(), SECKEYPublicKey);
|
|
if (!key) {
|
|
return nullptr;
|
|
}
|
|
|
|
key->keyType = dhKey;
|
|
key->pkcs11Slot = nullptr;
|
|
key->pkcs11ID = CK_INVALID_HANDLE;
|
|
|
|
// Set DH public key params.
|
|
if (!aPrime.ToSECItem(arena.get(), &key->u.dh.prime) ||
|
|
!aGenerator.ToSECItem(arena.get(), &key->u.dh.base) ||
|
|
!aKeyData.ToSECItem(arena.get(), &key->u.dh.publicValue)) {
|
|
return nullptr;
|
|
}
|
|
|
|
key->u.dh.prime.type = siUnsignedInteger;
|
|
key->u.dh.base.type = siUnsignedInteger;
|
|
key->u.dh.publicValue.type = siUnsignedInteger;
|
|
|
|
return UniqueSECKEYPublicKey(SECKEY_CopyPublicKey(key));
|
|
}
|
|
|
|
nsresult
|
|
CryptoKey::PublicDhKeyToRaw(SECKEYPublicKey* aPubKey,
|
|
CryptoBuffer& aRetVal,
|
|
const nsNSSShutDownPreventionLock& /*proofOfLock*/)
|
|
{
|
|
if (!aRetVal.Assign(&aPubKey->u.dh.publicValue)) {
|
|
return NS_ERROR_DOM_OPERATION_ERR;
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
UniqueSECKEYPublicKey
|
|
CryptoKey::PublicECKeyFromRaw(CryptoBuffer& aKeyData,
|
|
const nsString& aNamedCurve,
|
|
const nsNSSShutDownPreventionLock& /*proofOfLock*/)
|
|
{
|
|
UniquePLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE));
|
|
if (!arena) {
|
|
return nullptr;
|
|
}
|
|
|
|
SECItem rawItem = { siBuffer, nullptr, 0 };
|
|
if (!aKeyData.ToSECItem(arena.get(), &rawItem)) {
|
|
return nullptr;
|
|
}
|
|
|
|
uint32_t flen;
|
|
if (aNamedCurve.EqualsLiteral(WEBCRYPTO_NAMED_CURVE_P256)) {
|
|
flen = 32; // bytes
|
|
} else if (aNamedCurve.EqualsLiteral(WEBCRYPTO_NAMED_CURVE_P384)) {
|
|
flen = 48; // bytes
|
|
} else if (aNamedCurve.EqualsLiteral(WEBCRYPTO_NAMED_CURVE_P521)) {
|
|
flen = 66; // bytes
|
|
} else {
|
|
return nullptr;
|
|
}
|
|
|
|
// Check length of uncompressed point coordinates. There are 2 field elements
|
|
// and a leading point form octet (which must EC_POINT_FORM_UNCOMPRESSED).
|
|
if (rawItem.len != (2 * flen + 1)) {
|
|
return nullptr;
|
|
}
|
|
|
|
// No support for compressed points.
|
|
if (rawItem.data[0] != EC_POINT_FORM_UNCOMPRESSED) {
|
|
return nullptr;
|
|
}
|
|
|
|
return CreateECPublicKey(&rawItem, aNamedCurve);
|
|
}
|
|
|
|
nsresult
|
|
CryptoKey::PublicECKeyToRaw(SECKEYPublicKey* aPubKey,
|
|
CryptoBuffer& aRetVal,
|
|
const nsNSSShutDownPreventionLock& /*proofOfLock*/)
|
|
{
|
|
if (!aRetVal.Assign(&aPubKey->u.ec.publicValue)) {
|
|
return NS_ERROR_DOM_OPERATION_ERR;
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
bool
|
|
CryptoKey::PublicKeyValid(SECKEYPublicKey* aPubKey)
|
|
{
|
|
UniquePK11SlotInfo slot(PK11_GetInternalSlot());
|
|
if (!slot.get()) {
|
|
return false;
|
|
}
|
|
|
|
// This assumes that NSS checks the validity of a public key when
|
|
// it is imported into a PKCS#11 module, and returns CK_INVALID_HANDLE
|
|
// if it is invalid.
|
|
CK_OBJECT_HANDLE id = PK11_ImportPublicKey(slot.get(), aPubKey, PR_FALSE);
|
|
if (id == CK_INVALID_HANDLE) {
|
|
return false;
|
|
}
|
|
|
|
SECStatus rv = PK11_DestroyObject(slot.get(), id);
|
|
return (rv == SECSuccess);
|
|
}
|
|
|
|
bool
|
|
CryptoKey::WriteStructuredClone(JSStructuredCloneWriter* aWriter) const
|
|
{
|
|
nsNSSShutDownPreventionLock locker;
|
|
if (isAlreadyShutDown()) {
|
|
return false;
|
|
}
|
|
|
|
// Write in five pieces
|
|
// 1. Attributes
|
|
// 2. Symmetric key as raw (if present)
|
|
// 3. Private key as pkcs8 (if present)
|
|
// 4. Public key as spki (if present)
|
|
// 5. Algorithm in whatever form it chooses
|
|
CryptoBuffer priv, pub;
|
|
|
|
if (mPrivateKey) {
|
|
if (NS_FAILED(CryptoKey::PrivateKeyToPkcs8(mPrivateKey.get(), priv,
|
|
locker))) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (mPublicKey) {
|
|
if (NS_FAILED(CryptoKey::PublicKeyToSpki(mPublicKey.get(), pub, locker))) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return JS_WriteUint32Pair(aWriter, mAttributes, CRYPTOKEY_SC_VERSION) &&
|
|
WriteBuffer(aWriter, mSymKey) &&
|
|
WriteBuffer(aWriter, priv) &&
|
|
WriteBuffer(aWriter, pub) &&
|
|
mAlgorithm.WriteStructuredClone(aWriter);
|
|
}
|
|
|
|
bool
|
|
CryptoKey::ReadStructuredClone(JSStructuredCloneReader* aReader)
|
|
{
|
|
nsNSSShutDownPreventionLock locker;
|
|
if (isAlreadyShutDown()) {
|
|
return false;
|
|
}
|
|
|
|
// Ensure that NSS is initialized.
|
|
if (!EnsureNSSInitializedChromeOrContent()) {
|
|
return false;
|
|
}
|
|
|
|
uint32_t version;
|
|
CryptoBuffer sym, priv, pub;
|
|
|
|
bool read = JS_ReadUint32Pair(aReader, &mAttributes, &version) &&
|
|
(version == CRYPTOKEY_SC_VERSION) &&
|
|
ReadBuffer(aReader, sym) &&
|
|
ReadBuffer(aReader, priv) &&
|
|
ReadBuffer(aReader, pub) &&
|
|
mAlgorithm.ReadStructuredClone(aReader);
|
|
if (!read) {
|
|
return false;
|
|
}
|
|
|
|
if (sym.Length() > 0 && !mSymKey.Assign(sym)) {
|
|
return false;
|
|
}
|
|
if (priv.Length() > 0) {
|
|
mPrivateKey = CryptoKey::PrivateKeyFromPkcs8(priv, locker);
|
|
}
|
|
if (pub.Length() > 0) {
|
|
mPublicKey = CryptoKey::PublicKeyFromSpki(pub, locker);
|
|
}
|
|
|
|
// Ensure that what we've read is consistent
|
|
// If the attributes indicate a key type, should have a key of that type
|
|
if (!((GetKeyType() == SECRET && mSymKey.Length() > 0) ||
|
|
(GetKeyType() == PRIVATE && mPrivateKey) ||
|
|
(GetKeyType() == PUBLIC && mPublicKey))) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
} // namespace dom
|
|
} // namespace mozilla
|