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
--- 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");
}