Bug 1034854 - Add support for ECDSA to WebCrypto API. r=ttaubert, r=dkeeler, r=bz, a=lsblakk
authorRichard Barnes <rbarnes@mozilla.com>
Mon, 13 Oct 2014 23:19:00 +0200
changeset 234848 5de40c8a968fe2dcd29cac1908fc2b1a83af164e
parent 234847 ff89b2e7c1c0695df4f1d6ce337288ea4814a0d1
child 234849 526f7c0682e16ab194a16ef61f969ce5abf8ac92
push id611
push userraliiev@mozilla.com
push dateMon, 05 Jan 2015 23:23:16 +0000
treeherdermozilla-release@345cd3b9c445 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersttaubert, dkeeler, bz, lsblakk
bugs1034854
milestone35.0a2
Bug 1034854 - Add support for ECDSA to WebCrypto API. r=ttaubert, r=dkeeler, r=bz, a=lsblakk
config/external/nss/nss.def
dom/crypto/CryptoKey.cpp
dom/crypto/CryptoKey.h
dom/crypto/WebCryptoCommon.h
dom/crypto/WebCryptoTask.cpp
dom/crypto/test/test-vectors.js
dom/crypto/test/test_WebCrypto_ECDSA.html
dom/webidl/SubtleCrypto.webidl
--- a/config/external/nss/nss.def
+++ b/config/external/nss/nss.def
@@ -154,16 +154,18 @@ DER_EncodeTimeChoice_Util
 DER_Encode_Util
 DER_GeneralizedTimeToTime
 DER_GeneralizedTimeToTime_Util
 DER_GetInteger
 DER_GetInteger_Util
 DER_Lengths
 DER_SetUInteger
 DER_UTCTimeToTime_Util
+DSAU_DecodeDerSigToLen
+DSAU_EncodeDerSigWithLen
 DTLS_GetHandshakeTimeout
 DTLS_ImportFD
 HASH_Begin
 HASH_Create
 HASH_Destroy
 HASH_End
 HASH_GetHashObject
 HASH_GetType
@@ -302,16 +304,17 @@ PK11_DeleteTokenCertAndKey
 PK11_DeleteTokenPrivateKey
 PK11_DeleteTokenPublicKey
 PK11_DEREncodePublicKey
 PK11_Derive
 PK11_DeriveWithTemplate
 PK11_DestroyContext
 PK11_DestroyGenericObject
 PK11_DestroyMergeLog
+PK11_DestroyObject
 PK11_DestroyTokenObject
 PK11_DigestBegin
 PK11_DigestFinal
 PK11_DigestOp
 PK11_DoesMechanism
 PK11_Encrypt
 PK11_ExportDERPrivateKeyInfo
 PK11_ExtractKeyValue
@@ -363,16 +366,17 @@ PK11_GetSlotSeries
 PK11_GetTokenInfo
 PK11_GetTokenName
 PK11_HashBuf
 PK11_HasRootCerts
 PK11_ImportCert
 PK11_ImportCertForKey
 PK11_ImportCRL
 PK11_ImportDERPrivateKeyInfoAndReturnKey
+PK11_ImportPublicKey
 PK11_ImportSymKey
 PK11_InitPin
 PK11_IsDisabled
 PK11_IsFIPS
 PK11_IsFriendly
 PK11_IsHW
 PK11_IsInternal
 PK11_IsLoggedIn
@@ -665,11 +669,12 @@ SSL_VersionRangeGet
 SSL_VersionRangeSet
 SSL_VersionRangeSetDefault
 UTIL_SetForkState
 VFY_Begin
 VFY_CreateContext
 VFY_DestroyContext
 VFY_End
 VFY_Update
+VFY_VerifyData
 VFY_VerifyDataDirect
 VFY_VerifyDataWithAlgorithmID
 _SGN_VerifyPKCS1DigestInfo
--- a/dom/crypto/CryptoKey.cpp
+++ b/dom/crypto/CryptoKey.cpp
@@ -390,17 +390,22 @@ CryptoKey::PublicKeyFromSpki(CryptoBuffe
 
     SECStatus rv = SECITEM_CopyItem(spki->arena, &spki->algorithm.algorithm,
                                     &oidData->oid);
     if (rv != SECSuccess) {
       return nullptr;
     }
   }
 
-  return SECKEY_ExtractPublicKey(spki.get());
+  ScopedSECKEYPublicKey tmp(SECKEY_ExtractPublicKey(spki.get()));
+  if (!tmp.get() || !PublicKeyValid(tmp.get())) {
+    return nullptr;
+  }
+
+  return SECKEY_CopyPublicKey(tmp);
 }
 
 nsresult
 CryptoKey::PrivateKeyToPkcs8(SECKEYPrivateKey* aPrivKey,
                        CryptoBuffer& aRetVal,
                        const nsNSSShutDownPreventionLock& /*proofOfLock*/)
 {
   ScopedSECItem pkcs8Item(PK11_ExportDERPrivateKeyInfo(aPrivKey, nullptr));
@@ -838,16 +843,20 @@ CryptoKey::PublicKeyFromJwk(const JsonWe
 
     // 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 nullptr;
 }
 
 nsresult
 CryptoKey::PublicKeyToJwk(SECKEYPublicKey* aPubKey,
@@ -877,16 +886,36 @@ CryptoKey::PublicKeyToJwk(SECKEYPublicKe
       }
       return NS_OK;
     default:
       return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
   }
 }
 
 bool
