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

randombit / botan / 24063525848

06 Apr 2026 10:36PM UTC coverage: 89.448% (-0.007%) from 89.455%
24063525848

push

github

web-flow
Merge pull request #5521 from randombit/jack/fix-rollup

Rollup of small fixes

105878 of 118368 relevant lines covered (89.45%)

11475460.89 hits per line

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

86.58
/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,
2,872✔
53
                             const X509_Certificate& end_entity,
54
                             const std::vector<X509_Certificate>& end_entity_extra,
55
                             bool require_self_signed = false) :
2,872✔
56
            m_trusted_certstores(trusted_certstores), m_require_self_signed(require_self_signed) {
2,872✔
57
         if(std::ranges::any_of(trusted_certstores, [](auto* ptr) { return ptr == nullptr; })) {
5,744✔
58
            throw Invalid_Argument("Certificate store list must not contain nullptr");
×
59
         }
60

61
         for(const auto& cert : end_entity_extra) {
5,800✔
62
            if(!cert_in_any_trusted_store(cert)) {
5,856✔
63
               m_ee_extras.add_certificate(cert);
2,675✔
64
            }
65
         }
66

67
         m_stack.push_back({end_entity, cert_in_any_trusted_store(end_entity)});
2,872✔
68
      }
2,872✔
69

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

73
         while(!m_stack.empty()) {
17,650✔
74
            constexpr size_t MAX_DFS_STEPS = 1000;
15,522✔
75

76
            steps++;
15,522✔
77

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

84
            auto [last, trusted] = std::move(m_stack.back());  // move before pop_back
25,475✔
85
            m_stack.pop_back();
15,522✔
86

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

94
            // Certificate already seen in this path?
95
            const auto tag = last->tag();
9,953✔
96
            if(m_certs_seen.contains(tag)) {
9,953✔
97
               if(!m_error.has_value()) {
1,845✔
98
                  m_error = Certificate_Status_Code::CERT_CHAIN_LOOP;
1,683✔
99
               }
100
               continue;
1,845✔
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) {
8,108✔
108
               auto path = m_path_so_far;
2,750✔
109
               path.push_back(*last);
2,750✔
110
               push_issuers(*last);
2,750✔
111

112
               if(!m_require_self_signed || last->is_self_signed()) {
2,750✔
113
                  return path;
2,603✔
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
            }
2,750✔
124

125
            if(last->is_self_signed()) {
5,358✔
126
               if(!m_error.has_value()) {
250✔
127
                  m_error = Certificate_Status_Code::CANNOT_ESTABLISH_TRUST;
249✔
128
               }
129
               continue;
250✔
130
            }
131

132
            push_issuers(*last);
5,108✔
133
         }
15,522✔
134

135
         return std::nullopt;
2,128✔
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 {
449✔
144
         if(m_error.has_value()) {
449✔
145
            // Confirm it is an actual error code and not accidentally OK...
146
            BOTAN_ASSERT_NOMSG(static_cast<uint32_t>(m_error.value()) >= 3000);
449✔
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 {
5,800✔
155
         return std::ranges::any_of(m_trusted_certstores,
11,600✔
156
                                    [&](const Certificate_Store* store) { return store->contains(cert); });
5,530✔
157
      }
158

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

163
         // Search for trusted issuers
164
         std::vector<X509_Certificate> trusted_issuers;
7,858✔
165
         for(const Certificate_Store* store : m_trusted_certstores) {
15,671✔
166
            auto new_issuers = store->find_all_certs(issuer_dn, auth_key_id);
7,813✔
167
            trusted_issuers.insert(trusted_issuers.end(), new_issuers.begin(), new_issuers.end());
7,813✔
168
         }
7,813✔
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);
7,858✔
172

173
         // If we could not find any issuers, the current path ends here
174
         if(trusted_issuers.empty() && misc_issuers.empty()) {
7,858✔
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);
7,638✔
182
         m_certs_seen.emplace(cert.tag());
7,638✔
183

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

187
         for(const auto& trusted_cert : trusted_issuers) {
12,953✔
188
            m_stack.push_back({trusted_cert, true});
10,630✔
189
         }
190
         for(const auto& misc : misc_issuers) {
10,203✔
191
            m_stack.push_back({misc, false});
5,130✔
192
         }
193
      }
7,858✔
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,
2,584✔
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()) {
2,584✔
215
      throw Invalid_Argument("PKIX::check_chain cert_path empty");
×
216
   }
