tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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 });