+CryptoKey::PublicKeyValid(SECKEYPublicKey* aPubKey)
+{
+  ScopedPK11SlotInfo slot(PK11_GetInternalSlot());
+  if (!slot.get()) {
+    return false;
+  }
+
+  // This assumes that NSS checks the validity of a public key when
+  // it is imported into a PKCS#11 module, and returns CK_INVALID_HANDLE
+  // if it is invalid.
+  CK_OBJECT_HANDLE id = PK11_ImportPublicKey(slot, aPubKey, PR_FALSE);
+  if (id == CK_INVALID_HANDLE) {
+    return false;
+  }
+
+  SECStatus rv = PK11_DestroyObject(slot, id);
+  return (rv == SECSuccess);
+}
+
+bool
 CryptoKey::WriteStructuredClone(JSStructuredCloneWriter* aWriter) const
 {
   nsNSSShutDownPreventionLock locker;
   if (isAlreadyShutDown()) {
     return false;
   }
 
   // Write in five pieces
--- a/dom/crypto/CryptoKey.h
+++ b/dom/crypto/CryptoKey.h
@@ -166,16 +166,18 @@ public:
                                   const nsNSSShutDownPreventionLock& /*proofOfLock*/);
 
   static SECKEYPublicKey* PublicKeyFromJwk(const JsonWebKey& aKeyData,
                                            const nsNSSShutDownPreventionLock& /*proofOfLock*/);
   static nsresult PublicKeyToJwk(SECKEYPublicKey* aPrivKey,
                                  JsonWebKey& 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();
 
   nsRefPtr<nsIGlobalObject> mGlobal;
--- a/dom/crypto/WebCryptoCommon.h
+++ b/dom/crypto/WebCryptoCommon.h
@@ -22,16 +22,17 @@
 #define WEBCRYPTO_ALG_SHA256        "SHA-256"
 #define WEBCRYPTO_ALG_SHA384        "SHA-384"
 #define WEBCRYPTO_ALG_SHA512        "SHA-512"
 #define WEBCRYPTO_ALG_HMAC          "HMAC"
 #define WEBCRYPTO_ALG_PBKDF2        "PBKDF2"
 #define WEBCRYPTO_ALG_RSASSA_PKCS1  "RSASSA-PKCS1-v1_5"
 #define WEBCRYPTO_ALG_RSA_OAEP      "RSA-OAEP"
 #define WEBCRYPTO_ALG_ECDH          "ECDH"
+#define WEBCRYPTO_ALG_ECDSA         "ECDSA"
 
 // WebCrypto key formats
 #define WEBCRYPTO_KEY_FORMAT_RAW    "raw"
 #define WEBCRYPTO_KEY_FORMAT_PKCS8  "pkcs8"
 #define WEBCRYPTO_KEY_FORMAT_SPKI   "spki"
 #define WEBCRYPTO_KEY_FORMAT_JWK    "jwk"
 
 // WebCrypto key types
@@ -79,16 +80,19 @@
 #define JWK_ALG_RS1                 "RS1"      // RSASSA-PKCS1
 #define JWK_ALG_RS256               "RS256"
 #define JWK_ALG_RS384               "RS384"
 #define JWK_ALG_RS512               "RS512"
 #define JWK_ALG_RSA_OAEP            "RSA-OAEP" // RSA-OAEP
 #define JWK_ALG_RSA_OAEP_256        "RSA-OAEP-256"
 #define JWK_ALG_RSA_OAEP_384        "RSA-OAEP-384"
 #define JWK_ALG_RSA_OAEP_512        "RSA-OAEP-512"
+#define JWK_ALG_ECDSA_P_256         "ES256"
+#define JWK_ALG_ECDSA_P_384         "ES384"
+#define JWK_ALG_ECDSA_P_521         "ES521"
 
 // 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
 
@@ -220,16 +224,18 @@ NormalizeToken(const nsString& aName, ns
   } else if (NORMALIZED_EQUALS(aName, WEBCRYPTO_ALG_PBKDF2)) {
     aDest.AssignLiteral(WEBCRYPTO_ALG_PBKDF2);
   } else if (NORMALIZED_EQUALS(aName, WEBCRYPTO_ALG_RSASSA_PKCS1)) {
     aDest.AssignLiteral(WEBCRYPTO_ALG_RSASSA_PKCS1);
   } else if (NORMALIZED_EQUALS(aName, WEBCRYPTO_ALG_RSA_OAEP)) {
     aDest.AssignLiteral(WEBCRYPTO_ALG_RSA_OAEP);
   } else if (NORMALIZED_EQUALS(aName, WEBCRYPTO_ALG_ECDH)) {
     aDest.AssignLiteral(WEBCRYPTO_ALG_ECDH);
+  } else if (NORMALIZED_EQUALS(aName, WEBCRYPTO_ALG_ECDSA)) {
+    aDest.AssignLiteral(WEBCRYPTO_ALG_ECDSA);
   // Named curve values
   } else if (NORMALIZED_EQUALS(aName, WEBCRYPTO_NAMED_CURVE_P256)) {
     aDest.AssignLiteral(WEBCRYPTO_NAMED_CURVE_P256);
   } else if (NORMALIZED_EQUALS(aName, WEBCRYPTO_NAMED_CURVE_P384)) {
     aDest.AssignLiteral(WEBCRYPTO_NAMED_CURVE_P384);
   } else if (NORMALIZED_EQUALS(aName, WEBCRYPTO_NAMED_CURVE_P521)) {
     aDest.AssignLiteral(WEBCRYPTO_NAMED_CURVE_P521);
   } else {
--- a/dom/crypto/WebCryptoTask.cpp
+++ b/dom/crypto/WebCryptoTask.cpp
@@ -61,17 +61,18 @@ enum TelemetryAlgorithm {
   TA_SHA_1           = 14,
   TA_SHA_224         = 15,
   TA_SHA_256         = 16,
   TA_SHA_384         = 17,
   TA_SHA_512         = 18,
   // Later additions
   TA_AES_KW          = 19,
   TA_ECDH            = 20,
-  TA_PBKDF2          = 21
+  TA_PBKDF2          = 21,
+  TA_ECDSA           = 22,
 };
 
 // Convenience functions for extracting / converting information
 
 // OOM-safe CryptoBuffer initialization, suitable for constructors
 #define ATTEMPT_BUFFER_INIT(dst, src) \
   if (!dst.Assign(src)) { \
     mEarlyRv = NS_ERROR_DOM_UNKNOWN_ERR; \
@@ -777,20 +778,19 @@ public:
       case CKM_SHA_1:
         mMgfMechanism = CKG_MGF1_SHA1; break;
       case CKM_SHA256:
         mMgfMechanism = CKG_MGF1_SHA256; break;
       case CKM_SHA384:
         mMgfMechanism = CKG_MGF1_SHA384; break;
       case CKM_SHA512:
         mMgfMechanism = CKG_MGF1_SHA512; break;
-      default: {
+      default:
         mEarlyRv = NS_ERROR_DOM_NOT_SUPPORTED_ERR;
         return;
-      }
     }
   }
 
 private:
   CK_MECHANISM_TYPE mHashMechanism;
   CK_MECHANISM_TYPE mMgfMechanism;
   ScopedSECKEYPrivateKey mPrivKey;
   ScopedSECKEYPublicKey mPubKey;
@@ -950,59 +950,107 @@ private:
                                    mSignature.Length());
         equal = (cmp == 0);
       }
       mResultPromise->MaybeResolve(equal);
     }
   }
 };
 
