• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

randombit / botan / 25457312714

06 May 2026 07:43PM UTC coverage: 89.331% (-2.3%) from 91.667%
25457312714

push

github

randombit
In TLS 1.3 verification of client certs, check the correct extension for OCSP

This was checking if the client asked us (the server) for OCSP, instead of
checking if we asked the client for OCSP when we sent the CertificateRequest.

107574 of 120422 relevant lines covered (89.33%)

11482758.98 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.0
/src/tests/test_ocsp.cpp
1
/*
2
* (C) 2016 Jack Lloyd
3
* (C) 2022 René Meusel, Rohde & Schwarz Cybersecurity
4
*
5
* Botan is released under the Simplified BSD License (see license.txt)
6
*/
7

8
#include "tests.h"
9

10
#if defined(BOTAN_HAS_OCSP)
11
   #include "test_arb_eq.h"
12
   #include <botan/certstor.h>
13
   #include <botan/ocsp.h>
14
   #include <botan/x509path.h>
15
   #include <botan/internal/calendar.h>
16
#endif
17

18
namespace Botan_Tests {
19

20
namespace {
21

22
#if defined(BOTAN_HAS_OCSP) && defined(BOTAN_HAS_RSA) && defined(BOTAN_HAS_EMSA_PKCS1) && \
23
   defined(BOTAN_TARGET_OS_HAS_FILESYSTEM)
24

25
class OCSP_Tests final : public Test {
1✔
26
   private:
27
      static Botan::X509_Certificate load_test_X509_cert(const std::string& path) {
30✔
28
         return Botan::X509_Certificate(Test::data_file(path));
60✔
29
      }
30

31
      static Botan::OCSP::Response load_test_OCSP_resp(const std::string& path) {
12✔
32
         return Botan::OCSP::Response(Test::read_binary_data_file(path));
24✔
33
      }
34

35
      static Test::Result test_response_parsing() {
1✔
36
         Test::Result result("OCSP response parsing");
1✔
37

38
         // Simple parsing tests
39
         const std::vector<std::string> ocsp_input_paths = {
1✔
40
            "x509/ocsp/resp1.der", "x509/ocsp/resp2.der", "x509/ocsp/resp3.der"};
1✔
41

42
         for(const std::string& ocsp_input_path : ocsp_input_paths) {
4✔
43
            try {
3✔
44
               const Botan::OCSP::Response resp(Test::read_binary_data_file(ocsp_input_path));
6✔
45
               result.test_enum_eq(
6✔
46
                  "parsing was successful", resp.status(), Botan::OCSP::Response_Status_Code::Successful);
3✔
47
               result.test_success("Parsed input " + ocsp_input_path);
3✔
48
            } catch(Botan::Exception& e) {
3✔
49
               result.test_failure("Parsing failed", e.what());
×
50
            }
×
51
         }
52

53
         const Botan::OCSP::Response resp(
1✔
54
            Test::read_binary_data_file("x509/ocsp/patrickschmidt_ocsp_try_later_wrong_sig.der"));
2✔
55
         result.test_enum_eq(
2✔
56
            "parsing exposes correct status code", resp.status(), Botan::OCSP::Response_Status_Code::Try_Later);
1✔
57

58
         return result;
1✔
59
      }
1✔
60

61
      static Test::Result test_response_with_bykey_responder_id() {
1✔
62
         // RFC 6960's ASN.1 module is "DEFINITIONS EXPLICIT TAGS" and ResponderID's
63
         // CHOICE alternatives are not marked IMPLICIT, so byKey is encoded as
64
         // constructed [2] wrapping a primitive OCTET STRING. The data below
65
         // was produced using `openssl ocsp ... -resp_key_id`
66
         Test::Result result("OCSP response with byKey ResponderID");
1✔
67

68
         const Botan::OCSP::Response resp(Test::read_binary_data_file("x509/ocsp/byKey_responderID.der"));
2✔
69
         result.test_enum_eq(
2✔
70
            "Successful response status", resp.status(), Botan::OCSP::Response_Status_Code::Successful);
1✔
71

72
         const auto responder = load_test_X509_cert("x509/ocsp/byKey_responder.pem");
1✔
73

74
         result.test_is_true("byKey response has empty signer_name", resp.signer_name().empty());
1✔
75
         result.test_sz_eq("byKey hash is 20 bytes (SHA-1)", resp.signer_key_hash().size(), 20);
1✔
76
         result.test_bin_eq("byKey hash matches responder pubkey SHA-1",
1✔
77
                            resp.signer_key_hash(),
78
                            responder.subject_public_key_bitstring_sha1());
79

80
         test_arb_eq(
2✔
81
            result, "Responder is found via byKey", resp.find_signing_certificate(responder), std::optional(responder));
2✔
82

83
         return result;
1✔
84
      }
1✔
85

86
      static Test::Result test_response_certificate_access() {
1✔
87
         Test::Result result("OCSP response certificate access");
1✔
88

89
         try {
1✔
90
            const Botan::OCSP::Response resp1(Test::read_binary_data_file("x509/ocsp/resp1.der"));
2✔
91
            const auto& certs1 = resp1.certificates();
1✔
92
            if(result.test_sz_eq("Expected count of certificates", certs1.size(), 1)) {
1✔
93
               const auto& cert = certs1.front();
1✔
94
               const Botan::X509_DN expected_dn(
1✔
95
                  {std::make_pair("X520.CommonName", "Symantec Class 3 EV SSL CA - G3 OCSP Responder")});
1✔
96
               const bool matches = cert.subject_dn() == expected_dn;
1✔
97
               result.test_is_true("CN matches expected", matches);
1✔
98
            }
1✔
99

100
            const Botan::OCSP::Response resp2(Test::read_binary_data_file("x509/ocsp/resp2.der"));
2✔
101
            const auto& certs2 = resp2.certificates();
1✔
102
            result.test_sz_eq("Expect no certificates", certs2.size(), 0);
1✔
103
         } catch(Botan::Exception& e) {
1✔
104
            result.test_failure("Parsing failed", e.what());
×
105
         }
×
106

107
         return result;
1✔
108
      }
×
109

110
      static Test::Result test_request_encoding() {
1✔
111
         Test::Result result("OCSP request encoding");
1✔
112

113
         const Botan::X509_Certificate end_entity(Test::data_file("x509/ocsp/gmail.pem"));
2✔
114
         const Botan::X509_Certificate issuer(Test::data_file("x509/ocsp/google_g2.pem"));
2✔
115

116
         try {
1✔
117
            const Botan::OCSP::Request bogus(end_entity, issuer);
1✔
118
            result.test_failure("Bad arguments (swapped end entity, issuer) accepted");
×
119
         } catch(Botan::Invalid_Argument&) {
1✔
120
            result.test_success("Bad arguments rejected");
1✔
121
         }
1✔
122

123
         const std::string expected_request =
1✔
124
            "ME4wTKADAgEAMEUwQzBBMAkGBSsOAwIaBQAEFPLgavmFih2NcJtJGSN6qbUaKH5kBBRK3QYWG7z2aLV29YG2u2IaulqBLwIIQkg+DF+RYMY=";
1✔
125

126
         const Botan::OCSP::Request req1(issuer, end_entity);
1✔
127
         result.test_str_eq("Encoded OCSP request", req1.base64_encode(), expected_request);
1✔
128

129
         const Botan::OCSP::Request req2(issuer, BigInt::from_bytes(end_entity.serial_number()));
1✔
130
         result.test_str_eq("Encoded OCSP request", req2.base64_encode(), expected_request);
1✔
131

132
         return result;
2✔
133
      }
2✔
134

135
      static Test::Result test_response_find_signing_certificate() {
1✔
136
         Test::Result result("OCSP response finding signature certificates");
1✔
137

138
         // OCSP response is signed by the issuing CA itself
139
         auto randombit_ocsp = load_test_OCSP_resp("x509/ocsp/randombit_ocsp.der");
1✔
140
         auto randombit_ca = load_test_X509_cert("x509/ocsp/letsencrypt.pem");
1✔
141

142
         // OCSP response is signed by an authorized responder certificate
143
         // issued by the issuing CA and embedded in the response
144
         auto bdr_ocsp = load_test_OCSP_resp("x509/ocsp/bdr-ocsp-resp.der");
1✔
145
         auto bdr_responder = load_test_X509_cert("x509/ocsp/bdr-ocsp-responder.pem");
1✔
146
         auto bdr_ca = load_test_X509_cert("x509/ocsp/bdr-int.pem");
1✔
147

148
         // The response in bdr_ocsp contains two certificates
149
         if(result.test_sz_eq("both certificates found", bdr_ocsp.certificates().size(), 2)) {
1✔
150
            result.test_str_eq("first cert in response",
2✔
151
                               bdr_ocsp.certificates()[0].subject_info("X520.CommonName").at(0),
2✔
152
                               "D-TRUST OCSP 4 2-2 EV 2016");
153
            result.test_str_eq("second cert in response",
2✔
154
                               bdr_ocsp.certificates()[1].subject_info("X520.CommonName").at(0),
2✔
155
                               "D-TRUST CA 2-2 EV 2016");
156
         }
157

158
         // Dummy OCSP response is not signed at all
159
         auto dummy_ocsp = Botan::OCSP::Response(Botan::Certificate_Status_Code::OCSP_SERVER_NOT_AVAILABLE);
1✔
160

161
         // OCSP response is signed by 3rd party responder certificate that is
162
         // not included in the OCSP response itself
163
         // See `src/scripts/randombit_ocsp_forger.sh` for a helper script to recreate those.
164
         auto randombit_alt_resp_ocsp = load_test_OCSP_resp("x509/ocsp/randombit_ocsp_forged_valid_nocerts.der");
1✔
165
         auto randombit_alt_resp_cert = load_test_X509_cert("x509/ocsp/randombit_ocsp_forged_responder.pem");
1✔
166

167
         result.test_opt_is_null("Dummy has no signing certificate",
1✔
168
                                 dummy_ocsp.find_signing_certificate(Botan::X509_Certificate()));
2✔
169

170
         test_arb_eq(result,
2✔
171
                     "CA is returned as signing certificate",
172
                     randombit_ocsp.find_signing_certificate(randombit_ca),
1✔
173
                     std::optional(randombit_ca));
1✔
174
         result.test_opt_is_null("No signer certificate is returned when signer couldn't be determined",
1✔
175
                                 randombit_ocsp.find_signing_certificate(bdr_ca));
1✔
176

177
         test_arb_eq(result,
2✔
178
                     "Delegated responder certificate is returned for further validation",
179
                     bdr_ocsp.find_signing_certificate(bdr_ca),
1✔
180
                     std::optional(bdr_responder));
1✔
181

182
         result.test_opt_is_null(
1✔
183
            "Delegated responder without stapled certs does not find signer without user-provided certs",
184
            randombit_alt_resp_ocsp.find_signing_certificate(randombit_ca));
1✔
185

186
         auto trusted_responders = std::make_unique<Botan::Certificate_Store_In_Memory>(randombit_alt_resp_cert);
1✔
187
         test_arb_eq(result,
2✔
188
                     "Delegated responder returns user-provided cert",
189
                     randombit_alt_resp_ocsp.find_signing_certificate(randombit_ca, trusted_responders.get()),
2✔
190
                     std::optional(randombit_alt_resp_cert));
1✔
191

192
         return result;
2✔
193
      }
1✔
194

195
      static Test::Result test_response_verification_with_next_update_without_max_age() {
1✔
196
         Test::Result result("OCSP request check with next_update w/o max_age");
1✔
197

198
         auto ee = load_test_X509_cert("x509/ocsp/randombit.pem");
1✔
199
         auto ca = load_test_X509_cert("x509/ocsp/letsencrypt.pem");
1✔
200
         auto trust_root = load_test_X509_cert("x509/ocsp/geotrust.pem");
1✔
201

202
         const std::vector<Botan::X509_Certificate> cert_path = {ee, ca, trust_root};
4✔
203

204
         auto ocsp = load_test_OCSP_resp("x509/ocsp/randombit_ocsp.der");
1✔
205

206
         Botan::Certificate_Store_In_Memory certstore;
1✔
207
         certstore.add_certificate(trust_root);
1✔
208

209
         auto check_ocsp = [&](const std::chrono::system_clock::time_point valid_time,
5✔
210
                               const Botan::Certificate_Status_Code expected) {
211
            const auto ocsp_status = Botan::PKIX::check_ocsp(
8✔
212
               cert_path, {ocsp}, {&certstore}, valid_time, Botan::Path_Validation_Restrictions());
16✔
213

214
            return result.test_sz_eq("Expected size of ocsp_status", ocsp_status.size(), 2) &&
8✔
215
                   result.test_sz_eq("Expected size of ocsp_status[0]", ocsp_status[0].size(), 1) &&
4✔
216
                   result.test_is_true(std::string("Status: '") + Botan::to_string(expected) + "'",
28✔
217
                                       ocsp_status[0].contains(expected));
12✔
218
         };
8✔
219

220
         check_ocsp(Botan::calendar_point(2016, 11, 11, 12, 30, 0).to_std_timepoint(),
1✔
221
                    Botan::Certificate_Status_Code::OCSP_NOT_YET_VALID);
222
         check_ocsp(Botan::calendar_point(2016, 11, 18, 12, 30, 0).to_std_timepoint(),
1✔
223
                    Botan::Certificate_Status_Code::OCSP_RESPONSE_GOOD);
224
         check_ocsp(Botan::calendar_point(2016, 11, 20, 8, 30, 0).to_std_timepoint(),
1✔
225
                    Botan::Certificate_Status_Code::OCSP_RESPONSE_GOOD);
226
         check_ocsp(Botan::calendar_point(2016, 11, 28, 8, 30, 0).to_std_timepoint(),
1✔
227
                    Botan::Certificate_Status_Code::OCSP_HAS_EXPIRED);
228

229
         return result;
1✔
230
      }
2✔
231

232
      static Test::Result test_response_verification_with_next_update_with_max_age() {
1✔
233
         Test::Result result("OCSP request check with next_update with max_age");
1✔
234

235
         auto ee = load_test_X509_cert("x509/ocsp/randombit.pem");
1✔
236
         auto ca = load_test_X509_cert("x509/ocsp/letsencrypt.pem");
1✔
237
         auto trust_root = load_test_X509_cert("x509/ocsp/geotrust.pem");
1✔
238

239
         const std::vector<Botan::X509_Certificate> cert_path = {ee, ca, trust_root};
4✔
240

241
         auto ocsp = load_test_OCSP_resp("x509/ocsp/randombit_ocsp.der");
1✔
242

243
         Botan::Certificate_Store_In_Memory certstore;
1✔
244
         certstore.add_certificate(trust_root);
1✔
245

246
         // Some arbitrary time within the validity period of the test certs
247
         const auto max_age = std::chrono::minutes(59);
1✔
248

249
         auto check_ocsp = [&](const std::chrono::system_clock::time_point valid_time,
5✔
250
                               const Botan::Certificate_Status_Code expected) {
251
            const Botan::Path_Validation_Restrictions pvr(false, 110, false, max_age);
4✔
252
            const auto ocsp_status = Botan::PKIX::check_ocsp(cert_path, {ocsp}, {&certstore}, valid_time, pvr);
12✔
253

254
            return result.test_sz_eq("Expected size of ocsp_status", ocsp_status.size(), 2) &&
8✔
255
                   result.test_sz_eq("Expected size of ocsp_status[0]", ocsp_status[0].size(), 1) &&
4✔
256
                   result.test_is_true(std::string("Status: '") + Botan::to_string(expected) + "'",
28✔
257
                                       ocsp_status[0].contains(expected));
12✔
258
         };
8✔
259

260
         check_ocsp(Botan::calendar_point(2016, 11, 11, 12, 30, 0).to_std_timepoint(),
1✔
261
                    Botan::Certificate_Status_Code::OCSP_NOT_YET_VALID);
262
         check_ocsp(Botan::calendar_point(2016, 11, 18, 12, 30, 0).to_std_timepoint(),
1✔
263
                    Botan::Certificate_Status_Code::OCSP_RESPONSE_GOOD);
264
         check_ocsp(Botan::calendar_point(2016, 11, 20, 8, 30, 0).to_std_timepoint(),
1✔
265
                    Botan::Certificate_Status_Code::OCSP_RESPONSE_GOOD);
266
         check_ocsp(Botan::calendar_point(2016, 11, 28, 8, 30, 0).to_std_timepoint(),
1✔
267
                    Botan::Certificate_Status_Code::OCSP_HAS_EXPIRED);
268

269
         return result;
1✔
270
      }
2✔
271

272
      static Test::Result test_response_verification_without_next_update_with_max_age() {
1✔
273
         Test::Result result("OCSP request check w/o next_update with max_age");
1✔
274

275
         auto ee = load_test_X509_cert("x509/ocsp/patrickschmidt.pem");
1✔
276
         auto ca = load_test_X509_cert("x509/ocsp/bdrive_encryption.pem");
1✔
277
         auto trust_root = load_test_X509_cert("x509/ocsp/bdrive_root.pem");
1✔
278

279
         const std::vector<Botan::X509_Certificate> cert_path = {ee, ca, trust_root};
4✔
280

281
         auto ocsp = load_test_OCSP_resp("x509/ocsp/patrickschmidt_ocsp.der");
1✔
282

283
         Botan::Certificate_Store_In_Memory certstore;
1✔
284
         certstore.add_certificate(trust_root);
1✔
285

286
         // Some arbitrary time within the validity period of the test certs
287
         const auto max_age = std::chrono::minutes(59);
1✔
288

289
         auto check_ocsp = [&](const std::chrono::system_clock::time_point valid_time,
4✔
290
                               const Botan::Certificate_Status_Code expected) {
291
            const Botan::Path_Validation_Restrictions pvr(false, 110, false, max_age);
3✔
292
            const auto ocsp_status = Botan::PKIX::check_ocsp(cert_path, {ocsp}, {&certstore}, valid_time, pvr);
9✔
293

294
            result.test_sz_eq("Expected size of ocsp_status", ocsp_status.size(), 2);
3✔
295

296
            if(!ocsp_status.empty()) {
3✔
297
               result.test_sz_eq("Expected size of ocsp_status[0]", ocsp_status[0].size(), 1);
3✔
298

299
               result.test_is_true(std::string("Status: '") + Botan::to_string(expected) + "'",
15✔
300
                                   ocsp_status[0].contains(expected));
6✔
301
            }
302
         };
6✔
303

304
         check_ocsp(Botan::calendar_point(2019, 5, 28, 7, 0, 0).to_std_timepoint(),
1✔
305
                    Botan::Certificate_Status_Code::OCSP_NOT_YET_VALID);
306
         check_ocsp(Botan::calendar_point(2019, 5, 28, 7, 30, 0).to_std_timepoint(),
1✔
307
                    Botan::Certificate_Status_Code::OCSP_RESPONSE_GOOD);
308
         check_ocsp(Botan::calendar_point(2019, 5, 28, 8, 0, 0).to_std_timepoint(),
1✔
309
                    Botan::Certificate_Status_Code::OCSP_IS_TOO_OLD);
310

311
         return result;
1✔
312
      }
2✔
313

314
      static Test::Result test_response_verification_without_next_update_without_max_age() {
1✔
315
         Test::Result result("OCSP request check w/o next_update w/o max_age");
1✔
316

317
         auto ee = load_test_X509_cert("x509/ocsp/patrickschmidt.pem");
1✔
318
         auto ca = load_test_X509_cert("x509/ocsp/bdrive_encryption.pem");
1✔
319
         auto trust_root = load_test_X509_cert("x509/ocsp/bdrive_root.pem");
1✔
320

321
         const std::vector<Botan::X509_Certificate> cert_path = {ee, ca, trust_root};
4✔
322

323
         auto ocsp = load_test_OCSP_resp("x509/ocsp/patrickschmidt_ocsp.der");
1✔
324

325
         Botan::Certificate_Store_In_Memory certstore;
1✔
326
         certstore.add_certificate(trust_root);
1✔
327

328
         auto check_ocsp = [&](const std::chrono::system_clock::time_point valid_time,
4✔
329
                               const Botan::Certificate_Status_Code expected) {
330
            const auto ocsp_status = Botan::PKIX::check_ocsp(
6✔
331
               cert_path, {ocsp}, {&certstore}, valid_time, Botan::Path_Validation_Restrictions());
12✔
332

333
            result.test_sz_eq("Expected size of ocsp_status", ocsp_status.size(), 2);
3✔
334

335
            if(!ocsp_status.empty()) {
3✔
336
               result.test_sz_eq("Expected size of ocsp_status[0]", ocsp_status[0].size(), 1);
3✔
337
               result.test_is_true(std::string("Status: '") + Botan::to_string(expected) + "'",
15✔
338
                                   ocsp_status[0].contains(expected));
6✔
339
            }
340
         };
6✔
341

342
         check_ocsp(Botan::calendar_point(2019, 5, 28, 7, 0, 0).to_std_timepoint(),
1✔
343
                    Botan::Certificate_Status_Code::OCSP_NOT_YET_VALID);
344
         check_ocsp(Botan::calendar_point(2019, 5, 28, 7, 30, 0).to_std_timepoint(),
1✔
345
                    Botan::Certificate_Status_Code::OCSP_RESPONSE_GOOD);
346
         check_ocsp(Botan::calendar_point(2019, 5, 28, 8, 0, 0).to_std_timepoint(),
1✔
347
                    Botan::Certificate_Status_Code::OCSP_RESPONSE_GOOD);
348

349
         return result;
1✔
350
      }
2✔
351

352
      static Test::Result test_response_verification_softfail() {
1✔
353
         Test::Result result("OCSP request softfail check");
1✔
354

355
         auto ee = load_test_X509_cert("x509/ocsp/randombit.pem");
1✔
356
         auto ca = load_test_X509_cert("x509/ocsp/letsencrypt.pem");
1✔
357
         auto trust_root = load_test_X509_cert("x509/ocsp/geotrust.pem");
1✔
358

359
         const std::vector<Botan::X509_Certificate> cert_path = {ee, ca, trust_root};
4✔
360

361
         Botan::OCSP::Response ocsp(Botan::Certificate_Status_Code::OCSP_NO_REVOCATION_URL);
1✔
362

363
         Botan::Certificate_Store_In_Memory certstore;
1✔
364
         certstore.add_certificate(trust_root);
1✔
365

366
         // Some arbitrary time within the validity period of the test certs
367
         const auto valid_time = Botan::calendar_point(2016, 11, 20, 8, 30, 0).to_std_timepoint();
1✔
368
         const auto ocsp_status =
1✔
369
            Botan::PKIX::check_ocsp(cert_path, {ocsp}, {&certstore}, valid_time, Botan::Path_Validation_Restrictions());
4✔
370

371
         if(result.test_sz_eq("Expected size of ocsp_status", ocsp_status.size(), 2)) {
1✔
372
            if(result.test_sz_eq("Expected size of ocsp_status[0]", ocsp_status[0].size(), 1)) {
1✔
373
               result.test_sz_gt(
2✔
374
                  "Status warning", ocsp_status[0].count(Botan::Certificate_Status_Code::OCSP_NO_REVOCATION_URL), 0);
1✔
375
            }
376
         }
377

378
         return result;
1✔
379
      }
3✔
380

381
   #if defined(BOTAN_HAS_ONLINE_REVOCATION_CHECKS)
382
      static Test::Result test_online_request() {
1✔
383
         Test::Result result("OCSP online check");
1✔
384

385
         auto cert = load_test_X509_cert("x509/ocsp/digicert-ecdsa-int.pem");
1✔
386
         auto trust_root = load_test_X509_cert("x509/ocsp/digicert-root.pem");
1✔
387

388
         const std::vector<Botan::X509_Certificate> cert_path = {cert, trust_root};
3✔
389

390
         Botan::Certificate_Store_In_Memory certstore;
1✔
391
         certstore.add_certificate(trust_root);
1✔
392

393
         const auto ocsp_timeout = std::chrono::milliseconds(3000);
1✔
394
         const auto now = std::chrono::system_clock::now();
1✔
395
         auto ocsp_status = Botan::PKIX::check_ocsp_online(
1✔
396
            cert_path, {&certstore}, now, ocsp_timeout, Botan::Path_Validation_Restrictions());
2✔
397

398
         if(result.test_sz_eq("Expected size of ocsp_status", ocsp_status.size(), 1)) {
1✔
399
            if(result.test_sz_eq("Expected size of ocsp_status[0]", ocsp_status[0].size(), 1)) {
1✔
400
               const bool status_good = ocsp_status[0].contains(Botan::Certificate_Status_Code::OCSP_RESPONSE_GOOD);
1✔
401
               const bool server_not_found =
1✔
402
                  ocsp_status[0].contains(Botan::Certificate_Status_Code::OCSP_SERVER_NOT_AVAILABLE);
1✔
403
               result.test_is_true("Expected status", status_good || server_not_found);
1✔
404
            }
405
         }
406

407
         return result;
1✔
408
      }
2✔
409
   #endif
410

411
      static Test::Result test_response_verification_with_additionally_trusted_responder() {
1✔
412
         Test::Result result("OCSP response with user-defined (additional) responder certificate");
1✔
413

414
         // OCSP response is signed by 3rd party responder certificate that is
415
         // not included in the OCSP response itself
416
         // See `src/scripts/randombit_ocsp_forger.sh` for a helper script to recreate those.
417
         auto ocsp = load_test_OCSP_resp("x509/ocsp/randombit_ocsp_forged_valid_nocerts.der");
1✔
418
         auto responder = load_test_X509_cert("x509/ocsp/randombit_ocsp_forged_responder.pem");
1✔
419
         auto ca = load_test_X509_cert("x509/ocsp/letsencrypt.pem");
1✔
420

421
         Botan::Certificate_Store_In_Memory trusted_responders;
1✔
422

423
         // without providing the 3rd party responder certificate no issuer will be found
424
         result.test_opt_is_null("cannot find signing certificate without trusted responders",
1✔
425
                                 ocsp.find_signing_certificate(ca));
1✔
426
         result.test_opt_is_null("cannot find signing certificate without additional help",
1✔
427
                                 ocsp.find_signing_certificate(ca, &trusted_responders));
1✔
428

429
         // add the 3rd party responder certificate to the list of trusted OCSP responder certs
430
         // to find the issuer certificate of this response
431
         trusted_responders.add_certificate(responder);
1✔
432
         test_arb_eq(result,
2✔
433
                     "the responder certificate is returned when it is trusted",
434
                     ocsp.find_signing_certificate(ca, &trusted_responders),
1✔
435
                     std::optional(responder));
1✔
436

437
         result.test_enum_eq("the responder's signature checks out",
1✔
438
                             ocsp.verify_signature(responder),
1✔
439
                             Botan::Certificate_Status_Code::OCSP_SIGNATURE_OK);
440

441
         return result;
1✔
442
      }
1✔
443

444
      static Test::Result test_forged_ocsp_signature_is_rejected() {
1✔
445
         Test::Result result("OCSP response with forged signature is rejected by path validation");
1✔
446

447
         auto ee = load_test_X509_cert("x509/ocsp/randombit.pem");
1✔
448
         auto ca = load_test_X509_cert("x509/ocsp/letsencrypt.pem");
1✔
449
         auto trust_root = load_test_X509_cert("x509/ocsp/geotrust.pem");
1✔
450

451
         const std::vector<Botan::X509_Certificate> cert_path = {ee, ca, trust_root};
4✔
452

453
         Botan::Certificate_Store_In_Memory certstore;
1✔
454
         certstore.add_certificate(trust_root);
1✔
455

456
         const auto valid_time = Botan::calendar_point(2016, 11, 18, 12, 30, 0).to_std_timepoint();
1✔
457

458
         // Verify the unmodified response is accepted
459
         {
1✔
460
            auto ocsp = load_test_OCSP_resp("x509/ocsp/randombit_ocsp.der");
1✔
461
            const auto ocsp_status = Botan::PKIX::check_ocsp(
2✔
462
               cert_path, {ocsp}, {&certstore}, valid_time, Botan::Path_Validation_Restrictions());
4✔
463

464
            if(result.test_sz_eq("Legitimate: expected result count", ocsp_status.size(), 2) &&
2✔
465
               result.test_sz_eq("Legitimate: expected status count", ocsp_status[0].size(), 1)) {
1✔
466
               result.test_is_true("Legitimate response is accepted",
2✔
467
                                   ocsp_status[0].contains(Botan::Certificate_Status_Code::OCSP_RESPONSE_GOOD));
2✔
468
            }
469
         }
1✔
470

471
         // Tamper with the signature and verify check_ocsp rejects it
472
         {
1✔
473
            auto ocsp_bytes = Test::read_binary_data_file("x509/ocsp/randombit_ocsp.der");
1✔
474
            ocsp_bytes.back() ^= 0x01;
1✔
475
            Botan::OCSP::Response forged_ocsp(ocsp_bytes.data(), ocsp_bytes.size());
1✔
476

477
            const auto ocsp_status = Botan::PKIX::check_ocsp(
2✔
478
               cert_path, {forged_ocsp}, {&certstore}, valid_time, Botan::Path_Validation_Restrictions());
4✔
479

480
            if(result.test_sz_eq("Forged: expected result count", ocsp_status.size(), 2) &&
2✔
481
               result.test_sz_eq("Forged: expected status count", ocsp_status[0].size(), 1)) {
1✔
482
               result.test_is_true("Forged signature is rejected",
2✔
483
                                   ocsp_status[0].contains(Botan::Certificate_Status_Code::OCSP_SIGNATURE_ERROR));
2✔
484
            }
485
         }
1✔
486

487
         return result;
1✔
488
      }
4✔
489

490
      static Test::Result test_partial_stapling_preserves_per_slot_gap() {
1✔
491
         Test::Result result("OCSP partial stapling preserves per-slot gap for online fallback");
1✔
492

493
         auto ee = load_test_X509_cert("x509/ocsp/mychain_ee.pem");
1✔
494
         auto ca = load_test_X509_cert("x509/ocsp/mychain_int.pem");
1✔
495
         auto trust_root = load_test_X509_cert("x509/ocsp/mychain_root.pem");
1✔
496

497
         auto ocsp_for_ee = load_test_OCSP_resp("x509/ocsp/mychain_ocsp_for_ee.der");
1✔
498
         auto ocsp_for_int = load_test_OCSP_resp("x509/ocsp/mychain_ocsp_for_int_self_signed.der");
1✔
499

500
         Botan::Certificate_Store_In_Memory certstore;
1✔
501
         certstore.add_certificate(trust_root);
1✔
502

503
         const std::vector<Botan::X509_Certificate> cert_path = {ee, ca, trust_root};
4✔
504
         const auto valid_time = Botan::calendar_point(2022, 9, 22, 22, 30, 0).to_std_timepoint();
1✔
505
         const auto restrictions = Botan::Path_Validation_Restrictions();
2✔
506

507
         // Here the intermediate has a stapled OCSP but the leaf does not
508
         {
1✔
509
            const std::vector<std::optional<Botan::OCSP::Response>> staples = {std::nullopt, ocsp_for_int};
3✔
510
            const auto ocsp_status =
1✔
511
               Botan::PKIX::check_ocsp(cert_path, staples, {&certstore}, valid_time, restrictions);
1✔
512

513
            result.test_sz_eq("missing-leaf: ocsp_status sized to non-root certs", ocsp_status.size(), 2);
1✔
514
            if(ocsp_status.size() == 2) {
1✔
515
               result.test_is_true("missing-leaf: leaf slot is empty", ocsp_status[0].empty());
1✔
516
               result.test_is_false("missing-leaf: intermediate slot is filled", ocsp_status[1].empty());
1✔
517
            }
518
         }
1✔
519

520
         // Here the leaf has a stapled OCSP but the intermediate does not
521
         {
1✔
522
            const std::vector<std::optional<Botan::OCSP::Response>> staples = {ocsp_for_ee, std::nullopt};
3✔
523
            const auto ocsp_status =
1✔
524
               Botan::PKIX::check_ocsp(cert_path, staples, {&certstore}, valid_time, restrictions);
1✔
525

526
            result.test_sz_eq("missing-intermediate: ocsp_status sized to non-root certs", ocsp_status.size(), 2);
1✔
527
            if(ocsp_status.size() == 2) {
1✔
528
               result.test_is_false("missing-intermediate: leaf slot is filled", ocsp_status[0].empty());
1✔
529
               result.test_is_true("missing-intermediate: intermediate slot is empty", ocsp_status[1].empty());
1✔
530
            }
531
         }
1✔
532

533
         return result;
1✔
534
      }
4✔
535

536
      static Test::Result test_responder_cert_with_nocheck_extension() {
1✔
537
         Test::Result result("BDr's OCSP response contains certificate featuring NoCheck extension");
1✔
538

539
         auto ocsp = load_test_OCSP_resp("x509/ocsp/bdr-ocsp-resp.der");
1✔
540
         const bool contains_cert_with_nocheck =
1✔
541
            std::find_if(ocsp.certificates().cbegin(), ocsp.certificates().cend(), [](const auto& cert) {
1✔
542
               return cert.v3_extensions().extension_set(Botan::OID::from_string("PKIX.OCSP.NoCheck"));
1✔
543
            }) != ocsp.certificates().end();
1✔
544

545
         result.test_is_true("Contains NoCheck extension", contains_cert_with_nocheck);
1✔
546

547
         return result;
1✔
548
      }
1✔
549

550
   public:
551
      std::vector<Test::Result> run() override {
1✔
552
         std::vector<Test::Result> results;
1✔
553

554
         results.push_back(test_request_encoding());
2✔
555
         results.push_back(test_response_parsing());
2✔
556
         results.push_back(test_response_with_bykey_responder_id());
2✔
557
         results.push_back(test_response_certificate_access());
2✔
558
         results.push_back(test_response_find_signing_certificate());
2✔
559
         results.push_back(test_response_verification_with_next_update_without_max_age());
2✔
560
         results.push_back(test_response_verification_with_next_update_with_max_age());
2✔
561
         results.push_back(test_response_verification_without_next_update_with_max_age());
2✔
562
         results.push_back(test_response_verification_without_next_update_without_max_age());
2✔
563
         results.push_back(test_response_verification_softfail());
2✔
564
         results.push_back(test_response_verification_with_additionally_trusted_responder());
2✔
565
         results.push_back(test_forged_ocsp_signature_is_rejected());
2✔
566
         results.push_back(test_partial_stapling_preserves_per_slot_gap());
2✔
567
         results.push_back(test_responder_cert_with_nocheck_extension());
2✔
568

569
   #if defined(BOTAN_HAS_ONLINE_REVOCATION_CHECKS)
570
         if(Test::options().run_online_tests()) {
1✔
571
            results.push_back(test_online_request());
2✔
572
         }
573
   #endif
574

575
         return results;
1✔
576
      }
×
577
};
578

579
BOTAN_REGISTER_TEST("x509", "ocsp", OCSP_Tests);
580

581
#endif
582

583
}  // namespace
584

585
}  // namespace Botan_Tests
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc