• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1
2function run_test() {
3    setup({explicit_done: true});
4
5    var subtle = self.crypto.subtle; // Change to test prefixed implementations
6
7    // When are all these tests really done? When all the promises they use have resolved.
8    var all_promises = [];
9
10    // Source file [algorithm_name]_vectors.js provides the getTestVectors method
11    // for the algorithm that drives these tests.
12    var testVectors = getTestVectors();
13
14    // Test verification first, because signing tests rely on that working
15    testVectors.forEach(function(vector) {
16        var promise = importVectorKeys(vector, ["verify"], ["sign"])
17        .then(function(vectors) {
18            promise_test(function(test) {
19                var operation = subtle.verify(vector.algorithm, vector.publicKey, vector.signature, vector.plaintext)
20                .then(function(is_verified) {
21                    assert_true(is_verified, "Signature verified");
22                }, function(err) {
23                    assert_unreached("Verification should not throw error " + vector.name + ": " + err.message + "'");
24                });
25
26                return operation;
27            }, vector.name + " verification");
28
29        }, function(err) {
30            // We need a failed test if the importVectorKey operation fails, so
31            // we know we never tested verification.
32            promise_test(function(test) {
33                assert_unreached("importVectorKeys failed for " + vector.name + ". Message: ''" + err.message + "''");
34            }, "importVectorKeys step: " + vector.name + " verification");
35        });
36
37        all_promises.push(promise);
38    });
39
40    // Test verification with an altered buffer after call
41    testVectors.forEach(function(vector) {
42        var promise = importVectorKeys(vector, ["verify"], ["sign"])
43        .then(function(vectors) {
44            promise_test(function(test) {
45                var signature = copyBuffer(vector.signature);
46                var operation = subtle.verify(vector.algorithm, vector.publicKey, signature, vector.plaintext)
47                .then(function(is_verified) {
48                    assert_true(is_verified, "Signature verified");
49                }, function(err) {
50                    assert_unreached("Verification should not throw error " + vector.name + ": " + err.message + "'");
51                });
52
53                signature[0] = 255 - signature[0];
54                return operation;
55            }, vector.name + " verification with altered signature after call");
56        }, function(err) {
57            promise_test(function(test) {
58                assert_unreached("importVectorKeys failed for " + vector.name + ". Message: ''" + err.message + "''");
59            }, "importVectorKeys step: " + vector.name + " verification with altered signature after call");
60        });
61
62        all_promises.push(promise);
63    });
64
65    // Check for successful verification even if plaintext is altered after call.
66    testVectors.forEach(function(vector) {
67        var promise = importVectorKeys(vector, ["verify"], ["sign"])
68        .then(function(vectors) {
69            promise_test(function(test) {
70                var plaintext = copyBuffer(vector.plaintext);
71                var operation = subtle.verify(vector.algorithm, vector.publicKey, vector.signature, plaintext)
72                .then(function(is_verified) {
73                    assert_true(is_verified, "Signature verified");
74                }, function(err) {
75                    assert_unreached("Verification should not throw error " + vector.name + ": " + err.message + "'");
76                });
77
78                plaintext[0] = 255 - plaintext[0];
79                return operation;
80            }, vector.name + " with altered plaintext after call");
81        }, function(err) {
82            promise_test(function(test) {
83                assert_unreached("importVectorKeys failed for " + vector.name + ". Message: ''" + err.message + "''");
84            }, "importVectorKeys step: " + vector.name + " with altered plaintext after call");
85        });
86
87        all_promises.push(promise);
88    });
89
90    // Check for failures due to using privateKey to verify.
91    testVectors.forEach(function(vector) {
92        var promise = importVectorKeys(vector, ["verify"], ["sign"])
93        .then(function(vectors) {
94            promise_test(function(test) {
95                return subtle.verify(vector.algorithm, vector.privateKey, vector.signature, vector.plaintext)
96                .then(function(plaintext) {
97                    assert_unreached("Should have thrown error for using privateKey to verify in " + vector.name + ": " + err.message + "'");
98                }, function(err) {
99                    assert_equals(err.name, "InvalidAccessError", "Should throw InvalidAccessError instead of '" + err.message + "'");
100                });
101            }, vector.name + " using privateKey to verify");
102
103        }, function(err) {
104            promise_test(function(test) {
105                assert_unreached("importVectorKeys failed for " + vector.name + ". Message: ''" + err.message + "''");
106            }, "importVectorKeys step: " + vector.name + " using privateKey to verify");
107        });
108
109        all_promises.push(promise);
110    });
111
112    // Check for failures due to using publicKey to sign.
113    testVectors.forEach(function(vector) {
114        var promise = importVectorKeys(vector, ["verify"], ["sign"])
115        .then(function(vectors) {
116            promise_test(function(test) {
117                return subtle.sign(vector.algorithm, vector.publicKey, vector.plaintext)
118                .then(function(signature) {
119                    assert_unreached("Should have thrown error for using publicKey to sign in " + vector.name + ": " + err.message + "'");
120                }, function(err) {
121                    assert_equals(err.name, "InvalidAccessError", "Should throw InvalidAccessError instead of '" + err.message + "'");
122                });
123            }, vector.name + " using publicKey to sign");
124        }, function(err) {
125            promise_test(function(test) {
126                assert_unreached("importVectorKeys failed for " + vector.name + ". Message: ''" + err.message + "''");
127            }, "importVectorKeys step: " + vector.name + " using publicKey to sign");
128        });
129
130        all_promises.push(promise);
131    });
132
133    // Check for failures due to no "verify" usage.
134    testVectors.forEach(function(originalVector) {
135        var vector = Object.assign({}, originalVector);
136
137        var promise = importVectorKeys(vector, [], ["sign"])
138        .then(function(vectors) {
139            promise_test(function(test) {
140                return subtle.verify(vector.algorithm, vector.publicKey, vector.signature, vector.plaintext)
141                .then(function(plaintext) {
142                    assert_unreached("Should have thrown error for no verify usage in " + vector.name + ": " + err.message + "'");
143                }, function(err) {
144                    assert_equals(err.name, "InvalidAccessError", "Should throw InvalidAccessError instead of '" + err.message + "'");
145                });
146            }, vector.name + " no verify usage");
147        }, function(err) {
148            promise_test(function(test) {
149                assert_unreached("importVectorKeys failed for " + vector.name + ". Message: ''" + err.message + "''");
150            }, "importVectorKeys step: " + vector.name + " no verify usage");
151        });
152
153        all_promises.push(promise);
154    });
155
156    // Check for successful signing and verification.
157    testVectors.forEach(function(vector) {
158        // RSA signing is deterministic with PKCS#1 v1.5, or PSS with zero-length salts.
159        const isDeterministic = !("saltLength" in vector.algorithm) || vector.algorithm.saltLength == 0;
160        var promise = importVectorKeys(vector, ["verify"], ["sign"])
161        .then(function(vectors) {
162            promise_test(function(test) {
163                return subtle.sign(vector.algorithm, vector.privateKey, vector.plaintext)
164                .then(function(signature) {
165                    if (isDeterministic) {
166                        // If deterministic, we can check the output matches. Otherwise, we can only check it verifies.
167                        assert_true(equalBuffers(signature, vector.signature), "Signing did not give the expected output");
168                    }
169                    // Can we verify the new signature?
170                    return subtle.verify(vector.algorithm, vector.publicKey, signature, vector.plaintext)
171                    .then(function(is_verified) {
172                        assert_true(is_verified, "Round trip verifies");
173                        return signature;
174                    }, function(err) {
175                        assert_unreached("verify error for test " + vector.name + ": " + err.message + "'");
176                    });
177                })
178                .then(function(priorSignature) {
179                    // Will a second signing give us different signature? It should for PSS with non-empty salt
180                    return subtle.sign(vector.algorithm, vector.privateKey, vector.plaintext)
181                    .then(function(signature) {
182                        if (isDeterministic) {
183                            assert_true(equalBuffers(priorSignature, signature), "Two signings with empty salt give same signature")
184                        } else {
185                            assert_false(equalBuffers(priorSignature, signature), "Two signings with a salt give different signatures")
186                        }
187                    }, function(err) {
188                        assert_unreached("second time verify error for test " + vector.name + ": '" + err.message + "'");
189                    });
190                }, function(err) {
191                    assert_unreached("sign error for test " + vector.name + ": '" + err.message + "'");
192                });
193            }, vector.name + " round trip");
194
195        }, function(err) {
196            // We need a failed test if the importVectorKey operation fails, so
197            // we know we never tested signing or verifying
198            promise_test(function(test) {
199                assert_unreached("importVectorKeys failed for " + vector.name + ". Message: ''" + err.message + "''");
200            }, "importVectorKeys step: " + vector.name + " round trip");
201        });
202
203        all_promises.push(promise);
204    });
205
206
207    // Test signing with the wrong algorithm
208    testVectors.forEach(function(vector) {
209        // Want to get the key for the wrong algorithm
210        var alteredVector = Object.assign({}, vector);
211        alteredVector.algorithm = Object.assign({}, vector.algorithm);
212        if (vector.algorithm.name === "RSA-PSS") {
213            alteredVector.algorithm.name = "RSASSA-PKCS1-v1_5";
214        } else {
215            alteredVector.algorithm.name = "RSA-PSS";
216        }
217
218        var promise = importVectorKeys(alteredVector, ["verify"], ["sign"])
219        .then(function(vectors) {
220            promise_test(function(test) {
221                var operation = subtle.sign(vector.algorithm, alteredVector.privateKey, vector.plaintext)
222                .then(function(signature) {
223                    assert_unreached("Signing should not have succeeded for " + vector.name);
224                }, function(err) {
225                    assert_equals(err.name, "InvalidAccessError", "Should have thrown InvalidAccessError instead of '" + err.message + "'");
226                });
227
228                return operation;
229            }, vector.name + " signing with wrong algorithm name");
230
231        }, function(err) {
232            // We need a failed test if the importVectorKey operation fails, so
233            // we know we never tested verification.
234            promise_test(function(test) {
235                assert_unreached("importVectorKeys failed for " + vector.name + ". Message: ''" + err.message + "''");
236            }, "importVectorKeys step: " + vector.name + " signing with wrong algorithm name");
237        });
238
239        all_promises.push(promise);
240    });
241
242    // Test verification with the wrong algorithm
243    testVectors.forEach(function(vector) {
244        // Want to get the key for the wrong algorithm
245        var alteredVector = Object.assign({}, vector);
246        alteredVector.algorithm = Object.assign({}, vector.algorithm);
247        if (vector.algorithm.name === "RSA-PSS") {
248            alteredVector.algorithm.name = "RSASSA-PKCS1-v1_5";
249        } else {
250            alteredVector.algorithm.name = "RSA-PSS";
251        }
252
253        var promise = importVectorKeys(alteredVector, ["verify"], ["sign"])
254        .then(function(vectors) {
255            // Some tests are sign only
256            if (!("signature" in vector)) {
257                return;
258            }
259            promise_test(function(test) {
260                var operation = subtle.verify(vector.algorithm, alteredVector.publicKey, vector.signature, vector.plaintext)
261                .then(function(is_verified) {
262                    assert_unreached("Verification should not have succeeded for " + vector.name);
263                }, function(err) {
264                    assert_equals(err.name, "InvalidAccessError", "Should have thrown InvalidAccessError instead of '" + err.message + "'");
265                });
266
267                return operation;
268            }, vector.name + " verification with wrong algorithm name");
269
270        }, function(err) {
271            // We need a failed test if the importVectorKey operation fails, so
272            // we know we never tested verification.
273            promise_test(function(test) {
274                assert_unreached("importVectorKeys failed for " + vector.name + ". Message: ''" + err.message + "''");
275            }, "importVectorKeys step: " + vector.name + " verification with wrong algorithm name");
276        });
277
278        all_promises.push(promise);
279    });
280
281    // Verification should fail with wrong signature
282    testVectors.forEach(function(vector) {
283        var promise = importVectorKeys(vector, ["verify"], ["sign"])
284        .then(function(vectors) {
285            promise_test(function(test) {
286                var signature = copyBuffer(vector.signature);
287                signature[0] = 255 - signature[0];
288                var operation = subtle.verify(vector.algorithm, vector.publicKey, signature, vector.plaintext)
289                .then(function(is_verified) {
290                    assert_false(is_verified, "Signature NOT verified");
291                }, function(err) {
292                    assert_unreached("Verification should not throw error " + vector.name + ": " + err.message + "'");
293                });
294
295                return operation;
296            }, vector.name + " verification failure with altered signature");
297
298        }, function(err) {
299            // We need a failed test if the importVectorKey operation fails, so
300            // we know we never tested verification.
301            promise_test(function(test) {
302                assert_unreached("importVectorKeys failed for " + vector.name + ". Message: ''" + err.message + "''");
303            }, "importVectorKeys step: " + vector.name + " verification failure with altered signature");
304        });
305
306        all_promises.push(promise);
307    });
308
309    // [RSA-PSS] Verification should fail with wrong saltLength
310    testVectors.forEach(function(vector) {
311        if (vector.algorithm.name === "RSA-PSS") {
312            var promise = importVectorKeys(vector, ["verify"], ["sign"])
313            .then(function(vectors) {
314                promise_test(function(test) {
315                    const saltLength = vector.algorithm.saltLength === 32 ? 48 : 32;
316                    var operation = subtle.verify({ ...vector.algorithm, saltLength }, vector.publicKey, vector.signature, vector.plaintext)
317                    .then(function(is_verified) {
318                        assert_false(is_verified, "Signature NOT verified");
319                    }, function(err) {
320                        assert_unreached("Verification should not throw error " + vector.name + ": " + err.message + "'");
321                    });
322
323                    return operation;
324                }, vector.name + " verification failure with wrong saltLength");
325
326            }, function(err) {
327                // We need a failed test if the importVectorKey operation fails, so
328                // we know we never tested verification.
329                promise_test(function(test) {
330                    assert_unreached("importVectorKeys failed for " + vector.name + ". Message: ''" + err.message + "''");
331                }, "importVectorKeys step: " + vector.name + " verification failure with wrong saltLength");
332            });
333
334            all_promises.push(promise);
335        }
336    });
337
338    // Verification should fail with wrong plaintext
339    testVectors.forEach(function(vector) {
340        var promise = importVectorKeys(vector, ["verify"], ["sign"])
341        .then(function(vectors) {
342            promise_test(function(test) {
343                var plaintext = copyBuffer(vector.plaintext);
344                plaintext[0] = 255 - plaintext[0];
345                var operation = subtle.verify(vector.algorithm, vector.publicKey, vector.signature, plaintext)
346                .then(function(is_verified) {
347                    assert_false(is_verified, "Signature NOT verified");
348                }, function(err) {
349                    assert_unreached("Verification should not throw error " + vector.name + ": " + err.message + "'");
350                });
351
352                return operation;
353            }, vector.name + " verification failure with altered plaintext");
354
355        }, function(err) {
356            // We need a failed test if the importVectorKey operation fails, so
357            // we know we never tested verification.
358            promise_test(function(test) {
359                assert_unreached("importVectorKeys failed for " + vector.name + ". Message: ''" + err.message + "''");
360            }, "importVectorKeys step: " + vector.name + " verification failure with altered plaintext");
361        });
362
363        all_promises.push(promise);
364    });
365
366
367    promise_test(function() {
368        return Promise.all(all_promises)
369            .then(function() {done();})
370            .catch(function() {done();})
371    }, "setup");
372
373    // A test vector has all needed fields for signing and verifying, EXCEPT that the
374    // key field may be null. This function replaces that null with the Correct
375    // CryptoKey object.
376    //
377    // Returns a Promise that yields an updated vector on success.
378    function importVectorKeys(vector, publicKeyUsages, privateKeyUsages) {
379        var publicPromise, privatePromise;
380
381        if (vector.publicKey !== null) {
382            publicPromise = new Promise(function(resolve, reject) {
383                resolve(vector);
384            });
385        } else {
386            publicPromise = subtle.importKey(vector.publicKeyFormat, vector.publicKeyBuffer, {name: vector.algorithm.name, hash: vector.hash}, false, publicKeyUsages)
387            .then(function(key) {
388                vector.publicKey = key;
389                return vector;
390            });        // Returns a copy of the sourceBuffer it is sent.
391        }
392
393        if (vector.privateKey !== null) {
394            privatePromise = new Promise(function(resolve, reject) {
395                resolve(vector);
396            });
397        } else {
398            privatePromise = subtle.importKey(vector.privateKeyFormat, vector.privateKeyBuffer, {name: vector.algorithm.name, hash: vector.hash}, false, privateKeyUsages)
399            .then(function(key) {
400                vector.privateKey = key;
401                return vector;
402            });
403        }
404
405        return Promise.all([publicPromise, privatePromise]);
406    }
407
408    // Returns a copy of the sourceBuffer it is sent.
409    function copyBuffer(sourceBuffer) {
410        var source = new Uint8Array(sourceBuffer);
411        var copy = new Uint8Array(sourceBuffer.byteLength)
412
413        for (var i=0; i<source.byteLength; i++) {
414            copy[i] = source[i];
415        }
416
417        return copy;
418    }
419
420    function equalBuffers(a, b) {
421        if (a.byteLength !== b.byteLength) {
422            return false;
423        }
424
425        var aBytes = new Uint8Array(a);
426        var bBytes = new Uint8Array(b);
427
428        for (var i=0; i<a.byteLength; i++) {
429            if (aBytes[i] !== bBytes[i]) {
430                return false;
431            }
432        }
433
434        return true;
435    }
436
437    return;
438}
439