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