Bug 1344442 - Part 2: Improve test coverage of nsICryptoHash and nsICryptoHMAC implementations. r=keeler draft
authorCykesiopka <cykesiopka.bmo@gmail.com>
Wed, 08 Mar 2017 20:47:22 +0800
changeset 495265 e8fc9cb9c6b1d70c9162c6ed9fd49e6945dc57f4
parent 495264 de8b7e6f3fe03f5cd9d687fa7d410a2ca041b68e
child 495266 e26b0d69ba4e8b4688856a8b0cfaa4cfc522dac2
push id48280
push usercykesiopka.bmo@gmail.com
push dateWed, 08 Mar 2017 15:59:17 +0000
reviewerskeeler
bugs1344442
milestone55.0a1
Bug 1344442 - Part 2: Improve test coverage of nsICryptoHash and nsICryptoHMAC implementations. r=keeler The equivalent base 64 digests for the existing test cases were obtained using: > python2 > import binascii > binascii.b2a_base64(binascii.unhexlify(<input hex>)) The large input hash digest was obtained like so: > python2 > import hashlib > hashlib.md5(" " * 4100).hexdigest() The large input HMAC digest was obtained like so: > python2 > import hashlib > import hmac > hmac.new("test", " " * 4100, hashlib.md5).hexdigest() MozReview-Commit-ID: K0BxZdNemu6
security/manager/ssl/tests/unit/head_psm.js
security/manager/ssl/tests/unit/test_hash_algorithms.js
security/manager/ssl/tests/unit/test_hmac.js
--- a/security/manager/ssl/tests/unit/head_psm.js
+++ b/security/manager/ssl/tests/unit/head_psm.js
@@ -850,8 +850,20 @@ function loadPKCS11TestModule(expectModu
       pkcs11.deleteModule("PKCS11 Test Module");
     } catch (e) {
       Assert.ok(expectModuleUnloadToFail,
                 `Module unload should suceed only when expected: ${e}`);
     }
   });
   pkcs11.addModule("PKCS11 Test Module", libraryFile.path, 0, 0);
 }
+
+/**
+ * @param {String} data
+ * @returns {String}
+ */
+function hexify(data) {
+  // |slice(-2)| chomps off the last two characters of a string.
+  // Therefore, if the Unicode value is < 0x10, we have a single-character hex
+  // string when we want one that's two characters, and unconditionally
+  // prepending a "0" solves the problem.
+  return Array.from(data, (c, i) => ("0" + data.charCodeAt(i).toString(16)).slice(-2)).join("");
+}
--- a/security/manager/ssl/tests/unit/test_hash_algorithms.js
+++ b/security/manager/ssl/tests/unit/test_hash_algorithms.js
@@ -1,87 +1,150 @@
 "use strict";
 
+// This file tests various aspects of the nsICryptoHash implementation for all
+// of the supported algorithms.
+
 const messages = [
   "The quick brown fox jumps over the lazy dog",
   ""
 ];