217

218
   const bool is_end_entity_trust_anchor = (cert_path.size() == 1);
2,584✔
219

220
   const X509_Time validation_time(ref_time);
2,584✔
221

222
   CertificatePathStatusCodes cert_status(cert_path.size());
2,584✔
223

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

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

230
      const X509_Certificate& subject = cert_path[i];
7,647✔
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()) {
7,647✔
236
         continue;
5✔
237
      }
238

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

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

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

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

259
            if(sig_status.first != Certificate_Status_Code::VERIFIED) {
7,610✔
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;
7,208✔
264
               BOTAN_ASSERT_NOMSG(!hash_used_for_signature.empty());
7,208✔
265
               const auto& trusted_hashes = restrictions.trusted_hashes();
7,208✔
266

267
               // Ignore untrusted hashes on self-signed roots
268
               if(!trusted_hashes.empty() && !at_trust_anchor) {
7,208✔
269
                  if(!trusted_hashes.contains(hash_used_for_signature)) {
4,630✔
270
                     status.insert(Certificate_Status_Code::UNTRUSTED_HASH);
100✔
271
                  }
272
               }
273
            }
7,208✔
274
         }
7,610✔
275
      }
7,647✔
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) {
9,203✔
285
      for(auto status : cert_status.at(i)) {
7,091✔
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;
2,584✔
290
         }
291
      }
292
   }
293

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

298
   if(!cert_path[0].allowed_usage(usage)) {
2,276✔
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) {
2,276✔
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) {
8,837✔
318
      std::set<Certificate_Status_Code>& status = cert_status.at(i);
6,561✔
319

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

322
      const X509_Certificate& subject = cert_path[i];
6,561✔
323
      const auto issuer = [&]() -> std::optional<X509_Certificate> {
19,683✔
324
         if(!at_trust_anchor) {
6,561✔
325
            return cert_path[i + 1];
4,285✔
326
         } else if(subject.is_self_signed()) {
2,276✔
327
            return cert_path[i];
2,271✔
328
         } else {
329
            return {};  // Non self-signed trust anchors have no checkable issuers.
5✔
330
         }
331
      }();
6,561✔
332

333
      if(restrictions.require_self_signed_trust_anchors() && !issuer.has_value()) {
6,561✔
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()) {
6,561✔
339
         status.insert(Certificate_Status_Code::CHAIN_NAME_MISMATCH);
×
340
      }
341

342
      // Check the serial number
343
      if(subject.is_serial_negative()) {
6,561✔
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()) {
24,705✔
350
         const size_t dn_ub = X509_DN::lookup_ub(dn_pair.first);
18,144✔
351
         // dn_pair = <OID,str>
352
         if(dn_ub > 0 && dn_pair.second.size() > dn_ub) {
18,144✔
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();
6,561✔
360
      // Check all certs for valid time range
361
      if(validation_time < subject.not_before()) {
6,561✔
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()) {
6,561✔
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) {
6,561✔
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) {
6,561✔
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();
6,561✔
391
      const auto& extensions_vec = extensions.extensions();
6,561✔
392
      if(subject.x509_version() < 3 && !extensions_vec.empty()) {
6,561✔
393
         status.insert(Certificate_Status_Code::EXT_IN_V1_V2_CERT);
64✔
394
      }
395

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

400
      if(extensions_vec.size() != extensions.get_extension_oids().size()) {
6,561✔
401
         status.insert(Certificate_Status_Code::DUPLICATE_CERT_EXTENSION);
32✔
402
      }
403
   }
13,117✔
404

405
   // path len check
406
   size_t max_path_length = cert_path.size();
2,276✔
407
   for(size_t i = cert_path.size() - 1; i > 0; --i) {
6,561✔
408
      std::set<Certificate_Status_Code>& status = cert_status.at(i);
4,285✔
409
      const X509_Certificate& subject = cert_path[i];
4,285✔
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()) {
4,285✔
416
         if(max_path_length > 0) {
1,899✔
417
            max_path_length -= 1;
1,821✔
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()) {
4,285✔
428
         max_path_length = std::min(max_path_length, *path_len_constraint);
5,474✔
429
      }
430
   }
431

432
   return cert_status;
433
}
2,584✔
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()->contains(signing_cert)) {
43✔
451
      return Certificate_Status_Code::OK;
452
   }
453

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

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

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

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

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

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

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

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

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

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

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

547
}  // namespace
548

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

