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

randombit / botan / 25301118813

03 May 2026 10:10PM UTC coverage: 89.377% (+0.001%) from 89.376%
25301118813

push

github

web-flow
Merge pull request #5562 from randombit/jack/ocsp-fixes

Various OCSP hardenings

107198 of 119939 relevant lines covered (89.38%)

11279172.61 hits per line

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

97.69
/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) {
26✔
28
         return Botan::X509_Certificate(Test::data_file(path));
52✔
29
      }
30

31
      static Botan::OCSP::Response load_test_OCSP_resp(const std::string& path) {
10✔
32
         return Botan::OCSP::Response(Test::read_binary_data_file(path));
20✔
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_certificate_access() {
1✔
62
         Test::Result result("OCSP response certificate access");
1✔
63

64
         try {
1✔
65
            const Botan::OCSP::Response resp1(Test::read_binary_data_file("x509/ocsp/resp1.der"));
2✔
66
            const auto& certs1 = resp1.certificates();
1✔
67
            if(result.test_sz_eq("Expected count of certificates", certs1.size(), 1)) {
1✔
68
               const auto& cert = certs1.front();
1✔
69
               const Botan::X509_DN expected_dn(
1✔
70
                  {std::make_pair("X520.CommonName", "Symantec Class 3 EV SSL CA - G3 OCSP Responder")});
1✔
71
               const bool matches = cert.subject_dn() == expected_dn;
1✔
72
               result.test_is_true("CN matches expected", matches);
1✔
73
            }
1✔
74

75
            const Botan::OCSP::Response resp2(Test::read_binary_data_file("x509/ocsp/resp2.der"));
2✔
76
            const auto& certs2 = resp2.certificates();
1✔
77
            result.test_sz_eq("Expect no certificates", certs2.size(), 0);
1✔
78
         } catch(Botan::Exception& e) {
1✔
79
            result.test_failure("Parsing failed", e.what());
×
80
         }
×
81

82
         return result;
1✔
83
      }
×
84

85
      static Test::Result test_request_encoding() {
1✔
86
         Test::Result result("OCSP request encoding");
1✔
87

88
         const Botan::X509_Certificate end_entity(Test::data_file("x509/ocsp/gmail.pem"));
2✔
89
         const Botan::X509_Certificate issuer(Test::data_file("x509/ocsp/google_g2.pem"));
2✔
90

91
         try {
1✔
92
            const Botan::OCSP::Request bogus(end_entity, issuer);
1✔
93
            result.test_failure("Bad arguments (swapped end entity, issuer) accepted");
×
94
         } catch(Botan::Invalid_Argument&) {
1✔
95
            result.test_success("Bad arguments rejected");
1✔
96
         }
1✔
97

98
         const std::string expected_request =
1✔
99
            "ME4wTKADAgEAMEUwQzBBMAkGBSsOAwIaBQAEFPLgavmFih2NcJtJGSN6qbUaKH5kBBRK3QYWG7z2aLV29YG2u2IaulqBLwIIQkg+DF+RYMY=";
1✔
100

101
         const Botan::OCSP::Request req1(issuer, end_entity);
1✔
102
         result.test_str_eq("Encoded OCSP request", req1.base64_encode(), expected_request);
1✔
103

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

107
         return result;
2✔
108
      }
2✔
109

110
      static Test::Result test_response_find_signing_certificate() {
1✔
111
         Test::Result result("OCSP response finding signature certificates");
1✔
112

113
         // OCSP response is signed by the issuing CA itself
114
         auto randombit_ocsp = load_test_OCSP_resp("x509/ocsp/randombit_ocsp.der");
1✔
115
         auto randombit_ca = load_test_X509_cert("x509/ocsp/letsencrypt.pem");
1✔
116

117
         // OCSP response is signed by an authorized responder certificate
118
         // issued by the issuing CA and embedded in the response
119
         auto bdr_ocsp = load_test_OCSP_resp("x509/ocsp/bdr-ocsp-resp.der");
1✔
120
         auto bdr_responder = load_test_X509_cert("x509/ocsp/bdr-ocsp-responder.pem");
1✔
121
         auto bdr_ca = load_test_X509_cert("x509/ocsp/bdr-int.pem");
1✔
122

123
         // The response in bdr_ocsp contains two certificates
124
         if(result.test_sz_eq("both certificates found", bdr_ocsp.certificates().size(), 2)) {
1✔
125
            result.test_str_eq("first cert in response",
2✔
126
                               bdr_ocsp.certificates()[0].subject_info("X520.CommonName").at(0),
2✔
127
                               "D-TRUST OCSP 4 2-2 EV 2016");
128
            result.test_str_eq("second cert in response",
2✔
129
                               bdr_ocsp.certificates()[1].subject_info("X520.CommonName").at(0),
2✔
130
                               "D-TRUST CA 2-2 EV 2016");
131
         }
132

133
         // Dummy OCSP response is not signed at all
134
         auto dummy_ocsp = Botan::OCSP::Response(Botan::Certificate_Status_Code::OCSP_SERVER_NOT_AVAILABLE);
1✔
135

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

142
         result.test_opt_is_null("Dummy has no signing certificate",
1✔
143
                                 dummy_ocsp.find_signing_certificate(Botan::X509_Certificate()));
2✔
144

145
         test_arb_eq(result,
2✔
146
                     "CA is returned as signing certificate",
147
                     randombit_ocsp.find_signing_certificate(randombit_ca),
1✔
148
                     std::optional(randombit_ca));
1✔
149
         result.test_opt_is_null("No signer certificate is returned when signer couldn't be determined",
1✔
150
                                 randombit_ocsp.find_signing_certificate(bdr_ca));
1✔
151

152
         test_arb_eq(result,
2✔
153
                     "Delegated responder certificate is returned for further validation",
154
                     bdr_ocsp.find_signing_certificate(bdr_ca),
1✔
155
                     std::optional(bdr_responder));
1✔
156

157
         result.test_opt_is_null(
1✔
158
            "Delegated responder without stapled certs does not find signer without user-provided certs",
159
            randombit_alt_resp_ocsp.find_signing_certificate(randombit_ca));
1✔
160

161
         auto trusted_responders = std::make_unique<Botan::Certificate_Store_In_Memory>(randombit_alt_resp_cert);
1✔
162
         test_arb_eq(result,
2✔
163
                     "Delegated responder returns user-provided cert",
164
                     randombit_alt_resp_ocsp.find_signing_certificate(randombit_ca, trusted_responders.get()),
2✔
165
                     std::optional(randombit_alt_resp_cert));
1✔
166

167
         return result;
1✔
168
      }
1✔
169

170
      static Test::Result test_response_verification_with_next_update_without_max_age() {
1✔
171
         Test::Result result("OCSP request check with next_update w/o max_age");
1✔
172

173
         auto ee = load_test_X509_cert("x509/ocsp/randombit.pem");
1✔
174
         auto ca = load_test_X509_cert("x509/ocsp/letsencrypt.pem");
1✔
175
         auto trust_root = load_test_X509_cert("x509/ocsp/geotrust.pem");
1✔
176

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

179
         auto ocsp = load_test_OCSP_resp("x509/ocsp/randombit_ocsp.der");
1✔
180

181
         Botan::Certificate_Store_In_Memory certstore;
1✔
182
         certstore.add_certificate(trust_root);
1✔
183

184
         auto check_ocsp = [&](const std::chrono::system_clock::time_point valid_time,
5✔
185
                               const Botan::Certificate_Status_Code expected) {
186
            const auto ocsp_status = Botan::PKIX::check_ocsp(
8✔
187
               cert_path, {ocsp}, {&certstore}, valid_time, Botan::Path_Validation_Restrictions());
16✔
188

189
            return result.test_sz_eq("Expected size of ocsp_status", ocsp_status.size(), 1) &&
8✔
190
                   result.test_sz_eq("Expected size of ocsp_status[0]", ocsp_status[0].size(), 1) &&
4✔
191
                   result.test_is_true(std::string("Status: '") + Botan::to_string(expected) + "'",
28✔
192
                                       ocsp_status[0].contains(expected));
12✔
193
         };
8✔
194

195
         check_ocsp(Botan::calendar_point(2016, 11, 11, 12, 30, 0).to_std_timepoint(),
1✔
196
                    Botan::Certificate_Status_Code::OCSP_NOT_YET_VALID);
197
         check_ocsp(Botan::calendar_point(2016, 11, 18, 12, 30, 0).to_std_timepoint(),
1✔
198
                    Botan::Certificate_Status_Code::OCSP_RESPONSE_GOOD);
199
         check_ocsp(Botan::calendar_point(2016, 11, 20, 8, 30, 0).to_std_timepoint(),
1✔
200
                    Botan::Certificate_Status_Code::OCSP_RESPONSE_GOOD);
201
         check_ocsp(Botan::calendar_point(2016, 11, 28, 8, 30, 0).to_std_timepoint(),
1✔
202
                    Botan::Certificate_Status_Code::OCSP_HAS_EXPIRED);
203

204
         return result;
1✔
205
      }
2✔
206

207
      static Test::Result test_response_verification_with_next_update_with_max_age() {
1✔
208
         Test::Result result("OCSP request check with next_update with max_age");
1✔
209

210
         auto ee = load_test_X509_cert("x509/ocsp/randombit.pem");
1✔
211
         auto ca = load_test_X509_cert("x509/ocsp/letsencrypt.pem");
1✔
212
         auto trust_root = load_test_X509_cert("x509/ocsp/geotrust.pem");
1✔
213

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

216
         auto ocsp = load_test_OCSP_resp("x509/ocsp/randombit_ocsp.der");
1✔
217

218
         Botan::Certificate_Store_In_Memory certstore;
1✔
219
         certstore.add_certificate(trust_root);
1✔
220

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

224
         auto check_ocsp = [&](const std::chrono::system_clock::time_point valid_time,
5✔
225
                               const Botan::Certificate_Status_Code expected) {
226
            const Botan::Path_Validation_Restrictions pvr(false, 110, false, max_age);
4✔
227
            const auto ocsp_status = Botan::PKIX::check_ocsp(cert_path, {ocsp}, {&certstore}, valid_time, pvr);
12✔
228

229
            return result.test_sz_eq("Expected size of ocsp_status", ocsp_status.size(), 1) &&
8✔
230
                   result.test_sz_eq("Expected size of ocsp_status[0]", ocsp_status[0].size(), 1) &&
4✔
231
                   result.test_is_true(std::string("Status: '") + Botan::to_string(expected) + "'",
28✔
232
                                       ocsp_status[0].contains(expected));
12✔
233
         };
8✔
234

235
         check_ocsp(Botan::calendar_point(2016, 11, 11, 12, 30, 0).to_std_timepoint(),
1✔
236
                    Botan::Certificate_Status_Code::OCSP_NOT_YET_VALID);
237
         check_ocsp(Botan::calendar_point(2016, 11, 18, 12, 30, 0).to_std_timepoint(),
1✔
238
                    Botan::Certificate_Status_Code::OCSP_RESPONSE_GOOD);
239
         check_ocsp(Botan::calendar_point(2016, 11, 20, 8, 30, 0).to_std_timepoint(),
1✔
240
                    Botan::Certificate_Status_Code::OCSP_RESPONSE_GOOD);
241
         check_ocsp(Botan::calendar_point(2016, 11, 28, 8, 30, 0).to_std_timepoint(),
1✔
242
                    Botan::Certificate_Status_Code::OCSP_HAS_EXPIRED);
243

244
         return result;
1✔
245
      }
2✔
246

247
      static Test::Result test_response_verification_without_next_update_with_max_age() {
1✔
248
         Test::Result result("OCSP request check w/o next_update with max_age");
1✔
249

250
         auto ee = load_test_X509_cert("x509/ocsp/patrickschmidt.pem");
1✔
251
         auto ca = load_test_X509_cert("x509/ocsp/bdrive_encryption.pem");
1✔
252
         auto trust_root = load_test_X509_cert("x509/ocsp/bdrive_root.pem");
1✔
253

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

256
         auto ocsp = load_test_OCSP_resp("x509/ocsp/patrickschmidt_ocsp.der");
1✔
257

258
         Botan::Certificate_Store_In_Memory certstore;
1✔
259
         certstore.add_certificate(trust_root);
1✔
260

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

264
         auto check_ocsp = [&](const std::chrono::system_clock::time_point valid_time,
4✔
265
                               const Botan::Certificate_Status_Code expected) {
266
            const Botan::Path_Validation_Restrictions pvr(false, 110, false, max_age);
3✔
267
            const auto ocsp_status = Botan::PKIX::check_ocsp(cert_path, {ocsp}, {&certstore}, valid_time, pvr);
9✔
268

269
            result.test_sz_eq("Expected size of ocsp_status", ocsp_status.size(), 1);
3✔
270

271
            if(!ocsp_status.empty()) {
3✔
272
               result.test_sz_eq("Expected size of ocsp_status[0]", ocsp_status[0].size(), 1);
3✔
273

274
               result.test_is_true(std::string("Status: '") + Botan::to_string(expected) + "'",
15✔
275
                                   ocsp_status[0].contains(expected));
6✔
276
            }
277
         };
6✔
278

279
         check_ocsp(Botan::calendar_point(2019, 5, 28, 7, 0, 0).to_std_timepoint(),
1✔
280
                    Botan::Certificate_Status_Code::OCSP_NOT_YET_VALID);
281
         check_ocsp(Botan::calendar_point(2019, 5, 28, 7, 30, 0).to_std_timepoint(),
1✔
282
                    Botan::Certificate_Status_Code::OCSP_RESPONSE_GOOD);
283
         check_ocsp(Botan::calendar_point(2019, 5, 28, 8, 0, 0).to_std_timepoint(),
1✔
284
                    Botan::Certificate_Status_Code::OCSP_IS_TOO_OLD);
285

286
         return result;
1✔
287
      }
2✔
288

289
      static Test::Result test_response_verification_without_next_update_without_max_age() {
1✔
290
         Test::Result result("OCSP request check w/o next_update w/o max_age");
1✔
291

292
         auto ee = load_test_X509_cert("x509/ocsp/patrickschmidt.pem");
1✔
293
         auto ca = load_test_X509_cert("x509/ocsp/bdrive_encryption.pem");
1✔
294
         auto trust_root = load_test_X509_cert("x509/ocsp/bdrive_root.pem");
1✔
295

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

298
         auto ocsp = load_test_OCSP_resp("x509/ocsp/patrickschmidt_ocsp.der");
1✔
299

300
         Botan::Certificate_Store_In_Memory certstore;
1✔
301
         certstore.add_certificate(trust_root);
1✔
302

303
         auto check_ocsp = [&](const std::chrono::system_clock::time_point valid_time,
4✔
304
                               const Botan::Certificate_Status_Code expected) {
305
            const auto ocsp_status = Botan::PKIX::check_ocsp(
6✔
306
               cert_path, {ocsp}, {&certstore}, valid_time, Botan::Path_Validation_Restrictions());
12✔
307

308
            result.test_sz_eq("Expected size of ocsp_status", ocsp_status.size(), 1);
3✔
309

310
            if(!ocsp_status.empty()) {
3✔
311
               result.test_sz_eq("Expected size of ocsp_status[0]", ocsp_status[0].size(), 1);
3✔
312
               result.test_is_true(std::string("Status: '") + Botan::to_string(expected) + "'",
15✔
313
                                   ocsp_status[0].contains(expected));
6✔
314
            }
315
         };
6✔
316

317
         check_ocsp(Botan::calendar_point(2019, 5, 28, 7, 0, 0).to_std_timepoint(),
1✔
318
                    Botan::Certificate_Status_Code::OCSP_NOT_YET_VALID);
319
         check_ocsp(Botan::calendar_point(2019, 5, 28, 7, 30, 0).to_std_timepoint(),
1✔
320
                    Botan::Certificate_Status_Code::OCSP_RESPONSE_GOOD);
321
         check_ocsp(Botan::calendar_point(2019, 5, 28, 8, 0, 0).to_std_timepoint(),
1✔
322
                    Botan::Certificate_Status_Code::OCSP_RESPONSE_GOOD);
323

324
         return result;
1✔
325
      }
2✔
326

327
      static Test::Result test_response_verification_softfail() {
1✔
328
         Test::Result result("OCSP request softfail check");
1✔
329

330
         auto ee = load_test_X509_cert("x509/ocsp/randombit.pem");
1✔
331
         auto ca = load_test_X509_cert("x509/ocsp/letsencrypt.pem");
1✔
332
         auto trust_root = load_test_X509_cert("x509/ocsp/geotrust.pem");
1✔
333

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

336
         Botan::OCSP::Response ocsp(Botan::Certificate_Status_Code::OCSP_NO_REVOCATION_URL);
1✔
337

338
         Botan::Certificate_Store_In_Memory certstore;
1✔
339
         certstore.add_certificate(trust_root);
1✔
340

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

346
         if(result.test_sz_eq("Expected size of ocsp_status", ocsp_status.size(), 1)) {
1✔
347
            if(result.test_sz_eq("Expected size of ocsp_status[0]", ocsp_status[0].size(), 1)) {
1✔
348
               result.test_sz_gt(
2✔
349
                  "Status warning", ocsp_status[0].count(Botan::Certificate_Status_Code::OCSP_NO_REVOCATION_URL), 0);
1✔
350
            }
351
         }
352

353
         return result;
1✔
354
      }
3✔
355

356
   #if defined(BOTAN_HAS_ONLINE_REVOCATION_CHECKS)
357
      static Test::Result test_online_request() {
1✔
358
         Test::Result result("OCSP online check");
1✔
359

360
         auto cert = load_test_X509_cert("x509/ocsp/digicert-ecdsa-int.pem");
1✔
361
         auto trust_root = load_test_X509_cert("x509/ocsp/digicert-root.pem");
1✔
362

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

365
         Botan::Certificate_Store_In_Memory certstore;
1✔
366
         certstore.add_certificate(trust_root);
1✔
367

368
         const auto ocsp_timeout = std::chrono::milliseconds(3000);
1✔
369
         const auto now = std::chrono::system_clock::now();
1✔
370
         auto ocsp_status = Botan::PKIX::check_ocsp_online(
1✔
371
            cert_path, {&certstore}, now, ocsp_timeout, Botan::Path_Validation_Restrictions());
2✔
372

373
         if(result.test_sz_eq("Expected size of ocsp_status", ocsp_status.size(), 1)) {
1✔
374
            if(result.test_sz_eq("Expected size of ocsp_status[0]", ocsp_status[0].size(), 1)) {
1✔
375
               const bool status_good = ocsp_status[0].contains(Botan::Certificate_Status_Code::OCSP_RESPONSE_GOOD);
1✔
376
               const bool server_not_found =
1✔
377
                  ocsp_status[0].contains(Botan::Certificate_Status_Code::OCSP_SERVER_NOT_AVAILABLE);
1✔
378
               result.test_is_true("Expected status", status_good || server_not_found);
1✔
379
            }
380
         }
381

382
         return result;
1✔
383
      }
2✔
384
   #endif
385

386
      static Test::Result test_response_verification_with_additionally_trusted_responder() {
1✔
387
         Test::Result result("OCSP response with user-defined (additional) responder certificate");
1✔
388

389
         // OCSP response is signed by 3rd party responder certificate that is
390
         // not included in the OCSP response itself
391
         // See `src/scripts/randombit_ocsp_forger.sh` for a helper script to recreate those.
392
         auto ocsp = load_test_OCSP_resp("x509/ocsp/randombit_ocsp_forged_valid_nocerts.der");
1✔
393
         auto responder = load_test_X509_cert("x509/ocsp/randombit_ocsp_forged_responder.pem");
1✔
394
         auto ca = load_test_X509_cert("x509/ocsp/letsencrypt.pem");
1✔
395

396
         Botan::Certificate_Store_In_Memory trusted_responders;
1✔
397

398
         // without providing the 3rd party responder certificate no issuer will be found
399
         result.test_opt_is_null("cannot find signing certificate without trusted responders",
1✔
400
                                 ocsp.find_signing_certificate(ca));
1✔
401
         result.test_opt_is_null("cannot find signing certificate without additional help",
1✔
402
                                 ocsp.find_signing_certificate(ca, &trusted_responders));
1✔
403

404
         // add the 3rd party responder certificate to the list of trusted OCSP responder certs
405
         // to find the issuer certificate of this response
406
         trusted_responders.add_certificate(responder);
1✔
407
         test_arb_eq(result,
2✔
408
                     "the responder certificate is returned when it is trusted",
409
                     ocsp.find_signing_certificate(ca, &trusted_responders),
1✔
410
                     std::optional(responder));
1✔
411

412
         result.test_enum_eq("the responder's signature checks out",
1✔
413
                             ocsp.verify_signature(responder),
1✔
414
                             Botan::Certificate_Status_Code::OCSP_SIGNATURE_OK);
415

416
         return result;
1✔
417
      }
1✔
418

419
      static Test::Result test_forged_ocsp_signature_is_rejected() {
1✔
420
         Test::Result result("OCSP response with forged signature is rejected by path validation");
1✔
421

422
         auto ee = load_test_X509_cert("x509/ocsp/randombit.pem");
1✔
423
         auto ca = load_test_X509_cert("x509/ocsp/letsencrypt.pem");
1✔
424
         auto trust_root = load_test_X509_cert("x509/ocsp/geotrust.pem");
1✔
425

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

428
         Botan::Certificate_Store_In_Memory certstore;
1✔
429
         certstore.add_certificate(trust_root);
1✔
430

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

433
         // Verify the unmodified response is accepted
434
         {
1✔
435
            auto ocsp = load_test_OCSP_resp("x509/ocsp/randombit_ocsp.der");
1✔
436
            const auto ocsp_status = Botan::PKIX::check_ocsp(
2✔
437
               cert_path, {ocsp}, {&certstore}, valid_time, Botan::Path_Validation_Restrictions());
4✔
438

439
            if(result.test_sz_eq("Legitimate: expected result count", ocsp_status.size(), 1) &&
2✔
440
               result.test_sz_eq("Legitimate: expected status count", ocsp_status[0].size(), 1)) {
1✔
441
               result.test_is_true("Legitimate response is accepted",
2✔
442
                                   ocsp_status[0].contains(Botan::Certificate_Status_Code::OCSP_RESPONSE_GOOD));
2✔
443
            }
444
         }
1✔
445

446
         // Tamper with the signature and verify check_ocsp rejects it
447
         {
1✔
448
            auto ocsp_bytes = Test::read_binary_data_file("x509/ocsp/randombit_ocsp.der");
1✔
449
            ocsp_bytes.back() ^= 0x01;
1✔
450
            Botan::OCSP::Response forged_ocsp(ocsp_bytes.data(), ocsp_bytes.size());
1✔
451

452
            const auto ocsp_status = Botan::PKIX::check_ocsp(
2✔
453
               cert_path, {forged_ocsp}, {&certstore}, valid_time, Botan::Path_Validation_Restrictions());
4✔
454

455
            if(result.test_sz_eq("Forged: expected result count", ocsp_status.size(), 1) &&
2✔
456
               result.test_sz_eq("Forged: expected status count", ocsp_status[0].size(), 1)) {
1✔
457
               result.test_is_true("Forged signature is rejected",
2✔
458
                                   ocsp_status[0].contains(Botan::Certificate_Status_Code::OCSP_SIGNATURE_ERROR));
2✔
459
            }
460
         }
1✔
461

462
         return result;
1✔
463
      }
4✔
464

465
      static Test::Result test_responder_cert_with_nocheck_extension() {
1✔
466
         Test::Result result("BDr's OCSP response contains certificate featuring NoCheck extension");
1✔
467

468
         auto ocsp = load_test_OCSP_resp("x509/ocsp/bdr-ocsp-resp.der");
1✔
469
         const bool contains_cert_with_nocheck =
1✔
470
            std::find_if(ocsp.certificates().cbegin(), ocsp.certificates().cend(), [](const auto& cert) {
1✔
471
               return cert.v3_extensions().extension_set(Botan::OID::from_string("PKIX.OCSP.NoCheck"));
1✔
472
            }) != ocsp.certificates().end();
1✔
473

474
         result.test_is_true("Contains NoCheck extension", contains_cert_with_nocheck);
1✔
475

476
         return result;
1✔
477
      }
1✔
478

479
   public:
480
      std::vector<Test::Result> run() override {
1✔
481
         std::vector<Test::Result> results;
1✔
482

483
         results.push_back(test_request_encoding());
2✔
484
         results.push_back(test_response_parsing());
2✔
485
         results.push_back(test_response_certificate_access());
2✔
486
         results.push_back(test_response_find_signing_certificate());
2✔
487
         results.push_back(test_response_verification_with_next_update_without_max_age());
2✔
488
         results.push_back(test_response_verification_with_next_update_with_max_age());
2✔
489
         results.push_back(test_response_verification_without_next_update_with_max_age());
2✔
490
         results.push_back(test_response_verification_without_next_update_without_max_age());
2✔
491
         results.push_back(test_response_verification_softfail());
2✔
492
         results.push_back(test_response_verification_with_additionally_trusted_responder());
2✔
493
         results.push_back(test_forged_ocsp_signature_is_rejected());
2✔
494
         results.push_back(test_responder_cert_with_nocheck_extension());
2✔
495

496
   #if defined(BOTAN_HAS_ONLINE_REVOCATION_CHECKS)
497
         if(Test::options().run_online_tests()) {
1✔
498
            results.push_back(test_online_request());
2✔
499
         }
500
   #endif
501

502
         return results;
1✔
503
      }
×
504
};
505

506
BOTAN_REGISTER_TEST("x509", "ocsp", OCSP_Tests);
507

508
#endif
509

510
}  // namespace
511

512
}  // 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