-const hashes = {
-  md2: [
-    "03d85a0d629d2c442e987525319fc471",
-    "8350e5a3e24c153df2275c9f80692773"
-  ],
-  md5: [
-    "9e107d9d372bb6826bd81d3542a419d6",
-    "d41d8cd98f00b204e9800998ecf8427e"
-  ],
-  sha1: [
-    "2fd4e1c67a2d28fced849ee1bb76e7391b93eb12",
-    "da39a3ee5e6b4b0d3255bfef95601890afd80709"
-  ],
-  sha256: [
-    "d7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592",
-    "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
-  ],
-  sha384: [
-   "ca737f1014a48f4c0b6dd43cb177b0afd9e5169367544c494011e3317dbf9a509cb1e5dc1e85a941bbee3d7f2afbc9b1",
-   "38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b"
-  ],
-  sha512: [
-    "07e547d9586f6a73f73fbac0435ed76951218fb7d0c8d788a309d785436bbb642e93a252a954f23912547d1e8a3b5ed6e1bfd7097821233fa0538f3db854fee6",
-    "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e"
-  ]
-};
-
-function hexdigest(data) {
-  // |slice(-2)| chomps off the last two characters of a string.
-  //
-  // Therefore, if the Unicode value is < 10, we have a single-character hex
-  // string when we want one that's two characters, and unconditionally
-  // prepending a "0" solves the problem.
-  return Array.from(data, (c, i) => ("0" + data.charCodeAt(i).toString(16)).slice(-2)).join("");
-}
+const ALGORITHMS = [
+  {
+    initString: "md2",
+    initConstant: Ci.nsICryptoHash.MD2,
+    hexHashes: [
+      "03d85a0d629d2c442e987525319fc471",
+      "8350e5a3e24c153df2275c9f80692773",
+    ],
+    b64Hashes: [
+      "A9haDWKdLEQumHUlMZ/EcQ==",
+      "g1Dlo+JMFT3yJ1yfgGkncw==",
+    ],
+  },
+  {
+    initString: "md5",
+    initConstant: Ci.nsICryptoHash.MD5,
+    hexHashes: [
+      "9e107d9d372bb6826bd81d3542a419d6",
+      "d41d8cd98f00b204e9800998ecf8427e",
+    ],
+    b64Hashes: [
+      "nhB9nTcrtoJr2B01QqQZ1g==",
+      "1B2M2Y8AsgTpgAmY7PhCfg==",
+    ],
+  },
+  {
+    initString: "sha1",
+    initConstant: Ci.nsICryptoHash.SHA1,
+    hexHashes: [
+      "2fd4e1c67a2d28fced849ee1bb76e7391b93eb12",
+      "da39a3ee5e6b4b0d3255bfef95601890afd80709",
+    ],
+    b64Hashes: [
+      "L9ThxnotKPzthJ7hu3bnORuT6xI=",
+      "2jmj7l5rSw0yVb/vlWAYkK/YBwk=",
+    ],
+  },
+  {
+    initString: "sha256",
+    initConstant: Ci.nsICryptoHash.SHA256,
+    hexHashes: [
+      "d7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592",
+      "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+    ],
+    b64Hashes: [
+      "16j7swfXgJRpypq8sAguT41WUeRtPNt2LQLQvzfJ5ZI=",
+      "47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=",
+    ],
+  },
+  {
+    initString: "sha384",
+    initConstant: Ci.nsICryptoHash.SHA384,
+    hexHashes: [
+      "ca737f1014a48f4c0b6dd43cb177b0afd9e5169367544c494011e3317dbf9a509cb1e5dc1e85a941bbee3d7f2afbc9b1",
+      "38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b",
+    ],
+    b64Hashes: [
+      "ynN/EBSkj0wLbdQ8sXewr9nlFpNnVExJQBHjMX2/mlCcseXcHoWpQbvuPX8q+8mx",
+      "OLBgp1GsljhM2TJ+sbHjaiH9txEUvgdDTAzHv2P24donTt6/529l+9Ua0vFImLlb",
+    ],
+  },
+  {
+    initString: "sha512",
+    initConstant: Ci.nsICryptoHash.SHA512,
+    hexHashes: [
+      "07e547d9586f6a73f73fbac0435ed76951218fb7d0c8d788a309d785436bbb642e93a252a954f23912547d1e8a3b5ed6e1bfd7097821233fa0538f3db854fee6",
+      "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e",
+    ],
+    b64Hashes: [
+      // TODO(Bug 1338897): Stop inserting CRLFs every 64 characters.
+      "B+VH2VhvanP3P7rAQ17XaVEhj7fQyNeIownXhUNru2Quk6JSqVTyORJUfR6KO17W\r\n4b/XCXghIz+gU489uFT+5g==",
+      "z4PhNX7vuL3xVChQ1m2AB9Yg5AULVxXcg/SpIdNs6c5H0NE8XYXysP+DGNKHfuwv\r\nY7kxvUdBeoGlODJ6+SfaPg==",
+    ],
+  },
+];
 
 function doHash(algo, value, cmp) {
   let hash = Cc["@mozilla.org/security/hash;1"].createInstance(Ci.nsICryptoHash);
   hash.initWithString(algo);
 
   let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
                     .createInstance(Ci.nsIScriptableUnicodeConverter);
   converter.charset = "utf8";
   value = converter.convertToByteArray(value);
   hash.update(value, value.length);
-  equal(hexdigest(hash.finish(false)), cmp,
+  equal(hexify(hash.finish(false)), cmp,
         `Actual and expected hash for ${algo} should match`);
 
   hash.initWithString(algo);
   hash.update(value, value.length);
-  equal(hexdigest(hash.finish(false)), cmp,
+  equal(hexify(hash.finish(false)), cmp,
         `Actual and expected hash for ${algo} should match after re-init`);
 }
 
 function doHashStream(algo, value, cmp) {
+  // TODO(Bug 459835): Make updateFromStream() accept zero length streams.
+  if (value.length == 0) {
+    return;
+  }
+
   let hash = Cc["@mozilla.org/security/hash;1"].createInstance(Ci.nsICryptoHash);
   hash.initWithString(algo);
 
   let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
                     .createInstance(Ci.nsIScriptableUnicodeConverter);
   converter.charset = "utf8";
   let stream = converter.convertToInputStream(value);
   hash.updateFromStream(stream, stream.available());
-  equal(hexdigest(hash.finish(false)), cmp,
+  equal(hexify(hash.finish(false)), cmp,
         `Actual and expected hash for ${algo} should match updating from stream`);
 }
 
+function testInitConstantAndBase64(initConstant, algoName, message, expectedOutput) {
+  let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
+                    .createInstance(Ci.nsIScriptableUnicodeConverter);
+  converter.charset = "utf8";
+  let value = converter.convertToByteArray(message);
+
+  let hash = Cc["@mozilla.org/security/hash;1"].createInstance(Ci.nsICryptoHash);
+  hash.init(initConstant);
+  hash.update(value, value.length);
+  equal(hash.finish(true), expectedOutput,
+        `Actual and expected base64 hash for ${algoName} should match`);
+}
+
 function run_test() {
-  for (let algo in hashes) {
-    hashes[algo].forEach(
-      function(e, i) {
-        doHash(algo, messages[i], e);
+  for (let algo of ALGORITHMS) {
+    algo.hexHashes.forEach((hash, i) => {
+      doHash(algo.initString, messages[i], hash);
+      doHashStream(algo.initString, messages[i], hash);
+    });
+    algo.b64Hashes.forEach((hash, i) => {
+      testInitConstantAndBase64(algo.initConstant, algo.initString, messages[i],
+                                hash);
+    });
+  }
 
-        if (messages[i].length) {
-          // this test doesn't work for empty string/stream
-          doHashStream(algo, messages[i], e);
-        }
-      }
-    );
-  }
+  // Our buffer size for working with streams is 4096 bytes. This tests we
+  // handle larger inputs.
+  doHashStream("md5", " ".repeat(4100), "59f337d82f9ef5c9571bec4d78d66641");
 }
--- a/security/manager/ssl/tests/unit/test_hmac.js
+++ b/security/manager/ssl/tests/unit/test_hmac.js
@@ -1,88 +1,124 @@
 "use strict";
 
-var ScriptableUnicodeConverter =
-  Components.Constructor("@mozilla.org/intl/scriptableunicodeconverter",
-                         "nsIScriptableUnicodeConverter");
+// This file tests various aspects of the nsICryptoHMAC implementation for all
+// of the supported algorithms.
 
-function getHMAC(data, key, alg) {
-  let converter = new ScriptableUnicodeConverter();
+function getHMAC(data, key, alg, returnBase64) {
+  let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
+                    .createInstance(Ci.nsIScriptableUnicodeConverter);
   converter.charset = "utf8";
   let dataArray = converter.convertToByteArray(data);
 
   let keyObject = Cc["@mozilla.org/security/keyobjectfactory;1"]
                     .getService(Ci.nsIKeyObjectFactory)
                     .keyFromString(Ci.nsIKeyObject.HMAC, key);
 
   let cryptoHMAC = Cc["@mozilla.org/security/hmac;1"]
                      .createInstance(Ci.nsICryptoHMAC);
 
   cryptoHMAC.init(alg, keyObject);
   cryptoHMAC.update(dataArray, dataArray.length);
-  let digest1 = cryptoHMAC.finish(false);
+  let digest1 = cryptoHMAC.finish(returnBase64);
 
   cryptoHMAC.reset();
   cryptoHMAC.update(dataArray, dataArray.length);
-  let digest2 = cryptoHMAC.finish(false);
+  let digest2 = cryptoHMAC.finish(returnBase64);
+
+  let stream = converter.convertToInputStream(data);
+  cryptoHMAC.reset();
+  cryptoHMAC.updateFromStream(stream, stream.available());
+  let digestFromStream = cryptoHMAC.finish(returnBase64);
 
   equal(digest1, digest2,
         "Initial digest and digest after calling reset() should match");
 
+  equal(digest1, digestFromStream,
+        "Digest from buffer and digest from stream should match");
+
   return digest1;
 }
 
 function testHMAC(alg) {
   const key1 = "MyKey_ABCDEFGHIJKLMN";
   const key2 = "MyKey_01234567890123";
 
   const dataA = "Secret message";
   const dataB = "Secres message";
 
-  let digest1a = getHMAC(key1, dataA, alg);
-  let digest2 = getHMAC(key2, dataA, alg);
-  let digest1b = getHMAC(key1, dataA, alg);
+  let digest1a = getHMAC(key1, dataA, alg, false);
+  let digest2 = getHMAC(key2, dataA, alg, false);
+  let digest1b = getHMAC(key1, dataA, alg, false);
 
   equal(digest1a, digest1b,
         "The digests for the same key, data and algorithm should match");
   notEqual(digest1a, digest2, "The digests for different keys should not match");
 
-  let digest1 = getHMAC(key1, dataA, alg);
-  digest2 = getHMAC(key1, dataB, alg);
+  let digest1 = getHMAC(key1, dataA, alg, false);
+  digest2 = getHMAC(key1, dataB, alg, false);
 
   notEqual(digest1, digest2, "The digests for different data should not match");
 }
 
-function hexdigest(data) {
-  return Array.from(data, (c, i) => ("0" + data.charCodeAt(i).toString(16)).slice(-2)).join("");
-}
-
 function testVectors() {
-  // These are test vectors taken from RFC 4231, section 4.3. (Test Case 2)
   const keyTestVector = "Jefe";
   const dataTestVector = "what do ya want for nothing?";
 
-  let digest = hexdigest(getHMAC(dataTestVector, keyTestVector,
-                                 Ci.nsICryptoHMAC.SHA256));
-  equal(digest,
-        "5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843",
-        "Actual and expected SHA-256 digests should match");
+  // The base 64 values aren't in either of the RFCs below; they were just
+  // calculated using the hex vectors.
+  const vectors = [
+    // RFC 2202 section 2 test case 2.
+    {
+      algoID: Ci.nsICryptoHMAC.MD5, algoName: "MD5",
+      expectedDigest: "750c783e6ab0b503eaa86e310a5db738",
+      expectedBase64: "dQx4PmqwtQPqqG4xCl23OA==",
+    },
+    // RFC 2202 section 2 test case 3.
+    {
+      algoID: Ci.nsICryptoHMAC.SHA1, algoName: "SHA-1",
+      expectedDigest: "effcdf6ae5eb2fa2d27416d5f184df9c259a7c79",
+      expectedBase64: "7/zfauXrL6LSdBbV8YTfnCWafHk=",
+    },
+    // RFC 4231 section 4.3.
+    {
+      algoID: Ci.nsICryptoHMAC.SHA256, algoName: "SHA-256",
+      expectedDigest: "5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843",
+      expectedBase64: "W9zBRr9gdU5qBCQmCJV1x1oAPwidJzmDnexYuWTsOEM=",
+    },
+    {
+      algoID: Ci.nsICryptoHMAC.SHA384, algoName: "SHA-384",
+      expectedDigest: "af45d2e376484031617f78d2b58a6b1b9c7ef464f5a01b47e42ec3736322445e8e2240ca5e69e2c78b3239ecfab21649",
+      expectedBase64: "r0XS43ZIQDFhf3jStYprG5x+9GT1oBtH5C7Dc2MiRF6OIkDKXmnix4syOez6shZJ",
+    },
+    {
+      algoID: Ci.nsICryptoHMAC.SHA512, algoName: "SHA-512",
+      expectedDigest: "164b7a7bfcf819e2e395fbe73b56e0a387bd64222e831fd610270cd7ea2505549758bf75c05a994a6d034f65f8f0e6fdcaeab1a34d4a6b4b636e070a38bce737",
+      // TODO(Bug 1338897): Stop inserting CRLFs every 64 characters.
+      expectedBase64: "Fkt6e/z4GeLjlfvnO1bgo4e9ZCIugx/WECcM1+olBVSXWL91wFqZSm0DT2X48Ob9\r\nyuqxo01Ka0tjbgcKOLznNw==",
+    },
+  ];
 
-  digest = hexdigest(getHMAC(dataTestVector, keyTestVector,
-                             Ci.nsICryptoHMAC.SHA384));
-  equal(digest,
-        "af45d2e376484031617f78d2b58a6b1b9c7ef464f5a01b47e42ec3736322445e8e2240ca5e69e2c78b3239ecfab21649",
-        "Actual and expected SHA-384 digests should match");
-
-  digest = hexdigest(getHMAC(dataTestVector, keyTestVector,
-                             Ci.nsICryptoHMAC.SHA512));
-  equal(digest,
-        "164b7a7bfcf819e2e395fbe73b56e0a387bd64222e831fd610270cd7ea2505549758bf75c05a994a6d034f65f8f0e6fdcaeab1a34d4a6b4b636e070a38bce737",
-        "Actual and expected SHA-512 digests should match");
+  for (let vector of vectors) {
+    let digest = getHMAC(dataTestVector, keyTestVector, vector.algoID, false);
+    equal(hexify(digest), vector.expectedDigest,
+          `Actual and expected ${vector.algoName} digests should match`);
+    let b64Digest = getHMAC(dataTestVector, keyTestVector, vector.algoID, true);
+    equal(b64Digest, vector.expectedBase64,
+          `Actual and expected ${vector.algoName} base64 digest should match`);
+  }
 }
 
 function run_test() {
   testVectors();
 
+  testHMAC(Ci.nsICryptoHMAC.MD5);
   testHMAC(Ci.nsICryptoHMAC.SHA1);
+  testHMAC(Ci.nsICryptoHMAC.SHA256);
+  testHMAC(Ci.nsICryptoHMAC.SHA384);
   testHMAC(Ci.nsICryptoHMAC.SHA512);
-  testHMAC(Ci.nsICryptoHMAC.MD5);
+
+  // Our buffer size for working with streams is 4096 bytes. This tests we
+  // handle larger inputs.
+  let digest = getHMAC(" ".repeat(4100), "test", Ci.nsICryptoHMAC.MD5, false);
+  equal(hexify(digest), "befbc875f73a088cf04e77f2b1286010",
+        "Actual and expected digest for large stream should match");
 }