Bug 1034856 - Implement deriveBits() for DH r=rbarnes,smaug
authorTim Taubert <ttaubert@mozilla.com>
Thu, 21 Aug 2014 17:51:51 +0200
changeset 211013 0100d140637ae35d23a0d0058592eecb9e41f509
parent 211012 f5557f9935fbc126c02a6478f39ba5dd64ee07b8
child 211014 9527980656c9784e2604464dd8f21100af99c722
push id1
push userroot
push dateMon, 20 Oct 2014 17:29:22 +0000
reviewersrbarnes, smaug
bugs1034856
milestone36.0a1
Bug 1034856 - Implement deriveBits() for DH r=rbarnes,smaug
dom/crypto/WebCryptoTask.cpp
dom/crypto/test/test-vectors.js
dom/crypto/test/test_WebCrypto_DH.html
dom/webidl/SubtleCrypto.webidl
--- a/dom/crypto/WebCryptoTask.cpp
+++ b/dom/crypto/WebCryptoTask.cpp
@@ -2446,19 +2446,120 @@ public:
 
 private:
   size_t mLength;
   ScopedSECKEYPrivateKey mPrivKey;
   ScopedSECKEYPublicKey mPubKey;
 
   virtual nsresult DoCrypto() MOZ_OVERRIDE
   {
+    // CKM_SHA512_HMAC and CKA_SIGN are key type and usage attributes of the
+    // derived symmetric key and don't matter because we ignore them anyway.
     ScopedPK11SymKey symKey(PK11_PubDeriveWithKDF(
       mPrivKey, mPubKey, PR_FALSE, nullptr, nullptr, CKM_ECDH1_DERIVE,
-      CKM_CONCATENATE_DATA_AND_BASE, CKA_DERIVE, 0, CKD_NULL, nullptr, nullptr));
+      CKM_SHA512_HMAC, CKA_SIGN, 0, CKD_NULL, nullptr, nullptr));
+
+    if (!symKey.get()) {
+      return NS_ERROR_DOM_OPERATION_ERR;
+    }
+
+    nsresult rv = MapSECStatus(PK11_ExtractKeyValue(symKey));
+    if (NS_FAILED(rv)) {
+      return NS_ERROR_DOM_OPERATION_ERR;
+    }
+
+    // This doesn't leak, because the SECItem* returned by PK11_GetKeyData
+    // just refers to a buffer managed by symKey. The assignment copies the
+    // data, so mResult manages one copy, while symKey manages another.
+    ATTEMPT_BUFFER_ASSIGN(mResult, PK11_GetKeyData(symKey));
+
+    if (mLength > mResult.Length()) {
+      return NS_ERROR_DOM_DATA_ERR;
+    }
+
+    if (!mResult.SetLength(mLength)) {
+      return NS_ERROR_DOM_UNKNOWN_ERR;
+    }
+
+    return NS_OK;
+  }
+};
+
+class DeriveDhBitsTask : public ReturnArrayBufferViewTask
+{
+public:
+  DeriveDhBitsTask(JSContext* aCx,
+      const ObjectOrString& aAlgorithm, CryptoKey& aKey, uint32_t aLength)
+    : mLength(aLength),
+      mPrivKey(aKey.GetPrivateKey())
+  {
+    Init(aCx, aAlgorithm, aKey);
+  }
+
+  DeriveDhBitsTask(JSContext* aCx, const ObjectOrString& aAlgorithm,
+                   CryptoKey& aKey, const ObjectOrString& aTargetAlgorithm)
+    : mPrivKey(aKey.GetPrivateKey())
+  {
+    mEarlyRv = GetKeyLengthForAlgorithm(aCx, aTargetAlgorithm, mLength);
+    if (NS_SUCCEEDED(mEarlyRv)) {
+      Init(aCx, aAlgorithm, aKey);
+    }
+  }
+
+  void Init(JSContext* aCx, const ObjectOrString& aAlgorithm, CryptoKey& aKey)
+  {
+    CHECK_KEY_ALGORITHM(aKey.Algorithm(), WEBCRYPTO_ALG_DH);
+
+    // Check that we have a private key.
+    if (!mPrivKey) {
+      mEarlyRv = NS_ERROR_DOM_INVALID_ACCESS_ERR;
+      return;
+    }
+
+    mLength = mLength >> 3; // bits to bytes
+
+    // Retrieve the peer's public key.
+    RootedDictionary<DhKeyDeriveParams> params(aCx);
+    mEarlyRv = Coerce(aCx, params, aAlgorithm);
+    if (NS_FAILED(mEarlyRv)) {
+      mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR;
+      return;
+    }
+
+    CryptoKey* publicKey = params.mPublic;
+    mPubKey = publicKey->GetPublicKey();
+    if (!mPubKey) {
+      mEarlyRv = NS_ERROR_DOM_INVALID_ACCESS_ERR;
+      return;
+    }
+
+    KeyAlgorithmProxy alg1 = publicKey->Algorithm();
+    CHECK_KEY_ALGORITHM(alg1, WEBCRYPTO_ALG_DH);
+
+    // Both keys must use the same prime and generator.
+    KeyAlgorithmProxy alg2 = aKey.Algorithm();
+    if (alg1.mDh.mPrime != alg2.mDh.mPrime ||
+        alg1.mDh.mGenerator != alg2.mDh.mGenerator) {
+      mEarlyRv = NS_ERROR_DOM_DATA_ERR;
+      return;
+    }
+  }
+
+private:
+  size_t mLength;
+  ScopedSECKEYPrivateKey mPrivKey;
+  ScopedSECKEYPublicKey mPubKey;
+
+  virtual nsresult DoCrypto() MOZ_OVERRIDE
+  {
+    // CKM_SHA512_HMAC and CKA_SIGN are key type and usage attributes of the
+    // derived symmetric key and don't matter because we ignore them anyway.
+    ScopedPK11SymKey symKey(PK11_PubDeriveWithKDF(
+      mPrivKey, mPubKey, PR_FALSE, nullptr, nullptr, CKM_DH_PKCS_DERIVE,
+      CKM_SHA512_HMAC, CKA_SIGN, 0, CKD_NULL, nullptr, nullptr));
 
     if (!symKey.get()) {
       return NS_ERROR_DOM_OPERATION_ERR;
     }
 
     nsresult rv = MapSECStatus(PK11_ExtractKeyValue(symKey));
     if (NS_FAILED(rv)) {
       return NS_ERROR_DOM_OPERATION_ERR;
@@ -2869,16 +2970,20 @@ WebCryptoTask::CreateDeriveBitsTask(JSCo
   if (algName.EqualsASCII(WEBCRYPTO_ALG_PBKDF2)) {
     return new DerivePbkdfBitsTask(aCx, aAlgorithm, aKey, aLength);
   }
 
   if (algName.EqualsASCII(WEBCRYPTO_ALG_ECDH)) {
     return new DeriveEcdhBitsTask(aCx, aAlgorithm, aKey, aLength);
   }
 
+  if (algName.EqualsASCII(WEBCRYPTO_ALG_DH)) {
+    return new DeriveDhBitsTask(aCx, aAlgorithm, aKey, aLength);
+  }
+
   return new FailureTask(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
 }
 
 WebCryptoTask*
 WebCryptoTask::CreateWrapKeyTask(JSContext* aCx,
                                  const nsAString& aFormat,
                                  CryptoKey& aKey,
                                  CryptoKey& aWrappingKey,
--- a/dom/crypto/test/test-vectors.js
+++ b/dom/crypto/test/test-vectors.js
@@ -660,11 +660,18 @@ tv = {
   },
 
   // RFC 2409 <http://tools.ietf.org/html/rfc2409#section-6.1>
   dh: {
     prime: util.hex2abv(
       "ffffffffffffffffc90fdaa22168c234c4c6628b80dc1cd129024e088a67cc74" +
       "020bbea63b139b22514a08798e3404ddef9519b3cd3a431b302b0a6df25f1437" +
       "4fe1356d6d51c245e485b576625e7ec6f44c42e9a63a3620ffffffffffffffff"
+    ),
+
+    prime2: util.hex2abv(
+      "ffffffffffffffffc90fdaa22168c234c4c6628b80dc1cd129024e088a67cc74" +
+      "020bbea63b139b22514a08798e3404ddef9519b3cd3a431b302b0a6df25f1437" +
+      "4fe1356d6d51c245e485b576625e7ec6f44c42e9a637ed6b0bff5cb6f406b7ed" +
+      "ee386bfb5a899fa5ae9f24117c4b1fe649286651ece65381ffffffffffffffff"
     )
   },
 }
--- a/dom/crypto/test/test_WebCrypto_DH.html
+++ b/dom/crypto/test/test_WebCrypto_DH.html
@@ -50,16 +50,138 @@ TestArray.addTest(
                (x.privateKey.usages.length == 2) &&
                (x.privateKey.usages[0] == "deriveKey") &&
                (x.privateKey.usages[1] == "deriveBits");
       }),
       error(that)
     );
   }
 );
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+  "Derive bits from a DH key",
+  function() {
+    var that = this;
+    var alg = {
+      name: "DH",
+      prime: tv.dh.prime,
+      generator: new Uint8Array([0x02])
+    };
+
+    function doDerive(x) {
+      var alg = {
+        name: "DH",
+        public: x.publicKey
+      };
+      return crypto.subtle.deriveBits(alg, x.privateKey, 128);
+    }
+
+    crypto.subtle.generateKey(alg, false, ["deriveBits"])
+      .then(doDerive, error(that))
+      .then(complete(that, function (x) {
+        return x.byteLength == 16;
+      }), error(that));
+  }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+  "Test that DH deriveBits() fails when the public key is not a DH key",
+  function() {
+    var that = this;
+    var pubKey, privKey;
+    function setPub(x) { pubKey = x.publicKey; }
+    function setPriv(x) { privKey = x.privateKey; }
+
+    function doGenerateDH() {
+      var alg = {
+        name: "DH",
+        prime: tv.dh.prime,
+        generator: new Uint8Array([0x02])
+      };
+      return crypto.subtle.generateKey(alg, false, ["deriveBits"]);
+    }
+
+    function doGenerateRSA() {
+      var alg = {
+        name: "RSA-OAEP",
+        hash: "SHA-256",
+        modulusLength: 2048,
+        publicExponent: new Uint8Array([0x01, 0x00, 0x01])
+      };
+      return crypto.subtle.generateKey(alg, false, ["encrypt"])
+    }
+
+    function doDerive() {
+      var alg = {name: "DH", public: pubKey};
+      return crypto.subtle.deriveBits(alg, privKey, 128);
+    }
+
+    doGenerateDH()
+      .then(setPriv, error(that))
+      .then(doGenerateRSA, error(that))
+      .then(setPub, error(that))
+      .then(doDerive, error(that))
+      .then(error(that), complete(that));
+  }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+  "Test that DH deriveBits() fails when the given keys' primes or bases don't match",
+  function() {
+    var that = this;
+    var pubKey, privKey;
+    function setPub(x) { pubKey = x.publicKey; }
+    function setPriv(x) { privKey = x.privateKey; }
+
+    function doGenerateDH() {
+      var alg = {
+        name: "DH",
+        prime: tv.dh.prime,
+        generator: new Uint8Array([0x02])
+      };
+      return crypto.subtle.generateKey(alg, false, ["deriveBits"]);
+    }
+
+    function doGenerateDH2() {
+      var alg = {
+        name: "DH",
+        prime: tv.dh.prime2,
+        generator: new Uint8Array([0x02])
+      };
+      return crypto.subtle.generateKey(alg, false, ["deriveBits"]);
+    }
+
+    function doGenerateDH3() {
+      var alg = {
+        name: "DH",
+        prime: tv.dh.prime,
+        generator: new Uint8Array([0x03])
+      };
+      return crypto.subtle.generateKey(alg, false, ["deriveBits"]);
+    }
+
+    function doDerive() {
+      var alg = {name: "DH", public: pubKey};
+      return crypto.subtle.deriveBits(alg, privKey, 128);
+    }
+
+    doGenerateDH()
+      .then(setPriv, error(that))
+      .then(doGenerateDH2, error(that))
+      .then(setPub, error(that))
+      .then(doDerive, error(that))
+      .then(error(that), doGenerateDH3)
+      .then(setPub, error(that))
+      .then(doDerive, error(that))
+      .then(error(that), complete(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
@@ -82,16 +82,20 @@ dictionary AesDerivedKeyParams : Algorit
 dictionary HmacDerivedKeyParams : HmacImportParams {
   [EnforceRange] unsigned long length;
 };
 
 dictionary EcdhKeyDeriveParams : Algorithm {
   required CryptoKey public;
 };
 
+dictionary DhKeyDeriveParams : 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