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