558
   CertificatePathStatusCodes cert_status(cert_path.size() - 1);
57✔
559

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

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

575
   while(!cert_status.empty() && cert_status.back().empty()) {
102✔
576
      cert_status.pop_back();
45✔
577
   }
578

579
   return cert_status;
57✔
580
}
×
581

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

589
   CertificatePathStatusCodes cert_status(cert_path.size());
1,655✔
590
   const X509_Time validation_time(ref_time);
1,655✔
591

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

595
      if(i < crls.size() && crls[i].has_value()) {
2,980✔
596
         const X509_Certificate& subject = cert_path.at(i);
1,316✔
597
         const X509_Certificate& ca = cert_path.at(i + 1);
1,316✔
598

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

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

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

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

616
         status.insert(Certificate_Status_Code::VALID_CRL_CHECKED);
1,316✔
617

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

622
         const auto dp = subject.crl_distribution_points();
1,316✔
623
         if(!dp.empty()) {
1,316✔
624
            const auto crl_idp = crls[i]->crl_issuing_distribution_point();
416✔
625

626
            if(std::find(dp.begin(), dp.end(), crl_idp) == dp.end()) {
416✔
627
               status.insert(Certificate_Status_Code::NO_MATCHING_CRLDP);
416✔
628
            }
629
         }
416✔
630

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

645
   while(!cert_status.empty() && cert_status.back().empty()) {
4,935✔
646
      cert_status.pop_back();
3,280✔
647
   }
648

649
   return cert_status;
1,655✔
650
}
1,655✔
651

652
CertificatePathStatusCodes PKIX::check_crl(const std::vector<X509_Certificate>& cert_path,
1,649✔
653
                                           const std::vector<Certificate_Store*>& certstores,
654
                                           std::chrono::system_clock::time_point ref_time) {
655
   if(cert_path.empty()) {
1,649✔
656
      throw Invalid_Argument("PKIX::check_crl cert_path empty");
×
657
   }
658

659
   if(certstores.empty()) {
1,649✔
660
      throw Invalid_Argument("PKIX::check_crl certstores empty");
×
661
   }
662

663
   std::vector<std::optional<X509_CRL>> crls(cert_path.size());
1,649✔
664

665
   for(size_t i = 0; i != cert_path.size(); ++i) {
6,272✔
666
      for(auto* certstore : certstores) {
7,275✔
667
         crls[i] = certstore->find_crl_for(cert_path[i]);
4,667✔
668
         if(crls[i]) {
4,667✔
669
            break;
670
         }
671
      }
672
   }
673

674
   return PKIX::check_crl(cert_path, crls, ref_time);
3,298✔
675
}
1,649✔
676

677
#if defined(BOTAN_HAS_ONLINE_REVOCATION_CHECKS)
678

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

688
   std::vector<std::future<std::optional<OCSP::Response>>> ocsp_response_futures;
10✔
689

690
   size_t to_ocsp = 1;
10✔
691

692
   if(restrictions.ocsp_all_intermediates()) {
10✔
693
      to_ocsp = cert_path.size() - 1;
×
694
   }
695
   if(cert_path.size() == 1) {
10✔
696
      to_ocsp = 0;
×
697
   }
698

