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

randombit / botan / 25650639339

10 May 2026 07:03PM UTC coverage: 89.326% (-0.002%) from 89.328%
25650639339

push

github

web-flow
Merge pull request #5592 from randombit/jack/bn-hardening

Various BigInt/mp related hardenings and bug fixes

107853 of 120741 relevant lines covered (89.33%)

11294230.95 hits per line

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

97.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
         try {
1✔
54
            // Contrary to RFC 6960 this response includes a responseBytes with a non-successful status code
55
            const Botan::OCSP::Response resp(
1✔
56
               Test::read_binary_data_file("x509/ocsp/patrickschmidt_ocsp_try_later_wrong_sig.der"));
3✔
57
            result.test_failure("Accepted invalid encoding OCSP response");
×
58
            result.test_enum_eq(
×
59
               "parsing exposes correct status code", resp.status(), Botan::OCSP::Response_Status_Code::Try_Later);
×
60
         } catch(Botan::Exception&) {
1✔
61
            result.test_success("Rejected invalid encoding OCSP response");
1✔
62
         }
1✔
63

64
         // OCSPResponse SEQUENCE { ENUMERATED 0 }: successful status with no
65
         // responseBytes. RFC 6960 4.2.1 requires responseBytes when successful.
66
         const std::vector<uint8_t> successful_no_response_bytes = {0x30, 0x03, 0x0A, 0x01, 0x00};
1✔
67
         result.test_throws<Botan::Decoding_Error>("Successful status without responseBytes is rejected", [&] {
1✔
68
            const Botan::OCSP::Response resp(successful_no_response_bytes);
1✔
69
         });
×
70

71
         // OCSPResponse SEQUENCE { ENUMERATED 4 }: unknown response code
72
         const std::vector<uint8_t> failed_unknown_code = {0x30, 0x03, 0x0A, 0x01, 0x04};
1✔
73
         result.test_throws<Botan::Decoding_Error>("OCSPResponse with unknown response code is rejected",
1✔
74
                                                   [&] { const Botan::OCSP::Response resp(failed_unknown_code); });
2✔
75

76
         // SEQUENCE { ENUMERATED 3 }: bare tryLater with no trailer parses fine.
77
         const std::vector<uint8_t> try_later_no_trailer = {0x30, 0x03, 0x0A, 0x01, 0x03};
1✔
78
         result.test_no_throw("Bare non-successful status parses", [&] {
1✔
79
            const Botan::OCSP::Response r(try_later_no_trailer);
1✔
80
            result.test_enum_eq("parsed as Try_Later", r.status(), Botan::OCSP::Response_Status_Code::Try_Later);
1✔
81
         });
1✔
82

83
         return result;
1✔
84
      }
1✔
85

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

93
         const Botan::OCSP::Response resp(Test::read_binary_data_file("x509/ocsp/byKey_responderID.der"));
2✔
94
         result.test_enum_eq(
2✔
95
            "Successful response status", resp.status(), Botan::OCSP::Response_Status_Code::Successful);
1✔
96

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

99
         result.test_is_true("byKey response has empty signer_name", resp.signer_name().empty());
1✔
100
         result.test_sz_eq("byKey hash is 20 bytes (SHA-1)", resp.signer_key_hash().size(), 20);
1✔
101
         result.test_bin_eq("byKey hash matches responder pubkey SHA-1",
1✔
102
                            resp.signer_key_hash(),
103
                            responder.subject_public_key_bitstring_sha1());
104

105
         test_arb_eq(
2✔
106
            result, "Responder is found via byKey", resp.find_signing_certificate(responder), std::optional(responder));
2✔
107

108
         return result;
1✔
109
      }
1✔
110

