Bug 1050175 - Add raw import/export for EC public keys to the WebCrypto API r=rbarnes,smaug
authorTim Taubert <ttaubert@mozilla.com>
Tue, 28 Apr 2015 09:13:16 +0200
changeset 276868 924686491f34
parent 276721 da2e29afa891
child 276869 cbd4999d7e97
push id4932
push userjlund@mozilla.com
push dateMon, 10 Aug 2015 18:23:06 +0000
treeherdermozilla-beta@6dd5a4f5f745 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersrbarnes, smaug
bugs1050175
milestone41.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 1050175 - Add raw import/export for EC public keys to the WebCrypto API r=rbarnes,smaug
dom/crypto/CryptoKey.cpp
dom/crypto/CryptoKey.h
dom/crypto/WebCryptoTask.cpp
dom/crypto/test/test-vectors.js
dom/crypto/test/test_WebCrypto_ECDH.html
dom/crypto/test/test_WebCrypto_ECDSA.html
dom/webidl/SubtleCrypto.webidl
--- a/dom/crypto/CryptoKey.cpp
+++ b/dom/crypto/CryptoKey.cpp
@@ -947,16 +947,51 @@ CryptoKey::PrivateKeyToJwk(SECKEYPrivate
       return NS_OK;
     }
     default:
       return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
   }
 }
 
 SECKEYPublicKey*
+CreateECPublicKey(const SECItem* aKeyData, const nsString& aNamedCurve)
+{
+  ScopedPLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE));
+  if (!arena) {
+    return nullptr;
+  }
+
+  SECKEYPublicKey* key = PORT_ArenaZNew(arena, SECKEYPublicKey);
+  if (!key) {
+    return nullptr;
+  }
+
+  key->keyType = ecKey;
+  key->pkcs11Slot = nullptr;
+  key->pkcs11ID = CK_INVALID_HANDLE;
+
+  // Create curve parameters.
+  SECItem* params = CreateECParamsForCurve(aNamedCurve, arena);
+  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)) {
+    return nullptr;
+  }
+
+  return SECKEY_CopyPublicKey(key);
+}
+
+SECKEYPublicKey*
 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()))) {
@@ -997,49 +1032,28 @@ CryptoKey::PublicKeyFromJwk(const JsonWe
       return nullptr;
     }
 
     ScopedPLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE));
     if (!arena) {
       return nullptr;
     }
 
