Bug 1034855 - Implement JWK import/export for ECDH r=rbarnes,keeler
☠☠ backed out by 591fb2c4ee15 ☠ ☠
authorTim Taubert <ttaubert@mozilla.com>
Tue, 29 Jul 2014 11:10:07 +0200
changeset 197981 4af8993f153429a593d8ff50c1553ecf069ebbb0
parent 197980 f4f5f5b8421d29117b5e613db73546d25dfc4051
child 197982 4eb0ee62db1ed3412bca570b32a969fbf669669a
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)
reviewersrbarnes, keeler
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 JWK import/export for ECDH r=rbarnes,keeler
dom/crypto/CryptoKey.cpp
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
@@ -347,91 +347,189 @@ CryptoKey::PublicKeyToSpki(SECKEYPublicK
   if (!spkiItem.get()) {
     return NS_ERROR_DOM_INVALID_ACCESS_ERR;
   }
 
   aRetVal.Assign(spkiItem.get());
   return NS_OK;
 }
 
-SECKEYPrivateKey*
-CryptoKey::PrivateKeyFromJwk(const JsonWebKey& aJwk,
-                             const nsNSSShutDownPreventionLock& /*proofOfLock*/)
+SECItem*
+CreateECPointForCoordinates(const CryptoBuffer& aX,
+                            const CryptoBuffer& aY,
+                            PLArenaPool* aArena)
 {
-  if (!aJwk.mKty.WasPassed() || !aJwk.mKty.Value().EqualsLiteral(JWK_TYPE_RSA)) {
-    return nullptr;
-  }
-
-  // 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()))) {
+  // Check that both points have the same length.
+  if (aX.Length() != aY.Length()) {
     return nullptr;
   }
 
-  // Compute the ID for this key
-  // This is generated with a SHA-1 hash, so unlikely to collide
-  ScopedSECItem nItem(n.ToSECItem());
-  ScopedSECItem objID(PK11_MakeIDFromPubKey(nItem.get()));
-  if (!nItem.get() || !objID.get()) {
+  // Create point.
+  SECItem* point = ::SECITEM_AllocItem(aArena, nullptr, aX.Length() + aY.Length() + 1);
+  if (!point) {
     return nullptr;
   }
 
-  // Populate template from parameters
-  CK_OBJECT_CLASS privateKeyValue = CKO_PRIVATE_KEY;
-  CK_KEY_TYPE rsaValue = CKK_RSA;
-  CK_BBOOL falseValue = CK_FALSE;
-  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) },
-    { CKA_ID,               objID->data,           objID->len },
-    { CKA_MODULUS,          (void*) n.Elements(),  n.Length() },
-    { CKA_PUBLIC_EXPONENT,  (void*) e.Elements(),  e.Length() },
-    { CKA_PRIVATE_EXPONENT, (void*) d.Elements(),  d.Length() },
-    { CKA_PRIME_1,          (void*) p.Elements(),  p.Length() },
-    { CKA_PRIME_2,          (void*) q.Elements(),  q.Length() },
-    { CKA_EXPONENT_1,       (void*) dp.Elements(), dp.Length() },
-    { CKA_EXPONENT_2,       (void*) dq.Elements(), dq.Length() },
-    { CKA_COEFFICIENT,      (void*) qi.Elements(), qi.Length() },
-  };
+  // 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;
+}
 
