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

randombit / botan / 17643124989

11 Sep 2025 11:29AM UTC coverage: 90.702% (+0.04%) from 90.667%
17643124989

Pull #5047

github

web-flow
Merge 17a2ff8cf into 0d4cd13de
Pull Request #5047: X.509 Path: Option for Non-Self-Signed Trust Anchors

100577 of 110887 relevant lines covered (90.7%)

12132100.7 hits per line

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

86.18
/src/lib/x509/x509path.cpp
1
/*
2
* X.509 Certificate Path Validation
3
* (C) 2010,2011,2012,2014,2016 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/ocsp.h>
12
#include <botan/pk_keys.h>
13
#include <botan/x509_ext.h>
14
#include <botan/internal/stl_util.h>
15
#include <algorithm>
16
#include <chrono>
17
#include <set>
18
#include <sstream>
19
#include <string>
20
#include <vector>
21

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

27
#if defined(BOTAN_HAS_ECC_KEY)
28
   #include <botan/ecc_key.h>
29
#endif
30

31
namespace Botan {
32

33
/*
34
* PKIX path validation
35
*/
36
CertificatePathStatusCodes PKIX::check_chain(const std::vector<X509_Certificate>& cert_path,
2,403✔
37
                                             std::chrono::system_clock::time_point ref_time,
38
                                             std::string_view hostname,
39
                                             Usage_Type usage,
40
                                             const Path_Validation_Restrictions& restrictions) {
41
   if(cert_path.empty()) {
2,403✔
42
      throw Invalid_Argument("PKIX::check_chain cert_path empty");
×
43
   }
44

45
   const bool is_end_entity_trust_anchor = (cert_path.size() == 1);
2,403✔
46

47
   X509_Time validation_time(ref_time);
2,403✔
48

49
   CertificatePathStatusCodes cert_status(cert_path.size());
2,403✔
50

51
   // Before anything else verify the entire chain of signatures
52
   for(size_t i = 0; i != cert_path.size(); ++i) {
9,336✔
53
      std::set<Certificate_Status_Code>& status = cert_status.at(i);
6,933✔
54

55
      const bool at_trust_anchor = (i == cert_path.size() - 1);
6,933✔
56

57
      const X509_Certificate& subject = cert_path[i];
6,933✔
58

59
      // If using intermediate CAs as trust anchors, the signature of the trust
60
      // anchor cannot be verified since the issuer is not part of the
61
      // certificate chain
62
      if(!restrictions.require_self_signed_trust_anchors() && at_trust_anchor && !subject.is_self_signed()) {
6,933✔
63
         continue;
5✔
64
      }
65

66
      const X509_Certificate& issuer = cert_path[at_trust_anchor ? (i) : (i + 1)];
6,928✔
67

68
      // Check the signature algorithm is known
69
      if(!subject.signature_algorithm().oid().registered_oid()) {
6,928✔
70
         status.insert(Certificate_Status_Code::SIGNATURE_ALGO_UNKNOWN);
32✔
71
      } else {
72
         std::unique_ptr<Public_Key> issuer_key;
6,896✔
73
         try {
6,896✔
74
            issuer_key = issuer.subject_public_key();
6,896✔
75
         } catch(...) {
×
76
            status.insert(Certificate_Status_Code::CERT_PUBKEY_INVALID);
×
77
         }
×
78

79
         if(issuer_key) {
6,896✔
80
            if(issuer_key->estimated_strength() < restrictions.minimum_key_strength()) {
6,896✔
81
               status.insert(Certificate_Status_Code::SIGNATURE_METHOD_TOO_WEAK);
65✔
82
            }
83

84
            const auto sig_status = subject.verify_signature(*issuer_key);
6,896✔
85

86
            if(sig_status.first != Certificate_Status_Code::VERIFIED) {
6,896✔
87
               status.insert(sig_status.first);
108✔
88
            } else {
89
               // Signature is valid, check if hash used was acceptable
90
               const std::string hash_used_for_signature = sig_status.second;
6,788✔
91
               BOTAN_ASSERT_NOMSG(!hash_used_for_signature.empty());
6,788✔
92
               const auto& trusted_hashes = restrictions.trusted_hashes();
6,788✔
93

94
               // Ignore untrusted hashes on self-signed roots
95
               if(!trusted_hashes.empty() && !at_trust_anchor) {
6,788✔
96
                  if(!trusted_hashes.contains(hash_used_for_signature)) {
4,391✔
97
                     status.insert(Certificate_Status_Code::UNTRUSTED_HASH);
100✔
98
                  }
99
               }
100
            }
6,788✔
101
         }
6,896✔
102
      }
6,933✔
103
   }
104

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

111
   for(size_t i = 0; i != cert_path.size(); ++i) {
8,959✔
112
      for(auto status : cert_status.at(i)) {
6,860✔
113
         // This ignores errors relating to the key or hash being weak since
114
         // these are somewhat advisory
115
         if(static_cast<uint32_t>(status) >= 5000) {
304✔
116
            return cert_status;
2,403✔
117
         }
118
      }
119
   }
120

121
   if(!hostname.empty() && !cert_path[0].matches_dns_name(hostname)) {
2,263✔
122
      cert_status[0].insert(Certificate_Status_Code::CERT_NAME_NOMATCH);
27✔
123
   }
124

125
   if(!cert_path[0].allowed_usage(usage)) {
2,263✔
126
      if(usage == Usage_Type::OCSP_RESPONDER) {
5✔
127
         cert_status[0].insert(Certificate_Status_Code::OCSP_RESPONSE_MISSING_KEYUSAGE);
1✔
128
      }
129
      cert_status[0].insert(Certificate_Status_Code::INVALID_USAGE);
5✔
130
   }
131

132
   if(cert_path[0].has_constraints(Key_Constraints::KeyCertSign) && cert_path[0].is_CA_cert() == false) {
2,263✔
133
      /*
134
      "If the keyCertSign bit is asserted, then the cA bit in the
135
      basic constraints extension (Section 4.2.1.9) MUST also be
136
      asserted." - RFC 5280
137

138
      We don't bother doing this check on the rest of the path since they
139
      must have the cA bit asserted or the validation will fail anyway.
140
      */
141
      cert_status[0].insert(Certificate_Status_Code::INVALID_USAGE);
×
142
   }
143

144
   for(size_t i = 0; i != cert_path.size(); ++i) {
8,782✔
145
      std::set<Certificate_Status_Code>& status = cert_status.at(i);
6,519✔
146

147
      const bool at_trust_anchor = (i == cert_path.size() - 1);
6,519✔
148

149
      const X509_Certificate& subject = cert_path[i];
6,519✔
150
      const auto issuer = [&]() -> std::optional<X509_Certificate> {
19,557✔
151
         if(!at_trust_anchor) {
6,519✔
152
            return cert_path[i + 1];
4,256✔
153
         } else if(subject.is_self_signed()) {
2,263✔
154
            return cert_path[i];
2,258✔
155
         } else {
156
            return {};  // Non self-signed trust anchors have no checkable issuers.
5✔
157
         }
158
      }();
6,519✔
159

160
      if(restrictions.require_self_signed_trust_anchors() && !issuer.has_value()) {
13,038✔
161
         status.insert(Certificate_Status_Code::CHAIN_LACKS_TRUST_ROOT);
×
162
      }
163

164
      // This should never happen; it indicates a bug in path building
165
      if(issuer.has_value() && subject.issuer_dn() != issuer->subject_dn()) {
6,519✔
166
         status.insert(Certificate_Status_Code::CHAIN_NAME_MISMATCH);
×
167
      }
168

169
      // Check the serial number
170
      if(subject.is_serial_negative()) {
6,519✔
171
         status.insert(Certificate_Status_Code::CERT_SERIAL_NEGATIVE);
64✔
172
      }
173

174
      // Check the subject's DN components' length
175

176
      for(const auto& dn_pair : subject.subject_dn().dn_info()) {
24,620✔
177
         const size_t dn_ub = X509_DN::lookup_ub(dn_pair.first);
18,101✔
178
         // dn_pair = <OID,str>
179
         if(dn_ub > 0 && dn_pair.second.size() > dn_ub) {
18,101✔
180
            status.insert(Certificate_Status_Code::DN_TOO_LONG);
40✔
181
         }
182
      }
183

184
      // If so configured, allow trust anchors outside the validity period with
185
      // a warning rather than a hard error
186
      const bool enforce_validity_period = !at_trust_anchor || !restrictions.ignore_trusted_root_time_range();
6,519✔
187
      // Check all certs for valid time range
188
      if(validation_time < subject.not_before()) {
6,519✔
189
         if(enforce_validity_period) {
82✔
190
            status.insert(Certificate_Status_Code::CERT_NOT_YET_VALID);
80✔
191
         } else {
192
            status.insert(Certificate_Status_Code::TRUSTED_CERT_NOT_YET_VALID);  // only warn
2✔
193
         }
194
      }
195

196
      if(validation_time > subject.not_after()) {
6,519✔
197
         if(enforce_validity_period) {
301✔
198
            status.insert(Certificate_Status_Code::CERT_HAS_EXPIRED);
299✔
199
         } else {
200
            status.insert(Certificate_Status_Code::TRUSTED_CERT_HAS_EXPIRED);  // only warn
2✔
201
         }
202
      }
203

204
      // Check issuer constraints
205
      if(issuer.has_value() && !issuer->is_CA_cert() && !is_end_entity_trust_anchor) {
6,519✔
206
         status.insert(Certificate_Status_Code::CA_CERT_NOT_FOR_CERT_ISSUER);
118✔
207
      }
208

209
      // Check cert extensions
210

211
      if(subject.x509_version() == 1) {
6,519✔
212
         if(subject.v2_issuer_key_id().empty() == false || subject.v2_subject_key_id().empty() == false) {
37✔
213
            status.insert(Certificate_Status_Code::V2_IDENTIFIERS_IN_V1_CERT);
1✔
214
         }
215
      }
216

217
      const Extensions& extensions = subject.v3_extensions();
6,519✔
218
      const auto& extensions_vec = extensions.extensions();
6,519✔
219
      if(subject.x509_version() < 3 && !extensions_vec.empty()) {
6,519✔
220
         status.insert(Certificate_Status_Code::EXT_IN_V1_V2_CERT);
64✔
221
      }
222

223
      for(const auto& extension : extensions_vec) {
34,732✔
224
         extension.first->validate(subject, issuer, cert_path, cert_status, i);
28,213✔
225
      }
226

227
      if(extensions_vec.size() != extensions.get_extension_oids().size()) {
6,519✔
228
         status.insert(Certificate_Status_Code::DUPLICATE_CERT_EXTENSION);
32✔
229
      }
230
   }
13,033✔
231

232
   // path len check
233
   size_t max_path_length = cert_path.size();
2,263✔
234
   for(size_t i = cert_path.size() - 1; i > 0; --i) {
6,519✔
235
      std::set<Certificate_Status_Code>& status = cert_status.at(i);
4,256✔
236
      const X509_Certificate& subject = cert_path[i];
4,256✔
237

238
      /*
239
      * If the certificate was not self-issued, verify that max_path_length is
240
      * greater than zero and decrement max_path_length by 1.
241
      */
242
      if(subject.subject_dn() != subject.issuer_dn()) {
4,256✔
243
         if(max_path_length > 0) {
1,883✔
244
            max_path_length -= 1;
1,805✔
245
         } else {
246
            status.insert(Certificate_Status_Code::CERT_CHAIN_TOO_LONG);
78✔
247
         }
248
      }
249

250
      /*
251
      * If pathLenConstraint is present in the certificate and is less than max_path_length,
252
      * set max_path_length to the value of pathLenConstraint.
253
      */
254
      if(auto path_len_constraint = subject.path_length_constraint()) {
4,256✔
255
         max_path_length = std::min(max_path_length, *path_len_constraint);
5,467✔
256
      }
257
   }
258

259
   return cert_status;
260
}
2,403✔
261

