rsa.js (17358B)
1 function run_test() { 2 var subtle = self.crypto.subtle; // Change to test prefixed implementations 3 4 // When are all these tests really done? When all the promises they use have resolved. 5 var all_promises = []; 6 7 // Source file rsa_vectors.js provides the getTestVectors method 8 // for the RSA-OAEP algorithm that drives these tests. 9 var vectors = getTestVectors(); 10 var passingVectors = vectors.passing; 11 var failingVectors = vectors.failing; 12 13 // Test decryption, first, because encryption tests rely on that working 14 passingVectors.forEach(function(vector) { 15 var promise = importVectorKeys(vector, ["encrypt"], ["decrypt"]) 16 .then(function(vectors) { 17 // Get a one byte longer plaintext to encrypt 18 if (!("ciphertext" in vector)) { 19 return; 20 } 21 22 promise_test(function(test) { 23 return subtle.decrypt(vector.algorithm, vector.privateKey, vector.ciphertext) 24 .then(function(plaintext) { 25 assert_true(equalBuffers(plaintext, vector.plaintext, "Decryption works")); 26 }, function(err) { 27 assert_unreached("Decryption should not throw error " + vector.name + ": " + err.message + "'"); 28 }); 29 }, vector.name + " decryption"); 30 31 }, function(err) { 32 // We need a failed test if the importVectorKey operation fails, so 33 // we know we never tested encryption 34 promise_test(function(test) { 35 assert_unreached("importVectorKeys failed for " + vector.name + ". Message: ''" + err.message + "''"); 36 }, "importVectorKeys step: " + vector.name + " decryption"); 37 }); 38 39 all_promises.push(promise); 40 }); 41 42 // Test decryption with an altered buffer 43 passingVectors.forEach(function(vector) { 44 var promise = importVectorKeys(vector, ["encrypt"], ["decrypt"]) 45 .then(function(vectors) { 46 // Get a one byte longer plaintext to encrypt 47 if (!("ciphertext" in vector)) { 48 return; 49 } 50 51 promise_test(function(test) { 52 var ciphertext = copyBuffer(vector.ciphertext); 53 var operation = subtle.decrypt(vector.algorithm, vector.privateKey, ciphertext) 54 .then(function(plaintext) { 55 assert_true(equalBuffers(plaintext, vector.plaintext, "Decryption works")); 56 }, function(err) { 57 assert_unreached("Decryption should not throw error " + vector.name + ": " + err.message + "'"); 58 }); 59 ciphertext[0] = 255 - ciphertext[0]; 60 return operation; 61 }, vector.name + " decryption with altered ciphertext"); 62 63 }, function(err) { 64 // We need a failed test if the importVectorKey operation fails, so 65 // we know we never tested encryption 66 promise_test(function(test) { 67 assert_unreached("importVectorKeys failed for " + vector.name + ". Message: ''" + err.message + "''"); 68 }, "importVectorKeys step: " + vector.name + " decryption with altered ciphertext"); 69 }); 70 71 all_promises.push(promise); 72 }); 73 74 // Check for failures due to using publicKey to decrypt. 75 passingVectors.forEach(function(vector) { 76 var promise = importVectorKeys(vector, ["encrypt"], ["decrypt"]) 77 .then(function(vectors) { 78 promise_test(function(test) { 79 return subtle.decrypt(vector.algorithm, vector.publicKey, vector.ciphertext) 80 .then(function(plaintext) { 81 assert_unreached("Should have thrown error for using publicKey to decrypt in " + vector.name + ": " + err.message + "'"); 82 }, function(err) { 83 assert_equals(err.name, "InvalidAccessError", "Should throw InvalidAccessError instead of " + err.message); 84 }); 85 }, vector.name + " using publicKey to decrypt"); 86 87 }, function(err) { 88 // We need a failed test if the importVectorKey operation fails, so 89 // we know we never tested encryption 90 promise_test(function(test) { 91 assert_unreached("importVectorKeys failed for " + vector.name + ". Message: ''" + err.message + "''"); 92 }, "importVectorKeys step: " + vector.name + " using publicKey to decrypt"); 93 }); 94 95 all_promises.push(promise); 96 }); 97 98 99 // Check for failures due to no "decrypt" usage. 100 passingVectors.forEach(function(originalVector) { 101 var vector = Object.assign({}, originalVector); 102 103 var promise = importVectorKeys(vector, ["encrypt"], ["unwrapKey"]) 104 .then(function(vectors) { 105 // Get a one byte longer plaintext to encrypt 106 promise_test(function(test) { 107 return subtle.decrypt(vector.algorithm, vector.publicKey, vector.ciphertext) 108 .then(function(plaintext) { 109 assert_unreached("Should have thrown error for no decrypt usage in " + vector.name + ": " + err.message + "'"); 110 }, function(err) { 111 assert_equals(err.name, "InvalidAccessError", "Should throw InvalidAccessError instead of " + err.message); 112 }); 113 }, vector.name + " no decrypt usage"); 114 115 }, function(err) { 116 // We need a failed test if the importVectorKey operation fails, so 117 // we know we never tested encryption 118 promise_test(function(test) { 119 assert_unreached("importVectorKeys failed for " + vector.name + ". Message: ''" + err.message + "''"); 120 }, "importVectorKeys step: " + vector.name + " no decrypt usage"); 121 }); 122 123 all_promises.push(promise); 124 }); 125 126 127 // Check for successful encryption even if plaintext is altered after call. 128 passingVectors.forEach(function(vector) { 129 var promise = importVectorKeys(vector, ["encrypt"], ["decrypt"]) 130 .then(function(vectors) { 131 promise_test(function(test) { 132 var plaintext = copyBuffer(vector.plaintext); 133 var operation = subtle.encrypt(vector.algorithm, vector.publicKey, plaintext) 134 .then(function(ciphertext) { 135 assert_equals(ciphertext.byteLength * 8, vector.privateKey.algorithm.modulusLength, "Ciphertext length matches modulus length"); 136 // Can we get the original plaintext back via decrypt? 137 return subtle.decrypt(vector.algorithm, vector.privateKey, ciphertext) 138 .then(function(result) { 139 assert_true(equalBuffers(result, vector.plaintext), "Round trip returns original plaintext"); 140 return ciphertext; 141 }, function(err) { 142 assert_unreached("decrypt error for test " + vector.name + ": " + err.message + "'"); 143 }); 144 }) 145 .then(function(priorCiphertext) { 146 // Will a second encrypt give us different ciphertext, as it should? 147 return subtle.encrypt(vector.algorithm, vector.publicKey, vector.plaintext) 148 .then(function(ciphertext) { 149 assert_false(equalBuffers(priorCiphertext, ciphertext), "Two encrypts give different results") 150 }, function(err) { 151 assert_unreached("second time encrypt error for test " + vector.name + ": '" + err.message + "'"); 152 }); 153 }, function(err) { 154 assert_unreached("decrypt error for test " + vector.name + ": '" + err.message + "'"); 155 }); 156 157 plaintext[0] = 255 - plaintext[0]; 158 return operation; 159 }, vector.name + " with altered plaintext"); 160 161 }, function(err) { 162 // We need a failed test if the importVectorKey operation fails, so 163 // we know we never tested encryption 164 promise_test(function(test) { 165 assert_unreached("importVectorKeys failed for " + vector.name + ". Message: ''" + err.message + "''"); 166 }, "importVectorKeys step: " + vector.name + " with altered plaintext"); 167 }); 168 169 all_promises.push(promise); 170 }); 171 172 // Check for successful encryption. 173 passingVectors.forEach(function(vector) { 174 var promise = importVectorKeys(vector, ["encrypt"], ["decrypt"]) 175 .then(function(vectors) { 176 promise_test(function(test) { 177 return subtle.encrypt(vector.algorithm, vector.publicKey, vector.plaintext) 178 .then(function(ciphertext) { 179 assert_equals(ciphertext.byteLength * 8, vector.privateKey.algorithm.modulusLength, "Ciphertext length matches modulus length"); 180 181 // Can we get the original plaintext back via decrypt? 182 return subtle.decrypt(vector.algorithm, vector.privateKey, ciphertext) 183 .then(function(result) { 184 assert_true(equalBuffers(result, vector.plaintext), "Round trip returns original plaintext"); 185 return ciphertext; 186 }, function(err) { 187 assert_unreached("decrypt error for test " + vector.name + ": " + err.message + "'"); 188 }); 189 }) 190 .then(function(priorCiphertext) { 191 // Will a second encrypt give us different ciphertext, as it should? 192 return subtle.encrypt(vector.algorithm, vector.publicKey, vector.plaintext) 193 .then(function(ciphertext) { 194 assert_false(equalBuffers(priorCiphertext, ciphertext), "Two encrypts give different results") 195 }, function(err) { 196 assert_unreached("second time encrypt error for test " + vector.name + ": '" + err.message + "'"); 197 }); 198 }, function(err) { 199 assert_unreached("decrypt error for test " + vector.name + ": '" + err.message + "'"); 200 }); 201 }, vector.name); 202 203 }, function(err) { 204 // We need a failed test if the importVectorKey operation fails, so 205 // we know we never tested encryption 206 promise_test(function(test) { 207 assert_unreached("importVectorKeys failed for " + vector.name + ". Message: ''" + err.message + "''"); 208 }, "importVectorKeys step: " + vector.name); 209 }); 210 211 all_promises.push(promise); 212 }); 213 214 // Check for failures due to too long plaintext. 215 passingVectors.forEach(function(vector) { 216 var promise = importVectorKeys(vector, ["encrypt"], ["decrypt"]) 217 .then(function(vectors) { 218 // Get a one byte longer plaintext to encrypt 219 var plaintext = new Uint8Array(vector.plaintext.byteLength + 1); 220 plaintext.set(plaintext, 0); 221 plaintext.set(new Uint8Array([32]), vector.plaintext.byteLength); 222 promise_test(function(test) { 223 return subtle.encrypt(vector.algorithm, vector.publicKey, plaintext) 224 .then(function(ciphertext) { 225 assert_unreached("Should have thrown error for too long plaintext in " + vector.name + ": " + err.message + "'"); 226 }, function(err) { 227 assert_equals(err.name, "OperationError", "Should throw OperationError instead of " + err.message); 228 }); 229 }, vector.name + " too long plaintext"); 230 231 }, function(err) { 232 // We need a failed test if the importVectorKey operation fails, so 233 // we know we never tested encryption 234 promise_test(function(test) { 235 assert_unreached("importVectorKeys failed for " + vector.name + ". Message: ''" + err.message + "''"); 236 }, "importVectorKeys step: " + vector.name + " too long plaintext"); 237 }); 238 239 all_promises.push(promise); 240 }); 241 242 243 // Check for failures due to using privateKey to encrypt. 244 passingVectors.forEach(function(vector) { 245 var promise = importVectorKeys(vector, ["encrypt"], ["decrypt"]) 246 .then(function(vectors) { 247 promise_test(function(test) { 248 return subtle.encrypt(vector.algorithm, vector.privateKey, vector.plaintext) 249 .then(function(ciphertext) { 250 assert_unreached("Should have thrown error for using privateKey to encrypt in " + vector.name + ": " + err.message + "'"); 251 }, function(err) { 252 assert_equals(err.name, "InvalidAccessError", "Should throw InvalidAccessError instead of " + err.message); 253 }); 254 }, vector.name + " using privateKey to encrypt"); 255 256 }, function(err) { 257 // We need a failed test if the importVectorKey operation fails, so 258 // we know we never tested encryption 259 promise_test(function(test) { 260 assert_unreached("importVectorKeys failed for " + vector.name + ". Message: ''" + err.message + "''"); 261 }, "importVectorKeys step: " + vector.name + " using privateKey to encrypt"); 262 }); 263 264 all_promises.push(promise); 265 }); 266 267 268 // Check for failures due to no "encrypt usage". 269 passingVectors.forEach(function(originalVector) { 270 var vector = Object.assign({}, originalVector); 271 272 var promise = importVectorKeys(vector, [], ["decrypt"]) 273 .then(function(vectors) { 274 // Get a one byte longer plaintext to encrypt 275 promise_test(function(test) { 276 return subtle.encrypt(vector.algorithm, vector.publicKey, vector.plaintext) 277 .then(function(ciphertext) { 278 assert_unreached("Should have thrown error for no encrypt usage in " + vector.name + ": " + err.message + "'"); 279 }, function(err) { 280 assert_equals(err.name, "InvalidAccessError", "Should throw InvalidAccessError instead of " + err.message); 281 }); 282 }, vector.name + " no encrypt usage"); 283 284 }, function(err) { 285 // We need a failed test if the importVectorKey operation fails, so 286 // we know we never tested encryption 287 promise_test(function(test) { 288 assert_unreached("importVectorKeys failed for " + vector.name + ". Message: ''" + err.message + "''"); 289 }, "importVectorKeys step: " + vector.name + " no encrypt usage"); 290 }); 291 292 all_promises.push(promise); 293 }); 294 295 promise_test(function() { 296 return Promise.all(all_promises) 297 .then(function() {done();}) 298 .catch(function() {done();}) 299 }, "setup"); 300 301 // A test vector has all needed fields for encryption, EXCEPT that the 302 // key field may be null. This function replaces that null with the Correct 303 // CryptoKey object. 304 // 305 // Returns a Promise that yields an updated vector on success. 306 function importVectorKeys(vector, publicKeyUsages, privateKeyUsages) { 307 var publicPromise, privatePromise; 308 309 if (vector.publicKey !== null) { 310 publicPromise = new Promise(function(resolve, reject) { 311 resolve(vector); 312 }); 313 } else { 314 publicPromise = subtle.importKey(vector.publicKeyFormat, vector.publicKeyBuffer, {name: vector.algorithm.name, hash: vector.hash}, false, publicKeyUsages) 315 .then(function(key) { 316 vector.publicKey = key; 317 return vector; 318 }); // Returns a copy of the sourceBuffer it is sent. 319 function copyBuffer(sourceBuffer) { 320 var source = new Uint8Array(sourceBuffer); 321 var copy = new Uint8Array(sourceBuffer.byteLength) 322 323 for (var i=0; i<source.byteLength; i++) { 324 copy[i] = source[i]; 325 } 326 327 return copy; 328 } 329 330 } 331 332 if (vector.privateKey !== null) { 333 privatePromise = new Promise(function(resolve, reject) { 334 resolve(vector); 335 }); 336 } else { 337 privatePromise = subtle.importKey(vector.privateKeyFormat, vector.privateKeyBuffer, {name: vector.algorithm.name, hash: vector.hash}, false, privateKeyUsages) 338 .then(function(key) { 339 vector.privateKey = key; 340 return vector; 341 }); 342 } 343 344 return Promise.all([publicPromise, privatePromise]); 345 } 346 347 // Returns a copy of the sourceBuffer it is sent. 348 function copyBuffer(sourceBuffer) { 349 var source = new Uint8Array(sourceBuffer); 350 var copy = new Uint8Array(sourceBuffer.byteLength) 351 352 for (var i=0; i<source.byteLength; i++) { 353 copy[i] = source[i]; 354 } 355 356 return copy; 357 } 358 359 function equalBuffers(a, b) { 360 if (a.byteLength !== b.byteLength) { 361 return false; 362 } 363 364 var aBytes = new Uint8Array(a); 365 var bBytes = new Uint8Array(b); 366 367 for (var i=0; i<a.byteLength; i++) { 368 if (aBytes[i] !== bBytes[i]) { 369 return false; 370 } 371 } 372 373 return true; 374 } 375 376 return; 377 }