1 2function run_test() { 3 setup({explicit_done: true}); 4 5 var subtle = self.crypto.subtle; // Change to test prefixed implementations 6 7 // When are all these tests really done? When all the promises they use have resolved. 8 var all_promises = []; 9 10 // Source file [algorithm_name]_vectors.js provides the getTestVectors method 11 // for the algorithm that drives these tests. 12 var testVectors = getTestVectors(); 13 14 // Test verification first, because signing tests rely on that working 15 testVectors.forEach(function(vector) { 16 var promise = importVectorKeys(vector, ["verify"], ["sign"]) 17 .then(function(vectors) { 18 promise_test(function(test) { 19 var operation = subtle.verify(vector.algorithm, vector.publicKey, vector.signature, vector.plaintext) 20 .then(function(is_verified) { 21 assert_true(is_verified, "Signature verified"); 22 }, function(err) { 23 assert_unreached("Verification should not throw error " + vector.name + ": " + err.message + "'"); 24 }); 25 26 return operation; 27 }, vector.name + " verification"); 28 29 }, function(err) { 30 // We need a failed test if the importVectorKey operation fails, so 31 // we know we never tested verification. 32 promise_test(function(test) { 33 assert_unreached("importVectorKeys failed for " + vector.name + ". Message: ''" + err.message + "''"); 34 }, "importVectorKeys step: " + vector.name + " verification"); 35 }); 36 37 all_promises.push(promise); 38 }); 39 40 // Test verification with an altered buffer after call 41 testVectors.forEach(function(vector) { 42 var promise = importVectorKeys(vector, ["verify"], ["sign"]) 43 .then(function(vectors) { 44 promise_test(function(test) { 45 var signature = copyBuffer(vector.signature); 46 var operation = subtle.verify(vector.algorithm, vector.publicKey, signature, vector.plaintext) 47 .then(function(is_verified) { 48 assert_true(is_verified, "Signature verified"); 49 }, function(err) { 50 assert_unreached("Verification should not throw error " + vector.name + ": " + err.message + "'"); 51 }); 52 53 signature[0] = 255 - signature[0]; 54 return operation; 55 }, vector.name + " verification with altered signature after call"); 56 }, function(err) { 57 promise_test(function(test) { 58 assert_unreached("importVectorKeys failed for " + vector.name + ". Message: ''" + err.message + "''"); 59 }, "importVectorKeys step: " + vector.name + " verification with altered signature after call"); 60 }); 61 62 all_promises.push(promise); 63 }); 64 65 // Check for successful verification even if plaintext is altered after call. 66 testVectors.forEach(function(vector) { 67 var promise = importVectorKeys(vector, ["verify"], ["sign"]) 68 .then(function(vectors) { 69 promise_test(function(test) { 70 var plaintext = copyBuffer(vector.plaintext); 71 var operation = subtle.verify(vector.algorithm, vector.publicKey, vector.signature, plaintext) 72 .then(function(is_verified) { 73 assert_true(is_verified, "Signature verified"); 74 }, function(err) { 75 assert_unreached("Verification should not throw error " + vector.name + ": " + err.message + "'"); 76 }); 77 78 plaintext[0] = 255 - plaintext[0]; 79 return operation; 80 }, vector.name + " with altered plaintext after call"); 81 }, function(err) { 82 promise_test(function(test) { 83 assert_unreached("importVectorKeys failed for " + vector.name + ". Message: ''" + err.message + "''"); 84 }, "importVectorKeys step: " + vector.name + " with altered plaintext after call"); 85 }); 86 87 all_promises.push(promise); 88 }); 89 90 // Check for failures due to using privateKey to verify. 91 testVectors.forEach(function(vector) { 92 var promise = importVectorKeys(vector, ["verify"], ["sign"]) 93 .then(function(vectors) { 94 promise_test(function(test) { 95 return subtle.verify(vector.algorithm, vector.privateKey, vector.signature, vector.plaintext) 96 .then(function(plaintext) { 97 assert_unreached("Should have thrown error for using privateKey to verify in " + vector.name + ": " + err.message + "'"); 98 }, function(err) { 99 assert_equals(err.name, "InvalidAccessError", "Should throw InvalidAccessError instead of '" + err.message + "'"); 100 }); 101 }, vector.name + " using privateKey to verify"); 102 103 }, function(err) { 104 promise_test(function(test) { 105 assert_unreached("importVectorKeys failed for " + vector.name + ". Message: ''" + err.message + "''"); 106 }, "importVectorKeys step: " + vector.name + " using privateKey to verify"); 107 }); 108 109 all_promises.push(promise); 110 }); 111 112 // Check for failures due to using publicKey to sign. 113 testVectors.forEach(function(vector) { 114 var promise = importVectorKeys(vector, ["verify"], ["sign"]) 115 .then(function(vectors) { 116 promise_test(function(test) { 117 return subtle.sign(vector.algorithm, vector.publicKey, vector.plaintext) 118 .then(function(signature) { 119 assert_unreached("Should have thrown error for using publicKey to sign in " + vector.name + ": " + err.message + "'"); 120 }, function(err) { 121 assert_equals(err.name, "InvalidAccessError", "Should throw InvalidAccessError instead of '" + err.message + "'"); 122 }); 123 }, vector.name + " using publicKey to sign"); 124 }, function(err) { 125 promise_test(function(test) { 126 assert_unreached("importVectorKeys failed for " + vector.name + ". Message: ''" + err.message + "''"); 127 }, "importVectorKeys step: " + vector.name + " using publicKey to sign"); 128 }); 129 130 all_promises.push(promise); 131 }); 132 133 // Check for failures due to no "verify" usage. 134 testVectors.forEach(function(originalVector) { 135 var vector = Object.assign({}, originalVector); 136 137 var promise = importVectorKeys(vector, [], ["sign"]) 138 .then(function(vectors) { 139 promise_test(function(test) { 140 return subtle.verify(vector.algorithm, vector.publicKey, vector.signature, vector.plaintext) 141 .then(function(plaintext) { 142 assert_unreached("Should have thrown error for no verify usage in " + vector.name + ": " + err.message + "'"); 143 }, function(err) { 144 assert_equals(err.name, "InvalidAccessError", "Should throw InvalidAccessError instead of '" + err.message + "'"); 145 }); 146 }, vector.name + " no verify usage"); 147 }, function(err) { 148 promise_test(function(test) { 149 assert_unreached("importVectorKeys failed for " + vector.name + ". Message: ''" + err.message + "''"); 150 }, "importVectorKeys step: " + vector.name + " no verify usage"); 151 }); 152 153 all_promises.push(promise); 154 }); 155 156 // Check for successful signing and verification. 157 testVectors.forEach(function(vector) { 158 // RSA signing is deterministic with PKCS#1 v1.5, or PSS with zero-length salts. 159 const isDeterministic = !("saltLength" in vector.algorithm) || vector.algorithm.saltLength == 0; 160 var promise = importVectorKeys(vector, ["verify"], ["sign"]) 161 .then(function(vectors) { 162 promise_test(function(test) { 163 return subtle.sign(vector.algorithm, vector.privateKey, vector.plaintext) 164 .then(function(signature) { 165 if (isDeterministic) { 166 // If deterministic, we can check the output matches. Otherwise, we can only check it verifies. 167 assert_true(equalBuffers(signature, vector.signature), "Signing did not give the expected output"); 168 } 169 // Can we verify the new signature? 170 return subtle.verify(vector.algorithm, vector.publicKey, signature, vector.plaintext) 171 .then(function(is_verified) { 172 assert_true(is_verified, "Round trip verifies"); 173 return signature; 174 }, function(err) { 175 assert_unreached("verify error for test " + vector.name + ": " + err.message + "'"); 176 }); 177 }) 178 .then(function(priorSignature) { 179 // Will a second signing give us different signature? It should for PSS with non-empty salt 180 return subtle.sign(vector.algorithm, vector.privateKey, vector.plaintext) 181 .then(function(signature) { 182 if (isDeterministic) { 183 assert_true(equalBuffers(priorSignature, signature), "Two signings with empty salt give same signature") 184 } else { 185 assert_false(equalBuffers(priorSignature, signature), "Two signings with a salt give different signatures") 186 } 187 }, function(err) { 188 assert_unreached("second time verify error for test " + vector.name + ": '" + err.message + "'"); 189 }); 190 }, function(err) { 191 assert_unreached("sign error for test " + vector.name + ": '" + err.message + "'"); 192 }); 193 }, vector.name + " round trip"); 194 195 }, function(err) { 196 // We need a failed test if the importVectorKey operation fails, so 197 // we know we never tested signing or verifying 198 promise_test(function(test) { 199 assert_unreached("importVectorKeys failed for " + vector.name + ". Message: ''" + err.message + "''"); 200 }, "importVectorKeys step: " + vector.name + " round trip"); 201 }); 202 203 all_promises.push(promise); 204 }); 205 206 207 // Test signing with the wrong algorithm 208 testVectors.forEach(function(vector) { 209 // Want to get the key for the wrong algorithm 210 var alteredVector = Object.assign({}, vector); 211 alteredVector.algorithm = Object.assign({}, vector.algorithm); 212 if (vector.algorithm.name === "RSA-PSS") { 213 alteredVector.algorithm.name = "RSASSA-PKCS1-v1_5"; 214 } else { 215 alteredVector.algorithm.name = "RSA-PSS"; 216 } 217 218 var promise = importVectorKeys(alteredVector, ["verify"], ["sign"]) 219 .then(function(vectors) { 220 promise_test(function(test) { 221 var operation = subtle.sign(vector.algorithm, alteredVector.privateKey, vector.plaintext) 222 .then(function(signature) { 223 assert_unreached("Signing should not have succeeded for " + vector.name); 224 }, function(err) { 225 assert_equals(err.name, "InvalidAccessError", "Should have thrown InvalidAccessError instead of '" + err.message + "'"); 226 }); 227 228 return operation; 229 }, vector.name + " signing with wrong algorithm name"); 230 231 }, function(err) { 232 // We need a failed test if the importVectorKey operation fails, so 233 // we know we never tested verification. 234 promise_test(function(test) { 235 assert_unreached("importVectorKeys failed for " + vector.name + ". Message: ''" + err.message + "''"); 236 }, "importVectorKeys step: " + vector.name + " signing with wrong algorithm name"); 237 }); 238 239 all_promises.push(promise); 240 }); 241 242 // Test verification with the wrong algorithm 243 testVectors.forEach(function(vector) { 244 // Want to get the key for the wrong algorithm 245 var alteredVector = Object.assign({}, vector); 246 alteredVector.algorithm = Object.assign({}, vector.algorithm); 247 if (vector.algorithm.name === "RSA-PSS") { 248 alteredVector.algorithm.name = "RSASSA-PKCS1-v1_5"; 249 } else { 250 alteredVector.algorithm.name = "RSA-PSS"; 251 } 252 253 var promise = importVectorKeys(alteredVector, ["verify"], ["sign"]) 254 .then(function(vectors) { 255 // Some tests are sign only 256 if (!("signature" in vector)) { 257 return; 258 } 259 promise_test(function(test) { 260 var operation = subtle.verify(vector.algorithm, alteredVector.publicKey, vector.signature, vector.plaintext) 261 .then(function(is_verified) { 262 assert_unreached("Verification should not have succeeded for " + vector.name); 263 }, function(err) { 264 assert_equals(err.name, "InvalidAccessError", "Should have thrown InvalidAccessError instead of '" + err.message + "'"); 265 }); 266 267 return operation; 268 }, vector.name + " verification with wrong algorithm name"); 269 270 }, function(err) { 271 // We need a failed test if the importVectorKey operation fails, so 272 // we know we never tested verification. 273 promise_test(function(test) { 274 assert_unreached("importVectorKeys failed for " + vector.name + ". Message: ''" + err.message + "''"); 275 }, "importVectorKeys step: " + vector.name + " verification with wrong algorithm name"); 276 }); 277 278 all_promises.push(promise); 279 }); 280 281 // Verification should fail with wrong signature 282 testVectors.forEach(function(vector) { 283 var promise = importVectorKeys(vector, ["verify"], ["sign"]) 284 .then(function(vectors) { 285 promise_test(function(test) { 286 var signature = copyBuffer(vector.signature); 287 signature[0] = 255 - signature[0]; 288 var operation = subtle.verify(vector.algorithm, vector.publicKey, signature, vector.plaintext) 289 .then(function(is_verified) { 290 assert_false(is_verified, "Signature NOT verified"); 291 }, function(err) { 292 assert_unreached("Verification should not throw error " + vector.name + ": " + err.message + "'"); 293 }); 294 295 return operation; 296 }, vector.name + " verification failure with altered signature"); 297 298 }, function(err) { 299 // We need a failed test if the importVectorKey operation fails, so 300 // we know we never tested verification. 301 promise_test(function(test) { 302 assert_unreached("importVectorKeys failed for " + vector.name + ". Message: ''" + err.message + "''"); 303 }, "importVectorKeys step: " + vector.name + " verification failure with altered signature"); 304 }); 305 306 all_promises.push(promise); 307 }); 308 309 // [RSA-PSS] Verification should fail with wrong saltLength 310 testVectors.forEach(function(vector) { 311 if (vector.algorithm.name === "RSA-PSS") { 312 var promise = importVectorKeys(vector, ["verify"], ["sign"]) 313 .then(function(vectors) { 314 promise_test(function(test) { 315 const saltLength = vector.algorithm.saltLength === 32 ? 48 : 32; 316 var operation = subtle.verify({ ...vector.algorithm, saltLength }, vector.publicKey, vector.signature, vector.plaintext) 317 .then(function(is_verified) { 318 assert_false(is_verified, "Signature NOT verified"); 319 }, function(err) { 320 assert_unreached("Verification should not throw error " + vector.name + ": " + err.message + "'"); 321 }); 322 323 return operation; 324 }, vector.name + " verification failure with wrong saltLength"); 325 326 }, function(err) { 327 // We need a failed test if the importVectorKey operation fails, so 328 // we know we never tested verification. 329 promise_test(function(test) { 330 assert_unreached("importVectorKeys failed for " + vector.name + ". Message: ''" + err.message + "''"); 331 }, "importVectorKeys step: " + vector.name + " verification failure with wrong saltLength"); 332 }); 333 334 all_promises.push(promise); 335 } 336 }); 337 338 // Verification should fail with wrong plaintext 339 testVectors.forEach(function(vector) { 340 var promise = importVectorKeys(vector, ["verify"], ["sign"]) 341 .then(function(vectors) { 342 promise_test(function(test) { 343 var plaintext = copyBuffer(vector.plaintext); 344 plaintext[0] = 255 - plaintext[0]; 345 var operation = subtle.verify(vector.algorithm, vector.publicKey, vector.signature, plaintext) 346 .then(function(is_verified) { 347 assert_false(is_verified, "Signature NOT verified"); 348 }, function(err) { 349 assert_unreached("Verification should not throw error " + vector.name + ": " + err.message + "'"); 350 }); 351 352 return operation; 353 }, vector.name + " verification failure with altered plaintext"); 354 355 }, function(err) { 356 // We need a failed test if the importVectorKey operation fails, so 357 // we know we never tested verification. 358 promise_test(function(test) { 359 assert_unreached("importVectorKeys failed for " + vector.name + ". Message: ''" + err.message + "''"); 360 }, "importVectorKeys step: " + vector.name + " verification failure with altered plaintext"); 361 }); 362 363 all_promises.push(promise); 364 }); 365 366 367 promise_test(function() { 368 return Promise.all(all_promises) 369 .then(function() {done();}) 370 .catch(function() {done();}) 371 }, "setup"); 372 373 // A test vector has all needed fields for signing and verifying, EXCEPT that the 374 // key field may be null. This function replaces that null with the Correct 375 // CryptoKey object. 376 // 377 // Returns a Promise that yields an updated vector on success. 378 function importVectorKeys(vector, publicKeyUsages, privateKeyUsages) { 379 var publicPromise, privatePromise; 380 381 if (vector.publicKey !== null) { 382 publicPromise = new Promise(function(resolve, reject) { 383 resolve(vector); 384 }); 385 } else { 386 publicPromise = subtle.importKey(vector.publicKeyFormat, vector.publicKeyBuffer, {name: vector.algorithm.name, hash: vector.hash}, false, publicKeyUsages) 387 .then(function(key) { 388 vector.publicKey = key; 389 return vector; 390 }); // Returns a copy of the sourceBuffer it is sent. 391 } 392 393 if (vector.privateKey !== null) { 394 privatePromise = new Promise(function(resolve, reject) { 395 resolve(vector); 396 }); 397 } else { 398 privatePromise = subtle.importKey(vector.privateKeyFormat, vector.privateKeyBuffer, {name: vector.algorithm.name, hash: vector.hash}, false, privateKeyUsages) 399 .then(function(key) { 400 vector.privateKey = key; 401 return vector; 402 }); 403 } 404 405 return Promise.all([publicPromise, privatePromise]); 406 } 407 408 // Returns a copy of the sourceBuffer it is sent. 409 function copyBuffer(sourceBuffer) { 410 var source = new Uint8Array(sourceBuffer); 411 var copy = new Uint8Array(sourceBuffer.byteLength) 412 413 for (var i=0; i<source.byteLength; i++) { 414 copy[i] = source[i]; 415 } 416 417 return copy; 418 } 419 420 function equalBuffers(a, b) { 421 if (a.byteLength !== b.byteLength) { 422 return false; 423 } 424 425 var aBytes = new Uint8Array(a); 426 var bBytes = new Uint8Array(b); 427 428 for (var i=0; i<a.byteLength; i++) { 429 if (aBytes[i] !== bBytes[i]) { 430 return false; 431 } 432 } 433 434 return true; 435 } 436 437 return; 438} 439