| // | |
| // helpers.js | |
| // | |
| // Helper functions used by several WebCryptoAPI tests | |
| // | |
| var registeredAlgorithmNames = [ | |
| "RSASSA-PKCS1-v1_5", | |
| "RSA-PSS", | |
| "RSA-OAEP", | |
| "ECDSA", | |
| "ECDH", | |
| "AES-CTR", | |
| "AES-CBC", | |
| "AES-GCM", | |
| "AES-KW", | |
| "HMAC", | |
| "SHA-1", | |
| "SHA-256", | |
| "SHA-384", | |
| "SHA-512", | |
| "HKDF-CTR", | |
| "PBKDF2" | |
| ]; | |
| // Treats an array as a set, and generates an array of all non-empty | |
| // subsets (which are themselves arrays). | |
| // | |
| // The order of members of the "subsets" is not guaranteed. | |
| function allNonemptySubsetsOf(arr) { | |
| var results = []; | |
| var firstElement; | |
| var remainingElements; | |
| for(var i=0; i<arr.length; i++) { | |
| firstElement = arr[i]; | |
| remainingElements = arr.slice(i+1); | |
| results.push([firstElement]); | |
| if (remainingElements.length > 0) { | |
| allNonemptySubsetsOf(remainingElements).forEach(function(combination) { | |
| combination.push(firstElement); | |
| results.push(combination); | |
| }); | |
| } | |
| } | |
| return results; | |
| } | |
| // Create a string representation of keyGeneration parameters for | |
| // test names and labels. | |
| function objectToString(obj) { | |
| var keyValuePairs = []; | |
| if (Array.isArray(obj)) { | |
| return "[" + obj.map(function(elem){return objectToString(elem);}).join(", ") + "]"; | |
| } else if (typeof obj === "object") { | |
| Object.keys(obj).sort().forEach(function(keyName) { | |
| keyValuePairs.push(keyName + ": " + objectToString(obj[keyName])); | |
| }); | |
| return "{" + keyValuePairs.join(", ") + "}"; | |
| } else if (typeof obj === "undefined") { | |
| return "undefined"; | |
| } else { | |
| return obj.toString(); | |
| } | |
| var keyValuePairs = []; | |
| Object.keys(obj).sort().forEach(function(keyName) { | |
| var value = obj[keyName]; | |
| if (typeof value === "object") { | |
| value = objectToString(value); | |
| } else if (typeof value === "array") { | |
| value = "[" + value.map(function(elem){return objectToString(elem);}).join(", ") + "]"; | |
| } else { | |
| value = value.toString(); | |
| } | |
| keyValuePairs.push(keyName + ": " + value); | |
| }); | |
| return "{" + keyValuePairs.join(", ") + "}"; | |
| } | |
| // Is key a CryptoKey object with correct algorithm, extractable, and usages? | |
| // Is it a secret, private, or public kind of key? | |
| function assert_goodCryptoKey(key, algorithm, extractable, usages, kind) { | |
| var correctUsages = []; | |
| var registeredAlgorithmName; | |
| registeredAlgorithmNames.forEach(function(name) { | |
| if (name.toUpperCase() === algorithm.name.toUpperCase()) { | |
| registeredAlgorithmName = name; | |
| } | |
| }); | |
| assert_equals(key.constructor, CryptoKey, "Is a CryptoKey"); | |
| assert_equals(key.type, kind, "Is a " + kind + " key"); | |
| if (key.type === "public") { | |
| extractable = true; // public keys are always extractable | |
| } | |
| assert_equals(key.extractable, extractable, "Extractability is correct"); | |
| assert_equals(key.algorithm.name, registeredAlgorithmName, "Correct algorithm name"); | |
| assert_equals(key.algorithm.length, algorithm.length, "Correct length"); | |
| if (["HMAC", "RSASSA-PKCS1-v1_5", "RSA-PSS"].includes(registeredAlgorithmName)) { | |
| assert_equals(key.algorithm.hash.name.toUpperCase(), algorithm.hash.toUpperCase(), "Correct hash function"); | |
| } | |
| // usages is expected to be provided for a key pair, but we are checking | |
| // only a single key. The publicKey and privateKey portions of a key pair | |
| // recognize only some of the usages appropriate for a key pair. | |
| if (key.type === "public") { | |
| ["encrypt", "verify", "wrapKey"].forEach(function(usage) { | |
| if (usages.includes(usage)) { | |
| correctUsages.push(usage); | |
| } | |
| }); | |
| } else if (key.type === "private") { | |
| ["decrypt", "sign", "unwrapKey", "deriveKey", "deriveBits"].forEach(function(usage) { | |
| if (usages.includes(usage)) { | |
| correctUsages.push(usage); | |
| } | |
| }); | |
| } else { | |
| correctUsages = usages; | |
| } | |
| assert_equals((typeof key.usages), "object", key.type + " key.usages is an object"); | |
| assert_not_equals(key.usages, null, key.type + " key.usages isn't null"); | |
| // The usages parameter could have repeats, but the usages | |
| // property of the result should not. | |
| var usageCount = 0; | |
| key.usages.forEach(function(usage) { | |
| usageCount += 1; | |
| assert_in_array(usage, correctUsages, "Has " + usage + " usage"); | |
| }); | |
| assert_equals(key.usages.length, usageCount, "usages property is correct"); | |
| } | |
| // The algorithm parameter is an object with a name and other | |
| // properties. Given the name, generate all valid parameters. | |
| function allAlgorithmSpecifiersFor(algorithmName) { | |
| var results = []; | |
| // RSA key generation is slow. Test a minimal set of parameters | |
| var hashes = ["SHA-1", "SHA-256"]; | |
| // EC key generation is a lot faster. Check all curves in the spec | |
| var curves = ["P-256", "P-384", "P-521"]; | |
| if (algorithmName.toUpperCase().substring(0, 3) === "AES") { | |
| // Specifier properties are name and length | |
| [128, 192, 256].forEach(function(length) { | |
| results.push({name: algorithmName, length: length}); | |
| }); | |
| } else if (algorithmName.toUpperCase() === "HMAC") { | |
| [ | |
| {name: "SHA-1", length: 160}, | |
| {name: "SHA-256", length: 256}, | |
| {name: "SHA-384", length: 384}, | |
| {name: "SHA-512", length: 512} | |
| ].forEach(function(hashAlgorithm) { | |
| results.push({name: algorithmName, hash: hashAlgorithm.name, length: hashAlgorithm.length}); | |
| }); | |
| } else if (algorithmName.toUpperCase().substring(0, 3) === "RSA") { | |
| hashes.forEach(function(hashName) { | |
| results.push({name: algorithmName, hash: hashName, modulusLength: 2048, publicExponent: new Uint8Array([1,0,1])}); | |
| }); | |
| } else if (algorithmName.toUpperCase().substring(0, 2) === "EC") { | |
| curves.forEach(function(curveName) { | |
| results.push({name: algorithmName, namedCurve: curveName}); | |
| }); | |
| } | |
| return results; | |
| } | |
| // Create every possible valid usages parameter, given legal | |
| // usages. Note that an empty usages parameter is not always valid. | |
| // | |
| // There is an optional parameter - mandatoryUsages. If provided, | |
| // it should be an array containing those usages of which one must be | |
| // included. | |
| function allValidUsages(validUsages, emptyIsValid, mandatoryUsages) { | |
| if (typeof mandatoryUsages === "undefined") { | |
| mandatoryUsages = []; | |
| } | |
| okaySubsets = []; | |
| allNonemptySubsetsOf(validUsages).forEach(function(subset) { | |
| if (mandatoryUsages.length === 0) { | |
| okaySubsets.push(subset); | |
| } else { | |
| for (var i=0; i<mandatoryUsages.length; i++) { | |
| if (subset.includes(mandatoryUsages[i])) { | |
| okaySubsets.push(subset); | |
| return; | |
| } | |
| } | |
| } | |
| }); | |
| if (emptyIsValid) { | |
| okaySubsets.push([]); | |
| } | |
| okaySubsets.push(validUsages.concat(mandatoryUsages).concat(validUsages)); // Repeated values are allowed | |
| return okaySubsets; | |
| } | |
| // Algorithm name specifiers are case-insensitive. Generate several | |
| // case variations of a given name. | |
| function allNameVariants(name, slowTest) { | |
| var upCaseName = name.toUpperCase(); | |
| var lowCaseName = name.toLowerCase(); | |
| var mixedCaseName = upCaseName.substring(0, 1) + lowCaseName.substring(1); | |
| // for slow tests effectively cut the amount of work in third by only | |
| // returning one variation | |
| if (slowTest) return [mixedCaseName]; | |
| return [upCaseName, lowCaseName, mixedCaseName]; | |
| } |