111
      static Test::Result test_response_certificate_access() {
1✔
112
         Test::Result result("OCSP response certificate access");
1✔
113

114
         try {
1✔
115
            const Botan::OCSP::Response resp1(Test::read_binary_data_file("x509/ocsp/resp1.der"));
2✔
116
            const auto& certs1 = resp1.certificates();
1✔
117
            if(result.test_sz_eq("Expected count of certificates", certs1.size(), 1)) {
1✔
118
               const auto& cert = certs1.front();
1✔
119
               const Botan::X509_DN expected_dn(
1✔
120
                  {std::make_pair("X520.CommonName", "Symantec Class 3 EV SSL CA - G3 OCSP Responder")});
1✔
121
               const bool matches = cert.subject_dn() == expected_dn;
1✔
122
               result.test_is_true("CN matches expected", matches);
1✔
123
            }
1✔
124

125
            const Botan::OCSP::Response resp2(Test::read_binary_data_file("x509/ocsp/resp2.der"));
2✔
126
            const auto& certs2 = resp2.certificates();
1✔
127
            result.test_sz_eq("Expect no certificates", certs2.size(), 0);
1✔
128
         } catch(Botan::Exception& e) {
1✔
129
            result.test_failure("Parsing failed", e.what());
×
130
         }
×
131

132
         return result;
1✔
133
      }
×
134

135
      static Test::Result test_request_encoding() {
1✔
136
         Test::Result result("OCSP request encoding");
1✔
137

138
         const Botan::X509_Certificate end_entity(Test::data_file("x509/ocsp/gmail.pem"));
2✔
139
         const Botan::X509_Certificate issuer(Test::data_file("x509/ocsp/google_g2.pem"));
2✔
140

141
         try {
1✔
142
            const Botan::OCSP::Request bogus(end_entity, issuer);
1✔
143
            result.test_failure("Bad arguments (swapped end entity, issuer) accepted");
×
144
         } catch(Botan::Invalid_Argument&) {
1✔
145
            result.test_success("Bad arguments rejected");
1✔
146
         }
1✔
147

148
         const std::string expected_request =
1✔
149
            "ME4wTKADAgEAMEUwQzBBMAkGBSsOAwIaBQAEFPLgavmFih2NcJtJGSN6qbUaKH5kBBRK3QYWG7z2aLV29YG2u2IaulqBLwIIQkg+DF+RYMY=";
1✔
150

151
         const Botan::OCSP::Request req1(issuer, end_entity);
1✔
152
         result.test_str_eq("Encoded OCSP request", req1.base64_encode(), expected_request);
1✔
153

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

157
         return result;
2✔
158
      }
2✔
159

160
      static Test::Result test_response_find_signing_certificate() {
1✔
161
         Test::Result result("OCSP response finding signature certificates");
1✔
162

163
         // OCSP response is signed by the issuing CA itself
164
         auto randombit_ocsp = load_test_OCSP_resp("x509/ocsp/randombit_ocsp.der");
1✔
165
         auto randombit_ca = load_test_X509_cert("x509/ocsp/letsencrypt.pem");
1✔
166

167
         // OCSP response is signed by an authorized responder certificate
168
         // issued by the issuing CA and embedded in the response
169
         auto bdr_ocsp = load_test_OCSP_resp("x509/ocsp/bdr-ocsp-resp.der");
1✔
170
         auto bdr_responder = load_test_X509_cert("x509/ocsp/bdr-ocsp-responder.pem");
1✔
171
         auto bdr_ca = load_test_X509_cert("x509/ocsp/bdr-int.pem");
1✔
172

173
         // The response in bdr_ocsp contains two certificates
174
         if(result.test_sz_eq("both certificates found", bdr_ocsp.certificates().size(), 2)) {
1✔
175
            result.test_str_eq("first cert in response",
2✔
176
                               bdr_ocsp.certificates()[0].subject_info("X520.CommonName").at(0),
2✔
177
                               "D-TRUST OCSP 4 2-2 EV 2016");
178
            result.test_str_eq("second cert in response",
2✔
179
                               bdr_ocsp.certificates()[1].subject_info("X520.CommonName").at(0),
2✔
180
                               "D-TRUST CA 2-2 EV 2016");
181
         }
182

183
         // Dummy OCSP response is not signed at all
184
         auto dummy_ocsp = Botan::OCSP::Response(Botan::Certificate_Status_Code::OCSP_SERVER_NOT_AVAILABLE);
1✔
185

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

192
         result.test_opt_is_null("Dummy has no signing certificate",
1✔
193
                                 dummy_ocsp.find_signing_certificate(Botan::X509_Certificate()));
2✔
194

195
         test_arb_eq(result,
2✔
196
                     "CA is returned as signing certificate",
197
                     randombit_ocsp.find_signing_certificate(randombit_ca),
1✔
198
                     std::optional(randombit_ca));
1✔
199
         result.test_opt_is_null("No signer certificate is returned when signer couldn't be determined",
1✔
200
                                 randombit_ocsp.find_signing_certificate(bdr_ca));
1✔
201

202
         test_arb_eq(result,
2✔
203
                     "Delegated responder certificate is returned for further validation",
204
                     bdr_ocsp.find_signing_certificate(bdr_ca),
1✔
205
                     std::optional(bdr_responder));
1✔
206

207
         result.test_opt_is_null(
1✔
208
            "Delegated responder without stapled certs does not find signer without user-provided certs",
209
            randombit_alt_resp_ocsp.find_signing_certificate(randombit_ca));
1✔
210

211
         auto trusted_responders = std::make_unique<Botan::Certificate_Store_In_Memory>(randombit_alt_resp_cert);
1✔
212
         test_arb_eq(result,
2✔
213
                     "Delegated responder returns user-provided cert",
214
                     randombit_alt_resp_ocsp.find_signing_certificate(randombit_ca, trusted_responders.get()),
2✔
215
                     std::optional(randombit_alt_resp_cert));
1✔
216

217
         return result;
2✔
218
      }
