summaryrefslogtreecommitdiffstats
path: root/security/manager/ssl/tests/unit/test_cert_storage.js
blob: e6bd4d944be05f88f1b03acacb76ec4096b9b2b1 (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
/* -*- 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";

// This test checks a number of things:
// * it ensures that data loaded from revocations.txt on startup is present
// * it ensures that data served from OneCRL are persisted correctly
// * it ensures that items in the CertBlocklist are seen as revoked by the
//   cert verifier
// * it does a sanity check to ensure other cert verifier behavior is
//   unmodified

const { RemoteSecuritySettings } = ChromeUtils.importESModule(
  "resource://gre/modules/psm/RemoteSecuritySettings.sys.mjs"
);

// First, we need to setup appInfo for the blocklist service to work
var id = "xpcshell@tests.mozilla.org";
var appName = "XPCShell";
var version = "1";
var platformVersion = "1.9.2";
const { updateAppInfo } = ChromeUtils.importESModule(
  "resource://testing-common/AppInfo.sys.mjs"
);
updateAppInfo({
  name: appName,
  ID: id,
  version,
  platformVersion: platformVersion ? platformVersion : "1.0",
  crashReporter: true,
});

// we need to ensure we setup revocation data before certDB, or we'll start with
// no revocation.txt in the profile
var gProfile = do_get_profile();

var gRevocations = gProfile.clone();
gRevocations.append("revocations.txt");
if (!gRevocations.exists()) {
  let existing = do_get_file("test_onecrl/sample_revocations.txt", false);
  existing.copyTo(gProfile, "revocations.txt");
}

var certDB = Cc["@mozilla.org/security/x509certdb;1"].getService(
  Ci.nsIX509CertDB
);

const certBlocklist = [
  // test with some bad data ...
  {
    issuerName: "Some nonsense in issuer",
    serialNumber: "AkHVNA==",
  },
  {
    issuerName: "MA0xCzAJBgNVBAMMAmNh",
    serialNumber: "some nonsense in serial",
  },
  {
    issuerName: "and serial",
    serialNumber: "some nonsense in both issuer",
  },
  // some mixed
  // In these case, the issuer name and the valid serialNumber correspond
  // to test-int.pem in bad_certs/
  {
    issuerName: "MBIxEDAOBgNVBAMMB1Rlc3QgQ0E=",
    serialNumber: "oops! more nonsense.",
  },
  {
    issuerName: "MBIxEDAOBgNVBAMMB1Rlc3QgQ0E=",
    serialNumber: "a0X7/7DlTaedpgrIJg25iBPOkIM=",
  },
  // ... and some good
  // In this case, the issuer name and the valid serialNumber correspond
  // to other-test-ca.pem in bad_certs/ (for testing root revocation)
  {
    issuerName: "MBgxFjAUBgNVBAMMDU90aGVyIHRlc3QgQ0E=",
    serialNumber: "Rym6o+VN9xgZXT/QLrvN/nv1ZN4=",
  },
  // These items correspond to an entry in sample_revocations.txt where:
  // isser name is the base-64 encoded subject DN for the shared Test
  // Intermediate and the serialNumbers are base-64 encoded 78 and 31,
  // respectively.
  // We need this to ensure that existing items are retained if they're
  // also in the blocklist
  {
    issuerName: "MBwxGjAYBgNVBAMMEVRlc3QgSW50ZXJtZWRpYXRl",
    serialNumber: "Tg==",
  },
  {
    issuerName: "MBwxGjAYBgNVBAMMEVRlc3QgSW50ZXJtZWRpYXRl",
    serialNumber: "Hw==",
  },
  // This item revokes same-issuer-ee.pem by subject and pubKeyHash.
  {
    subject: "MCIxIDAeBgNVBAMMF0Fub3RoZXIgVGVzdCBFbmQtZW50aXR5",
    pubKeyHash: "VCIlmPM9NkgFQtrs4Oa5TeFcDu6MWRTKSNdePEhOgD8=",
  },
];

function verify_cert(file, expectedError) {
  let ee = constructCertFromFile(file);
  return checkCertErrorGeneric(
    certDB,
    ee,
    expectedError,
    certificateUsageSSLServer
  );
}

// The certificate blocklist currently only applies to TLS server certificates.
async function verify_non_tls_usage_succeeds(file) {
  let ee = constructCertFromFile(file);
  await checkCertErrorGeneric(
    certDB,
    ee,
    PRErrorCodeSuccess,
    certificateUsageSSLClient
  );
  await checkCertErrorGeneric(
    certDB,
    ee,
    PRErrorCodeSuccess,
    certificateUsageEmailSigner
  );
  await checkCertErrorGeneric(
    certDB,
    ee,
    PRErrorCodeSuccess,
    certificateUsageEmailRecipient
  );
}

function load_cert(cert, trust) {
  let file = "bad_certs/" + cert + ".pem";
  addCertFromFile(certDB, file, trust);
}

async function update_blocklist() {
  const { OneCRLBlocklistClient } = RemoteSecuritySettings.init();

  const fakeEvent = {
    current: certBlocklist, // with old .txt revocations.
    deleted: [],
    created: certBlocklist, // with new cert storage.
    updated: [],
  };
  await OneCRLBlocklistClient.emit("sync", { data: fakeEvent });
  // Save the last check timestamp, used by cert_storage to assert
  // if the blocklist is «fresh».
  Services.prefs.setIntPref(
    OneCRLBlocklistClient.lastCheckTimePref,
    Math.floor(Date.now() / 1000)
  );
}

function run_test() {
  // import the certificates we need
  load_cert("test-ca", "CTu,CTu,CTu");
  load_cert("test-int", ",,");
  load_cert("other-test-ca", "CTu,CTu,CTu");

  add_task(async function () {
    // check some existing items in revocations.txt are blocked.
    // This test corresponds to:
    // issuer: MBIxEDAOBgNVBAMMB1Rlc3QgQ0E= (CN=Test CA)
    // serial: Kg== (42)
    let file = "test_onecrl/ee-revoked-by-revocations-txt.pem";
    await verify_cert(file, SEC_ERROR_REVOKED_CERTIFICATE);

    // This test corresponds to:
    // issuer: MBwxGjAYBgNVBAMMEVRlc3QgSW50ZXJtZWRpYXRl (CN=Test Intermediate)
    // serial: Tg== (78)
    file = "test_onecrl/another-ee-revoked-by-revocations-txt.pem";
    await verify_cert(file, SEC_ERROR_REVOKED_CERTIFICATE);

    // And this test corresponds to:
    // issuer: MBwxGjAYBgNVBAMMEVRlc3QgSW50ZXJtZWRpYXRl (CN=Test Intermediate)
    // serial: Hw== (31)
    // (we test this issuer twice to ensure we can read multiple serials)
    file = "test_onecrl/another-ee-revoked-by-revocations-txt-serial-2.pem";
    await verify_cert(file, SEC_ERROR_REVOKED_CERTIFICATE);

    // Test that a certificate revoked by subject and public key hash in
    // revocations.txt is revoked
    // subject: MCsxKTAnBgNVBAMMIEVFIFJldm9rZWQgQnkgU3ViamVjdCBhbmQgUHViS2V5
    // (CN=EE Revoked By Subject and PubKey)
    // pubkeyhash: VCIlmPM9NkgFQtrs4Oa5TeFcDu6MWRTKSNdePEhOgD8= (this is the
    // shared RSA SPKI)
    file = "test_onecrl/ee-revoked-by-subject-and-pubkey.pem";
    await verify_cert(file, SEC_ERROR_REVOKED_CERTIFICATE);

    // Soon we'll load a blocklist which revokes test-int.pem, which issued
    // test-int-ee.pem.
    // Check the cert validates before we load the blocklist
    file = "test_onecrl/test-int-ee.pem";
    await verify_cert(file, PRErrorCodeSuccess);

    // The blocklist also revokes other-test-ca.pem, which issued
    // other-ca-ee.pem. Check the cert validates before we load the blocklist
    file = "bad_certs/other-issuer-ee.pem";
    await verify_cert(file, PRErrorCodeSuccess);

    // The blocklist will revoke same-issuer-ee.pem via subject / pubKeyHash.
    // Check the cert validates before we load the blocklist
    file = "test_onecrl/same-issuer-ee.pem";
    await verify_cert(file, PRErrorCodeSuccess);
  });

  // blocklist load is async so we must use add_test from here
  add_task(update_blocklist);

  add_task(async function () {
    // The blocklist will be loaded now. Let's check the data is sane.
    // In particular, we should still have the revoked issuer / serial pair
    // that was in revocations.txt but not the blocklist.
    let file = "test_onecrl/ee-revoked-by-revocations-txt.pem";
    await verify_cert(file, SEC_ERROR_REVOKED_CERTIFICATE);

    // We should also still have the revoked issuer / serial pairs that were in
    // revocations.txt and are also in the blocklist.
    file = "test_onecrl/another-ee-revoked-by-revocations-txt.pem";
    await verify_cert(file, SEC_ERROR_REVOKED_CERTIFICATE);
    file = "test_onecrl/another-ee-revoked-by-revocations-txt-serial-2.pem";
    await verify_cert(file, SEC_ERROR_REVOKED_CERTIFICATE);

    // The cert revoked by subject and pubkeyhash should still be revoked.
    file = "test_onecrl/ee-revoked-by-subject-and-pubkey.pem";
    await verify_cert(file, SEC_ERROR_REVOKED_CERTIFICATE);

    // Check the blocklisted intermediate now causes a failure
    file = "test_onecrl/test-int-ee.pem";
    await verify_cert(file, SEC_ERROR_REVOKED_CERTIFICATE);
    await verify_non_tls_usage_succeeds(file);

    // Check the ee with the blocklisted root also causes a failure
    file = "bad_certs/other-issuer-ee.pem";
    await verify_cert(file, SEC_ERROR_REVOKED_CERTIFICATE);
    await verify_non_tls_usage_succeeds(file);

    // Check the ee blocked by subject / pubKey causes a failure
    file = "test_onecrl/same-issuer-ee.pem";
    await verify_cert(file, SEC_ERROR_REVOKED_CERTIFICATE);
    await verify_non_tls_usage_succeeds(file);

    // Check a non-blocklisted chain still validates OK
    file = "bad_certs/default-ee.pem";
    await verify_cert(file, PRErrorCodeSuccess);

    // Check a bad cert is still bad (unknown issuer)
    file = "bad_certs/unknownissuer.pem";
    await verify_cert(file, SEC_ERROR_UNKNOWN_ISSUER);
  });

  run_next_test();
}