Bug 1034856 - Implement raw public key import/export for DH. r=rbarnes, r=smaug, a=lsblakk
authorTim Taubert <ttaubert@mozilla.com>
Fri, 22 Aug 2014 12:02:14 +0200
changeset 234895 8e01a459f5322fc38bd1e7997f89f3400cdebd51
parent 234894 4db14b0721a27be97e216f2f00a2b642b71e2c1c
child 234896 cc040db9b406d929a0a7c78963be27578c5ec53e
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)
reviewersrbarnes, smaug, lsblakk
bugs1034856
milestone35.0a2
Bug 1034856 - Implement raw public key import/export for DH. r=rbarnes, r=smaug, a=lsblakk
dom/crypto/CryptoKey.cpp
dom/crypto/CryptoKey.h
dom/crypto/WebCryptoTask.cpp
dom/crypto/test/test-vectors.js
dom/crypto/test/test_WebCrypto_DH.html
dom/webidl/SubtleCrypto.webidl
--- a/dom/crypto/CryptoKey.cpp
+++ b/dom/crypto/CryptoKey.cpp
@@ -891,16 +891,59 @@ CryptoKey::PublicKeyToJwk(SECKEYPublicKe
         return NS_ERROR_DOM_OPERATION_ERR;
       }
       return NS_OK;
     default:
       return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
   }
 }
 
+SECKEYPublicKey*
+CryptoKey::PublicDhKeyFromRaw(CryptoBuffer& aKeyData,
+                              const CryptoBuffer& aPrime,
+                              const CryptoBuffer& aGenerator,
+                              const nsNSSShutDownPreventionLock& /*proofOfLock*/)
+{
+  ScopedPLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE));
+  if (!arena) {
+    return nullptr;
+  }
+
+  SECKEYPublicKey* key = PORT_ArenaZNew(arena, SECKEYPublicKey);
+  if (!key) {
+    return nullptr;
+  }
+
+  key->keyType = dhKey;
+  key->pkcs11Slot = nullptr;
+  key->pkcs11ID = CK_INVALID_HANDLE;
+
+  // Set DH public key params.
+  if (!aPrime.ToSECItem(arena, &key->u.dh.prime) ||
+      !aGenerator.ToSECItem(arena, &key->u.dh.base) ||
+      !aKeyData.ToSECItem(arena, &key->u.dh.publicValue)) {
+    return nullptr;
+  }
+
+  key->u.dh.prime.type = siUnsignedInteger;
+  key->u.dh.base.type = siUnsignedInteger;
+  key->u.dh.publicValue.type = siUnsignedInteger;
+
+  return SECKEY_CopyPublicKey(key);
+}
+
+nsresult
+CryptoKey::PublicDhKeyToRaw(SECKEYPublicKey* aPubKey,
+                            CryptoBuffer& aRetVal,
+                            const nsNSSShutDownPreventionLock& /*proofOfLock*/)
+{
+  aRetVal.Assign(&aPubKey->u.dh.publicValue);
+  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
@@ -166,16 +166,24 @@ public:
                                   const nsNSSShutDownPreventionLock& /*proofOfLock*/);
 
   static SECKEYPublicKey* PublicKeyFromJwk(const JsonWebKey& aKeyData,
                                            const nsNSSShutDownPreventionLock& /*proofOfLock*/);
   static nsresult PublicKeyToJwk(SECKEYPublicKey* aPrivKey,
                                  JsonWebKey& aRetVal,
                                  const nsNSSShutDownPreventionLock& /*proofOfLock*/);
 
+  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 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
@@ -1741,16 +1741,98 @@ private:
     if (mDataIsJwk && !JwkCompatible(mJwk, mKey)) {
       return NS_ERROR_DOM_DATA_ERR;
     }
 
     return NS_OK;
   }
 };
 