1✔
219

220
      static Test::Result test_response_verification_with_next_update_without_max_age() {
1✔
221
         Test::Result result("OCSP request check with next_update w/o max_age");
1✔
222

223
         auto ee = load_test_X509_cert("x509/ocsp/randombit.pem");
1✔
224
         auto ca = load_test_X509_cert("x509/ocsp/letsencrypt.pem");
1✔
225
         auto trust_root = load_test_X509_cert("x509/ocsp/geotrust.pem");
1✔
226

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

229
         auto ocsp = load_test_OCSP_resp("x509/ocsp/randombit_ocsp.der");
1✔
230

231
         Botan::Certificate_Store_In_Memory certstore;
1✔
232
         certstore.add_certificate(trust_root);
1✔
233

234
         auto check_ocsp = [&](const std::chrono::system_clock::time_point valid_time,
5✔
235
                               const Botan::Certificate_Status_Code expected) {
236
            const auto ocsp_status = Botan::PKIX::check_ocsp(
8✔
237
               cert_path, {ocsp}, {&certstore}, valid_time, Botan::Path_Validation_Restrictions());
16✔
238

239
            return result.test_sz_eq("Expected size of ocsp_status", ocsp_status.size(), 2) &&
8✔
240
                   result.test_sz_eq("Expected size of ocsp_status[0]", ocsp_status[0].size(), 1) &&
4✔
241
                   result.test_is_true(std::string("Status: '") + Botan::to_string(expected) + "'",
28✔
242
                                       ocsp_status[0].contains(expected));
12✔
243
         };
8✔
244

245
         check_ocsp(Botan::calendar_point(2016, 11, 11, 12, 30, 0).to_std_timepoint(),
1✔
246
                    Botan::Certificate_Status_Code::OCSP_NOT_YET_VALID);
247
         check_ocsp(Botan::calendar_point(2016, 11, 18, 12, 30, 0).to_std_timepoint(),
1✔
248
                    Botan::Certificate_Status_Code::OCSP_RESPONSE_GOOD);
249
         check_ocsp(Botan::calendar_point(2016, 11, 20, 8, 30, 0).to_std_timepoint(),
1✔
250
                    Botan::Certificate_Status_Code::OCSP_RESPONSE_GOOD);
251
         check_ocsp(Botan::calendar_point(2016, 11, 28, 8, 30, 0).to_std_timepoint(),
1✔
252
                    Botan::Certificate_Status_Code::OCSP_HAS_EXPIRED);
253

254
         return result;
1✔
255
      }