699
   for(size_t i = 0; i < to_ocsp; ++i) {
20✔
700
      const auto& subject = cert_path.at(i);
10✔
701
      const auto& issuer = cert_path.at(i + 1);
10✔
702

703
      if(subject.ocsp_responder().empty()) {
10✔
704
         ocsp_response_futures.emplace_back(std::async(std::launch::deferred, []() -> std::optional<OCSP::Response> {
18✔
705
            return OCSP::Response(Certificate_Status_Code::OCSP_NO_REVOCATION_URL);
18✔
706
         }));
707
      } else {
708
         auto ocsp_url = subject.ocsp_responder();
1✔
709
         auto ocsp_req = OCSP::Request(issuer, BigInt::from_bytes(subject.serial_number()));
1✔
710
         ocsp_response_futures.emplace_back(
2✔
711
            std::async(std::launch::async, [ocsp_url, ocsp_req, timeout]() -> std::optional<OCSP::Response> {
3✔
712
               HTTP::Response http;
1✔
713
               try {
1✔
714
                  http = HTTP::POST_sync(ocsp_url,
2✔
715
                                         "application/ocsp-request",
716
                                         ocsp_req.BER_encode(),
2✔
717
                                         /*redirects*/ 1,
718
                                         timeout);
1✔
719
               } catch(std::exception&) {
×
720
                  // log e.what() ?
721
               }
×
722
               if(http.status_code() != 200) {
1✔
723
                  return OCSP::Response(Certificate_Status_Code::OCSP_SERVER_NOT_AVAILABLE);
×
724
               }
725
               // Check the MIME type?
726

727
               return OCSP::Response(http.body());
1✔
728
            }));
1✔
729
      }
1✔
730
   }
731

732
   std::vector<std::optional<OCSP::Response>> ocsp_responses;
10✔
733
   ocsp_responses.reserve(ocsp_response_futures.size());
10✔
734

735
   for(auto& ocsp_response_future : ocsp_response_futures) {
20✔
736
      ocsp_responses.push_back(ocsp_response_future.get());
30✔
737
   }
738

739
   return PKIX::check_ocsp(cert_path, ocsp_responses, trusted_certstores, ref_time, restrictions);
20✔
740
}
10✔
741

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

754
   std::vector<std::future<std::optional<X509_CRL>>> future_crls;
×
755
   std::vector<std::optional<X509_CRL>> crls(cert_path.size());
×
756

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

766
      // TODO: check if CRL is expired and re-request?
767

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

787
            http.throw_unless_ok();
×
788
            // check the mime type?
789
            return X509_CRL(http.body());
×
790
         }));
×
791
      }
×
792
   }
793

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

805
   auto crl_status = PKIX::check_crl(cert_path, crls, ref_time);
×
806

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

817
   return crl_status;
×
818
}
×
819

820
#endif
821

