summaryrefslogtreecommitdiffstats
path: root/security/manager/ssl/tests/unit/test_ev_certs.js
blob: 937e4509f346242b2575b84f8eaf5682215ecd55 (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
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
// -*- 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/.

"use strict";

// Tests that end-entity certificates that should successfully verify as EV
// (Extended Validation) do so and that end-entity certificates that should not
// successfully verify as EV do not. Also tests related situations (e.g. that
// failure to fetch an OCSP response results in no EV treatment).
//
// A quick note about the certificates in these tests: generally, an EV
// certificate chain will have an end-entity with a specific policy OID followed
// by an intermediate with the anyPolicy OID chaining to a root with no policy
// OID (since it's a trust anchor, it can be omitted). In these tests, the
// specific policy OID is 1.3.6.1.4.1.13769.666.666.666.1.500.9.1 and is
// referred to as the test OID. In order to reflect what will commonly be
// encountered, the end-entity of any given test path will have the test OID
// unless otherwise specified in the name of the test path. Similarly, the
// intermediate will have the anyPolicy OID, again unless otherwise specified.
// For example, for the path where the end-entity does not have an OCSP URI
// (referred to as "no-ocsp-ee-path-{ee,int}", the end-entity has the test OID
// whereas the intermediate has the anyPolicy OID.
// For another example, for the test OID path ("test-oid-path-{ee,int}"), both
// the end-entity and the intermediate have the test OID.

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

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

Services.prefs.setCharPref("network.dns.localDomains", "www.example.com");
Services.prefs.setIntPref("security.OCSP.enabled", 1);
const evroot = addCertFromFile(certdb, "test_ev_certs/evroot.pem", "CTu,,");
addCertFromFile(certdb, "test_ev_certs/non-evroot-ca.pem", "CTu,,");

const SERVER_PORT = 8888;

function failingOCSPResponder() {
  return getFailingHttpServer(SERVER_PORT, ["www.example.com"]);
}

class EVCertVerificationResult {
  constructor(
    testcase,
    expectedPRErrorCode,
    expectedEV,
    resolve,
    ocspResponder
  ) {
    this.testcase = testcase;
    this.expectedPRErrorCode = expectedPRErrorCode;
    this.expectedEV = expectedEV;
    this.resolve = resolve;
    this.ocspResponder = ocspResponder;
  }

  verifyCertFinished(prErrorCode, verifiedChain, hasEVPolicy) {
    equal(
      prErrorCode,
      this.expectedPRErrorCode,
      `${this.testcase} should have expected error code`
    );
    equal(
      hasEVPolicy,
      this.expectedEV,
      `${this.testcase} should result in expected EV status`
    );
    this.ocspResponder.stop(this.resolve);
  }
}

function asyncTestEV(
  cert,
  expectedPRErrorCode,
  expectedEV,
  expectedOCSPRequestPaths,
  ocspResponseTypes = undefined
) {
  let now = Date.now() / 1000;
  return new Promise((resolve, reject) => {
    let ocspResponder =
      expectedOCSPRequestPaths.length > 0
        ? startOCSPResponder(
            SERVER_PORT,
            "www.example.com",
            "test_ev_certs",
            expectedOCSPRequestPaths,
            expectedOCSPRequestPaths.slice(),
            null,
            ocspResponseTypes
          )
        : failingOCSPResponder();
    let result = new EVCertVerificationResult(
      cert.subjectName,
      expectedPRErrorCode,
      expectedEV,
      resolve,
      ocspResponder
    );
    certdb.asyncVerifyCertAtTime(
      cert,
      certificateUsageSSLServer,
      0,
      "ev-test.example.com",
      now,
      result
    );
  });
}

function ensureVerifiesAsEV(testcase) {
  let cert = constructCertFromFile(`test_ev_certs/${testcase}-ee.pem`);
  addCertFromFile(certdb, `test_ev_certs/${testcase}-int.pem`, ",,");
  let expectedOCSPRequestPaths = gEVExpected
    ? [`${testcase}-int`, `${testcase}-ee`]
    : [`${testcase}-ee`];
  return asyncTestEV(
    cert,
    PRErrorCodeSuccess,
    gEVExpected,
    expectedOCSPRequestPaths
  );
}

function ensureVerifiesAsEVWithNoOCSPRequests(testcase) {
  let cert = constructCertFromFile(`test_ev_certs/${testcase}-ee.pem`);
  addCertFromFile(certdb, `test_ev_certs/${testcase}-int.pem`, ",,");
  return asyncTestEV(cert, PRErrorCodeSuccess, gEVExpected, []);
}

function ensureVerifiesAsDV(testcase, expectedOCSPRequestPaths = undefined) {
  let cert = constructCertFromFile(`test_ev_certs/${testcase}-ee.pem`);
  addCertFromFile(certdb, `test_ev_certs/${testcase}-int.pem`, ",,");
  return asyncTestEV(
    cert,
    PRErrorCodeSuccess,
    false,
    expectedOCSPRequestPaths ? expectedOCSPRequestPaths : [`${testcase}-ee`]
  );
}

function ensureVerificationFails(testcase, expectedPRErrorCode) {
  let cert = constructCertFromFile(`test_ev_certs/${testcase}-ee.pem`);
  addCertFromFile(certdb, `test_ev_certs/${testcase}-int.pem`, ",,");
  return asyncTestEV(cert, expectedPRErrorCode, false, []);
}

function verifyWithFlags_LOCAL_ONLY_and_MUST_BE_EV(testcase, expectSuccess) {
  let cert = constructCertFromFile(`test_ev_certs/${testcase}-ee.pem`);
  addCertFromFile(certdb, `test_ev_certs/${testcase}-int.pem`, ",,");
  let now = Date.now() / 1000;
  let expectedErrorCode = SEC_ERROR_POLICY_VALIDATION_FAILED;
  if (expectSuccess && gEVExpected) {
    expectedErrorCode = PRErrorCodeSuccess;
  }
  return new Promise((resolve, reject) => {
    let ocspResponder = failingOCSPResponder();
    let result = new EVCertVerificationResult(
      cert.subjectName,
      expectedErrorCode,
      expectSuccess && gEVExpected,
      resolve,
      ocspResponder
    );
    let flags =
      Ci.nsIX509CertDB.FLAG_LOCAL_ONLY | Ci.nsIX509CertDB.FLAG_MUST_BE_EV;
    certdb.asyncVerifyCertAtTime(
      cert,
      certificateUsageSSLServer,
      flags,
      "ev-test.example.com",
      now,
      result
    );
  });
}

function ensureNoOCSPMeansNoEV(testcase) {
  return verifyWithFlags_LOCAL_ONLY_and_MUST_BE_EV(testcase, false);
}

function ensureVerifiesAsEVWithFLAG_LOCAL_ONLY(testcase) {
  return verifyWithFlags_LOCAL_ONLY_and_MUST_BE_EV(testcase, true);
}

function ensureOneCRLSkipsOCSPForIntermediates(testcase) {
  let cert = constructCertFromFile(`test_ev_certs/${testcase}-ee.pem`);
  addCertFromFile(certdb, `test_ev_certs/${testcase}-int.pem`, ",,");
  return asyncTestEV(cert, PRErrorCodeSuccess, gEVExpected, [`${testcase}-ee`]);
}

function verifyWithDifferentOCSPResponseTypes(testcase, responses, expectEV) {
  let cert = constructCertFromFile(`test_ev_certs/${testcase}-ee.pem`);
  addCertFromFile(certdb, `test_ev_certs/${testcase}-int.pem`, ",,");
  let expectedOCSPRequestPaths = gEVExpected
    ? [`${testcase}-int`, `${testcase}-ee`]
    : [`${testcase}-ee`];
  let ocspResponseTypes = gEVExpected ? responses : responses.slice(1);
  return asyncTestEV(
    cert,
    PRErrorCodeSuccess,
    gEVExpected && expectEV,
    expectedOCSPRequestPaths,
    ocspResponseTypes
  );
}

function ensureVerifiesAsEVWithOldIntermediateOCSPResponse(testcase) {
  return verifyWithDifferentOCSPResponseTypes(
    testcase,
    ["longvalidityalmostold", "good"],
    true
  );
}

function ensureVerifiesAsDVWithOldEndEntityOCSPResponse(testcase) {
  return verifyWithDifferentOCSPResponseTypes(
    testcase,
    ["good", "longvalidityalmostold"],
    false
  );
}

function ensureVerifiesAsDVWithVeryOldEndEntityOCSPResponse(testcase) {
  return verifyWithDifferentOCSPResponseTypes(
    testcase,
    ["good", "ancientstillvalid"],
    false
  );
}

// These should all verify as EV.
add_task(async function plainExpectSuccessEVTests() {
  await ensureVerifiesAsEV("anyPolicy-int-path");
  await ensureVerifiesAsEV("test-oid-path");
  await ensureVerifiesAsEV("cabforum-oid-path");
  await ensureVerifiesAsEV("cabforum-and-test-oid-ee-path");
  await ensureVerifiesAsEV("test-and-cabforum-oid-ee-path");
  await ensureVerifiesAsEV("reverse-order-oids-path");
  // In this case, the end-entity has both the CA/B Forum OID and the test OID
  // (in that order). The intermediate has the CA/B Forum OID. Since the
  // implementation uses the first EV policy it encounters in the end-entity as
  // the required one, this successfully verifies as EV.
  await ensureVerifiesAsEV("cabforum-and-test-oid-ee-cabforum-oid-int-path");
});

// These fail for various reasons to verify as EV, but fallback to DV should
// succeed.
add_task(async function expectDVFallbackTests() {
  await ensureVerifiesAsDV("anyPolicy-ee-path");
  await ensureVerifiesAsDV("non-ev-root-path");
  await ensureVerifiesAsDV(
    "no-ocsp-ee-path",
    gEVExpected ? ["no-ocsp-ee-path-int"] : []
  );
  await ensureVerifiesAsDV("no-ocsp-int-path");
  // In this case, the end-entity has the test OID and the intermediate has the
  // CA/B Forum OID. Since the CA/B Forum OID is not treated the same as the
  // anyPolicy OID, this will not verify as EV.
  await ensureVerifiesAsDV("test-oid-ee-cabforum-oid-int-path");
  // In this case, the end-entity has both the test OID and the CA/B Forum OID
  // (in that order). The intermediate has only the CA/B Forum OID. Since the
  // implementation uses the first EV policy it encounters in the end-entity as
  // the required one, this fails to verify as EV.
  await ensureVerifiesAsDV("test-and-cabforum-oid-ee-cabforum-oid-int-path");
});

// Test that removing the trust bits from an EV root causes verifications
// relying on that root to fail (and then test that adding back the trust bits
// causes the verifications to succeed again).
add_task(async function evRootTrustTests() {
  clearOCSPCache();
  info("untrusting evroot");
  certdb.setCertTrust(
    evroot,
    Ci.nsIX509Cert.CA_CERT,
    Ci.nsIX509CertDB.UNTRUSTED
  );
  await ensureVerificationFails("test-oid-path", SEC_ERROR_UNKNOWN_ISSUER);
  info("re-trusting evroot");
  certdb.setCertTrust(
    evroot,
    Ci.nsIX509Cert.CA_CERT,
    Ci.nsIX509CertDB.TRUSTED_SSL
  );
  await ensureVerifiesAsEV("test-oid-path");
});

// Test that if FLAG_LOCAL_ONLY and FLAG_MUST_BE_EV are specified, that no OCSP
// requests are made (this also means that nothing will verify as EV).
add_task(async function localOnlyMustBeEVTests() {
  clearOCSPCache();
  await ensureNoOCSPMeansNoEV("anyPolicy-ee-path");
  await ensureNoOCSPMeansNoEV("anyPolicy-int-path");
  await ensureNoOCSPMeansNoEV("non-ev-root-path");
  await ensureNoOCSPMeansNoEV("no-ocsp-ee-path");
  await ensureNoOCSPMeansNoEV("no-ocsp-int-path");
  await ensureNoOCSPMeansNoEV("test-oid-path");
});

// Under certain conditions, OneCRL allows us to skip OCSP requests for
// intermediates.
add_task(async function oneCRLTests() {
  clearOCSPCache();

  // enable OneCRL OCSP skipping - allow staleness of up to 30 hours
  Services.prefs.setIntPref(
    "security.onecrl.maximum_staleness_in_seconds",
    108000
  );
  // set the blocklist-background-update-timer value to the recent past
  Services.prefs.setIntPref(
    "services.settings.security.onecrl.checked",
    Math.floor(Date.now() / 1000) - 1
  );
  Services.prefs.setIntPref(
    "app.update.lastUpdateTime.blocklist-background-update-timer",
    Math.floor(Date.now() / 1000) - 1
  );

  await ensureOneCRLSkipsOCSPForIntermediates("anyPolicy-int-path");
  await ensureOneCRLSkipsOCSPForIntermediates("no-ocsp-int-path");
  await ensureOneCRLSkipsOCSPForIntermediates("test-oid-path");

  clearOCSPCache();
  // disable OneCRL OCSP Skipping (no staleness allowed)
  Services.prefs.setIntPref("security.onecrl.maximum_staleness_in_seconds", 0);
  await ensureVerifiesAsEV("anyPolicy-int-path");
  // Because the intermediate in this case is missing an OCSP URI, it will not
  // validate as EV, but it should fall back to DV.
  await ensureVerifiesAsDV("no-ocsp-int-path");
  await ensureVerifiesAsEV("test-oid-path");

  clearOCSPCache();
  // enable OneCRL OCSP skipping - allow staleness of up to 30 hours
  Services.prefs.setIntPref(
    "security.onecrl.maximum_staleness_in_seconds",
    108000
  );
  // set the blocklist-background-update-timer value to the more distant past
  Services.prefs.setIntPref(
    "services.settings.security.onecrl.checked",
    Math.floor(Date.now() / 1000) - 108080
  );
  Services.prefs.setIntPref(
    "app.update.lastUpdateTime.blocklist-background-update-timer",
    Math.floor(Date.now() / 1000) - 108080
  );
  await ensureVerifiesAsEV("anyPolicy-int-path");
  await ensureVerifiesAsDV("no-ocsp-int-path");
  await ensureVerifiesAsEV("test-oid-path");

  clearOCSPCache();
  // test the OCSP behavior when services.settings.security.onecrl.checked is in the
  // distant past and blacklist-background-update-timer is recent
  // enable OneCRL OCSP skipping - allow staleness of up to 30 hours
  Services.prefs.setIntPref(
    "security.onecrl.maximum_staleness_in_seconds",
    108000
  );
  // set the blocklist-background-update-timer value to the recent past
  // (services.settings.security.onecrl.checked defaults to 0)
  Services.prefs.setIntPref(
    "app.update.lastUpdateTime.blocklist-background-update-timer",
    Math.floor(Date.now() / 1000) - 1
  );

  await ensureVerifiesAsEV("anyPolicy-int-path");
  await ensureVerifiesAsDV("no-ocsp-int-path");
  await ensureVerifiesAsEV("test-oid-path");

  clearOCSPCache();
  // test the OCSP behavior when services.settings.security.onecrl.checked is recent
  // enable OneCRL OCSP skipping - allow staleness of up to 30 hours
  Services.prefs.setIntPref(
    "security.onecrl.maximum_staleness_in_seconds",
    108000
  );
  // now set services.settings.security.onecrl.checked to a recent value
  Services.prefs.setIntPref(
    "services.settings.security.onecrl.checked",
    Math.floor(Date.now() / 1000) - 1
  );
  await ensureOneCRLSkipsOCSPForIntermediates("anyPolicy-int-path");
  await ensureOneCRLSkipsOCSPForIntermediates("no-ocsp-int-path");
  await ensureOneCRLSkipsOCSPForIntermediates("test-oid-path");

  Services.prefs.clearUserPref("security.onecrl.maximum_staleness_in_seconds");
  Services.prefs.clearUserPref("services.settings.security.onecrl.checked");
  Services.prefs.clearUserPref(
    "app.update.lastUpdateTime.blocklist-background-update-timer"
  );
});

// Prime the OCSP cache and then ensure that we can validate certificates as EV
// without hitting the network. There's two cases here: one where we simply
// validate like normal and then check that the network was never accessed and
// another where we use flags to mandate that the network not be used.
add_task(async function ocspCachingTests() {
  clearOCSPCache();

  await ensureVerifiesAsEV("anyPolicy-int-path");
  await ensureVerifiesAsEV("test-oid-path");

  await ensureVerifiesAsEVWithNoOCSPRequests("anyPolicy-int-path");
  await ensureVerifiesAsEVWithNoOCSPRequests("test-oid-path");

  await ensureVerifiesAsEVWithFLAG_LOCAL_ONLY("anyPolicy-int-path");
  await ensureVerifiesAsEVWithFLAG_LOCAL_ONLY("test-oid-path");
});

// Old-but-still-valid OCSP responses are accepted for intermediates but not
// end-entity certificates (because of OCSP soft-fail this results in DV
// fallback).
add_task(async function oldOCSPResponseTests() {
  clearOCSPCache();

  await ensureVerifiesAsEVWithOldIntermediateOCSPResponse("anyPolicy-int-path");
  await ensureVerifiesAsEVWithOldIntermediateOCSPResponse("test-oid-path");

  clearOCSPCache();
  await ensureVerifiesAsDVWithOldEndEntityOCSPResponse("anyPolicy-int-path");
  await ensureVerifiesAsDVWithOldEndEntityOCSPResponse("test-oid-path");

  clearOCSPCache();
  await ensureVerifiesAsDVWithVeryOldEndEntityOCSPResponse(
    "anyPolicy-int-path"
  );
  await ensureVerifiesAsDVWithVeryOldEndEntityOCSPResponse("test-oid-path");
});