262
namespace {
263

264
Certificate_Status_Code verify_ocsp_signing_cert(const X509_Certificate& signing_cert,
41✔
265
                                                 const X509_Certificate& ca,
266
                                                 const std::vector<X509_Certificate>& extra_certs,
267
                                                 const std::vector<Certificate_Store*>& certstores,
268
                                                 std::chrono::system_clock::time_point ref_time,
269
                                                 const Path_Validation_Restrictions& restrictions) {
270
   // RFC 6960 4.2.2.2
271
   //    [Applications] MUST reject the response if the certificate
272
   //    required to validate the signature on the response does not
273
   //    meet at least one of the following criteria:
274
   //
275
   //    1. Matches a local configuration of OCSP signing authority
276
   //       for the certificate in question, or
277
   if(restrictions.trusted_ocsp_responders()->certificate_known(signing_cert)) {
41✔
278
      return Certificate_Status_Code::OK;
279
   }
280

281
   // RFC 6960 4.2.2.2
282
   //
283
   //    2. Is the certificate of the CA that issued the certificate
284
   //       in question, or
285
   if(signing_cert == ca) {
41✔
286
      return Certificate_Status_Code::OK;
287
   }
288

289
   // RFC 6960 4.2.2.2
290
   //
291
   //    3. Includes a value of id-kp-OCSPSigning in an extended key
292
   //       usage extension and is issued by the CA that issued the
293
   //       certificate in question as stated above.
294

295
   // TODO: Implement OCSP revocation check of OCSP signer certificate
296
   // Note: This needs special care to prevent endless loops on specifically
297
   //       forged chains of OCSP responses referring to each other.
298
   //
299
   // Currently, we're disabling OCSP-based revocation checks by setting the
300
   // timeout to 0. Additionally, the library's API would not allow an
301
   // application to pass in the required "second order" OCSP responses. I.e.
302
   // "second order" OCSP checks would need to rely on `check_ocsp_online()`
303
   // which is not an option for some applications (e.g. that require a proxy
304
   // for external HTTP requests).
305
   const auto ocsp_timeout = std::chrono::milliseconds::zero();
23✔
306
   const auto relaxed_restrictions =
23✔
307
      Path_Validation_Restrictions(false /* do not enforce revocation data */,
308
                                   restrictions.minimum_key_strength(),
309
                                   false /* OCSP is not available, so don't try for intermediates */,
310
                                   restrictions.trusted_hashes());
23✔
311

312
   const auto validation_result = x509_path_validate(concat(std::vector{signing_cert}, extra_certs),
69✔
313
                                                     relaxed_restrictions,
314
                                                     certstores,
315
                                                     {} /* hostname */,
316
                                                     Botan::Usage_Type::OCSP_RESPONDER,
317
                                                     ref_time,
318
                                                     ocsp_timeout);
46✔
319

320
   return validation_result.result();
23✔
321
}
46✔
322

323
}  // namespace
324