+SECKEYPrivateKey*
+PrivateKeyFromPrivateKeyTemplate(SECItem* aObjID,
+                                 CK_ATTRIBUTE* aTemplate,
+                                 CK_ULONG aTemplateSize)
+{
   // Create a generic object with the contents of the key
   ScopedPK11SlotInfo slot(PK11_GetInternalSlot());
   if (!slot.get()) {
     return nullptr;
   }
 
   ScopedPK11GenericObject obj(PK11_CreateGenericObject(slot.get(),
-                                                       keyTemplate,
-                                                       PR_ARRAY_SIZE(keyTemplate),
+                                                       aTemplate,
+                                                       aTemplateSize,
                                                        PR_FALSE));
   if (!obj.get()) {
     return nullptr;
   }
 
   // Have NSS translate the object to a private key by inspection
   // and make a copy we can own
-  ScopedSECKEYPrivateKey privKey(PK11_FindKeyByKeyID(slot.get(), objID.get(),
+  ScopedSECKEYPrivateKey privKey(PK11_FindKeyByKeyID(slot.get(), aObjID,
                                                      nullptr));
   if (!privKey.get()) {
     return nullptr;
   }
+
   return SECKEY_CopyPrivateKey(privKey.get());
 }
 
+SECKEYPrivateKey*
+CryptoKey::PrivateKeyFromJwk(const JsonWebKey& aJwk,
+                             const nsNSSShutDownPreventionLock& /*proofOfLock*/)
+{
+  if (!aJwk.mKty.WasPassed()) {
+    return nullptr;
+  }
+
+  CK_OBJECT_CLASS privateKeyValue = CKO_PRIVATE_KEY;
+  CK_BBOOL falseValue = CK_FALSE;
+
+  if (aJwk.mKty.Value().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 (!NormalizeNamedCurveValue(aJwk.mCrv.Value(), namedCurve)) {
+      return nullptr;
+    }
+
+    ScopedPLArenaPool 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;
+    }
+
+    // Compute the ID for this key
+    // This is generated with a SHA-1 hash, so unlikely to collide
+    ScopedSECItem objID(PK11_MakeIDFromPubKey(ecPoint));
+    if (!objID.get()) {
+      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) },
+      { CKA_ID,               objID->data,          objID->len },
+      { CKA_EC_PARAMS,        params->data,         params->len },
+      { CKA_EC_POINT,         ecPoint->data,        ecPoint->len },
+      { CKA_VALUE,            (void*) d.Elements(), d.Length() },
+    };
+
+    return PrivateKeyFromPrivateKeyTemplate(objID, keyTemplate,
+                                            PR_ARRAY_SIZE(keyTemplate));
+  }
+
+  if (aJwk.mKty.Value().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;
+    }
+
+    // Compute the ID for this key
+    // This is generated with a SHA-1 hash, so unlikely to collide
+    ScopedSECItem nItem(n.ToSECItem());
+    if (!nItem.get()) {
+      return nullptr;
+    }
+
+    ScopedSECItem objID(PK11_MakeIDFromPubKey(nItem.get()));
+    if (!objID.get()) {
+      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) },
+      { CKA_ID,               objID->data,           objID->len },
+      { CKA_MODULUS,          (void*) n.Elements(),  n.Length() },
+      { CKA_PUBLIC_EXPONENT,  (void*) e.Elements(),  e.Length() },
+      { CKA_PRIVATE_EXPONENT, (void*) d.Elements(),  d.Length() },
+      { CKA_PRIME_1,          (void*) p.Elements(),  p.Length() },
+      { CKA_PRIME_2,          (void*) q.Elements(),  q.Length() },
+      { CKA_EXPONENT_1,       (void*) dp.Elements(), dp.Length() },
+      { CKA_EXPONENT_2,       (void*) dq.Elements(), dq.Length() },
+      { CKA_COEFFICIENT,      (void*) qi.Elements(), qi.Length() },
+    };
+
+    return PrivateKeyFromPrivateKeyTemplate(objID, keyTemplate,
+                                            PR_ARRAY_SIZE(keyTemplate));
+  }
+
+  return nullptr;
+}
+
 bool ReadAndEncodeAttribute(SECKEYPrivateKey* aKey,
                             CK_ATTRIBUTE_TYPE aAttribute,
                             Optional<nsString>& aDst)
 {
   ScopedSECItem item(new SECItem());
   if (PK11_ReadRawAttribute(PK11_TypePrivKey, aKey, aAttribute, item)
         != SECSuccess) {
     return false;
@@ -444,16 +542,81 @@ bool ReadAndEncodeAttribute(SECKEYPrivat
 
   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;
+  }
+
+  ScopedSECItem ecPointX(::SECITEM_AllocItem(nullptr, nullptr, flen));
+  ScopedSECItem 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) || NS_FAILED(x.ToJwkBase64(aRetVal.mX.Value())) ||
+      !y.Assign(ecPointY) || NS_FAILED(y.ToJwkBase64(aRetVal.mY.Value()))) {
+    return false;
+  }
+
+  aRetVal.mKty.Construct(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();
@@ -474,60 +637,138 @@ CryptoKey::PrivateKeyToJwk(SECKEYPrivate
           !ReadAndEncodeAttribute(aPrivKey, CKA_EXPONENT_2, aRetVal.mDq) ||
           !ReadAndEncodeAttribute(aPrivKey, CKA_COEFFICIENT, aRetVal.mQi)) {
         return NS_ERROR_DOM_OPERATION_ERR;
       }
 
       aRetVal.mKty.Construct(NS_LITERAL_STRING(JWK_TYPE_RSA));
       return NS_OK;
     }
