summaryrefslogtreecommitdiffstats
path: root/security/manager/ssl/tests/unit/test_session_resumption.js
blob: fe7252a6303e24eba921f9df0b0fa5ce23f507a5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
// Any copyright is dedicated to the Public Domain.
// http://creativecommons.org/publicdomain/zero/1.0/
"use strict";

// Tests that PSM makes the correct determination of the security status of
// loads involving session resumption (i.e. when a TLS handshake bypasses the
// AuthCertificate callback).

do_get_profile();
const certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
  Ci.nsIX509CertDB
);

registerCleanupFunction(() => {
  Services.prefs.clearUserPref("security.OCSP.enabled");
});

Services.prefs.setIntPref("security.OCSP.enabled", 1);

addCertFromFile(certdb, "bad_certs/evroot.pem", "CTu,,");
addCertFromFile(certdb, "bad_certs/ev-test-intermediate.pem", ",,");

// For expired.example.com, the platform will make a connection that will fail.
// Using information gathered at that point, an override will be added and
// another connection will be made. This connection will succeed. At that point,
// as long as the session cache isn't cleared, subsequent new connections should
// use session resumption, thereby bypassing the AuthCertificate hook. We need
// to ensure that the correct security state is propagated to the new connection
// information object.
function add_resume_non_ev_with_override_test() {
  // This adds the override and makes one successful connection.
  add_cert_override_test("expired.example.com", SEC_ERROR_EXPIRED_CERTIFICATE);

  // This connects again, using session resumption. Note that we don't clear
  // the TLS session cache between these operations (that would defeat the
  // purpose).
  add_connection_test(
    "expired.example.com",
    PRErrorCodeSuccess,
    null,
    transportSecurityInfo => {
      ok(transportSecurityInfo.resumed, "connection should be resumed");
      ok(
        transportSecurityInfo.securityState &
          Ci.nsIWebProgressListener.STATE_CERT_USER_OVERRIDDEN,
        "expired.example.com should have STATE_CERT_USER_OVERRIDDEN flag"
      );
      equal(
        transportSecurityInfo.succeededCertChain.length,
        0,
        "expired.example.com should not have succeededCertChain set"
      );
      equal(
        transportSecurityInfo.failedCertChain.length,
        2,
        "expired.example.com should have failedCertChain set"
      );
      equal(
        transportSecurityInfo.overridableErrorCategory,
        Ci.nsITransportSecurityInfo.ERROR_TIME,
        "expired.example.com should have time overridable error category"
      );
      ok(
        !transportSecurityInfo.isExtendedValidation,
        "expired.example.com should not have isExtendedValidation set"
      );

      let certOverrideService = Cc[
        "@mozilla.org/security/certoverride;1"
      ].getService(Ci.nsICertOverrideService);
      certOverrideService.clearValidityOverride(
        "expired.example.com",
        8443,
        {}
      );
    }
  );
}

// Helper function that adds a test that connects to ev-test.example.com and
// verifies that it validates as EV (or not, if we're running a non-debug
// build). This assumes that an appropriate OCSP responder is running or that
// good responses are cached.
function add_one_ev_test(resumed) {
  add_connection_test(
    "ev-test.example.com",
    PRErrorCodeSuccess,
    null,
    transportSecurityInfo => {
      equal(
        transportSecurityInfo.resumed,
        resumed,
        "connection should be resumed or not resumed as expected"
      );
      ok(
        !(
          transportSecurityInfo.securityState &
          Ci.nsIWebProgressListener.STATE_CERT_USER_OVERRIDDEN
        ),
        "ev-test.example.com should not have STATE_CERT_USER_OVERRIDDEN flag"
      );
      equal(
        transportSecurityInfo.succeededCertChain.length,
        3,
        "ev-test.example.com should have succeededCertChain set"
      );
      equal(
        transportSecurityInfo.failedCertChain.length,
        0,
        "ev-test.example.com should not have failedCertChain set"
      );
      equal(
        transportSecurityInfo.overridableErrorCategory,
        Ci.nsITransportSecurityInfo.ERROR_UNSET,
        "ev-test.example.com should not have an overridable error category"
      );
      ok(
        !gEVExpected || transportSecurityInfo.isExtendedValidation,
        "ev-test.example.com should have isExtendedValidation set " +
          "(or this is a non-debug build)"
      );
    }
  );
}

