Bug 1034855 - Implement deriveBits() for ECDH r=rbarnes,smaug
authorTim Taubert <ttaubert@mozilla.com>
Tue, 29 Jul 2014 11:11:26 +0200
changeset 219795 2b61628b375c47e3ae20b263b3607f561ba25ece
parent 219794 8305c22206c290f1f05acaee255d0e36a89dd91a
child 219796 9e38ab4b2b00208dfe64299644fe979e95c35029
push id3979
push userraliiev@mozilla.com
push dateMon, 13 Oct 2014 16:35:44 +0000
treeherdermozilla-beta@30f2cc610691 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersrbarnes, smaug
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 deriveBits() for ECDH r=rbarnes,smaug
dom/crypto/WebCryptoTask.cpp
dom/crypto/test/tests.js
dom/webidl/SubtleCrypto.webidl
--- a/dom/crypto/WebCryptoTask.cpp
+++ b/dom/crypto/WebCryptoTask.cpp
@@ -2278,16 +2278,126 @@ private:
   {
     if (mTask && !mResolved) {
       mTask->Skip();
     }
     mTask = nullptr;
   }
 };
 
+class DeriveEcdhBitsTask : public ReturnArrayBufferViewTask
+{
+public:
+  DeriveEcdhBitsTask(JSContext* aCx,
+      const ObjectOrString& aAlgorithm, CryptoKey& aKey, uint32_t aLength)
+    : mLength(aLength),
+      mPrivKey(aKey.GetPrivateKey())
+  {
+    Init(aCx, aAlgorithm, aKey);
+  }
+
+  DeriveEcdhBitsTask(JSContext* aCx, const ObjectOrString& aAlgorithm,
+                     CryptoKey& aKey, const ObjectOrString& aTargetAlgorithm)
+    : mPrivKey(aKey.GetPrivateKey())
+  {
+    mEarlyRv = GetKeySizeForAlgorithm(aCx, aTargetAlgorithm, mLength);
+    if (NS_SUCCEEDED(mEarlyRv)) {
+      Init(aCx, aAlgorithm, aKey);
+    }
+  }
+
+  void Init(JSContext* aCx, const ObjectOrString& aAlgorithm, CryptoKey& aKey)
+  {
+    // Check that we have a private key.
+    if (!mPrivKey) {
+      mEarlyRv = NS_ERROR_DOM_INVALID_ACCESS_ERR;
+      return;
+    }
+
+    // Length must be a multiple of 8 bigger than zero.
+    if (mLength == 0 || mLength % 8) {
+      mEarlyRv = NS_ERROR_DOM_DATA_ERR;
+      return;
+    }
+
+    mLength = mLength >> 3; // bits to bytes
+
+    // Retrieve the peer's public key.
+    RootedDictionary<EcdhKeyDeriveParams> params(aCx);
+    mEarlyRv = Coerce(aCx, params, aAlgorithm);
+    if (NS_FAILED(mEarlyRv) || !params.mPublic.WasPassed()) {
+      mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR;
+      return;
+    }
+
+    CryptoKey* publicKey = params.mPublic.Value();
+    mPubKey = publicKey->GetPublicKey();
+    if (!mPubKey) {
+      mEarlyRv = NS_ERROR_DOM_INVALID_ACCESS_ERR;
+      return;
+    }
+
+    nsRefPtr<KeyAlgorithm> publicAlgorithm = publicKey->Algorithm();
+
+    // Given public key must be an ECDH key.
+    nsString algName;
+    publicAlgorithm->GetName(algName);
+    if (!algName.EqualsLiteral(WEBCRYPTO_ALG_ECDH)) {
+      mEarlyRv = NS_ERROR_DOM_INVALID_ACCESS_ERR;
+      return;
+    }
+
+    // Both keys must use the same named curve.
+    nsString curve1, curve2;
+    static_cast<EcKeyAlgorithm*>(aKey.Algorithm())->GetNamedCurve(curve1);
+    static_cast<EcKeyAlgorithm*>(publicAlgorithm.get())->GetNamedCurve(curve2);
+
+    if (!curve1.Equals(curve2)) {
+      mEarlyRv = NS_ERROR_DOM_DATA_ERR;
+      return;
+    }
+  }
+
+private:
+  size_t mLength;
+  ScopedSECKEYPrivateKey mPrivKey;
+  ScopedSECKEYPublicKey mPubKey;
+
+  virtual nsresult DoCrypto() MOZ_OVERRIDE
+  {
+    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));
+
+    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;
+  }
+};
+
 template<class KeyEncryptTask>
 class WrapKeyTask : public ExportKeyTask
 {
 public:
   WrapKeyTask(JSContext* aCx,
               const nsAString& aFormat,
               CryptoKey& aKey,
               CryptoKey& aWrappingKey,
@@ -2561,16 +2671,20 @@ WebCryptoTask::CreateDeriveBitsTask(JSCo
   if (NS_FAILED(rv)) {
     return new FailureTask(rv);
   }
 
   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);
+  }
+
   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/tests.js
+++ b/dom/crypto/test/tests.js
@@ -1890,8 +1890,111 @@ TestArray.addTest(
                (x.privateKey.usages.length == 2) &&
                (x.privateKey.usages[0] == "deriveKey") &&
                (x.privateKey.usages[1] == "deriveBits");
       }),
       error(that)
     );
   }
 );
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+  "Generate an ECDH key and derive some bits",
+  function() {
+    var that = this;
+    var alg = { name: "ECDH", namedCurve: "P-256" };
+
+    var pair;
+    function setKeyPair(x) { pair = x; }
+
+    function doDerive(n) {
+      return function (x) {
+        var alg = { name: "ECDH", public: pair.publicKey };
+        return crypto.subtle.deriveBits(alg, pair.privateKey, n * 8);
+      }
+    }
+
+    crypto.subtle.generateKey(alg, false, ["deriveBits"])
+      .then(setKeyPair, error(that))
+      .then(doDerive(2), error(that))
+      .then(function (x) {
+        // Deriving less bytes works.
+        if (x.byteLength != 2) {
+          throw "should have derived two bytes";
+        }
+      })
+      // Deriving more than the curve yields doesn't.
+      .then(doDerive(33), error(that))
+      .then(error(that), complete(that));
+  }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+  "Test that ECDH deriveBits() fails when the public key is not an ECDH key",
+  function() {
+    var that = this;
+    var pubKey, privKey;
+    function setPub(x) { pubKey = x.publicKey; }
+    function setPriv(x) { privKey = x.privateKey; }
+
+    function doGenerateP256() {
+      var alg = { name: "ECDH", namedCurve: "P-256" };
+      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: "ECDH", public: pubKey };
+      return crypto.subtle.deriveBits(alg, privKey, 16);
+    }
+
+    doGenerateP256()
+      .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 ECDH deriveBits() fails when the given keys' curves don't match",
+  function() {
+    var that = this;
+    var pubKey, privKey;
+    function setPub(x) { pubKey = x.publicKey; }
+    function setPriv(x) { privKey = x.privateKey; }
+
+    function doGenerateP256() {
+      var alg = { name: "ECDH", namedCurve: "P-256" };
+      return crypto.subtle.generateKey(alg, false, ["deriveBits"]);
+    }
+
+    function doGenerateP384() {
+      var alg = { name: "ECDH", namedCurve: "P-384" };
+      return crypto.subtle.generateKey(alg, false, ["deriveBits"]);
+    }
+
+    function doDerive() {
+      var alg = { name: "ECDH", public: pubKey };
+      return crypto.subtle.deriveBits(alg, privKey, 16);
+    }
+
+    doGenerateP256()
+      .then(setPriv, error(that))
+      .then(doGenerateP384, error(that))
+      .then(setPub, error(that))
+      .then(doDerive, error(that))
+      .then(error(that), complete(that));
+  }
+);
--- a/dom/webidl/SubtleCrypto.webidl
+++ b/dom/webidl/SubtleCrypto.webidl
@@ -109,16 +109,20 @@ dictionary DhKeyGenParams : Algorithm {
   BigInteger generator;
 };
 
 typedef DOMString NamedCurve;
 dictionary EcKeyGenParams : Algorithm {
   NamedCurve namedCurve;
 };
 
+dictionary EcdhKeyDeriveParams : Algorithm {
+  CryptoKey public;
+};
+
 
 /***** JWK *****/
 
 dictionary RsaOtherPrimesInfo {
   // The following fields are defined in Section 6.3.2.7 of JSON Web Algorithms
   DOMString r;
   DOMString d;
   DOMString t;