2✔
256

257
      static Test::Result test_response_verification_with_next_update_with_max_age() {
1✔
258
         Test::Result result("OCSP request check with next_update with max_age");
1✔
259

260
         auto ee = load_test_X509_cert("x509/ocsp/randombit.pem");
1✔
261
         auto ca = load_test_X509_cert("x509/ocsp/letsencrypt.pem");
1✔
262
         auto trust_root = load_test_X509_cert("x509/ocsp/geotrust.pem");
1✔
263

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

266
         auto ocsp = load_test_OCSP_resp("x509/ocsp/randombit_ocsp.der");
1✔
267

268
         Botan::Certificate_Store_In_Memory certstore;
1✔
269
         certstore.add_certificate(trust_root);
1✔
270

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

274
         auto check_ocsp = [&](const std::chrono::system_clock::time_point valid_time,
5✔
275
                               const Botan::Certificate_Status_Code expected) {
276
            const Botan::Path_Validation_Restrictions pvr(false, 110, false, max_age);
4✔
277
            const auto ocsp_status = Botan::PKIX::check_ocsp(cert_path, {ocsp}, {&certstore}, valid_time, pvr);
12✔
278

279
            return result.test_sz_eq("Expected size of ocsp_status", ocsp_status.size(), 2) &&
8✔
280
                   result.test_sz_eq("Expected size of ocsp_status[0]", ocsp_status[0].size(), 1) &&
4✔
281
                   result.test_is_true(std::string("Status: '") + Botan::to_string(expected) + "'",
28✔
282
                                       ocsp_status[0].contains(expected));
12✔
283
         };
8✔
284

285
         check_ocsp(Botan::calendar_point(2016, 11, 11, 12, 30, 0).to_std_timepoint(),
1✔
286
                    Botan::Certificate_Status_Code::OCSP_NOT_YET_VALID);
287
         check_ocsp(Botan::calendar_point(2016, 11, 18, 12, 30, 0).to_std_timepoint(),
1✔
288
                    Botan::Certificate_Status_Code::OCSP_RESPONSE_GOOD);
289
         check_ocsp(Botan::calendar_point(2016, 11, 20, 8, 30, 0).to_std_timepoint(),
1✔
290
                    Botan::Certificate_Status_Code::OCSP_RESPONSE_GOOD);
291
         check_ocsp(Botan::calendar_point(2016, 11, 28, 8, 30, 0).to_std_timepoint(),
1✔
292
                    Botan::Certificate_Status_Code::OCSP_HAS_EXPIRED);
293

294
         return result;
1✔
295
      }
2✔
296

297
      static Test::Result test_response_verification_without_next_update_with_max_age() {
1✔
298
         Test::Result result("OCSP request check w/o next_update with max_age");
1✔
299

300
         auto ee = load_test_X509_cert("x509/ocsp/patrickschmidt.pem");
1✔
301
         auto ca = load_test_X509_cert("x509/ocsp/bdrive_encryption.pem");
1✔
302
         auto trust_root = load_test_X509_cert("x509/ocsp/bdrive_root.pem");
1✔
303

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

306
         auto ocsp = load_test_OCSP_resp("x509/ocsp/patrickschmidt_ocsp.der");
1✔
307

308
         Botan::Certificate_Store_In_Memory certstore;
1✔
309
         certstore.add_certificate(trust_root);
1✔
310

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

314
         auto check_ocsp = [&](const std::chrono::system_clock::time_point valid_time,
4✔
315
                               const Botan::Certificate_Status_Code expected) {
316
            const Botan::Path_Validation_Restrictions pvr(false, 110, false, max_age);
3✔
317
            const auto ocsp_status = Botan::PKIX::check_ocsp(cert_path, {ocsp}, {&certstore}, valid_time, pvr);
9✔
318

319
            result.test_sz_eq("Expected size of ocsp_status", ocsp_status.size(), 2);
3✔
320

321
            if(!ocsp_status.empty()) {
3✔
322
               result.test_sz_eq("Expected size of ocsp_status[0]", ocsp_status[0].size(), 1);
3✔
323

324
               result.test_is_true(std::string("Status: '") + Botan::to_string(expected) + "'",
15✔
325
                                   ocsp_status[0].contains(expected));
6✔
326
            }
327
         };
6✔
328

329
         check_ocsp(Botan::calendar_point(2019, 5, 28, 7, 0, 0).to_std_timepoint(),
1✔
330
                    Botan::Certificate_Status_Code::OCSP_NOT_YET_VALID);
331
         check_ocsp(Botan::calendar_point(2019, 5, 28, 7, 30, 0).to_std_timepoint(),
1✔
332
                    Botan::Certificate_Status_Code::OCSP_RESPONSE_GOOD);
333
         check_ocsp(Botan::calendar_point(2019, 5, 28, 8, 0, 0).to_std_timepoint(),
1✔
334
                    Botan::Certificate_Status_Code::OCSP_IS_TOO_OLD);
335

336
         return result;
1✔
337
      }
