helpers.js 9.7 KB

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