hkdf.js (17220B)
1 function define_tests() { 2 // May want to test prefixed implementations. 3 var subtle = self.crypto.subtle; 4 5 // hkdf2_vectors sets up test data with the correct derivations for each 6 // test case. 7 var testData = getTestData(); 8 var derivedKeys = testData.derivedKeys; 9 var salts = testData.salts; 10 var derivations = testData.derivations; 11 var infos = testData.infos; 12 13 // What kinds of keys can be created with deriveKey? The following: 14 var derivedKeyTypes = testData.derivedKeyTypes; 15 16 return setUpBaseKeys(derivedKeys) 17 .then(function(allKeys) { 18 // We get several kinds of base keys. Normal ones that can be used for 19 // derivation operations, ones that lack the deriveBits usage, ones 20 // that lack the deriveKeys usage, and one key that is for the wrong 21 // algorithm (not HKDF in this case). 22 var baseKeys = allKeys.baseKeys; 23 var noBits = allKeys.noBits; 24 var noKey = allKeys.noKey; 25 var wrongKey = allKeys.wrongKey; 26 27 // Test each combination of derivedKey size, salt size, hash function, 28 // and number of iterations. The derivations object is structured in 29 // that way, so navigate it to run tests and compare with correct results. 30 Object.keys(derivations).forEach(function(derivedKeySize) { 31 Object.keys(derivations[derivedKeySize]).forEach(function(saltSize) { 32 Object.keys(derivations[derivedKeySize][saltSize]).forEach(function(hashName) { 33 Object.keys(derivations[derivedKeySize][saltSize][hashName]).forEach(function(infoSize) { 34 var testName = derivedKeySize + " derivedKey, " + saltSize + " salt, " + hashName + ", with " + infoSize + " info"; 35 var algorithm = {name: "HKDF", salt: salts[saltSize], info: infos[infoSize], hash: hashName}; 36 37 // Check for correct deriveBits result 38 subsetTest(promise_test, function(test) { 39 return subtle.deriveBits(algorithm, baseKeys[derivedKeySize], 256) 40 .then(function(derivation) { 41 assert_true(equalBuffers(derivation, derivations[derivedKeySize][saltSize][hashName][infoSize]), "Derived correct key"); 42 }, function(err) { 43 assert_unreached("deriveBits failed with error " + err.name + ": " + err.message); 44 }); 45 }, testName); 46 47 // 0 length 48 subsetTest(promise_test, function(test) { 49 return subtle.deriveBits(algorithm, baseKeys[derivedKeySize], 0) 50 .then(function(derivation) { 51 assert_equals(derivation.byteLength, 0, "Derived correctly empty key"); 52 }, function(err) { 53 assert_unreached("deriveBits failed with error " + err.name + ": " + err.message); 54 }); 55 }, testName + " with 0 length"); 56 57 // Check for correct deriveKey results for every kind of 58 // key that can be created by the deriveKeys operation. 59 derivedKeyTypes.forEach(function(derivedKeyType) { 60 var testName = "Derived key of type "; 61 Object.keys(derivedKeyType.algorithm).forEach(function(prop) { 62 testName += prop + ": " + derivedKeyType.algorithm[prop] + " "; 63 }); 64 testName += " using " + derivedKeySize + " derivedKey, " + saltSize + " salt, " + hashName + ", with " + infoSize + " info"; 65 66 // Test the particular key derivation. 67 subsetTest(promise_test, function(test) { 68 return subtle.deriveKey(algorithm, baseKeys[derivedKeySize], derivedKeyType.algorithm, true, derivedKeyType.usages) 69 .then(function(key) { 70 // Need to export the key to see that the correct bits were set. 71 return subtle.exportKey("raw", key) 72 .then(function(buffer) { 73 assert_true(equalBuffers(buffer, derivations[derivedKeySize][saltSize][hashName][infoSize].slice(0, derivedKeyType.algorithm.length/8)), "Exported key matches correct value"); 74 }, function(err) { 75 assert_unreached("Exporting derived key failed with error " + err.name + ": " + err.message); 76 }); 77 }, function(err) { 78 assert_unreached("deriveKey failed with error " + err.name + ": " + err.message); 79 80 }); 81 }, testName); 82 83 // Test various error conditions for deriveKey: 84 85 // - illegal name for hash algorithm (NotSupportedError) 86 var badHash = hashName.substring(0, 3) + hashName.substring(4); 87 subsetTest(promise_test, function(test) { 88 var badAlgorithm = {name: "HKDF", salt: salts[saltSize], hash: badHash, info: algorithm.info}; 89 return subtle.deriveKey(badAlgorithm, baseKeys[derivedKeySize], derivedKeyType.algorithm, true, derivedKeyType.usages) 90 .then(function(key) { 91 assert_unreached("bad hash name should have thrown an NotSupportedError"); 92 }, function(err) { 93 assert_equals(err.name, "NotSupportedError", "deriveKey with bad hash name correctly threw NotSupportedError: " + err.message); 94 }); 95 }, testName + " with bad hash name " + badHash); 96 97 // - baseKey usages missing "deriveKey" (InvalidAccessError) 98 subsetTest(promise_test, function(test) { 99 return subtle.deriveKey(algorithm, noKey[derivedKeySize], derivedKeyType.algorithm, true, derivedKeyType.usages) 100 .then(function(key) { 101 assert_unreached("missing deriveKey usage should have thrown an InvalidAccessError"); 102 }, function(err) { 103 assert_equals(err.name, "InvalidAccessError", "deriveKey with missing deriveKey usage correctly threw InvalidAccessError: " + err.message); 104 }); 105 }, testName + " with missing deriveKey usage"); 106 107 // - baseKey algorithm does not match HKDF (InvalidAccessError) 108 subsetTest(promise_test, function(test) { 109 return subtle.deriveKey(algorithm, wrongKey, derivedKeyType.algorithm, true, derivedKeyType.usages) 110 .then(function(key) { 111 assert_unreached("wrong (ECDH) key should have thrown an InvalidAccessError"); 112 }, function(err) { 113 assert_equals(err.name, "InvalidAccessError", "deriveKey with wrong (ECDH) key correctly threw InvalidAccessError: " + err.message); 114 }); 115 }, testName + " with wrong (ECDH) key"); 116 117 }); 118 119 // Test various error conditions for deriveBits below: 120 121 // missing salt (TypeError) 122 subsetTest(promise_test, function(test) { 123 return subtle.deriveBits({name: "HKDF", info: infos[infoSize], hash: hashName}, baseKeys[derivedKeySize], 0) 124 .then(function(derivation) { 125 assert_equals(derivation.byteLength, 0, "Derived even with missing salt"); 126 }, function(err) { 127 assert_equals(err.name, "TypeError", "deriveBits missing salt correctly threw OperationError: " + err.message); 128 }); 129 }, testName + " with missing salt"); 130 131 // missing info (TypeError) 132 subsetTest(promise_test, function(test) { 133 return subtle.deriveBits({name: "HKDF", salt: salts[saltSize], hash: hashName}, baseKeys[derivedKeySize], 0) 134 .then(function(derivation) { 135 assert_equals(derivation.byteLength, 0, "Derived even with missing info"); 136 }, function(err) { 137 assert_equals(err.name, "TypeError", "deriveBits missing info correctly threw OperationError: " + err.message); 138 }); 139 }, testName + " with missing info"); 140 141 // length not multiple of 8 (OperationError) 142 subsetTest(promise_test, function(test) { 143 return subtle.deriveBits(algorithm, baseKeys[derivedKeySize], 44) 144 .then(function(derivation) { 145 assert_unreached("non-multiple of 8 length should have thrown an OperationError"); 146 }, function(err) { 147 assert_equals(err.name, "OperationError", "deriveBits with non-multiple of 8 length correctly threw OperationError: " + err.message); 148 }); 149 }, testName + " with non-multiple of 8 length"); 150 151 // - illegal name for hash algorithm (NotSupportedError) 152 var badHash = hashName.substring(0, 3) + hashName.substring(4); 153 subsetTest(promise_test, function(test) { 154 var badAlgorithm = {name: "HKDF", salt: salts[saltSize], hash: badHash, info: algorithm.info}; 155 return subtle.deriveBits(badAlgorithm, baseKeys[derivedKeySize], 256) 156 .then(function(derivation) { 157 assert_unreached("bad hash name should have thrown an NotSupportedError"); 158 }, function(err) { 159 assert_equals(err.name, "NotSupportedError", "deriveBits with bad hash name correctly threw NotSupportedError: " + err.message); 160 }); 161 }, testName + " with bad hash name " + badHash); 162 163 // - baseKey usages missing "deriveBits" (InvalidAccessError) 164 subsetTest(promise_test, function(test) { 165 return subtle.deriveBits(algorithm, noBits[derivedKeySize], 256) 166 .then(function(derivation) { 167 assert_unreached("missing deriveBits usage should have thrown an InvalidAccessError"); 168 }, function(err) { 169 assert_equals(err.name, "InvalidAccessError", "deriveBits with missing deriveBits usage correctly threw InvalidAccessError: " + err.message); 170 }); 171 }, testName + " with missing deriveBits usage"); 172 173 // - baseKey algorithm does not match HKDF (InvalidAccessError) 174 subsetTest(promise_test, function(test) { 175 return subtle.deriveBits(algorithm, wrongKey, 256) 176 .then(function(derivation) { 177 assert_unreached("wrong (ECDH) key should have thrown an InvalidAccessError"); 178 }, function(err) { 179 assert_equals(err.name, "InvalidAccessError", "deriveBits with wrong (ECDH) key correctly threw InvalidAccessError: " + err.message); 180 }); 181 }, testName + " with wrong (ECDH) key"); 182 }); 183 }); 184 185 // - legal algorithm name but not digest one (e.g., PBKDF2) (NotSupportedError) 186 var nonDigestHash = "PBKDF2"; 187 Object.keys(infos).forEach(function(infoSize) { 188 var testName = derivedKeySize + " derivedKey, " + saltSize + " salt, " + nonDigestHash + ", with " + infoSize + " info"; 189 var algorithm = {name: "HKDF", salt: salts[saltSize], hash: nonDigestHash}; 190 if (infoSize !== "missing") { 191 algorithm.info = infos[infoSize]; 192 } 193 194 subsetTest(promise_test, function(test) { 195 return subtle.deriveBits(algorithm, baseKeys[derivedKeySize], 256) 196 .then(function(derivation) { 197 assert_unreached("non-digest algorithm should have thrown an NotSupportedError"); 198 }, function(err) { 199 assert_equals(err.name, "NotSupportedError", "deriveBits with non-digest algorithm correctly threw NotSupportedError: " + err.message); 200 }); 201 }, testName + " with non-digest algorithm " + nonDigestHash); 202 203 derivedKeyTypes.forEach(function(derivedKeyType) { 204 var testName = "Derived key of type "; 205 Object.keys(derivedKeyType.algorithm).forEach(function(prop) { 206 testName += prop + ": " + derivedKeyType.algorithm[prop] + " "; 207 }); 208 testName += " using " + derivedKeySize + " derivedKey, " + saltSize + " salt, " + nonDigestHash + ", with " + infoSize + " info"; 209 210 subsetTest(promise_test, function(test) { 211 return subtle.deriveKey(algorithm, baseKeys[derivedKeySize], derivedKeyType.algorithm, true, derivedKeyType.usages) 212 .then(function(derivation) { 213 assert_unreached("non-digest algorithm should have thrown an NotSupportedError"); 214 }, function(err) { 215 assert_equals(err.name, "NotSupportedError", "derivekey with non-digest algorithm correctly threw NotSupportedError: " + err.message); 216 }); 217 }, testName); 218 }); 219 220 }); 221 222 }); 223 }); 224 }); 225 226 // Deriving bits and keys requires starting with a base key, which is created 227 // by importing a derivedKey. setUpBaseKeys returns a promise that yields the 228 // necessary base keys. 229 function setUpBaseKeys(derivedKeys) { 230 var promises = []; 231 232 var baseKeys = {}; 233 var noBits = {}; 234 var noKey = {}; 235 var wrongKey = null; 236 237 Object.keys(derivedKeys).forEach(function(derivedKeySize) { 238 var promise = subtle.importKey("raw", derivedKeys[derivedKeySize], {name: "HKDF"}, false, ["deriveKey", "deriveBits"]) 239 .then(function(baseKey) { 240 baseKeys[derivedKeySize] = baseKey; 241 }, function(err) { 242 baseKeys[derivedKeySize] = null; 243 }); 244 promises.push(promise); 245 246 promise = subtle.importKey("raw", derivedKeys[derivedKeySize], {name: "HKDF"}, false, ["deriveBits"]) 247 .then(function(baseKey) { 248 noKey[derivedKeySize] = baseKey; 249 }, function(err) { 250 noKey[derivedKeySize] = null; 251 }); 252 promises.push(promise); 253 254 promise = subtle.importKey("raw", derivedKeys[derivedKeySize], {name: "HKDF"}, false, ["deriveKey"]) 255 .then(function(baseKey) { 256 noBits[derivedKeySize] = baseKey; 257 }, function(err) { 258 noBits[derivedKeySize] = null; 259 }); 260 promises.push(promise); 261 }); 262 263 var promise = subtle.generateKey({name: "ECDH", namedCurve: "P-256"}, false, ["deriveKey", "deriveBits"]) 264 .then(function(baseKey) { 265 wrongKey = baseKey.privateKey; 266 }, function(err) { 267 wrongKey = null; 268 }); 269 promises.push(promise); 270 271 272 return Promise.all(promises).then(function() { 273 return {baseKeys: baseKeys, noBits: noBits, noKey: noKey, wrongKey: wrongKey}; 274 }); 275 } 276 277 function equalBuffers(a, b) { 278 if (a.byteLength !== b.byteLength) { 279 return false; 280 } 281 282 var aBytes = new Uint8Array(a); 283 var bBytes = new Uint8Array(b); 284 285 for (var i=0; i<a.byteLength; i++) { 286 if (aBytes[i] !== bBytes[i]) { 287 return false; 288 } 289 } 290 291 return true; 292 } 293 294 }