325
CertificatePathStatusCodes PKIX::check_ocsp(const std::vector<X509_Certificate>& cert_path,
55✔
326
                                            const std::vector<std::optional<OCSP::Response>>& ocsp_responses,
327
                                            const std::vector<Certificate_Store*>& certstores,
328
                                            std::chrono::system_clock::time_point ref_time,
329
                                            const Path_Validation_Restrictions& restrictions) {
330
   if(cert_path.empty()) {
55✔
331
      throw Invalid_Argument("PKIX::check_ocsp cert_path empty");
×
332
   }
333

334
   CertificatePathStatusCodes cert_status(cert_path.size() - 1);
55✔
335

336
   for(size_t i = 0; i != cert_path.size() - 1; ++i) {
150✔
337
      std::set<Certificate_Status_Code>& status = cert_status.at(i);
95✔
338

339
      const X509_Certificate& subject = cert_path.at(i);
95✔
340
      const X509_Certificate& ca = cert_path.at(i + 1);
95✔
341

342
      if(i < ocsp_responses.size() && (ocsp_responses.at(i) != std::nullopt) &&
95✔
343
         (ocsp_responses.at(i)->status() == OCSP::Response_Status_Code::Successful)) {
52✔
344
         try {
52✔
345
            const auto& ocsp_response = ocsp_responses.at(i);
52✔
346

347
            if(auto dummy_status = ocsp_response->dummy_status()) {
52✔
348
               // handle softfail conditions
349
               status.insert(dummy_status.value());
10✔
350
            } else if(auto signing_cert =
42✔
351
                         ocsp_response->find_signing_certificate(ca, restrictions.trusted_ocsp_responders());
42✔
352
                      !signing_cert) {
42✔
353
               status.insert(Certificate_Status_Code::OCSP_ISSUER_NOT_FOUND);
1✔
354
            } else if(auto ocsp_signing_cert_status =
41✔
355
                         verify_ocsp_signing_cert(signing_cert.value(),
41✔
356
                                                  ca,
357
                                                  concat(ocsp_response->certificates(), cert_path),
41✔
358
                                                  certstores,
359
                                                  ref_time,
360
                                                  restrictions);
41✔
361
                      ocsp_signing_cert_status > Certificate_Status_Code::FIRST_ERROR_STATUS) {
41✔
362
               status.insert(ocsp_signing_cert_status);
4✔
363
               status.insert(Certificate_Status_Code::OCSP_ISSUER_NOT_TRUSTED);
4✔
364
            } else {
365
               status.insert(ocsp_response->status_for(ca, subject, ref_time, restrictions.max_ocsp_age()));
37✔
366
            }
42✔
367
         } catch(Exception&) {
×
368
            status.insert(Certificate_Status_Code::OCSP_RESPONSE_INVALID);
×
369
         }
×
370
      }
371
   }
372

373
   while(!cert_status.empty() && cert_status.back().empty()) {
98✔
374
      cert_status.pop_back();
43✔
375
   }
376

377
   return cert_status;
55✔
378
}
×
379

380
CertificatePathStatusCodes PKIX::check_crl(const std::vector<X509_Certificate>& cert_path,
2,406✔
381
                                           const std::vector<std::optional<X509_CRL>>& crls,
382
                                           std::chrono::system_clock::time_point ref_time) {
383
   if(cert_path.empty()) {
2,406✔
384
      throw Invalid_Argument("PKIX::check_crl cert_path empty");
×
385
   }
386

387
   CertificatePathStatusCodes cert_status(cert_path.size());
2,406✔
388
   const X509_Time validation_time(ref_time);
2,406✔
389

390
   for(size_t i = 0; i != cert_path.size() - 1; ++i) {
6,940✔
391
      std::set<Certificate_Status_Code>& status = cert_status.at(i);
4,534✔
392

393
      if(i < crls.size() && crls[i].has_value()) {
4,534✔
394
         const X509_Certificate& subject = cert_path.at(i);
1,385✔
395
         const X509_Certificate& ca = cert_path.at(i + 1);
1,385✔
396

397
         if(!ca.allowed_usage(Key_Constraints::CrlSign)) {
1,385✔
398
            status.insert(Certificate_Status_Code::CA_CERT_NOT_FOR_CRL_ISSUER);
4✔
399
         }
400

401
         if(validation_time < crls[i]->this_update()) {
1,385✔
402
            status.insert(Certificate_Status_Code::CRL_NOT_YET_VALID);
32✔
403
         }
404

405
         if(crls[i]->next_update().time_is_set() && validation_time > crls[i]->next_update()) {
1,385✔
406
            status.insert(Certificate_Status_Code::CRL_HAS_EXPIRED);
36✔
407
         }
408

409
         auto ca_key = ca.subject_public_key();
1,385✔
410
         if(crls[i]->check_signature(*ca_key) == false) {
1,385✔
411
            status.insert(Certificate_Status_Code::CRL_BAD_SIGNATURE);
35✔
412
         }
413

414
         status.insert(Certificate_Status_Code::VALID_CRL_CHECKED);
1,385✔
415

416
         if(crls[i]->is_revoked(subject)) {
1,385✔
417
            status.insert(Certificate_Status_Code::CERT_IS_REVOKED);
276✔
418
         }
419

420
         const auto dp = subject.crl_distribution_points();
1,385✔
421
         if(!dp.empty()) {
1,385✔
422
            const auto crl_idp = crls[i]->crl_issuing_distribution_point();
416✔
423

424
            if(std::find(dp.begin(), dp.end(), crl_idp) == dp.end()) {
416✔
425
               status.insert(Certificate_Status_Code::NO_MATCHING_CRLDP);
416✔
426
            }
427
         }
416✔
428

429
         for(const auto& extension : crls[i]->extensions().extensions()) {
4,223✔
430
            // XXX this is wrong - the OID might be defined but the extension not full parsed
431
            // for example see #1652
432

433
            // is the extension critical and unknown?
434
            if(extension.second && !extension.first->oid_of().registered_oid()) {
2,876✔
435
               /* NIST Certificate Path Valiadation Testing document: "When an implementation does not recognize a critical extension in the
436
                * crlExtensions field, it shall assume that identified certificates have been revoked and are no longer valid"
437
                */
438
               status.insert(Certificate_Status_Code::CERT_IS_REVOKED);
38✔
439
            }
440
         }
1,385✔
441
      }
2,770✔
442
   }
443

444
   while(!cert_status.empty() && cert_status.back().empty()) {
7,922✔
445
      cert_status.pop_back();
5,516✔
446
   }
447

448
   return cert_status;
2,406✔
449
}
2,406✔
450

