| function run_test(algorithmNames, slowTest) { | |
| var subtle = crypto.subtle; // Change to test prefixed implementations | |
| // These tests check that generateKey successfully creates keys | |
| // when provided any of a wide set of correct parameters | |
| // and that they can be exported afterwards. | |
| // | |
| // There are a lot of combinations of possible parameters, | |
| // resulting in a very large number of tests | |
| // performed. | |
| // Setup: define the correct behaviors that should be sought, and create | |
| // helper functions that generate all possible test parameters for | |
| // different situations. | |
| var testVectors = getGenerateKeyTestVectors(algorithmNames); | |
| function parameterString(algorithm, extractable, usages) { | |
| var result = "(" + | |
| objectToString(algorithm) + ", " + | |
| objectToString(extractable) + ", " + | |
| objectToString(usages) + | |
| ")"; | |
| return result; | |
| } | |
| // Test that a given combination of parameters is successful | |
| function testSuccess(algorithm, extractable, usages, resultType, testTag) { | |
| // algorithm, extractable, and usages are the generateKey parameters | |
| // resultType is the expected result, either the CryptoKey object or "CryptoKeyPair" | |
| // testTag is a string to prepend to the test name. | |
| promise_test(function(test) { | |
| return subtle.generateKey(algorithm, extractable, usages) | |
| .then(function(result) { | |
| if (resultType === "CryptoKeyPair") { | |
| assert_goodCryptoKey(result.privateKey, algorithm, extractable, usages, "private"); | |
| assert_goodCryptoKey(result.publicKey, algorithm, true, usages, "public"); | |
| } else { | |
| assert_goodCryptoKey(result, algorithm, extractable, usages, "secret"); | |
| } | |
| return result; | |
| }, function(err) { | |
| assert_unreached("generateKey threw an unexpected error: " + err.toString()); | |
| }) | |
| .then(async function (result) { | |
| if (resultType === "CryptoKeyPair") { | |
| // TODO: enable ML-KEM JWK when its definition is done in IETF JOSE WG. | |
| const isMlKem = result.publicKey.algorithm.name.startsWith('ML-KEM'); | |
| const isHybridKem = result.publicKey.algorithm.name.startsWith('MLKEM'); | |
| const promises = []; | |
| if (!isMlKem) { | |
| promises.push(subtle.exportKey('jwk', result.publicKey)); | |
| promises.push(extractable ? subtle.exportKey('jwk', result.privateKey) : undefined); | |
| } | |
| if (!isHybridKem) { | |
| promises.push(subtle.exportKey('spki', result.publicKey)); | |
| if (extractable) | |
| promises.push(subtle.exportKey('pkcs8', result.privateKey)); | |
| } | |
| switch (result.publicKey.algorithm.name.substring(0, 2)) { | |
| case 'ML': | |
| promises.push(subtle.exportKey('raw-public', result.publicKey)); | |
| if (extractable) | |
| promises.push(subtle.exportKey('raw-seed', result.privateKey)); | |
| break; | |
| case 'SL': | |
| promises.push(subtle.exportKey('raw-public', result.publicKey)); | |
| if (extractable) | |
| promises.push(subtle.exportKey('raw-private', result.privateKey)); | |
| break; | |
| case 'EC': | |
| case 'Ed': | |
| case 'X2': | |
| case 'X4': | |
| promises.push(subtle.exportKey('raw', result.publicKey)); | |
| break; | |
| case 'RS': | |
| break; | |
| default: | |
| throw new Error('not implemented'); | |
| } | |
| const [jwkPub, jwkPriv] = await Promise.all(promises); | |
| if (extractable && !isMlKem) { | |
| // Test that the JWK public key is a superset of the JWK private key. | |
| for (const [prop, value] of Object.entries(jwkPub)) { | |
| if (prop !== 'key_ops') { | |
| assert_equals(value, jwkPriv[prop], `Property ${prop} is equal in public and private JWK`); | |
| } | |
| } | |
| } | |
| } else { | |
| if (extractable) { | |
| await Promise.all([ | |
| subtle.exportKey(/cha|ocb|kmac/i.test(result.algorithm.name) ? 'raw-secret' : 'raw', result), | |
| subtle.exportKey('jwk', result), | |
| ]); | |
| } | |
| } | |
| }, function(err) { | |
| assert_unreached("exportKey threw an unexpected error: " + err.toString()); | |
| }) | |
| }, testTag + ": generateKey" + parameterString(algorithm, extractable, usages)); | |
| // Special case for ECDH and ECDSA: check that the generated key length is consistent. | |
| // Particularly for P-521, there is a high risk of the generated key being one byte short | |
| // if the implementation isn't careful. | |
| if (algorithm.namedCurve && extractable) { | |
| promise_test(async function(test) { | |
| // We run about 20 variants of this test, times 10 key generations below, | |
| // so this should have a decent chance of catching issues. | |
| await Promise.all(Array.from({ length: 10 }).map(async () => { | |
| const { privateKey, publicKey } = await subtle.generateKey(algorithm, extractable, usages); | |
| const [jwkPub, jwkPriv] = await Promise.all([ | |
| subtle.exportKey('jwk', publicKey), | |
| subtle.exportKey('jwk', privateKey), | |
| ]); | |
| const expectedLength = Math.ceil(Math.ceil(parseInt(algorithm.namedCurve.substring(2)) / 8) * 4/3); | |
| assert_equals(jwkPub.x.length, expectedLength, "Public key value x has correct length"); | |
| assert_equals(jwkPub.y.length, expectedLength, "Public key value y has correct length"); | |
| assert_equals(jwkPriv.d.length, expectedLength, "Private key value d has correct length"); | |
| })); | |
| }, testTag + ": generateKey" + parameterString(algorithm, extractable, usages) + " produces consistent length key"); | |
| } | |
| } | |
| // Test all valid sets of parameters for successful | |
| // key generation. | |
| testVectors.forEach(function(vector) { | |
| allNameVariants(vector.name, slowTest).forEach(function(name) { | |
| allAlgorithmSpecifiersFor(name).forEach(function(algorithm) { | |
| allValidUsages(vector.usages, false, vector.mandatoryUsages).forEach(function(usages) { | |
| [false, true].forEach(function(extractable) { | |
| subsetTest(testSuccess, algorithm, extractable, usages, vector.resultType, "Success"); | |
| }); | |
| }); | |
| }); | |
| }); | |
| }); | |
| } |