-    case ecKey: // TODO: Bug 1034855
+    case ecKey: {
+      // Read EC params.
+      ScopedSECItem params(::SECITEM_AllocItem(nullptr, nullptr, 0));
+      SECStatus rv = PK11_ReadRawAttribute(PK11_TypePrivKey, aPrivKey,
+                                           CKA_EC_PARAMS, params);
+      if (rv != SECSuccess) {
+        return NS_ERROR_DOM_OPERATION_ERR;
+      }
+
+      // Read public point Q.
+      ScopedSECItem ecPoint(::SECITEM_AllocItem(nullptr, nullptr, 0));
+      rv = PK11_ReadRawAttribute(PK11_TypePrivKey, aPrivKey, CKA_EC_POINT,
+                                 ecPoint);
+      if (rv != SECSuccess) {
+        return NS_ERROR_DOM_OPERATION_ERR;
+      }
+
+      if (!ECKeyToJwk(PK11_TypePrivKey, aPrivKey, params, 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;
   }
 }
 
 SECKEYPublicKey*
 CryptoKey::PublicKeyFromJwk(const JsonWebKey& aJwk,
                             const nsNSSShutDownPreventionLock& /*proofOfLock*/)
 {
-  if (!aJwk.mKty.WasPassed() || !aJwk.mKty.Value().EqualsLiteral(JWK_TYPE_RSA)) {
-    return nullptr;
-  }
-
-  // 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()))) {
+  if (!aJwk.mKty.WasPassed()) {
     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,}
-  };
+  if (aJwk.mKty.Value().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;
+    }
 
-  ScopedSECItem pkDer(SEC_ASN1EncodeItem(nullptr, nullptr, &input,
-                                         rsaPublicKeyTemplate));
-  if (!pkDer.get()) {
-    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,}
+    };
+
+    ScopedSECItem pkDer(SEC_ASN1EncodeItem(nullptr, nullptr, &input,
+                                           rsaPublicKeyTemplate));
+    if (!pkDer.get()) {
+      return nullptr;
+    }
+
+    return SECKEY_ImportDERPublicKey(pkDer.get(), CKK_RSA);
   }
 