2✔
338

339
      static Test::Result test_response_verification_without_next_update_without_max_age() {
1✔
340
         Test::Result result("OCSP request check w/o next_update w/o max_age");
1✔
341

342
         auto ee = load_test_X509_cert("x509/ocsp/patrickschmidt.pem");
1✔
343
         auto ca = load_test_X509_cert("x509/ocsp/bdrive_encryption.pem");
1✔
344
         auto trust_root = load_test_X509_cert("x509/ocsp/bdrive_root.pem");
1✔
345

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

348
         auto ocsp = load_test_OCSP_resp("x509/ocsp/patrickschmidt_ocsp.der");
1✔
349

350
         Botan::Certificate_Store_In_Memory certstore;
1✔
351
         certstore.add_certificate(trust_root);
1✔
352

353
         auto check_ocsp = [&](const std::chrono::system_clock::time_point valid_time,
4✔
354
                               const Botan::Certificate_Status_Code expected) {
355
            const auto ocsp_status = Botan::PKIX::check_ocsp(
6✔
356
               cert_path, {ocsp}, {&certstore}, valid_time, Botan::Path_Validation_Restrictions());
12✔
357

358
            result.test_sz_eq("Expected size of ocsp_status", ocsp_status.size(), 2);
3✔
359

360
            if(!ocsp_status.empty()) {
3✔
361
               result.test_sz_eq("Expected size of ocsp_status[0]", ocsp_status[0].size(), 1);
3✔
362
               result.test_is_true(std::string("Status: '") + Botan::to_string(expected) + "'",
15✔
363
                                   ocsp_status[0].contains(expected));
6✔
364
            }
365
         };
6✔
366

367
         check_ocsp(Botan::calendar_point(2019, 5, 28, 7, 0, 0).to_std_timepoint(),
1✔
368
                    Botan::Certificate_Status_Code::OCSP_NOT_YET_VALID);
369
         check_ocsp(Botan::calendar_point(2019, 5, 28, 7, 30, 0).to_std_timepoint(),
1✔
370
                    Botan::Certificate_Status_Code::OCSP_RESPONSE_GOOD);
371
         check_ocsp(Botan::calendar_point(2019, 5, 28, 8, 0, 0).to_std_timepoint(),
1✔
372
                    Botan::Certificate_Status_Code::OCSP_RESPONSE_GOOD);
373

374
         return result;
1✔
375
      }
2✔
376

