summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/WebCryptoAPI/generateKey/failures.js
blob: c39e4d211cbdf466efd59a2cf822b4aa8a50f896 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
function run_test(algorithmNames) {
    var subtle = crypto.subtle; // Change to test prefixed implementations

    setup({explicit_timeout: true});

// These tests check that generateKey throws an error, and that
// the error is of the right type, for a wide set of incorrect parameters.
//
// Error testing occurs by setting the parameter that should trigger the
// error to an invalid value, then combining that with all valid
// parameters that should be checked earlier by generateKey, and all
// valid and invalid parameters that should be checked later by
// generateKey.
//
// There are a lot of combinations of possible parameters for both
// success and failure modes, resulting in a very large number of tests
// performed.


// Setup: define the correct behaviors that should be sought, and create
// helper functions that generate all possible test parameters for
// different situations.

    var allTestVectors = [ // Parameters that should work for generateKey
        {name: "AES-CTR",  resultType: CryptoKey, usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], mandatoryUsages: []},
        {name: "AES-CBC",  resultType: CryptoKey, usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], mandatoryUsages: []},
        {name: "AES-GCM",  resultType: CryptoKey, usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], mandatoryUsages: []},
        {name: "AES-KW",   resultType: CryptoKey, usages: ["wrapKey", "unwrapKey"], mandatoryUsages: []},
        {name: "HMAC",     resultType: CryptoKey, usages: ["sign", "verify"], mandatoryUsages: []},
        {name: "RSASSA-PKCS1-v1_5", resultType: "CryptoKeyPair", usages: ["sign", "verify"], mandatoryUsages: ["sign"]},
        {name: "RSA-PSS",  resultType: "CryptoKeyPair", usages: ["sign", "verify"], mandatoryUsages: ["sign"]},
        {name: "RSA-OAEP", resultType: "CryptoKeyPair", usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], mandatoryUsages: ["decrypt", "unwrapKey"]},
        {name: "ECDSA",    resultType: "CryptoKeyPair", usages: ["sign", "verify"], mandatoryUsages: ["sign"]},
        {name: "ECDH",     resultType: "CryptoKeyPair", usages: ["deriveKey", "deriveBits"], mandatoryUsages: ["deriveKey", "deriveBits"]},
        {name: "Ed25519",  resultType: "CryptoKeyPair", usages: ["sign", "verify"], mandatoryUsages: ["sign"]},
        {name: "Ed448",    resultType: "CryptoKeyPair", usages: ["sign", "verify"], mandatoryUsages: ["sign"]},
        {name: "X25519",   resultType: "CryptoKeyPair", usages: ["deriveKey", "deriveBits"], mandatoryUsages: ["deriveKey", "deriveBits"]},
        {name: "X448",     resultType: "CryptoKeyPair", usages: ["deriveKey", "deriveBits"], mandatoryUsages: ["deriveKey", "deriveBits"]},
    ];

    var testVectors = [];
    if (algorithmNames && !Array.isArray(algorithmNames)) {
        algorithmNames = [algorithmNames];
    };
    allTestVectors.forEach(function(vector) {
        if (!algorithmNames || algorithmNames.includes(vector.name)) {
            testVectors.push(vector);
        }
    });


    function parameterString(algorithm, extractable, usages) {
        if (typeof algorithm !== "object" && typeof algorithm !== "string") {
            alert(algorithm);
        }

        var result = "(" +
                        objectToString(algorithm) + ", " +
                        objectToString(extractable) + ", " +
                        objectToString(usages) +
                     ")";

        return result;
    }

    // Test that a given combination of parameters results in an error,
    // AND that it is the correct kind of error.
    //
    // Expected error is either a number, tested against the error code,
    // or a string, tested against the error name.
    function testError(algorithm, extractable, usages, expectedError, testTag) {
        promise_test(function(test) {
            return crypto.subtle.generateKey(algorithm, extractable, usages)
            .then(function(result) {
                assert_unreached("Operation succeeded, but should not have");
            }, function(err) {
                if (typeof expectedError === "number") {
                    assert_equals(err.code, expectedError, testTag + " not supported");
                } else {
                    assert_equals(err.name, expectedError, testTag + " not supported");
                }
            });
        }, testTag + ": generateKey" + parameterString(algorithm, extractable, usages));
    }


    // Given an algorithm name, create several invalid parameters.
    function badAlgorithmPropertySpecifiersFor(algorithmName) {
        var results = [];

        if (algorithmName.toUpperCase().substring(0, 3) === "AES") {
            // Specifier properties are name and length
            [64, 127, 129, 255, 257, 512].forEach(function(length) {
                results.push({name: algorithmName, length: length});
            });
        } else if (algorithmName.toUpperCase().substring(0, 3) === "RSA") {
            [new Uint8Array([1]), new Uint8Array([1,0,0])].forEach(function(publicExponent) {
                results.push({name: algorithmName, hash: "SHA-256", modulusLength: 1024, publicExponent: publicExponent});
            });
        } else if (algorithmName.toUpperCase().substring(0, 2) === "EC") {
            ["P-512", "Curve25519"].forEach(function(curveName) {
                results.push({name: algorithmName, namedCurve: curveName});
            });
        }

        return results;
    }


    // Don't create an exhaustive list of all invalid usages,
    // because there would usually be nearly 2**8 of them,
    // way too many to test. Instead, create every singleton
    // of an illegal usage, and "poison" every valid usage
    // with an illegal one.
    function invalidUsages(validUsages, mandatoryUsages) {
        var results = [];

        var illegalUsages = [];
        ["encrypt", "decrypt", "sign", "verify", "wrapKey", "unwrapKey", "deriveKey", "deriveBits"].forEach(function(usage) {
            if (!validUsages.includes(usage)) {
                illegalUsages.push(usage);
            }
        });

        var goodUsageCombinations = allValidUsages(validUsages, false, mandatoryUsages);

        illegalUsages.forEach(function(illegalUsage) {
            results.push([illegalUsage]);
            goodUsageCombinations.forEach(function(usageCombination) {
                results.push(usageCombination.concat([illegalUsage]));
            });
        });

        return results;
    }