822
Certificate_Status_Code PKIX::build_certificate_path(std::vector<X509_Certificate>& cert_path,
5✔
823
                                                     const std::vector<Certificate_Store*>& trusted_certstores,
824
                                                     const X509_Certificate& end_entity,
825
                                                     const std::vector<X509_Certificate>& end_entity_extra) {
826
   CertificatePathBuilder builder(trusted_certstores, end_entity, end_entity_extra);
5✔
827

828
   std::vector<X509_Certificate> first_path;
5✔
829

830
   while(auto path = builder.next()) {
13✔
831
      BOTAN_ASSERT_NOMSG(path->empty() == false);
11✔
832

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

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

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

851
   // Failed to build any path at all
852
   return builder.error();
×
853
}
5✔
854

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

864
   while(auto path = builder.next()) {
16✔
865
      BOTAN_ASSERT_NOMSG(path->empty() == false);
11✔
866
      cert_paths_out.push_back(std::move(*path));
11✔
867
   }
11✔
868

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

878
void PKIX::merge_revocation_status(CertificatePathStatusCodes& chain_status,
1,649✔
879
                                   const CertificatePathStatusCodes& crl_status,
880
                                   const CertificatePathStatusCodes& ocsp_status,
881
                                   const Path_Validation_Restrictions& restrictions) {
882
   if(chain_status.empty()) {
1,649✔
883
      throw Invalid_Argument("PKIX::merge_revocation_status chain_status was empty");
×
884
   }
885

886
   for(size_t i = 0; i != chain_status.size() - 1; ++i) {
4,623✔
887
      bool had_crl = false;
2,974✔
888
      bool had_ocsp = false;
2,974✔
889

890
      if(i < crl_status.size() && !crl_status[i].empty()) {
2,974✔
891
         for(auto&& code : crl_status[i]) {
3,453✔
892
            if(code == Certificate_Status_Code::VALID_CRL_CHECKED) {
2,143✔
893
               had_crl = true;
1,310✔
894
            }
895
            chain_status[i].insert(code);
2,143✔
896
         }
897
      }
898

899
      if(i < ocsp_status.size() && !ocsp_status[i].empty()) {
2,974✔
900
         for(auto&& code : ocsp_status[i]) {
74✔
901
            // NO_REVOCATION_URL and OCSP_SERVER_NOT_AVAILABLE are softfail
902
            if(code == Certificate_Status_Code::OCSP_RESPONSE_GOOD ||
38✔
903
               code == Certificate_Status_Code::OCSP_NO_REVOCATION_URL ||
18✔
904
               code == Certificate_Status_Code::OCSP_SERVER_NOT_AVAILABLE) {
905
               had_ocsp = true;
20✔
906
            }
907

908
            chain_status[i].insert(code);
38✔
909
         }
910
      }
911

912
      if(had_crl == false && had_ocsp == false) {
2,974✔
913
         if((restrictions.require_revocation_information() && i == 0) ||
1,644✔
914
            (restrictions.ocsp_all_intermediates() && i > 0)) {
1,568✔
915
            chain_status[i].insert(Certificate_Status_Code::NO_REVOCATION_DATA);
143✔
916
         }
917
      }
918
   }
919
}
1,649✔
920

921
Certificate_Status_Code PKIX::overall_status(const CertificatePathStatusCodes& cert_status) {
5,171✔
922
   if(cert_status.empty()) {
5,171✔
923
      throw Invalid_Argument("PKIX::overall_status empty cert status");
×
924
   }
925

926
   Certificate_Status_Code overall_status = Certificate_Status_Code::OK;
927

928
   // take the "worst" error as overall
929
   for(const std::set<Certificate_Status_Code>& s : cert_status) {
20,466✔
930
      if(!s.empty()) {
15,295✔
931
         auto worst = *s.rbegin();
4,747✔
932
         // Leave informative OCSP/CRL confirmations on cert-level status only
933
         if(worst >= Certificate_Status_Code::FIRST_ERROR_STATUS && worst > overall_status) {
4,747✔
934
            overall_status = worst;
15,295✔
935
         }
936
      }
937
   }
938
   return overall_status;
5,171✔
939
}
940

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

953
   const X509_Certificate& end_entity = end_certs[0];
2,862✔
954
   std::vector<X509_Certificate> end_entity_extra;
2,862✔
955
   for(size_t i = 1; i < end_certs.size(); ++i) {
5,740✔
956
      end_entity_extra.push_back(end_certs[i]);
2,878✔
957
   }
958

959
   const bool require_self_signed = restrictions.require_self_signed_trust_anchors();
2,862✔
960

961
   CertificatePathBuilder builder(trusted_roots, end_entity, end_entity_extra, require_self_signed);
2,862✔
962

963
   constexpr size_t max_paths = 50;
2,862✔
964
   constexpr size_t max_verifications = 200;
2,862✔
965

966
   std::optional<Path_Validation_Result> first_path_error;
2,862✔
967
   size_t paths_checked = 0;
2,862✔
968
   size_t certs_checked = 0;
2,862✔
969

970
   while(auto cert_path = builder.next()) {
4,702✔
971
      BOTAN_ASSERT_NOMSG(cert_path->empty() == false);
2,581✔
972

973
      paths_checked += 1;
2,581✔
974
      certs_checked += cert_path->size();
2,581✔
975
      if(paths_checked > max_paths || certs_checked > max_verifications) {
2,581✔
976
         break;
977
      }
978

979
      CertificatePathStatusCodes status = PKIX::check_chain(*cert_path, ref_time, hostname, usage, restrictions);
2,581✔
980

981
      // Skip revocation checks if the chain already has fatal errors.
982
      if(PKIX::overall_status(status) < Certificate_Status_Code::FIRST_ERROR_STATUS_TO_SKIP_REVOCATION) {
2,581✔
983
         const CertificatePathStatusCodes crl_status = PKIX::check_crl(*cert_path, trusted_roots, ref_time);
1,649✔
984

985
         CertificatePathStatusCodes ocsp_status;
1,649✔
986

987
         if(!ocsp_resp.empty()) {
1,649✔
988
            ocsp_status = PKIX::check_ocsp(*cert_path, ocsp_resp, trusted_roots, ref_time, restrictions);
30✔
989
         }
990

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

1000
         PKIX::merge_revocation_status(status, crl_status, ocsp_status, restrictions);
1,649✔
1001
      }
1,649✔
1002

1003
      Path_Validation_Result pvd(status, std::move(*cert_path));
2,581✔
1004
      if(pvd.successful_validation()) {
2,581✔
1005
         return pvd;
741✔
1006
      } else if(!first_path_error.has_value()) {
1,840✔
1007
         // Save the errors from the first path we attempted
1008
         first_path_error = std::move(pvd);
1,678✔
1009
      }
1010
   }
5,162✔
1011

1012
   if(first_path_error.has_value()) {
2,121✔
1013
      // We found at least one path, but none of them verified
1014
      // Return arbitrarily the error from the first path attempted
1015
      return first_path_error.value();
1,672✔
1016
   } else {
1017
      // Failed to build any path at all
1018
      return Path_Validation_Result(builder.error());
449✔
1019
   }
1020
}
4,488✔
1021

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

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