-    SECKEYPublicKey* key = PORT_ArenaZNew(arena, SECKEYPublicKey);
-    if (!key) {
+    // Create point.
+    SECItem* point = CreateECPointForCoordinates(x, y, arena.get());
+    if (!point) {
       return nullptr;
     }
 
-    key->keyType = ecKey;
-    key->pkcs11Slot = nullptr;
-    key->pkcs11ID = CK_INVALID_HANDLE;
-
     nsString namedCurve;
     if (!NormalizeToken(aJwk.mCrv.Value(), namedCurve)) {
       return nullptr;
     }
 
-    // Create parameters.
-    SECItem* params = CreateECParamsForCurve(namedCurve, arena.get());
-    if (!params) {
-      return nullptr;
-    }
-    key->u.ec.DEREncodedParams = *params;
-
-    // Create point.
-    SECItem* point = CreateECPointForCoordinates(x, y, arena.get());
-    if (!point) {
-      return nullptr;
-    }
-    key->u.ec.publicValue = *point;
-
-    if (!PublicKeyValid(key)) {
-      return nullptr;
-    }
-
-    return SECKEY_CopyPublicKey(key);
+    return CreateECPublicKey(point, namedCurve);
   }
 
   return nullptr;
 }
 
 nsresult
 CryptoKey::PublicKeyToJwk(SECKEYPublicKey* aPubKey,
                           JsonWebKey& aRetVal,
@@ -1110,16 +1124,67 @@ nsresult
 CryptoKey::PublicDhKeyToRaw(SECKEYPublicKey* aPubKey,
                             CryptoBuffer& aRetVal,
                             const nsNSSShutDownPreventionLock& /*proofOfLock*/)
 {
   aRetVal.Assign(&aPubKey->u.dh.publicValue);
   return NS_OK;
 }
 
+SECKEYPublicKey*
+CryptoKey::PublicECKeyFromRaw(CryptoBuffer& aKeyData,
+                              const nsString& aNamedCurve,
+                              const nsNSSShutDownPreventionLock& /*proofOfLock*/)
+{
+  ScopedPLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE));
+  if (!arena) {
+    return nullptr;
+  }
+
+  SECItem rawItem = { siBuffer, nullptr, 0 };
+  if (!aKeyData.ToSECItem(arena, &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)
 {
   ScopedPK11SlotInfo slot(PK11_GetInternalSlot());
   if (!slot.get()) {
     return false;
   }
 
--- a/dom/crypto/CryptoKey.h
+++ b/dom/crypto/CryptoKey.h
@@ -175,16 +175,23 @@ public:
   static SECKEYPublicKey* PublicDhKeyFromRaw(CryptoBuffer& aKeyData,
                                              const CryptoBuffer& aPrime,
                                              const CryptoBuffer& aGenerator,
                                              const nsNSSShutDownPreventionLock& /*proofOfLock*/);
   static nsresult PublicDhKeyToRaw(SECKEYPublicKey* aPubKey,
                                    CryptoBuffer& aRetVal,
                                    const nsNSSShutDownPreventionLock& /*proofOfLock*/);
 
+  static SECKEYPublicKey* PublicECKeyFromRaw(CryptoBuffer& aKeyData,
+                                             const nsString& aNamedCurve,
+                                             const nsNSSShutDownPreventionLock& /*proofOfLock*/);
+  static nsresult PublicECKeyToRaw(SECKEYPublicKey* aPubKey,
+                                   CryptoBuffer& aRetVal,
+                                   const nsNSSShutDownPreventionLock& /*proofOfLock*/);
+
   static bool PublicKeyValid(SECKEYPublicKey* aPubKey);
 
   // Structured clone methods use these to clone keys
   bool WriteStructuredClone(JSStructuredCloneWriter* aWriter) const;
   bool ReadStructuredClone(JSStructuredCloneReader* aReader);
 
 private:
   ~CryptoKey();
--- a/dom/crypto/WebCryptoTask.cpp
+++ b/dom/crypto/WebCryptoTask.cpp
@@ -1639,33 +1639,57 @@ private:
 
 class ImportEcKeyTask : public ImportKeyTask
 {
 public:
   ImportEcKeyTask(JSContext* aCx, const nsAString& aFormat,
                   const ObjectOrString& aAlgorithm, bool aExtractable,
                   const Sequence<nsString>& aKeyUsages)
   {
-    ImportKeyTask::Init(aCx, aFormat, aAlgorithm, aExtractable, aKeyUsages);
+    Init(aCx, aFormat, aAlgorithm, aExtractable, aKeyUsages);
   }
 
   ImportEcKeyTask(JSContext* aCx, const nsAString& aFormat,
                   JS::Handle<JSObject*> aKeyData,
                   const ObjectOrString& aAlgorithm, bool aExtractable,
                   const Sequence<nsString>& aKeyUsages)
   {
-    ImportKeyTask::Init(aCx, aFormat, aAlgorithm, aExtractable, aKeyUsages);
+    Init(aCx, aFormat, aAlgorithm, aExtractable, aKeyUsages);
     if (NS_FAILED(mEarlyRv)) {
       return;
     }
 
     SetKeyData(aCx, aKeyData);
     NS_ENSURE_SUCCESS_VOID(mEarlyRv);
   }
 
+  void Init(JSContext* aCx, const nsAString& aFormat,
+            const ObjectOrString& aAlgorithm, bool aExtractable,
+            const Sequence<nsString>& aKeyUsages)
+  {
+    ImportKeyTask::Init(aCx, aFormat, aAlgorithm, aExtractable, aKeyUsages);
+    if (NS_FAILED(mEarlyRv)) {
+      return;
+    }
+
+    if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_RAW)) {
+      RootedDictionary<EcKeyImportParams> params(aCx);
+      mEarlyRv = Coerce(aCx, params, aAlgorithm);
+      if (NS_FAILED(mEarlyRv) || !params.mNamedCurve.WasPassed()) {
+        mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR;
+        return;
+      }
+
+      if (!NormalizeToken(params.mNamedCurve.Value(), mNamedCurve)) {
+        mEarlyRv = NS_ERROR_DOM_NOT_SUPPORTED_ERR;
+        return;
+      }
+    }
+  }
+
 private:
   nsString mNamedCurve;
 
   virtual nsresult DoCrypto() override
   {
     // Import the key data itself
     ScopedSECKEYPublicKey pubKey;
     ScopedSECKEYPrivateKey privKey;
@@ -1675,24 +1699,29 @@ private:
       // 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 if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_SPKI) ||
+    } else if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_RAW) ||
+               mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_SPKI) ||
                (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_JWK) &&
                 !mJwk.mD.WasPassed())) {
       // Public key import
-      if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_SPKI)) {
+      if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_RAW)) {
+        pubKey = CryptoKey::PublicECKeyFromRaw(mKeyData, mNamedCurve, locker);
+      } else if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_SPKI)) {
         pubKey = CryptoKey::PublicKeyFromSpki(mKeyData, locker);
+      } else if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_JWK)) {
+        pubKey = CryptoKey::PublicKeyFromJwk(mJwk, locker);
       } else {
-        pubKey = CryptoKey::PublicKeyFromJwk(mJwk, locker);
+        MOZ_ASSERT(false);
       }
 
       if (!pubKey) {
         return NS_ERROR_DOM_DATA_ERR;
       }
 
       if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_SPKI)) {
         if (!CheckEncodedECParameters(&pubKey->u.ec.DEREncodedParams)) {
@@ -1896,16 +1925,24 @@ private:
       if (mPublicKey && mPublicKey->keyType == dhKey) {
         nsresult rv = CryptoKey::PublicDhKeyToRaw(mPublicKey, mResult, locker);
         if (NS_FAILED(rv)) {
           return NS_ERROR_DOM_OPERATION_ERR;
         }
         return NS_OK;
       }
 
+      if (mPublicKey && mPublicKey->keyType == ecKey) {
+        nsresult rv = CryptoKey::PublicECKeyToRaw(mPublicKey, mResult, locker);
+        if (NS_FAILED(rv)) {
+          return NS_ERROR_DOM_OPERATION_ERR;
+        }
+        return NS_OK;
+      }
+
       mResult = mSymKey;
       if (mResult.Length() == 0) {
         return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
       }
 
       return NS_OK;
     } else if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_PKCS8)) {
       if (!mPrivateKey) {
--- a/dom/crypto/test/test-vectors.js
+++ b/dom/crypto/test/test-vectors.js
@@ -513,16 +513,21 @@ tv = {
 
     // vector with algorithm = id-ecPublicKey
     spki_id_ecpk: util.hex2abv(
       "3059301306072a8648ce3d020106082a8648ce3d030107034200045ce7b86e3b" +
       "32660403e63712ef0998deae1027faec3c1be9f76f934dfeb58e98f4cf075b39" +
       "405dd1f1adeb090107edcfb2b4963739d87679e3056cb0557d0adf"
     ),
 
+    raw: util.hex2abv(
+      "045ce7b86e3b32660403e63712ef0998deae1027faec3c1be9f76f934dfeb58e" +
+      "98f4cf075b39405dd1f1adeb090107edcfb2b4963739d87679e3056cb0557d0adf"
+    ),
+
     secret: util.hex2abv(
       "35669cd5c244ba6c1ea89b8802c3d1db815cd769979072e6556eb98548c65f7d"
     )
   },
 
   // KASValidityTest_ECCEphemeralUnified_NOKC_ZZOnly_init.fax [ED]
   // <http://csrc.nist.gov/groups/STM/cavp/documents/keymgmt/kastestvectors.zip>
   ecdh_p384: {
@@ -601,17 +606,41 @@ tv = {
       y: "9M8HWzlAXdHxresJAQftz7K0ljc52HZ54wVssFV9Ct8"
     },
 
     // The Y coordinate is missing.
     jwk_missing_y: {
       kty: "EC",
       crv: "P-256",
       x: "XOe4bjsyZgQD5jcS7wmY3q4QJ_rsPBvp92-TTf61jpg",
-    }
+    },
+
+    // Public point with Y not on the curve.
+    raw_bad: util.hex2abv(
+      "045ce7b86e3b32660403e63712ef0998deae1027faec3c1be9f76f934dfeb58e" +
+      "98f4cf075b39405dd1f1adeb090106edcfb2b4963739d87679e3056cb0557d0adf"
+    ),
+
+    // Public point with Y a little too short.
+    raw_short: util.hex2abv(
+      "045ce7b86e3b32660403e63712ef0998deae1027faec3c1be9f76f934dfeb58e" +
+      "98f4cf075b39405dd1f1adeb090107edcfb2b4963739d87679e3056cb0557d0a"
+    ),
+
+    // Public point with Y a little too long.
+    raw_long: util.hex2abv(
+      "045ce7b86e3b32660403e63712ef0998deae1027faec3c1be9f76f934dfeb58e" +
+      "98f4cf075b39405dd1f1adeb090107edcfb2b4963739d87679e3056cb0557d0adfff"
+    ),
+
+    // Public point with EC_POINT_FORM_COMPRESSED_Y0.
+    raw_compressed: util.hex2abv(
+      "025ce7b86e3b32660403e63712ef0998deae1027faec3c1be9f76f934dfeb58e" +
+      "98f4cf075b39405dd1f1adeb090107edcfb2b4963739d87679e3056cb0557d0adf"
+    )
   },
 
   // NIST ECDSA test vectors
   // http://csrc.nist.gov/groups/STM/cavp/index.html
   ecdsa_verify: {
     pub_jwk: {
       "kty": "EC",
       "crv": "P-521",
@@ -621,16 +650,23 @@ tv = {
       "x": "AGE4f9a5WRTohfkS7fu1-ydGVQJ_IWxAkcqD4ZM2dA_Y" +
            "Gu3-BH9RtCvfaBYRIQE-DVWxF6FOQwP5Jsjeu3en_arR",
       // 00e7d0c75c38626e895ca21526b9f9fdf84dcecb93f2b233390550d2b1463b7ee3
       // f58df7346435ff0434199583c97c665a97f12f706f2357da4b40288def888e59e6
       "y": "AOfQx1w4Ym6JXKIVJrn5_fhNzsuT8rIzOQVQ0rFGO37j" +
            "9Y33NGQ1_wQ0GZWDyXxmWpfxL3BvI1faS0Aoje-Ijlnm",
     },
 
+    raw: util.hex2abv(
+      "040061387fd6b95914e885f912edfbb5fb274655027f216c4091ca83e19336740fd" +
+      "81aedfe047f51b42bdf68161121013e0d55b117a14e4303f926c8debb77a7fdaad1" +
+      "00e7d0c75c38626e895ca21526b9f9fdf84dcecb93f2b233390550d2b1463b7ee3f" +
+      "58df7346435ff0434199583c97c665a97f12f706f2357da4b40288def888e59e6"
+    ),
+
     "data": util.hex2abv(
             "9ecd500c60e701404922e58ab20cc002651fdee7cbc9336adda33e4c1088fab1" +
             "964ecb7904dc6856865d6c8e15041ccf2d5ac302e99d346ff2f686531d255216" +
             "78d4fd3f76bbf2c893d246cb4d7693792fe18172108146853103a51f824acc62" +
             "1cb7311d2463c3361ea707254f2b052bc22cb8012873dcbb95bf1a5cc53ab89f"
           ),
     "sig": util.hex2abv(
             "004de826ea704ad10bc0f7538af8a3843f284f55c8b946af9235af5af74f2b76e0" +
--- a/dom/crypto/test/test_WebCrypto_ECDH.html
+++ b/dom/crypto/test/test_WebCrypto_ECDH.html
@@ -433,16 +433,124 @@ TestArray.addTest(
       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));
   }
 );
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+  "Raw import/export of a public ECDH key (P-256)",
+  function () {
+    var that = this;
+    var alg = { name: "ECDH", namedCurve: "P-256" };
+
+    function doExport(x) {
+      return crypto.subtle.exportKey("raw", x);
+    }
+
+    crypto.subtle.importKey("raw", tv.ecdh_p256.raw, alg, true, ["deriveBits"])
+      .then(doExport)
+      .then(memcmp_complete(that, tv.ecdh_p256.raw), error(that));
+  }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+  "Test that importing bad raw ECDH keys fails",
+  function () {
+    var that = this;
+    var alg = { name: "ECDH", namedCurve: "P-256" };
+    var tvs = tv.ecdh_p256_negative.raw_bad;
+
+    crypto.subtle.importKey("raw", tv, alg, false, ["deriveBits"])
+      .then(error(that), complete(that));
+  }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+  "Test that importing ECDH keys with an unknown format fails",
+  function () {
+    var that = this;
+    var alg = { name: "ECDH", namedCurve: "P-256" };
+    var tvs = tv.ecdh_p256.raw;
+
+    crypto.subtle.importKey("unknown", tv, alg, false, ["deriveBits"])
+      .then(error(that), complete(that));
+  }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+  "Test that importing too short raw ECDH keys fails",
+  function () {
+    var that = this;
+    var alg = { name: "ECDH", namedCurve: "P-256" };
+    var tvs = tv.ecdh_p256_negative.raw_short;
+
+    crypto.subtle.importKey("raw", tv, alg, false, ["deriveBits"])
+      .then(error(that), complete(that));
+  }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+  "Test that importing too long raw ECDH keys fails",
+  function () {
+    var that = this;
+    var alg = { name: "ECDH", namedCurve: "P-256" };
+    var tvs = tv.ecdh_p256_negative.raw_long;
+
+    crypto.subtle.importKey("raw", tv, alg, false, ["deriveBits"])
+      .then(error(that), complete(that));
+  }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+  "Test that importing compressed raw ECDH keys fails",
+  function () {
+    var that = this;
+    var alg = { name: "ECDH", namedCurve: "P-256" };
+    var tvs = tv.ecdh_p256_negative.raw_compressed;
+
+    crypto.subtle.importKey("raw", tv, alg, false, ["deriveBits"])
+      .then(error(that), complete(that));
+  }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+  "RAW/JWK import ECDH keys (P-256) and derive a known secret",
+  function () {
+    var that = this;
+    var alg = { name: "ECDH", namedCurve: "P-256" };
+
+    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("raw", tv.ecdh_p256.raw, 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));
+  }
+);
 /*]]>*/</script>
 </head>
 
 <body>
 
 <div id="content">
 	<div id="head">
 		<b>Web</b>Crypto<br>