451
CertificatePathStatusCodes PKIX::check_crl(const std::vector<X509_Certificate>& cert_path,
2,400✔
452
                                           const std::vector<Certificate_Store*>& certstores,
453
                                           std::chrono::system_clock::time_point ref_time) {
454
   if(cert_path.empty()) {
2,400✔
455
      throw Invalid_Argument("PKIX::check_crl cert_path empty");
×
456
   }
457

458
   if(certstores.empty()) {
2,400✔
459
      throw Invalid_Argument("PKIX::check_crl certstores empty");
×
460
   }
461

462
   std::vector<std::optional<X509_CRL>> crls(cert_path.size());
2,400✔
463

464
   for(size_t i = 0; i != cert_path.size(); ++i) {
9,328✔
465
      for(auto* certstore : certstores) {
11,781✔
466
         crls[i] = certstore->find_crl_for(cert_path[i]);
6,956✔
467
         if(crls[i]) {
6,956✔
468
            break;
469
         }
470
      }
471
   }
472

473
   return PKIX::check_crl(cert_path, crls, ref_time);
4,800✔
474
}
2,400✔
475

476
#if defined(BOTAN_HAS_ONLINE_REVOCATION_CHECKS)
477

478
CertificatePathStatusCodes PKIX::check_ocsp_online(const std::vector<X509_Certificate>& cert_path,
10✔
479
                                                   const std::vector<Certificate_Store*>& trusted_certstores,
480
                                                   std::chrono::system_clock::time_point ref_time,
481
                                                   std::chrono::milliseconds timeout,
482
                                                   const Path_Validation_Restrictions& restrictions) {
483
   if(cert_path.empty()) {
10✔
484
      throw Invalid_Argument("PKIX::check_ocsp_online cert_path empty");
×
485
   }
486

487
   std::vector<std::future<std::optional<OCSP::Response>>> ocsp_response_futures;
10✔
488

489
   size_t to_ocsp = 1;
10✔
490

491
   if(restrictions.ocsp_all_intermediates()) {
10✔
492
      to_ocsp = cert_path.size() - 1;
×
493
   }
494
   if(cert_path.size() == 1) {
10✔
495
      to_ocsp = 0;
×
496
   }
497

498
   for(size_t i = 0; i < to_ocsp; ++i) {
20✔
499
      const X509_Certificate& subject = cert_path.at(i);
10✔
500
      const X509_Certificate& issuer = cert_path.at(i + 1);
10✔
501

502
      if(subject.ocsp_responder().empty()) {
10✔
503
         ocsp_response_futures.emplace_back(std::async(std::launch::deferred, [&]() -> std::optional<OCSP::Response> {
18✔
504
            return OCSP::Response(Certificate_Status_Code::OCSP_NO_REVOCATION_URL);
18✔
505
         }));
506
      } else {
507
         ocsp_response_futures.emplace_back(std::async(std::launch::async, [&]() -> std::optional<OCSP::Response> {
2✔
508
            OCSP::Request req(issuer, BigInt::from_bytes(subject.serial_number()));
1✔
509

510
            HTTP::Response http;
1✔
511
            try {
1✔
512
               http = HTTP::POST_sync(subject.ocsp_responder(),
2✔
513
                                      "application/ocsp-request",
514
                                      req.BER_encode(),
2✔
515
                                      /*redirects*/ 1,
516
                                      timeout);
1✔
517
            } catch(std::exception&) {
×
518
               // log e.what() ?
519
            }
×
520
            if(http.status_code() != 200) {
1✔
521
               return OCSP::Response(Certificate_Status_Code::OCSP_SERVER_NOT_AVAILABLE);
×
522
            }
523
            // Check the MIME type?
524

525
            return OCSP::Response(http.body());
1✔
526
         }));
2✔
527
      }
528
   }
529

530
   std::vector<std::optional<OCSP::Response>> ocsp_responses;
10✔
531
   ocsp_responses.reserve(ocsp_response_futures.size());
10✔
532

533
   for(auto& ocsp_response_future : ocsp_response_futures) {
20✔
534
      ocsp_responses.push_back(ocsp_response_future.get());
30✔
535
   }
536

537
   return PKIX::check_ocsp(cert_path, ocsp_responses, trusted_certstores, ref_time, restrictions);
20✔
538
}
10✔
539

