Bug 1034856 - Implement SPKI public key import/export for DH. r=rbarnes, a=lsblakk
authorTim Taubert <ttaubert@mozilla.com>
Fri, 22 Aug 2014 15:32:15 +0200
changeset 233576 cc040db9b406d929a0a7c78963be27578c5ec53e
parent 233575 8e01a459f5322fc38bd1e7997f89f3400cdebd51
child 233577 9b74e7cf7995e8b10bff9134cce24ca4a4a5c4d6
push id4187
push userbhearsum@mozilla.com
push dateFri, 28 Nov 2014 15:29:12 +0000
treeherdermozilla-beta@f23cc6a30c11 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersrbarnes, lsblakk
bugs1034856
milestone35.0a2
Bug 1034856 - Implement SPKI public key import/export for DH. r=rbarnes, a=lsblakk
dom/crypto/CryptoKey.cpp
dom/crypto/WebCryptoCommon.h
dom/crypto/WebCryptoTask.cpp
dom/crypto/test/test-vectors.js
dom/crypto/test/test_WebCrypto_DH.html
--- a/dom/crypto/CryptoKey.cpp
+++ b/dom/crypto/CryptoKey.cpp
@@ -7,16 +7,31 @@
 #include "pk11pub.h"
 #include "cryptohi.h"
 #include "ScopedNSSTypes.h"
 #include "mozilla/dom/CryptoKey.h"
 #include "mozilla/dom/WebCryptoCommon.h"
 #include "mozilla/dom/SubtleCryptoBinding.h"
 #include "mozilla/dom/ToJSValue.h"
 
