1// 2// helpers.js 3// 4// Helper functions used by several WebCryptoAPI tests 5// 6 7var 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]; 29 30 31// Treats an array as a set, and generates an array of all non-empty 32// subsets (which are themselves arrays). 33// 34// The order of members of the "subsets" is not guaranteed. 35function allNonemptySubsetsOf(arr) { 36 var results = []; 37 var firstElement; 38 var remainingElements; 39 40 for(var i=0; i<arr.length; i++) { 41 firstElement = arr[i]; 42 remainingElements = arr.slice(i+1); 43 results.push([firstElement]); 44 45 if (remainingElements.length > 0) { 46 allNonemptySubsetsOf(remainingElements).forEach(function(combination) { 47 combination.push(firstElement); 48 results.push(combination); 49 }); 50 } 51 } 52 53 return results; 54} 55 56 57// Create a string representation of keyGeneration parameters for 58// test names and labels. 59function objectToString(obj) { 60 var keyValuePairs = []; 61 62 if (Array.isArray(obj)) { 63 return "[" + obj.map(function(elem){return objectToString(elem);}).join(", ") + "]"; 64 } else if (typeof obj === "object") { 65 Object.keys(obj).sort().forEach(function(keyName) { 66 keyValuePairs.push(keyName + ": " + objectToString(obj[keyName])); 67 }); 68 return "{" + keyValuePairs.join(", ") + "}"; 69 } else if (typeof obj === "undefined") { 70 return "undefined"; 71 } else { 72 return obj.toString(); 73 } 74 75 var keyValuePairs = []; 76 77 Object.keys(obj).sort().forEach(function(keyName) { 78 var value = obj[keyName]; 79 if (typeof value === "object") { 80 value = objectToString(value); 81 } else if (typeof value === "array") { 82 value = "[" + value.map(function(elem){return objectToString(elem);}).join(", ") + "]"; 83 } else { 84 value = value.toString(); 85 } 86 87 keyValuePairs.push(keyName + ": " + value); 88 }); 89 90 return "{" + keyValuePairs.join(", ") + "}"; 91} 92 93// Is key a CryptoKey object with correct algorithm, extractable, and usages? 94// Is it a secret, private, or public kind of key? 95function assert_goodCryptoKey(key, algorithm, extractable, usages, kind) { 96 var correctUsages = []; 97 98 var registeredAlgorithmName; 99 registeredAlgorithmNames.forEach(function(name) { 100 if (name.toUpperCase() === algorithm.name.toUpperCase()) { 101 registeredAlgorithmName = name; 102 } 103 }); 104 105 assert_equals(key.constructor, CryptoKey, "Is a CryptoKey"); 106 assert_equals(key.type, kind, "Is a " + kind + " key"); 107 assert_equals(key.extractable, extractable, "Extractability is correct"); 108 109 assert_equals(key.algorithm.name, registeredAlgorithmName, "Correct algorithm name"); 110 if (key.algorithm.name.toUpperCase() === "HMAC" && algorithm.length === undefined) { 111 switch (key.algorithm.hash.name.toUpperCase()) { 112 case 'SHA-1': 113 case 'SHA-256': 114 assert_equals(key.algorithm.length, 512, "Correct length"); 115 break; 116 case 'SHA-384': 117 case 'SHA-512': 118 assert_equals(key.algorithm.length, 1024, "Correct length"); 119 break; 120 default: 121 assert_unreached("Unrecognized hash"); 122 } 123 } else { 124 assert_equals(key.algorithm.length, algorithm.length, "Correct length"); 125 } 126 if (["HMAC", "RSASSA-PKCS1-v1_5", "RSA-PSS"].includes(registeredAlgorithmName)) { 127 assert_equals(key.algorithm.hash.name.toUpperCase(), algorithm.hash.toUpperCase(), "Correct hash function"); 128 } 129 130 if (/^(?:Ed|X)(?:25519|448)$/.test(key.algorithm.name)) { 131 assert_false('namedCurve' in key.algorithm, "Does not have a namedCurve property"); 132 } 133 134 // usages is expected to be provided for a key pair, but we are checking 135 // only a single key. The publicKey and privateKey portions of a key pair 136 // recognize only some of the usages appropriate for a key pair. 137 if (key.type === "public") { 138 ["encrypt", "verify", "wrapKey"].forEach(function(usage) { 139 if (usages.includes(usage)) { 140 correctUsages.push(usage); 141 } 142 }); 143 } else if (key.type === "private") { 144 ["decrypt", "sign", "unwrapKey", "deriveKey", "deriveBits"].forEach(function(usage) { 145 if (usages.includes(usage)) { 146 correctUsages.push(usage); 147 } 148 }); 149 } else { 150 correctUsages = usages; 151 } 152 153 assert_equals((typeof key.usages), "object", key.type + " key.usages is an object"); 154 assert_not_equals(key.usages, null, key.type + " key.usages isn't null"); 155 156 // The usages parameter could have repeats, but the usages 157 // property of the result should not. 158 var usageCount = 0; 159 key.usages.forEach(function(usage) { 160 usageCount += 1; 161 assert_in_array(usage, correctUsages, "Has " + usage + " usage"); 162 }); 163 assert_equals(key.usages.length, usageCount, "usages property is correct"); 164 assert_equals(key[Symbol.toStringTag], 'CryptoKey', "has the expected Symbol.toStringTag"); 165} 166 167 168// The algorithm parameter is an object with a name and other 169// properties. Given the name, generate all valid parameters. 170function allAlgorithmSpecifiersFor(algorithmName) { 171 var results = []; 172 173 // RSA key generation is slow. Test a minimal set of parameters 174 var hashes = ["SHA-1", "SHA-256"]; 175 176 // EC key generation is a lot faster. Check all curves in the spec 177 var curves = ["P-256", "P-384", "P-521"]; 178 179 if (algorithmName.toUpperCase().substring(0, 3) === "AES") { 180 // Specifier properties are name and length 181 [128, 192, 256].forEach(function(length) { 182 results.push({name: algorithmName, length: length}); 183 }); 184 } else if (algorithmName.toUpperCase() === "HMAC") { 185 [ 186 {hash: "SHA-1", length: 160}, 187 {hash: "SHA-256", length: 256}, 188 {hash: "SHA-384", length: 384}, 189 {hash: "SHA-512", length: 512}, 190 {hash: "SHA-1"}, 191 {hash: "SHA-256"}, 192 {hash: "SHA-384"}, 193 {hash: "SHA-512"}, 194 ].forEach(function(hashAlgorithm) { 195 results.push({name: algorithmName, ...hashAlgorithm}); 196 }); 197 } else if (algorithmName.toUpperCase().substring(0, 3) === "RSA") { 198 hashes.forEach(function(hashName) { 199 results.push({name: algorithmName, hash: hashName, modulusLength: 2048, publicExponent: new Uint8Array([1,0,1])}); 200 }); 201 } else if (algorithmName.toUpperCase().substring(0, 2) === "EC") { 202 curves.forEach(function(curveName) { 203 results.push({name: algorithmName, namedCurve: curveName}); 204 }); 205 } else if (algorithmName.toUpperCase().substring(0, 1) === "X" || algorithmName.toUpperCase().substring(0, 2) === "ED") { 206 results.push({ name: algorithmName }); 207 } 208 209 return results; 210} 211 212 213// Create every possible valid usages parameter, given legal 214// usages. Note that an empty usages parameter is not always valid. 215// 216// There is an optional parameter - mandatoryUsages. If provided, 217// it should be an array containing those usages of which one must be 218// included. 219function allValidUsages(validUsages, emptyIsValid, mandatoryUsages) { 220 if (typeof mandatoryUsages === "undefined") { 221 mandatoryUsages = []; 222 } 223 224 var okaySubsets = []; 225 allNonemptySubsetsOf(validUsages).forEach(function(subset) { 226 if (mandatoryUsages.length === 0) { 227 okaySubsets.push(subset); 228 } else { 229 for (var i=0; i<mandatoryUsages.length; i++) { 230 if (subset.includes(mandatoryUsages[i])) { 231 okaySubsets.push(subset); 232 return; 233 } 234 } 235 } 236 }); 237 238 if (emptyIsValid) { 239 okaySubsets.push([]); 240 } 241 242 okaySubsets.push(validUsages.concat(mandatoryUsages).concat(validUsages)); // Repeated values are allowed 243 return okaySubsets; 244} 245 246function unique(names) { 247 return [...new Set(names)]; 248} 249 250// Algorithm name specifiers are case-insensitive. Generate several 251// case variations of a given name. 252function allNameVariants(name, slowTest) { 253 var upCaseName = name.toUpperCase(); 254 var lowCaseName = name.toLowerCase(); 255 var mixedCaseName = upCaseName.substring(0, 1) + lowCaseName.substring(1); 256 257 // for slow tests effectively cut the amount of work in third by only 258 // returning one variation 259 if (slowTest) return [mixedCaseName]; 260 return unique([upCaseName, lowCaseName, mixedCaseName]); 261} 262