540
CertificatePathStatusCodes PKIX::check_crl_online(const std::vector<X509_Certificate>& cert_path,
×
541
                                                  const std::vector<Certificate_Store*>& certstores,
542
                                                  Certificate_Store_In_Memory* crl_store,
543
                                                  std::chrono::system_clock::time_point ref_time,
544
                                                  std::chrono::milliseconds timeout) {
545
   if(cert_path.empty()) {
×
546
      throw Invalid_Argument("PKIX::check_crl_online cert_path empty");
×
547
   }
548
   if(certstores.empty()) {
×
549
      throw Invalid_Argument("PKIX::check_crl_online certstores empty");
×
550
   }
551

552
   std::vector<std::future<std::optional<X509_CRL>>> future_crls;
×
553
   std::vector<std::optional<X509_CRL>> crls(cert_path.size());
×
554

555
   for(size_t i = 0; i != cert_path.size(); ++i) {
×
556
      const std::optional<X509_Certificate>& cert = cert_path.at(i);
×
557
      for(auto* certstore : certstores) {
×
558
         crls[i] = certstore->find_crl_for(*cert);
×
559
         if(crls[i].has_value()) {
×
560
            break;
561
         }
562
      }
563

564
      // TODO: check if CRL is expired and re-request?
565

566
      // Only request if we don't already have a CRL
567
      if(crls[i]) {
×
568
         /*
569
         We already have a CRL, so just insert this empty one to hold a place in the vector
570
         so that indexes match up
571
         */
572
         future_crls.emplace_back(std::future<std::optional<X509_CRL>>());
×
573
      } else if(cert->crl_distribution_point().empty()) {
×
574
         // Avoid creating a thread for this case
575
         future_crls.emplace_back(std::async(std::launch::deferred, [&]() -> std::optional<X509_CRL> {
×
576
            throw Not_Implemented("No CRL distribution point for this certificate");
×
577
         }));
578
      } else {
579
         future_crls.emplace_back(std::async(std::launch::async, [&]() -> std::optional<X509_CRL> {
×
580
            auto http = HTTP::GET_sync(cert->crl_distribution_point(),
×
581
                                       /*redirects*/ 1,
582
                                       timeout);
×
583

584
            http.throw_unless_ok();
×
585
            // check the mime type?
586
            return X509_CRL(http.body());
×
587
         }));
×
588
      }
589
   }
×
590

591
   for(size_t i = 0; i != future_crls.size(); ++i) {
×
592
      if(future_crls[i].valid()) {
×
593
         try {
×
594
            crls[i] = future_crls[i].get();
×
595
         } catch(std::exception&) {
×
596
            // crls[i] left null
597
            // todo: log exception e.what() ?
598
         }
×
599
      }
600
   }
601

602
   auto crl_status = PKIX::check_crl(cert_path, crls, ref_time);
×
603

604
   if(crl_store != nullptr) {
×
605
      for(size_t i = 0; i != crl_status.size(); ++i) {
×
606
         if(crl_status[i].contains(Certificate_Status_Code::VALID_CRL_CHECKED)) {
×
607
            // better be non-null, we supposedly validated it
608
            BOTAN_ASSERT_NOMSG(crls[i].has_value());
×
609
            crl_store->add_crl(*crls[i]);
×
610
         }
611
      }
612
   }
613

614
   return crl_status;
×
615
}
×
616

617
#endif
618

619
Certificate_Status_Code PKIX::build_certificate_path(std::vector<X509_Certificate>& cert_path,
5✔
620
                                                     const std::vector<Certificate_Store*>& trusted_certstores,
621
                                                     const X509_Certificate& end_entity,
622
                                                     const std::vector<X509_Certificate>& end_entity_extra) {
623
   std::vector<std::vector<X509_Certificate>> all_cert_paths;
5✔
624
   const auto build_all_paths_res =
5✔
625
      build_all_certificate_paths(all_cert_paths, trusted_certstores, end_entity, end_entity_extra);
5✔
626

627
   if(build_all_paths_res != Certificate_Status_Code::OK) {
5✔
628
      return build_all_paths_res;
629
   }
630
   BOTAN_ASSERT_NOMSG(!all_cert_paths.empty());
5✔
631

632
   // Paths ending in self-signed certificates are preferred.
633
   const auto first_with_self_signed_anchor = std::ranges::find_if(all_cert_paths, [&](const auto& left_path) {
11✔
634
      BOTAN_ASSERT_NOMSG(!left_path.empty());
11✔
635
      return left_path.back().is_self_signed();
11✔
636
   });
637
   if(first_with_self_signed_anchor != all_cert_paths.end()) {
5✔
638
      cert_path.insert(cert_path.end(), first_with_self_signed_anchor->begin(), first_with_self_signed_anchor->end());
3✔
639
   } else {
640
      cert_path.insert(cert_path.end(), all_cert_paths.front().begin(), all_cert_paths.front().end());
2✔
641
   }
642
   return Certificate_Status_Code::OK;
643
}
5✔
644

645
/**
646
 * utilities for PKIX::build_all_certificate_paths
647
 */
648
namespace {
649
// <certificate, trusted?>
650
using cert_maybe_trusted = std::pair<std::optional<X509_Certificate>, bool>;
651
}  // namespace
652

653
/**
654
 * Build all possible certificate paths from the end certificate to self-signed trusted roots.
655
 *
656
 * All potentially valid paths are put into the cert_paths vector. If no potentially valid paths are found,
657
 * one of the encountered errors is returned arbitrarily.
658
 *
659
 * todo add a path building function that returns detailed information on errors encountered while building
660
 * the potentially numerous path candidates.
661
 *
662
 * Basically, a DFS is performed starting from the end certificate. A stack (vector) serves to control the DFS.
663
 * At the beginning of each iteration, a pair is popped from the stack that contains (1) the next certificate
664
 * to add to the path (2) a bool that indicates if the certificate is part of a trusted certstore. Ideally, we
665
 * follow the unique issuer of the current certificate until a trusted root is reached. However, the issuer DN +
666
 * authority key id need not be unique among the certificates used for building the path. In such a case,
667
 * we consider all the matching issuers by pushing <IssuerCert, trusted?> on the stack for each of them.
668
 *
669
 */
