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
|
// -*- 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);
do_register_cleanup(() => {
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);
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(function* plainExpectSuccessEVTests() {
yield ensureVerifiesAsEV("anyPolicy-int-path");
yield ensureVerifiesAsEV("test-oid-path");
yield ensureVerifiesAsEV("cabforum-oid-path");
yield ensureVerifiesAsEV("cabforum-and-test-oid-ee-path");
yield ensureVerifiesAsEV("test-and-cabforum-oid-ee-path");
yield 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.
yield 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(function* expectDVFallbackTests() {
yield ensureVerifiesAsDV("anyPolicy-ee-path");
yield ensureVerifiesAsDV("non-ev-root-path");
yield ensureVerifiesAsDV("no-ocsp-ee-path",
gEVExpected ? [ "no-ocsp-ee-path-int" ] : []);
yield 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.
yield 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.
yield 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(function* evRootTrustTests() {
clearOCSPCache();
let evroot = certdb.findCertByNickname("evroot");
do_print("untrusting evroot");
certdb.setCertTrust(evroot, Ci.nsIX509Cert.CA_CERT,
Ci.nsIX509CertDB.UNTRUSTED);
yield ensureVerificationFails("test-oid-path", SEC_ERROR_UNKNOWN_ISSUER);
do_print("re-trusting evroot");
certdb.setCertTrust(evroot, Ci.nsIX509Cert.CA_CERT,
Ci.nsIX509CertDB.TRUSTED_SSL);
yield 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(function* localOnlyMustBeEVTests() {
clearOCSPCache();
yield ensureNoOCSPMeansNoEV("anyPolicy-ee-path");
yield ensureNoOCSPMeansNoEV("anyPolicy-int-path");
yield ensureNoOCSPMeansNoEV("non-ev-root-path");
yield ensureNoOCSPMeansNoEV("no-ocsp-ee-path");
yield ensureNoOCSPMeansNoEV("no-ocsp-int-path");
yield ensureNoOCSPMeansNoEV("test-oid-path");
});
// Under certain conditions, OneCRL allows us to skip OCSP requests for
// intermediates.
add_task(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.blocklist.onecrl.checked",
Math.floor(Date.now() / 1000) - 1);
Services.prefs.setIntPref(
"app.update.lastUpdateTime.blocklist-background-update-timer",
Math.floor(Date.now() / 1000) - 1);
yield ensureOneCRLSkipsOCSPForIntermediates("anyPolicy-int-path");
yield ensureOneCRLSkipsOCSPForIntermediates("no-ocsp-int-path");
yield ensureOneCRLSkipsOCSPForIntermediates("test-oid-path");
clearOCSPCache();
// disable OneCRL OCSP Skipping (no staleness allowed)
Services.prefs.setIntPref("security.onecrl.maximum_staleness_in_seconds", 0);
yield 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.
yield ensureVerifiesAsDV("no-ocsp-int-path");
yield 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.blocklist.onecrl.checked",
Math.floor(Date.now() / 1000) - 108080);
Services.prefs.setIntPref(
"app.update.lastUpdateTime.blocklist-background-update-timer",
Math.floor(Date.now() / 1000) - 108080);
yield ensureVerifiesAsEV("anyPolicy-int-path");
yield ensureVerifiesAsDV("no-ocsp-int-path");
yield ensureVerifiesAsEV("test-oid-path");
clearOCSPCache();
// test that setting "security.onecrl.via.amo" results in the correct
// OCSP behavior when services.blocklist.onecrl.checked is in the distant past
// and blacklist-background-update-timer is recent
Services.prefs.setBoolPref("security.onecrl.via.amo", false);
// 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.blocklist.onecrl.checked defaults to 0)
Services.prefs.setIntPref(
"app.update.lastUpdateTime.blocklist-background-update-timer",
Math.floor(Date.now() / 1000) - 1);
yield ensureVerifiesAsEV("anyPolicy-int-path");
yield ensureVerifiesAsDV("no-ocsp-int-path");
yield ensureVerifiesAsEV("test-oid-path");
clearOCSPCache();
// test that setting "security.onecrl.via.amo" results in the correct
// OCSP behavior when services.blocklist.onecrl.checked is recent
Services.prefs.setBoolPref("security.onecrl.via.amo", false);
// enable OneCRL OCSP skipping - allow staleness of up to 30 hours
Services.prefs.setIntPref("security.onecrl.maximum_staleness_in_seconds",
108000);
// now set services.blocklist.onecrl.checked to a recent value
Services.prefs.setIntPref("services.blocklist.onecrl.checked",
Math.floor(Date.now() / 1000) - 1);
yield ensureOneCRLSkipsOCSPForIntermediates("anyPolicy-int-path");
yield ensureOneCRLSkipsOCSPForIntermediates("no-ocsp-int-path");
yield ensureOneCRLSkipsOCSPForIntermediates("test-oid-path");
Services.prefs.clearUserPref("security.onecrl.via.amo");
Services.prefs.clearUserPref("security.onecrl.maximum_staleness_in_seconds");
Services.prefs.clearUserPref("services.blocklist.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(function* ocspCachingTests() {
clearOCSPCache();
yield ensureVerifiesAsEV("anyPolicy-int-path");
yield ensureVerifiesAsEV("test-oid-path");
yield ensureVerifiesAsEVWithNoOCSPRequests("anyPolicy-int-path");
yield ensureVerifiesAsEVWithNoOCSPRequests("test-oid-path");
yield ensureVerifiesAsEVWithFLAG_LOCAL_ONLY("anyPolicy-int-path");
yield 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(function* oldOCSPResponseTests() {
clearOCSPCache();
yield ensureVerifiesAsEVWithOldIntermediateOCSPResponse("anyPolicy-int-path");
yield ensureVerifiesAsEVWithOldIntermediateOCSPResponse("test-oid-path");
clearOCSPCache();
yield ensureVerifiesAsDVWithOldEndEntityOCSPResponse("anyPolicy-int-path");
yield ensureVerifiesAsDVWithOldEndEntityOCSPResponse("test-oid-path");
clearOCSPCache();
yield ensureVerifiesAsDVWithVeryOldEndEntityOCSPResponse(
"anyPolicy-int-path");
yield ensureVerifiesAsDVWithVeryOldEndEntityOCSPResponse("test-oid-path");
});
|