// This test is similar, except with extended validation. We should connect
// successfully, and the certificate should be EV in debug builds. Without
// clearing the session cache, we should connect successfully again, this time
// with session resumption. The certificate should again be EV in debug builds.
function add_resume_ev_test() {
  const SERVER_PORT = 8888;
  let expectedRequestPaths = ["ev-test"];
  let responseTypes = ["good"];
  // Since we cache OCSP responses, we only ever actually serve one set.
  let ocspResponder;
  // If we don't wrap this in an `add_test`, the OCSP responder will be running
  // while we are actually running unrelated testcases, which can disrupt them.
  add_test(() => {
    ocspResponder = startOCSPResponder(
      SERVER_PORT,
      "localhost",
      "bad_certs",
      expectedRequestPaths,
      expectedRequestPaths.slice(),
      null,
      responseTypes
    );
    run_next_test();
  });
  // We should be able to connect and verify the certificate as EV (in debug
  // builds).
  add_one_ev_test(false);
  // We should be able to connect again (using session resumption). In debug
  // builds, the certificate should be noted as EV. Again, it's important that
  // nothing clears the TLS cache in between these two operations.
  add_one_ev_test(true);

  add_test(() => {
    ocspResponder.stop(run_next_test);
  });
}

const GOOD_DOMAIN = "good.include-subdomains.pinning.example.com";

// Helper function that adds a test that connects to a domain that should
// succeed (but isn't EV) and verifies that its succeededCertChain gets set
// appropriately.
function add_one_non_ev_test() {
  add_connection_test(
    GOOD_DOMAIN,
    PRErrorCodeSuccess,
    null,
    transportSecurityInfo => {
      ok(
        !(
          transportSecurityInfo.securityState &
          Ci.nsIWebProgressListener.STATE_CERT_USER_OVERRIDDEN
        ),
        `${GOOD_DOMAIN} should not have STATE_CERT_USER_OVERRIDDEN flag`
      );
      ok(
        transportSecurityInfo.succeededCertChain,
        `${GOOD_DOMAIN} should have succeededCertChain set`
      );
      equal(
        transportSecurityInfo.overridableErrorCategory,
        0,
        `${GOOD_DOMAIN} should not have an overridable error category set`
      );
      ok(
        !transportSecurityInfo.isExtendedValidation,
        `${GOOD_DOMAIN} should not have isExtendedValidation set`
      );
    }
  );
}

// This test is similar, except with non-extended validation. We should connect
// successfully, and the certificate should not be EV. Without clearing the
// session cache, we should connect successfully again, this time with session
// resumption. In this case, though, we want to ensure the succeededCertChain is
// set.
function add_resume_non_ev_test() {
  add_one_non_ev_test();
  add_one_non_ev_test();
}

const statsPtr = getSSLStatistics();
const toInt32 = ctypes.Int64.lo;

// Connect to the same domain with two origin attributes and check if any ssl
// session is resumed.
function add_origin_attributes_test(
  originAttributes1,
  originAttributes2,
  resumeExpected
) {
  add_connection_test(
    GOOD_DOMAIN,
    PRErrorCodeSuccess,
    clearSessionCache,
    null,
    null,
    originAttributes1
  );

  let hitsBeforeConnect;
  let missesBeforeConnect;
  let expectedHits = resumeExpected ? 1 : 0;
  let expectedMisses = 1 - expectedHits;

  add_connection_test(
    GOOD_DOMAIN,
    PRErrorCodeSuccess,
    function () {
      // Add the hits and misses before connection.
      let stats = statsPtr.contents;
      hitsBeforeConnect = toInt32(stats.sch_sid_cache_hits);
      missesBeforeConnect = toInt32(stats.sch_sid_cache_misses);
    },
    function () {
      let stats = statsPtr.contents;
      equal(
        toInt32(stats.sch_sid_cache_hits),
        hitsBeforeConnect + expectedHits,
        "Unexpected cache hits"
      );
      equal(
        toInt32(stats.sch_sid_cache_misses),
        missesBeforeConnect + expectedMisses,
        "Unexpected cache misses"
      );
    },
    null,
    originAttributes2
  );
}

function add_resumption_tests() {
  add_resume_ev_test();
  add_resume_non_ev_test();
  add_resume_non_ev_with_override_test();
  add_origin_attributes_test({}, {}, true);
  add_origin_attributes_test({ userContextId: 1 }, { userContextId: 2 }, false);
  add_origin_attributes_test({ userContextId: 3 }, { userContextId: 3 }, true);
  add_origin_attributes_test(
    { firstPartyDomain: "foo.com" },
    { firstPartyDomain: "bar.com" },
    false
  );
  add_origin_attributes_test(
    { firstPartyDomain: "baz.com" },
    { firstPartyDomain: "baz.com" },
    true
  );
}

function run_test() {
  add_tls_server_setup("BadCertAndPinningServer", "bad_certs");
  add_resumption_tests();
  // Enable external session cache and reset the status.
  add_test(function () {
    Services.prefs.setBoolPref("network.ssl_tokens_cache_enabled", true);
    certdb.clearOCSPCache();
    run_next_test();
  });
  // Do tests again.
  add_resumption_tests();
  run_next_test();
}