377
      static Test::Result test_response_verification_softfail() {
1✔
378
         Test::Result result("OCSP request softfail check");
1✔
379

380
         auto ee = load_test_X509_cert("x509/ocsp/randombit.pem");
1✔
381
         auto ca = load_test_X509_cert("x509/ocsp/letsencrypt.pem");
1✔
382
         auto trust_root = load_test_X509_cert("x509/ocsp/geotrust.pem");
1✔
383

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

386
         Botan::OCSP::Response ocsp(Botan::Certificate_Status_Code::OCSP_NO_REVOCATION_URL);
1✔
387

388
         Botan::Certificate_Store_In_Memory certstore;
1✔
389
         certstore.add_certificate(trust_root);
1✔
390

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

396
         if(result.test_sz_eq("Expected size of ocsp_status", ocsp_status.size(), 2)) {
1✔
397
            if(result.test_sz_eq("Expected size of ocsp_status[0]", ocsp_status[0].size(), 1)) {
1✔
398
               result.test_sz_gt(
2✔
399
                  "Status warning", ocsp_status[0].count(Botan::Certificate_Status_Code::OCSP_NO_REVOCATION_URL), 0);
1✔
400
            }
401
         }
402

403
         return result;
1✔
404
      }
3✔
405

406
   #if defined(BOTAN_HAS_ONLINE_REVOCATION_CHECKS)
407
      static Test::Result test_online_request() {
1✔
408
         Test::Result result("OCSP online check");
1✔
409

410
         auto cert = load_test_X509_cert("x509/ocsp/digicert-ecdsa-int.pem");
1✔
411
         auto trust_root = load_test_X509_cert("x509/ocsp/digicert-root.pem");
1✔
412

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

415
         Botan::Certificate_Store_In_Memory certstore;
1✔
416
         certstore.add_certificate(trust_root);
1✔
417

418
         const auto ocsp_timeout = std::chrono::milliseconds(3000);
1✔
419
         const auto now = std::chrono::system_clock::now();
1✔
420
         auto ocsp_status = Botan::PKIX::check_ocsp_online(
1✔
421
            cert_path, {&certstore}, now, ocsp_timeout, Botan::Path_Validation_Restrictions());
2✔
422

423
         if(result.test_sz_eq("Expected size of ocsp_status", ocsp_status.size(), 1)) {
1✔
424
            if(result.test_sz_eq("Expected size of ocsp_status[0]", ocsp_status[0].size(), 1)) {
1✔
425
               const bool status_good = ocsp_status[0].contains(Botan::Certificate_Status_Code::OCSP_RESPONSE_GOOD);
1✔
426
               const bool server_not_found =
1✔
427
                  ocsp_status[0].contains(Botan::Certificate_Status_Code::OCSP_SERVER_NOT_AVAILABLE);
1✔
428
               result.test_is_true("Expected status", status_good || server_not_found);
1✔
429
            }
430
         }
431

432
         return result;
1✔
433
      }
2✔
434
   #endif
435

436
      static Test::Result test_response_verification_with_additionally_trusted_responder() {
1✔
437
         Test::Result result("OCSP response with user-defined (additional) responder certificate");
1✔
438

439
         // OCSP response is signed by 3rd party responder certificate that is
440
         // not included in the OCSP response itself
441
         // See `src/scripts/randombit_ocsp_forger.sh` for a helper script to recreate those.
442
         auto ocsp = load_test_OCSP_resp("x509/ocsp/randombit_ocsp_forged_valid_nocerts.der");
1✔
443
         auto responder = load_test_X509_cert("x509/ocsp/randombit_ocsp_forged_responder.pem");
1✔
444
         auto ca = load_test_X509_cert("x509/ocsp/letsencrypt.pem");
1✔
445

446
         Botan::Certificate_Store_In_Memory trusted_responders;
1✔
447

448
         // without providing the 3rd party responder certificate no issuer will be found
449
         result.test_opt_is_null("cannot find signing certificate without trusted responders",
1✔
450
                                 ocsp.find_signing_certificate(ca));
1✔
451
         result.test_opt_is_null("cannot find signing certificate without additional help",
1✔
452
                                 ocsp.find_signing_certificate(ca, &trusted_responders));
1✔
453

454
         // add the 3rd party responder certificate to the list of trusted OCSP responder certs
455
         // to find the issuer certificate of this response
456
         trusted_responders.add_certificate(responder);
1✔
457
         test_arb_eq(result,
2✔
458
                     "the responder certificate is returned when it is trusted",
459
                     ocsp.find_signing_certificate(ca, &trusted_responders),
1✔
460
                     std::optional(responder));
1✔
461

462
         result.test_enum_eq("the responder's signature checks out",
1✔
463
                             ocsp.verify_signature(responder),
1✔
464
                             Botan::Certificate_Status_Code::OCSP_SIGNATURE_OK);
465

466
         return result;
1✔
467
      }