-  return SECKEY_ImportDERPublicKey(pkDer.get(), CKK_RSA);
+  if (aJwk.mKty.Value().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;
+    }
+
+    ScopedSECKEYPublicKey key(PORT_ZNew(SECKEYPublicKey));
+    if (!key) {
+      return nullptr;
+    }
+
+    key->keyType = ecKey;
+    key->pkcs11Slot = nullptr;
+    key->pkcs11ID = CK_INVALID_HANDLE;
+
+    nsString namedCurve;
+    if (!NormalizeNamedCurveValue(aJwk.mCrv.Value(), namedCurve)) {
+      return nullptr;
+    }
+
+    ScopedPLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE));
+    if (!arena) {
+      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;
+
+    return SECKEY_CopyPublicKey(key);
+  }
+
+  return nullptr;
 }
 
 nsresult
 CryptoKey::PublicKeyToJwk(SECKEYPublicKey* aPubKey,
                           JsonWebKey& aRetVal,
                           const nsNSSShutDownPreventionLock& /*proofOfLock*/)
 {
   switch (aPubKey->keyType) {
@@ -541,17 +782,22 @@ CryptoKey::PublicKeyToJwk(SECKEYPublicKe
           NS_FAILED(n.ToJwkBase64(aRetVal.mN.Value())) ||
           NS_FAILED(e.ToJwkBase64(aRetVal.mE.Value()))) {
         return NS_ERROR_DOM_OPERATION_ERR;
       }
 
       aRetVal.mKty.Construct(NS_LITERAL_STRING(JWK_TYPE_RSA));
       return NS_OK;
     }
-    case ecKey: // TODO
+    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;
   }
 }
 
 bool
 CryptoKey::WriteStructuredClone(JSStructuredCloneWriter* aWriter) const
 {
--- a/dom/crypto/WebCryptoTask.cpp
+++ b/dom/crypto/WebCryptoTask.cpp
@@ -1649,16 +1649,99 @@ private:
     if (mDataIsJwk && !JwkCompatible(mJwk, mKey)) {
       return NS_ERROR_DOM_DATA_ERR;
     }
 
     return NS_OK;
   }
 };
 
