• 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

86.01
/src/lib/x509/x509path.cpp
1
/*
2
* X.509 Certificate Path Validation
3
* (C) 2010,2011,2012,2014,2016,2026 Jack Lloyd
4
* (C) 2017 Fabian Weissberg, Rohde & Schwarz Cybersecurity
5
*
6
* Botan is released under the Simplified BSD License (see license.txt)
7
*/
8

9
#include <botan/x509path.h>
10

11
#include <botan/assert.h>
12
#include <botan/ocsp.h>
13
#include <botan/pk_keys.h>
14
#include <botan/x509_ext.h>
15
#include <botan/internal/concat_util.h>
16
#include <algorithm>
17
#include <chrono>
18
#include <set>
19
#include <sstream>
20
#include <string>
21
#include <unordered_set>
22
#include <vector>
23

24
#if defined(BOTAN_HAS_ONLINE_REVOCATION_CHECKS)
25
   #include <botan/internal/http_util.h>
26
   #include <future>
27
#endif
28

29
namespace Botan {
30

31
namespace {
32

33
/**
34
 * Lazy DFS iterator that yields certificate paths one at a time.
35
 *
36
 * Build all possible certificate paths from the end certificate to self-signed trusted roots.
37
 *
38
 * Basically, a DFS is performed starting from the end certificate. A stack (vector)
39
 * serves to control the DFS. At the beginning of each iteration, a pair is popped from
40
 * the stack that contains (1) the next certificate to add to the path (2) a bool that
41
 * indicates if the certificate is part of a trusted certstore. Ideally, we follow the
42
 * unique issuer of the current certificate until a trusted root is reached. However, the
43
 * issuer DN + authority key id need not be unique among the certificates used for
44
 * building the path. In such a case, we consider all the matching issuers by pushing
45
 * <IssuerCert, trusted?> on the stack for each of them.
46
 *
47
 * Each call to next() resumes the search and returns the next discovered path, or nullopt
48
 * when the search space is exhausted.
49
*/
50
class CertificatePathBuilder {
51
   public:
52
      CertificatePathBuilder(const std::vector<Certificate_Store*>& trusted_certstores,
4,053✔
53
                             const X509_Certificate& end_entity,
54
                             const std::vector<X509_Certificate>& end_entity_extra,
55
                             bool require_self_signed = false) :
4,053✔
56
            m_trusted_certstores(trusted_certstores), m_require_self_signed(require_self_signed) {
4,053✔
57
         if(std::ranges::any_of(trusted_certstores, [](auto* ptr) { return ptr == nullptr; })) {
8,106✔
58
            throw Invalid_Argument("Certificate store list must not contain nullptr");
×
59
         }
60

61
         for(const auto& cert : end_entity_extra) {
7,087✔
62
            if(!cert_in_any_trusted_store(cert)) {
6,068✔
63
               m_ee_extras.add_certificate(cert);
2,782✔
64
            }
65
         }
66

67
         m_stack.push_back({end_entity, cert_in_any_trusted_store(end_entity)});
4,053✔
68
      }
4,053✔
69

70
      std::optional<std::vector<X509_Certificate>> next() {
5,925✔
71
         size_t steps = 0;
5,925✔
72

73
         while(!m_stack.empty()) {
20,080✔
74
            constexpr size_t MAX_DFS_STEPS = 1000;
17,943✔
75

76
            steps++;
17,943✔
77

78
            if(steps > MAX_DFS_STEPS) {
17,943✔
79
               // Intentionally overwrite any previous builder error
80
               m_error = Certificate_Status_Code::CERT_ISSUER_NOT_FOUND;
×
81
               return std::nullopt;
3,788✔
82
            }
83

84
            auto [last, trusted] = std::move(m_stack.back());  // move before pop_back
30,284✔
85
            m_stack.pop_back();
17,943✔
86

87
            // Found a deletion marker that guides the DFS, backtracking
88
            if(!last.has_value()) {
17,943✔
89
               m_certs_seen.erase(m_path_so_far.back().tag());
5,602✔
90
               m_path_so_far.pop_back();
5,602✔
91
               continue;
5,602✔
92
            }
93

94
            // Certificate already seen in this path?
95
            const auto tag = last->tag();
12,341✔
96
            if(m_certs_seen.contains(tag)) {
12,341✔
97
               if(!m_error.has_value()) {
1,858✔
98
                  m_error = Certificate_Status_Code::CERT_CHAIN_LOOP;
1,696✔
99
               }
100
               continue;
1,858✔
101
            }
102

103
            // A valid path has been discovered. It includes endpoints that may end
104
            // with either a self-signed or a non-self-signed certificate. For
105
            // certificates that are not self-signed, additional paths could
106
            // potentially extend from the current one.
107
            if(trusted) {
10,483✔
108
               auto path = m_path_so_far;
3,935✔
109
               path.push_back(*last);
3,935✔
110
               push_issuers(*last);
3,935✔
111

112
               if(!m_require_self_signed || last->is_self_signed()) {
3,935✔
113
                  return path;
3,788✔
114
               }
115

116
               /*
117
               This unconditionally overwrites the error because it's likely the most
118
               informative error in this context - we found a path that seemed entirely
119
               suitable, except that self-signed roots are required so it was skipped.
120
               */
121
               m_error = Certificate_Status_Code::CANNOT_ESTABLISH_TRUST;
147✔
122
               continue;
147✔
123
            }
3,935✔
124

125
            if(last->is_self_signed()) {
6,548✔
126
               if(!m_error.has_value()) {
247✔
127
                  m_error = Certificate_Status_Code::CANNOT_ESTABLISH_TRUST;
246✔
128
               }
129
               continue;
247✔
130
            }
131

132
            push_issuers(*last);
6,301✔
133
         }
17,943✔
134

135
         return std::nullopt;
2,137✔
136
      }
137

138
      /**
139
      * Return the first error encountered during path building
140
      *
141
      * Only used as a last resort if there were no successful paths
142
      */
143
      Certificate_Status_Code error() const {
445✔
144
         if(m_error.has_value()) {
445✔
145
            // Confirm it is an actual error code and not accidentally OK...
146
            BOTAN_ASSERT_NOMSG(static_cast<uint32_t>(m_error.value()) >= 3000);
445✔
147
            return m_error.value();
148
         } else {
149
            return Certificate_Status_Code::CERT_ISSUER_NOT_FOUND;
150
         }
151
      }
152

153
   private:
154
      bool cert_in_any_trusted_store(const X509_Certificate& cert) const {
7,087✔
155
         return std::ranges::any_of(m_trusted_certstores,
14,174✔
156
                                    [&](const Certificate_Store* store) { return store->contains(cert); });
6,821✔
157
      }
158

159
      void push_issuers(const X509_Certificate& cert) {
10,236✔
160
         const X509_DN& issuer_dn = cert.issuer_dn();
10,236✔
161
         const std::vector<uint8_t>& auth_key_id = cert.authority_key_id();
10,236✔
162

163
         // Search for trusted issuers
164
         std::vector<X509_Certificate> trusted_issuers;
10,236✔
165
         for(const Certificate_Store* store : m_trusted_certstores) {
20,428✔
166
            auto new_issuers = store->find_all_certs(issuer_dn, auth_key_id);
10,192✔
167
            trusted_issuers.insert(trusted_issuers.end(), new_issuers.begin(), new_issuers.end());
10,192✔
168
         }
10,192✔
169

170
         // Search the supplemental certs
171
         const std::vector<X509_Certificate> misc_issuers = m_ee_extras.find_all_certs(issuer_dn, auth_key_id);
10,236✔
172

173
         // If we could not find any issuers, the current path ends here
174
         if(trusted_issuers.empty() && misc_issuers.empty()) {
10,236✔
175
            if(!m_error.has_value()) {
220✔
176
               m_error = Certificate_Status_Code::CERT_ISSUER_NOT_FOUND;
220✔
177
            }
178
            return;
220✔
179
         }
180

181
         m_path_so_far.push_back(cert);
10,016✔
182
         m_certs_seen.emplace(cert.tag());
10,016✔
183

184
         // Push a deletion marker on the stack for backtracking later
185
         m_stack.push_back({std::nullopt, false});
10,016✔
186

187
         for(const auto& trusted_cert : trusted_issuers) {
17,701✔
188
            m_stack.push_back({trusted_cert, true});
15,370✔
189
         }
190
         for(const auto& misc : misc_issuers) {
12,589✔
191
            m_stack.push_back({misc, false});
5,146✔
192
         }
193
      }
10,236✔
194

195
      const std::vector<Certificate_Store*> m_trusted_certstores;
196
      const bool m_require_self_signed;
197
      Certificate_Store_In_Memory m_ee_extras;
198
      std::vector<std::pair<std::optional<X509_Certificate>, bool>> m_stack;
199
      std::vector<X509_Certificate> m_path_so_far;
200
      std::unordered_set<X509_Certificate::Tag, X509_Certificate::TagHash> m_certs_seen;
201
      std::optional<Certificate_Status_Code> m_error;
202
};
203

204
}  // namespace
205

206
/*
207
* PKIX path validation
208
*/
209
CertificatePathStatusCodes PKIX::check_chain(const std::vector<X509_Certificate>& cert_path,
3,769✔
210
                                             std::chrono::system_clock::time_point ref_time,
211
                                             std::string_view hostname,
212
                                             Usage_Type usage,
213
                                             const Path_Validation_Restrictions& restrictions) {
214
   if(cert_path.empty()) {
3,769✔
215
      throw Invalid_Argument("PKIX::check_chain cert_path empty");
×
216
   }
217

218
   const bool is_end_entity_trust_anchor = (cert_path.size() == 1);
3,769✔
219

220
   const X509_Time validation_time(ref_time);
3,769✔
221

222
   CertificatePathStatusCodes cert_status(cert_path.size());
3,769✔
223

224
   // Before anything else verify the entire chain of signatures
225
   for(size_t i = 0; i != cert_path.size(); ++i) {
13,793✔
226
      std::set<Certificate_Status_Code>& status = cert_status.at(i);
10,024✔
227

228
      const bool at_trust_anchor = (i == cert_path.size() - 1);
10,024✔
229

230
      const X509_Certificate& subject = cert_path[i];
10,024✔
231

232
      // If using intermediate CAs as trust anchors, the signature of the trust
233
      // anchor cannot be verified since the issuer is not part of the
234
      // certificate chain
235
      if(!restrictions.require_self_signed_trust_anchors() && at_trust_anchor && !subject.is_self_signed()) {
10,024✔
236
         continue;
5✔
237
      }
238

239
      const X509_Certificate& issuer = cert_path[at_trust_anchor ? (i) : (i + 1)];
10,019✔
240

241
      // Check the signature algorithm is known
242
      if(!subject.signature_algorithm().oid().registered_oid()) {
10,019✔
243
         status.insert(Certificate_Status_Code::SIGNATURE_ALGO_UNKNOWN);
32✔
244
      } else {
245
         std::unique_ptr<Public_Key> issuer_key;
9,987✔
246
         try {
9,987✔
247
            issuer_key = issuer.subject_public_key();
9,987✔
248
         } catch(...) {
×
249
            status.insert(Certificate_Status_Code::CERT_PUBKEY_INVALID);
×
250
         }
×
251

252
         if(issuer_key) {
9,987✔
253
            if(issuer_key->estimated_strength() < restrictions.minimum_key_strength()) {
9,987✔
254
               status.insert(Certificate_Status_Code::SIGNATURE_METHOD_TOO_WEAK);
65✔
255
            }
256

257
            const auto sig_status = subject.verify_signature(*issuer_key);
9,987✔
258

259
            if(sig_status.first != Certificate_Status_Code::VERIFIED) {
9,987✔
260
               status.insert(sig_status.first);
402✔
261
            } else {
262
               // Signature is valid, check if hash used was acceptable
263
               const std::string hash_used_for_signature = sig_status.second;
9,585✔
264
               BOTAN_ASSERT_NOMSG(!hash_used_for_signature.empty());
9,585✔
265
               const auto& trusted_hashes = restrictions.trusted_hashes();
9,585✔
266

267
               // Ignore untrusted hashes on self-signed roots
268
               if(!trusted_hashes.empty() && !at_trust_anchor) {
9,585✔
269
                  if(!trusted_hashes.contains(hash_used_for_signature)) {
5,822✔
270
                     status.insert(Certificate_Status_Code::UNTRUSTED_HASH);
100✔
271
                  }
272
               }
273
            }
9,585✔
274
         }
9,987✔
275
      }
10,024✔
276
   }
277

278
   // If any of the signatures were invalid, return immediately; we know the
279
   // chain is invalid and signature failure is always considered the most
280
   // critical result. This does mean other problems in the certificate (eg
281
   // expired) will not be reported, but we'd have to assume any such data is
282
   // anyway arbitrary considering we couldn't verify the signature chain
283

284
   for(size_t i = 0; i != cert_path.size(); ++i) {
12,765✔
285
      for(auto status : cert_status.at(i)) {
9,468✔
286
         // This ignores errors relating to the key or hash being weak since
287
         // these are somewhat advisory
288
         if(static_cast<uint32_t>(status) >= 5000) {
472✔
289
            return cert_status;
3,769✔
290
         }
291
      }
292
   }
293

294
   if(!hostname.empty() && !cert_path[0].matches_dns_name(hostname)) {
3,461✔
295
      cert_status[0].insert(Certificate_Status_Code::CERT_NAME_NOMATCH);
27✔
296
   }
297

298
   if(!cert_path[0].allowed_usage(usage)) {
3,461✔
299
      if(usage == Usage_Type::OCSP_RESPONDER) {
5✔
300
         cert_status[0].insert(Certificate_Status_Code::OCSP_RESPONSE_MISSING_KEYUSAGE);
1✔
301
      }
302
      cert_status[0].insert(Certificate_Status_Code::INVALID_USAGE);
5✔
303
   }
304

305
   if(cert_path[0].has_constraints(Key_Constraints::KeyCertSign) && cert_path[0].is_CA_cert() == false) {
3,461✔
306
      /*
307
      "If the keyCertSign bit is asserted, then the cA bit in the
308
      basic constraints extension (Section 4.2.1.9) MUST also be
309
      asserted." - RFC 5280
310

311
      We don't bother doing this check on the rest of the path since they
312
      must have the cA bit asserted or the validation will fail anyway.
313
      */
314
      cert_status[0].insert(Certificate_Status_Code::INVALID_USAGE);
×
315
   }
316

317
   for(size_t i = 0; i != cert_path.size(); ++i) {
12,399✔
318
      std::set<Certificate_Status_Code>& status = cert_status.at(i);
8,938✔
319

320
      const bool at_trust_anchor = (i == cert_path.size() - 1);
8,938✔
321

322
      const X509_Certificate& subject = cert_path[i];
8,938✔
323
      const auto issuer = [&]() -> std::optional<X509_Certificate> {
26,814✔
324
         if(!at_trust_anchor) {
8,938✔
325
            return cert_path[i + 1];
5,477✔
326
         } else if(subject.is_self_signed()) {
3,461✔
327
            return cert_path[i];
3,456✔
328
         } else {
329
            return {};  // Non self-signed trust anchors have no checkable issuers.
5✔
330
         }
331
      }();
8,938✔
332

333
      if(restrictions.require_self_signed_trust_anchors() && !issuer.has_value()) {
8,938✔
334
         status.insert(Certificate_Status_Code::CHAIN_LACKS_TRUST_ROOT);
×
335
      }
336

337
      // This should never happen; it indicates a bug in path building
338
      if(issuer.has_value() && subject.issuer_dn() != issuer->subject_dn()) {
8,938✔
339
         status.insert(Certificate_Status_Code::CHAIN_NAME_MISMATCH);
×
340
      }
341

342
      // Check the serial number
343
      if(subject.is_serial_negative()) {
8,938✔
344
         status.insert(Certificate_Status_Code::CERT_SERIAL_NEGATIVE);
64✔
345
      }
346

347
      // Check the subject's DN components' length
348

349
      for(const auto& dn_pair : subject.subject_dn().dn_info()) {
28,231✔
350
         const size_t dn_ub = X509_DN::lookup_ub(dn_pair.first);
19,293✔
351
         // dn_pair = <OID,str>
352
         if(dn_ub > 0 && dn_pair.second.size() > dn_ub) {
19,293✔
353
            status.insert(Certificate_Status_Code::DN_TOO_LONG);
40✔
354
         }
355
      }
356

357
      // If so configured, allow trust anchors outside the validity period with
358
      // a warning rather than a hard error
359
      const bool enforce_validity_period = !at_trust_anchor || !restrictions.ignore_trusted_root_time_range();
8,938✔
360
      // Check all certs for valid time range
361
      if(validation_time < subject.not_before()) {
8,938✔
362
         if(enforce_validity_period) {
82✔
363
            status.insert(Certificate_Status_Code::CERT_NOT_YET_VALID);
80✔
364
         } else {
365
            status.insert(Certificate_Status_Code::TRUSTED_CERT_NOT_YET_VALID);  // only warn
2✔
366
         }
367
      }
368

369
      if(validation_time > subject.not_after()) {
8,938✔
370
         if(enforce_validity_period) {
310✔
371
            status.insert(Certificate_Status_Code::CERT_HAS_EXPIRED);
308✔
372
         } else {
373
            status.insert(Certificate_Status_Code::TRUSTED_CERT_HAS_EXPIRED);  // only warn
2✔
374
         }
375
      }
376

377
      // Check issuer constraints
378
      if(issuer.has_value() && !issuer->is_CA_cert() && !is_end_entity_trust_anchor) {
8,938✔
379
         status.insert(Certificate_Status_Code::CA_CERT_NOT_FOR_CERT_ISSUER);
118✔
380
      }
381

382
      // Check cert extensions
383

384
      if(subject.x509_version() == 1) {
8,938✔
385
         if(subject.v2_issuer_key_id().empty() == false || subject.v2_subject_key_id().empty() == false) {
37✔
386
            status.insert(Certificate_Status_Code::V2_IDENTIFIERS_IN_V1_CERT);
1✔
387
         }
388
      }
389

390
      const Extensions& extensions = subject.v3_extensions();
8,938✔
391
      const auto& extensions_vec = extensions.extensions();
8,938✔
392
      if(subject.x509_version() < 3 && !extensions_vec.empty()) {
8,938✔
393
         status.insert(Certificate_Status_Code::EXT_IN_V1_V2_CERT);
64✔
394
      }
395

396
      for(const auto& extension : extensions_vec) {
43,287✔
397
         extension.first->validate(subject, issuer, cert_path, cert_status, i);
34,349✔
398
      }
399

400
      if(extensions_vec.size() != extensions.get_extension_oids().size()) {
8,938✔
401
         status.insert(Certificate_Status_Code::DUPLICATE_CERT_EXTENSION);
32✔
402
      }
403
   }
17,871✔
404

405
   // path len check
406
   size_t max_path_length = cert_path.size();
3,461✔
407
   for(size_t i = cert_path.size() - 1; i > 0; --i) {
8,938✔
408
      std::set<Certificate_Status_Code>& status = cert_status.at(i);
5,477✔
409
      const X509_Certificate& subject = cert_path[i];
5,477✔
410

411
      /*
412
      * If the certificate was not self-issued, verify that max_path_length is
413
      * greater than zero and decrement max_path_length by 1.
414
      */
415
      if(subject.subject_dn() != subject.issuer_dn()) {
5,477✔
416
         if(max_path_length > 0) {
1,906✔
417
            max_path_length -= 1;
1,828✔
418
         } else {
419
            status.insert(Certificate_Status_Code::CERT_CHAIN_TOO_LONG);
78✔
420
         }
421
      }
422

423
      /*
424
      * If pathLenConstraint is present in the certificate and is less than max_path_length,
425
      * set max_path_length to the value of pathLenConstraint.
426
      */
427
      if(auto path_len_constraint = subject.path_length_constraint()) {
5,477✔
428
         max_path_length = std::min(max_path_length, *path_len_constraint);
5,472✔
429
      }
430
   }
431

432
   return cert_status;
433
}
3,769✔
434

435
namespace {
436

437
Certificate_Status_Code verify_ocsp_signing_cert(const X509_Certificate& signing_cert,
43✔
438
                                                 const X509_Certificate& ca,
439
                                                 const std::vector<X509_Certificate>& extra_certs,
440
                                                 const std::vector<Certificate_Store*>& certstores,
441
                                                 std::chrono::system_clock::time_point ref_time,
442
                                                 const Path_Validation_Restrictions& restrictions) {
443
   // RFC 6960 4.2.2.2
444
   //    [Applications] MUST reject the response if the certificate
445
   //    required to validate the signature on the response does not
446
   //    meet at least one of the following criteria:
447
   //
448
   //    1. Matches a local configuration of OCSP signing authority
449
   //       for the certificate in question, or
450
   if(restrictions.trusted_ocsp_responders() != nullptr &&
86✔
451
      restrictions.trusted_ocsp_responders()->contains(signing_cert)) {
43✔
452
      return Certificate_Status_Code::OK;
453
   }
454

455
   // RFC 6960 4.2.2.2
456
   //
457
   //    2. Is the certificate of the CA that issued the certificate
458
   //       in question, or
459
   if(signing_cert == ca) {
43✔
460
      return Certificate_Status_Code::OK;
461
   }
462

463
   // RFC 6960 4.2.2.2
464
   //
465
   //    3. Includes a value of id-kp-OCSPSigning in an extended key
466
   //       usage extension and is issued by the CA that issued the
467
   //       certificate in question as stated above.
468

469
   // Verify the delegated responder was issued by the CA that issued
470
   // the certificate in question (the EKU and signature chain are
471
   // verified by the path validation below).
472
   if(signing_cert.issuer_dn() != ca.subject_dn()) {
23✔
473
      return Certificate_Status_Code::OCSP_ISSUER_NOT_TRUSTED;
474
   } else {
475
      // If both key identifiers are available, verify they match to
476
      // handle CAs that share a subject DN but have different keys
477
      // (eg re-keyed or cross-certified CAs).
478
      const auto& aki = signing_cert.authority_key_id();
21✔
479
      const auto& ski = ca.subject_key_id();
21✔
480
      if(!aki.empty() && !ski.empty() && aki != ski) {
21✔
481
         return Certificate_Status_Code::OCSP_ISSUER_NOT_TRUSTED;
482
      }
483
   }
484

485
   // TODO: Implement OCSP revocation check of OCSP signer certificate
486
   // Note: This needs special care to prevent endless loops on specifically
487
   //       forged chains of OCSP responses referring to each other.
488
   //
489
   // Currently, we're disabling OCSP-based revocation checks by setting the
490
   // timeout to 0. Additionally, the library's API would not allow an
491
   // application to pass in the required "second order" OCSP responses. I.e.
492
   // "second order" OCSP checks would need to rely on `check_ocsp_online()`
493
   // which is not an option for some applications (e.g. that require a proxy
494
   // for external HTTP requests).
495
   const auto ocsp_timeout = std::chrono::milliseconds::zero();
21✔
496
   const auto relaxed_restrictions =
21✔
497
      Path_Validation_Restrictions(false /* do not enforce revocation data */,
498
                                   restrictions.minimum_key_strength(),
499
                                   false /* OCSP is not available, so don't try for intermediates */,
500
                                   restrictions.trusted_hashes());
21✔
501

502
   const auto validation_result = x509_path_validate(concat(std::vector{signing_cert}, extra_certs),
63✔
503
                                                     relaxed_restrictions,
504
                                                     certstores,
505
                                                     {} /* hostname */,
506
                                                     Botan::Usage_Type::OCSP_RESPONDER,
507
                                                     ref_time,
508
                                                     ocsp_timeout);
42✔
509

510
   return validation_result.result();
21✔
511
}
42✔
512

513
std::set<Certificate_Status_Code> evaluate_ocsp_response(const OCSP::Response& ocsp_response,
54✔
514
                                                         const X509_Certificate& subject,
515
                                                         const X509_Certificate& ca,
516
                                                         const std::vector<X509_Certificate>& cert_path,
517
                                                         const std::vector<Certificate_Store*>& certstores,
518
                                                         std::chrono::system_clock::time_point ref_time,
519
                                                         const Path_Validation_Restrictions& restrictions) {
520
   // Handle softfail conditions (eg. OCSP unavailable)
521
   if(auto dummy_status = ocsp_response.dummy_status()) {
54✔
522
      return {dummy_status.value()};
10✔
523
   }
524

525
   // Find the certificate that signed this OCSP response
526
   auto signing_cert = ocsp_response.find_signing_certificate(ca, restrictions.trusted_ocsp_responders());
44✔
527
   if(!signing_cert) {
44✔
528
      return {Certificate_Status_Code::OCSP_ISSUER_NOT_FOUND};
1✔
529
   }
530

531
   // Verify the signing certificate is trusted
532
   auto cert_status = verify_ocsp_signing_cert(
43✔
533
      signing_cert.value(), ca, concat(ocsp_response.certificates(), cert_path), certstores, ref_time, restrictions);
43✔
534
   if(cert_status >= Certificate_Status_Code::FIRST_ERROR_STATUS) {
43✔
535
      return {cert_status, Certificate_Status_Code::OCSP_ISSUER_NOT_TRUSTED};
4✔
536
   }
537

538
   // Verify the cryptographic signature on the OCSP response
539
   auto sig_status = ocsp_response.verify_signature(signing_cert.value(), restrictions);
39✔
540
   if(sig_status != Certificate_Status_Code::OCSP_SIGNATURE_OK) {
39✔
541
      return {sig_status};
1✔
542
   }
543

544
   // All checks passed, return the certificate's revocation status
545
   return {ocsp_response.status_for(ca, subject, ref_time, restrictions.max_ocsp_age())};
38✔
546
}
44✔
547

548
}  // namespace
549

550
CertificatePathStatusCodes PKIX::check_ocsp(const std::vector<X509_Certificate>& cert_path,
185✔
551
                                            const std::vector<std::optional<OCSP::Response>>& ocsp_responses,
552
                                            const std::vector<Certificate_Store*>& certstores,
553
                                            std::chrono::system_clock::time_point ref_time,
554
                                            const Path_Validation_Restrictions& restrictions) {
555
   if(cert_path.empty()) {
185✔
556
      throw Invalid_Argument("PKIX::check_ocsp cert_path empty");
×
557
   }
558

559
   CertificatePathStatusCodes cert_status(cert_path.size() - 1);
185✔
560

561
   for(size_t i = 0; i != cert_path.size() - 1; ++i) {
412✔
562
      const X509_Certificate& subject = cert_path.at(i);
227✔
563
      const X509_Certificate& ca = cert_path.at(i + 1);
227✔
564

565
      if(i < ocsp_responses.size() && ocsp_responses.at(i).has_value() &&
227✔
566
         ocsp_responses.at(i)->status() == OCSP::Response_Status_Code::Successful) {
54✔
567
         try {
54✔
568
            cert_status.at(i) = evaluate_ocsp_response(
162✔
569
               ocsp_responses.at(i).value(), subject, ca, cert_path, certstores, ref_time, restrictions);
108✔
570
         } catch(Exception&) {
×
571
            cert_status.at(i).insert(Certificate_Status_Code::OCSP_RESPONSE_INVALID);
×
572
         }
×
573
      }
574
   }
575

576
   while(!cert_status.empty() && cert_status.back().empty()) {
358✔
577
      cert_status.pop_back();
173✔
578
   }
579

580
   return cert_status;
185✔
581
}
×
582

583
CertificatePathStatusCodes PKIX::check_crl(const std::vector<X509_Certificate>& cert_path,
2,827✔
584
                                           const std::vector<std::optional<X509_CRL>>& crls,
585
                                           std::chrono::system_clock::time_point ref_time) {
586
   if(cert_path.empty()) {
2,827✔
587
      throw Invalid_Argument("PKIX::check_crl cert_path empty");
×
588
   }
589

590
   CertificatePathStatusCodes cert_status(cert_path.size());
2,827✔
591
   const X509_Time validation_time(ref_time);
2,827✔
592

593
   for(size_t i = 0; i != cert_path.size() - 1; ++i) {
6,980✔
594
      std::set<Certificate_Status_Code>& status = cert_status.at(i);
4,153✔
595

596
      if(i < crls.size() && crls[i].has_value()) {
4,153✔
597
         const X509_Certificate& subject = cert_path.at(i);
1,315✔
598
         const X509_Certificate& ca = cert_path.at(i + 1);
1,315✔
599

600
         if(!ca.allowed_usage(Key_Constraints::CrlSign)) {
1,315✔
601
            status.insert(Certificate_Status_Code::CA_CERT_NOT_FOR_CRL_ISSUER);
4✔
602
         }
603

604
         if(validation_time < crls[i]->this_update()) {
1,315✔
605
            status.insert(Certificate_Status_Code::CRL_NOT_YET_VALID);
32✔
606
         }
607

608
         if(crls[i]->next_update().time_is_set() && validation_time > crls[i]->next_update()) {
1,315✔
609
            status.insert(Certificate_Status_Code::CRL_HAS_EXPIRED);
36✔
610
         }
611

612
         auto ca_key = ca.subject_public_key();
1,315✔
613
         if(crls[i]->check_signature(*ca_key) == false) {
1,315✔
614
            status.insert(Certificate_Status_Code::CRL_BAD_SIGNATURE);
35✔
615
         } else {
616
            status.insert(Certificate_Status_Code::VALID_CRL_CHECKED);
1,280✔
617

618
            if(crls[i]->is_revoked(subject)) {
1,280✔
619
               status.insert(Certificate_Status_Code::CERT_IS_REVOKED);
275✔
620
            }
621

622
            if(!crls[i]->has_matching_distribution_point(subject)) {
1,280✔
623
               status.insert(Certificate_Status_Code::NO_MATCHING_CRLDP);
384✔
624
            }
625

626
            for(const auto& [extension, critical] : crls[i]->extensions().extensions()) {
3,909✔
627
               if(critical) {
2,629✔
628
                  /* NIST Certificate Path Validation Testing document: "When an implementation does
629
                  * not recognize a critical extension in the crlExtensions field, it shall assume
630
                  * that identified certificates have been revoked and are no longer valid"
631
                  */
632
                  if(dynamic_cast<const Cert_Extension::Unknown_Extension*>(extension.get()) != nullptr) {
72✔
633
                     status.insert(Certificate_Status_Code::CERT_IS_REVOKED);
40✔
634
                  }
635
               }
636
            }
1,280✔
637
         }
638
      }
1,315✔
639
   }
640

641
   while(!cert_status.empty() && cert_status.back().empty()) {
8,453✔
642
      cert_status.pop_back();
5,626✔
643
   }
644

645
   return cert_status;
2,827✔
646
}
2,827✔
647

648
CertificatePathStatusCodes PKIX::check_crl(const std::vector<X509_Certificate>& cert_path,
2,821✔
649
                                           const std::vector<Certificate_Store*>& certstores,
650
                                           std::chrono::system_clock::time_point ref_time) {
651
   if(cert_path.empty()) {
2,821✔
652
      throw Invalid_Argument("PKIX::check_crl cert_path empty");
×
653
   }
654

655
   if(certstores.empty()) {
2,821✔
656
      throw Invalid_Argument("PKIX::check_crl certstores empty");
×
657
   }
658

659
   std::vector<std::optional<X509_CRL>> crls(cert_path.size());
2,821✔
660

661
   for(size_t i = 0; i != cert_path.size(); ++i) {
9,789✔
662
      for(auto* certstore : certstores) {
11,967✔
663
         crls[i] = certstore->find_crl_for(cert_path[i]);
7,012✔
664
         if(crls[i]) {
7,012✔
665
            break;
666
         }
667
      }
668
   }
669

670
   return PKIX::check_crl(cert_path, crls, ref_time);
5,642✔
671
}
2,821✔
672

673
#if defined(BOTAN_HAS_ONLINE_REVOCATION_CHECKS)
674

675
CertificatePathStatusCodes PKIX::check_ocsp_online(const std::vector<X509_Certificate>& cert_path,
10✔
676
                                                   const std::vector<Certificate_Store*>& trusted_certstores,
677
                                                   std::chrono::system_clock::time_point ref_time,
678
                                                   std::chrono::milliseconds timeout,
679
                                                   const Path_Validation_Restrictions& restrictions) {
680
   if(cert_path.empty()) {
10✔
681
      throw Invalid_Argument("PKIX::check_ocsp_online cert_path empty");
×
682
   }
683

684
   std::vector<std::future<std::optional<OCSP::Response>>> ocsp_response_futures;
10✔
685

686
   size_t to_ocsp = 1;
10✔
687

688
   if(restrictions.ocsp_all_intermediates()) {
10✔
689
      to_ocsp = cert_path.size() - 1;
×
690
   }
691
   if(cert_path.size() == 1) {
10✔
692
      to_ocsp = 0;
×
693
   }
694

695
   for(size_t i = 0; i < to_ocsp; ++i) {
20✔
696
      const auto& subject = cert_path.at(i);
10✔
697
      const auto& issuer = cert_path.at(i + 1);
10✔
698

699
      if(subject.ocsp_responder().empty()) {
10✔
700
         ocsp_response_futures.emplace_back(std::async(std::launch::deferred, []() -> std::optional<OCSP::Response> {
18✔
701
            return OCSP::Response(Certificate_Status_Code::OCSP_NO_REVOCATION_URL);
18✔
702
         }));
703
      } else {
704
         auto ocsp_url = subject.ocsp_responder();
1✔
705
         auto ocsp_req = OCSP::Request(issuer, BigInt::from_bytes(subject.serial_number()));
1✔
706
         ocsp_response_futures.emplace_back(
2✔
707
            std::async(std::launch::async, [ocsp_url, ocsp_req, timeout]() -> std::optional<OCSP::Response> {
3✔
708
               HTTP::Response http;
1✔
709
               try {
1✔
710
                  http = HTTP::POST_sync(ocsp_url,
2✔
711
                                         "application/ocsp-request",
712
                                         ocsp_req.BER_encode(),
2✔
713
                                         /*redirects*/ 1,
714
                                         timeout);
1✔
715

716
                  if(http.status_code() != 200) {
1✔
717
                     return OCSP::Response(Certificate_Status_Code::OCSP_SERVER_NOT_AVAILABLE);
×
718
                  }
719

720
                  return OCSP::Response(http.body());
1✔
721
               } catch(std::exception&) {
×
722
                  return OCSP::Response(Certificate_Status_Code::OCSP_SERVER_NOT_AVAILABLE);
×
723
               }
×
724
            }));
1✔
725
      }
1✔
726
   }
727

728
   std::vector<std::optional<OCSP::Response>> ocsp_responses;
10✔
729
   ocsp_responses.reserve(ocsp_response_futures.size());
10✔
730

731
   for(auto& ocsp_response_future : ocsp_response_futures) {
20✔
732
      ocsp_responses.push_back(ocsp_response_future.get());
30✔
733
   }
734

735
   return PKIX::check_ocsp(cert_path, ocsp_responses, trusted_certstores, ref_time, restrictions);
20✔
736
}
10✔
737

738
CertificatePathStatusCodes PKIX::check_crl_online(const std::vector<X509_Certificate>& cert_path,
×
739
                                                  const std::vector<Certificate_Store*>& certstores,
740
                                                  Certificate_Store_In_Memory* crl_store,
741
                                                  std::chrono::system_clock::time_point ref_time,
742
                                                  std::chrono::milliseconds timeout) {
743
   if(cert_path.empty()) {
×
744
      throw Invalid_Argument("PKIX::check_crl_online cert_path empty");
×
745
   }
746
   if(certstores.empty()) {
×
747
      throw Invalid_Argument("PKIX::check_crl_online certstores empty");
×
748
   }
749

750
   std::vector<std::future<std::optional<X509_CRL>>> future_crls;
×
751
   std::vector<std::optional<X509_CRL>> crls(cert_path.size());
×
752

753
   for(size_t i = 0; i != cert_path.size(); ++i) {
×
754
      const auto& cert = cert_path.at(i);
×
755
      for(auto* certstore : certstores) {
×
756
         crls[i] = certstore->find_crl_for(cert);
×
757
         if(crls[i].has_value()) {
×
758
            break;
759
         }
760
      }
761

762
      // TODO: check if CRL is expired and re-request?
763

764
      // Only request if we don't already have a CRL
765
      if(crls[i]) {
×
766
         /*
767
         We already have a CRL, so just insert this empty one to hold a place in the vector
768
         so that indexes match up
769
         */
770
         future_crls.emplace_back(std::future<std::optional<X509_CRL>>());
×
771
      } else if(cert.crl_distribution_point().empty()) {
×
772
         // Avoid creating a thread for this case
773
         future_crls.emplace_back(std::async(std::launch::deferred, []() -> std::optional<X509_CRL> {
×
774
            throw Not_Implemented("No CRL distribution point for this certificate");
×
775
         }));
776
      } else {
777
         auto cdp = cert.crl_distribution_point();
×
778
         future_crls.emplace_back(std::async(std::launch::async, [cdp, timeout]() -> std::optional<X509_CRL> {
×
779
            auto http = HTTP::GET_sync(cdp,
×
780
                                       /*redirects*/ 1,
781
                                       timeout);
×
782

783
            http.throw_unless_ok();
×
784
            // check the mime type?
785
            return X509_CRL(http.body());
×
786
         }));
×
787
      }
×
788
   }
789

790
   for(size_t i = 0; i != future_crls.size(); ++i) {
×
791
      if(future_crls[i].valid()) {
×
792
         try {
×
793
            crls[i] = future_crls[i].get();
×
794
         } catch(std::exception&) {
×
795
            // crls[i] left null
796
            // todo: log exception e.what() ?
797
         }
×
798
      }
799
   }
800

801
   auto crl_status = PKIX::check_crl(cert_path, crls, ref_time);
×
802

803
   if(crl_store != nullptr) {
×
804
      for(size_t i = 0; i != crl_status.size(); ++i) {
×
805
         if(crl_status[i].contains(Certificate_Status_Code::VALID_CRL_CHECKED)) {
×
806
            // better be non-null, we supposedly validated it
807
            BOTAN_ASSERT_NOMSG(crls[i].has_value());
×
808
            crl_store->add_crl(*crls[i]);
×
809
         }
810
      }
811
   }
812

813
   return crl_status;
×
814
}
×
815

816
#endif
817

818
Certificate_Status_Code PKIX::build_certificate_path(std::vector<X509_Certificate>& cert_path,
5✔
819
                                                     const std::vector<Certificate_Store*>& trusted_certstores,
820
                                                     const X509_Certificate& end_entity,
821
                                                     const std::vector<X509_Certificate>& end_entity_extra) {
822
   CertificatePathBuilder builder(trusted_certstores, end_entity, end_entity_extra);
5✔
823

824
   std::vector<X509_Certificate> first_path;
5✔
825

826
   while(auto path = builder.next()) {
13✔
827
      BOTAN_ASSERT_NOMSG(path->empty() == false);
11✔
828

829
      // Prefer paths ending in self-signed certificates.
830
      if(path->back().is_self_signed()) {
11✔
831
         cert_path.insert(cert_path.end(), path->begin(), path->end());
3✔
832
         return Certificate_Status_Code::OK;
3✔
833
      }
834

835
      // Save the first path for later just in case we find nothing better
836
      if(first_path.empty()) {
8✔
837
         first_path = std::move(*path);
4✔
838
      }
839
   }
11✔
840

841
   if(!first_path.empty()) {
2✔
842
      // We found a path, it's not self-signed but it's as good as can be formed...
843
      cert_path.insert(cert_path.end(), first_path.begin(), first_path.end());
2✔
844
      return Certificate_Status_Code::OK;
2✔
845
   }
846

847
   // Failed to build any path at all
848
   return builder.error();
×
849
}
5✔
850

851
Certificate_Status_Code PKIX::build_all_certificate_paths(std::vector<std::vector<X509_Certificate>>& cert_paths_out,
5✔
852
                                                          const std::vector<Certificate_Store*>& trusted_certstores,
853
                                                          const X509_Certificate& end_entity,
854
                                                          const std::vector<X509_Certificate>& end_entity_extra) {
855
   if(!cert_paths_out.empty()) {
5✔
856
      throw Invalid_Argument("PKIX::build_all_certificate_paths: cert_paths_out must be empty");
×
857
   }
858
   CertificatePathBuilder builder(trusted_certstores, end_entity, end_entity_extra);
5✔
859

860
   while(auto path = builder.next()) {
16✔
861
      BOTAN_ASSERT_NOMSG(path->empty() == false);
11✔
862
      cert_paths_out.push_back(std::move(*path));
11✔
863
   }
11✔
864

865
   if(!cert_paths_out.empty()) {
5✔
866
      // Was able to generate at least one potential path
867
      return Certificate_Status_Code::OK;
868
   } else {
869
      // Could not construct any potentially valid path...
870
      return builder.error();
×
871
   }
872
}
5✔
873

874
void PKIX::merge_revocation_status(CertificatePathStatusCodes& chain_status,
2,821✔
875
                                   const CertificatePathStatusCodes& crl_status,
876
                                   const CertificatePathStatusCodes& ocsp_status,
877
                                   const Path_Validation_Restrictions& restrictions) {
878
   if(chain_status.empty()) {
2,821✔
879
      throw Invalid_Argument("PKIX::merge_revocation_status chain_status was empty");
×
880
   }
881

882
   for(size_t i = 0; i != chain_status.size() - 1; ++i) {
6,968✔
883
      bool had_crl = false;
4,147✔
884
      bool had_ocsp = false;
4,147✔
885

886
      if(i < crl_status.size() && !crl_status[i].empty()) {
4,147✔
887
         for(auto&& code : crl_status[i]) {
3,385✔
888
            if(code == Certificate_Status_Code::VALID_CRL_CHECKED) {
2,076✔
889
               had_crl = true;
1,275✔
890
            }
891
            chain_status[i].insert(code);
2,076✔
892
         }
893
      }
894

895
      if(i < ocsp_status.size() && !ocsp_status[i].empty()) {
4,147✔
896
         for(auto&& code : ocsp_status[i]) {
74✔
897
            // NO_REVOCATION_URL and OCSP_SERVER_NOT_AVAILABLE are softfail
898
            if(code == Certificate_Status_Code::OCSP_RESPONSE_GOOD ||
38✔
899
               code == Certificate_Status_Code::OCSP_NO_REVOCATION_URL ||
18✔
900
               code == Certificate_Status_Code::OCSP_SERVER_NOT_AVAILABLE) {
901
               had_ocsp = true;
20✔
902
            }
903

904
            chain_status[i].insert(code);
38✔
905
         }
906
      }
907

908
      if(had_crl == false && had_ocsp == false) {
4,147✔
909
         if((restrictions.require_revocation_information() && i == 0) ||
2,852✔
910
            (restrictions.ocsp_all_intermediates() && i > 0)) {
2,742✔
911
            chain_status[i].insert(Certificate_Status_Code::NO_REVOCATION_DATA);
177✔
912
         }
913
      }
914
   }
915
}
2,821✔
916

917
Certificate_Status_Code PKIX::overall_status(const CertificatePathStatusCodes& cert_status) {
7,541✔
918
   if(cert_status.empty()) {
7,541✔
919
      throw Invalid_Argument("PKIX::overall_status empty cert status");
×
920
   }
921

922
   Certificate_Status_Code overall_status = Certificate_Status_Code::OK;
923

924
   // take the "worst" error as overall
925
   for(const std::set<Certificate_Status_Code>& s : cert_status) {
27,590✔
926
      if(!s.empty()) {
20,049✔
927
         auto worst = *s.rbegin();
4,772✔
928
         // Leave informative OCSP/CRL confirmations on cert-level status only
929
         if(worst >= Certificate_Status_Code::FIRST_ERROR_STATUS && worst > overall_status) {
4,772✔
930
            overall_status = worst;
20,049✔
931
         }
932
      }
933
   }
934
   return overall_status;
7,541✔
935
}
936

937
Path_Validation_Result x509_path_validate(const std::vector<X509_Certificate>& end_certs,
4,043✔
938
                                          const Path_Validation_Restrictions& restrictions,
939
                                          const std::vector<Certificate_Store*>& trusted_roots,
940
                                          std::string_view hostname,
941
                                          Usage_Type usage,
942
                                          std::chrono::system_clock::time_point ref_time,
943
                                          std::chrono::milliseconds ocsp_timeout,
944
                                          const std::vector<std::optional<OCSP::Response>>& ocsp_resp) {
945
   if(end_certs.empty()) {
4,043✔
946
      throw Invalid_Argument("x509_path_validate called with no subjects");
×
947
   }
948

949
   const X509_Certificate& end_entity = end_certs[0];
4,043✔
950
   std::vector<X509_Certificate> end_entity_extra;
4,043✔
951
   for(size_t i = 1; i < end_certs.size(); ++i) {
7,027✔
952
      end_entity_extra.push_back(end_certs[i]);
2,984✔
953
   }
954

955
   const bool require_self_signed = restrictions.require_self_signed_trust_anchors();
4,043✔
956

957
   CertificatePathBuilder builder(trusted_roots, end_entity, end_entity_extra, require_self_signed);
4,043✔
958

959
   constexpr size_t max_paths = 50;
4,043✔
960
   constexpr size_t max_verifications = 200;
4,043✔
961

962
   std::optional<Path_Validation_Result> first_path_error;
4,043✔
963
   size_t paths_checked = 0;
4,043✔
964
   size_t certs_checked = 0;
4,043✔
965

966
   while(auto cert_path = builder.next()) {
5,896✔
967
      BOTAN_ASSERT_NOMSG(cert_path->empty() == false);
3,766✔
968

969
      paths_checked += 1;
3,766✔
970
      certs_checked += cert_path->size();
3,766✔
971
      if(paths_checked > max_paths || certs_checked > max_verifications) {
3,766✔
972
         first_path_error = Path_Validation_Result(Certificate_Status_Code::EXCEEDED_SEARCH_LIMITS);
×
973
         break;
×
974
      }
975

976
      CertificatePathStatusCodes status = PKIX::check_chain(*cert_path, ref_time, hostname, usage, restrictions);
3,766✔
977

978
      // Skip revocation checks if the chain already has fatal errors.
979
      if(PKIX::overall_status(status) < Certificate_Status_Code::FIRST_ERROR_STATUS_TO_SKIP_REVOCATION) {
3,766✔
980
         const CertificatePathStatusCodes crl_status = PKIX::check_crl(*cert_path, trusted_roots, ref_time);
2,821✔
981

982
         CertificatePathStatusCodes ocsp_status;
2,821✔
983

984
         if(!ocsp_resp.empty()) {
2,821✔
985
            ocsp_status = PKIX::check_ocsp(*cert_path, ocsp_resp, trusted_roots, ref_time, restrictions);
158✔
986
         }
987

988
         if(ocsp_status.empty() && ocsp_timeout != std::chrono::milliseconds(0)) {
2,821✔
989
#if defined(BOTAN_TARGET_OS_HAS_THREADS) && defined(BOTAN_HAS_HTTP_UTIL)
990
            ocsp_status = PKIX::check_ocsp_online(*cert_path, trusted_roots, ref_time, ocsp_timeout, restrictions);
9✔
991
#else
992
            ocsp_status.resize(1);
993
            ocsp_status[0].insert(Certificate_Status_Code::OCSP_NO_HTTP);
994
#endif
995
         }
996

997
         PKIX::merge_revocation_status(status, crl_status, ocsp_status, restrictions);
2,821✔
998
      }
2,821✔
999

1000
      Path_Validation_Result pvd(status, std::move(*cert_path));
3,766✔
1001
      if(pvd.successful_validation()) {
3,766✔
1002
         return pvd;
1,913✔
1003
      } else if(!first_path_error.has_value()) {
1,853✔
1004
         // Save the errors from the first path we attempted
1005
         first_path_error = std::move(pvd);
1,691✔
1006
      }
1007
   }
7,532✔
1008

1009
   if(first_path_error.has_value()) {
2,130✔
1010
      // We found at least one path, but none of them verified
1011
      // Return arbitrarily the error from the first path attempted
1012
      return first_path_error.value();
1,685✔
1013
   } else {
1014
      // Failed to build any path at all
1015
      return Path_Validation_Result(builder.error());
445✔
1016
   }
1017
}
6,841✔
1018

1019
Path_Validation_Result x509_path_validate(const X509_Certificate& end_cert,
283✔
1020
                                          const Path_Validation_Restrictions& restrictions,
1021
                                          const std::vector<Certificate_Store*>& trusted_roots,
1022
                                          std::string_view hostname,
1023
                                          Usage_Type usage,
1024
                                          std::chrono::system_clock::time_point when,
1025
                                          std::chrono::milliseconds ocsp_timeout,
1026
                                          const std::vector<std::optional<OCSP::Response>>& ocsp_resp) {
1027
   std::vector<X509_Certificate> certs;
283✔
1028
   certs.push_back(end_cert);
283✔
1029
   return x509_path_validate(certs, restrictions, trusted_roots, hostname, usage, when, ocsp_timeout, ocsp_resp);
566✔
1030
}
283✔
1031

1032
Path_Validation_Result x509_path_validate(const std::vector<X509_Certificate>& end_certs,
1,970✔
1033
                                          const Path_Validation_Restrictions& restrictions,
1034
                                          const Certificate_Store& store,
1035
                                          std::string_view hostname,
1036
                                          Usage_Type usage,
1037
                                          std::chrono::system_clock::time_point when,
1038
                                          std::chrono::milliseconds ocsp_timeout,
1039
                                          const std::vector<std::optional<OCSP::Response>>& ocsp_resp) {
1040
   std::vector<Certificate_Store*> trusted_roots;
1,970✔
1041
   trusted_roots.push_back(const_cast<Certificate_Store*>(&store));
1,970✔
1042

1043
   return x509_path_validate(end_certs, restrictions, trusted_roots, hostname, usage, when, ocsp_timeout, ocsp_resp);
1,970✔
1044
}
1,970✔
1045

1046
Path_Validation_Result x509_path_validate(const X509_Certificate& end_cert,
214✔
1047
                                          const Path_Validation_Restrictions& restrictions,
1048
                                          const Certificate_Store& store,
1049
                                          std::string_view hostname,
1050
                                          Usage_Type usage,
1051
                                          std::chrono::system_clock::time_point when,
1052
                                          std::chrono::milliseconds ocsp_timeout,
1053
                                          const std::vector<std::optional<OCSP::Response>>& ocsp_resp) {
1054
   std::vector<X509_Certificate> certs;
214✔
1055
   certs.push_back(end_cert);
214✔
1056

1057
   std::vector<Certificate_Store*> trusted_roots;
214✔
1058
   trusted_roots.push_back(const_cast<Certificate_Store*>(&store));
214✔
1059

1060
   return x509_path_validate(certs, restrictions, trusted_roots, hostname, usage, when, ocsp_timeout, ocsp_resp);
428✔
1061
}
214✔
1062

1063
Path_Validation_Restrictions::Path_Validation_Restrictions(bool require_rev,
2,188✔
1064
                                                           size_t key_strength,
1065
                                                           bool ocsp_intermediates,
1066
                                                           std::chrono::seconds max_ocsp_age,
1067
                                                           std::unique_ptr<Certificate_Store> trusted_ocsp_responders,
1068
                                                           bool ignore_trusted_root_time_range,
1069
                                                           bool require_self_signed_trust_anchors) :
2,188✔
1070
      m_require_revocation_information(require_rev),
2,188✔
1071
      m_ocsp_all_intermediates(ocsp_intermediates),
2,188✔
1072
      m_minimum_key_strength(key_strength),
2,188✔
1073
      m_max_ocsp_age(max_ocsp_age),
2,188✔
1074
      m_trusted_ocsp_responders(std::move(trusted_ocsp_responders)),
2,188✔
1075
      m_ignore_trusted_root_time_range(ignore_trusted_root_time_range),
2,188✔
1076
      m_require_self_signed_trust_anchors(require_self_signed_trust_anchors) {
2,188✔
1077
   if(key_strength <= 80) {
2,188✔
1078
      m_trusted_hashes.insert("SHA-1");
840✔
1079
   }
1080

1081
   m_trusted_hashes.insert("SHA-224");
4,376✔
1082
   m_trusted_hashes.insert("SHA-256");
4,376✔
1083
   m_trusted_hashes.insert("SHA-384");
4,376✔
1084
   m_trusted_hashes.insert("SHA-512");
4,376✔
1085
   m_trusted_hashes.insert("SHAKE-256(512)");  // Dilithium/ML-DSA
4,376✔
1086
   m_trusted_hashes.insert("SHAKE-256(912)");  // Ed448
4,376✔
1087
}
2,188✔
1088

1089
namespace {
1090
CertificatePathStatusCodes find_warnings(const CertificatePathStatusCodes& all_statuses) {
3,767✔
1091
   CertificatePathStatusCodes warnings;
3,767✔
1092
   for(const auto& status_set_i : all_statuses) {
13,789✔
1093
      std::set<Certificate_Status_Code> warning_set_i;
10,022✔
1094
      for(const auto& code : status_set_i) {
14,111✔
1095
         if(code >= Certificate_Status_Code::FIRST_WARNING_STATUS &&
4,089✔
1096
            code < Certificate_Status_Code::FIRST_ERROR_STATUS) {
1097
            warning_set_i.insert(code);
117✔
1098
         }
1099
      }
1100
      warnings.push_back(warning_set_i);
10,022✔
1101
   }
10,022✔
1102
   return warnings;
3,767✔
1103
}
×
1104
}  // namespace
1105

1106
Path_Validation_Result::Path_Validation_Result(CertificatePathStatusCodes status,
3,767✔
1107
                                               std::vector<X509_Certificate>&& cert_chain) :
3,767✔
1108
      m_all_status(std::move(status)),
3,767✔
1109
      m_warnings(find_warnings(m_all_status)),
3,767✔
1110
      m_cert_path(std::move(cert_chain)),
3,767✔
1111
      m_overall(PKIX::overall_status(m_all_status)) {}
3,767✔
1112

1113
const X509_Certificate& Path_Validation_Result::trust_root() const {
22✔
1114
   if(m_cert_path.empty()) {
22✔
1115
      throw Invalid_State("Path_Validation_Result::trust_root no path set");
×
1116
   }
1117
   if(result() != Certificate_Status_Code::VERIFIED) {
22✔
1118
      throw Invalid_State("Path_Validation_Result::trust_root meaningless with invalid status");
×
1119
   }
1120

1121
   return m_cert_path[m_cert_path.size() - 1];
22✔
1122
}
1123

1124
bool Path_Validation_Result::successful_validation() const {
5,543✔
1125
   return (result() == Certificate_Status_Code::VERIFIED || result() == Certificate_Status_Code::OCSP_RESPONSE_GOOD ||
5,543✔
1126
           result() == Certificate_Status_Code::VALID_CRL_CHECKED);
2,236✔
1127
}
1128

1129
bool Path_Validation_Result::no_warnings() const {
76✔
1130
   for(const auto& status_set_i : m_warnings) {
302✔
1131
      if(!status_set_i.empty()) {
228✔
1132
         return false;
76✔
1133
      }
1134
   }
1135
   return true;
1136
}
1137

1138
CertificatePathStatusCodes Path_Validation_Result::warnings() const {
74✔
1139
   return m_warnings;
74✔
1140
}
1141

1142
std::string Path_Validation_Result::result_string() const {
2,211✔
1143
   return status_string(result());
2,211✔
1144
}
1145

1146
const char* Path_Validation_Result::status_string(Certificate_Status_Code code) {
2,252✔
1147
   if(const char* s = to_string(code)) {
2,252✔
1148
      return s;
2,252✔
1149
   }
1150

1151
   return "Unknown error";
1152
}
1153

1154
std::string Path_Validation_Result::warnings_string() const {
76✔
1155
   const std::string sep(", ");
76✔
1156
   std::ostringstream oss;
76✔
1157
   for(size_t i = 0; i < m_warnings.size(); i++) {
306✔
1158
      for(auto code : m_warnings[i]) {
232✔
1159
         oss << "[" << std::to_string(i) << "] " << status_string(code) << sep;
4✔
1160
      }
1161
   }
1162

1163
   std::string res = oss.str();
76✔
1164
   // remove last sep
1165
   if(res.size() >= sep.size()) {
76✔
1166
      res = res.substr(0, res.size() - sep.size());
2✔
1167
   }
1168
   return res;
152✔
1169
}
76✔
1170
}  // namespace Botan
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