1046
   return x509_path_validate(end_certs, restrictions, trusted_roots, hostname, usage, when, ocsp_timeout, ocsp_resp);
1,951✔
1047
}
1,951✔
1048

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

1060
   std::vector<Certificate_Store*> trusted_roots;
214✔
1061
   trusted_roots.push_back(const_cast<Certificate_Store*>(&store));
214✔
1062

1063
   return x509_path_validate(certs, restrictions, trusted_roots, hostname, usage, when, ocsp_timeout, ocsp_resp);
428✔
1064
}
214✔
1065

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

1084
   m_trusted_hashes.insert("SHA-224");
2,048✔
1085
   m_trusted_hashes.insert("SHA-256");
2,048✔
1086
   m_trusted_hashes.insert("SHA-384");
2,048✔
1087
   m_trusted_hashes.insert("SHA-512");
2,048✔
1088
   m_trusted_hashes.insert("SHAKE-256(512)");  // Dilithium/ML-DSA
2,048✔
1089
   m_trusted_hashes.insert("SHAKE-256(912)");  // Ed448
2,048✔
1090
}
1,024✔
1091

1092
namespace {
1093
CertificatePathStatusCodes find_warnings(const CertificatePathStatusCodes& all_statuses) {
2,582✔
1094
   CertificatePathStatusCodes warnings;
2,582✔
1095
   for(const auto& status_set_i : all_statuses) {
10,227✔
1096
      std::set<Certificate_Status_Code> warning_set_i;
7,645✔
1097
      for(const auto& code : status_set_i) {
11,754✔
1098
         if(code >= Certificate_Status_Code::FIRST_WARNING_STATUS &&
4,109✔
1099
            code < Certificate_Status_Code::FIRST_ERROR_STATUS) {
1100
            warning_set_i.insert(code);
117✔
1101
         }
1102
      }
1103
      warnings.push_back(warning_set_i);
7,645✔
1104
   }
7,645✔
1105
   return warnings;
2,582✔
1106
}
×
1107
}  // namespace
1108

1109
Path_Validation_Result::Path_Validation_Result(CertificatePathStatusCodes status,
2,582✔
1110
                                               std::vector<X509_Certificate>&& cert_chain) :
2,582✔
1111
      m_all_status(std::move(status)),
2,582✔
1112
      m_warnings(find_warnings(m_all_status)),
2,582✔
1113
      m_cert_path(std::move(cert_chain)),
2,582✔
1114
      m_overall(PKIX::overall_status(m_all_status)) {}
2,582✔
1115

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

1124
   return m_cert_path[m_cert_path.size() - 1];
22✔
1125
}
1126

1127
bool Path_Validation_Result::successful_validation() const {
3,196✔
1128
   return (result() == Certificate_Status_Code::VERIFIED || result() == Certificate_Status_Code::OCSP_RESPONSE_GOOD ||
3,196✔
1129
           result() == Certificate_Status_Code::VALID_CRL_CHECKED);
2,227✔
1130
}
1131

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

1141
CertificatePathStatusCodes Path_Validation_Result::warnings() const {
74✔
1142
   return m_warnings;
74✔
1143
}
1144

1145
std::string Path_Validation_Result::result_string() const {
2,196✔
1146
   return status_string(result());
2,196✔
1147
}
1148

1149
const char* Path_Validation_Result::status_string(Certificate_Status_Code code) {
2,237✔
1150
   if(const char* s = to_string(code)) {
2,237✔
1151
      return s;
2,237✔
1152
   }
1153

1154
   return "Unknown error";
1155
}
1156

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

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