670
Certificate_Status_Code PKIX::build_all_certificate_paths(std::vector<std::vector<X509_Certificate>>& cert_paths_out,
2,880✔
671
                                                          const std::vector<Certificate_Store*>& trusted_certstores,
672
                                                          const X509_Certificate& end_entity,
673
                                                          const std::vector<X509_Certificate>& end_entity_extra) {
674
   if(!cert_paths_out.empty()) {
2,880✔
675
      throw Invalid_Argument("PKIX::build_all_certificate_paths: cert_paths_out must be empty");
×
676
   }
677
   if(std::ranges::any_of(trusted_certstores, [](auto* ptr) { return ptr == nullptr; })) {
5,760✔
678
      throw Invalid_Argument("certificate store list must not contain nullptr");
×
679
   }
680

681
   auto cert_in_any_trusted_store = [&](const X509_Certificate& cert) {
8,611✔
682
      return std::ranges::any_of(trusted_certstores,
5,731✔
683
                                 [&](const Certificate_Store* store) { return store->certificate_known(cert); });
5,417✔
684
   };
2,880✔
685

686
   /*
687
    * Pile up error messages
688
    */
689
   std::vector<Certificate_Status_Code> stats;
2,880✔
690

691
   Certificate_Store_In_Memory ee_extras;
2,880✔
692
   for(const auto& cert : end_entity_extra) {
5,731✔
693
      if(!cert_in_any_trusted_store(cert)) {
2,851✔
694
         ee_extras.add_certificate(cert);
2,599✔
695
      }
696
   }
697

698
   /*
699
   * This is an inelegant but functional way of preventing path loops
700
   * (where C1 -> C2 -> C3 -> C1). We store a set of all the certificate
701
   * fingerprints in the path. If there is a duplicate, we error out.
702
   * TODO: save fingerprints in result struct? Maybe useful for blacklists, etc.
703
   */
704
   std::set<std::string> certs_seen;
2,880✔
705

706
   // new certs are added and removed from the path during the DFS
707
   // it is copied into cert_paths_out when we encounter a trusted root
708
   std::vector<X509_Certificate> path_so_far;
2,880✔
709

710
   std::vector<cert_maybe_trusted> stack = {{end_entity, cert_in_any_trusted_store(end_entity)}};
8,640✔
711

712
   while(!stack.empty()) {
20,340✔
713
      std::optional<X509_Certificate> last = stack.back().first;
17,460✔
714
      // found a deletion marker that guides the DFS, backtracking
715
      if(last == std::nullopt) {
17,460✔
716
         stack.pop_back();
7,274✔
717
         std::string fprint = path_so_far.back().fingerprint("SHA-256");
7,274✔
718
         certs_seen.erase(fprint);
7,274✔
719
         path_so_far.pop_back();
7,274✔
720
      }
7,274✔
721
      // process next cert on the path
722
      else {
723
         const bool trusted = stack.back().second;
10,186✔
724
         stack.pop_back();
10,186✔
725

726
         // certificate already seen?
727
         const std::string fprint = last->fingerprint("SHA-256");
10,186✔
728
         if(certs_seen.count(fprint) == 1) {
10,186✔
729
            stats.push_back(Certificate_Status_Code::CERT_CHAIN_LOOP);
2,403✔
730
            // the current path ended in a loop
731
            continue;
2,403✔
732
         }
733

734
         // A valid path has been discovered. It includes endpoints that may end
735
         // with either a self-signed or a non-self-signed certificate. For
736
         // certificates that are not self-signed, additional paths could
737
         // potentially extend from the current one.
738
         if(trusted) {
7,783✔
739
            cert_paths_out.push_back(path_so_far);
2,569✔
740
            cert_paths_out.back().push_back(*last);
2,569✔
741
         } else if(last->is_self_signed()) {
5,214✔
742
            stats.push_back(Certificate_Status_Code::CANNOT_ESTABLISH_TRUST);
240✔
743
            continue;
240✔
744
         }
745

746
         const X509_DN issuer_dn = last->issuer_dn();
7,543✔
747
         const std::vector<uint8_t> auth_key_id = last->authority_key_id();
7,543✔
748

749
         // search for trusted issuers
750
         std::vector<X509_Certificate> trusted_issuers;
7,543✔
751
         for(Certificate_Store* store : trusted_certstores) {
14,980✔
752
            auto new_issuers = store->find_all_certs(issuer_dn, auth_key_id);
7,437✔
753
            trusted_issuers.insert(trusted_issuers.end(), new_issuers.begin(), new_issuers.end());
7,437✔
754
         }
7,437✔
755

756
         // search the supplemental certs
757
         std::vector<X509_Certificate> misc_issuers = ee_extras.find_all_certs(issuer_dn, auth_key_id);
7,543✔
758

759
         // if we could not find any issuers, the current path ends here
760
         if(trusted_issuers.empty() && misc_issuers.empty()) {
7,543✔
761
            stats.push_back(Certificate_Status_Code::CERT_ISSUER_NOT_FOUND);
269✔
762
            continue;
269✔
763
         }
764

765
         // push the latest certificate onto the path_so_far
766
         path_so_far.push_back(*last);
7,274✔
767
         certs_seen.emplace(fprint);
7,274✔
768

769
         // push a deletion marker on the stack for backtracking later
770
         stack.push_back({std::optional<X509_Certificate>(), false});
7,274✔
771

772
         for(const auto& trusted_cert : trusted_issuers) {
12,227✔
773
            stack.push_back({trusted_cert, true});
9,906✔
774
         }
775

776
         for(const auto& misc : misc_issuers) {
9,627✔
777
            stack.push_back({misc, false});
4,706✔
778
         }
779
      }
17,231✔
780
   }
17,460✔
781

782
   // could not construct any potentially valid path
783
   if(cert_paths_out.empty()) {
2,880✔
784
      if(stats.empty()) {
337✔
785
         throw Internal_Error("X509 path building failed for unknown reasons");
×
786
      } else {
787
         // arbitrarily return the first error
788
         return stats[0];
337✔
789
      }
790
   } else {
791
      return Certificate_Status_Code::OK;
792
   }
793
}
5,760✔
794

795
void PKIX::merge_revocation_status(CertificatePathStatusCodes& chain_status,
2,400✔
796
                                   const CertificatePathStatusCodes& crl_status,
797
                                   const CertificatePathStatusCodes& ocsp_status,
798
                                   const Path_Validation_Restrictions& restrictions) {
799
   if(chain_status.empty()) {
2,400✔
800
      throw Invalid_Argument("PKIX::merge_revocation_status chain_status was empty");
×
801
   }
802

803
   for(size_t i = 0; i != chain_status.size() - 1; ++i) {
6,928✔
804
      bool had_crl = false;
4,528✔
805
      bool had_ocsp = false;
4,528✔
806

807
      if(i < crl_status.size() && !crl_status[i].empty()) {
4,528✔
808
         for(auto&& code : crl_status[i]) {
3,590✔
809
            if(code == Certificate_Status_Code::VALID_CRL_CHECKED) {
2,211✔
810
               had_crl = true;
1,379✔
811
            }
812
            chain_status[i].insert(code);
2,211✔
813
         }
814
      }
815

816
      if(i < ocsp_status.size() && !ocsp_status[i].empty()) {
4,528✔
817
         for(auto&& code : ocsp_status[i]) {
76✔
818
            // NO_REVOCATION_URL and OCSP_SERVER_NOT_AVAILABLE are softfail
819
            if(code == Certificate_Status_Code::OCSP_RESPONSE_GOOD ||
40✔
820
               code == Certificate_Status_Code::OCSP_NO_REVOCATION_URL ||
20✔
821
               code == Certificate_Status_Code::OCSP_SERVER_NOT_AVAILABLE) {
822
               had_ocsp = true;
20✔
823
            }
824

825
            chain_status[i].insert(code);
40✔
826
         }
827
      }
828

829
      if(had_crl == false && had_ocsp == false) {
4,528✔
830
         if((restrictions.require_revocation_information() && i == 0) ||
3,129✔
831
            (restrictions.ocsp_all_intermediates() && i > 0)) {
3,053✔
832
            chain_status[i].insert(Certificate_Status_Code::NO_REVOCATION_DATA);
143✔
833
         }
834
      }
835
   }
836
}
2,400✔
837

