Bug 1034856 - Implement SPKI public key import/export for DH. r=rbarnes, a=lsblakk
--- a/dom/crypto/CryptoKey.cpp
+++ b/dom/crypto/CryptoKey.cpp
@@ -7,16 +7,31 @@
#include "pk11pub.h"
#include "cryptohi.h"
#include "ScopedNSSTypes.h"
#include "mozilla/dom/CryptoKey.h"
#include "mozilla/dom/WebCryptoCommon.h"
#include "mozilla/dom/SubtleCryptoBinding.h"
#include "mozilla/dom/ToJSValue.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
@@ -379,22 +394,35 @@ CryptoKey::PublicKeyFromSpki(CryptoBuffe
return nullptr;
}
ScopedCERTSubjectPublicKeyInfo spki(SECKEY_DecodeDERSubjectPublicKeyInfo(spkiItem.get()));
if (!spki) {
return nullptr;
}
- // Check for id-ecDH. Per the WebCrypto spec we must support it but NSS
- // does unfortunately not know about it. Let's change the algorithm to
- // id-ecPublicKey to make NSS happy.
- if (SECITEM_ItemsAreEqual(&SEC_OID_DATA_EC_DH, &spki->algorithm.algorithm)) {
- // Retrieve OID data for id-ecPublicKey (1.2.840.10045.2.1).
- SECOidData* oidData = SECOID_FindOIDByTag(SEC_OID_ANSIX962_EC_PUBLIC_KEY);
+ 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;
@@ -418,31 +446,98 @@ CryptoKey::PrivateKeyToPkcs8(SECKEYPriva
if (!pkcs8Item.get()) {
return NS_ERROR_DOM_INVALID_ACCESS_ERR;
}
aRetVal.Assign(pkcs8Item.get());
return NS_OK;
}
nsresult
-CryptoKey::PublicKeyToSpki(SECKEYPublicKey* aPubKey,
- CryptoBuffer& aRetVal,
- const nsNSSShutDownPreventionLock& /*proofOfLock*/)
+PublicDhKeyToSpki(SECKEYPublicKey* aPubKey,
+ CERTSubjectPublicKeyInfo* aSpki,
+ PLArenaPool* aArena)
{
- ScopedCERTSubjectPublicKeyInfo spki(SECKEY_CreateSubjectPublicKeyInfo(aPubKey));
- if (!spki) {
+ SECItem* params = ::SECITEM_AllocItem(aArena, nullptr, 0);
+ if (!params) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+
+ SECItem* rvItem = SEC_ASN1EncodeItem(aArena, params, aPubKey,
+ SECKEY_DHParamKeyTemplate);
+ if (!rvItem) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+
+ SECStatus rv = SECOID_SetAlgorithmID(aArena, &aSpki->algorithm,
+ SEC_OID_X942_DIFFIE_HELMAN_KEY, params);
+ if (rv != SECSuccess) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+
+ rvItem = SEC_ASN1EncodeItem(aArena, &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*/)
+{
+ ScopedPLArenaPool arena;
+ ScopedCERTSubjectPublicKeyInfo spki;
+
+ // NSS doesn't support exporting DH public keys.
+ if (aPubKey->keyType == dhKey) {
+ arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+ if (!arena) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+
+ // It's alright to assign the result of PORT_ArenaZNew(ScopedPLArenaPool)
+ // to a ScopedCERTSubjectPublicKeyInfo as long as we don't set |spki->arena|
+ // as that's what would be freed when |spki| goes out of scope.
+ spki = PORT_ArenaZNew(arena, CERTSubjectPublicKeyInfo);
+ if (!spki) {
+ return NS_ERROR_DOM_OPERATION_ERR;
+ }
+
+ nsresult rv = PublicDhKeyToSpki(aPubKey, spki, arena);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ spki = 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). NSS doesn't know about that OID and there is
+ // 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) {
+ 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,
- &SEC_OID_DATA_EC_DH);
+ oidData);
if (rv != SECSuccess) {
return NS_ERROR_DOM_OPERATION_ERR;
}
}
const SEC_ASN1Template* tpl = SEC_ASN1_GET(CERT_SubjectPublicKeyInfoTemplate);
ScopedSECItem spkiItem(SEC_ASN1EncodeItem(nullptr, nullptr, spki, tpl));
--- a/dom/crypto/WebCryptoCommon.h
+++ b/dom/crypto/WebCryptoCommon.h
@@ -97,16 +97,24 @@
// Define an unknown mechanism type
#define UNKNOWN_CK_MECHANISM CKM_VENDOR_DEFINED+1
// python security/pkix/tools/DottedOIDToCode.py id-ecDH 1.3.132.112
static const uint8_t id_ecDH[] = { 0x2b, 0x81, 0x04, 0x70 };
const SECItem SEC_OID_DATA_EC_DH = { siBuffer, (unsigned char*)id_ecDH,
PR_ARRAY_SIZE(id_ecDH) };
+// python security/pkix/tools/DottedOIDToCode.py dhKeyAgreement 1.2.840.113549.1.3.1
+static const uint8_t dhKeyAgreement[] = {
+ 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x03, 0x01
+};
+const SECItem SEC_OID_DATA_DH_KEY_AGREEMENT = { siBuffer,
+ (unsigned char*)dhKeyAgreement,
+ PR_ARRAY_SIZE(dhKeyAgreement) };
+
namespace mozilla {
namespace dom {
// Helper functions for structured cloning
inline bool
ReadString(JSStructuredCloneReader* aReader, nsString& aString)
{
bool read;
--- a/dom/crypto/WebCryptoTask.cpp
+++ b/dom/crypto/WebCryptoTask.cpp
@@ -1771,47 +1771,62 @@ public:
const ObjectOrString& aAlgorithm, bool aExtractable,
const Sequence<nsString>& aKeyUsages)
{
ImportKeyTask::Init(aCx, aFormat, aAlgorithm, aExtractable, aKeyUsages);
if (NS_FAILED(mEarlyRv)) {
return;
}
- RootedDictionary<DhImportKeyParams> params(aCx);
- mEarlyRv = Coerce(aCx, params, aAlgorithm);
- if (NS_FAILED(mEarlyRv)) {
- mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR;
- return;
+ if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_RAW)) {
+ RootedDictionary<DhImportKeyParams> params(aCx);
+ mEarlyRv = Coerce(aCx, params, aAlgorithm);
+ if (NS_FAILED(mEarlyRv)) {
+ mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR;
+ return;
+ }
+
+ CryptoBuffer prime;
+ ATTEMPT_BUFFER_INIT(mPrime, params.mPrime);
+
+ CryptoBuffer generator;
+ ATTEMPT_BUFFER_INIT(mGenerator, params.mGenerator);
}
-
- CryptoBuffer prime;
- ATTEMPT_BUFFER_INIT(mPrime, params.mPrime);
-
- CryptoBuffer generator;
- ATTEMPT_BUFFER_INIT(mGenerator, params.mGenerator);
}
private:
CryptoBuffer mPrime;
CryptoBuffer mGenerator;
virtual nsresult DoCrypto() MOZ_OVERRIDE
{
// Import the key data itself
ScopedSECKEYPublicKey pubKey;
nsNSSShutDownPreventionLock locker;
- if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_RAW)) {
+ if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_RAW) ||
+ mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_SPKI)) {
// Public key import
- pubKey = CryptoKey::PublicDhKeyFromRaw(mKeyData, mPrime, mGenerator, locker);
+ if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_RAW)) {
+ pubKey = CryptoKey::PublicDhKeyFromRaw(mKeyData, mPrime, mGenerator, locker);
+ } else if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_SPKI)) {
+ pubKey = CryptoKey::PublicKeyFromSpki(mKeyData, locker);
+ } else {
+ MOZ_ASSERT(false);
+ }
+
if (!pubKey) {
return NS_ERROR_DOM_DATA_ERR;
}
+ if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_SPKI)) {
+ ATTEMPT_BUFFER_ASSIGN(mPrime, &pubKey->u.dh.prime);
+ ATTEMPT_BUFFER_ASSIGN(mGenerator, &pubKey->u.dh.base);
+ }
+
mKey->SetPublicKey(pubKey.get());
mKey->SetType(CryptoKey::PUBLIC);
} else {
return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
}
return NS_OK;
}
--- a/dom/crypto/test/test-vectors.js
+++ b/dom/crypto/test/test-vectors.js
@@ -692,11 +692,28 @@ tv = {
"2f7f3826dcef3a1586956105ebea805d871f34c46c25bc30fc66b2db26cb0a93"
),
raw: util.hex2abv(
"4fc9904887ac7fabff87f054003547c2d9458c1f6f584c140d7271f8b266bb39" +
"0af7e3f625a629bec9c6a057a4cbe1a556d5e3eb2ff1c6ff677a08b0c7c50911" +
"0b9e7c6dbc961ca4360362d3dbcffc5bf2bb7207e0a5922f77cf5464b316aa49" +
"fb62b338ebcdb30bf573d07b663bb7777b69d6317df0a4f636ba3d9acbf9e8ac"
+ ),
+
+ spki: util.hex2abv(
+ "308201a33082011806092a864886f70d01030130820109028181008b79f180cb" +
+ "d3f282de92e8b8f2d092674ffda61f01ed961f8ef04a1b7a3709ff748c2abf62" +
+ "26cf0c4538e48838193da456e92ee530ef7aa703e741585e475b26cd64fa9781" +
+ "9181cef27de2449cd385c49c9b030f89873b5b7eaf063a788f00db3cb670c738" +
+ "46bc4f76af062d672bde8f29806b81548411ab48b99aebfd9c2d090281800298" +
+ "43c81d0ea285c41a49b1a2f8e11a56a4b39040dfbc5ec040150c16f72f874152" +
+ "f9c44c659d86f7717b2425b62597e9a453b13da327a31cde2cced600915252d3" +
+ "0262d1e54f4f864ace0e484f98abdbb37ebb0ba4106af5f0935b744677fa2f7f" +
+ "3826dcef3a1586956105ebea805d871f34c46c25bc30fc66b2db26cb0a930000" +
+ "038184000281804fc9904887ac7fabff87f054003547c2d9458c1f6f584c140d" +
+ "7271f8b266bb390af7e3f625a629bec9c6a057a4cbe1a556d5e3eb2ff1c6ff67" +
+ "7a08b0c7c509110b9e7c6dbc961ca4360362d3dbcffc5bf2bb7207e0a5922f77" +
+ "cf5464b316aa49fb62b338ebcdb30bf573d07b663bb7777b69d6317df0a4f636" +
+ "ba3d9acbf9e8ac"
)
},
}
--- a/dom/crypto/test/test_WebCrypto_DH.html
+++ b/dom/crypto/test/test_WebCrypto_DH.html
@@ -226,16 +226,32 @@ TestArray.addTest(
.then(setPriv, error(that))
.then(doImport, error(that))
.then(doDerive, error(that))
.then(complete(that, function (x) {
return x.byteLength == 16;
}), error(that));
}
);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+ "SPKI import/export of a public DH key",
+ function() {
+ var that = this;
+
+ function doExport(x) {
+ return crypto.subtle.exportKey("spki", x);
+ }
+
+ crypto.subtle.importKey("spki", tv.dh_nist.spki, "DH", true, ["deriveBits"])
+ .then(doExport, error(that))
+ .then(memcmp_complete(that, tv.dh_nist.spki), error(that));
+ }
+);
/*]]>*/</script>
</head>
<body>
<div id="content">
<div id="head">
<b>Web</b>Crypto<br>