+// Templates taken from security/nss/lib/cryptohi/seckey.c
+// These would ideally be exported by NSS and until that
+// happens we have to keep our own copies.
+const SEC_ASN1Template SECKEY_DHPublicKeyTemplate[] = {
+    { SEC_ASN1_INTEGER, offsetof(SECKEYPublicKey,u.dh.publicValue), },
+    { 0, }
+};
+const SEC_ASN1Template SECKEY_DHParamKeyTemplate[] = {
+    { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(SECKEYPublicKey) },
+    { SEC_ASN1_INTEGER, offsetof(SECKEYPublicKey,u.dh.prime), },
+    { SEC_ASN1_INTEGER, offsetof(SECKEYPublicKey,u.dh.base), },
+    { SEC_ASN1_SKIP_REST },
+    { 0, }
+};
+
 namespace mozilla {
 namespace dom {
 
 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(CryptoKey, mGlobal)
 NS_IMPL_CYCLE_COLLECTING_ADDREF(CryptoKey)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(CryptoKey)
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CryptoKey)
   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
@@ -379,22 +394,35 @@ CryptoKey::PublicKeyFromSpki(CryptoBuffe
     return nullptr;
   }
 
   ScopedCERTSubjectPublicKeyInfo spki(SECKEY_DecodeDERSubjectPublicKeyInfo(spkiItem.get()));
   if (!spki) {
     return nullptr;
   }
 
-  // Check for id-ecDH. Per the WebCrypto spec we must support it but NSS
-  // does unfortunately not know about it. Let's change the algorithm to
-  // id-ecPublicKey to make NSS happy.
-  if (SECITEM_ItemsAreEqual(&SEC_OID_DATA_EC_DH, &spki->algorithm.algorithm)) {
-    // Retrieve OID data for id-ecPublicKey (1.2.840.10045.2.1).
-    SECOidData* oidData = SECOID_FindOIDByTag(SEC_OID_ANSIX962_EC_PUBLIC_KEY);
+  bool isECDHAlgorithm = SECITEM_ItemsAreEqual(&SEC_OID_DATA_EC_DH,
+                                               &spki->algorithm.algorithm);
+  bool isDHAlgorithm = SECITEM_ItemsAreEqual(&SEC_OID_DATA_DH_KEY_AGREEMENT,
+                                             &spki->algorithm.algorithm);
+
+  // Check for |id-ecDH| and |dhKeyAgreement|. Per the WebCrypto spec we must
+  // support these OIDs but NSS does unfortunately not know about them. Let's
+  // change the algorithm to |id-ecPublicKey| or |dhPublicKey| to make NSS happy.
+  if (isECDHAlgorithm || isDHAlgorithm) {
+    SECOidTag oid = SEC_OID_UNKNOWN;
+    if (isECDHAlgorithm) {
+      oid = SEC_OID_ANSIX962_EC_PUBLIC_KEY;
+    } else if (isDHAlgorithm) {
+      oid = SEC_OID_X942_DIFFIE_HELMAN_KEY;
+    } else {
+      MOZ_ASSERT(false);
+    }
+
+    SECOidData* oidData = SECOID_FindOIDByTag(oid);
     if (!oidData) {
       return nullptr;
     }
 
     SECStatus rv = SECITEM_CopyItem(spki->arena, &spki->algorithm.algorithm,
                                     &oidData->oid);
     if (rv != SECSuccess) {
       return nullptr;
@@ -418,31 +446,98 @@ CryptoKey::PrivateKeyToPkcs8(SECKEYPriva
   if (!pkcs8Item.get()) {
     return NS_ERROR_DOM_INVALID_ACCESS_ERR;
   }
   aRetVal.Assign(pkcs8Item.get());
   return NS_OK;
 }
 
 nsresult
-CryptoKey::PublicKeyToSpki(SECKEYPublicKey* aPubKey,
-                     CryptoBuffer& aRetVal,
-                     const nsNSSShutDownPreventionLock& /*proofOfLock*/)
+PublicDhKeyToSpki(SECKEYPublicKey* aPubKey,
+                  CERTSubjectPublicKeyInfo* aSpki,
+                  PLArenaPool* aArena)
 {
-  ScopedCERTSubjectPublicKeyInfo spki(SECKEY_CreateSubjectPublicKeyInfo(aPubKey));
-  if (!spki) {
+  SECItem* params = ::SECITEM_AllocItem(aArena, nullptr, 0);
+  if (!params) {
+    return NS_ERROR_DOM_OPERATION_ERR;
+  }
+
+  SECItem* rvItem = SEC_ASN1EncodeItem(aArena, params, aPubKey,
+                                       SECKEY_DHParamKeyTemplate);
+  if (!rvItem) {
+    return NS_ERROR_DOM_OPERATION_ERR;
+  }
+
+  SECStatus rv = SECOID_SetAlgorithmID(aArena, &aSpki->algorithm,
+                                       SEC_OID_X942_DIFFIE_HELMAN_KEY, params);
+  if (rv != SECSuccess) {
+    return NS_ERROR_DOM_OPERATION_ERR;
+  }
+
+  rvItem = SEC_ASN1EncodeItem(aArena, &aSpki->subjectPublicKey, aPubKey,
+                              SECKEY_DHPublicKeyTemplate);
+  if (!rvItem) {
     return NS_ERROR_DOM_OPERATION_ERR;
   }
 
+  // The public value is a BIT_STRING encoded as an INTEGER. After encoding
+  // an INT we need to adjust the length to reflect the number of bits.
+  aSpki->subjectPublicKey.len <<= 3;
+
+  return NS_OK;
+}
+
+nsresult
+CryptoKey::PublicKeyToSpki(SECKEYPublicKey* aPubKey,
+                           CryptoBuffer& aRetVal,
+                           const nsNSSShutDownPreventionLock& /*proofOfLock*/)
+{
+  ScopedPLArenaPool arena;
+  ScopedCERTSubjectPublicKeyInfo spki;
+
+  // NSS doesn't support exporting DH public keys.
+  if (aPubKey->keyType == dhKey) {
+    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+    if (!arena) {
+      return NS_ERROR_DOM_OPERATION_ERR;
+    }
+
+    // It's alright to assign the result of PORT_ArenaZNew(ScopedPLArenaPool)
+    // to a ScopedCERTSubjectPublicKeyInfo as long as we don't set |spki->arena|
+    // as that's what would be freed when |spki| goes out of scope.
+    spki = PORT_ArenaZNew(arena, CERTSubjectPublicKeyInfo);
+    if (!spki) {
+      return NS_ERROR_DOM_OPERATION_ERR;
+    }
+
+    nsresult rv = PublicDhKeyToSpki(aPubKey, spki, arena);
+    NS_ENSURE_SUCCESS(rv, rv);
+  } else {
+    spki = SECKEY_CreateSubjectPublicKeyInfo(aPubKey);
+    if (!spki) {
+      return NS_ERROR_DOM_OPERATION_ERR;
+    }
+  }
+
   // Per WebCrypto spec we must export ECDH SPKIs with the algorithm OID
-  // id-ecDH (1.3.132.112). NSS doesn't know about that OID and there is
+  // id-ecDH (1.3.132.112) and DH SPKIs with OID dhKeyAgreement
+  // (1.2.840.113549.1.3.1). NSS doesn't know about these OIDs and there is
   // no way to specify the algorithm to use when exporting a public key.
-  if (aPubKey->keyType == ecKey) {
+  if (aPubKey->keyType == ecKey || aPubKey->keyType == dhKey) {
+    const SECItem* oidData = nullptr;
+    if (aPubKey->keyType == ecKey) {
+      oidData = &SEC_OID_DATA_EC_DH;
+    } else if (aPubKey->keyType == dhKey) {
+      oidData = &SEC_OID_DATA_DH_KEY_AGREEMENT;
+    } else {
+      MOZ_ASSERT(false);
+    }
+
     SECStatus rv = SECITEM_CopyItem(spki->arena, &spki->algorithm.algorithm,
-                                    &SEC_OID_DATA_EC_DH);
+                                    oidData);
     if (rv != SECSuccess) {
       return NS_ERROR_DOM_OPERATION_ERR;
     }
   }
 
   const SEC_ASN1Template* tpl = SEC_ASN1_GET(CERT_SubjectPublicKeyInfoTemplate);
   ScopedSECItem spkiItem(SEC_ASN1EncodeItem(nullptr, nullptr, spki, tpl));
 
--- a/dom/crypto/WebCryptoCommon.h
+++ b/dom/crypto/WebCryptoCommon.h
@@ -97,16 +97,24 @@
 // Define an unknown mechanism type
 #define UNKNOWN_CK_MECHANISM        CKM_VENDOR_DEFINED+1
 
 // python security/pkix/tools/DottedOIDToCode.py id-ecDH 1.3.132.112
 static const uint8_t id_ecDH[] = { 0x2b, 0x81, 0x04, 0x70 };
 const SECItem SEC_OID_DATA_EC_DH = { siBuffer, (unsigned char*)id_ecDH,
                                      PR_ARRAY_SIZE(id_ecDH) };
 
+// python security/pkix/tools/DottedOIDToCode.py dhKeyAgreement 1.2.840.113549.1.3.1
+static const uint8_t dhKeyAgreement[] = {
+  0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x03, 0x01
+};
+const SECItem SEC_OID_DATA_DH_KEY_AGREEMENT = { siBuffer,
+                                                (unsigned char*)dhKeyAgreement,
+                                                PR_ARRAY_SIZE(dhKeyAgreement) };
+
 namespace mozilla {
 namespace dom {
 
 // Helper functions for structured cloning
 inline bool
 ReadString(JSStructuredCloneReader* aReader, nsString& aString)
 {
   bool read;
--- a/dom/crypto/WebCryptoTask.cpp
+++ b/dom/crypto/WebCryptoTask.cpp
@@ -1771,47 +1771,62 @@ public:
             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;
+    if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_RAW)) {
+      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);
     }
-
-    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)) {
+    if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_RAW) ||
+        mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_SPKI)) {
       // Public key import
-      pubKey = CryptoKey::PublicDhKeyFromRaw(mKeyData, mPrime, mGenerator, locker);
+      if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_RAW)) {
+        pubKey = CryptoKey::PublicDhKeyFromRaw(mKeyData, mPrime, mGenerator, locker);
+      } else if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_SPKI)) {
+        pubKey = CryptoKey::PublicKeyFromSpki(mKeyData, locker);
+      } else {
+        MOZ_ASSERT(false);
+      }
+
       if (!pubKey) {
         return NS_ERROR_DOM_DATA_ERR;
       }
 