838
Certificate_Status_Code PKIX::overall_status(const CertificatePathStatusCodes& cert_status) {
2,409✔
839
   if(cert_status.empty()) {
2,409✔
840
      throw Invalid_Argument("PKIX::overall_status empty cert status");
×
841
   }
842

843
   Certificate_Status_Code overall_status = Certificate_Status_Code::OK;
844

845
   // take the "worst" error as overall
846
   for(const std::set<Certificate_Status_Code>& s : cert_status) {
9,348✔
847
      if(!s.empty()) {
6,939✔
848
         auto worst = *s.rbegin();
2,832✔
849
         // Leave informative OCSP/CRL confirmations on cert-level status only
850
         if(worst >= Certificate_Status_Code::FIRST_ERROR_STATUS && worst > overall_status) {
2,832✔
851
            overall_status = worst;
6,939✔
852
         }
853
      }
854
   }
855
   return overall_status;
2,409✔
856
}
857

858
Path_Validation_Result x509_path_validate(const std::vector<X509_Certificate>& end_certs,
2,870✔
859
                                          const Path_Validation_Restrictions& restrictions,
860
                                          const std::vector<Certificate_Store*>& trusted_roots,
861
                                          std::string_view hostname,
862
                                          Usage_Type usage,
863
                                          std::chrono::system_clock::time_point ref_time,
864
                                          std::chrono::milliseconds ocsp_timeout,
865
                                          const std::vector<std::optional<OCSP::Response>>& ocsp_resp) {
866
   if(end_certs.empty()) {
2,870✔
867
      throw Invalid_Argument("x509_path_validate called with no subjects");
×
868
   }
869

870
   const X509_Certificate& end_entity = end_certs[0];
2,870✔
871
   std::vector<X509_Certificate> end_entity_extra;
2,870✔
872
   for(size_t i = 1; i < end_certs.size(); ++i) {
5,671✔
873
      end_entity_extra.push_back(end_certs[i]);
2,801✔
874
   }
875

876
   std::vector<std::vector<X509_Certificate>> cert_paths;
2,870✔
877
   Certificate_Status_Code path_building_result =
2,870✔
878
      PKIX::build_all_certificate_paths(cert_paths, trusted_roots, end_entity, end_entity_extra);
2,870✔
879

880
   // If we cannot successfully build a chain to a trusted self-signed root, stop now
881
   if(path_building_result != Certificate_Status_Code::OK) {
2,870✔
882
      return Path_Validation_Result(path_building_result);
337✔
883
   }
884

885
   // If we require trust anchors to be self-signed we need to filter all paths
886
   // not ending in a self-signed certificate.
887
   if(restrictions.require_self_signed_trust_anchors()) {
2,533✔
888
      auto has_non_self_signed_trust_anchor = [](const auto& cert_path) {
1,646✔
889
         return cert_path.empty() || !cert_path.back().is_self_signed();
1,646✔
890
      };
891
      std::erase_if(cert_paths, has_non_self_signed_trust_anchor);
1,632✔
892
   }
893
   if(cert_paths.empty()) {
2,533✔
894
      return Path_Validation_Result(Certificate_Status_Code::CANNOT_ESTABLISH_TRUST);
133✔
895
   }
896

897
   std::vector<Path_Validation_Result> error_results;
2,400✔
898
   // Try validating all the potentially valid paths and return the first one to validate properly
899
   for(auto cert_path : cert_paths) {
4,066✔
900
      CertificatePathStatusCodes status = PKIX::check_chain(cert_path, ref_time, hostname, usage, restrictions);
2,400✔
901

902
      CertificatePathStatusCodes crl_status = PKIX::check_crl(cert_path, trusted_roots, ref_time);
2,400✔
903

904
      CertificatePathStatusCodes ocsp_status;
2,400✔
905

906
      if(!ocsp_resp.empty()) {
2,400✔
907
         ocsp_status = PKIX::check_ocsp(cert_path, ocsp_resp, trusted_roots, ref_time, restrictions);
30✔
908
      }
909

910
      if(ocsp_status.empty() && ocsp_timeout != std::chrono::milliseconds(0)) {
2,400✔
911
#if defined(BOTAN_TARGET_OS_HAS_THREADS) && defined(BOTAN_HAS_HTTP_UTIL)
912
         ocsp_status = PKIX::check_ocsp_online(cert_path, trusted_roots, ref_time, ocsp_timeout, restrictions);
9✔
913
#else
914
         ocsp_status.resize(1);
915
         ocsp_status[0].insert(Certificate_Status_Code::OCSP_NO_HTTP);
916
#endif
917
      }
918

919
      PKIX::merge_revocation_status(status, crl_status, ocsp_status, restrictions);
2,400✔
920

921
      Path_Validation_Result pvd(status, std::move(cert_path));
2,400✔
922
      if(pvd.successful_validation()) {
2,400✔
923
         return pvd;
734✔
924
      } else {
925
         error_results.push_back(std::move(pvd));
1,666✔
926
      }
927
   }
2,400✔
928
   return error_results[0];
1,666✔
929
}
5,247✔
930

931
Path_Validation_Result x509_path_validate(const X509_Certificate& end_cert,
282✔
932
                                          const Path_Validation_Restrictions& restrictions,
933
                                          const std::vector<Certificate_Store*>& trusted_roots,
934
                                          std::string_view hostname,
935
                                          Usage_Type usage,
936
                                          std::chrono::system_clock::time_point when,
937
                                          std::chrono::milliseconds ocsp_timeout,
938
                                          const std::vector<std::optional<OCSP::Response>>& ocsp_resp) {
939
   std::vector<X509_Certificate> certs;
282✔
940
   certs.push_back(end_cert);
282✔
941
   return x509_path_validate(certs, restrictions, trusted_roots, hostname, usage, when, ocsp_timeout, ocsp_resp);
564✔
942
}
282✔
943

944
Path_Validation_Result x509_path_validate(const std::vector<X509_Certificate>& end_certs,
1,949✔
945
                                          const Path_Validation_Restrictions& restrictions,
946
                                          const Certificate_Store& store,
947
                                          std::string_view hostname,
948
                                          Usage_Type usage,
949
                                          std::chrono::system_clock::time_point when,
950
                                          std::chrono::milliseconds ocsp_timeout,
951
                                          const std::vector<std::optional<OCSP::Response>>& ocsp_resp) {
952
   std::vector<Certificate_Store*> trusted_roots;
1,949✔
953
   trusted_roots.push_back(const_cast<Certificate_Store*>(&store));
1,949✔
954

955
   return x509_path_validate(end_certs, restrictions, trusted_roots, hostname, usage, when, ocsp_timeout, ocsp_resp);
3,898✔
956
}
1,949✔
957

