tor-browser

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

test_session_resumption.js (9475B)


      1 // -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
      2 // Any copyright is dedicated to the Public Domain.
      3 // http://creativecommons.org/publicdomain/zero/1.0/
      4 "use strict";
      5 
      6 // Tests that PSM makes the correct determination of the security status of
      7 // loads involving session resumption (i.e. when a TLS handshake bypasses the
      8 // AuthCertificate callback).
      9 
     10 do_get_profile();
     11 const certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
     12  Ci.nsIX509CertDB
     13 );
     14 
     15 registerCleanupFunction(() => {
     16  Services.prefs.clearUserPref("security.OCSP.enabled");
     17 });
     18 
     19 Services.prefs.setIntPref("security.OCSP.enabled", 1);
     20 
     21 addCertFromFile(certdb, "bad_certs/evroot.pem", "CTu,,");
     22 addCertFromFile(certdb, "bad_certs/ev-test-intermediate.pem", ",,");
     23 
     24 // For expired.example.com, the platform will make a connection that will fail.
     25 // Using information gathered at that point, an override will be added and
     26 // another connection will be made. This connection will succeed. At that point,
     27 // as long as the session cache isn't cleared, subsequent new connections should
     28 // use session resumption, thereby bypassing the AuthCertificate hook. We need
     29 // to ensure that the correct security state is propagated to the new connection
     30 // information object.
     31 function add_resume_non_ev_with_override_test() {
     32  // This adds the override and makes one successful connection.
     33  add_cert_override_test("expired.example.com", SEC_ERROR_EXPIRED_CERTIFICATE);
     34 
     35  // This connects again, using session resumption. Note that we don't clear
     36  // the TLS session cache between these operations (that would defeat the
     37  // purpose).
     38  add_connection_test(
     39    "expired.example.com",
     40    PRErrorCodeSuccess,
     41    null,
     42    transportSecurityInfo => {
     43      ok(transportSecurityInfo.resumed, "connection should be resumed");
     44      ok(
     45        transportSecurityInfo.securityState &
     46          Ci.nsIWebProgressListener.STATE_CERT_USER_OVERRIDDEN,
     47        "expired.example.com should have STATE_CERT_USER_OVERRIDDEN flag"
     48      );
     49      equal(
     50        transportSecurityInfo.succeededCertChain.length,
     51        0,
     52        "expired.example.com should not have succeededCertChain set"
     53      );
     54      equal(
     55        transportSecurityInfo.handshakeCertificates.length,
     56        2,
     57        "expired.example.com should have handshakeCertificates set"
     58      );
     59      equal(
     60        transportSecurityInfo.overridableErrorCategory,
     61        Ci.nsITransportSecurityInfo.ERROR_TIME,
     62        "expired.example.com should have time overridable error category"
     63      );
     64      ok(
     65        !transportSecurityInfo.isExtendedValidation,
     66        "expired.example.com should not have isExtendedValidation set"
     67      );
     68 
     69      let certOverrideService = Cc[
     70        "@mozilla.org/security/certoverride;1"
     71      ].getService(Ci.nsICertOverrideService);
     72      certOverrideService.clearValidityOverride(
     73        "expired.example.com",
     74        8443,
     75        {}
     76      );
     77    }
     78  );
     79 }
     80 
     81 // Helper function that adds a test that connects to ev-test.example.com and
     82 // verifies that it validates as EV (or not, if we're running a non-debug
     83 // build). This assumes that an appropriate OCSP responder is running or that
     84 // good responses are cached.
     85 function add_one_ev_test(resumed) {
     86  add_connection_test(
     87    "ev-test.example.com",
     88    PRErrorCodeSuccess,
     89    null,
     90    transportSecurityInfo => {
     91      equal(
     92        transportSecurityInfo.resumed,
     93        resumed,
     94        "connection should be resumed or not resumed as expected"
     95      );
     96      ok(
     97        !(
     98          transportSecurityInfo.securityState &
     99          Ci.nsIWebProgressListener.STATE_CERT_USER_OVERRIDDEN
    100        ),
    101        "ev-test.example.com should not have STATE_CERT_USER_OVERRIDDEN flag"
    102      );
    103      equal(
    104        transportSecurityInfo.succeededCertChain.length,
    105        3,
    106        "ev-test.example.com should have succeededCertChain set"
    107      );
    108      equal(
    109        transportSecurityInfo.handshakeCertificates.length,
    110        2,
    111        "ev-test.example.com should have handshakeCertificates set"
    112      );
    113      equal(
    114        transportSecurityInfo.overridableErrorCategory,
    115        Ci.nsITransportSecurityInfo.ERROR_UNSET,
    116        "ev-test.example.com should not have an overridable error category"
    117      );
    118      ok(
    119        !gEVExpected || transportSecurityInfo.isExtendedValidation,
    120        "ev-test.example.com should have isExtendedValidation set " +
    121          "(or this is a non-debug build)"
    122      );
    123    }
    124  );
    125 }
    126 
    127 // This test is similar, except with extended validation. We should connect
    128 // successfully, and the certificate should be EV in debug builds. Without
    129 // clearing the session cache, we should connect successfully again, this time
    130 // with session resumption. The certificate should again be EV in debug builds.
    131 function add_resume_ev_test() {
    132  const SERVER_PORT = 8888;
    133  let expectedRequestPaths = ["ev-test"];
    134  let responseTypes = ["good"];
    135  // Since we cache OCSP responses, we only ever actually serve one set.
    136  let ocspResponder;
    137  // If we don't wrap this in an `add_test`, the OCSP responder will be running
    138  // while we are actually running unrelated testcases, which can disrupt them.
    139  add_test(() => {
    140    ocspResponder = startOCSPResponder(
    141      SERVER_PORT,
    142      "localhost",
    143      "bad_certs",
    144      expectedRequestPaths,
    145      expectedRequestPaths.slice(),
    146      null,
    147      responseTypes
    148    );
    149    run_next_test();
    150  });
    151  // We should be able to connect and verify the certificate as EV (in debug
    152  // builds).
    153  add_one_ev_test(false);
    154  // We should be able to connect again (using session resumption). In debug
    155  // builds, the certificate should be noted as EV. Again, it's important that
    156  // nothing clears the TLS cache in between these two operations.
    157  add_one_ev_test(true);
    158 
    159  add_test(() => {
    160    ocspResponder.stop(run_next_test);
    161  });
    162 }
    163 
    164 const GOOD_DOMAIN = "good.include-subdomains.pinning.example.com";
    165 
    166 // Helper function that adds a test that connects to a domain that should
    167 // succeed (but isn't EV) and verifies that its succeededCertChain gets set
    168 // appropriately.
    169 function add_one_non_ev_test() {
    170  add_connection_test(
    171    GOOD_DOMAIN,
    172    PRErrorCodeSuccess,
    173    null,
    174    transportSecurityInfo => {
    175      ok(
    176        !(
    177          transportSecurityInfo.securityState &
    178          Ci.nsIWebProgressListener.STATE_CERT_USER_OVERRIDDEN
    179        ),
    180        `${GOOD_DOMAIN} should not have STATE_CERT_USER_OVERRIDDEN flag`
    181      );
    182      ok(
    183        transportSecurityInfo.succeededCertChain,
    184        `${GOOD_DOMAIN} should have succeededCertChain set`
    185      );
    186      equal(
    187        transportSecurityInfo.overridableErrorCategory,
    188        0,
    189        `${GOOD_DOMAIN} should not have an overridable error category set`
    190      );
    191      ok(
    192        !transportSecurityInfo.isExtendedValidation,
    193        `${GOOD_DOMAIN} should not have isExtendedValidation set`
    194      );
    195    }
    196  );
    197 }
    198 
    199 // This test is similar, except with non-extended validation. We should connect
    200 // successfully, and the certificate should not be EV. Without clearing the
    201 // session cache, we should connect successfully again, this time with session
    202 // resumption. In this case, though, we want to ensure the succeededCertChain is
    203 // set.
    204 function add_resume_non_ev_test() {
    205  add_one_non_ev_test();
    206  add_one_non_ev_test();
    207 }
    208 
    209 const statsPtr = getSSLStatistics();
    210 const toInt32 = ctypes.Int64.lo;
    211 
    212 // Connect to the same domain with two origin attributes and check if any ssl
    213 // session is resumed.
    214 function add_origin_attributes_test(
    215  originAttributes1,
    216  originAttributes2,
    217  resumeExpected
    218 ) {
    219  add_connection_test(
    220    GOOD_DOMAIN,
    221    PRErrorCodeSuccess,
    222    clearSessionCache,
    223    null,
    224    null,
    225    originAttributes1
    226  );
    227 
    228  let hitsBeforeConnect;
    229  let missesBeforeConnect;
    230  let expectedHits = resumeExpected ? 1 : 0;
    231  let expectedMisses = 1 - expectedHits;
    232 
    233  add_connection_test(
    234    GOOD_DOMAIN,
    235    PRErrorCodeSuccess,
    236    function () {
    237      // Add the hits and misses before connection.
    238      let stats = statsPtr.contents;
    239      hitsBeforeConnect = toInt32(stats.sch_sid_cache_hits);
    240      missesBeforeConnect = toInt32(stats.sch_sid_cache_misses);
    241    },
    242    function () {
    243      let stats = statsPtr.contents;
    244      equal(
    245        toInt32(stats.sch_sid_cache_hits),
    246        hitsBeforeConnect + expectedHits,
    247        "Unexpected cache hits"
    248      );
    249      equal(
    250        toInt32(stats.sch_sid_cache_misses),
    251        missesBeforeConnect + expectedMisses,
    252        "Unexpected cache misses"
    253      );
    254    },
    255    null,
    256    originAttributes2
    257  );
    258 }
    259 
    260 function add_resumption_tests() {
    261  add_resume_ev_test();
    262  add_resume_non_ev_test();
    263  add_resume_non_ev_with_override_test();
    264  add_origin_attributes_test({}, {}, true);
    265  add_origin_attributes_test({ userContextId: 1 }, { userContextId: 2 }, false);
    266  add_origin_attributes_test({ userContextId: 3 }, { userContextId: 3 }, true);
    267  add_origin_attributes_test(
    268    { firstPartyDomain: "foo.com" },
    269    { firstPartyDomain: "bar.com" },
    270    false
    271  );
    272  add_origin_attributes_test(
    273    { firstPartyDomain: "baz.com" },
    274    { firstPartyDomain: "baz.com" },
    275    true
    276  );
    277 }
    278 
    279 function run_test() {
    280  add_tls_server_setup("BadCertAndPinningServer", "bad_certs");
    281  add_resumption_tests();
    282  // Enable external session cache and reset the status.
    283  add_test(function () {
    284    Services.prefs.setBoolPref("network.ssl_tokens_cache_enabled", true);
    285    certdb.clearOCSPCache();
    286    run_next_test();
    287  });
    288  // Do tests again.
    289  add_resumption_tests();
    290  run_next_test();
    291 }