1✔
468

469
      static Test::Result test_forged_ocsp_signature_is_rejected() {
1✔
470
         Test::Result result("OCSP response with forged signature is rejected by path validation");
1✔
471

472
         auto ee = load_test_X509_cert("x509/ocsp/randombit.pem");
1✔
473
         auto ca = load_test_X509_cert("x509/ocsp/letsencrypt.pem");
1✔
474
         auto trust_root = load_test_X509_cert("x509/ocsp/geotrust.pem");
1✔
475

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

478
         Botan::Certificate_Store_In_Memory certstore;
1✔
479
         certstore.add_certificate(trust_root);
1✔
480

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

483
         // Verify the unmodified response is accepted
484
         {
1✔
485
            auto ocsp = load_test_OCSP_resp("x509/ocsp/randombit_ocsp.der");
1✔
486
            const auto ocsp_status = Botan::PKIX::check_ocsp(
2✔
487
               cert_path, {ocsp}, {&certstore}, valid_time, Botan::Path_Validation_Restrictions());
4✔
488

489
            if(result.test_sz_eq("Legitimate: expected result count", ocsp_status.size(), 2) &&
2✔
490
               result.test_sz_eq("Legitimate: expected status count", ocsp_status[0].size(), 1)) {
1✔
491
               result.test_is_true("Legitimate response is accepted",
2✔
492
                                   ocsp_status[0].contains(Botan::Certificate_Status_Code::OCSP_RESPONSE_GOOD));
2✔
493
            }
494
         }
1✔
495

496
         // Tamper with the signature and verify check_ocsp rejects it
497
         {
1✔
498
            auto ocsp_bytes = Test::read_binary_data_file("x509/ocsp/randombit_ocsp.der");
1✔
499
            ocsp_bytes.back() ^= 0x01;
1✔
500
            Botan::OCSP::Response forged_ocsp(ocsp_bytes.data(), ocsp_bytes.size());
1✔
501

502
            const auto ocsp_status = Botan::PKIX::check_ocsp(
2✔
503
               cert_path, {forged_ocsp}, {&certstore}, valid_time, Botan::Path_Validation_Restrictions());
4✔
504

505
            if(result.test_sz_eq("Forged: expected result count", ocsp_status.size(), 2) &&
2✔
506
               result.test_sz_eq("Forged: expected status count", ocsp_status[0].size(), 1)) {
1✔
507
               result.test_is_true("Forged signature is rejected",
2✔
508
                                   ocsp_status[0].contains(Botan::Certificate_Status_Code::OCSP_SIGNATURE_ERROR));
2✔
509
            }
510
         }
1✔
511

512
         return result;
1✔
513
      }
4✔
514

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

518
         auto ee = load_test_X509_cert("x509/ocsp/mychain_ee.pem");
1✔
519
         auto ca = load_test_X509_cert("x509/ocsp/mychain_int.pem");
1✔
520
         auto trust_root = load_test_X509_cert("x509/ocsp/mychain_root.pem");
1✔
521

522
         auto ocsp_for_ee = load_test_OCSP_resp("x509/ocsp/mychain_ocsp_for_ee.der");
1✔
523
         auto ocsp_for_int = load_test_OCSP_resp("x509/ocsp/mychain_ocsp_for_int_self_signed.der");
1✔
524

525
         Botan::Certificate_Store_In_Memory certstore;
1✔
526
         certstore.add_certificate(trust_root);
1✔
527

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

532
         // Here the intermediate has a stapled OCSP but the leaf does not
