Bug 1034855 - Implement SPKI import/export for ECDH r=keeler,rbarnes
☠☠ backed out by 591fb2c4ee15 ☠ ☠
authorTim Taubert <ttaubert@mozilla.com>
Mon, 04 Aug 2014 09:39:12 +0200
changeset 197983 0dc569f749916ced0f5ee0b10320b31c339d118e
parent 197982 4eb0ee62db1ed3412bca570b32a969fbf669669a
child 197984 1dae223802054caeb748a86ea73b3a559d74530b
push id27256
push userkwierso@gmail.com
push dateWed, 06 Aug 2014 00:06:20 +0000
treeherdermozilla-central@6cbdd4d523a7 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskeeler, rbarnes
bugs1034855
milestone34.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1034855 - Implement SPKI import/export for ECDH r=keeler,rbarnes
dom/crypto/CryptoKey.cpp
dom/crypto/WebCryptoCommon.h
dom/crypto/WebCryptoTask.cpp
dom/crypto/test/test-vectors.js
dom/crypto/test/tests.js
--- a/dom/crypto/CryptoKey.cpp
+++ b/dom/crypto/CryptoKey.cpp
@@ -317,16 +317,33 @@ 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);
+    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());
 }
 
 nsresult
 CryptoKey::PrivateKeyToPkcs8(SECKEYPrivateKey* aPrivKey,
                        CryptoBuffer& aRetVal,
                        const nsNSSShutDownPreventionLock& /*proofOfLock*/)
 {
@@ -338,21 +355,35 @@ CryptoKey::PrivateKeyToPkcs8(SECKEYPriva
   return NS_OK;
 }
 
 nsresult
 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;
 }
 
 SECItem*
 CreateECPointForCoordinates(const CryptoBuffer& aX,
                             const CryptoBuffer& aY,
                             PLArenaPool* aArena)
--- a/dom/crypto/WebCryptoCommon.h
+++ b/dom/crypto/WebCryptoCommon.h
@@ -87,16 +87,21 @@
 
 // JWK usages
 #define JWK_USE_ENC                 "enc"
 #define JWK_USE_SIG                 "sig"
 
 // 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 {
 
 // Helper functions for structured cloning
 inline bool
 ReadString(JSStructuredCloneReader* aReader, nsString& aString)
 {
   bool read;
--- a/dom/crypto/WebCryptoTask.cpp
+++ b/dom/crypto/WebCryptoTask.cpp
@@ -253,16 +253,36 @@ GetKeySizeForAlgorithm(JSContext* aCx, c
 
     aLength = length;
     return NS_OK;
   }
 
   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<JSObject*> aSrc)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   // Try ArrayBuffer
   RootedTypedArray<ArrayBuffer> ab(aCx);
@@ -1677,47 +1697,71 @@ public:
     SetKeyData(aCx, aKeyData);
   }
 
 private:
   nsString mNamedCurve;
 
   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) {
         return NS_ERROR_DOM_DATA_ERR;
       }
 
       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;
   }
 
   virtual nsresult AfterCrypto() MOZ_OVERRIDE
   {
     // Check permissions for the requested operation
--- a/dom/crypto/test/test-vectors.js
+++ b/dom/crypto/test/test-vectors.js
@@ -467,16 +467,30 @@ tv = {
     jwk_priv: {
       kty: "EC",
       crv: "P-256",
       d: "qq_LEzeJpR00KM5DQvL2MNtJcbi0KcGVcoPIHNnwm2A",
       x: "FNwJHA-FwnSx5tKXFV_iLN408gbKUHRV06WnQlzTdN4",
       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"
     )
   },
 
   // KASValidityTest_ECCEphemeralUnified_NOKC_ZZOnly_init.fax [ED]
   // <http://csrc.nist.gov/groups/STM/cavp/documents/keymgmt/kastestvectors.zip>
   ecdh_p384: {
--- a/dom/crypto/test/tests.js
+++ b/dom/crypto/test/tests.js
@@ -2186,8 +2186,70 @@ TestArray.addTest(
         .then(setPriv, error(that)),
       crypto.subtle.importKey("jwk", tv.ecdh_p521.jwk_pub, alg, false, ["deriveBits"])
         .then(setPub, error(that))
     ]).then(doDerive, error(that))
       .then(doSignAndVerify, error(that))
       .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));
+  }
+);