// These tests check that generateKey throws an error, and that // the error is of the right type, for a wide set of incorrect parameters. // // Error testing occurs by setting the parameter that should trigger the // error to an invalid value, then combining that with all valid // parameters that should be checked earlier by generateKey, and all // valid and invalid parameters that should be checked later by // generateKey. // // There are a lot of combinations of possible parameters for both // success and failure modes, 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. function parameterString(algorithm, extractable, usages) { if (typeof algorithm !== "object" && typeof algorithm !== "string") { alert(algorithm); } var result = "(" + objectToString(algorithm) + ", " + objectToString(extractable) + ", " + objectToString(usages) + ")"; return result; } // Test that a given combination of parameters results in an error, // AND that it is the correct kind of error. // // Expected error is either a number, tested against the error code, // or a string, tested against the error name. function testError(algorithm, extractable, usages, expectedError, testTag) { promise_test(function(test) { return crypto.subtle.generateKey(algorithm, extractable, usages) .then(function(result) { assert_unreached("Operation succeeded, but should not have"); }, function(err) { if (typeof expectedError === "number") { assert_equals(err.code, expectedError, testTag + " not supported"); } else { assert_equals(err.name, expectedError, testTag + " not supported"); } }); }, testTag + ": generateKey" + parameterString(algorithm, extractable, usages)); } // Algorithm normalization happens before generateKey looks at any other // argument, so these cases are independent of the algorithm under test and // only need to run once for the whole suite. function run_bad_algorithm_test() { // Algorithm normalization should fail with "Not supported" var badAlgorithmNames = [ "AES", {name: "AES"}, {name: "AES", length: 128}, {name: "AES-CMAC", length: 128}, // Removed after CR {name: "AES-CFB", length: 128}, // Removed after CR {name: "HMAC", hash: "MD5"}, {name: "RSA", hash: "SHA-256", modulusLength: 2048, publicExponent: new Uint8Array([1,0,1])}, {name: "RSA-PSS", hash: "SHA", modulusLength: 2048, publicExponent: new Uint8Array([1,0,1])}, {name: "EC", namedCurve: "P521"} ]; // Algorithm normalization failures should be found first // - all other parameters can be good or bad, should fail // due to NotSupportedError. badAlgorithmNames.forEach(function(algorithm) { allValidUsages(["decrypt", "sign", "deriveBits"], true, []) // Small search space, shouldn't matter because should fail before used .forEach(function(usages) { [false, true, "RED", 7].forEach(function(extractable){ testError(algorithm, extractable, usages, "NotSupportedError", "Bad algorithm"); }); }); }); // Empty algorithm should fail with TypeError allValidUsages(["decrypt", "sign", "deriveBits"], true, []) // Small search space, shouldn't matter because should fail before used .forEach(function(usages) { [false, true, "RED", 7].forEach(function(extractable){ testError({}, extractable, usages, "TypeError", "Empty algorithm"); }); }); } function run_test(algorithmNames) { var testVectors = getGenerateKeyTestVectors(algorithmNames); // Given an algorithm name, create several invalid parameters. function badAlgorithmPropertySpecifiersFor(algorithmName) { var results = []; if (algorithmName.toUpperCase().substring(0, 3) === "AES") { // Specifier properties are name and length [64, 127, 129, 255, 257, 512, 128 + 2**32, 128 - 2**32].forEach(function(length) { results.push({name: algorithmName, length: length}); }); } else if (algorithmName.toUpperCase() === "HMAC") { [128 + 2**32, 128 - 2**32].forEach(function(length) { results.push({name: algorithmName, hash: "SHA-256", length: length}); }); } else if (algorithmName.toUpperCase().substring(0, 3) === "RSA") { [new Uint8Array([1]), new Uint8Array([1,0,0])].forEach(function(publicExponent) { results.push({name: algorithmName, hash: "SHA-256", modulusLength: 1024, publicExponent: publicExponent}); }); results.push({name: algorithmName, hash: "SHA-256", modulusLength: 1024 + 2 ** 32, publicExponent: new Uint8Array([1,0,1])}); results.push({name: algorithmName, hash: "SHA-256", modulusLength: 1024 - 2 ** 32, publicExponent: new Uint8Array([1,0,1])}); } else if (algorithmName.toUpperCase().substring(0, 2) === "EC") { ["P-512", "Curve25519"].forEach(function(curveName) { results.push({name: algorithmName, namedCurve: curveName}); }); } return results; } // Don't create an exhaustive list of all invalid usages because // there would be too many to test. Instead, create every singleton // of an illegal usage, and "poison" every valid usage // with an illegal one. function invalidUsages(validUsages, mandatoryUsages) { var results = []; var illegalUsages = []; allKeyUsages.forEach(function(usage) { if (!validUsages.includes(usage)) { illegalUsages.push(usage); } }); var goodUsageCombinations = allValidUsages(validUsages, false, mandatoryUsages); illegalUsages.forEach(function(illegalUsage) { results.push([illegalUsage]); goodUsageCombinations.forEach(function(usageCombination) { results.push(usageCombination.concat([illegalUsage])); }); }); return results; } // Now test for properly handling errors // - Bad usages for algorithm // - Bad key lengths // Algorithms normalize okay, but usages bad (though not empty). // It shouldn't matter what other extractable is. Should fail // due to SyntaxError testVectors.forEach(function(vector) { var name = vector.name; allAlgorithmSpecifiersFor(name).forEach(function(algorithm) { invalidUsages(vector.usages, vector.mandatoryUsages).forEach(function(usages) { [true].forEach(function(extractable) { testError(algorithm, extractable, usages, "SyntaxError", "Bad usages"); }); }); }); }); // Other algorithm properties should be checked next, so try good // algorithm names and usages, but bad algorithm properties next. // - Special case: normally bad usage [] isn't checked until after properties, // so it's included in this test case. It should NOT cause an error. testVectors.forEach(function(vector) { var name = vector.name; badAlgorithmPropertySpecifiersFor(name).forEach(function(algorithm) { allValidUsages(vector.usages, true, vector.mandatoryUsages) .forEach(function(usages) { [false, true].forEach(function(extractable) { if (name.substring(0,2) === "EC") { testError(algorithm, extractable, usages, "NotSupportedError", "Bad algorithm property"); } else if (name.substring(0,3) === "RSA" && (algorithm.modulusLength < 0 || algorithm.modulusLength > 2**32) || (name.substring(0,3) === "AES" || name === "HMAC") && (algorithm.length < 0 || algorithm.length > 2**32)) { testError(algorithm, extractable, usages, "TypeError", "Bad algorithm property"); } else { testError(algorithm, extractable, usages, "OperationError", "Bad algorithm property"); } }); }); }); }); // The last thing that should be checked is empty usages (disallowed for secret and private keys). testVectors.forEach(function(vector) { var name = vector.name; allAlgorithmSpecifiersFor(name).forEach(function(algorithm) { var usages = []; [false, true].forEach(function(extractable) { testError(algorithm, extractable, usages, "SyntaxError", "Empty usages"); }); }); }); }