-class RsassaPkcs1Task : public WebCryptoTask
+class AsymmetricSignVerifyTask : public WebCryptoTask
 {
 public:
-  RsassaPkcs1Task(JSContext* aCx, const ObjectOrString& aAlgorithm,
-                  CryptoKey& aKey,
-                  const CryptoOperationData& aSignature,
-                  const CryptoOperationData& aData,
-                  bool aSign)
+  AsymmetricSignVerifyTask(JSContext* aCx,
+                           const ObjectOrString& aAlgorithm,
+                           CryptoKey& aKey,
+                           const CryptoOperationData& aSignature,
+                           const CryptoOperationData& aData,
+                           bool aSign)
     : mOidTag(SEC_OID_UNKNOWN)
     , mPrivKey(aKey.GetPrivateKey())
     , mPubKey(aKey.GetPublicKey())
     , mSign(aSign)
     , mVerified(false)
+    , mEcdsa(false)
   {
-    Telemetry::Accumulate(Telemetry::WEBCRYPTO_ALG, TA_RSASSA_PKCS1);
-
-    CHECK_KEY_ALGORITHM(aKey.Algorithm(), WEBCRYPTO_ALG_RSASSA_PKCS1);
-
     ATTEMPT_BUFFER_INIT(mData, aData);
     if (!aSign) {
       ATTEMPT_BUFFER_INIT(mSignature, aSignature);
     }
 
-    // Look up the SECOidTag based on the KeyAlgorithm
-    // static_cast is safe because we only get here if the algorithm name
-    // is RSASSA-PKCS1-v1_5, and that only happens if we've constructed
-    // an RsaHashedKeyAlgorithm
-    CK_MECHANISM_TYPE mech;
-    mech = KeyAlgorithmProxy::GetMechanism(aKey.Algorithm().mRsa.mHash);
-
-    switch (mech) {
-      case CKM_SHA_1:
-        mOidTag = SEC_OID_PKCS1_SHA1_WITH_RSA_ENCRYPTION; break;
-      case CKM_SHA256:
-        mOidTag = SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION; break;
-      case CKM_SHA384:
-        mOidTag = SEC_OID_PKCS1_SHA384_WITH_RSA_ENCRYPTION; break;
-      case CKM_SHA512:
-        mOidTag = SEC_OID_PKCS1_SHA512_WITH_RSA_ENCRYPTION; break;
-      default: {
-        mEarlyRv = NS_ERROR_DOM_NOT_SUPPORTED_ERR;
+    nsString algName;
+    mEarlyRv = GetAlgorithmName(aCx, aAlgorithm, algName);
+    if (NS_FAILED(mEarlyRv)) {
+      return;
+    }
+
+    // Look up the SECOidTag
+    if (algName.EqualsLiteral(WEBCRYPTO_ALG_RSASSA_PKCS1)) {
+      mEcdsa = false;
+      Telemetry::Accumulate(Telemetry::WEBCRYPTO_ALG, TA_RSASSA_PKCS1);
+      CHECK_KEY_ALGORITHM(aKey.Algorithm(), WEBCRYPTO_ALG_RSASSA_PKCS1);
+
+      // For RSA, the hash name comes from the key algorithm
+      nsString hashName = aKey.Algorithm().mRsa.mHash.mName;
+      switch (MapAlgorithmNameToMechanism(hashName)) {
+        case CKM_SHA_1:
+          mOidTag = SEC_OID_PKCS1_SHA1_WITH_RSA_ENCRYPTION; break;
+        case CKM_SHA256:
+          mOidTag = SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION; break;
+        case CKM_SHA384:
+          mOidTag = SEC_OID_PKCS1_SHA384_WITH_RSA_ENCRYPTION; break;
+        case CKM_SHA512:
+          mOidTag = SEC_OID_PKCS1_SHA512_WITH_RSA_ENCRYPTION; break;
+        default:
+          mEarlyRv = NS_ERROR_DOM_NOT_SUPPORTED_ERR;
+          return;
+      }
+    } else if (algName.EqualsLiteral(WEBCRYPTO_ALG_ECDSA)) {
+      mEcdsa = true;
+      Telemetry::Accumulate(Telemetry::WEBCRYPTO_ALG, TA_ECDSA);
+      CHECK_KEY_ALGORITHM(aKey.Algorithm(), WEBCRYPTO_ALG_ECDSA);
+
+      // For ECDSA, the hash name comes from the algorithm parameter
+      RootedDictionary<EcdsaParams> params(aCx);
+      mEarlyRv = Coerce(aCx, params, aAlgorithm);
+      if (NS_FAILED(mEarlyRv)) {
+        mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR;
         return;
       }
+
+      nsString hashName;
+      mEarlyRv = GetAlgorithmName(aCx, params.mHash, hashName);
+      if (NS_FAILED(mEarlyRv)) {
+        mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR;
+        return;
+      }
+
+      CK_MECHANISM_TYPE hashMechanism = MapAlgorithmNameToMechanism(hashName);
+      if (hashMechanism == UNKNOWN_CK_MECHANISM) {
+        mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR;
+        return;
+      }
+
+      switch (hashMechanism) {
+        case CKM_SHA_1:
+          mOidTag = SEC_OID_ANSIX962_ECDSA_SHA1_SIGNATURE; break;
+        case CKM_SHA256:
+          mOidTag = SEC_OID_ANSIX962_ECDSA_SHA256_SIGNATURE; break;
+        case CKM_SHA384:
+          mOidTag = SEC_OID_ANSIX962_ECDSA_SHA384_SIGNATURE; break;
+        case CKM_SHA512:
+          mOidTag = SEC_OID_ANSIX962_ECDSA_SHA512_SIGNATURE; break;
+        default:
+          mEarlyRv = NS_ERROR_DOM_NOT_SUPPORTED_ERR;
+          return;
+      }
+    } else {
+      // This shouldn't happen; CreateSignVerifyTask shouldn't create
+      // one of these unless it's for the above algorithms.
+      MOZ_ASSERT(false);
     }
 
     // Check that we have the appropriate key
     if ((mSign && !mPrivKey) || (!mSign && !mPubKey)) {
       mEarlyRv = NS_ERROR_DOM_INVALID_ACCESS_ERR;
       return;
     }
   }
@@ -1010,61 +1058,71 @@ public:
 private:
   SECOidTag mOidTag;
   ScopedSECKEYPrivateKey mPrivKey;
   ScopedSECKEYPublicKey mPubKey;
   CryptoBuffer mSignature;
   CryptoBuffer mData;
   bool mSign;
   bool mVerified;
+  bool mEcdsa;
 
   virtual nsresult DoCrypto() MOZ_OVERRIDE
   {
     nsresult rv;
     if (mSign) {
       ScopedSECItem signature((SECItem*) PORT_Alloc(sizeof(SECItem)));
       ScopedSGNContext ctx(SGN_NewContext(mOidTag, mPrivKey));
-      if (!ctx) {
+      if (!signature.get() || !ctx.get()) {
         return NS_ERROR_DOM_OPERATION_ERR;
       }
 
-      rv = MapSECStatus(SGN_Begin(ctx));
-      NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_OPERATION_ERR);
-
-      rv = MapSECStatus(SGN_Update(ctx, mData.Elements(), mData.Length()));
-      NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_OPERATION_ERR);
-
-      rv = MapSECStatus(SGN_End(ctx, signature));
-      NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_OPERATION_ERR);
-
-      ATTEMPT_BUFFER_ASSIGN(mSignature, signature);
-    } else {
-      ScopedSECItem signature(mSignature.ToSECItem());
-      if (!signature) {
-        return NS_ERROR_DOM_UNKNOWN_ERR;
+      rv = MapSECStatus(SEC_SignData(signature, mData.Elements(),
+                                     mData.Length(), mPrivKey, mOidTag));
+
+      if (mEcdsa) {
+        // DER-decode the signature
+        int signatureLength = PK11_SignatureLen(mPrivKey);
+        ScopedSECItem rawSignature(DSAU_DecodeDerSigToLen(signature.get(),
+                                                          signatureLength));
+        if (!rawSignature.get()) {
+          return NS_ERROR_DOM_OPERATION_ERR;
+        }
+
+        ATTEMPT_BUFFER_ASSIGN(mSignature, rawSignature);
+      } else {
+        ATTEMPT_BUFFER_ASSIGN(mSignature, signature);
       }
 
-      ScopedVFYContext ctx(VFY_CreateContext(mPubKey, signature,
-                                             mOidTag, nullptr));
-      if (!ctx) {
-        int err = PORT_GetError();
-        if (err == SEC_ERROR_BAD_SIGNATURE) {
-          mVerified = false;
-          return NS_OK;
+    } else {
+      ScopedSECItem signature;
+
+      if (mEcdsa) {
+        // DER-encode the signature
+        ScopedSECItem rawSignature(mSignature.ToSECItem());
+        if (!rawSignature.get()) {
+          return NS_ERROR_DOM_UNKNOWN_ERR;
         }
-        return NS_ERROR_DOM_OPERATION_ERR;
+
+        signature = (SECItem*) PORT_Alloc(sizeof(SECItem));
+        if (!signature.get()) {
+          return NS_ERROR_DOM_UNKNOWN_ERR;
+        }
+        rv = MapSECStatus(DSAU_EncodeDerSigWithLen(signature, rawSignature,
+                                                   rawSignature->len));
+        NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_OPERATION_ERR);
+      } else {
+        signature = mSignature.ToSECItem();
+        if (!signature) {
+          return NS_ERROR_DOM_UNKNOWN_ERR;
+        }
       }
 
-      rv = MapSECStatus(VFY_Begin(ctx));
-      NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_OPERATION_ERR);
-
-      rv = MapSECStatus(VFY_Update(ctx, mData.Elements(), mData.Length()));
-      NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_OPERATION_ERR);
-
-      rv = MapSECStatus(VFY_End(ctx));
+      rv = MapSECStatus(VFY_VerifyData(mData.Elements(), mData.Length(),
+                                       mPubKey, signature, mOidTag, nullptr));
       mVerified = NS_SUCCEEDED(rv);
     }
 
     return NS_OK;
   }
 
   virtual void Resolve() MOZ_OVERRIDE
   {
@@ -1656,21 +1714,32 @@ private:
       }
     }
 
     return NS_OK;
   }
 
   virtual nsresult AfterCrypto() MOZ_OVERRIDE
   {
+    uint32_t privateAllowedUsages = 0, publicAllowedUsages = 0;
+    if (mAlgName.EqualsLiteral(WEBCRYPTO_ALG_ECDH)) {
+      privateAllowedUsages = CryptoKey::DERIVEBITS | CryptoKey::DERIVEKEY;
+      publicAllowedUsages = CryptoKey::DERIVEBITS | CryptoKey::DERIVEKEY;
+    } else if (mAlgName.EqualsLiteral(WEBCRYPTO_ALG_ECDSA)) {
+      privateAllowedUsages = CryptoKey::SIGN;
+      publicAllowedUsages = CryptoKey::VERIFY;
+    }
+
     // Check permissions for the requested operation
-    if (mKey->GetKeyType() == CryptoKey::PRIVATE &&
-        mKey->HasUsageOtherThan(CryptoKey::DERIVEBITS | CryptoKey::DERIVEKEY)) {
-      return NS_ERROR_DOM_DATA_ERR;
-    }
+    if ((mKey->GetKeyType() == CryptoKey::PRIVATE &&
+         mKey->HasUsageOtherThan(privateAllowedUsages)) ||
+        (mKey->GetKeyType() == CryptoKey::PUBLIC &&
+         mKey->HasUsageOtherThan(publicAllowedUsages))) {
+       return NS_ERROR_DOM_DATA_ERR;
+     }
 
     mKey->Algorithm().MakeEc(mAlgName, mNamedCurve);
 
     if (mDataIsJwk && !JwkCompatible(mJwk, mKey)) {
       return NS_ERROR_DOM_DATA_ERR;
     }
 
     return NS_OK;
@@ -1986,17 +2055,18 @@ public:
 
       // Set up params struct
       mRsaParams.keySizeInBits = modulusLength;
       bool converted = publicExponent.GetBigIntValue(mRsaParams.pe);
       if (!converted) {
         mEarlyRv = NS_ERROR_DOM_INVALID_ACCESS_ERR;
         return;
       }
-    } else if (algName.EqualsLiteral(WEBCRYPTO_ALG_ECDH)) {
+    } else if (algName.EqualsLiteral(WEBCRYPTO_ALG_ECDH) ||
+               algName.EqualsLiteral(WEBCRYPTO_ALG_ECDSA)) {
       RootedDictionary<EcKeyGenParams> params(aCx);
       mEarlyRv = Coerce(aCx, params, aAlgorithm);
       if (NS_FAILED(mEarlyRv)) {
         mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR;
         return;
       }
 
       if (!NormalizeToken(params.mNamedCurve, mNamedCurve)) {
@@ -2009,17 +2079,18 @@ public:
       mKeyPair.mPrivateKey.get()->Algorithm().MakeEc(algName, mNamedCurve);
       mMechanism = CKM_EC_KEY_PAIR_GEN;
     } else {
       mEarlyRv = NS_ERROR_DOM_NOT_SUPPORTED_ERR;
       return;
     }
 
     // Set key usages.
-    if (algName.EqualsLiteral(WEBCRYPTO_ALG_RSASSA_PKCS1)) {
+    if (algName.EqualsLiteral(WEBCRYPTO_ALG_RSASSA_PKCS1) ||
+        algName.EqualsLiteral(WEBCRYPTO_ALG_ECDSA)) {
       privateAllowedUsages = CryptoKey::SIGN;
       publicAllowedUsages = CryptoKey::VERIFY;
     } else if (algName.EqualsLiteral(WEBCRYPTO_ALG_RSA_OAEP)) {
       privateAllowedUsages = CryptoKey::DECRYPT | CryptoKey::UNWRAPKEY;
       publicAllowedUsages = CryptoKey::ENCRYPT | CryptoKey::WRAPKEY;
     } else if (algName.EqualsLiteral(WEBCRYPTO_ALG_ECDH)) {
       privateAllowedUsages = CryptoKey::DERIVEKEY | CryptoKey::DERIVEBITS;
       publicAllowedUsages = 0;
@@ -2171,20 +2242,19 @@ public:
     }
 
     // Check the given hash algorithm.
     switch (MapAlgorithmNameToMechanism(hashName)) {
       case CKM_SHA_1: mHashOidTag = SEC_OID_HMAC_SHA1; break;
       case CKM_SHA256: mHashOidTag = SEC_OID_HMAC_SHA256; break;
       case CKM_SHA384: mHashOidTag = SEC_OID_HMAC_SHA384; break;
       case CKM_SHA512: mHashOidTag = SEC_OID_HMAC_SHA512; break;
-      default: {
+      default:
         mEarlyRv = NS_ERROR_DOM_NOT_SUPPORTED_ERR;
         return;
-      }
     }
 
     ATTEMPT_BUFFER_INIT(mSalt, params.mSalt)
     mLength = aLength >> 3; // bits to bytes
     mIterations = params.mIterations;
   }
 
 private:
@@ -2539,18 +2609,20 @@ WebCryptoTask::CreateSignVerifyTask(JSCo
   nsString algName;
   nsresult rv = GetAlgorithmName(aCx, aAlgorithm, algName);
   if (NS_FAILED(rv)) {
     return new FailureTask(rv);
   }
 
   if (algName.EqualsLiteral(WEBCRYPTO_ALG_HMAC)) {
     return new HmacTask(aCx, aAlgorithm, aKey, aSignature, aData, aSign);
-  } else if (algName.EqualsLiteral(WEBCRYPTO_ALG_RSASSA_PKCS1)) {
-    return new RsassaPkcs1Task(aCx, aAlgorithm, aKey, aSignature, aData, aSign);
+  } else if (algName.EqualsLiteral(WEBCRYPTO_ALG_RSASSA_PKCS1) ||
+             algName.EqualsLiteral(WEBCRYPTO_ALG_ECDSA)) {
+    return new AsymmetricSignVerifyTask(aCx, aAlgorithm, aKey, aSignature,
+                                        aData, aSign);
   }
 
   return new FailureTask(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
 }
 
 WebCryptoTask*
 WebCryptoTask::CreateDigestTask(JSContext* aCx,
                                 const ObjectOrString& aAlgorithm,
@@ -2613,17 +2685,18 @@ WebCryptoTask::CreateImportKeyTask(JSCon
       algName.EqualsLiteral(WEBCRYPTO_ALG_PBKDF2) ||
       algName.EqualsLiteral(WEBCRYPTO_ALG_HMAC)) {
     return new ImportSymmetricKeyTask(aCx, aFormat, aKeyData, aAlgorithm,
                                       aExtractable, aKeyUsages);
   } else if (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)) {
+  } else if (algName.EqualsLiteral(WEBCRYPTO_ALG_ECDH) ||
+             algName.EqualsLiteral(WEBCRYPTO_ALG_ECDSA)) {
     return new ImportEcKeyTask(aCx, aFormat, aKeyData, aAlgorithm,
                                aExtractable, aKeyUsages);
   } else {
     return new FailureTask(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
   }
 }
 
 WebCryptoTask*
@@ -2689,17 +2762,18 @@ WebCryptoTask::CreateGenerateKeyTask(JSC
   if (algName.EqualsASCII(WEBCRYPTO_ALG_AES_CBC) ||
       algName.EqualsASCII(WEBCRYPTO_ALG_AES_CTR) ||
       algName.EqualsASCII(WEBCRYPTO_ALG_AES_GCM) ||
       algName.EqualsASCII(WEBCRYPTO_ALG_AES_KW) ||
       algName.EqualsASCII(WEBCRYPTO_ALG_HMAC)) {
     return new GenerateSymmetricKeyTask(aCx, aAlgorithm, aExtractable, aKeyUsages);
   } else if (algName.EqualsASCII(WEBCRYPTO_ALG_RSASSA_PKCS1) ||
              algName.EqualsASCII(WEBCRYPTO_ALG_RSA_OAEP) ||
-             algName.EqualsASCII(WEBCRYPTO_ALG_ECDH)) {
+             algName.EqualsASCII(WEBCRYPTO_ALG_ECDH) ||
+             algName.EqualsASCII(WEBCRYPTO_ALG_ECDSA)) {
     return new GenerateAsymmetricKeyTask(aCx, aAlgorithm, aExtractable, aKeyUsages);
   } else {
     return new FailureTask(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
   }
 }
 
 WebCryptoTask*
 WebCryptoTask::CreateDeriveKeyTask(JSContext* aCx,
--- a/dom/crypto/test/test-vectors.js
+++ b/dom/crypto/test/test-vectors.js
@@ -603,9 +603,59 @@ tv = {
 
     // The Y coordinate is missing.
     jwk_missing_y: {
       kty: "EC",
       crv: "P-256",
       x: "XOe4bjsyZgQD5jcS7wmY3q4QJ_rsPBvp92-TTf61jpg",
     }
   },
+
+  // NIST ECDSA test vectors
+  // http://csrc.nist.gov/groups/STM/cavp/index.html
+  ecdsa_verify: {
+    pub_jwk: {
+      "kty": "EC",
+      "crv": "P-521",
+
+      // 0061387fd6b95914e885f912edfbb5fb274655027f216c4091ca83e19336740fd8
+      // 1aedfe047f51b42bdf68161121013e0d55b117a14e4303f926c8debb77a7fdaad1
+      "x": "AGE4f9a5WRTohfkS7fu1-ydGVQJ_IWxAkcqD4ZM2dA_Y" +
+           "Gu3-BH9RtCvfaBYRIQE-DVWxF6FOQwP5Jsjeu3en_arR",
+      // 00e7d0c75c38626e895ca21526b9f9fdf84dcecb93f2b233390550d2b1463b7ee3
+      // f58df7346435ff0434199583c97c665a97f12f706f2357da4b40288def888e59e6
+      "y": "AOfQx1w4Ym6JXKIVJrn5_fhNzsuT8rIzOQVQ0rFGO37j" +
+           "9Y33NGQ1_wQ0GZWDyXxmWpfxL3BvI1faS0Aoje-Ijlnm",
+    },
+
+    "data": util.hex2abv(
+            "9ecd500c60e701404922e58ab20cc002651fdee7cbc9336adda33e4c1088fab1" +
+            "964ecb7904dc6856865d6c8e15041ccf2d5ac302e99d346ff2f686531d255216" +
+            "78d4fd3f76bbf2c893d246cb4d7693792fe18172108146853103a51f824acc62" +
+            "1cb7311d2463c3361ea707254f2b052bc22cb8012873dcbb95bf1a5cc53ab89f"
+          ),
+    "sig": util.hex2abv(
+            "004de826ea704ad10bc0f7538af8a3843f284f55c8b946af9235af5af74f2b76e0" +
+            "99e4bc72fd79d28a380f8d4b4c919ac290d248c37983ba05aea42e2dd79fdd33e8" +
+            "0087488c859a96fea266ea13bf6d114c429b163be97a57559086edb64aed4a1859" +
+            "4b46fb9efc7fd25d8b2de8f09ca0587f54bd287299f47b2ff124aac566e8ee3b43"
+          ),
+
+    // Same as "sig", but with the last few octets set to 0
+    "sig_tampered": util.hex2abv(
+            "004de826ea704ad10bc0f7538af8a3843f284f55c8b946af9235af5af74f2b76e0" +
+            "99e4bc72fd79d28a380f8d4b4c919ac290d248c37983ba05aea42e2dd79fdd33e8" +
+            "0087488c859a96fea266ea13bf6d114c429b163be97a57559086edb64aed4a1859" +
+            "4b46fb9efc7fd25d8b2de8f09ca0587f54bd287299f47b2ff124aac56600000000"
+          )
+  },
+
+  ecdsa_bad: {
+    pub_jwk: {
+      "kty": "EC",
+      "crv": "P-521",
+      "x": "BhOH_WuVkU6IX5Eu37tfsnRlUCfyFsQJHKg-GTNnQP2B" +
+           "rt_gR_UbQr32gWESEBPg1VsRehTkMD-SbI3rt3p_2q0B",
+      "y": "AUNouOdGgHsraPNhXNeNdhpGTd15GPyN9R0iWWL98ePc" +
+           "JD4mUQD/DsEzNZ4zLkTdSa/Y5fOP6GEzVzQy0zwC+goD"
+    }
+  }
 }
new file mode 100644
--- /dev/null
+++ b/dom/crypto/test/test_WebCrypto_ECDSA.html
@@ -0,0 +1,162 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+<title>WebCrypto Test Suite</title>
+<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
+<link rel="stylesheet" href="./test_WebCrypto.css"/>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+
+<!-- Utilities for manipulating ABVs -->
+<script src="util.js"></script>
+
+<!-- A simple wrapper around IndexedDB -->
+<script src="simpledb.js"></script>
+
+<!-- Test vectors drawn from the literature -->
+<script src="./test-vectors.js"></script>
+
+<!-- General testing framework -->
+<script src="./test-array.js"></script>
+
+<script>/*<![CDATA[*/
+"use strict";
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+  "Generate an ECDSA key for named curve P-256",
+  function() {
+    var that = this;
+    var alg = { name: "ECDSA", namedCurve: "P-256" };
+    crypto.subtle.generateKey(alg, false, ["sign", "verify"]).then(
+      complete(that, function(x) {
+        return exists(x.publicKey) &&
+               (x.publicKey.algorithm.name == alg.name) &&
+               (x.publicKey.algorithm.namedCurve == alg.namedCurve) &&
+               (x.publicKey.type == "public") &&
+               x.publicKey.extractable &&
+               (x.publicKey.usages.length == 1) &&
+               (x.publicKey.usages[0] == "verify") &&
+               exists(x.privateKey) &&
+               (x.privateKey.algorithm.name == alg.name) &&
+               (x.privateKey.algorithm.namedCurve == alg.namedCurve) &&
+               (x.privateKey.type == "private") &&
+               !x.privateKey.extractable &&
+               (x.privateKey.usages.length == 1) &&
+               (x.privateKey.usages[0] == "sign")
+      }),
+      error(that)
+    );
+  }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+  "ECDSA JWK import and verify a known-good signature",
+  function() {
+    var that = this;
+    var alg = { name: "ECDSA", namedCurve: "P-256", hash: "SHA-256" };
+
+    function doVerify(x) {
+      return crypto.subtle.verify(alg, x, tv.ecdsa_verify.sig, tv.ecdsa_verify.data);
+    }
+
+    crypto.subtle.importKey("jwk", tv.ecdsa_verify.pub_jwk, alg, true, ["verify"])
+      .then(doVerify)
+      .then(complete(that), error(that))
+  }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+  "ECDSA JWK import and reject a known-bad signature",
+  function() {
+    var that = this;
+    var alg = { name: "ECDSA", namedCurve: "P-256", hash: "SHA-256" };
+
+    function doVerify(x) {
+      return crypto.subtle.verify(alg, x, tv.ecdsa_verify.sig_tampered,
+                                          tv.ecdsa_verify.data);
+    }
+
+    crypto.subtle.importKey("jwk", tv.ecdsa_verify.pub_jwk, alg, true, ["verify"])
+      .then(doVerify)
+      .then(complete(that, x => !x), error(that))
+  }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+  "ECDSA sign/verify round-trip",
+  function() {
+    var that = this;
+    var alg = { name: "ECDSA", namedCurve: "P-521", hash: "SHA-512" };
+    var pubKey;
+
+
+    function doSign(keyPair) {
+      pubKey = keyPair.publicKey;
+      return crypto.subtle.sign(alg, keyPair.privateKey, tv.ecdsa_verify.data);
+    }
+    function doVerify(sig) {
+      return crypto.subtle.verify(alg, pubKey, sig, tv.ecdsa_verify.data);
+    }
+
+    crypto.subtle.generateKey(alg, true, ["sign", "verify"])
+      .then(doSign)
+      .then(doVerify)
+      .then(complete(that), error(that))
+  }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+  "Verify that ECDSA import fails with a known-bad public key",
+  function() {
+    var that = this;
+    var alg = { name: "ECDSA", namedCurve: "P-256", hash: "SHA-256" };
+
+    function doVerify(x) {
+      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))
+  }
+);
+
+/*]]>*/</script>
+</head>
+
+<body>
+
+<div id="content">
+	<div id="head">
+		<b>Web</b>Crypto<br>
+	</div>
+
+    <div id="start" onclick="start();">RUN ALL</div>
+
+    <div id="resultDiv" class="content">
+    Summary:
+    <span class="pass"><span id="passN">0</span> passed, </span>
+    <span class="fail"><span id="failN">0</span> failed, </span>
+    <span class="pending"><span id="pendingN">0</span> pending.</span>
+    <br/>
+    <br/>
+
+    <table id="results">
+        <tr>
+            <th>Test</th>
+            <th>Result</th>
+            <th>Time</th>
+        </tr>
+    </table>
+
+    </div>
+
+    <div id="foot"></div>
+</div>
+
+</body>
+</html>
--- a/dom/webidl/SubtleCrypto.webidl
+++ b/dom/webidl/SubtleCrypto.webidl
@@ -82,16 +82,19 @@ dictionary AesDerivedKeyParams : Algorit
 dictionary HmacDerivedKeyParams : HmacImportParams {
   [EnforceRange] unsigned long length;
 };
 
 dictionary EcdhKeyDeriveParams : Algorithm {
   required CryptoKey public;
 };
 
+dictionary EcdsaParams : Algorithm {
+  required AlgorithmIdentifier hash;
+};
 
 /***** 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;