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/. */
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",
27 function getSignatureVerifier() {
28 return Cc["@mozilla.org/security/contentsignatureverifier;1"].getService(
29 Ci.nsIContentSignatureVerifier
33 function getCertHash(name) {
34 let cert = constructCertFromFile(`test_content_signing/${name}.pem`);
35 return cert.sha256Fingerprint.replace(/:/g, "");
38 function loadChain(prefix, names) {
40 for (let name of names) {
41 let filename = `${prefix}_${name}.pem`;
42 chain.push(readFile(do_get_file(filename)));
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;
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
58 equal(errorSnapshot[k].values[i] || 0, expected_value);
61 VERIFICATION_HISTOGRAM.snapshot().values[i] || 0,
66 VERIFICATION_HISTOGRAM.snapshot().values[i] +
71 VERIFICATION_HISTOGRAM.clear();
72 ERROR_HISTOGRAM.clear();
75 add_task(async function run_test() {
77 const DATA = readFile(do_get_file(TEST_DATA_DIR + "test.txt"));
78 const GOOD_SIGNATURE =
80 readFile(do_get_file(TEST_DATA_DIR + "test.txt.signature")).trim();
83 "p384ecdsa=WqRXFQ7tnlVufpg7A-ZavXvWd2Zln0o4woHBy26C2r" +
84 "UWM4GJke4pE8ecHiXoi-7KnZXty6Pe3s4o3yAIyKDP9jUC52Ek1G" +
85 "q25j_X703nP5rk5gM1qz5Fe-qCWakPPl6L";
87 let remoteNewTabChain = loadChain(TEST_DATA_DIR + "content_signing", [
92 let oneCRLChain = loadChain(TEST_DATA_DIR + "content_signing", [
97 let oneCRLBadKeyChain = loadChain(TEST_DATA_DIR + "content_signing", [
98 "onecrl_wrong_key_ee",
102 let noSANChain = loadChain(TEST_DATA_DIR + "content_signing", [
107 let expiredOneCRLChain = loadChain(TEST_DATA_DIR + "content_signing", [
112 let notValidYetOneCRLChain = loadChain(TEST_DATA_DIR + "content_signing", [
113 "onecrl_ee_not_valid_yet",
117 // Check signature verification works without throwing when using the wrong
119 VERIFICATION_HISTOGRAM.clear();
120 let chain1 = oneCRLChain.join("\n");
121 let verifier = getSignatureVerifier();
123 !(await verifier.asyncVerifyContentSignature(
128 Ci.nsIContentSignatureVerifier.ContentSignatureProdRoot
130 "using the wrong root, signatures should fail to verify but not throw."
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
137 await verifier.asyncVerifyContentSignature(
142 Ci.nsIX509CertDB.AppXPCShellRoot
144 "A OneCRL signature should verify with the OneCRL chain"
146 let chain2 = remoteNewTabChain.join("\n");
148 await verifier.asyncVerifyContentSignature(
153 Ci.nsIX509CertDB.AppXPCShellRoot
155 "A newtab signature should verify with the newtab chain"
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");
163 !(await verifier.asyncVerifyContentSignature(
168 Ci.nsIX509CertDB.AppXPCShellRoot
170 "A bad signature should not verify"
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");
179 !(await verifier.asyncVerifyContentSignature(
184 Ci.nsIX509CertDB.AppXPCShellRoot
186 "A signature should not verify if the signing key is wrong"
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");
195 !(await verifier.asyncVerifyContentSignature(
200 Ci.nsIX509CertDB.AppXPCShellRoot
202 "A signature should not verify if the signing key is wrong (RSA)"
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");
210 !(await verifier.asyncVerifyContentSignature(
215 Ci.nsIX509CertDB.AppXPCShellRoot
217 "A signature should not verify if the chain is incomplete (missing int)"
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");
225 !(await verifier.asyncVerifyContentSignature(
230 Ci.nsIX509CertDB.AppXPCShellRoot
232 "A OneCRL signature should not verify if we require the newtab SAN"
234 // Check for invalid EE cert.
235 check_telemetry(7, 1, getCertHash("content_signing_onecrl_ee"));
237 chain2 = remoteNewTabChain.join("\n");
239 !(await verifier.asyncVerifyContentSignature(
244 Ci.nsIX509CertDB.AppXPCShellRoot
246 "A newtab signature should not verify if we require the OneCRL SAN"
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
253 !(await verifier.asyncVerifyContentSignature(
258 Ci.nsIX509CertDB.AppXPCShellRoot
260 "A signature should not verify if the SANs do not match an empty name"
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");
268 !(await verifier.asyncVerifyContentSignature(
273 Ci.nsIX509CertDB.AppXPCShellRoot
275 "A signature should not verify if the signing certificate is expired"
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");
283 !(await verifier.asyncVerifyContentSignature(
288 Ci.nsIX509CertDB.AppXPCShellRoot
290 "A signature should not verify if the signing certificate is not valid yet"
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;
297 !(await verifier.asyncVerifyContentSignature(
302 Ci.nsIX509CertDB.AppXPCShellRoot
304 "A signature should not verify if the SANs do not match a related name"
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";
311 !(await verifier.asyncVerifyContentSignature(
316 Ci.nsIX509CertDB.AppXPCShellRoot
318 "A signature should not verify if the SANs do not match a random name"
321 // check good signatures with chains that have strange or missing SANs
322 chain1 = noSANChain.join("\n");
324 !(await verifier.asyncVerifyContentSignature(
329 Ci.nsIX509CertDB.AppXPCShellRoot
331 "A signature should not verify if the SANs do not match a supplied name"
334 // Check malformed signature data
335 chain1 = oneCRLChain.join("\n");
336 let bad_signatures = [
338 "p384ecdsa=WqRXFQ7tnlVufpg7A-ZavXvWd2Zln0o4woHBy26C2rUWM4GJke4pE8ecHiXoi-" +
339 "7KnZXty6Pe3s4o3yAIyKDP9jUC52Ek1Gq25j_X703nP5rk5gM1qz5Fe-qCWakPPl6L==",
340 // incorrectly encoded
341 "p384ecdsa='WqRXFQ7tnlVufpg7A-ZavXvWd2Zln0o4woHBy26C2rUWM4GJke4pE8ecHiXoi" +
342 "-7KnZXty6Pe3s4o3yAIyKDP9jUC52Ek1Gq25j_X703nP5rk5gM1qz5Fe-qCWakPPl6L=",
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==",
353 for (let badSig of bad_signatures) {
354 await Assert.rejects(
355 verifier.asyncVerifyContentSignature(
360 Ci.nsIX509CertDB.AppXPCShellRoot
363 `Bad or malformed signature "${badSig}" should be rejected`
367 // Check malformed and missing certificate chain data
368 let chainSuffix = [oneCRLChain[1], oneCRLChain[2]].join("\n");
372 // completely wrong data
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-----",
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);
393 for (let badChain of badChains) {
394 await Assert.rejects(
395 verifier.asyncVerifyContentSignature(
400 Ci.nsIX509CertDB.AppXPCShellRoot
403 `Bad chain data starting "${badChain.substring(0, 80)}" ` +
409 !(await verifier.asyncVerifyContentSignature(
410 DATA + "appended data",
414 Ci.nsIX509CertDB.AppXPCShellRoot
416 "A good signature should not verify if the data is tampered with (append)"
419 !(await verifier.asyncVerifyContentSignature(
420 "prefixed data" + DATA,
424 Ci.nsIX509CertDB.AppXPCShellRoot
426 "A good signature should not verify if the data is tampered with (prefix)"
429 !(await verifier.asyncVerifyContentSignature(
430 DATA.replace(/e/g, "i"),
434 Ci.nsIX509CertDB.AppXPCShellRoot
436 "A good signature should not verify if the data is tampered with (modify)"