• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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