• 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

84.58
/src/lib/x509/ocsp.cpp
1
/*
2
* OCSP
3
* (C) 2012,2013 Jack Lloyd
4
*
5
* Botan is released under the Simplified BSD License (see license.txt)
6
*/
7

8
#include <botan/ocsp.h>
9

10
#include <botan/base64.h>
11
#include <botan/ber_dec.h>
12
#include <botan/certstor.h>
13
#include <botan/der_enc.h>
14
#include <botan/hash.h>
15
#include <botan/pubkey.h>
16
#include <botan/x509_ext.h>
17
#include <botan/x509path.h>
18

19
#if defined(BOTAN_HAS_HTTP_UTIL)
20
   #include <botan/internal/http_util.h>
21
#endif
22

23
namespace Botan::OCSP {
24

25
CertID::CertID(const X509_Certificate& issuer, const BigInt& subject_serial) : m_subject_serial(subject_serial) {
4✔
26
   /*
27
   In practice it seems some responders, including, notably,
28
   ocsp.verisign.com, will reject anything but SHA-1 here
29
   */
30
   auto hash = HashFunction::create_or_throw("SHA-1");
4✔
31

32
   m_hash_id = AlgorithmIdentifier(hash->name(), AlgorithmIdentifier::USE_NULL_PARAM);
4✔
33
   m_issuer_key_hash = unlock(hash->process(issuer.subject_public_key_bitstring()));
8✔
34
   m_issuer_dn_hash = unlock(hash->process(issuer.raw_subject_dn()));
12✔
35
}
4✔
36

37
bool CertID::is_id_for(const X509_Certificate& issuer, const X509_Certificate& subject) const {
38✔
38
   try {
38✔
39
      if(BigInt::from_bytes(subject.serial_number()) != m_subject_serial) {
76✔
40
         return false;
×
41
      }
42

43
      const std::string hash_algo = m_hash_id.oid().to_formatted_string();
38✔
44

45
      if(hash_algo != "SHA-1" && hash_algo != "SHA-256") {
38✔
46
         return false;
47
      }
48

49
      auto hash = HashFunction::create_or_throw(hash_algo);
38✔
50

51
      if(m_issuer_dn_hash != unlock(hash->process(subject.raw_issuer_dn()))) {
152✔
52
         return false;
53
      }
54

55
      if(m_issuer_key_hash != unlock(hash->process(issuer.subject_public_key_bitstring()))) {
152✔
56
         return false;
57
      }
58
   } catch(...) {
38✔
59
      return false;
×
60
   }
×
61

62
   return true;
38✔
63
}
64

65
void CertID::encode_into(DER_Encoder& to) const {
3✔
66
   to.start_sequence()
3✔
67
      .encode(m_hash_id)
3✔
68
      .encode(m_issuer_dn_hash, ASN1_Type::OctetString)
3✔
69
      .encode(m_issuer_key_hash, ASN1_Type::OctetString)
3✔
70
      .encode(m_subject_serial)
3✔
71
      .end_cons();
3✔
72
}
3✔
73

74
void CertID::decode_from(BER_Decoder& from) {
45✔
75
   /*
76
   * RFC 6960 Section 4.1.1
77
   *
78
   * CertID ::= SEQUENCE {
79
   *    hashAlgorithm       AlgorithmIdentifier,
80
   *    issuerNameHash      OCTET STRING,
81
   *    issuerKeyHash       OCTET STRING,
82
   *    serialNumber        CertificateSerialNumber }
83
   */
84
   from.start_sequence()
90✔
85
      .decode(m_hash_id)
45✔
86
      .decode(m_issuer_dn_hash, ASN1_Type::OctetString)
45✔
87
      .decode(m_issuer_key_hash, ASN1_Type::OctetString)
45✔
88
      .decode(m_subject_serial)
45✔
89
      .end_cons();
43✔
90
}
43✔
91

92
void SingleResponse::encode_into(DER_Encoder& /*to*/) const {
×
93
   throw Not_Implemented("SingleResponse::encode_into");
×
94
}
95

96
void SingleResponse::decode_from(BER_Decoder& from) {
45✔
97
   /*
98
   * RFC 6960 Section 4.2.1
99
   *
100
   * SingleResponse ::= SEQUENCE {
101
   *    certID                       CertID,
102
   *    certStatus                   CertStatus,
103
   *    thisUpdate                   GeneralizedTime,
104
   *    nextUpdate         [0]       EXPLICIT GeneralizedTime OPTIONAL,
105
   *    singleExtensions   [1]       EXPLICIT Extensions OPTIONAL }
106
   *
107
   * CertStatus ::= CHOICE {
108
   *    good        [0]     IMPLICIT NULL,
109
   *    revoked     [1]     IMPLICIT RevokedInfo,
110
   *    unknown     [2]     IMPLICIT UnknownInfo }
111
   *
112
   * RevokedInfo ::= SEQUENCE {
113
   *    revocationTime              GeneralizedTime,
114
   *    revocationReason    [0]     EXPLICIT CRLReason OPTIONAL }
115
   */
116
   BER_Object cert_status;
45✔
117
   Extensions extensions;
45✔
118

119
   from.start_sequence()
45✔
120
      .decode(m_certid)
45✔
121
      .get_next(cert_status)
43✔
122
      .decode(m_thisupdate)
43✔
123
      .decode_optional(m_nextupdate, ASN1_Type(0), ASN1_Class::ContextSpecific | ASN1_Class::Constructed)
77✔
124
      .decode_optional(extensions, ASN1_Type(1), ASN1_Class::ContextSpecific | ASN1_Class::Constructed)
84✔
125
      .end_cons();
37✔
126

127
   const auto cert_status_class = cert_status.get_class();
37✔
128
   if(cert_status_class != ASN1_Class::ContextSpecific &&
37✔
129
      cert_status_class != (ASN1_Class::ContextSpecific | ASN1_Class::Constructed)) {
1✔
130
      throw Decoding_Error("OCSP::SingleResponse: certStatus has unexpected class tag");
×
131
   }
132

133
   // TODO: should verify the cert_status body and decode RevokedInfo
134
   m_cert_status = static_cast<uint32_t>(cert_status.type());
37✔
135
   if(m_cert_status > 2) {
37✔
136
      throw Decoding_Error("Unknown OCSP CertStatus tag");
×
137
   }
138

139
   // We don't currently recognize any extensions here so if any are critical we should reject
140
   m_has_unknown_critical_ext = !extensions.critical_extensions().empty();
45✔
141
}
45✔
142

143
namespace {
144

145
// TODO: should this be in a header somewhere?
146
void decode_optional_list(BER_Decoder& ber, ASN1_Type tag, std::vector<X509_Certificate>& output) {
990✔
147
   const BER_Object obj = ber.get_next_object();
990✔
148

149
   if(!obj.is_a(tag, ASN1_Class::ContextSpecific | ASN1_Class::Constructed)) {
990✔
150
      ber.push_back(obj);
24✔
151
      return;
24✔
152
   }
153

154
   BER_Decoder list(obj, BER_Decoder::Limits::DER());
966✔
155
   auto seq = list.start_sequence();
966✔
156
   while(seq.more_items()) {
978✔
157
      output.push_back([&] {
2,868✔
158
         X509_Certificate cert;
941✔
159
         cert.decode_from(seq);
941✔
160
         return cert;
45✔
161
      }());
1,882✔
162
   }
163
   seq.end_cons();
37✔
164
}
2,815✔
165

166
}  // namespace
167

168
Request::Request(const X509_Certificate& issuer_cert, const X509_Certificate& subject_cert) :
2✔
169
      m_issuer(issuer_cert), m_certid(m_issuer, BigInt::from_bytes(subject_cert.serial_number())) {
2✔
170
   if(subject_cert.issuer_dn() != issuer_cert.subject_dn()) {
2✔
171
      throw Invalid_Argument("Invalid cert pair to OCSP::Request (mismatched issuer,subject args?)");
1✔
172
   }
173
}
3✔
174

175
Request::Request(const X509_Certificate& issuer_cert, const BigInt& subject_serial) :
2✔
176
      m_issuer(issuer_cert), m_certid(m_issuer, subject_serial) {}
2✔
177

178
std::vector<uint8_t> Request::BER_encode() const {
3✔
179
   /*
180
   * RFC 6960 Section 4.1.1
181
   *
182
   * OCSPRequest ::= SEQUENCE {
183
   *    tbsRequest                  TBSRequest,
184
   *    optionalSignature   [0]    EXPLICIT Signature OPTIONAL }
185
   *
186
   * TBSRequest ::= SEQUENCE {
187
   *    version             [0]    EXPLICIT Version DEFAULT v1,
188
   *    requestList                SEQUENCE OF Request }
189
   *
190
   * Request ::= SEQUENCE {
191
   *    reqCert                    CertID }
192
   */
193
   std::vector<uint8_t> output;
3✔
194
   DER_Encoder(output)
6✔
195
      .start_sequence()
3✔
196
      .start_sequence()
3✔
197
      .start_explicit(0)
3✔
198
      .encode(static_cast<size_t>(0))  // version #
3✔
199
      .end_explicit()
3✔
200
      .start_sequence()
3✔
201
      .start_sequence()
3✔
202
      .encode(m_certid)
3✔
203
      .end_cons()
3✔
204
      .end_cons()
3✔
205
      .end_cons()
3✔
206
      .end_cons();
3✔
207

208
   return output;
3✔
209
}
×
210

211
std::string Request::base64_encode() const {
2✔
212
   return Botan::base64_encode(BER_encode());
4✔
213
}
214

215
Response::Response(Certificate_Status_Code status) :
11✔
216
      m_status(Response_Status_Code::Successful), m_dummy_response_status(status) {}
11✔
217

218
Response::Response(const uint8_t response_bits[], size_t response_bits_len) :
2,905✔
219
      m_response_bits(response_bits, response_bits + response_bits_len) {
2,905✔
220
   /*
221
   * RFC 6960 Section 4.2.1
222
   *
223
   * OCSPResponse ::= SEQUENCE {
224
   *    responseStatus         OCSPResponseStatus,
225
   *    responseBytes      [0] EXPLICIT ResponseBytes OPTIONAL }
226
   *
227
   * OCSPResponseStatus ::= ENUMERATED { ... }
228
   *
229
   * ResponseBytes ::= SEQUENCE {
230
   *    responseType   OBJECT IDENTIFIER,
231
   *    response       OCTET STRING }
232
   */
233
   BER_Decoder outer_decoder(m_response_bits, BER_Decoder::Limits::DER());
2,905✔
234
   BER_Decoder response_outer = outer_decoder.start_sequence();
2,905✔
235

236
   size_t resp_status = 0;
1,004✔
237

238
   response_outer.decode(resp_status, ASN1_Type::Enumerated, ASN1_Class::Universal);
1,004✔
239

240
   /*
241
   RFC 6960 4.2.1
242

243
   OCSPResponseStatus ::= ENUMERATED {
244
       successful            (0),  -- Response has valid confirmations
245
       malformedRequest      (1),  -- Illegal confirmation request
246
       internalError         (2),  -- Internal error in issuer
247
       tryLater              (3),  -- Try again later
248
                                   -- (4) is not used
249
       sigRequired           (5),  -- Must sign the request
250
       unauthorized          (6)   -- Request unauthorized
251
   }
252
   */
253
   if(resp_status >= 7) {
993✔
254
      throw Decoding_Error("Unknown OCSPResponseStatus code");
×
255
   }
256

257
   m_status = static_cast<Response_Status_Code>(resp_status);
993✔
258

259
   if(m_status != Response_Status_Code::Successful) {
993✔
260
      return;
1✔
261
   }
262

263
   if(response_outer.more_items()) {
992✔
264
      BER_Decoder response_bytes_ctx = response_outer.start_context_specific(0);
992✔
265
      BER_Decoder response_bytes = response_bytes_ctx.start_sequence();
992✔
266

267
      response_bytes.decode_and_check(OID({1, 3, 6, 1, 5, 5, 7, 48, 1, 1}), "Unknown response type in OCSP response");
992✔
268

269
      /*
270
      * RFC 6960 Section 4.2.1
271
      *
272
      * BasicOCSPResponse ::= SEQUENCE {
273
      *    tbsResponseData      ResponseData,
274
      *    signatureAlgorithm   AlgorithmIdentifier,
275
      *    signature            BIT STRING,
276
      *    certs            [0] EXPLICIT SEQUENCE OF Certificate OPTIONAL }
277
      */
278
      BER_Decoder basic_response_decoder(response_bytes.get_next_octet_string(), BER_Decoder::Limits::DER());
992✔
279
      BER_Decoder basicresponse = basic_response_decoder.start_sequence();
992✔
280

281
      basicresponse.start_sequence()
992✔
282
         .raw_bytes(m_tbs_bits)
990✔
283
         .end_cons()
990✔
284
         .decode(m_sig_algo)
990✔
285
         .decode(m_signature, ASN1_Type::BitString);
990✔
286
      decode_optional_list(basicresponse, ASN1_Type(0), m_certs);
990✔
287

288
      basicresponse.verify_end();
61✔
289
      basic_response_decoder.verify_end();
60✔
290

291
      /*
292
      * RFC 6960 Section 4.2.1
293
      *
294
      * ResponseData ::= SEQUENCE {
295
      *    version              [0] EXPLICIT Version DEFAULT v1,
296
      *    responderID              ResponderID,
297
      *    producedAt               GeneralizedTime,
298
      *    responses                SEQUENCE OF SingleResponse,
299
      *    responseExtensions   [1] EXPLICIT Extensions OPTIONAL }
300
      *
301
      * ResponderID ::= CHOICE {
302
      *    byName   [1] Name,
303
      *    byKey    [2] KeyHash }
304
      */
305
      size_t responsedata_version = 0;
60✔
306
      Extensions extensions;
60✔
307

308
      BER_Decoder(m_tbs_bits, BER_Decoder::Limits::DER())
83✔
309
         .decode_optional(responsedata_version, ASN1_Type(0), ASN1_Class::ContextSpecific | ASN1_Class::Constructed)
60✔
310

311
         .decode_optional(m_signer_name, ASN1_Type(1), ASN1_Class::ContextSpecific | ASN1_Class::Constructed)
116✔
312

313
         .decode_optional_string(
56✔
314
            m_key_hash, ASN1_Type::OctetString, 2, ASN1_Class::ContextSpecific | ASN1_Class::Constructed)
56✔
315

316
         .decode(m_produced_at)
56✔
317

318
         .decode_list(m_responses)
45✔
319

320
         .decode_optional(extensions, ASN1_Type(1), ASN1_Class::ContextSpecific | ASN1_Class::Constructed)
93✔
321

322
         .verify_end();
37✔
323

324
      const bool has_signer = !m_signer_name.empty();
37✔
325
      const bool has_key_hash = !m_key_hash.empty();
37✔
326

327
      if(has_signer && has_key_hash) {
37✔
328
         throw Decoding_Error("OCSP response includes both byName and byKey in responderID field");
×
329
      }
330
      if(!has_signer && !has_key_hash) {
37✔
331
         throw Decoding_Error("OCSP response contains neither byName nor byKey in responderID field");
×
332
      }
333

334
      response_bytes.verify_end();
37✔
335
      response_bytes_ctx.verify_end();
37✔
336

337
      // We don't currently recognize any extensions here so if any are critical we should reject
338
      m_has_unknown_critical_ext = !extensions.critical_extensions().empty();
60✔
339
   }
3,857✔
340

341
   response_outer.verify_end();
37✔
342
   outer_decoder.verify_end();
37✔
343

344
   if(m_has_unknown_critical_ext == false) {
37✔
345
      // Check all of the SingleResponse extensions
346
      for(const auto& sr : m_responses) {
74✔
347
         if(sr.has_unknown_critical_extension()) {
37✔
348
            m_has_unknown_critical_ext = true;
×
349
            break;
×
350
         }
351
      }
352
   }
353
}
20,122✔
354

355
bool Response::is_issued_by(const X509_Certificate& candidate) const {
117✔
356
   if(!m_signer_name.empty()) {
117✔
357
      return (candidate.subject_dn() == m_signer_name);
114✔
358
   }
359

360
   if(!m_key_hash.empty()) {
3✔
361
      return (candidate.subject_public_key_bitstring_sha1() == m_key_hash);
2✔
362
   }
363

364
   return false;
365
}
366

367
Certificate_Status_Code Response::verify_signature(const X509_Certificate& issuer) const {
1✔
368
   const Path_Validation_Restrictions restrictions;
2✔
369

370
   return this->verify_signature(issuer, restrictions);
2✔
371
}
1✔
372

373
Certificate_Status_Code Response::verify_signature(const X509_Certificate& issuer,
40✔
374
                                                   const Path_Validation_Restrictions& restrictions) const {
375
   if(m_dummy_response_status) {
40✔
376
      return m_dummy_response_status.value();
×
377
   }
378

379
   if(m_signer_name.empty() && m_key_hash.empty()) {
40✔
380
      return Certificate_Status_Code::OCSP_RESPONSE_INVALID;
381
   }
382

383
   if(!is_issued_by(issuer)) {
40✔
384
      return Certificate_Status_Code::OCSP_ISSUER_NOT_FOUND;
385
   }
386

387
   try {
40✔
388
      auto pub_key = issuer.subject_public_key();
40✔
389

390
      PK_Verifier verifier(*pub_key, m_sig_algo);
40✔
391

392
      const bool valid_signature = verifier.verify_message(ASN1::put_in_sequence(m_tbs_bits), m_signature);
40✔
393

394
      if(valid_signature == false) {
40✔
395
         return Certificate_Status_Code::OCSP_SIGNATURE_ERROR;
396
      }
397

398
      if(m_has_unknown_critical_ext) {
39✔
399
         return Certificate_Status_Code::UNKNOWN_CRITICAL_EXTENSION;
400
      }
401

402
      const auto& trusted_hashes = restrictions.trusted_hashes();
39✔
403
      if(!trusted_hashes.empty() && !trusted_hashes.contains(verifier.hash_function())) {
78✔
404
         return Certificate_Status_Code::UNTRUSTED_HASH;
405
      }
406

407
      if(pub_key->estimated_strength() < restrictions.minimum_key_strength()) {
39✔
408
         return Certificate_Status_Code::SIGNATURE_METHOD_TOO_WEAK;
×
409
      }
410

411
      return Certificate_Status_Code::OCSP_SIGNATURE_OK;
412
   } catch(Exception&) {
80✔
413
      return Certificate_Status_Code::OCSP_SIGNATURE_ERROR;
×
414
   }
×
415
}
416

417
std::optional<X509_Certificate> Response::find_signing_certificate(
53✔
418
   const X509_Certificate& issuer_certificate, const Certificate_Store* trusted_ocsp_responders) const {
419
   using namespace std::placeholders;
53✔
420

421
   // Check whether the CA issuing the certificate in question also signed this
422
   if(is_issued_by(issuer_certificate)) {
53✔
423
      return issuer_certificate;
21✔
424
   }
425

426
   // Then try to find a delegated responder certificate in the stapled certs
427
   for(const auto& cert : m_certs) {
32✔
428
      if(this->is_issued_by(cert)) {
24✔
429
         return cert;
24✔
430
      }
431
   }
432

433
   // Last resort: check the additionally provides trusted OCSP responders
434
   if(trusted_ocsp_responders != nullptr) {
8✔
435
      if(!m_key_hash.empty()) {
4✔
436
         auto signing_cert = trusted_ocsp_responders->find_cert_by_pubkey_sha1(m_key_hash);
×
437
         if(signing_cert) {
×
438
            return signing_cert;
×
439
         }
440
      }
×
441

442
      if(!m_signer_name.empty()) {
4✔
443
         auto signing_cert = trusted_ocsp_responders->find_cert(m_signer_name, {});
4✔
444
         if(signing_cert) {
4✔
445
            return signing_cert;
2✔
446
         }
447
      }
4✔
448
   }
449

450
   return std::nullopt;
6✔
451
}
452

453
Certificate_Status_Code Response::status_for(const X509_Certificate& issuer,
38✔
454
                                             const X509_Certificate& subject,
455
                                             std::chrono::system_clock::time_point ref_time,
456
                                             std::chrono::seconds max_age) const {
457
   if(m_dummy_response_status) {
38✔
458
      return m_dummy_response_status.value();
×
459
   }
460

461
   for(const auto& response : m_responses) {
38✔
462
      if(response.certid().is_id_for(issuer, subject)) {
38✔
463
         const X509_Time x509_ref_time(ref_time);
38✔
464

465
         if(response.cert_status() == 1) {
38✔
466
            return Certificate_Status_Code::CERT_IS_REVOKED;
467
         }
468

469
         if(response.this_update() > x509_ref_time) {
38✔
470
            return Certificate_Status_Code::OCSP_NOT_YET_VALID;
471
         }
472

473
         if(response.next_update().time_is_set()) {
28✔
474
            if(x509_ref_time > response.next_update()) {
20✔
475
               return Certificate_Status_Code::OCSP_HAS_EXPIRED;
476
            }
477
         } else if(max_age > std::chrono::seconds::zero() &&
12✔
478
                   ref_time - response.this_update().to_std_timepoint() > max_age) {
8✔
479
            return Certificate_Status_Code::OCSP_IS_TOO_OLD;
2✔
480
         }
481

482
         if(response.cert_status() == 0) {
20✔
483
            return Certificate_Status_Code::OCSP_RESPONSE_GOOD;
484
         } else {
485
            return Certificate_Status_Code::OCSP_BAD_STATUS;
×
486
         }
487
      }
38✔
488
   }
489

490
   return Certificate_Status_Code::OCSP_CERT_NOT_LISTED;
491
}
492

493
#if defined(BOTAN_HAS_HTTP_UTIL)
494

495
Response online_check(const X509_Certificate& issuer,
×
496
                      const BigInt& subject_serial,
497
                      std::string_view ocsp_responder,
498
                      std::chrono::milliseconds timeout) {
499
   if(ocsp_responder.empty()) {
×
500
      throw Invalid_Argument("No OCSP responder specified");
×
501
   }
502

503
   const OCSP::Request req(issuer, subject_serial);
×
504

505
   auto http = HTTP::POST_sync(ocsp_responder, "application/ocsp-request", req.BER_encode(), 1, timeout);
×
506

507
   http.throw_unless_ok();
×
508

509
   // Check the MIME type?
510

511
   return OCSP::Response(http.body());
×
512
}
×
513

514
Response online_check(const X509_Certificate& issuer,
×
515
                      const X509_Certificate& subject,
516
                      std::chrono::milliseconds timeout) {
517
   if(subject.issuer_dn() != issuer.subject_dn()) {
×
518
      throw Invalid_Argument("Invalid cert pair to OCSP::online_check (mismatched issuer,subject args?)");
×
519
   }
520

521
   return online_check(issuer, BigInt::from_bytes(subject.serial_number()), subject.ocsp_responder(), timeout);
×
522
}
523

524
#endif
525

526
}  // namespace Botan::OCSP
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