diff --git a/dom/crypto/CryptoKey.cpp b/dom/crypto/CryptoKey.cpp index eda5a324cc0..cb9d7065cef 100644 --- a/dom/crypto/CryptoKey.cpp +++ b/dom/crypto/CryptoKey.cpp @@ -322,6 +322,23 @@ CryptoKey::PublicKeyFromSpki(CryptoBuffer& aKeyData, 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); + if (!oidData) { + return nullptr; + } + + SECStatus rv = SECITEM_CopyItem(spki->arena, &spki->algorithm.algorithm, + &oidData->oid); + if (rv != SECSuccess) { + return nullptr; + } + } + return SECKEY_ExtractPublicKey(spki.get()); } @@ -343,11 +360,25 @@ CryptoKey::PublicKeyToSpki(SECKEYPublicKey* aPubKey, CryptoBuffer& aRetVal, const nsNSSShutDownPreventionLock& /*proofOfLock*/) { - ScopedSECItem spkiItem(PK11_DEREncodePublicKey(aPubKey)); - if (!spkiItem.get()) { - return NS_ERROR_DOM_INVALID_ACCESS_ERR; + ScopedCERTSubjectPublicKeyInfo 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 + // no way to specify the algorithm to use when exporting a public key. + if (aPubKey->keyType == ecKey) { + SECStatus rv = SECITEM_CopyItem(spki->arena, &spki->algorithm.algorithm, + &SEC_OID_DATA_EC_DH); + 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)); + aRetVal.Assign(spkiItem.get()); return NS_OK; } diff --git a/dom/crypto/WebCryptoCommon.h b/dom/crypto/WebCryptoCommon.h index 22f01d0c767..5a0e2c1e00a 100644 --- a/dom/crypto/WebCryptoCommon.h +++ b/dom/crypto/WebCryptoCommon.h @@ -92,6 +92,11 @@ // 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) }; + namespace mozilla { namespace dom { diff --git a/dom/crypto/WebCryptoTask.cpp b/dom/crypto/WebCryptoTask.cpp index e4991aa048c..f23febc3876 100644 --- a/dom/crypto/WebCryptoTask.cpp +++ b/dom/crypto/WebCryptoTask.cpp @@ -258,6 +258,26 @@ GetKeySizeForAlgorithm(JSContext* aCx, const ObjectOrString& aAlgorithm, return NS_ERROR_DOM_NOT_SUPPORTED_ERR; } +inline bool +MapOIDTagToNamedCurve(SECOidTag aOIDTag, nsString& aResult) +{ + switch (aOIDTag) { + case SEC_OID_SECG_EC_SECP256R1: + aResult.AssignLiteral(WEBCRYPTO_NAMED_CURVE_P256); + break; + case SEC_OID_SECG_EC_SECP384R1: + aResult.AssignLiteral(WEBCRYPTO_NAMED_CURVE_P384); + break; + case SEC_OID_SECG_EC_SECP521R1: + aResult.AssignLiteral(WEBCRYPTO_NAMED_CURVE_P521); + break; + default: + return false; + } + + return true; +} + // Helper function to clone data from an ArrayBuffer or ArrayBufferView object inline bool CloneData(JSContext* aCx, CryptoBuffer& aDst, JS::Handle aSrc) @@ -1682,16 +1702,12 @@ private: virtual nsresult DoCrypto() MOZ_OVERRIDE { - if (!mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_JWK)) { - return NS_ERROR_DOM_NOT_SUPPORTED_ERR; - } - // Import the key data itself ScopedSECKEYPublicKey pubKey; ScopedSECKEYPrivateKey privKey; nsNSSShutDownPreventionLock locker; - if (mJwk.mD.WasPassed()) { + if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_JWK) && mJwk.mD.WasPassed()) { // Private key import privKey = CryptoKey::PrivateKeyFromJwk(mJwk, locker); if (!privKey) { @@ -1700,19 +1716,47 @@ private: mKey->SetPrivateKey(privKey.get()); mKey->SetType(CryptoKey::PRIVATE); - } else { + } else if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_SPKI) || + (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_JWK) && + !mJwk.mD.WasPassed())) { // Public key import - pubKey = CryptoKey::PublicKeyFromJwk(mJwk, locker); + if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_SPKI)) { + pubKey = CryptoKey::PublicKeyFromSpki(mKeyData, locker); + } else { + pubKey = CryptoKey::PublicKeyFromJwk(mJwk, locker); + } + if (!pubKey) { return NS_ERROR_DOM_DATA_ERR; } + if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_SPKI)) { + if (!CheckEncodedECParameters(&pubKey->u.ec.DEREncodedParams)) { + return NS_ERROR_DOM_OPERATION_ERR; + } + + // Construct the OID tag. + SECItem oid = { siBuffer, nullptr, 0 }; + oid.len = pubKey->u.ec.DEREncodedParams.data[1]; + oid.data = pubKey->u.ec.DEREncodedParams.data + 2; + + // Find a matching and supported named curve. + if (!MapOIDTagToNamedCurve(SECOID_FindOIDTag(&oid), mNamedCurve)) { + return NS_ERROR_DOM_NOT_SUPPORTED_ERR; + } + } + mKey->SetPublicKey(pubKey.get()); mKey->SetType(CryptoKey::PUBLIC); + } else { + return NS_ERROR_DOM_NOT_SUPPORTED_ERR; } - if (!NormalizeNamedCurveValue(mJwk.mCrv.Value(), mNamedCurve)) { - return NS_ERROR_DOM_NOT_SUPPORTED_ERR; + // Extract 'crv' parameter from JWKs. + if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_JWK)) { + if (!NormalizeNamedCurveValue(mJwk.mCrv.Value(), mNamedCurve)) { + return NS_ERROR_DOM_NOT_SUPPORTED_ERR; + } } return NS_OK; diff --git a/dom/crypto/test/test-vectors.js b/dom/crypto/test/test-vectors.js index f5e47b20810..76b2df05cf3 100644 --- a/dom/crypto/test/test-vectors.js +++ b/dom/crypto/test/test-vectors.js @@ -472,6 +472,20 @@ tv = { y: "is9pWAaneK4RdxmdLfsq5IwizDmUS2w8OGS99sKm3ek" }, + // vector with algorithm = id-ecDH + spki: util.hex2abv( + "3056301006042b81047006082a8648ce3d030107034200045ce7b86e3b326604" + + "03e63712ef0998deae1027faec3c1be9f76f934dfeb58e98f4cf075b39405dd1" + + "f1adeb090107edcfb2b4963739d87679e3056cb0557d0adf" + ), + + // vector with algorithm = id-ecPublicKey + spki_id_ecpk: util.hex2abv( + "3059301306072a8648ce3d020106082a8648ce3d030107034200045ce7b86e3b" + + "32660403e63712ef0998deae1027faec3c1be9f76f934dfeb58e98f4cf075b39" + + "405dd1f1adeb090107edcfb2b4963739d87679e3056cb0557d0adf" + ), + secret: util.hex2abv( "35669cd5c244ba6c1ea89b8802c3d1db815cd769979072e6556eb98548c65f7d" ) diff --git a/dom/crypto/test/tests.js b/dom/crypto/test/tests.js index 20f85ba11a3..f7dee5ccb11 100644 --- a/dom/crypto/test/tests.js +++ b/dom/crypto/test/tests.js @@ -2191,3 +2191,65 @@ TestArray.addTest( .then(complete(that), error(that)); } ); + +// ----------------------------------------------------------------------------- +TestArray.addTest( + "SPKI import/export of public ECDH keys (P-256)", + function () { + var that = this; + var alg = { name: "ECDH" }; + var keys = ["spki", "spki_id_ecpk"]; + + function doImport(key) { + return crypto.subtle.importKey("spki", tv.ecdh_p256[key], alg, true, ["deriveBits"]); + } + + function doExport(x) { + return crypto.subtle.exportKey("spki", x); + } + + function nextKey() { + var key = keys.shift(); + var imported = doImport(key); + var derived = imported.then(doExport); + + return derived.then(function (x) { + if (!util.memcmp(x, tv.ecdh_p256.spki)) { + throw "exported key is invalid"; + } + + if (keys.length) { + return nextKey(); + } + }); + } + + nextKey().then(complete(that), error(that)); + } +); + +// ----------------------------------------------------------------------------- +TestArray.addTest( + "SPKI/JWK import ECDH keys (P-256) and derive a known secret", + function () { + var that = this; + var alg = { name: "ECDH" }; + + var pubKey, privKey; + function setPub(x) { pubKey = x; } + function setPriv(x) { privKey = x; } + + function doDerive() { + var alg = { name: "ECDH", public: pubKey }; + return crypto.subtle.deriveBits(alg, privKey, tv.ecdh_p256.secret.byteLength * 8); + } + + Promise.all([ + crypto.subtle.importKey("spki", tv.ecdh_p256.spki, alg, false, ["deriveBits"]) + .then(setPub), + crypto.subtle.importKey("jwk", tv.ecdh_p256.jwk_priv, alg, false, ["deriveBits"]) + .then(setPriv) + ]).then(doDerive) + .then(memcmp_complete(that, tv.ecdh_p256.secret), error(that)); + } +);