// Now test for properly handling errors
// - Unsupported algorithm
// - Bad usages for algorithm
// - Bad key lengths

    // Algorithm normalization should fail with "Not supported"
    var badAlgorithmNames = [
        "AES",
        {name: "AES"},
        {name: "AES", length: 128},
        {name: "AES-CMAC", length: 128},    // Removed after CR
        {name: "AES-CFB", length: 128},      // Removed after CR
        {name: "HMAC", hash: "MD5"},
        {name: "RSA", hash: "SHA-256", modulusLength: 2048, publicExponent: new Uint8Array([1,0,1])},
        {name: "RSA-PSS", hash: "SHA", modulusLength: 2048, publicExponent: new Uint8Array([1,0,1])},
        {name: "EC", namedCurve: "P521"}
    ];


    // Algorithm normalization failures should be found first
    // - all other parameters can be good or bad, should fail
    //   due to NotSupportedError.
    badAlgorithmNames.forEach(function(algorithm) {
        allValidUsages(["decrypt", "sign", "deriveBits"], true, []) // Small search space, shouldn't matter because should fail before used
        .forEach(function(usages) {
            [false, true, "RED", 7].forEach(function(extractable){
                testError(algorithm, extractable, usages, "NotSupportedError", "Bad algorithm");
            });
        });
    });


    // Algorithms normalize okay, but usages bad (though not empty).
    // It shouldn't matter what other extractable is. Should fail
    // due to SyntaxError
    testVectors.forEach(function(vector) {
        var name = vector.name;

        allAlgorithmSpecifiersFor(name).forEach(function(algorithm) {
            invalidUsages(vector.usages, vector.mandatoryUsages).forEach(function(usages) {
                [true].forEach(function(extractable) {
                    testError(algorithm, extractable, usages, "SyntaxError", "Bad usages");
                });
            });
        });
    });


    // Other algorithm properties should be checked next, so try good
    // algorithm names and usages, but bad algorithm properties next.
    // - Special case: normally bad usage [] isn't checked until after properties,
    //   so it's included in this test case. It should NOT cause an error.
    testVectors.forEach(function(vector) {
        var name = vector.name;
        badAlgorithmPropertySpecifiersFor(name).forEach(function(algorithm) {
            allValidUsages(vector.usages, true, vector.mandatoryUsages)
            .forEach(function(usages) {
                [false, true].forEach(function(extractable) {
                    if (name.substring(0,2) === "EC") {
                        testError(algorithm, extractable, usages, "NotSupportedError", "Bad algorithm property");
                    } else {
                        testError(algorithm, extractable, usages, "OperationError", "Bad algorithm property");
                    }
                });
            });
        });
    });


    // The last thing that should be checked is an empty usages (for secret keys).
    testVectors.forEach(function(vector) {
        var name = vector.name;

        allAlgorithmSpecifiersFor(name).forEach(function(algorithm) {
            var usages = [];
            [false, true].forEach(function(extractable) {
                testError(algorithm, extractable, usages, "SyntaxError", "Empty usages");
            });
        });
    });


}