+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);
+  }
+
+  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);
+    if (NS_FAILED(mEarlyRv)) {
+      return;
+    }
+
+    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()) {
+      // 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 {
+      // Public key import
+      pubKey = CryptoKey::PublicKeyFromJwk(mJwk, locker);
+      if (!pubKey) {
+        return NS_ERROR_DOM_DATA_ERR;
+      }
+
+      mKey->SetPublicKey(pubKey.get());
+      mKey->SetType(CryptoKey::PUBLIC);
+    }
+
+    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
+    if (mKey->GetKeyType() == CryptoKey::PRIVATE &&
+        mKey->HasUsageOtherThan(CryptoKey::DERIVEBITS | CryptoKey::DERIVEKEY)) {
+      return NS_ERROR_DOM_DATA_ERR;
+    }
+
+    nsIGlobalObject* global = mKey->GetParentObject();
+    mKey->SetAlgorithm(new EcKeyAlgorithm(global, mAlgName, mNamedCurve));
+
+    if (mDataIsJwk && !JwkCompatible(mJwk, mKey)) {
+      return NS_ERROR_DOM_DATA_ERR;
+    }
+
+    return NS_OK;
+  }
+};
+
 class ExportKeyTask : public WebCryptoTask
 {
 public:
   ExportKeyTask(const nsAString& aFormat, CryptoKey& aKey)
     : mFormat(aFormat)
     , mSymKey(aKey.GetSymKey())
     , mPrivateKey(aKey.GetPrivateKey())
     , mPublicKey(aKey.GetPublicKey())
@@ -2584,16 +2667,19 @@ WebCryptoTask::CreateImportKeyTask(JSCon
       algName.EqualsLiteral(WEBCRYPTO_ALG_HMAC)) {
     return new ImportSymmetricKeyTask(aCx, aFormat, aKeyData, aAlgorithm,
                                       aExtractable, aKeyUsages);
   } else if (algName.EqualsLiteral(WEBCRYPTO_ALG_RSAES_PKCS1) ||
              algName.EqualsLiteral(WEBCRYPTO_ALG_RSASSA_PKCS1) ||
              algName.EqualsLiteral(WEBCRYPTO_ALG_RSA_OAEP)) {
     return new ImportRsaKeyTask(aCx, aFormat, aKeyData, aAlgorithm,
                                 aExtractable, aKeyUsages);
+  } else if (algName.EqualsLiteral(WEBCRYPTO_ALG_ECDH)) {
+    return new ImportEcKeyTask(aCx, aFormat, aKeyData, aAlgorithm,
+                               aExtractable, aKeyUsages);
   } else {
     return new FailureTask(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
   }
 }
 
 WebCryptoTask*
 WebCryptoTask::CreateExportKeyTask(const nsAString& aFormat,
                              CryptoKey& aKey)
--- a/dom/crypto/test/test-vectors.js
+++ b/dom/crypto/test/test-vectors.js
@@ -447,10 +447,119 @@ tv = {
       "41fc6064b147071a4c30426d82fc90d888f94990267c64beef8c304a4b2b26fb" +
       "93724d6a9472fa16bc50c5b9b8b59afb62cfe9ea3ba042c73a6ade3502818100" +
       "a51883e9ac0539859df3d25c716437008bb4bd8ec4786eb4bc643299daef5e3e" +
       "5af5863a6ac40a597b83a27583f6a658d408825105b16d31b6ed088fc623f648" +
       "fd6d95e9cefcb0745763cddf564c87bcf4ba7928e74fd6a3080481f588d535e4" +
       "c026b58a21e1e5ec412ff241b436043e29173f1dc6cb943c09742de989547288" +
       "0416021442c6ee70beb7465928a1efe692d2281b8f7b53d6"
     )
-  }
+  },
+
+  // KASValidityTest_ECCEphemeralUnified_NOKC_ZZOnly_init.fax [EC]
+  // <http://csrc.nist.gov/groups/STM/cavp/documents/keymgmt/kastestvectors.zip>
+  ecdh_p256: {
+    jwk_pub: {
+      kty: "EC",
+      crv: "P-256",
+      x: "XOe4bjsyZgQD5jcS7wmY3q4QJ_rsPBvp92-TTf61jpg",
+      y: "9M8HWzlAXdHxresJAQftz7K0ljc52HZ54wVssFV9Ct8"
+    },
+
+    jwk_priv: {
+      kty: "EC",
+      crv: "P-256",
+      d: "qq_LEzeJpR00KM5DQvL2MNtJcbi0KcGVcoPIHNnwm2A",
+      x: "FNwJHA-FwnSx5tKXFV_iLN408gbKUHRV06WnQlzTdN4",
+      y: "is9pWAaneK4RdxmdLfsq5IwizDmUS2w8OGS99sKm3ek"
+    },
+
+    secret: util.hex2abv(
+      "35669cd5c244ba6c1ea89b8802c3d1db815cd769979072e6556eb98548c65f7d"
+    )
+  },
+
+  // KASValidityTest_ECCEphemeralUnified_NOKC_ZZOnly_init.fax [ED]
+  // <http://csrc.nist.gov/groups/STM/cavp/documents/keymgmt/kastestvectors.zip>
+  ecdh_p384: {
+    jwk_pub: {
+      kty: "EC",
+      crv: "P-384",
+      x: "YoV6fhCph4kyt7sUkqiZOtbRs0rF6etPqlnrn1nzSB95NElaw4uTK7Pn2nlFFqqH",
+      y: "bf3tRz6icq3-W6hhmoqDTBKjdOQUJ5xHr5kX4X-h5MZk_P_nCrG3IUVl1SAbhWDw"
+    },
+
+    jwk_priv: {
+      kty: "EC",
+      crv: "P-384",
+      d: "RT8f0pRw4CL1Tgk4rwuNnNbFoQBNTTBkr7WVLLm4fDA3boYZpNB_t-rbMVLx0CRp",
+      x: "_XwhXRnOzEfCsWIRCz3QLClaDkigQFvXmqYNdh/7vJdADykPbfGi1VgAu3XJdXoD",
+      y: "S1P_FBCXYGE-5VPvTCRnFT7bPIPmUPV9qKTM24TQFYEUgIDfzCLsyGCWK-rhP6jU"
+    },
+
+    secret: util.hex2abv(
+      "a3d28aa18f905a48a5f166b4ddbf5f6b499e43858ccdd80b869946aba2c5d461" +
+      "db6a1e5b1137687801878ff0f8d9a7b3"
+    )
+  },
+
+  // KASValidityTest_ECCEphemeralUnified_NOKC_ZZOnly_init.fax [EE]
+  // <http://csrc.nist.gov/groups/STM/cavp/documents/keymgmt/kastestvectors.zip>
+  ecdh_p521: {
+    jwk_pub: {
+      kty: "EC",
+      crv: "P-521",
+      x: "AeCLgRZ-BPqfhq4jt409-E26VHW5l29q74cHbIbQiS_-Gcqdo-087jHdPXUksGpr" +
+         "Nyp_RcTZd94t3peXzQziQIqo",
+      y: "AZIAp8QVnU9hBOkLScv0dy540uGtBWHkWj4DGh-Exh4iWZ0E-YBS8-HVx2eB-nfG" +
+         "AGEy4-BzfpFFlfidOS1Tg77J"
+    },
+
+    jwk_priv: {
+      kty: "EC",
+      crv: "P-521",
+      d: "ABtsfkDGFarQU4kb7e2gPszGCTT8GLDaaJbFQenFZce3qp_dh0qZarXHKBZ-BVic" +
+         "NeIW5Sk661UoNfwykSvmh77S",
+      x: "AcD_6Eb4A-8QdUM70c6F0WthN1kvV4fohS8QHbod6B4y1ZDU54mQuCR-3IBjcV1c" +
+         "oh18uxbyUn5szMuCgjZUiD0y",
+      y: "AU3WKJffztkhAQetBXaLvUSIHa87HMn8vZFB04lWipH-SrsrAu_4N-6iam0OD4EJ" +
+         "0kOMH8iEh7yuivaKsFRzm2-m"
+    },
+
+    secret: util.hex2abv(
+      "00561eb17d856552c21b8cbe7d3d60d1ea0db738b77d4050fa2dbd0773edc395" +
+      "09854d9e30e843964ed3fd303339e338f31289120a38f94e9dc9ff7d4b3ea8f2" +
+      "5e01"
+    )
+  },
+
+  // Some test vectors that we should fail to import.
+  ecdh_p256_negative: {
+    // The given curve doesn't exist / isn't supported.
+    jwk_bad_crv: {
+      kty: "EC",
+      crv: "P-123",
+      x: "XOe4bjsyZgQD5jcS7wmY3q4QJ_rsPBvp92-TTf61jpg",
+      y: "9M8HWzlAXdHxresJAQftz7K0ljc52HZ54wVssFV9Ct8"
+    },
+
+    // The crv parameter is missing.
+    jwk_missing_crv: {
+      kty: "EC",
+      x: "XOe4bjsyZgQD5jcS7wmY3q4QJ_rsPBvp92-TTf61jpg",
+      y: "9M8HWzlAXdHxresJAQftz7K0ljc52HZ54wVssFV9Ct8"
+    },
+
+    // The X coordinate is missing.
+    jwk_missing_x: {
+      kty: "EC",
+      crv: "P-256",
+      y: "9M8HWzlAXdHxresJAQftz7K0ljc52HZ54wVssFV9Ct8"
+    },
+
+    // The Y coordinate is missing.
+    jwk_missing_y: {
+      kty: "EC",
+      crv: "P-256",
+      x: "XOe4bjsyZgQD5jcS7wmY3q4QJ_rsPBvp92-TTf61jpg",
+    }
+  },
 }