--- a/dom/crypto/test/test_WebCrypto_ECDSA.html
+++ b/dom/crypto/test/test_WebCrypto_ECDSA.html
@@ -139,16 +139,50 @@ TestArray.addTest(
       return crypto.subtle.verify(alg, x, tv.ecdsa_verify.sig, tv.ecdsa_verify.data);
     }
 
     crypto.subtle.importKey("jwk", tv.ecdsa_bad.pub_jwk, alg, true, ["verify"])
       .then(error(that), complete(that))
   }
 );
 
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+  "Raw import/export of a public ECDSA key (P-521)",
+  function () {
+    var that = this;
+    var alg = { name: "ECDSA", namedCurve: "P-521", hash: "SHA-512" };
+
+    function doExport(x) {
+      return crypto.subtle.exportKey("raw", x);
+    }
+
+    crypto.subtle.importKey("raw", tv.ecdsa_verify.raw, alg, true, ["verify"])
+      .then(doExport)
+      .then(memcmp_complete(that, tv.ecdsa_verify.raw), error(that));
+  }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+  "ECDSA raw import and verify a known-good signature",
+  function() {
+    var that = this;
+    var alg = { name: "ECDSA", namedCurve: "P-521", hash: "SHA-512" };
+
+    function doVerify(x) {
+      return crypto.subtle.verify(alg, x, tv.ecdsa_verify.sig, tv.ecdsa_verify.data);
+    }
+
+    crypto.subtle.importKey("raw", tv.ecdsa_verify.raw, alg, true, ["verify"])
+      .then(doVerify)
+      .then(complete(that), error(that))
+  }
+);
+
 /*]]>*/</script>
 </head>
 
 <body>
 
 <div id="content">
         <div id="head">
                 <b>Web</b>Crypto<br>
--- a/dom/webidl/SubtleCrypto.webidl
+++ b/dom/webidl/SubtleCrypto.webidl
@@ -95,16 +95,20 @@ dictionary DhImportKeyParams : Algorithm
   required BigInteger prime;
   required BigInteger generator;
 };
 
 dictionary EcdsaParams : Algorithm {
   required AlgorithmIdentifier hash;
 };
 
+dictionary EcKeyImportParams : Algorithm {
+  NamedCurve namedCurve;
+};
+
 /***** JWK *****/
 
 dictionary RsaOtherPrimesInfo {
   // The following fields are defined in Section 6.3.2.7 of JSON Web Algorithms
   required DOMString r;
   required DOMString d;
   required DOMString t;
 };