+class ImportDhKeyTask : public ImportKeyTask
+{
+public:
+  ImportDhKeyTask(JSContext* aCx, const nsAString& aFormat,
+                  const ObjectOrString& aAlgorithm, bool aExtractable,
+                  const Sequence<nsString>& aKeyUsages)
+  {
+    Init(aCx, aFormat, aAlgorithm, aExtractable, aKeyUsages);
+  }
+
+  ImportDhKeyTask(JSContext* aCx, const nsAString& aFormat,
+                  JS::Handle<JSObject*> aKeyData,
+                  const ObjectOrString& aAlgorithm, bool aExtractable,
+                  const Sequence<nsString>& aKeyUsages)
+  {
+    Init(aCx, aFormat, aAlgorithm, aExtractable, aKeyUsages);
+    if (NS_SUCCEEDED(mEarlyRv)) {
+      SetKeyData(aCx, aKeyData);
+    }
+  }
+
+  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;
+    }
+
+    RootedDictionary<DhImportKeyParams> params(aCx);
+    mEarlyRv = Coerce(aCx, params, aAlgorithm);
+    if (NS_FAILED(mEarlyRv)) {
+      mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR;
+      return;
+    }
+
+    CryptoBuffer prime;
+    ATTEMPT_BUFFER_INIT(mPrime, params.mPrime);
+
+    CryptoBuffer generator;
+    ATTEMPT_BUFFER_INIT(mGenerator, params.mGenerator);
+  }
+
+private:
+  CryptoBuffer mPrime;
+  CryptoBuffer mGenerator;
+
+  virtual nsresult DoCrypto() MOZ_OVERRIDE
+  {
+    // Import the key data itself
+    ScopedSECKEYPublicKey pubKey;
+
+    nsNSSShutDownPreventionLock locker;
+    if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_RAW)) {
+      // Public key import
+      pubKey = CryptoKey::PublicDhKeyFromRaw(mKeyData, mPrime, mGenerator, locker);
+      if (!pubKey) {
+        return NS_ERROR_DOM_DATA_ERR;
+      }
+
+      mKey->SetPublicKey(pubKey.get());
+      mKey->SetType(CryptoKey::PUBLIC);
+    } else {
+      return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
+    }
+
+    return NS_OK;
+  }
+
+  virtual nsresult AfterCrypto() MOZ_OVERRIDE
+  {
+    // Check permissions for the requested operation
+    if (mKey->HasUsageOtherThan(CryptoKey::DERIVEBITS | CryptoKey::DERIVEKEY)) {
+      return NS_ERROR_DOM_DATA_ERR;
+    }
+
+    mKey->Algorithm().MakeDh(mAlgName, mPrime, mGenerator);
+    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())
@@ -1781,16 +1863,24 @@ private:
     mPublicKey.dispose();
   }
 
   virtual nsresult DoCrypto() MOZ_OVERRIDE
   {
     nsNSSShutDownPreventionLock locker;
 
     if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_RAW)) {
+      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;
+      }
+
       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) {
@@ -2820,16 +2910,19 @@ WebCryptoTask::CreateImportKeyTask(JSCon
   } 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) ||
              algName.EqualsLiteral(WEBCRYPTO_ALG_ECDSA)) {
     return new ImportEcKeyTask(aCx, aFormat, aKeyData, aAlgorithm,
                                aExtractable, aKeyUsages);
+  } else if (algName.EqualsLiteral(WEBCRYPTO_ALG_DH)) {
+    return new ImportDhKeyTask(aCx, aFormat, aKeyData, aAlgorithm,
+                               aExtractable, aKeyUsages);
   } else {
     return new FailureTask(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
   }
 }
 
 WebCryptoTask*
 WebCryptoTask::CreateExportKeyTask(const nsAString& aFormat,
                                    CryptoKey& aKey)