958
Path_Validation_Result x509_path_validate(const X509_Certificate& end_cert,
223✔
959
                                          const Path_Validation_Restrictions& restrictions,
960
                                          const Certificate_Store& store,
961
                                          std::string_view hostname,
962
                                          Usage_Type usage,
963
                                          std::chrono::system_clock::time_point when,
964
                                          std::chrono::milliseconds ocsp_timeout,
965
                                          const std::vector<std::optional<OCSP::Response>>& ocsp_resp) {
966
   std::vector<X509_Certificate> certs;
223✔
967
   certs.push_back(end_cert);
223✔
968

969
   std::vector<Certificate_Store*> trusted_roots;
223✔
970
   trusted_roots.push_back(const_cast<Certificate_Store*>(&store));
223✔
971

972
   return x509_path_validate(certs, restrictions, trusted_roots, hostname, usage, when, ocsp_timeout, ocsp_resp);
446✔
973
}
223✔
974

975
Path_Validation_Restrictions::Path_Validation_Restrictions(bool require_rev,
1,027✔
976
                                                           size_t key_strength,
977
                                                           bool ocsp_intermediates,
978
                                                           std::chrono::seconds max_ocsp_age,
979
                                                           std::unique_ptr<Certificate_Store> trusted_ocsp_responders,
980
                                                           bool ignore_trusted_root_time_range,
981
                                                           bool require_self_signed_trust_anchors) :
1,027✔
982
      m_require_revocation_information(require_rev),
1,027✔
983
      m_ocsp_all_intermediates(ocsp_intermediates),
1,027✔
984
      m_minimum_key_strength(key_strength),
1,027✔
985
      m_max_ocsp_age(max_ocsp_age),
1,027✔
986
      m_trusted_ocsp_responders(std::move(trusted_ocsp_responders)),
1,027✔
987
      m_ignore_trusted_root_time_range(ignore_trusted_root_time_range),
1,027✔
988
      m_require_self_signed_trust_anchors(require_self_signed_trust_anchors) {
1,027✔
989
   if(key_strength <= 80) {
1,027✔
990
      m_trusted_hashes.insert("SHA-1");
830✔
991
   }
992

993
   m_trusted_hashes.insert("SHA-224");
2,054✔
994
   m_trusted_hashes.insert("SHA-256");
2,054✔
995
   m_trusted_hashes.insert("SHA-384");
2,054✔
996
   m_trusted_hashes.insert("SHA-512");
2,054✔
997
   m_trusted_hashes.insert("SHAKE-256(512)");  // Dilithium/ML-DSA
2,054✔
998
   m_trusted_hashes.insert("SHAKE-256(912)");  // Ed448
2,054✔
999
}
1,027✔
1000

1001
namespace {
1002
CertificatePathStatusCodes find_warnings(const CertificatePathStatusCodes& all_statuses) {
2,401✔
1003
   CertificatePathStatusCodes warnings;
2,401✔
1004
   for(const auto& status_set_i : all_statuses) {
9,332✔
1005
      std::set<Certificate_Status_Code> warning_set_i;
6,931✔
1006
      for(const auto& code : status_set_i) {
10,805✔
1007
         if(code >= Certificate_Status_Code::FIRST_WARNING_STATUS &&
3,874✔
1008
            code < Certificate_Status_Code::FIRST_ERROR_STATUS) {
1009
            warning_set_i.insert(code);
117✔
1010
         }
1011
      }
1012
      warnings.push_back(warning_set_i);
6,931✔
1013
   }
6,931✔
1014
   return warnings;
2,401✔
1015
}
×
1016
}  // namespace
1017

1018
Path_Validation_Result::Path_Validation_Result(CertificatePathStatusCodes status,
2,401✔
1019
                                               std::vector<X509_Certificate>&& cert_chain) :
2,401✔
1020
      m_all_status(std::move(status)),
2,401✔
1021
      m_warnings(find_warnings(m_all_status)),
2,401✔
1022
      m_cert_path(std::move(cert_chain)),
2,401✔
1023
      m_overall(PKIX::overall_status(m_all_status)) {}
2,401✔
1024

1025
const X509_Certificate& Path_Validation_Result::trust_root() const {
22✔
1026
   if(m_cert_path.empty()) {
22✔
1027
      throw Invalid_State("Path_Validation_Result::trust_root no path set");
×
1028
   }
1029
   if(result() != Certificate_Status_Code::VERIFIED) {
22✔
1030
      throw Invalid_State("Path_Validation_Result::trust_root meaningless with invalid status");
×
1031
   }
1032

1033
   return m_cert_path[m_cert_path.size() - 1];
22✔
1034
}
1035

1036
bool Path_Validation_Result::successful_validation() const {
3,025✔
1037
   return (result() == Certificate_Status_Code::VERIFIED || result() == Certificate_Status_Code::OCSP_RESPONSE_GOOD ||
3,025✔
1038
           result() == Certificate_Status_Code::VALID_CRL_CHECKED);
2,068✔
1039
}
1040

1041
bool Path_Validation_Result::no_warnings() const {
76✔
1042
   for(const auto& status_set_i : m_warnings) {
302✔
1043
      if(!status_set_i.empty()) {
228✔
1044
         return false;
76✔
1045
      }
1046
   }
1047
   return true;
1048
}
1049

1050
CertificatePathStatusCodes Path_Validation_Result::warnings() const {
74✔
1051
   return m_warnings;
74✔
1052
}
1053

1054
std::string Path_Validation_Result::result_string() const {
2,211✔
1055
   return status_string(result());
2,211✔
1056
}
1057

1058
const char* Path_Validation_Result::status_string(Certificate_Status_Code code) {
2,255✔
1059
   if(const char* s = to_string(code)) {
2,255✔
1060
      return s;
2,255✔
1061
   }
1062

1063
   return "Unknown error";
1064
}
1065

1066
std::string Path_Validation_Result::warnings_string() const {
76✔
1067
   const std::string sep(", ");
76✔
1068
   std::ostringstream oss;
76✔
1069
   for(size_t i = 0; i < m_warnings.size(); i++) {
306✔
1070
      for(auto code : m_warnings[i]) {
232✔
1071
         oss << "[" << std::to_string(i) << "] " << status_string(code) << sep;
4✔
1072
      }
1073
   }
1074

1075
   std::string res = oss.str();
76✔
1076
   // remove last sep
1077
   if(res.size() >= sep.size()) {
76✔
1078
      res = res.substr(0, res.size() - sep.size());
2✔
1079
   }
1080
   return res;
152✔
1081
}
76✔
1082
}  // 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