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