@@ -2856,17 +2949,18 @@ WebCryptoTask::CreateExportKeyTask(const
   if (algName.EqualsLiteral(WEBCRYPTO_ALG_AES_CBC) ||
       algName.EqualsLiteral(WEBCRYPTO_ALG_AES_CTR) ||
       algName.EqualsLiteral(WEBCRYPTO_ALG_AES_GCM) ||
       algName.EqualsLiteral(WEBCRYPTO_ALG_AES_KW) ||
       algName.EqualsLiteral(WEBCRYPTO_ALG_PBKDF2) ||
       algName.EqualsLiteral(WEBCRYPTO_ALG_HMAC) ||
       algName.EqualsLiteral(WEBCRYPTO_ALG_RSASSA_PKCS1) ||
       algName.EqualsLiteral(WEBCRYPTO_ALG_RSA_OAEP) ||
-      algName.EqualsLiteral(WEBCRYPTO_ALG_ECDH)) {
+      algName.EqualsLiteral(WEBCRYPTO_ALG_ECDH) ||
+      algName.EqualsLiteral(WEBCRYPTO_ALG_DH)) {
     return new ExportKeyTask(aFormat, aKey);
   }
 
   return new FailureTask(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
 }
 
 WebCryptoTask*
 WebCryptoTask::CreateGenerateKeyTask(JSContext* aCx,
--- a/dom/crypto/test/test-vectors.js
+++ b/dom/crypto/test/test-vectors.js
@@ -667,11 +667,36 @@ tv = {
       "4fe1356d6d51c245e485b576625e7ec6f44c42e9a63a3620ffffffffffffffff"
     ),
 
     prime2: util.hex2abv(
       "ffffffffffffffffc90fdaa22168c234c4c6628b80dc1cd129024e088a67cc74" +
       "020bbea63b139b22514a08798e3404ddef9519b3cd3a431b302b0a6df25f1437" +
       "4fe1356d6d51c245e485b576625e7ec6f44c42e9a637ed6b0bff5cb6f406b7ed" +
       "ee386bfb5a899fa5ae9f24117c4b1fe649286651ece65381ffffffffffffffff"
+    ),
+  },
+
+  // KASValidityTest_FFCStatic_NOKC_ZZOnly_resp.fax [FA]
+  // <http://csrc.nist.gov/groups/STM/cavp/documents/keymgmt/kastestvectors.zip>
+  dh_nist: {
+    prime: util.hex2abv(
+      "8b79f180cbd3f282de92e8b8f2d092674ffda61f01ed961f8ef04a1b7a3709ff" +
+      "748c2abf6226cf0c4538e48838193da456e92ee530ef7aa703e741585e475b26" +
+      "cd64fa97819181cef27de2449cd385c49c9b030f89873b5b7eaf063a788f00db" +
+      "3cb670c73846bc4f76af062d672bde8f29806b81548411ab48b99aebfd9c2d09"
+    ),
+
+    gen: util.hex2abv(
+      "029843c81d0ea285c41a49b1a2f8e11a56a4b39040dfbc5ec040150c16f72f87" +
+      "4152f9c44c659d86f7717b2425b62597e9a453b13da327a31cde2cced6009152" +
+      "52d30262d1e54f4f864ace0e484f98abdbb37ebb0ba4106af5f0935b744677fa" +
+      "2f7f3826dcef3a1586956105ebea805d871f34c46c25bc30fc66b2db26cb0a93"
+    ),
+
+    raw: util.hex2abv(
+      "4fc9904887ac7fabff87f054003547c2d9458c1f6f584c140d7271f8b266bb39" +
+      "0af7e3f625a629bec9c6a057a4cbe1a556d5e3eb2ff1c6ff677a08b0c7c50911" +
+      "0b9e7c6dbc961ca4360362d3dbcffc5bf2bb7207e0a5922f77cf5464b316aa49" +
+      "fb62b338ebcdb30bf573d07b663bb7777b69d6317df0a4f636ba3d9acbf9e8ac"
     )
   },
 }
--- a/dom/crypto/test/test_WebCrypto_DH.html
+++ b/dom/crypto/test/test_WebCrypto_DH.html
@@ -172,16 +172,70 @@ TestArray.addTest(
       .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));
   }
 );
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+  "Raw import/export of a public DH key",
+  function () {
+    var that = this;
+    var alg = {
+      name: "DH",
+      prime: tv.dh_nist.prime,
+      generator: tv.dh_nist.gen
+    };
+
+    function doExport(x) {
+      return crypto.subtle.exportKey("raw", x);
+    }
+
+    crypto.subtle.importKey("raw", tv.dh_nist.raw, alg, true, ["deriveBits"])
+      .then(doExport)
+      .then(memcmp_complete(that, tv.dh_nist.raw), error(that));
+  }
+);
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+  "Derive bits from an imported public and a generated private DH key",
+  function() {
+    var that = this;
+    var alg = {
+      name: "DH",
+      prime: tv.dh_nist.prime,
+      generator: tv.dh_nist.gen
+    };
+
+    var privKey;
+    function setPriv(x) { privKey = x.privateKey; }
+
+    function doImport() {
+      return crypto.subtle.importKey("raw", tv.dh_nist.raw, alg, true, ["deriveBits"]);
+    }
+
+    function doDerive(pubKey) {
+      var alg = {name: "DH", public: pubKey};
+      return crypto.subtle.deriveBits(alg, privKey, 128);
+    }
+
+    crypto.subtle.generateKey(alg, false, ["deriveBits"])
+      .then(setPriv, error(that))
+      .then(doImport, error(that))
+      .then(doDerive, error(that))
+      .then(complete(that, function (x) {
+        return x.byteLength == 16;
+      }), 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
@@ -86,16 +86,21 @@ dictionary HmacDerivedKeyParams : HmacIm
 dictionary EcdhKeyDeriveParams : Algorithm {
   required CryptoKey public;
 };
 
 dictionary DhKeyDeriveParams : Algorithm {
   required CryptoKey public;
 };
 
+dictionary DhImportKeyParams : Algorithm {
+  required BigInteger prime;
+  required BigInteger generator;
+};
+
 dictionary EcdsaParams : Algorithm {
   required AlgorithmIdentifier hash;
 };
 
 /***** JWK *****/
 
 dictionary RsaOtherPrimesInfo {
   // The following fields are defined in Section 6.3.2.7 of JSON Web Algorithms