helpers.js (10895B)
1 // 2 // helpers.js 3 // 4 // Helper functions used by several WebCryptoAPI tests 5 // 6 7 var registeredAlgorithmNames = [ 8 "RSASSA-PKCS1-v1_5", 9 "RSA-PSS", 10 "RSA-OAEP", 11 "ECDSA", 12 "ECDH", 13 "AES-CTR", 14 "AES-CBC", 15 "AES-GCM", 16 "AES-KW", 17 "HMAC", 18 "SHA-1", 19 "SHA-256", 20 "SHA-384", 21 "SHA-512", 22 "HKDF", 23 "PBKDF2", 24 "Ed25519", 25 "Ed448", 26 "X25519", 27 "X448", 28 "ML-DSA-44", 29 "ML-DSA-65", 30 "ML-DSA-87", 31 "ML-KEM-512", 32 "ML-KEM-768", 33 "ML-KEM-1024", 34 "ChaCha20-Poly1305", 35 "Argon2i", 36 "Argon2d", 37 "Argon2id", 38 "AES-OCB", 39 "KMAC128", 40 "KMAC256", 41 ]; 42 43 44 // Treats an array as a set, and generates an array of all non-empty 45 // subsets (which are themselves arrays). 46 // 47 // The order of members of the "subsets" is not guaranteed. 48 function allNonemptySubsetsOf(arr) { 49 var results = []; 50 var firstElement; 51 var remainingElements; 52 53 for(var i=0; i<arr.length; i++) { 54 firstElement = arr[i]; 55 remainingElements = arr.slice(i+1); 56 results.push([firstElement]); 57 58 if (remainingElements.length > 0) { 59 allNonemptySubsetsOf(remainingElements).forEach(function(combination) { 60 combination.push(firstElement); 61 results.push(combination); 62 }); 63 } 64 } 65 66 return results; 67 } 68 69 70 // Create a string representation of keyGeneration parameters for 71 // test names and labels. 72 function objectToString(obj) { 73 var keyValuePairs = []; 74 75 if (Array.isArray(obj)) { 76 return "[" + obj.map(function(elem){return objectToString(elem);}).join(", ") + "]"; 77 } else if (typeof obj === "object") { 78 Object.keys(obj).sort().forEach(function(keyName) { 79 keyValuePairs.push(keyName + ": " + objectToString(obj[keyName])); 80 }); 81 return "{" + keyValuePairs.join(", ") + "}"; 82 } else if (typeof obj === "undefined") { 83 return "undefined"; 84 } else { 85 return obj.toString(); 86 } 87 88 var keyValuePairs = []; 89 90 Object.keys(obj).sort().forEach(function(keyName) { 91 var value = obj[keyName]; 92 if (typeof value === "object") { 93 value = objectToString(value); 94 } else if (typeof value === "array") { 95 value = "[" + value.map(function(elem){return objectToString(elem);}).join(", ") + "]"; 96 } else { 97 value = value.toString(); 98 } 99 100 keyValuePairs.push(keyName + ": " + value); 101 }); 102 103 return "{" + keyValuePairs.join(", ") + "}"; 104 } 105 106 // Is key a CryptoKey object with correct algorithm, extractable, and usages? 107 // Is it a secret, private, or public kind of key? 108 function assert_goodCryptoKey(key, algorithm, extractable, usages, kind) { 109 if (typeof algorithm === "string") { 110 algorithm = { name: algorithm }; 111 } 112 113 var correctUsages = []; 114 115 var registeredAlgorithmName; 116 registeredAlgorithmNames.forEach(function(name) { 117 if (name.toUpperCase() === algorithm.name.toUpperCase()) { 118 registeredAlgorithmName = name; 119 } 120 }); 121 122 assert_equals(key.constructor, CryptoKey, "Is a CryptoKey"); 123 assert_equals(key.type, kind, "Is a " + kind + " key"); 124 assert_equals(key.extractable, extractable, "Extractability is correct"); 125 126 assert_equals(key.algorithm.name, registeredAlgorithmName, "Correct algorithm name"); 127 if (key.algorithm.name.toUpperCase() === "HMAC" && algorithm.length === undefined) { 128 switch (key.algorithm.hash.name.toUpperCase()) { 129 case 'SHA-1': 130 case 'SHA-256': 131 assert_equals(key.algorithm.length, 512, "Correct length"); 132 break; 133 case 'SHA-384': 134 case 'SHA-512': 135 assert_equals(key.algorithm.length, 1024, "Correct length"); 136 break; 137 default: 138 assert_unreached("Unrecognized hash"); 139 } 140 } else if (key.algorithm.name.toUpperCase().startsWith("KMAC") && algorithm.length === undefined) { 141 switch (key.algorithm.name.toUpperCase()) { 142 case 'KMAC128': 143 assert_equals(key.algorithm.length, 128, "Correct length"); 144 break; 145 case 'KMAC256': 146 assert_equals(key.algorithm.length, 256, "Correct length"); 147 break; 148 } 149 } else { 150 assert_equals(key.algorithm.length, algorithm.length, "Correct length"); 151 } 152 if (["HMAC", "RSASSA-PKCS1-v1_5", "RSA-PSS"].includes(registeredAlgorithmName)) { 153 assert_equals(key.algorithm.hash.name.toUpperCase(), algorithm.hash.toUpperCase(), "Correct hash function"); 154 } 155 156 if (/^(?:Ed|X)(?:25519|448)$/.test(key.algorithm.name)) { 157 assert_false('namedCurve' in key.algorithm, "Does not have a namedCurve property"); 158 } 159 160 // usages is expected to be provided for a key pair, but we are checking 161 // only a single key. The publicKey and privateKey portions of a key pair 162 // recognize only some of the usages appropriate for a key pair. 163 if (key.type === "public") { 164 ["encrypt", "verify", "wrapKey", "encapsulateBits", "encapsulateKey"].forEach(function(usage) { 165 if (usages.includes(usage)) { 166 correctUsages.push(usage); 167 } 168 }); 169 } else if (key.type === "private") { 170 ["decrypt", "sign", "unwrapKey", "deriveKey", "deriveBits", "decapsulateBits", "decapsulateKey"].forEach(function(usage) { 171 if (usages.includes(usage)) { 172 correctUsages.push(usage); 173 } 174 }); 175 } else { 176 correctUsages = usages; 177 } 178 179 assert_equals((typeof key.usages), "object", key.type + " key.usages is an object"); 180 assert_not_equals(key.usages, null, key.type + " key.usages isn't null"); 181 182 // The usages parameter could have repeats, but the usages 183 // property of the result should not. 184 var usageCount = 0; 185 key.usages.forEach(function(usage) { 186 usageCount += 1; 187 assert_in_array(usage, correctUsages, "Has " + usage + " usage"); 188 }); 189 assert_equals(key.usages.length, usageCount, "usages property is correct"); 190 assert_equals(key[Symbol.toStringTag], 'CryptoKey', "has the expected Symbol.toStringTag"); 191 } 192 193 194 // The algorithm parameter is an object with a name and other 195 // properties. Given the name, generate all valid parameters. 196 function allAlgorithmSpecifiersFor(algorithmName) { 197 var results = []; 198 199 // RSA key generation is slow. Test a minimal set of parameters 200 var hashes = ["SHA-1", "SHA-256"]; 201 202 // EC key generation is a lot faster. Check all curves in the spec 203 var curves = ["P-256", "P-384", "P-521"]; 204 205 if (algorithmName.toUpperCase().substring(0, 3) === "AES") { 206 // Specifier properties are name and length 207 [128, 192, 256].forEach(function(length) { 208 results.push({name: algorithmName, length: length}); 209 }); 210 } else if (algorithmName.toUpperCase() === "HMAC") { 211 [ 212 {hash: "SHA-1", length: 160}, 213 {hash: "SHA-256", length: 256}, 214 {hash: "SHA-384", length: 384}, 215 {hash: "SHA-512", length: 512}, 216 {hash: "SHA-1"}, 217 {hash: "SHA-256"}, 218 {hash: "SHA-384"}, 219 {hash: "SHA-512"}, 220 ].forEach(function(hashAlgorithm) { 221 results.push({name: algorithmName, ...hashAlgorithm}); 222 }); 223 } else if (algorithmName.toUpperCase().substring(0, 3) === "RSA") { 224 hashes.forEach(function(hashName) { 225 results.push({name: algorithmName, hash: hashName, modulusLength: 2048, publicExponent: new Uint8Array([1,0,1])}); 226 }); 227 } else if (algorithmName.toUpperCase().substring(0, 2) === "EC") { 228 curves.forEach(function(curveName) { 229 results.push({name: algorithmName, namedCurve: curveName}); 230 }); 231 } else if (algorithmName.toUpperCase().startsWith("KMAC")) { 232 [ 233 {length: 128}, 234 {length: 160}, 235 {length: 256}, 236 ].forEach(function(hashAlgorithm) { 237 results.push({name: algorithmName, ...hashAlgorithm}); 238 }); 239 } else { 240 results.push(algorithmName); 241 results.push({ name: algorithmName }); 242 } 243 244 return results; 245 } 246 247 248 // Create every possible valid usages parameter, given legal 249 // usages. Note that an empty usages parameter is not always valid. 250 // 251 // There is an optional parameter - mandatoryUsages. If provided, 252 // it should be an array containing those usages of which one must be 253 // included. 254 function allValidUsages(validUsages, emptyIsValid, mandatoryUsages) { 255 if (typeof mandatoryUsages === "undefined") { 256 mandatoryUsages = []; 257 } 258 259 var okaySubsets = []; 260 allNonemptySubsetsOf(validUsages).forEach(function(subset) { 261 if (mandatoryUsages.length === 0) { 262 okaySubsets.push(subset); 263 } else { 264 for (var i=0; i<mandatoryUsages.length; i++) { 265 if (subset.includes(mandatoryUsages[i])) { 266 okaySubsets.push(subset); 267 return; 268 } 269 } 270 } 271 }); 272 273 if (emptyIsValid && validUsages.length !== 0) { 274 okaySubsets.push([]); 275 } 276 277 okaySubsets.push(validUsages.concat(mandatoryUsages).concat(validUsages)); // Repeated values are allowed 278 return okaySubsets; 279 } 280 281 function unique(names) { 282 return [...new Set(names)]; 283 } 284 285 // Algorithm name specifiers are case-insensitive. Generate several 286 // case variations of a given name. 287 function allNameVariants(name, slowTest) { 288 var upCaseName = name.toUpperCase(); 289 var lowCaseName = name.toLowerCase(); 290 var mixedCaseName = upCaseName.substring(0, 1) + lowCaseName.substring(1); 291 292 // for slow tests effectively cut the amount of work in third by only 293 // returning one variation 294 if (slowTest) return [mixedCaseName]; 295 return unique([upCaseName, lowCaseName, mixedCaseName]); 296 } 297 298 // Builds a hex string representation for an array-like input. 299 // "bytes" can be an Array of bytes, an ArrayBuffer, or any TypedArray. 300 // The output looks like this: 301 // ab034c99 302 function bytesToHexString(bytes) 303 { 304 if (!bytes) 305 return null; 306 307 bytes = new Uint8Array(bytes); 308 var hexBytes = []; 309 310 for (var i = 0; i < bytes.length; ++i) { 311 var byteString = bytes[i].toString(16); 312 if (byteString.length < 2) 313 byteString = "0" + byteString; 314 hexBytes.push(byteString); 315 } 316 317 return hexBytes.join(""); 318 } 319 320 function hexStringToUint8Array(hexString) 321 { 322 if (hexString.length % 2 != 0) 323 throw "Invalid hexString"; 324 var arrayBuffer = new Uint8Array(hexString.length / 2); 325 326 for (var i = 0; i < hexString.length; i += 2) { 327 var byteValue = parseInt(hexString.substr(i, 2), 16); 328 if (byteValue == NaN) 329 throw "Invalid hexString"; 330 arrayBuffer[i/2] = byteValue; 331 } 332 333 return arrayBuffer; 334 }