summaryrefslogtreecommitdiffstats
path: root/security/manager/ssl/tests/unit/test_ev_certs.js
blob: f1636239195d7904582a0a740700e050884db552 (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
// -*- 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
      ? 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 = [`${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 verifyWithOCSPResponseType(testcase, response, expectEV) {
  let cert = constructCertFromFile(`test_ev_certs/${testcase}-ee.pem`);
  addCertFromFile(certdb, `test_ev_certs/${testcase}-int.pem`, ",,");
  let expectedOCSPRequestPaths = [`${testcase}-ee`];
  let ocspResponseTypes = [response];
  return asyncTestEV(
    cert,
    PRErrorCodeSuccess,
    gEVExpected && expectEV,
    expectedOCSPRequestPaths,
    ocspResponseTypes
  );
}

function ensureVerifiesAsDVWithOldEndEntityOCSPResponse(testcase) {
  return verifyWithOCSPResponseType(testcase, "longvalidityalmostold", false);
}

function ensureVerifiesAsDVWithVeryOldEndEntityOCSPResponse(testcase) {
  return verifyWithOCSPResponseType(testcase, "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 tries all EV policies it encounters, this successfully
  // verifies as EV.
  await ensureVerifiesAsEV("cabforum-and-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 tries all EV policies it encounters, this successfully
  // verifies as EV.
  await ensureVerifiesAsEV("test-and-cabforum-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", []);
  await ensureVerifiesAsEV("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");
});

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

// 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();

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

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