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

randombit / botan / 29673219783

18 Jul 2026 10:42PM UTC coverage: 89.416% (+0.009%) from 89.407%
29673219783

push

github

randombit
Fix EC_Scalar_Data_BN::square_self

It computed the square then failed to update the stored value

114352 of 127887 relevant lines covered (89.42%)

10766049.99 hits per line

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

96.59
/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/ber_dec.h>
13
   #include <botan/certstor.h>
14
   #include <botan/der_enc.h>
15
   #include <botan/hex.h>
16
   #include <botan/ocsp.h>
17
   #include <botan/x509path.h>
18
   #include <botan/internal/calendar.h>
19
#endif
20

21
namespace Botan_Tests {
22

23
namespace {
24

25
#if defined(BOTAN_HAS_OCSP) && defined(BOTAN_HAS_RSA) && defined(BOTAN_HAS_EMSA_PKCS1) && \
26
   defined(BOTAN_TARGET_OS_HAS_FILESYSTEM)
27

28
class OCSP_Tests final : public Test {
1✔
29
   private:
30
      static Botan::X509_Certificate load_test_X509_cert(const std::string& path) {
35✔
31
         return Botan::X509_Certificate(Test::data_file(path));
70✔
32
      }
33

34
      static Botan::OCSP::Response load_test_OCSP_resp(const std::string& path) {
13✔
35
         return Botan::OCSP::Response(Test::read_binary_data_file(path));
26✔
36
      }
37

38
      static Test::Result test_certid_serial_sign() {
1✔
39
         Test::Result result("OCSP CertID serial matching respects sign");
1✔
40

41
         const std::string base = "x509/serial_numbers/";
1✔
42
         const auto ca = load_test_X509_cert(base + "ca.pem");
1✔
43
         const auto pos = load_test_X509_cert(base + "pos255.pem");
1✔
44
         const auto neg = load_test_X509_cert(base + "neg255.pem");
1✔
45

46
         const Botan::OCSP::CertID neg_id(ca, neg.serial());
1✔
47
         result.test_is_true("CertID matches its own cert", neg_id.is_id_for(ca, neg));
1✔
48
         result.test_is_false("CertID does not match same-magnitude positive serial", neg_id.is_id_for(ca, pos));
1✔
49

50
         const Botan::OCSP::CertID pos_id(ca, pos.serial());
1✔
51
         result.test_is_true("positive CertID matches its own cert", pos_id.is_id_for(ca, pos));
1✔
52
         result.test_is_false("positive CertID does not match negative serial", pos_id.is_id_for(ca, neg));
1✔
53

54
         // The sign survives an encode/decode round trip
55
         std::vector<uint8_t> der;
1✔
56
         Botan::DER_Encoder enc(der);
1✔
57
         neg_id.encode_into(enc);
1✔
58
         Botan::OCSP::CertID neg_id_rt;
1✔
59
         Botan::BER_Decoder dec(der);
1✔
60
         neg_id_rt.decode_from(dec);
1✔
61
         result.test_is_true("round-tripped CertID matches its own cert", neg_id_rt.is_id_for(ca, neg));
1✔
62
         result.test_is_false("round-tripped CertID does not match positive serial", neg_id_rt.is_id_for(ca, pos));
1✔
63

64
         return result;
2✔
65
      }
1✔
66

67
      static Test::Result test_response_parsing() {
1✔
68
         Test::Result result("OCSP response parsing");
1✔
69

70
         // Simple parsing tests
71
         const std::vector<std::string> ocsp_input_paths = {
1✔
72
            "x509/ocsp/resp1.der", "x509/ocsp/resp2.der", "x509/ocsp/resp3.der"};
1✔
73

74
         for(const std::string& ocsp_input_path : ocsp_input_paths) {
4✔
75
            try {
3✔
76
               const Botan::OCSP::Response resp(Test::read_binary_data_file(ocsp_input_path));
6✔
77
               result.test_enum_eq(
6✔
78
                  "parsing was successful", resp.status(), Botan::OCSP::Response_Status_Code::Successful);
3✔
79
               result.test_success("Parsed input " + ocsp_input_path);
3✔
80
            } catch(Botan::Exception& e) {
3✔
81
               result.test_failure("Parsing failed", e.what());
×
82
            }
×
83
         }
84

85
         try {
1✔
86
            // Contrary to RFC 6960 this response includes a responseBytes with a non-successful status code
87
            const Botan::OCSP::Response resp(
1✔
88
               Test::read_binary_data_file("x509/ocsp/patrickschmidt_ocsp_try_later_wrong_sig.der"));
3✔
89
            result.test_failure("Accepted invalid encoding OCSP response");
×
90
            result.test_enum_eq(
×
91
               "parsing exposes correct status code", resp.status(), Botan::OCSP::Response_Status_Code::Try_Later);
×
92
         } catch(Botan::Exception&) {
1✔
93
            result.test_success("Rejected invalid encoding OCSP response");
1✔
94
         }
1✔
95

96
         // OCSPResponse SEQUENCE { ENUMERATED 0 }: successful status with no
97
         // responseBytes. RFC 6960 4.2.1 requires responseBytes when successful.
98
         const std::vector<uint8_t> successful_no_response_bytes = {0x30, 0x03, 0x0A, 0x01, 0x00};
1✔
99
         result.test_throws<Botan::Decoding_Error>("Successful status without responseBytes is rejected", [&] {
1✔
100
            const Botan::OCSP::Response resp(successful_no_response_bytes);
1✔
101
         });
×
102

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

108
         // SEQUENCE { ENUMERATED 3 }: bare tryLater with no trailer parses fine.
109
         const std::vector<uint8_t> try_later_no_trailer = {0x30, 0x03, 0x0A, 0x01, 0x03};
1✔
110
         result.test_no_throw("Bare non-successful status parses", [&] {
1✔
111
            const Botan::OCSP::Response r(try_later_no_trailer);
1✔
112
            result.test_enum_eq("parsed as Try_Later", r.status(), Botan::OCSP::Response_Status_Code::Try_Later);
1✔
113
         });
1✔
114

115
         return result;
1✔
116
      }
1✔
117

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

125
         const Botan::OCSP::Response resp(Test::read_binary_data_file("x509/ocsp/byKey_responderID.der"));
2✔
126
         result.test_enum_eq(
2✔
127
            "Successful response status", resp.status(), Botan::OCSP::Response_Status_Code::Successful);
1✔
128

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

131
         result.test_is_true("byKey response has empty signer_name", resp.signer_name().empty());
1✔
132
         result.test_sz_eq("byKey hash is 20 bytes (SHA-1)", resp.signer_key_hash().size(), 20);
1✔
133
         result.test_bin_eq("byKey hash matches responder pubkey SHA-1",
1✔
134
                            resp.signer_key_hash(),
135
                            responder.subject_public_key_bitstring_sha1());
136

137
         test_arb_eq(
2✔
138
            result, "Responder is found via byKey", resp.find_signing_certificate(responder), std::optional(responder));
2✔
139

140
         return result;
1✔
141
      }
1✔
142

143
      static Test::Result test_response_certificate_access() {
1✔
144
         Test::Result result("OCSP response certificate access");
1✔
145

146
         try {
1✔
147
            const Botan::OCSP::Response resp1(Test::read_binary_data_file("x509/ocsp/resp1.der"));
2✔
148
            const auto& certs1 = resp1.certificates();
1✔
149
            if(result.test_sz_eq("Expected count of certificates", certs1.size(), 1)) {
1✔
150
               const auto& cert = certs1.front();
1✔
151
               const Botan::X509_DN expected_dn(
1✔
152
                  {std::make_pair("X520.CommonName", "Symantec Class 3 EV SSL CA - G3 OCSP Responder")});
1✔
153
               const bool matches = cert.subject_dn() == expected_dn;
1✔
154
               result.test_is_true("CN matches expected", matches);
1✔
155
            }
1✔
156

157
            const Botan::OCSP::Response resp2(Test::read_binary_data_file("x509/ocsp/resp2.der"));
2✔
158
            const auto& certs2 = resp2.certificates();
1✔
159
            result.test_sz_eq("Expect no certificates", certs2.size(), 0);
1✔
160
         } catch(Botan::Exception& e) {
1✔
161
            result.test_failure("Parsing failed", e.what());
×
162
         }
×
163

164
         return result;
1✔
165
      }
×
166

167
      static Test::Result test_revoked_response_decoding() {
1✔
168
         Test::Result result("OCSP revoked response decoding");
1✔
169

170
         const auto resp = load_test_OCSP_resp("x509/ocsp/randombit_ocsp_forged_revoked.der");
1✔
171
         result.test_enum_eq("response parses", resp.status(), Botan::OCSP::Response_Status_Code::Successful);
1✔
172

173
         if(result.test_sz_eq("one SingleResponse", resp.responses().size(), 1)) {
1✔
174
            const auto& sr = resp.responses()[0];
1✔
175

176
            const auto ee = load_test_X509_cert("x509/ocsp/randombit.pem");
1✔
177
            const auto ca = load_test_X509_cert("x509/ocsp/letsencrypt.pem");
1✔
178
            result.test_is_true("certid matches the subject cert", sr.certid().is_id_for(ca, ee));
1✔
179

180
            result.test_sz_eq("revoked cert status", sr.cert_status(), 1);
1✔
181
            if(result.test_is_true("revocation time is set", sr.revocation_time().has_value())) {
1✔
182
               result.test_str_eq("revocation time", sr.revocation_time()->to_string(), "20161118120000Z");
1✔
183
            }
184
            result.test_is_false("no revocation reason was provided", sr.revocation_reason().has_value());
1✔
185
            result.test_str_eq("thisUpdate", sr.this_update().to_string(), "20161118110000Z");
1✔
186
            result.test_str_eq("nextUpdate", sr.next_update().to_string(), "20161123110000Z");
1✔
187
         }
1✔
188

189
         return result;
1✔
190
      }
1✔
191

192
      static Test::Result test_single_response_encoding() {
1✔
193
         Test::Result result("OCSP SingleResponse encoding");
1✔
194

195
         // The CertID, times, and revoked encoding below appear verbatim in
196
         // x509/ocsp/randombit_ocsp_forged_revoked.der, which was produced by OpenSSL
197
         const std::string certid_hex =
1✔
198
            "304B300906052B0E03021A050004147EE66AE7729AB3FCF8A220646C16A12D6071085D"
199
            "0414A84A6A63047DDDBAE6D139B7A64565EFF3A8ECA1021203E89ED07A424B72A35FAD167F48A4F25AD2";
1✔
200

201
         const std::string this_update_hex = "180F32303136313131383131303030305A";
1✔
202
         const std::string next_update_hex = "A011180F32303136313132333131303030305A";
1✔
203
         const std::string revocation_time_hex = "180F32303136313131383132303030305A";
1✔
204

205
         Botan::OCSP::CertID certid;
1✔
206
         Botan::BER_Decoder(Botan::hex_decode(certid_hex)).decode(certid);
1✔
207

208
         const Botan::X509_Time this_update("20161118110000Z", Botan::ASN1_Type::GeneralizedTime);
1✔
209
         const Botan::X509_Time next_update("20161123110000Z", Botan::ASN1_Type::GeneralizedTime);
1✔
210
         const Botan::X509_Time revocation_time("20161118120000Z", Botan::ASN1_Type::GeneralizedTime);
1✔
211

212
         auto decode_single_response = [](const std::string& hex) {
8✔
213
            Botan::OCSP::SingleResponse sr;
7✔
214
            Botan::BER_Decoder(Botan::hex_decode(hex)).decode(sr);
13✔
215
            return sr;
4✔
216
         };
3✔
217

218
         {
1✔
219
            const auto sr = Botan::OCSP::SingleResponse::good(certid, this_update, next_update);
1✔
220
            const std::string expected = "3073" + certid_hex + "8000" + this_update_hex + next_update_hex;
4✔
221
            result.test_str_eq("good encoding", Botan::hex_encode(sr.BER_encode()), expected);
1✔
222

223
            const auto decoded = decode_single_response(expected);
1✔
224
            result.test_sz_eq("good cert status", decoded.cert_status(), 0);
1✔
225
            result.test_str_eq("good thisUpdate", decoded.this_update().to_string(), "20161118110000Z");
1✔
226
            result.test_str_eq("good nextUpdate", decoded.next_update().to_string(), "20161123110000Z");
1✔
227
            result.test_is_false("good has no revocation time", decoded.revocation_time().has_value());
1✔
228
            result.test_is_false("good has no revocation reason", decoded.revocation_reason().has_value());
1✔
229
         }
1✔
230

231
         {
1✔
232
            const auto sr = Botan::OCSP::SingleResponse::unknown(certid, this_update, Botan::X509_Time());
1✔
233
            const std::string expected = "3060" + certid_hex + "8200" + this_update_hex;
3✔
234
            result.test_str_eq("unknown encoding omits unset nextUpdate", Botan::hex_encode(sr.BER_encode()), expected);
1✔
235

236
            const auto decoded = decode_single_response(expected);
1✔
237
            result.test_sz_eq("unknown cert status", decoded.cert_status(), 2);
1✔
238
            result.test_is_false("unknown nextUpdate is unset", decoded.next_update().time_is_set());
1✔
239
         }
1✔
240

241
         const std::string revoked_no_reason_hex =
1✔
242
            "308184" + certid_hex + "A111" + revocation_time_hex + this_update_hex + next_update_hex;
5✔
243

244
         {
1✔
245
            const auto sr =
1✔
246
               Botan::OCSP::SingleResponse::revoked(certid, revocation_time, std::nullopt, this_update, next_update);
1✔
247
            result.test_str_eq("revoked encoding", Botan::hex_encode(sr.BER_encode()), revoked_no_reason_hex);
1✔
248

249
            const auto ocsp_bits = Test::read_binary_data_file("x509/ocsp/randombit_ocsp_forged_revoked.der");
1✔
250
            result.test_is_true("revoked encoding matches the OpenSSL-produced SingleResponse",
1✔
251
                                Botan::hex_encode(ocsp_bits).find(revoked_no_reason_hex) != std::string::npos);
1✔
252

253
            const auto decoded = decode_single_response(revoked_no_reason_hex);
1✔
254
            result.test_sz_eq("revoked cert status", decoded.cert_status(), 1);
1✔
255
            if(result.test_is_true("revoked has revocation time", decoded.revocation_time().has_value())) {
1✔
256
               result.test_str_eq("revocation time", decoded.revocation_time()->to_string(), "20161118120000Z");
1✔
257
            }
258
            result.test_is_false("revoked has no revocation reason", decoded.revocation_reason().has_value());
1✔
259
         }
1✔
260

261
         {
1✔
262
            const auto sr = Botan::OCSP::SingleResponse::revoked(
1✔
263
               certid, revocation_time, Botan::CRL_Code::KeyCompromise, this_update, next_update);
1✔
264
            const std::string expected =
1✔
265
               "308189" + certid_hex + "A116" + revocation_time_hex + "A0030A0101" + this_update_hex + next_update_hex;
6✔
266
            result.test_str_eq("revoked encoding with reason", Botan::hex_encode(sr.BER_encode()), expected);
1✔
267

268
            const auto decoded = decode_single_response(expected);
1✔
269
            result.test_sz_eq("revoked cert status", decoded.cert_status(), 1);
1✔
270
            if(result.test_is_true("revoked has revocation reason", decoded.revocation_reason().has_value())) {
1✔
271
               result.test_enum_eq(
1✔
272
                  "reason is keyCompromise", *decoded.revocation_reason(), Botan::CRL_Code::KeyCompromise);
1✔
273
            }
274
         }
1✔
275

276
         {
1✔
277
            // CRLReason unspecified (0) is not encoded at all
278
            const auto sr = Botan::OCSP::SingleResponse::revoked(
1✔
279
               certid, revocation_time, Botan::CRL_Code::Unspecified, this_update, next_update);
1✔
280
            result.test_str_eq(
2✔
281
               "unspecified reason is omitted", Botan::hex_encode(sr.BER_encode()), revoked_no_reason_hex);
2✔
282
         }
×
283

284
         const Botan::X509_Time utc_time("161118110000Z", Botan::ASN1_Type::UtcTime);
1✔
285

286
         result.test_throws<Botan::Invalid_Argument>("UTCTime thisUpdate is rejected", [&] {
1✔
287
            const auto sr = Botan::OCSP::SingleResponse::good(certid, utc_time, next_update);
1✔
288
         });
×
289

290
         result.test_throws<Botan::Invalid_Argument>("UTCTime revocationTime is rejected", [&] {
1✔
291
            const auto sr =
1✔
292
               Botan::OCSP::SingleResponse::revoked(certid, utc_time, std::nullopt, this_update, next_update);
1✔
293
         });
×
294

295
         result.test_throws<Botan::Decoding_Error>("Unknown CRLReason enumeration is rejected", [&] {
1✔
296
            decode_single_response("308189" + certid_hex + "A116" + revocation_time_hex + "A0030A0107" +
8✔
297
                                   this_update_hex + next_update_hex);
3✔
298
         });
×
299

300
         result.test_throws<Botan::Decoding_Error>("UTCTime revocationTime is rejected when decoding", [&] {
1✔
301
            decode_single_response("308182" + certid_hex + "A10F170D3136313131383132303030305A" + this_update_hex +
7✔
302
                                   next_update_hex);
303
         });
×
304

305
         result.test_throws<Botan::Decoding_Error>("good status with contents is rejected", [&] {
1✔
306
            decode_single_response("3075" + certid_hex + "80020500" + this_update_hex + next_update_hex);
7✔
307
         });
×
308

309
         return result;
1✔
310
      }
1✔
311

312
      static Test::Result test_request_encoding() {
1✔
313
         Test::Result result("OCSP request encoding");
1✔
314

315
         const Botan::X509_Certificate end_entity(Test::data_file("x509/ocsp/gmail.pem"));
2✔
316
         const Botan::X509_Certificate issuer(Test::data_file("x509/ocsp/google_g2.pem"));
2✔
317

318
         try {
1✔
319
            const Botan::OCSP::Request bogus(end_entity, issuer);
1✔
320
            result.test_failure("Bad arguments (swapped end entity, issuer) accepted");
×
321
         } catch(Botan::Invalid_Argument&) {
1✔
322
            result.test_success("Bad arguments rejected");
1✔
323
         }
1✔
324

325
         const std::string expected_request =
1✔
326
            "ME4wTKADAgEAMEUwQzBBMAkGBSsOAwIaBQAEFPLgavmFih2NcJtJGSN6qbUaKH5kBBRK3QYWG7z2aLV29YG2u2IaulqBLwIIQkg+DF+RYMY=";
1✔
327

328
         const Botan::OCSP::Request req1(issuer, end_entity);
1✔
329
         result.test_str_eq("Encoded OCSP request", req1.base64_encode(), expected_request);
1✔
330

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

334
         return result;
2✔
335
      }
2✔
336

337
      static Test::Result test_response_find_signing_certificate() {
1✔
338
         Test::Result result("OCSP response finding signature certificates");
1✔
339

340
         // OCSP response is signed by the issuing CA itself
341
         auto randombit_ocsp = load_test_OCSP_resp("x509/ocsp/randombit_ocsp.der");
1✔
342
         auto randombit_ca = load_test_X509_cert("x509/ocsp/letsencrypt.pem");
1✔
343

344
         // OCSP response is signed by an authorized responder certificate
345
         // issued by the issuing CA and embedded in the response
346
         auto bdr_ocsp = load_test_OCSP_resp("x509/ocsp/bdr-ocsp-resp.der");
1✔
347
         auto bdr_responder = load_test_X509_cert("x509/ocsp/bdr-ocsp-responder.pem");
1✔
348
         auto bdr_ca = load_test_X509_cert("x509/ocsp/bdr-int.pem");
1✔
349

350
         // The response in bdr_ocsp contains two certificates
351
         if(result.test_sz_eq("both certificates found", bdr_ocsp.certificates().size(), 2)) {
1✔
352
            result.test_str_eq("first cert in response",
2✔
353
                               bdr_ocsp.certificates()[0].subject_dn().get_first_attribute("CN"),
1✔
354
                               "D-TRUST OCSP 4 2-2 EV 2016");
355
            result.test_str_eq("second cert in response",
2✔
356
                               bdr_ocsp.certificates()[1].subject_dn().get_first_attribute("CN"),
2✔
357
                               "D-TRUST CA 2-2 EV 2016");
358
         }
359

360
         // Dummy OCSP response is not signed at all
361
         auto dummy_ocsp = Botan::OCSP::Response(Botan::Certificate_Status_Code::OCSP_SERVER_NOT_AVAILABLE);
1✔
362

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

369
         result.test_opt_is_null("Dummy has no signing certificate",
1✔
370
                                 dummy_ocsp.find_signing_certificate(Botan::X509_Certificate()));
2✔
371

372
         test_arb_eq(result,
2✔
373
                     "CA is returned as signing certificate",
374
                     randombit_ocsp.find_signing_certificate(randombit_ca),
1✔
375
                     std::optional(randombit_ca));
1✔
376
         result.test_opt_is_null("No signer certificate is returned when signer couldn't be determined",
1✔
377
                                 randombit_ocsp.find_signing_certificate(bdr_ca));
1✔
378

379
         test_arb_eq(result,
2✔
380
                     "Delegated responder certificate is returned for further validation",
381
                     bdr_ocsp.find_signing_certificate(bdr_ca),
1✔
382
                     std::optional(bdr_responder));
1✔
383

384
         result.test_opt_is_null(
1✔
385
            "Delegated responder without stapled certs does not find signer without user-provided certs",
386
            randombit_alt_resp_ocsp.find_signing_certificate(randombit_ca));
1✔
387

388
         auto trusted_responders = std::make_unique<Botan::Certificate_Store_In_Memory>(randombit_alt_resp_cert);
1✔
389
         test_arb_eq(result,
2✔
390
                     "Delegated responder returns user-provided cert",
391
                     randombit_alt_resp_ocsp.find_signing_certificate(randombit_ca, trusted_responders.get()),
2✔
392
                     std::optional(randombit_alt_resp_cert));
1✔
393

394
         return result;
2✔
395
      }
1✔
396

397
      static Test::Result test_response_verification_with_next_update_without_max_age() {
1✔
398
         Test::Result result("OCSP request check with next_update w/o max_age");
1✔
399

400
         auto ee = load_test_X509_cert("x509/ocsp/randombit.pem");
1✔
401
         auto ca = load_test_X509_cert("x509/ocsp/letsencrypt.pem");
1✔
402
         auto trust_root = load_test_X509_cert("x509/ocsp/geotrust.pem");
1✔
403

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

406
         auto ocsp = load_test_OCSP_resp("x509/ocsp/randombit_ocsp.der");
1✔
407

408
         Botan::Certificate_Store_In_Memory certstore;
1✔
409
         certstore.add_certificate(trust_root);
1✔
410

411
         auto check_ocsp = [&](const std::chrono::system_clock::time_point valid_time,
5✔
412
                               const Botan::Certificate_Status_Code expected) {
413
            const auto ocsp_status = Botan::PKIX::check_ocsp(
12✔
414
               cert_path, {ocsp}, {&certstore}, valid_time, Botan::Path_Validation_Restrictions());
16✔
415

416
            return result.test_sz_eq("Expected size of ocsp_status", ocsp_status.size(), 2) &&
8✔
417
                   result.test_sz_eq("Expected size of ocsp_status[0]", ocsp_status[0].size(), 1) &&
4✔
418
                   result.test_is_true(std::string("Status: '") + Botan::to_string(expected) + "'",
28✔
419
                                       ocsp_status[0].contains(expected));
12✔
420
         };
8✔
421

422
         check_ocsp(Botan::calendar_point(2016, 11, 11, 12, 30, 0).to_std_timepoint(),
1✔
423
                    Botan::Certificate_Status_Code::OCSP_NOT_YET_VALID);
424
         check_ocsp(Botan::calendar_point(2016, 11, 18, 12, 30, 0).to_std_timepoint(),
1✔
425
                    Botan::Certificate_Status_Code::OCSP_RESPONSE_GOOD);
426
         check_ocsp(Botan::calendar_point(2016, 11, 20, 8, 30, 0).to_std_timepoint(),
1✔
427
                    Botan::Certificate_Status_Code::OCSP_RESPONSE_GOOD);
428
         check_ocsp(Botan::calendar_point(2016, 11, 28, 8, 30, 0).to_std_timepoint(),
1✔
429
                    Botan::Certificate_Status_Code::OCSP_HAS_EXPIRED);
430

431
         return result;
1✔
432
      }
2✔
433

434
      static Test::Result test_response_verification_with_next_update_with_max_age() {
1✔
435
         Test::Result result("OCSP request check with next_update with max_age");
1✔
436

437
         auto ee = load_test_X509_cert("x509/ocsp/randombit.pem");
1✔
438
         auto ca = load_test_X509_cert("x509/ocsp/letsencrypt.pem");
1✔
439
         auto trust_root = load_test_X509_cert("x509/ocsp/geotrust.pem");
1✔
440

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

443
         auto ocsp = load_test_OCSP_resp("x509/ocsp/randombit_ocsp.der");
1✔
444

445
         Botan::Certificate_Store_In_Memory certstore;
1✔
446
         certstore.add_certificate(trust_root);
1✔
447

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

451
         auto check_ocsp = [&](const std::chrono::system_clock::time_point valid_time,
5✔
452
                               const Botan::Certificate_Status_Code expected) {
453
            const Botan::Path_Validation_Restrictions pvr(false, 110, false, max_age);
4✔
454
            const auto ocsp_status = Botan::PKIX::check_ocsp(cert_path, {ocsp}, {&certstore}, valid_time, pvr);
12✔
455

456
            return result.test_sz_eq("Expected size of ocsp_status", ocsp_status.size(), 2) &&
8✔
457
                   result.test_sz_eq("Expected size of ocsp_status[0]", ocsp_status[0].size(), 1) &&
4✔
458
                   result.test_is_true(std::string("Status: '") + Botan::to_string(expected) + "'",
28✔
459
                                       ocsp_status[0].contains(expected));
12✔
460
         };
8✔
461

462
         check_ocsp(Botan::calendar_point(2016, 11, 11, 12, 30, 0).to_std_timepoint(),
1✔
463
                    Botan::Certificate_Status_Code::OCSP_NOT_YET_VALID);
464
         check_ocsp(Botan::calendar_point(2016, 11, 18, 12, 30, 0).to_std_timepoint(),
1✔
465
                    Botan::Certificate_Status_Code::OCSP_RESPONSE_GOOD);
466
         check_ocsp(Botan::calendar_point(2016, 11, 20, 8, 30, 0).to_std_timepoint(),
1✔
467
                    Botan::Certificate_Status_Code::OCSP_RESPONSE_GOOD);
468
         check_ocsp(Botan::calendar_point(2016, 11, 28, 8, 30, 0).to_std_timepoint(),
1✔
469
                    Botan::Certificate_Status_Code::OCSP_HAS_EXPIRED);
470

471
         return result;
1✔
472
      }
2✔
473

474
      static Test::Result test_response_verification_without_next_update_with_max_age() {
1✔
475
         Test::Result result("OCSP request check w/o next_update with max_age");
1✔
476

477
         auto ee = load_test_X509_cert("x509/ocsp/patrickschmidt.pem");
1✔
478
         auto ca = load_test_X509_cert("x509/ocsp/bdrive_encryption.pem");
1✔
479
         auto trust_root = load_test_X509_cert("x509/ocsp/bdrive_root.pem");
1✔
480

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

483
         auto ocsp = load_test_OCSP_resp("x509/ocsp/patrickschmidt_ocsp.der");
1✔
484

485
         Botan::Certificate_Store_In_Memory certstore;
1✔
486
         certstore.add_certificate(trust_root);
1✔
487

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

491
         auto check_ocsp = [&](const std::chrono::system_clock::time_point valid_time,
4✔
492
                               const Botan::Certificate_Status_Code expected) {
493
            const Botan::Path_Validation_Restrictions pvr(false, 110, false, max_age);
3✔
494
            const auto ocsp_status = Botan::PKIX::check_ocsp(cert_path, {ocsp}, {&certstore}, valid_time, pvr);
9✔
495

496
            result.test_sz_eq("Expected size of ocsp_status", ocsp_status.size(), 2);
3✔
497

498
            if(!ocsp_status.empty()) {
3✔
499
               result.test_sz_eq("Expected size of ocsp_status[0]", ocsp_status[0].size(), 1);
3✔
500

501
               result.test_is_true(std::string("Status: '") + Botan::to_string(expected) + "'",
15✔
502
                                   ocsp_status[0].contains(expected));
6✔
503
            }
504
         };
6✔
505

506
         check_ocsp(Botan::calendar_point(2019, 5, 28, 7, 0, 0).to_std_timepoint(),
1✔
507
                    Botan::Certificate_Status_Code::OCSP_NOT_YET_VALID);
508
         check_ocsp(Botan::calendar_point(2019, 5, 28, 7, 30, 0).to_std_timepoint(),
1✔
509
                    Botan::Certificate_Status_Code::OCSP_RESPONSE_GOOD);
510
         check_ocsp(Botan::calendar_point(2019, 5, 28, 8, 0, 0).to_std_timepoint(),
1✔
511
                    Botan::Certificate_Status_Code::OCSP_IS_TOO_OLD);
512

513
         return result;
1✔
514
      }
2✔
515

516
      static Test::Result test_response_verification_without_next_update_without_max_age() {
1✔
517
         Test::Result result("OCSP request check w/o next_update w/o max_age");
1✔
518

519
         auto ee = load_test_X509_cert("x509/ocsp/patrickschmidt.pem");
1✔
520
         auto ca = load_test_X509_cert("x509/ocsp/bdrive_encryption.pem");
1✔
521
         auto trust_root = load_test_X509_cert("x509/ocsp/bdrive_root.pem");
1✔
522

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

525
         auto ocsp = load_test_OCSP_resp("x509/ocsp/patrickschmidt_ocsp.der");
1✔
526

527
         Botan::Certificate_Store_In_Memory certstore;
1✔
528
         certstore.add_certificate(trust_root);
1✔
529

530
         auto check_ocsp = [&](const std::chrono::system_clock::time_point valid_time,
4✔
531
                               const Botan::Certificate_Status_Code expected) {
532
            const auto ocsp_status = Botan::PKIX::check_ocsp(
9✔
533
               cert_path, {ocsp}, {&certstore}, valid_time, Botan::Path_Validation_Restrictions());
12✔
534

535
            result.test_sz_eq("Expected size of ocsp_status", ocsp_status.size(), 2);
3✔
536

537
            if(!ocsp_status.empty()) {
3✔
538
               result.test_sz_eq("Expected size of ocsp_status[0]", ocsp_status[0].size(), 1);
3✔
539
               result.test_is_true(std::string("Status: '") + Botan::to_string(expected) + "'",
15✔
540
                                   ocsp_status[0].contains(expected));
6✔
541
            }
542
         };
6✔
543

544
         check_ocsp(Botan::calendar_point(2019, 5, 28, 7, 0, 0).to_std_timepoint(),
1✔
545
                    Botan::Certificate_Status_Code::OCSP_NOT_YET_VALID);
546
         check_ocsp(Botan::calendar_point(2019, 5, 28, 7, 30, 0).to_std_timepoint(),
1✔
547
                    Botan::Certificate_Status_Code::OCSP_RESPONSE_GOOD);
548
         check_ocsp(Botan::calendar_point(2019, 5, 28, 8, 0, 0).to_std_timepoint(),
1✔
549
                    Botan::Certificate_Status_Code::OCSP_RESPONSE_GOOD);
550

551
         return result;
1✔
552
      }
2✔
553

554
      static Test::Result test_response_verification_softfail() {
1✔
555
         Test::Result result("OCSP request softfail check");
1✔
556

557
         auto ee = load_test_X509_cert("x509/ocsp/randombit.pem");
1✔
558
         auto ca = load_test_X509_cert("x509/ocsp/letsencrypt.pem");
1✔
559
         auto trust_root = load_test_X509_cert("x509/ocsp/geotrust.pem");
1✔
560

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

563
         Botan::OCSP::Response ocsp(Botan::Certificate_Status_Code::OCSP_NO_REVOCATION_URL);
1✔
564

565
         Botan::Certificate_Store_In_Memory certstore;
1✔
566
         certstore.add_certificate(trust_root);
1✔
567

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

573
         if(result.test_sz_eq("Expected size of ocsp_status", ocsp_status.size(), 2)) {
1✔
574
            if(result.test_sz_eq("Expected size of ocsp_status[0]", ocsp_status[0].size(), 1)) {
1✔
575
               result.test_sz_gt(
2✔
576
                  "Status warning", ocsp_status[0].count(Botan::Certificate_Status_Code::OCSP_NO_REVOCATION_URL), 0);
1✔
577
            }
578
         }
579

580
         return result;
1✔
581
      }
3✔
582

583
   #if defined(BOTAN_HAS_ONLINE_REVOCATION_CHECKS)
584
      static Test::Result test_online_request() {
1✔
585
         Test::Result result("OCSP online check");
1✔
586

587
         auto cert = load_test_X509_cert("x509/ocsp/digicert-ecdsa-int.pem");
1✔
588
         auto trust_root = load_test_X509_cert("x509/ocsp/digicert-root.pem");
1✔
589

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

592
         Botan::Certificate_Store_In_Memory certstore;
1✔
593
         certstore.add_certificate(trust_root);
1✔
594

595
         const auto ocsp_timeout = std::chrono::milliseconds(3000);
1✔
596
         const auto now = std::chrono::system_clock::now();
1✔
597
         auto ocsp_status = Botan::PKIX::check_ocsp_online(
1✔
598
            cert_path, {&certstore}, now, ocsp_timeout, Botan::Path_Validation_Restrictions());
2✔
599

600
         if(result.test_sz_eq("Expected size of ocsp_status", ocsp_status.size(), 1)) {
1✔
601
            if(result.test_sz_eq("Expected size of ocsp_status[0]", ocsp_status[0].size(), 1)) {
1✔
602
               const bool status_good = ocsp_status[0].contains(Botan::Certificate_Status_Code::OCSP_RESPONSE_GOOD);
1✔
603
               const bool server_not_found =
1✔
604
                  ocsp_status[0].contains(Botan::Certificate_Status_Code::OCSP_SERVER_NOT_AVAILABLE);
1✔
605
               result.test_is_true("Expected status", status_good || server_not_found);
1✔
606
            }
607
         }
608

609
         return result;
1✔
610
      }
2✔
611
   #endif
612

613
      static Test::Result test_response_verification_with_additionally_trusted_responder() {
1✔
614
         Test::Result result("OCSP response with user-defined (additional) responder certificate");
1✔
615

616
         // OCSP response is signed by 3rd party responder certificate that is
617
         // not included in the OCSP response itself
618
         // See `src/scripts/randombit_ocsp_forger.sh` for a helper script to recreate those.
619
         auto ocsp = load_test_OCSP_resp("x509/ocsp/randombit_ocsp_forged_valid_nocerts.der");
1✔
620
         auto responder = load_test_X509_cert("x509/ocsp/randombit_ocsp_forged_responder.pem");
1✔
621
         auto ca = load_test_X509_cert("x509/ocsp/letsencrypt.pem");
1✔
622

623
         Botan::Certificate_Store_In_Memory trusted_responders;
1✔
624

625
         // without providing the 3rd party responder certificate no issuer will be found
626
         result.test_opt_is_null("cannot find signing certificate without trusted responders",
1✔
627
                                 ocsp.find_signing_certificate(ca));
1✔
628
         result.test_opt_is_null("cannot find signing certificate without additional help",
1✔
629
                                 ocsp.find_signing_certificate(ca, &trusted_responders));
1✔
630

631
         // add the 3rd party responder certificate to the list of trusted OCSP responder certs
632
         // to find the issuer certificate of this response
633
         trusted_responders.add_certificate(responder);
1✔
634
         test_arb_eq(result,
2✔
635
                     "the responder certificate is returned when it is trusted",
636
                     ocsp.find_signing_certificate(ca, &trusted_responders),
1✔
637
                     std::optional(responder));
1✔
638

639
         result.test_enum_eq("the responder's signature checks out",
1✔
640
                             ocsp.verify_signature(responder),
1✔
641
                             Botan::Certificate_Status_Code::OCSP_SIGNATURE_OK);
642

643
         return result;
1✔
644
      }
1✔
645

646
      static Test::Result test_forged_ocsp_signature_is_rejected() {
1✔
647
         Test::Result result("OCSP response with forged signature is rejected by path validation");
1✔
648

649
         auto ee = load_test_X509_cert("x509/ocsp/randombit.pem");
1✔
650
         auto ca = load_test_X509_cert("x509/ocsp/letsencrypt.pem");
1✔
651
         auto trust_root = load_test_X509_cert("x509/ocsp/geotrust.pem");
1✔
652

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

655
         Botan::Certificate_Store_In_Memory certstore;
1✔
656
         certstore.add_certificate(trust_root);
1✔
657

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

660
         // Verify the unmodified response is accepted
661
         {
1✔
662
            auto ocsp = load_test_OCSP_resp("x509/ocsp/randombit_ocsp.der");
1✔
663
            const auto ocsp_status = Botan::PKIX::check_ocsp(
3✔
664
               cert_path, {ocsp}, {&certstore}, valid_time, Botan::Path_Validation_Restrictions());
4✔
665

666
            if(result.test_sz_eq("Legitimate: expected result count", ocsp_status.size(), 2) &&
2✔
667
               result.test_sz_eq("Legitimate: expected status count", ocsp_status[0].size(), 1)) {
1✔
668
               result.test_is_true("Legitimate response is accepted",
2✔
669
                                   ocsp_status[0].contains(Botan::Certificate_Status_Code::OCSP_RESPONSE_GOOD));
2✔
670
            }
671
         }
1✔
672

673
         // Tamper with the signature and verify check_ocsp rejects it
674
         {
1✔
675
            auto ocsp_bytes = Test::read_binary_data_file("x509/ocsp/randombit_ocsp.der");
1✔
676
            ocsp_bytes.back() ^= 0x01;
1✔
677
            Botan::OCSP::Response forged_ocsp(ocsp_bytes.data(), ocsp_bytes.size());
1✔
678

679
            const auto ocsp_status = Botan::PKIX::check_ocsp(
3✔
680
               cert_path, {forged_ocsp}, {&certstore}, valid_time, Botan::Path_Validation_Restrictions());
4✔
681

682
            if(result.test_sz_eq("Forged: expected result count", ocsp_status.size(), 2) &&
2✔
683
               result.test_sz_eq("Forged: expected status count", ocsp_status[0].size(), 1)) {
1✔
684
               result.test_is_true("Forged signature is rejected",
2✔
685
                                   ocsp_status[0].contains(Botan::Certificate_Status_Code::OCSP_SIGNATURE_ERROR));
2✔
686
            }
687
         }
1✔
688

689
         return result;
1✔
690
      }
4✔
691

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

695
         auto ee = load_test_X509_cert("x509/ocsp/mychain_ee.pem");
1✔
696
         auto ca = load_test_X509_cert("x509/ocsp/mychain_int.pem");
1✔
697
         auto trust_root = load_test_X509_cert("x509/ocsp/mychain_root.pem");
1✔
698

699
         auto ocsp_for_ee = load_test_OCSP_resp("x509/ocsp/mychain_ocsp_for_ee.der");
1✔
700
         auto ocsp_for_int = load_test_OCSP_resp("x509/ocsp/mychain_ocsp_for_int_self_signed.der");
1✔
701

702
         Botan::Certificate_Store_In_Memory certstore;
1✔
703
         certstore.add_certificate(trust_root);
1✔
704

705
         const std::vector<Botan::X509_Certificate> cert_path = {ee, ca, trust_root};
4✔
706
         const auto valid_time = Botan::calendar_point(2022, 9, 22, 22, 30, 0).to_std_timepoint();
1✔
707
         const auto restrictions = Botan::Path_Validation_Restrictions();
1✔
708

709
         // Here the intermediate has a stapled OCSP but the leaf does not
710
         {
1✔
711
            const std::vector<std::optional<Botan::OCSP::Response>> staples = {std::nullopt, ocsp_for_int};
3✔
712
            const auto ocsp_status =
1✔
713
               Botan::PKIX::check_ocsp(cert_path, staples, {&certstore}, valid_time, restrictions);
1✔
714

715
            result.test_sz_eq("missing-leaf: ocsp_status sized to non-root certs", ocsp_status.size(), 2);
1✔
716
            if(ocsp_status.size() == 2) {
1✔
717
               result.test_is_true("missing-leaf: leaf slot is empty", ocsp_status[0].empty());
1✔
718
               result.test_is_false("missing-leaf: intermediate slot is filled", ocsp_status[1].empty());
1✔
719
            }
720
         }
1✔
721

722
         // Here the leaf has a stapled OCSP but the intermediate does not
723
         {
1✔
724
            const std::vector<std::optional<Botan::OCSP::Response>> staples = {ocsp_for_ee, std::nullopt};
3✔
725
            const auto ocsp_status =
1✔
726
               Botan::PKIX::check_ocsp(cert_path, staples, {&certstore}, valid_time, restrictions);
1✔
727

728
            result.test_sz_eq("missing-intermediate: ocsp_status sized to non-root certs", ocsp_status.size(), 2);
1✔
729
            if(ocsp_status.size() == 2) {
1✔
730
               result.test_is_false("missing-intermediate: leaf slot is filled", ocsp_status[0].empty());
1✔
731
               result.test_is_true("missing-intermediate: intermediate slot is empty", ocsp_status[1].empty());
1✔
732
            }
733
         }
1✔
734

735
         return result;
1✔
736
      }
4✔
737

738
      static Test::Result test_responder_cert_with_nocheck_extension() {
1✔
739
         Test::Result result("BDr's OCSP response contains certificate featuring NoCheck extension");
1✔
740

741
         auto ocsp = load_test_OCSP_resp("x509/ocsp/bdr-ocsp-resp.der");
1✔
742
         const bool contains_cert_with_nocheck =
1✔
743
            std::find_if(ocsp.certificates().cbegin(), ocsp.certificates().cend(), [](const auto& cert) {
1✔
744
               return cert.v3_extensions().extension_set(Botan::OID::from_string("PKIX.OCSP.NoCheck"));
1✔
745
            }) != ocsp.certificates().end();
1✔
746

747
         result.test_is_true("Contains NoCheck extension", contains_cert_with_nocheck);
1✔
748

749
         return result;
1✔
750
      }
1✔
751

752
   public:
753
      std::vector<Test::Result> run() override {
1✔
754
         std::vector<Test::Result> results;
1✔
755

756
         results.push_back(test_request_encoding());
2✔
757
         results.push_back(test_certid_serial_sign());
2✔
758
         results.push_back(test_response_parsing());
2✔
759
         results.push_back(test_revoked_response_decoding());
2✔
760
         results.push_back(test_single_response_encoding());
2✔
761
         results.push_back(test_response_with_bykey_responder_id());
2✔
762
         results.push_back(test_response_certificate_access());
2✔
763
         results.push_back(test_response_find_signing_certificate());
2✔
764
         results.push_back(test_response_verification_with_next_update_without_max_age());
2✔
765
         results.push_back(test_response_verification_with_next_update_with_max_age());
2✔
766
         results.push_back(test_response_verification_without_next_update_with_max_age());
2✔
767
         results.push_back(test_response_verification_without_next_update_without_max_age());
2✔
768
         results.push_back(test_response_verification_softfail());
2✔
769
         results.push_back(test_response_verification_with_additionally_trusted_responder());
2✔
770
         results.push_back(test_forged_ocsp_signature_is_rejected());
2✔
771
         results.push_back(test_partial_stapling_preserves_per_slot_gap());
2✔
772
         results.push_back(test_responder_cert_with_nocheck_extension());
2✔
773

774
   #if defined(BOTAN_HAS_ONLINE_REVOCATION_CHECKS)
775
         if(Test::options().run_online_tests()) {
1✔
776
            results.push_back(test_online_request());
2✔
777
         }
778
   #endif
779

780
         return results;
1✔
781
      }
×
782
};
783

784
BOTAN_REGISTER_TEST("x509", "ocsp", OCSP_Tests);
785

786
#endif
787

788
}  // namespace
789

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

© 2026 Coveralls, Inc