533
         {
1✔
534
            const std::vector<std::optional<Botan::OCSP::Response>> staples = {std::nullopt, ocsp_for_int};
3✔
535
            const auto ocsp_status =
1✔
536
               Botan::PKIX::check_ocsp(cert_path, staples, {&certstore}, valid_time, restrictions);
1✔
537

538
            result.test_sz_eq("missing-leaf: ocsp_status sized to non-root certs", ocsp_status.size(), 2);
1✔
539
            if(ocsp_status.size() == 2) {
1✔
540
               result.test_is_true("missing-leaf: leaf slot is empty", ocsp_status[0].empty());
1✔
541
               result.test_is_false("missing-leaf: intermediate slot is filled", ocsp_status[1].empty());
1✔
542
            }
543
         }
1✔
544

545
         // Here the leaf has a stapled OCSP but the intermediate does not
546
         {
1✔
547
            const std::vector<std::optional<Botan::OCSP::Response>> staples = {ocsp_for_ee, std::nullopt};
3✔
548
            const auto ocsp_status =
1✔
549
               Botan::PKIX::check_ocsp(cert_path, staples, {&certstore}, valid_time, restrictions);
1✔
550

551
            result.test_sz_eq("missing-intermediate: ocsp_status sized to non-root certs", ocsp_status.size(), 2);
1✔
552
            if(ocsp_status.size() == 2) {
1✔
553
               result.test_is_false("missing-intermediate: leaf slot is filled", ocsp_status[0].empty());
1✔
554
               result.test_is_true("missing-intermediate: intermediate slot is empty", ocsp_status[1].empty());
1✔
555
            }
556
         }
1✔
557

558
         return result;
1✔
559
      }
4✔
560

561
      static Test::Result test_responder_cert_with_nocheck_extension() {
1✔
562
         Test::Result result("BDr's OCSP response contains certificate featuring NoCheck extension");
1✔
563

564
         auto ocsp = load_test_OCSP_resp("x509/ocsp/bdr-ocsp-resp.der");
1✔
565
         const bool contains_cert_with_nocheck =
1✔
566
            std::find_if(ocsp.certificates().cbegin(), ocsp.certificates().cend(), [](const auto& cert) {
1✔
567
               return cert.v3_extensions().extension_set(Botan::OID::from_string("PKIX.OCSP.NoCheck"));
1✔
568
            }) != ocsp.certificates().end();
1✔
569

570
         result.test_is_true("Contains NoCheck extension", contains_cert_with_nocheck);
1✔
571

572
         return result;
1✔
573
      }
1✔
574

575
   public:
576
      std::vector<Test::Result> run() override {
1✔
577
         std::vector<Test::Result> results;
1✔
578

579
         results.push_back(test_request_encoding());
2✔
580
         results.push_back(test_response_parsing());
2✔
581
         results.push_back(test_response_with_bykey_responder_id());
2✔
582
         results.push_back(test_response_certificate_access());
2✔
583
         results.push_back(test_response_find_signing_certificate());
2✔
584
         results.push_back(test_response_verification_with_next_update_without_max_age());
2✔
585
         results.push_back(test_response_verification_with_next_update_with_max_age());
2✔
586
         results.push_back(test_response_verification_without_next_update_with_max_age());
2✔
587
         results.push_back(test_response_verification_without_next_update_without_max_age());
2✔
588
         results.push_back(test_response_verification_softfail());
2✔
589
         results.push_back(test_response_verification_with_additionally_trusted_responder());
2✔
590
         results.push_back(test_forged_ocsp_signature_is_rejected());
2✔
591
         results.push_back(test_partial_stapling_preserves_per_slot_gap());
2✔
592
         results.push_back(test_responder_cert_with_nocheck_extension());
2✔
593

594
   #if defined(BOTAN_HAS_ONLINE_REVOCATION_CHECKS)
595
         if(Test::options().run_online_tests()) {
1✔
596
            results.push_back(test_online_request());
2✔
597
         }
598
   #endif
599

600
         return results;
1✔
601
      }
×
602
};
603

604
BOTAN_REGISTER_TEST("x509", "ocsp", OCSP_Tests);
605

606
#endif
607

608
}  // namespace
609

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