--- a/dom/crypto/test/tests.js
+++ b/dom/crypto/test/tests.js
@@ -1993,8 +1993,153 @@ TestArray.addTest(
     doGenerateP256()
       .then(setPriv, error(that))
       .then(doGenerateP384, error(that))
       .then(setPub, error(that))
       .then(doDerive, error(that))
       .then(error(that), complete(that));
   }
 );
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+  "JWK import an ECDH public and private key and derive bits (P-256)",
+  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("jwk", tv.ecdh_p256.jwk_priv, alg, false, ["deriveBits"])
+        .then(setPriv, error(that)),
+      crypto.subtle.importKey("jwk", tv.ecdh_p256.jwk_pub, alg, false, ["deriveBits"])
+        .then(setPub, error(that))
+    ]).then(doDerive, error(that))
+      .then(memcmp_complete(that, tv.ecdh_p256.secret), error(that));
+  }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+  "JWK import an ECDH public and private key and derive bits (P-384)",
+  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_p384.secret.byteLength * 8);
+    }
+
+    Promise.all([
+      crypto.subtle.importKey("jwk", tv.ecdh_p384.jwk_priv, alg, false, ["deriveBits"])
+        .then(setPriv, error(that)),
+      crypto.subtle.importKey("jwk", tv.ecdh_p384.jwk_pub, alg, false, ["deriveBits"])
+        .then(setPub, error(that))
+    ]).then(doDerive, error(that))
+      .then(memcmp_complete(that, tv.ecdh_p384.secret), error(that));
+  }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+  "JWK import an ECDH public and private key and derive bits (P-521)",
+  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_p521.secret.byteLength * 8);
+    }
+
+    Promise.all([
+      crypto.subtle.importKey("jwk", tv.ecdh_p521.jwk_priv, alg, false, ["deriveBits"])
+        .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(memcmp_complete(that, tv.ecdh_p521.secret), error(that));
+  }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+  "JWK import/export roundtrip with ECDH (P-256)",
+  function () {
+    var that = this;
+    var alg = { name: "ECDH" };
+
+    var pubKey, privKey;
+    function setPub(x) { pubKey = x; }
+    function setPriv(x) { privKey = x; }
+
+    function doExportPub() {
+      return crypto.subtle.exportKey("jwk", pubKey);
+    }
+    function doExportPriv() {
+      return crypto.subtle.exportKey("jwk", privKey);
+    }
+
+    Promise.all([
+      crypto.subtle.importKey("jwk", tv.ecdh_p256.jwk_priv, alg, true, ["deriveBits"])
+        .then(setPriv, error(that)),
+      crypto.subtle.importKey("jwk", tv.ecdh_p256.jwk_pub, alg, true, ["deriveBits"])
+        .then(setPub, error(that))
+    ]).then(doExportPub, error(that))
+      .then(function (x) {
+        var tp = tv.ecdh_p256.jwk_pub;
+        if ((tp.kty != x.kty) &&
+            (tp.crv != x.crv) &&
+            (tp.x != x.x) &&
+            (tp.y != x.y)) {
+          throw "exported public key doesn't match";
+        }
+      }, error(that))
+      .then(doExportPriv, error(that))
+      .then(complete(that, function (x) {
+        var tp = tv.ecdh_p256.jwk_priv;
+        return (tp.kty == x.kty) &&
+               (tp.crv == x.crv) &&
+               (tp.d == x.d) &&
+               (tp.x == x.x) &&
+               (tp.y == x.y);
+      }), error(that));
+  }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+  "Test that importing bad JWKs fails",
+  function () {
+    var that = this;
+    var alg = { name: "ECDH" };
+    var tvs = tv.ecdh_p256_negative;
+
+    function doTryImport(jwk) {
+      return function () {
+        return crypto.subtle.importKey("jwk", jwk, alg, false, ["deriveBits"]);
+      }
+    }
+
+    doTryImport(tvs.jwk_bad_crv)()
+      .then(error(that), doTryImport(tvs.jwk_missing_crv))
+      .then(error(that), doTryImport(tvs.jwk_missing_x))
+      .then(error(that), doTryImport(tvs.jwk_missing_y))
+      .then(error(that), complete(that));
+  }
+);