+      if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_SPKI)) {
+        ATTEMPT_BUFFER_ASSIGN(mPrime, &pubKey->u.dh.prime);
+        ATTEMPT_BUFFER_ASSIGN(mGenerator, &pubKey->u.dh.base);
+      }
+
       mKey->SetPublicKey(pubKey.get());
       mKey->SetType(CryptoKey::PUBLIC);
     } else {
       return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
     }
 
     return NS_OK;
   }
--- a/dom/crypto/test/test-vectors.js
+++ b/dom/crypto/test/test-vectors.js
@@ -692,11 +692,28 @@ tv = {
       "2f7f3826dcef3a1586956105ebea805d871f34c46c25bc30fc66b2db26cb0a93"
     ),
 
     raw: util.hex2abv(
       "4fc9904887ac7fabff87f054003547c2d9458c1f6f584c140d7271f8b266bb39" +
       "0af7e3f625a629bec9c6a057a4cbe1a556d5e3eb2ff1c6ff677a08b0c7c50911" +
       "0b9e7c6dbc961ca4360362d3dbcffc5bf2bb7207e0a5922f77cf5464b316aa49" +
       "fb62b338ebcdb30bf573d07b663bb7777b69d6317df0a4f636ba3d9acbf9e8ac"
+    ),
+
+    spki: util.hex2abv(
+      "308201a33082011806092a864886f70d01030130820109028181008b79f180cb" +
+      "d3f282de92e8b8f2d092674ffda61f01ed961f8ef04a1b7a3709ff748c2abf62" +
+      "26cf0c4538e48838193da456e92ee530ef7aa703e741585e475b26cd64fa9781" +
+      "9181cef27de2449cd385c49c9b030f89873b5b7eaf063a788f00db3cb670c738" +
+      "46bc4f76af062d672bde8f29806b81548411ab48b99aebfd9c2d090281800298" +
+      "43c81d0ea285c41a49b1a2f8e11a56a4b39040dfbc5ec040150c16f72f874152" +
+      "f9c44c659d86f7717b2425b62597e9a453b13da327a31cde2cced600915252d3" +
+      "0262d1e54f4f864ace0e484f98abdbb37ebb0ba4106af5f0935b744677fa2f7f" +
+      "3826dcef3a1586956105ebea805d871f34c46c25bc30fc66b2db26cb0a930000" +
+      "038184000281804fc9904887ac7fabff87f054003547c2d9458c1f6f584c140d" +
+      "7271f8b266bb390af7e3f625a629bec9c6a057a4cbe1a556d5e3eb2ff1c6ff67" +
+      "7a08b0c7c509110b9e7c6dbc961ca4360362d3dbcffc5bf2bb7207e0a5922f77" +
+      "cf5464b316aa49fb62b338ebcdb30bf573d07b663bb7777b69d6317df0a4f636" +
+      "ba3d9acbf9e8ac"
     )
   },
 }
--- a/dom/crypto/test/test_WebCrypto_DH.html
+++ b/dom/crypto/test/test_WebCrypto_DH.html
@@ -226,16 +226,32 @@ TestArray.addTest(
       .then(setPriv, error(that))
       .then(doImport, error(that))
       .then(doDerive, error(that))
       .then(complete(that, function (x) {
         return x.byteLength == 16;
       }), error(that));
   }
 );
+
+// -----------------------------------------------------------------------------
+TestArray.addTest(
+  "SPKI import/export of a public DH key",
+  function() {
+    var that = this;
+
+    function doExport(x) {
+      return crypto.subtle.exportKey("spki", x);
+    }
+
+    crypto.subtle.importKey("spki", tv.dh_nist.spki, "DH", true, ["deriveBits"])
+      .then(doExport, error(that))
+      .then(memcmp_complete(that, tv.dh_nist.spki), error(that));
+  }
+);
 /*]]>*/</script>
 </head>
 
 <body>
 
 <div id="content">
 	<div id="head">
 		<b>Web</b>Crypto<br>