summaryrefslogtreecommitdiffstats
path: root/security/manager/ssl/tests/unit/test_pinning.js
blob: f1818200292b3fdc097a5d6e56115102beb2858c (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
// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
//
// For all cases, the acceptable pinset includes only certificates pinned to
// Test End Entity Cert (signed by issuer testCA). Other certificates
// are issued by otherCA, which is never in the pinset but is a user-specified
// trust anchor. This test covers multiple cases:
//
// Pinned domain include-subdomains.pinning.example.com includes subdomains
// - PASS: include-subdomains.pinning.example.com serves a correct cert
// - PASS: good.include-subdomains.pinning.example.com serves a correct cert
// - FAIL (strict): bad.include-subdomains.pinning.example.com serves a cert
// not in the pinset
// - PASS (mitm): bad.include-subdomains.pinning.example.com serves a cert not
// in the pinset, but issued by a user-specified trust domain
//
// Pinned domain exclude-subdomains.pinning.example.com excludes subdomains
// - PASS: exclude-subdomains.pinning.example.com serves a correct cert
// - FAIL: exclude-subdomains.pinning.example.com serves an incorrect cert
// (TODO: test using verifyCertNow)
// - PASS: sub.exclude-subdomains.pinning.example.com serves an incorrect cert

"use strict";

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

function add_clear_override(host) {
  add_test(function() {
    let certOverrideService = Cc["@mozilla.org/security/certoverride;1"]
                                .getService(Ci.nsICertOverrideService);
    certOverrideService.clearValidityOverride(host, 8443);
    run_next_test();
  });
}

function test_strict() {
  // In strict mode, we always evaluate pinning data, regardless of whether the
  // issuer is a built-in trust anchor. We only enforce pins that are not in
  // test mode.
  add_test(function() {
    Services.prefs.setIntPref("security.cert_pinning.enforcement_level", 2);
    run_next_test();
  });

  // Normally this is overridable. But, since we have pinning information for
  // this host, we don't allow overrides.
  add_prevented_cert_override_test(
    "unknownissuer.include-subdomains.pinning.example.com",
    Ci.nsICertOverrideService.ERROR_UNTRUSTED,
    SEC_ERROR_UNKNOWN_ISSUER);
  add_clear_override("unknownissuer.include-subdomains.pinning.example.com");

  // Issued by otherCA, which is not in the pinset for pinning.example.com.
  add_connection_test("bad.include-subdomains.pinning.example.com",
                      MOZILLA_PKIX_ERROR_KEY_PINNING_FAILURE);

  // Check that using a FQDN doesn't bypass pinning.
  add_connection_test("bad.include-subdomains.pinning.example.com.",
                      MOZILLA_PKIX_ERROR_KEY_PINNING_FAILURE);
  // For some reason this is also navigable (see bug 1118522).
  add_connection_test("bad.include-subdomains.pinning.example.com..",
                      MOZILLA_PKIX_ERROR_KEY_PINNING_FAILURE);

  // These domains serve certs that match the pinset.
  add_connection_test("include-subdomains.pinning.example.com",
                      PRErrorCodeSuccess);
  add_connection_test("good.include-subdomains.pinning.example.com",
                      PRErrorCodeSuccess);
  add_connection_test("exclude-subdomains.pinning.example.com",
                      PRErrorCodeSuccess);

  // This domain serves a cert that doesn't match the pinset, but subdomains
  // are excluded.
  add_connection_test("sub.exclude-subdomains.pinning.example.com",
                      PRErrorCodeSuccess);

  // This domain's pinset is exactly the same as
  // include-subdomains.pinning.example.com, serves the same cert as
  // bad.include-subdomains.pinning.example.com, but it should pass because
  // it's in test_mode.
  add_connection_test("test-mode.pinning.example.com",
                      PRErrorCodeSuccess);
  // Similarly, this pin is in test-mode, so it should be overridable.
  add_cert_override_test("unknownissuer.test-mode.pinning.example.com",
                         Ci.nsICertOverrideService.ERROR_UNTRUSTED,
                         SEC_ERROR_UNKNOWN_ISSUER);
  add_clear_override("unknownissuer.test-mode.pinning.example.com");
}

function test_mitm() {
  // In MITM mode, we allow pinning to pass if the chain resolves to any
  // user-specified trust anchor, even if it is not in the pinset.
  add_test(function() {
    Services.prefs.setIntPref("security.cert_pinning.enforcement_level", 1);
    run_next_test();
  });

  add_connection_test("include-subdomains.pinning.example.com",
                      PRErrorCodeSuccess);
  add_connection_test("good.include-subdomains.pinning.example.com",
                      PRErrorCodeSuccess);

  // Normally this is overridable. But, since we have pinning information for
  // this host, we don't allow overrides (since building a trusted chain fails,
  // we have no reason to believe this was issued by a user-added trust
  // anchor, so we can't allow overrides for it).
  add_prevented_cert_override_test(
    "unknownissuer.include-subdomains.pinning.example.com",
    Ci.nsICertOverrideService.ERROR_UNTRUSTED,
    SEC_ERROR_UNKNOWN_ISSUER);
  add_clear_override("unknownissuer.include-subdomains.pinning.example.com");

  // In this case, even though otherCA is not in the pinset, it is a
  // user-specified trust anchor and the pinning check succeeds.
  add_connection_test("bad.include-subdomains.pinning.example.com",
                      PRErrorCodeSuccess);

  add_connection_test("exclude-subdomains.pinning.example.com",
                      PRErrorCodeSuccess);
  add_connection_test("sub.exclude-subdomains.pinning.example.com",
                      PRErrorCodeSuccess);
  add_connection_test("test-mode.pinning.example.com", PRErrorCodeSuccess);
  add_cert_override_test("unknownissuer.test-mode.pinning.example.com",
                         Ci.nsICertOverrideService.ERROR_UNTRUSTED,
                         SEC_ERROR_UNKNOWN_ISSUER);
  add_clear_override("unknownissuer.test-mode.pinning.example.com");
}

function test_disabled() {
  // Disable pinning.
  add_test(function() {
    Services.prefs.setIntPref("security.cert_pinning.enforcement_level", 0);
    run_next_test();
  });

  add_connection_test("include-subdomains.pinning.example.com",
                      PRErrorCodeSuccess);
  add_connection_test("good.include-subdomains.pinning.example.com",
                      PRErrorCodeSuccess);
  add_connection_test("bad.include-subdomains.pinning.example.com",
                      PRErrorCodeSuccess);
  add_connection_test("exclude-subdomains.pinning.example.com",
                      PRErrorCodeSuccess);
  add_connection_test("sub.exclude-subdomains.pinning.example.com",
                      PRErrorCodeSuccess);
  add_connection_test("test-mode.pinning.example.com", PRErrorCodeSuccess);

  add_cert_override_test("unknownissuer.include-subdomains.pinning.example.com",
                         Ci.nsICertOverrideService.ERROR_UNTRUSTED,
                         SEC_ERROR_UNKNOWN_ISSUER);
  add_clear_override("unknownissuer.include-subdomains.pinning.example.com");
  add_cert_override_test("unknownissuer.test-mode.pinning.example.com",
                         Ci.nsICertOverrideService.ERROR_UNTRUSTED,
                         SEC_ERROR_UNKNOWN_ISSUER);
  add_clear_override("unknownissuer.test-mode.pinning.example.com");
}

function test_enforce_test_mode() {
  // In enforce test mode, we always enforce all pins, even test pins.
  add_test(function() {
    Services.prefs.setIntPref("security.cert_pinning.enforcement_level", 3);
    run_next_test();
  });

  // Normally this is overridable. But, since we have pinning information for
  // this host, we don't allow overrides.
  add_prevented_cert_override_test(
    "unknownissuer.include-subdomains.pinning.example.com",
    Ci.nsICertOverrideService.ERROR_UNTRUSTED,
    SEC_ERROR_UNKNOWN_ISSUER);
  add_clear_override("unknownissuer.include-subdomains.pinning.example.com");

  // Issued by otherCA, which is not in the pinset for pinning.example.com.
  add_connection_test("bad.include-subdomains.pinning.example.com",
                      MOZILLA_PKIX_ERROR_KEY_PINNING_FAILURE);

  // These domains serve certs that match the pinset.
  add_connection_test("include-subdomains.pinning.example.com",
                      PRErrorCodeSuccess);
  add_connection_test("good.include-subdomains.pinning.example.com",
                      PRErrorCodeSuccess);
  add_connection_test("exclude-subdomains.pinning.example.com",
                      PRErrorCodeSuccess);

  // This domain serves a cert that doesn't match the pinset, but subdomains
  // are excluded.
  add_connection_test("sub.exclude-subdomains.pinning.example.com",
                      PRErrorCodeSuccess);

  // This domain's pinset is exactly the same as
  // include-subdomains.pinning.example.com, serves the same cert as
  // bad.include-subdomains.pinning.example.com, is in test-mode, but we are
  // enforcing test mode pins.
  add_connection_test("test-mode.pinning.example.com",
                      MOZILLA_PKIX_ERROR_KEY_PINNING_FAILURE);
  // Normally this is overridable. But, since we have pinning information for
  // this host (and since we're enforcing test mode), we don't allow overrides.
  add_prevented_cert_override_test(
    "unknownissuer.test-mode.pinning.example.com",
    Ci.nsICertOverrideService.ERROR_UNTRUSTED,
    SEC_ERROR_UNKNOWN_ISSUER);
  add_clear_override("unknownissuer.test-mode.pinning.example.com");
}

function check_pinning_telemetry() {
  let service = Cc["@mozilla.org/base/telemetry;1"].getService(Ci.nsITelemetry);
  let prod_histogram = service.getHistogramById("CERT_PINNING_RESULTS")
                         .snapshot();
  let test_histogram = service.getHistogramById("CERT_PINNING_TEST_RESULTS")
                         .snapshot();
  // Because all of our test domains are pinned to user-specified trust
  // anchors, effectively only strict mode and enforce test-mode get evaluated
  equal(prod_histogram.counts[0], 4,
        "Actual and expected prod (non-Mozilla) failure count should match");
  equal(prod_histogram.counts[1], 4,
        "Actual and expected prod (non-Mozilla) success count should match");
  equal(test_histogram.counts[0], 2,
        "Actual and expected test (non-Mozilla) failure count should match");
  equal(test_histogram.counts[1], 0,
        "Actual and expected test (non-Mozilla) success count should match");

  let moz_prod_histogram = service.getHistogramById("CERT_PINNING_MOZ_RESULTS")
                             .snapshot();
  let moz_test_histogram =
    service.getHistogramById("CERT_PINNING_MOZ_TEST_RESULTS").snapshot();
  equal(moz_prod_histogram.counts[0], 0,
        "Actual and expected prod (Mozilla) failure count should match");
  equal(moz_prod_histogram.counts[1], 0,
        "Actual and expected prod (Mozilla) success count should match");
  equal(moz_test_histogram.counts[0], 0,
        "Actual and expected test (Mozilla) failure count should match");
  equal(moz_test_histogram.counts[1], 0,
        "Actual and expected test (Mozilla) success count should match");

  let per_host_histogram =
    service.getHistogramById("CERT_PINNING_MOZ_RESULTS_BY_HOST").snapshot();
  equal(per_host_histogram.counts[0], 0,
        "Actual and expected per host (Mozilla) failure count should match");
  equal(per_host_histogram.counts[1], 2,
        "Actual and expected per host (Mozilla) success count should match");
  run_next_test();
}

function run_test() {
  // Ensure that static pinning works when HPKP is disabled.
  Services.prefs.setBoolPref("security.cert_pinning.hpkp.enabled", false);

  add_tls_server_setup("BadCertServer", "bad_certs");

  // Add a user-specified trust anchor.
  addCertFromFile(certdb, "bad_certs/other-test-ca.pem", "CTu,u,u");

  test_strict();
  test_mitm();
  test_disabled();
  test_enforce_test_mode();

  add_test(function () {
    check_pinning_telemetry();
  });
  run_next_test();
}