no bug - Import translations from android-l10n r=release a=l10n CLOSED TREE
[gecko.git] / security / manager / ssl / tests / unit / test_content_signing.js
blob1f0f26bd1206b3badb9ff05229dcfb7da0044a7d
1 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3  * License, v. 2.0. If a copy of the MPL was not distributed with this
4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 "use strict";
7 // These tests ensure content signatures are working correctly.
9 const TEST_DATA_DIR = "test_content_signing/";
11 const ONECRL_NAME = "oneCRL-signer.mozilla.org";
12 const ABOUT_NEWTAB_NAME = "remotenewtab.content-signature.mozilla.org";
13 var VERIFICATION_HISTOGRAM = Services.telemetry.getHistogramById(
14   "CONTENT_SIGNATURE_VERIFICATION_STATUS"
16 var ERROR_HISTOGRAM = Services.telemetry.getKeyedHistogramById(
17   "CONTENT_SIGNATURE_VERIFICATION_ERRORS"
20 // Enable the collection (during test) for all products so even products
21 // that don't collect the data will be able to run the test without failure.
22 Services.prefs.setBoolPref(
23   "toolkit.telemetry.testing.overrideProductsCheck",
24   true
27 function getSignatureVerifier() {
28   return Cc["@mozilla.org/security/contentsignatureverifier;1"].getService(
29     Ci.nsIContentSignatureVerifier
30   );
33 function getCertHash(name) {
34   let cert = constructCertFromFile(`test_content_signing/${name}.pem`);
35   return cert.sha256Fingerprint.replace(/:/g, "");
38 function loadChain(prefix, names) {
39   let chain = [];
40   for (let name of names) {
41     let filename = `${prefix}_${name}.pem`;
42     chain.push(readFile(do_get_file(filename)));
43   }
44   return chain;
47 function check_telemetry(expected_index, expected, expectedId) {
48   for (let i = 0; i < 10; i++) {
49     let expected_value = 0;
50     if (i == expected_index) {
51       expected_value = expected;
52     }
53     let errorSnapshot = ERROR_HISTOGRAM.snapshot();
54     for (let k in errorSnapshot) {
55       // We clear the histogram every time so there should be only this one
56       // category.
57       equal(k, expectedId);
58       equal(errorSnapshot[k].values[i] || 0, expected_value);
59     }
60     equal(
61       VERIFICATION_HISTOGRAM.snapshot().values[i] || 0,
62       expected_value,
63       "count " +
64         i +
65         ": " +
66         VERIFICATION_HISTOGRAM.snapshot().values[i] +
67         " expected " +
68         expected_value
69     );
70   }
71   VERIFICATION_HISTOGRAM.clear();
72   ERROR_HISTOGRAM.clear();
75 add_task(async function run_test() {
76   // set up some data
77   const DATA = readFile(do_get_file(TEST_DATA_DIR + "test.txt"));
78   const GOOD_SIGNATURE =
79     "p384ecdsa=" +
80     readFile(do_get_file(TEST_DATA_DIR + "test.txt.signature")).trim();
82   const BAD_SIGNATURE =
83     "p384ecdsa=WqRXFQ7tnlVufpg7A-ZavXvWd2Zln0o4woHBy26C2r" +
84     "UWM4GJke4pE8ecHiXoi-7KnZXty6Pe3s4o3yAIyKDP9jUC52Ek1G" +
85     "q25j_X703nP5rk5gM1qz5Fe-qCWakPPl6L";
87   let remoteNewTabChain = loadChain(TEST_DATA_DIR + "content_signing", [
88     "remote_newtab_ee",
89     "int",
90   ]);
92   let oneCRLChain = loadChain(TEST_DATA_DIR + "content_signing", [
93     "onecrl_ee",
94     "int",
95   ]);
97   let oneCRLBadKeyChain = loadChain(TEST_DATA_DIR + "content_signing", [
98     "onecrl_wrong_key_ee",
99     "int",
100   ]);
102   let noSANChain = loadChain(TEST_DATA_DIR + "content_signing", [
103     "onecrl_no_SAN_ee",
104     "int",
105   ]);
107   let expiredOneCRLChain = loadChain(TEST_DATA_DIR + "content_signing", [
108     "onecrl_ee_expired",
109     "int",
110   ]);
112   let notValidYetOneCRLChain = loadChain(TEST_DATA_DIR + "content_signing", [
113     "onecrl_ee_not_valid_yet",
114     "int",
115   ]);
117   // Check signature verification works without throwing when using the wrong
118   // root
119   VERIFICATION_HISTOGRAM.clear();
120   let chain1 = oneCRLChain.join("\n");
121   let verifier = getSignatureVerifier();
122   ok(
123     !(await verifier.asyncVerifyContentSignature(
124       DATA,
125       GOOD_SIGNATURE,
126       chain1,
127       ONECRL_NAME,
128       Ci.nsIContentSignatureVerifier.ContentSignatureProdRoot
129     )),
130     "using the wrong root, signatures should fail to verify but not throw."
131   );
132   // Check for generic chain building error.
133   check_telemetry(6, 1, getCertHash("content_signing_onecrl_ee"));
135   // Check good signatures from good certificates with the correct SAN
136   ok(
137     await verifier.asyncVerifyContentSignature(
138       DATA,
139       GOOD_SIGNATURE,
140       chain1,
141       ONECRL_NAME,
142       Ci.nsIX509CertDB.AppXPCShellRoot
143     ),
144     "A OneCRL signature should verify with the OneCRL chain"
145   );
146   let chain2 = remoteNewTabChain.join("\n");
147   ok(
148     await verifier.asyncVerifyContentSignature(
149       DATA,
150       GOOD_SIGNATURE,
151       chain2,
152       ABOUT_NEWTAB_NAME,
153       Ci.nsIX509CertDB.AppXPCShellRoot
154     ),
155     "A newtab signature should verify with the newtab chain"
156   );
157   // Check for valid signature
158   check_telemetry(0, 2, getCertHash("content_signing_remote_newtab_ee"));
160   // Check a bad signature when a good chain is provided
161   chain1 = oneCRLChain.join("\n");
162   ok(
163     !(await verifier.asyncVerifyContentSignature(
164       DATA,
165       BAD_SIGNATURE,
166       chain1,
167       ONECRL_NAME,
168       Ci.nsIX509CertDB.AppXPCShellRoot
169     )),
170     "A bad signature should not verify"
171   );
172   // Check for invalid signature
173   check_telemetry(1, 1, getCertHash("content_signing_onecrl_ee"));
175   // Check a good signature from cert with good SAN but a different key than the
176   // one used to create the signature
177   let badKeyChain = oneCRLBadKeyChain.join("\n");
178   ok(
179     !(await verifier.asyncVerifyContentSignature(
180       DATA,
181       GOOD_SIGNATURE,
182       badKeyChain,
183       ONECRL_NAME,
184       Ci.nsIX509CertDB.AppXPCShellRoot
185     )),
186     "A signature should not verify if the signing key is wrong"
187   );
188   // Check for wrong key in cert.
189   check_telemetry(9, 1, getCertHash("content_signing_onecrl_wrong_key_ee"));
191   // Check a good signature from cert with good SAN but a different key than the
192   // one used to create the signature (this time, an RSA key)
193   let rsaKeyChain = oneCRLBadKeyChain.join("\n");
194   ok(
195     !(await verifier.asyncVerifyContentSignature(
196       DATA,
197       GOOD_SIGNATURE,
198       rsaKeyChain,
199       ONECRL_NAME,
200       Ci.nsIX509CertDB.AppXPCShellRoot
201     )),
202     "A signature should not verify if the signing key is wrong (RSA)"
203   );
204   // Check for wrong key in cert.
205   check_telemetry(9, 1, getCertHash("content_signing_onecrl_wrong_key_ee"));
207   // Check a good signature from cert with good SAN but with no path to root
208   let missingInt = [oneCRLChain[0], oneCRLChain[2]].join("\n");
209   ok(
210     !(await verifier.asyncVerifyContentSignature(
211       DATA,
212       GOOD_SIGNATURE,
213       missingInt,
214       ONECRL_NAME,
215       Ci.nsIX509CertDB.AppXPCShellRoot
216     )),
217     "A signature should not verify if the chain is incomplete (missing int)"
218   );
219   // Check for generic chain building error.
220   check_telemetry(6, 1, getCertHash("content_signing_onecrl_ee"));
222   // Check good signatures from good certificates with the wrong SANs
223   chain1 = oneCRLChain.join("\n");
224   ok(
225     !(await verifier.asyncVerifyContentSignature(
226       DATA,
227       GOOD_SIGNATURE,
228       chain1,
229       ABOUT_NEWTAB_NAME,
230       Ci.nsIX509CertDB.AppXPCShellRoot
231     )),
232     "A OneCRL signature should not verify if we require the newtab SAN"
233   );
234   // Check for invalid EE cert.
235   check_telemetry(7, 1, getCertHash("content_signing_onecrl_ee"));
237   chain2 = remoteNewTabChain.join("\n");
238   ok(
239     !(await verifier.asyncVerifyContentSignature(
240       DATA,
241       GOOD_SIGNATURE,
242       chain2,
243       ONECRL_NAME,
244       Ci.nsIX509CertDB.AppXPCShellRoot
245     )),
246     "A newtab signature should not verify if we require the OneCRL SAN"
247   );
248   // Check for invalid EE cert.
249   check_telemetry(7, 1, getCertHash("content_signing_remote_newtab_ee"));
251   // Check good signatures with good chains with some other invalid names
252   ok(
253     !(await verifier.asyncVerifyContentSignature(
254       DATA,
255       GOOD_SIGNATURE,
256       chain1,
257       "",
258       Ci.nsIX509CertDB.AppXPCShellRoot
259     )),
260     "A signature should not verify if the SANs do not match an empty name"
261   );
262   // Check for invalid EE cert.
263   check_telemetry(7, 1, getCertHash("content_signing_onecrl_ee"));
265   // Test expired certificate.
266   let chainExpired = expiredOneCRLChain.join("\n");
267   ok(
268     !(await verifier.asyncVerifyContentSignature(
269       DATA,
270       GOOD_SIGNATURE,
271       chainExpired,
272       "",
273       Ci.nsIX509CertDB.AppXPCShellRoot
274     )),
275     "A signature should not verify if the signing certificate is expired"
276   );
277   // Check for expired cert.
278   check_telemetry(4, 1, getCertHash("content_signing_onecrl_ee_expired"));
280   // Test not valid yet certificate.
281   let chainNotValidYet = notValidYetOneCRLChain.join("\n");
282   ok(
283     !(await verifier.asyncVerifyContentSignature(
284       DATA,
285       GOOD_SIGNATURE,
286       chainNotValidYet,
287       "",
288       Ci.nsIX509CertDB.AppXPCShellRoot
289     )),
290     "A signature should not verify if the signing certificate is not valid yet"
291   );
292   // Check for not yet valid cert.
293   check_telemetry(5, 1, getCertHash("content_signing_onecrl_ee_not_valid_yet"));
295   let relatedName = "subdomain." + ONECRL_NAME;
296   ok(
297     !(await verifier.asyncVerifyContentSignature(
298       DATA,
299       GOOD_SIGNATURE,
300       chain1,
301       relatedName,
302       Ci.nsIX509CertDB.AppXPCShellRoot
303     )),
304     "A signature should not verify if the SANs do not match a related name"
305   );
307   let randomName =
308     "\xb1\x9bU\x1c\xae\xaa3\x19H\xdb\xed\xa1\xa1\xe0\x81\xfb" +
309     "\xb2\x8f\x1cP\xe5\x8b\x9c\xc2s\xd3\x1f\x8e\xbbN";
310   ok(
311     !(await verifier.asyncVerifyContentSignature(
312       DATA,
313       GOOD_SIGNATURE,
314       chain1,
315       randomName,
316       Ci.nsIX509CertDB.AppXPCShellRoot
317     )),
318     "A signature should not verify if the SANs do not match a random name"
319   );
321   // check good signatures with chains that have strange or missing SANs
322   chain1 = noSANChain.join("\n");
323   ok(
324     !(await verifier.asyncVerifyContentSignature(
325       DATA,
326       GOOD_SIGNATURE,
327       chain1,
328       ONECRL_NAME,
329       Ci.nsIX509CertDB.AppXPCShellRoot
330     )),
331     "A signature should not verify if the SANs do not match a supplied name"
332   );
334   // Check malformed signature data
335   chain1 = oneCRLChain.join("\n");
336   let bad_signatures = [
337     // wrong length
338     "p384ecdsa=WqRXFQ7tnlVufpg7A-ZavXvWd2Zln0o4woHBy26C2rUWM4GJke4pE8ecHiXoi-" +
339       "7KnZXty6Pe3s4o3yAIyKDP9jUC52Ek1Gq25j_X703nP5rk5gM1qz5Fe-qCWakPPl6L==",
340     // incorrectly encoded
341     "p384ecdsa='WqRXFQ7tnlVufpg7A-ZavXvWd2Zln0o4woHBy26C2rUWM4GJke4pE8ecHiXoi" +
342       "-7KnZXty6Pe3s4o3yAIyKDP9jUC52Ek1Gq25j_X703nP5rk5gM1qz5Fe-qCWakPPl6L=",
343     // missing directive
344     "other_directive=WqRXFQ7tnlVufpg7A-ZavXvWd2Zln0o4woHBy26C2rUWM4GJke4pE8ec" +
345       "HiXoi-7KnZXty6Pe3s4o3yAIyKDP9jUC52Ek1Gq25j_X703nP5rk5gM1qz5Fe-qCWakPPl6L",
346     // actually sha256 with RSA
347     "p384ecdsa=XS_jiQsS5qlzQyUKaA1nAnQn_OvxhvDfKybflB8Xe5gNH1wNmPGK1qN-jpeTfK" +
348       "6ob3l3gCTXrsMnOXMeht0kPP3wLfVgXbuuO135pQnsv0c-ltRMWLe56Cm4S4Z6E7WWKLPWaj" +
349       "jhAcG5dZxjffP9g7tuPP4lTUJztyc4d1z_zQZakEG7R0vN7P5_CaX9MiMzP4R7nC3H4Ba6yi" +
350       "yjlGvsZwJ_C5zDQzWWs95czUbMzbDScEZ_7AWnidw91jZn-fUK3xLb6m-Zb_b4GAqZ-vnXIf" +
351       "LpLB1Nzal42BQZn7i4rhAldYdcVvy7rOMlsTUb5Zz6vpVW9LCT9lMJ7Sq1xbU-0g==",
352   ];
353   for (let badSig of bad_signatures) {
354     await Assert.rejects(
355       verifier.asyncVerifyContentSignature(
356         DATA,
357         badSig,
358         chain1,
359         ONECRL_NAME,
360         Ci.nsIX509CertDB.AppXPCShellRoot
361       ),
362       /NS_ERROR/,
363       `Bad or malformed signature "${badSig}" should be rejected`
364     );
365   }
367   // Check malformed and missing certificate chain data
368   let chainSuffix = [oneCRLChain[1], oneCRLChain[2]].join("\n");
369   let badChains = [
370     // no data
371     "",
372     // completely wrong data
373     "blah blah \n blah",
374   ];
376   let badSections = [
377     // data that looks like PEM but isn't
378     "-----BEGIN CERTIFICATE-----\nBSsPRlYp5+gaFMRIczwUzaioRfteCjr94xyz0g==\n",
379     // data that will start to parse but won't base64decode
380     "-----BEGIN CERTIFICATE-----\nnon-base64-stuff\n-----END CERTIFICATE-----",
381     // data with garbage outside of PEM sections
382     "this data is garbage\n-----BEGIN CERTIFICATE-----\nnon-base64-stuff\n" +
383       "-----END CERTIFICATE-----",
384   ];
386   for (let badSection of badSections) {
387     // ensure we test each bad section on its own...
388     badChains.push(badSection);
389     // ... and as part of a chain with good certificates
390     badChains.push(badSection + "\n" + chainSuffix);
391   }
393   for (let badChain of badChains) {
394     await Assert.rejects(
395       verifier.asyncVerifyContentSignature(
396         DATA,
397         GOOD_SIGNATURE,
398         badChain,
399         ONECRL_NAME,
400         Ci.nsIX509CertDB.AppXPCShellRoot
401       ),
402       /NS_ERROR/,
403       `Bad chain data starting "${badChain.substring(0, 80)}" ` +
404         "should be rejected"
405     );
406   }
408   ok(
409     !(await verifier.asyncVerifyContentSignature(
410       DATA + "appended data",
411       GOOD_SIGNATURE,
412       chain1,
413       ONECRL_NAME,
414       Ci.nsIX509CertDB.AppXPCShellRoot
415     )),
416     "A good signature should not verify if the data is tampered with (append)"
417   );
418   ok(
419     !(await verifier.asyncVerifyContentSignature(
420       "prefixed data" + DATA,
421       GOOD_SIGNATURE,
422       chain1,
423       ONECRL_NAME,
424       Ci.nsIX509CertDB.AppXPCShellRoot
425     )),
426     "A good signature should not verify if the data is tampered with (prefix)"
427   );
428   ok(
429     !(await verifier.asyncVerifyContentSignature(
430       DATA.replace(/e/g, "i"),
431       GOOD_SIGNATURE,
432       chain1,
433       ONECRL_NAME,
434       Ci.nsIX509CertDB.AppXPCShellRoot
435     )),
436     "A good signature should not verify if the data is tampered with (modify)"
437   );