Botan 3.13.0
Crypto and TLS for C&
x509path.cpp
Go to the documentation of this file.
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 <botan/internal/x509_utils.h>
17#include <algorithm>
18#include <chrono>
19#include <iterator>
20#include <set>
21#include <span>
22#include <sstream>
23#include <string>
24#include <unordered_set>
25#include <vector>
26
27#if defined(BOTAN_HAS_ONLINE_REVOCATION_CHECKS)
28 #include <botan/uri.h>
29 #include <botan/internal/http_util.h>
30 #include <future>
31#endif
32
33namespace Botan {
34
35namespace {
36
37constexpr size_t PathBuildingDfsBudget = 300;
38constexpr size_t PathBuildingMaximumChainLength = 16;
39constexpr size_t PathBuildingVerificationBudget = 200;
40constexpr size_t PathBuildingMaxPathsExamined = 50;
41
42struct CrlApplicability {
43 bool usable; // RFC 5280 6.3.3(b) gate: can be searched for a revocation entry
44 bool full_coverage; // can also serve as VALID_CRL_CHECKED non-revocation evidence
45};
46
47/*
48* Single-pass evaluation of how this CRL applies to this cert. Combines:
49* - 6.3.3(b)(1)/(b)(2)(i) name match (via distribution_point_match)
50* - 6.3.3(b)(2)(ii)-(iv) IDP scope booleans
51* - the (currently unsupported) indirect-CRL gate from (f)-(g)
52* - 6.3.3(d)(3) DP-reasons / IDP-onlySomeReasons full-coverage check
53* Computing the two answers together keeps their matching rules in sync and
54* avoids re-walking the cert's CDP. `full_coverage` is reported only when
55* `usable` is true; reason-mask accumulation across multiple CRLs per
56* 6.3.3(d)-(l) is not implemented, so a reason-limited CRL never alone
57* certifies full coverage.
58*/
59CrlApplicability crl_applicability_for(const X509_CRL& crl, const X509_Certificate& subject) {
60 /*
61 * RFC 5280 6.3.3
62 *
63 * (b) Verify the issuer and scope of the complete CRL as follows:
64 *
65 * (1) If the DP includes cRLIssuer, then verify that the issuer
66 * field in the complete CRL matches cRLIssuer in the DP and
67 * that the complete CRL contains an issuing distribution
68 * point extension with the indirectCRL boolean asserted.
69 * Otherwise, verify that the CRL issuer matches the
70 * certificate issuer.
71 *
72 * (2) If the complete CRL includes an issuing distribution point
73 * (IDP) CRL extension, check the following:
74 *
75 * (i) If the distribution point name is present in the IDP CRL
76 * extension and the distribution field is present in the
77 * DP, then verify that one of the names in the IDP matches
78 * one of the names in the DP. If the distribution point
79 * name is present in the IDP CRL extension and the
80 * distribution field is omitted from the DP, then verify
81 * that one of the names in the IDP matches one of the names
82 * in the cRLIssuer field of the DP.
83 *
84 * (ii) If the onlyContainsUserCerts boolean is asserted in the
85 * IDP CRL extension, verify that the certificate does not
86 * include the basic constraints extension with the cA
87 * boolean asserted.
88 *
89 * (iii) If the onlyContainsCACerts boolean is asserted in the
90 * IDP CRL extension, verify that the certificate
91 * includes the basic constraints extension with the cA
92 * boolean asserted.
93 *
94 * (iv) Verify that the onlyContainsAttributeCerts boolean is not
95 * asserted.
96 */
97 const auto match = distribution_point_match(crl, subject);
98 if(!match.any) {
99 return {false, false};
100 }
101
102 const auto* idp = crl.extensions().get_extension_object_as<Cert_Extension::CRL_Issuing_Distribution_Point>();
103 if(idp == nullptr) {
104 return {true, match.any_with_absent_reasons};
105 }
106
107 // X509_Certificate::is_CA_cert has additional gates (KU + EKU) besides the basicConstraints
108 const bool basicConstraints_isCa = [&]() {
109 if(const auto* ext = subject.v3_extensions().get_extension_object_as<Cert_Extension::Basic_Constraints>()) {
110 return ext->get_is_ca();
111 } else {
112 return false;
113 }
114 }();
115
116 // step (ii)
117 if(idp->only_contains_user_certs() && basicConstraints_isCa) {
118 return {false, false};
119 }
120
121 // step (iii)
122 if(idp->only_contains_ca_certs() && !basicConstraints_isCa) {
123 return {false, false};
124 }
125
126 // step (iv)
127 if(idp->only_contains_attribute_certs()) {
128 return {false, false};
129 }
130
131 /*
132 * RFC 5280 6.3.3(f)-(g) requires validating the cRLIssuer's certification
133 * path and verifying the CRL signature with that key when indirectCRL is
134 * asserted. PKIX::check_crl currently verifies the CRL signature against
135 * the cert's direct issuer key only, so an indirect CRL cannot be
136 * evaluated correctly. Reject as inapplicable rather than risk a
137 * misleading status.
138 */
139 if(idp->indirect_crl()) {
140 return {false, false};
141 }
142
143 /*
144 * Full reason coverage additionally requires the IDP to omit onlySomeReasons.
145 * RFC 5280 6.3.3(d) computes the reason mask per (DP, IDP) pair, so a CRL
146 * that is reason-limited on either side cannot alone prove non-revocation
147 * across every reason; reason-mask accumulation across multiple CRLs per
148 * 6.3.3(d)-(l) is not yet implemented.
149 */
150 const bool only_some_reasons = idp->only_some_reasons().has_value();
151 const bool full = match.any_with_absent_reasons && !only_some_reasons;
152 return {true, full};
153}
154
155/**
156 * Lazy DFS iterator that yields certificate paths one at a time.
157 *
158 * Build all possible certificate paths from the end certificate to self-signed trusted roots.
159 *
160 * Basically, a DFS is performed starting from the end certificate. A stack (vector)
161 * serves to control the DFS. At the beginning of each iteration, a pair is popped from
162 * the stack that contains (1) the next certificate to add to the path (2) a bool that
163 * indicates if the certificate is part of a trusted certstore. Ideally, we follow the
164 * unique issuer of the current certificate until a trusted root is reached. However, the
165 * issuer DN + authority key id need not be unique among the certificates used for
166 * building the path. In such a case, we consider all the matching issuers by pushing
167 * <IssuerCert, trusted?> on the stack for each of them.
168 *
169 * Each call to next() resumes the search and returns the next discovered path, or nullopt
170 * when the search space is exhausted.
171*/
172class CertificatePathBuilder final {
173 public:
174 CertificatePathBuilder(const std::vector<Certificate_Store*>& trusted_certstores,
175 const X509_Certificate& end_entity,
176 std::span<const X509_Certificate> end_entity_extra,
177 size_t dfs_budget,
178 bool require_self_signed) :
179 m_trusted_certstores(trusted_certstores),
180 m_require_self_signed(require_self_signed),
181 m_dfs_budget(dfs_budget) {
182 BOTAN_ARG_CHECK(m_dfs_budget > 0, "DFS budget must be non-zero");
183
184 if(std::ranges::any_of(trusted_certstores, [](auto* ptr) { return ptr == nullptr; })) {
185 throw Invalid_Argument("Certificate store list must not contain nullptr");
186 }
187
188 for(const auto& cert : end_entity_extra) {
189 if(!cert_in_any_trusted_store(cert)) {
190 m_ee_extras.add_certificate(cert);
191 }
192 }
193
194 m_stack.push_back({end_entity, cert_in_any_trusted_store(end_entity)});
195 }
196
197 std::optional<std::vector<X509_Certificate>> next() {
198 while(!m_stack.empty()) {
199 if(m_dfs_budget == 0) {
200 // Intentionally overwrite any previous builder error
202 return std::nullopt;
203 }
204
205 BOTAN_ASSERT_NOMSG(m_dfs_budget > 0);
206 m_dfs_budget -= 1;
207
208 auto [last, trusted] = std::move(m_stack.back()); // move before pop_back
209 m_stack.pop_back();
210
211 // Found a deletion marker that guides the DFS, backtracking
212 if(!last.has_value()) {
213 m_certs_seen.erase(m_path_so_far.back().tag());
214 m_path_so_far.pop_back();
215 continue;
216 }
217
218 // Certificate already seen in this path?
219 const auto tag = last->tag();
220 if(m_certs_seen.contains(tag)) {
221 if(!m_error.has_value()) {
223 }
224 continue;
225 }
226
227 // A valid path has been discovered. It includes endpoints that may end
228 // with either a self-signed or a non-self-signed certificate. For
229 // certificates that are not self-signed, additional paths could
230 // potentially extend from the current one.
231 if(trusted) {
232 auto path = m_path_so_far;
233 path.push_back(*last);
234 push_issuers(*last);
235
236 if(!m_require_self_signed || last->is_self_signed()) {
237 return path;
238 }
239
240 /*
241 This unconditionally overwrites the error because it's likely the most
242 informative error in this context - we found a path that seemed entirely
243 suitable, except that self-signed roots are required so it was skipped.
244 */
246 continue;
247 }
248
249 if(last->is_self_signed()) {
250 if(!m_error.has_value()) {
252 }
253 continue;
254 }
255
256 push_issuers(*last);
257 }
258
259 return std::nullopt;
260 }
261
262 /**
263 * Return the first error encountered during path building
264 *
265 * Only used as a last resort if there were no successful paths
266 */
267 Certificate_Status_Code error() const {
268 if(m_error.has_value()) {
269 // Confirm it is an actual error code and not accidentally OK...
270 BOTAN_ASSERT_NOMSG(static_cast<uint32_t>(m_error.value()) >= 3000);
271 return m_error.value();
272 } else {
274 }
275 }
276
277 private:
278 bool cert_in_any_trusted_store(const X509_Certificate& cert) const {
279 return std::ranges::any_of(m_trusted_certstores,
280 [&](const Certificate_Store* store) { return store->contains(cert); });
281 }
282
283 void push_issuers(const X509_Certificate& cert) {
284 const X509_DN& issuer_dn = cert.issuer_dn();
285 const std::vector<uint8_t>& auth_key_id = cert.authority_key_id();
286
287 // Common case is a single trusted store; steal its buffer and only
288 // move-append if multiple stores return matches.
289 std::vector<X509_Certificate> trusted_issuers;
290 for(const Certificate_Store* store : m_trusted_certstores) {
291 auto new_issuers = store->find_all_certs(issuer_dn, auth_key_id);
292 if(trusted_issuers.empty()) {
293 trusted_issuers = std::move(new_issuers);
294 } else {
295 trusted_issuers.insert(trusted_issuers.end(),
296 std::make_move_iterator(new_issuers.begin()),
297 std::make_move_iterator(new_issuers.end()));
298 }
299 }
300
301 // Search the supplemental certs
302 const std::vector<X509_Certificate> misc_issuers = m_ee_extras.find_all_certs(issuer_dn, auth_key_id);
303
304 // If we could not find any issuers, the current path ends here
305 if(trusted_issuers.empty() && misc_issuers.empty()) {
306 if(!m_error.has_value()) {
308 }
309 return;
310 }
311
312 m_path_so_far.push_back(cert);
313 m_certs_seen.emplace(cert.tag());
314
315 // Push a deletion marker on the stack for backtracking later
316 m_stack.push_back({std::nullopt, false});
317
318 // The stack is LIFO so push trusted issuers last; preferring them
319 // keeps the DFS from wandering through cross-signed CAs when the
320 // trust anchor issued the certificate directly.
321 for(const auto& misc : misc_issuers) {
322 m_stack.push_back({misc, false});
323 }
324 for(const auto& trusted_cert : trusted_issuers) {
325 m_stack.push_back({trusted_cert, true});
326 }
327 }
328
329 const std::vector<Certificate_Store*> m_trusted_certstores;
330 const bool m_require_self_signed;
331 Certificate_Store_In_Memory m_ee_extras;
332 std::vector<std::pair<std::optional<X509_Certificate>, bool>> m_stack;
333 std::vector<X509_Certificate> m_path_so_far;
334 std::unordered_set<X509_Certificate::Tag, X509_Certificate::TagHash> m_certs_seen;
335 std::optional<Certificate_Status_Code> m_error;
336 size_t m_dfs_budget = 0;
337};
338
339} // namespace
340
341/*
342* PKIX path validation
343*/
344CertificatePathStatusCodes PKIX::check_chain(const std::vector<X509_Certificate>& cert_path,
345 std::chrono::system_clock::time_point ref_time,
346 std::string_view hostname,
347 Usage_Type usage,
348 const Path_Validation_Restrictions& restrictions) {
349 if(cert_path.empty()) {
350 throw Invalid_Argument("PKIX::check_chain cert_path empty");
351 }
352
353 const bool is_end_entity_trust_anchor = (cert_path.size() == 1);
354
355 const X509_Time validation_time(ref_time);
356
357 CertificatePathStatusCodes cert_status(cert_path.size());
358
359 // Before anything else verify the entire chain of signatures
360 for(size_t i = 0; i != cert_path.size(); ++i) {
361 std::set<Certificate_Status_Code>& status = cert_status.at(i);
362
363 const bool at_trust_anchor = (i == cert_path.size() - 1);
364
365 const X509_Certificate& subject = cert_path[i];
366
367 // If using intermediate CAs as trust anchors, the signature of the trust
368 // anchor cannot be verified since the issuer is not part of the
369 // certificate chain
370 if(!restrictions.require_self_signed_trust_anchors() && at_trust_anchor && !subject.is_self_signed()) {
371 continue;
372 }
373
374 const X509_Certificate& issuer = cert_path[at_trust_anchor ? (i) : (i + 1)];
375
376 // Check the signature algorithm is known
377 if(!subject.signature_algorithm().oid().registered_oid()) {
379 } else {
380 std::unique_ptr<Public_Key> issuer_key;
381 try {
382 issuer_key = issuer.subject_public_key();
383 } catch(...) {
385 }
386
387 if(issuer_key) {
388 if(issuer_key->estimated_strength() < restrictions.minimum_key_strength()) {
390 }
391
392 const auto sig_status = subject.verify_signature(*issuer_key);
393
394 if(sig_status.first != Certificate_Status_Code::VERIFIED) {
395 status.insert(sig_status.first);
396 } else {
397 // Signature is valid, check if hash used was acceptable
398 const std::string hash_used_for_signature = sig_status.second;
399 BOTAN_ASSERT_NOMSG(!hash_used_for_signature.empty());
400 const auto& trusted_hashes = restrictions.trusted_hashes();
401
402 // Ignore untrusted hashes on self-signed roots
403 if(!trusted_hashes.empty() && !at_trust_anchor) {
404 if(!trusted_hashes.contains(hash_used_for_signature)) {
406 }
407 }
408 }
409 }
410 }
411 }
412
413 // If any of the signatures were invalid, return immediately; we know the
414 // chain is invalid and signature failure is always considered the most
415 // critical result. This does mean other problems in the certificate (eg
416 // expired) will not be reported, but we'd have to assume any such data is
417 // anyway arbitrary considering we couldn't verify the signature chain
418
419 for(size_t i = 0; i != cert_path.size(); ++i) {
420 for(auto status : cert_status.at(i)) {
421 // This ignores errors relating to the key or hash being weak since
422 // these are somewhat advisory
423 if(static_cast<uint32_t>(status) >= 5000) {
424 return cert_status;
425 }
426 }
427 }
428
429 if(!hostname.empty() && !cert_path[0].matches_dns_name(hostname)) {
430 cert_status[0].insert(Certificate_Status_Code::CERT_NAME_NOMATCH);
431 }
432
433 if(!cert_path[0].allowed_usage(usage)) {
434 if(usage == Usage_Type::OCSP_RESPONDER) {
436 }
437 cert_status[0].insert(Certificate_Status_Code::INVALID_USAGE);
438 }
439
440 if(cert_path[0].has_constraints(Key_Constraints::KeyCertSign) && cert_path[0].is_CA_cert() == false) {
441 /*
442 "If the keyCertSign bit is asserted, then the cA bit in the
443 basic constraints extension (Section 4.2.1.9) MUST also be
444 asserted." - RFC 5280
445
446 We don't bother doing this check on the rest of the path since they
447 must have the cA bit asserted or the validation will fail anyway.
448 */
449 cert_status[0].insert(Certificate_Status_Code::INVALID_USAGE);
450 }
451
452 for(size_t i = 0; i != cert_path.size(); ++i) {
453 std::set<Certificate_Status_Code>& status = cert_status.at(i);
454
455 const bool at_trust_anchor = (i == cert_path.size() - 1);
456
457 const X509_Certificate& subject = cert_path[i];
458 const auto issuer = [&]() -> std::optional<X509_Certificate> {
459 if(!at_trust_anchor) {
460 return cert_path[i + 1];
461 } else if(subject.is_self_signed()) {
462 return cert_path[i];
463 } else {
464 return {}; // Non self-signed trust anchors have no checkable issuers.
465 }
466 }();
467
468 if(restrictions.require_self_signed_trust_anchors() && !issuer.has_value()) {
470 }
471
472 // This should never happen; it indicates a bug in path building
473 if(issuer.has_value() && subject.issuer_dn() != issuer->subject_dn()) {
475 }
476
477 // Check the serial number
478 if(subject.serial().is_negative()) {
480 }
481
482 // Check the subject's DN components' length
483
484 for(const auto& rdn : subject.subject_dn().rdns()) {
485 for(const auto& ava : rdn) {
486 const size_t dn_ub = X509_DN::lookup_ub(ava.first);
487 if(dn_ub > 0 && ava.second.size() > dn_ub) {
489 }
490 }
491 }
492
493 // If so configured, allow trust anchors outside the validity period with
494 // a warning rather than a hard error
495 const bool enforce_validity_period = !at_trust_anchor || !restrictions.ignore_trusted_root_time_range();
496 // Check all certs for valid time range
497 if(validation_time < subject.not_before()) {
498 if(enforce_validity_period) {
500 } else {
501 status.insert(Certificate_Status_Code::TRUSTED_CERT_NOT_YET_VALID); // only warn
502 }
503 }
504
505 if(validation_time > subject.not_after()) {
506 if(enforce_validity_period) {
508 } else {
509 status.insert(Certificate_Status_Code::TRUSTED_CERT_HAS_EXPIRED); // only warn
510 }
511 }
512
513 // Check issuer constraints
514 if(issuer.has_value() && !issuer->is_CA_cert() && !is_end_entity_trust_anchor) {
516 }
517
518 // Check cert extensions
519
520 if(subject.x509_version() == 1) {
521 if(subject.v2_issuer_key_id().empty() == false || subject.v2_subject_key_id().empty() == false) {
523 }
524 }
525
526 const Extensions& extensions = subject.v3_extensions();
527 if(subject.x509_version() < 3 && !extensions.get_extension_oids().empty()) {
529 }
530
531 extensions.validate(subject, issuer, cert_path, cert_status, i);
532 }
533
534 // path len check
535 size_t max_path_length = cert_path.size();
536 for(size_t i = cert_path.size() - 1; i > 0; --i) {
537 std::set<Certificate_Status_Code>& status = cert_status.at(i);
538 const X509_Certificate& subject = cert_path[i];
539
540 /*
541 * If the certificate was not self-issued, verify that max_path_length is
542 * greater than zero and decrement max_path_length by 1.
543 */
544 if(subject.subject_dn() != subject.issuer_dn()) {
545 if(max_path_length > 0) {
546 max_path_length -= 1;
547 } else {
549 }
550 }
551
552 /*
553 * If pathLenConstraint is present in the certificate and is less than max_path_length,
554 * set max_path_length to the value of pathLenConstraint.
555 */
556 if(auto path_len_constraint = subject.path_length_constraint()) {
557 max_path_length = std::min(max_path_length, *path_len_constraint);
558 }
559 }
560
561 return cert_status;
562}
563
564namespace {
565
566Certificate_Status_Code verify_ocsp_signing_cert(const X509_Certificate& signing_cert,
567 const X509_Certificate& ca,
568 const std::vector<X509_Certificate>& extra_certs,
569 const std::vector<Certificate_Store*>& certstores,
570 std::chrono::system_clock::time_point ref_time,
571 const Path_Validation_Restrictions& restrictions) {
572 // RFC 6960 4.2.2.2
573 // [Applications] MUST reject the response if the certificate
574 // required to validate the signature on the response does not
575 // meet at least one of the following criteria:
576 //
577 // 1. Matches a local configuration of OCSP signing authority
578 // for the certificate in question, or
579 if(const auto* trusted_responders = restrictions.trusted_ocsp_responders()) {
580 if(trusted_responders->contains(signing_cert)) {
582 }
583 }
584
585 // RFC 6960 4.2.2.2
586 //
587 // 2. Is the certificate of the CA that issued the certificate
588 // in question, or
589 if(signing_cert == ca) {
591 }
592
593 // RFC 6960 4.2.2.2
594 //
595 // 3. Includes a value of id-kp-OCSPSigning in an extended key
596 // usage extension and is issued by the CA that issued the
597 // certificate in question as stated above.
598
599 // Verify the delegated responder was issued by the CA that issued
600 // the certificate in question (the EKU and signature chain are
601 // verified by the path validation below).
602 //
603 // RFC 6960 4.2.2.2 again
604 //
605 // Systems relying on OCSP responses MUST recognize a delegation
606 // certificate as being issued by the CA that issued the
607 // certificate in question only if the delegation certificate
608 // and the certificate being checked for revocation were signed
609 // by the same key.
610 if(signing_cert.issuer_dn() != ca.subject_dn()) {
612 } else {
613 // If both key identifiers are available, verify they match to
614 // handle CAs that share a subject DN but have different keys
615 // (eg re-keyed or cross-certified CAs).
616 const auto& aki = signing_cert.authority_key_id();
617 const auto& ski = ca.subject_key_id();
618 if(!aki.empty() && !ski.empty() && aki != ski) {
620 }
621 }
622
623 try {
624 const auto ca_pub_key = ca.subject_public_key();
625 if(!ca_pub_key || !signing_cert.check_signature(*ca_pub_key)) {
627 }
628 } catch(...) {
630 }
631
632 // TODO: Implement OCSP revocation check of OCSP signer certificate
633 // Note: This needs special care to prevent endless loops on specifically
634 // forged chains of OCSP responses referring to each other.
635 //
636 // RFC 6960 4.2.2.2.1 seems to imply that generally OCSP checking of OCSP
637 // signers is not realistic; it suggests either using the nocheck extension,
638 // "using CRL Distribution Points if the check should be done using CRLs",
639 // or just punts with
640 // A CA may choose not to specify any method of revocation checking
641 // for the responder's certificate, in which case it would be up to
642 // the OCSP client's local security policy to decide whether that
643 // certificate should be checked for revocation or not.
644 //
645 // Currently, we're disabling OCSP-based revocation checks by setting the
646 // timeout to 0. Additionally, the library's API would not allow an
647 // application to pass in the required "second order" OCSP responses. I.e.
648 // "second order" OCSP checks would need to rely on `check_ocsp_online()`
649 // which is not an option for some applications (e.g. that require a proxy
650 // for external HTTP requests).
651 const auto ocsp_timeout = std::chrono::milliseconds::zero();
652 const auto relaxed_restrictions =
653 Path_Validation_Restrictions(false /* do not enforce revocation data */,
654 restrictions.minimum_key_strength(),
655 false /* OCSP is not available, so don't try for intermediates */,
656 restrictions.trusted_hashes(),
657 /* max_ocsp_age */ std::chrono::seconds(0),
658 /* trusted_responders */ {},
659 restrictions.ignore_trusted_root_time_range(),
661 restrictions.accept_ocsp_softfail());
662
663 const auto validation_result = x509_path_validate(concat(std::vector{signing_cert}, extra_certs),
664 relaxed_restrictions,
665 certstores,
666 {} /* hostname */,
668 ref_time,
669 ocsp_timeout);
670
671 return validation_result.result();
672}
673
674std::set<Certificate_Status_Code> evaluate_ocsp_response(const OCSP::Response& ocsp_response,
675 const X509_Certificate& subject,
676 const X509_Certificate& ca,
677 const std::vector<X509_Certificate>& cert_path,
678 const std::vector<Certificate_Store*>& certstores,
679 std::chrono::system_clock::time_point ref_time,
680 const Path_Validation_Restrictions& restrictions) {
681 // Handle softfail conditions (eg. OCSP unavailable)
682 if(auto dummy_status = ocsp_response.dummy_status()) {
683 return {dummy_status.value()};
684 }
685
686 // Find the certificate that signed this OCSP response
687 auto signing_cert = ocsp_response.find_signing_certificate(ca, restrictions.trusted_ocsp_responders());
688 if(!signing_cert) {
690 }
691
692 // Verify the signing certificate is trusted
693 auto cert_status = verify_ocsp_signing_cert(
694 signing_cert.value(), ca, concat(ocsp_response.certificates(), cert_path), certstores, ref_time, restrictions);
697 }
698
699 // Verify the cryptographic signature on the OCSP response
700 auto sig_status = ocsp_response.verify_signature(signing_cert.value(), restrictions);
702 return {sig_status};
703 }
704
705 // All checks passed, return the certificate's revocation status
706 return {ocsp_response.status_for(ca, subject, ref_time, restrictions.max_ocsp_age())};
707}
708
709} // namespace
710
711CertificatePathStatusCodes PKIX::check_ocsp(const std::vector<X509_Certificate>& cert_path,
712 const std::vector<std::optional<OCSP::Response>>& ocsp_responses,
713 const std::vector<Certificate_Store*>& certstores,
714 std::chrono::system_clock::time_point ref_time,
715 const Path_Validation_Restrictions& restrictions) {
716 if(cert_path.empty()) {
717 throw Invalid_Argument("PKIX::check_ocsp cert_path empty");
718 }
719
720 CertificatePathStatusCodes cert_status(cert_path.size() - 1);
721
722 for(size_t i = 0; i != cert_path.size() - 1; ++i) {
723 const X509_Certificate& subject = cert_path.at(i);
724 const X509_Certificate& ca = cert_path.at(i + 1);
725
726 if(subject.skip_revocation_check()) {
727 continue;
728 }
729
730 if(i < ocsp_responses.size() && ocsp_responses.at(i).has_value() &&
731 ocsp_responses.at(i)->status() == OCSP::Response_Status_Code::Successful) {
732 try {
733 cert_status.at(i) = evaluate_ocsp_response(
734 ocsp_responses.at(i).value(), subject, ca, cert_path, certstores, ref_time, restrictions);
735 } catch(Exception&) {
736 cert_status.at(i).insert(Certificate_Status_Code::OCSP_RESPONSE_INVALID);
737 }
738 }
739 }
740
741 return cert_status;
742}
743
744CertificatePathStatusCodes PKIX::check_crl(const std::vector<X509_Certificate>& cert_path,
745 const std::vector<std::optional<X509_CRL>>& crls,
746 std::chrono::system_clock::time_point ref_time) {
747 if(cert_path.empty()) {
748 throw Invalid_Argument("PKIX::check_crl cert_path empty");
749 }
750
751 CertificatePathStatusCodes cert_status(cert_path.size());
752 const X509_Time validation_time(ref_time);
753
754 for(size_t i = 0; i != cert_path.size() - 1; ++i) {
755 std::set<Certificate_Status_Code>& status = cert_status.at(i);
756
757 if(cert_path.at(i).skip_revocation_check()) {
758 continue;
759 }
760
761 if(i < crls.size() && crls[i].has_value()) {
762 const X509_Certificate& subject = cert_path.at(i);
763 const X509_Certificate& ca = cert_path.at(i + 1);
764
765 // RFC 5280 6.3.3 step (b)(2): if the CRL's IDP scope or
766 // distributionPoint name excludes this certificate, do not use it
767 // to determine revocation status. Treat as if no CRL was supplied
768 // so the caller's policy (strict revocation or soft fail) decides
769 // the outcome.
770 const auto applic = crl_applicability_for(*crls[i], subject);
771 if(!applic.usable) {
772 continue;
773 }
774
777 }
778
779 if(validation_time < crls[i]->this_update()) {
781 }
782
783 if(crls[i]->next_update().time_is_set() && validation_time > crls[i]->next_update()) {
785 }
786
787 auto ca_key = ca.subject_public_key();
788 if(crls[i]->check_signature(*ca_key) == false) {
790 } else {
791 /*
792 RFC 5280 5.2 "If a CRL contains a critical extension that the
793 application cannot process, then the application MUST NOT use that
794 CRL to determine the status of certificates."
795
796 RFC 5280 5.3 "If a CRL contains a critical CRL entry extension that
797 the application cannot process, then the application MUST NOT use
798 that CRL to determine the status of any certificates."
799 */
800 const bool crl_is_not_usable = crls[i]->has_unknown_critical_extension();
801
802 if(crl_is_not_usable) {
804 } else if(crls[i]->is_revoked(subject)) {
805 // A reason-limited CRL that lists the cert still proves the
806 // cert is revoked (the cert was revoked for whichever reason
807 // the CRL covers). Surface CERT_IS_REVOKED regardless of
808 // full-coverage status.
810 } else if(applic.full_coverage) {
811 // Cert not listed AND the CRL covers every reason: positive
812 // non-revocation evidence.
814 }
815 // else: cert not listed but CRL only covers some reasons. No
816 // positive evidence is recorded; the caller's policy (strict
817 // revocation -> NO_REVOCATION_DATA, soft fail -> validates)
818 // decides what happens next.
819 }
820 }
821 }
822
823 while(!cert_status.empty() && cert_status.back().empty()) {
824 cert_status.pop_back();
825 }
826
827 return cert_status;
828}
829
830CertificatePathStatusCodes PKIX::check_crl(const std::vector<X509_Certificate>& cert_path,
831 const std::vector<Certificate_Store*>& certstores,
832 std::chrono::system_clock::time_point ref_time) {
833 if(cert_path.empty()) {
834 throw Invalid_Argument("PKIX::check_crl cert_path empty");
835 }
836
837 if(certstores.empty()) {
838 throw Invalid_Argument("PKIX::check_crl certstores empty");
839 }
840
841 std::vector<std::optional<X509_CRL>> crls(cert_path.size());
842
843 for(size_t i = 0; i != cert_path.size(); ++i) {
844 if(cert_path[i].skip_revocation_check()) {
845 continue;
846 }
847 for(auto* certstore : certstores) {
848 crls[i] = certstore->find_crl_for(cert_path[i]);
849 if(crls[i]) {
850 break;
851 }
852 }
853 }
854
855 return PKIX::check_crl(cert_path, crls, ref_time);
856}
857
858#if defined(BOTAN_HAS_ONLINE_REVOCATION_CHECKS)
859
860CertificatePathStatusCodes PKIX::check_ocsp_online(const std::vector<X509_Certificate>& cert_path,
861 const std::vector<Certificate_Store*>& trusted_certstores,
862 std::chrono::system_clock::time_point ref_time,
863 std::chrono::milliseconds timeout,
864 const Path_Validation_Restrictions& restrictions) {
865 if(cert_path.empty()) {
866 throw Invalid_Argument("PKIX::check_ocsp_online cert_path empty");
867 }
868
869 std::vector<std::future<std::optional<OCSP::Response>>> ocsp_response_futures;
870
871 size_t to_ocsp = 1;
872
873 if(restrictions.ocsp_all_intermediates()) {
874 to_ocsp = cert_path.size() - 1;
875 }
876 if(cert_path.size() == 1) {
877 to_ocsp = 0;
878 }
879
880 for(size_t i = 0; i < to_ocsp; ++i) {
881 const auto& subject = cert_path.at(i);
882 const auto& issuer = cert_path.at(i + 1);
883
884 if(subject.skip_revocation_check()) {
885 ocsp_response_futures.emplace_back(
886 std::async(std::launch::deferred, []() -> std::optional<OCSP::Response> { return std::nullopt; }));
887 } else {
888 const auto ocsp_urls = URI::filter_scheme("http", subject.ocsp_responder_uris());
889
890 if(ocsp_urls.empty()) {
891 ocsp_response_futures.emplace_back(std::async(std::launch::deferred, []() -> std::optional<OCSP::Response> {
893 }));
894 } else {
895 auto ocsp_req = OCSP::Request(issuer, subject);
896 ocsp_response_futures.emplace_back(
897 std::async(std::launch::async, [ocsp_urls, ocsp_req, timeout]() -> std::optional<OCSP::Response> {
898 HTTP::Response http;
899 try {
900 http = HTTP::POST_sync(ocsp_urls[0],
901 "application/ocsp-request",
902 ocsp_req.BER_encode(),
904
905 if(http.status_code() != 200) {
907 }
908
909 OCSP::Response response(http.body());
910
911 /*
912 * RFC 6960 2.3: "In case of errors, the OCSP responder may return an
913 * error message. These messages are not signed." Since such responses
914 * (eg tryLater) carry no revocation information, treat them the same
915 * as the server being unavailable.
916 */
917 if(response.status() != OCSP::Response_Status_Code::Successful) {
919 }
920
921 return response;
922 } catch(std::exception&) {
924 }
925 }));
926 }
927 }
928 }
929
930 std::vector<std::optional<OCSP::Response>> ocsp_responses;
931 ocsp_responses.reserve(ocsp_response_futures.size());
932
933 for(auto& ocsp_response_future : ocsp_response_futures) {
934 ocsp_responses.push_back(ocsp_response_future.get());
935 }
936
937 return PKIX::check_ocsp(cert_path, ocsp_responses, trusted_certstores, ref_time, restrictions);
938}
939
940CertificatePathStatusCodes PKIX::check_crl_online(const std::vector<X509_Certificate>& cert_path,
941 const std::vector<Certificate_Store*>& certstores,
943 std::chrono::system_clock::time_point ref_time,
944 std::chrono::milliseconds timeout) {
945 if(cert_path.empty()) {
946 throw Invalid_Argument("PKIX::check_crl_online cert_path empty");
947 }
948 if(certstores.empty()) {
949 throw Invalid_Argument("PKIX::check_crl_online certstores empty");
950 }
951
952 std::vector<std::future<std::optional<X509_CRL>>> future_crls;
953 std::vector<std::optional<X509_CRL>> crls(cert_path.size());
954
955 for(size_t i = 0; i != cert_path.size(); ++i) {
956 const auto& cert = cert_path.at(i);
957
958 if(cert.skip_revocation_check()) {
959 future_crls.emplace_back(
960 std::async(std::launch::deferred, []() -> std::optional<X509_CRL> { return std::nullopt; }));
961 continue;
962 }
963
964 for(auto* certstore : certstores) {
965 crls[i] = certstore->find_crl_for(cert);
966 if(crls[i].has_value()) {
967 break;
968 }
969 }
970
971 // TODO: check if CRL is expired and re-request?
972
973 // Only request if we don't already have a CRL
974 if(crls[i]) {
975 /*
976 We already have a CRL, so just insert this empty one to hold a place in the vector
977 so that indexes match up
978 */
979 future_crls.emplace_back(std::future<std::optional<X509_CRL>>());
980 } else {
981 const auto cdp_uris = URI::filter_scheme("http", cert.crl_distribution_point_uris());
982
983 if(cdp_uris.empty()) {
984 future_crls.emplace_back(std::async(std::launch::deferred, []() -> std::optional<X509_CRL> {
985 throw Not_Implemented("No CRL distribution point for this certificate");
986 }));
987 } else {
988 future_crls.emplace_back(std::async(std::launch::async, [cdp_uris, timeout]() -> std::optional<X509_CRL> {
989 auto http = HTTP::GET_sync(
990 cdp_uris[0], HTTP::RequestLimits().set_timeout(timeout).set_max_body_size(32 * 1024 * 1024));
991
992 http.throw_unless_ok();
993 // check the mime type?
994 return X509_CRL(http.body());
995 }));
996 }
997 }
998 }
999
1000 for(size_t i = 0; i != future_crls.size(); ++i) {
1001 if(future_crls[i].valid()) {
1002 try {
1003 crls[i] = future_crls[i].get();
1004 } catch(std::exception&) {
1005 // crls[i] left null
1006 // todo: log exception e.what() ?
1007 }
1008 }
1009 }
1010
1011 auto crl_status = PKIX::check_crl(cert_path, crls, ref_time);
1012
1013 if(crl_store != nullptr) {
1014 for(size_t i = 0; i != crl_status.size(); ++i) {
1015 if(crl_status[i].contains(Certificate_Status_Code::VALID_CRL_CHECKED)) {
1016 // better be non-null, we supposedly validated it
1017 BOTAN_ASSERT_NOMSG(crls[i].has_value());
1018 crl_store->add_crl(*crls[i]);
1019 }
1020 }
1021 }
1022
1023 return crl_status;
1024}
1025
1026#endif
1027
1028Certificate_Status_Code PKIX::build_certificate_path(std::vector<X509_Certificate>& cert_path,
1029 const std::vector<Certificate_Store*>& trusted_certstores,
1030 const X509_Certificate& end_entity,
1031 const std::vector<X509_Certificate>& end_entity_extra,
1032 std::optional<size_t> max_paths) {
1033 if(max_paths.has_value() && max_paths.value() == 0) {
1035 }
1036
1037 CertificatePathBuilder builder(
1038 trusted_certstores, end_entity, end_entity_extra, PathBuildingDfsBudget, /*require_self_signed=*/false);
1039
1040 std::vector<X509_Certificate> first_path;
1041 size_t paths_examined = 0;
1042
1043 while(auto path = builder.next()) {
1044 BOTAN_ASSERT_NOMSG(path->empty() == false);
1045
1046 if(max_paths.has_value() && paths_examined >= max_paths.value()) {
1047 break;
1048 }
1049 paths_examined += 1;
1050
1051 // Prefer paths ending in self-signed certificates.
1052 if(path->back().is_self_signed()) {
1053 cert_path.insert(cert_path.end(), path->begin(), path->end());
1055 }
1056
1057 // Save the first path for later just in case we find nothing better
1058 if(first_path.empty()) {
1059 first_path = std::move(*path);
1060 }
1061 }
1062
1063 if(!first_path.empty()) {
1064 // We found a path, it's not self-signed but it's as good as can be formed...
1065 cert_path.insert(cert_path.end(), first_path.begin(), first_path.end());
1067 }
1068
1069 // Failed to build any path at all
1070 return builder.error();
1071}
1072
1073Certificate_Status_Code PKIX::build_all_certificate_paths(std::vector<std::vector<X509_Certificate>>& cert_paths_out,
1074 const std::vector<Certificate_Store*>& trusted_certstores,
1075 const X509_Certificate& end_entity,
1076 const std::vector<X509_Certificate>& end_entity_extra,
1077 std::optional<size_t> max_paths) {
1078 if(!cert_paths_out.empty()) {
1079 throw Invalid_Argument("PKIX::build_all_certificate_paths: cert_paths_out must be empty");
1080 }
1081 CertificatePathBuilder builder(
1082 trusted_certstores, end_entity, end_entity_extra, PathBuildingDfsBudget, /*require_self_signed=*/false);
1083
1084 while(auto path = builder.next()) {
1085 BOTAN_ASSERT_NOMSG(path->empty() == false);
1086 if(max_paths.has_value() && cert_paths_out.size() >= max_paths.value()) {
1087 // More paths exist than the caller permitted us to enumerate
1089 }
1090 cert_paths_out.push_back(std::move(*path));
1091 }
1092
1093 if(!cert_paths_out.empty()) {
1094 // Was able to generate at least one potential path
1096 } else {
1097 // Could not construct any potentially valid path...
1098 return builder.error();
1099 }
1100}
1101
1103 const CertificatePathStatusCodes& crl_status,
1104 const CertificatePathStatusCodes& ocsp_status,
1105 const Path_Validation_Restrictions& restrictions) {
1106 if(chain_status.empty()) {
1107 throw Invalid_Argument("PKIX::merge_revocation_status chain_status was empty");
1108 }
1109
1110 for(size_t i = 0; i != chain_status.size() - 1; ++i) {
1111 bool had_crl = false;
1112 bool had_ocsp = false;
1113
1114 // RFC 5280 6.3.3 treats revocation status as determined once cert_status
1115 // is not UNREVOKED, so CERT_IS_REVOKED (whether from CRL or OCSP) is
1116 // revocation evidence on a par with VALID_CRL_CHECKED / OCSP_RESPONSE_GOOD;
1117 // omitting it would surface a spurious NO_REVOCATION_DATA alongside the
1118 // revocation, e.g. when a reason-limited CRL lists the cert.
1119 if(i < crl_status.size() && !crl_status[i].empty()) {
1120 for(auto&& code : crl_status[i]) {
1122 had_crl = true;
1123 }
1124 chain_status[i].insert(code);
1125 }
1126 }
1127
1128 if(i < ocsp_status.size() && !ocsp_status[i].empty()) {
1129 for(auto&& code : ocsp_status[i]) {
1130 const bool was_definitive =
1132
1133 const bool was_softfail = code == Certificate_Status_Code::OCSP_NO_REVOCATION_URL ||
1136
1137 const bool accepted_softfail = was_softfail && restrictions.accept_ocsp_softfail();
1138
1139 if(was_definitive || accepted_softfail) {
1140 had_ocsp = true;
1141 }
1142
1143 chain_status[i].insert(code);
1144 }
1145 }
1146
1147 if(had_crl == false && had_ocsp == false) {
1148 if((restrictions.require_revocation_information() && i == 0) ||
1149 (restrictions.ocsp_all_intermediates() && i > 0)) {
1150 chain_status[i].insert(Certificate_Status_Code::NO_REVOCATION_DATA);
1151 }
1152 }
1153 }
1154}
1155
1157 if(cert_status.empty()) {
1158 throw Invalid_Argument("PKIX::overall_status empty cert status");
1159 }
1160
1162
1163 // take the "worst" error as overall
1164 for(const std::set<Certificate_Status_Code>& s : cert_status) {
1165 if(!s.empty()) {
1166 auto worst = *s.rbegin();
1167 // Leave informative OCSP/CRL confirmations on cert-level status only
1169 overall_status = worst;
1170 }
1171 }
1172 }
1173 return overall_status;
1174}
1175
1176Path_Validation_Result x509_path_validate(const std::vector<X509_Certificate>& end_certs,
1177 const Path_Validation_Restrictions& restrictions,
1178 const std::vector<Certificate_Store*>& trusted_roots,
1179 std::string_view hostname,
1180 Usage_Type usage,
1181 std::chrono::system_clock::time_point ref_time,
1182 std::chrono::milliseconds ocsp_timeout,
1183 const std::vector<std::optional<OCSP::Response>>& ocsp_resp) {
1184 if(end_certs.empty()) {
1185 throw Invalid_Argument("x509_path_validate called with no subjects");
1186 }
1187
1188 const X509_Certificate& end_entity = end_certs[0];
1189 const auto end_entity_extra = std::span<const X509_Certificate>(end_certs).subspan(1);
1190
1191 const bool require_self_signed = restrictions.require_self_signed_trust_anchors();
1192
1193 CertificatePathBuilder builder(
1194 trusted_roots, end_entity, end_entity_extra, PathBuildingDfsBudget, require_self_signed);
1195
1196 std::optional<Path_Validation_Result> first_path_error;
1197 size_t paths_checked = 0;
1198 size_t certs_checked = 0;
1199
1200 while(auto cert_path = builder.next()) {
1201 BOTAN_ASSERT_NOMSG(cert_path->empty() == false);
1202
1203 if(cert_path->size() > PathBuildingMaximumChainLength) {
1204 continue;
1205 }
1206
1207 paths_checked += 1;
1208 certs_checked += cert_path->size();
1209 if(paths_checked > PathBuildingMaxPathsExamined || certs_checked > PathBuildingVerificationBudget) {
1211 break;
1212 }
1213
1214 CertificatePathStatusCodes status = PKIX::check_chain(*cert_path, ref_time, hostname, usage, restrictions);
1215
1216 // Skip revocation checks if the chain already has fatal errors.
1218 const CertificatePathStatusCodes crl_status = PKIX::check_crl(*cert_path, trusted_roots, ref_time);
1219
1220 CertificatePathStatusCodes ocsp_status;
1221
1222 if(!ocsp_resp.empty()) {
1223 ocsp_status = PKIX::check_ocsp(*cert_path, ocsp_resp, trusted_roots, ref_time, restrictions);
1224 }
1225
1226 if(ocsp_timeout != std::chrono::milliseconds(0)) {
1227 const size_t to_online = restrictions.ocsp_all_intermediates() ? (cert_path->size() - 1) : 1;
1228 bool need_online = false;
1229 for(size_t i = 0; i < to_online; ++i) {
1230 if((*cert_path)[i].skip_revocation_check()) {
1231 continue;
1232 }
1233 if(i >= ocsp_status.size() || ocsp_status[i].empty()) {
1234 need_online = true;
1235 break;
1236 }
1237 }
1238
1239 if(need_online) {
1240#if defined(BOTAN_TARGET_OS_HAS_THREADS) && defined(BOTAN_HAS_HTTP_UTIL)
1241 auto online_status =
1242 PKIX::check_ocsp_online(*cert_path, trusted_roots, ref_time, ocsp_timeout, restrictions);
1243 if(ocsp_status.size() < online_status.size()) {
1244 ocsp_status.resize(online_status.size());
1245 }
1246 for(size_t i = 0; i < online_status.size(); ++i) {
1247 if(ocsp_status[i].empty()) {
1248 ocsp_status[i] = std::move(online_status[i]);
1249 }
1250 }
1251#else
1252 if(ocsp_status.size() < to_online) {
1253 ocsp_status.resize(to_online);
1254 }
1255 for(size_t i = 0; i < to_online; ++i) {
1256 if(ocsp_status[i].empty()) {
1257 ocsp_status[i].insert(Certificate_Status_Code::OCSP_NO_HTTP);
1258 }
1259 }
1260#endif
1261 }
1262 }
1263
1264 PKIX::merge_revocation_status(status, crl_status, ocsp_status, restrictions);
1265
1266 // merge_revocation_status flags NO_REVOCATION_DATA when require_revocation
1267 // is set; clear it for certs where RFC 9608 Section 4 says to skip the check.
1268 for(size_t i = 0; i + 1 < cert_path->size() && i < status.size(); ++i) {
1269 if((*cert_path)[i].skip_revocation_check()) {
1271 }
1272 }
1273 }
1274
1275 Path_Validation_Result pvd(status, std::move(*cert_path));
1276 if(pvd.successful_validation()) {
1277 return pvd;
1278 } else if(!first_path_error.has_value()) {
1279 // Save the errors from the first path we attempted
1280 first_path_error = std::move(pvd);
1281 }
1282 }
1283
1284 if(first_path_error.has_value()) {
1285 // We found at least one path, but none of them verified
1286 // Return arbitrarily the error from the first path attempted
1287 return first_path_error.value();
1288 } else {
1289 // Failed to build any path at all
1290 return Path_Validation_Result(builder.error());
1291 }
1292}
1293
1295 const Path_Validation_Restrictions& restrictions,
1296 const std::vector<Certificate_Store*>& trusted_roots,
1297 std::string_view hostname,
1298 Usage_Type usage,
1299 std::chrono::system_clock::time_point when,
1300 std::chrono::milliseconds ocsp_timeout,
1301 const std::vector<std::optional<OCSP::Response>>& ocsp_resp) {
1302 std::vector<X509_Certificate> certs;
1303 certs.push_back(end_cert);
1304 return x509_path_validate(certs, restrictions, trusted_roots, hostname, usage, when, ocsp_timeout, ocsp_resp);
1305}
1306
1307Path_Validation_Result x509_path_validate(const std::vector<X509_Certificate>& end_certs,
1308 const Path_Validation_Restrictions& restrictions,
1309 const Certificate_Store& store,
1310 std::string_view hostname,
1311 Usage_Type usage,
1312 std::chrono::system_clock::time_point when,
1313 std::chrono::milliseconds ocsp_timeout,
1314 const std::vector<std::optional<OCSP::Response>>& ocsp_resp) {
1315 std::vector<Certificate_Store*> trusted_roots;
1316 trusted_roots.push_back(const_cast<Certificate_Store*>(&store));
1317
1318 return x509_path_validate(end_certs, restrictions, trusted_roots, hostname, usage, when, ocsp_timeout, ocsp_resp);
1319}
1320
1322 const Path_Validation_Restrictions& restrictions,
1323 const Certificate_Store& store,
1324 std::string_view hostname,
1325 Usage_Type usage,
1326 std::chrono::system_clock::time_point when,
1327 std::chrono::milliseconds ocsp_timeout,
1328 const std::vector<std::optional<OCSP::Response>>& ocsp_resp) {
1329 std::vector<X509_Certificate> certs;
1330 certs.push_back(end_cert);
1331
1332 std::vector<Certificate_Store*> trusted_roots;
1333 trusted_roots.push_back(const_cast<Certificate_Store*>(&store));
1334
1335 return x509_path_validate(certs, restrictions, trusted_roots, hostname, usage, when, ocsp_timeout, ocsp_resp);
1336}
1337
1339 size_t key_strength,
1340 bool ocsp_intermediates,
1341 std::chrono::seconds max_ocsp_age,
1342 std::unique_ptr<Certificate_Store> trusted_ocsp_responders,
1345 bool accept_ocsp_softfail) :
1346 m_require_revocation_information(require_rev),
1347 m_ocsp_all_intermediates(ocsp_intermediates),
1348 m_minimum_key_strength(key_strength),
1349 m_max_ocsp_age(max_ocsp_age),
1350 m_trusted_ocsp_responders(std::move(trusted_ocsp_responders)),
1351 m_ignore_trusted_root_time_range(ignore_trusted_root_time_range),
1352 m_require_self_signed_trust_anchors(require_self_signed_trust_anchors),
1353 m_accept_ocsp_softfail(accept_ocsp_softfail) {
1354 if(key_strength <= 80) {
1355 m_trusted_hashes.insert("SHA-1");
1356 }
1357
1358 m_trusted_hashes.insert("SHA-224");
1359 m_trusted_hashes.insert("SHA-256");
1360 m_trusted_hashes.insert("SHA-384");
1361 m_trusted_hashes.insert("SHA-512");
1362 m_trusted_hashes.insert("SHAKE-256(512)"); // Dilithium/ML-DSA
1363 m_trusted_hashes.insert("SHAKE-256(912)"); // Ed448
1364
1365 // SLH-DSA-SHAKE reports the H_msg output length, which depends on the parameter set
1366 m_trusted_hashes.insert("SHAKE-256(240)"); // SLH-DSA-SHAKE-128s
1367 m_trusted_hashes.insert("SHAKE-256(272)"); // SLH-DSA-SHAKE-128f
1368 m_trusted_hashes.insert("SHAKE-256(312)"); // SLH-DSA-SHAKE-192s
1369 m_trusted_hashes.insert("SHAKE-256(336)"); // SLH-DSA-SHAKE-192f
1370 m_trusted_hashes.insert("SHAKE-256(376)"); // SLH-DSA-SHAKE-256s
1371 m_trusted_hashes.insert("SHAKE-256(392)"); // SLH-DSA-SHAKE-256f
1372}
1373
1374namespace {
1375CertificatePathStatusCodes find_warnings(const CertificatePathStatusCodes& all_statuses) {
1377 for(const auto& status_set_i : all_statuses) {
1378 std::set<Certificate_Status_Code> warning_set_i;
1379 for(const auto& code : status_set_i) {
1382 warning_set_i.insert(code);
1383 }
1384 }
1385 warnings.push_back(warning_set_i);
1386 }
1387 return warnings;
1388}
1389} // namespace
1390
1392 std::vector<X509_Certificate>&& cert_chain) :
1393 m_all_status(std::move(status)),
1394 m_warnings(find_warnings(m_all_status)),
1395 m_cert_path(std::move(cert_chain)),
1396 m_overall(PKIX::overall_status(m_all_status)) {}
1397
1399 if(m_cert_path.empty()) {
1400 throw Invalid_State("Path_Validation_Result::trust_root no path set");
1401 }
1403 throw Invalid_State("Path_Validation_Result::trust_root meaningless with invalid status");
1404 }
1405
1406 return m_cert_path[m_cert_path.size() - 1];
1407}
1408
1413
1415 for(const auto& status_set_i : m_warnings) {
1416 if(!status_set_i.empty()) {
1417 return false;
1418 }
1419 }
1420 return true;
1421}
1422
1424 return m_warnings;
1425}
1426
1428 return status_string(result());
1429}
1430
1432 if(const char* s = to_string(code)) {
1433 return s;
1434 }
1435
1436 return "Unknown error";
1437}
1438
1440 const std::string sep(", ");
1441 std::ostringstream oss;
1442 for(size_t i = 0; i < m_warnings.size(); i++) {
1443 for(auto code : m_warnings[i]) {
1444 oss << "[" << std::to_string(i) << "] " << status_string(code) << sep;
1445 }
1446 }
1447
1448 std::string res = oss.str();
1449 // remove last sep
1450 if(res.size() >= sep.size()) {
1451 res = res.substr(0, res.size() - sep.size());
1452 }
1453 return res;
1454}
1455} // namespace Botan
#define BOTAN_ASSERT_NOMSG(expr)
Definition assert.h:75
#define BOTAN_ARG_CHECK(expr, msg)
Definition assert.h:33
const OID & oid() const
Definition asn1_obj.h:688
const std::optional< ReasonFlags > & only_some_reasons() const
Definition x509_ext.h:731
void validate(const X509_Certificate &subject, const std::optional< X509_Certificate > &issuer, const std::vector< X509_Certificate > &cert_path, std::vector< std::set< Certificate_Status_Code > > &cert_status, size_t pos) const
Definition x509_ext.cpp:281
const std::vector< OID > & get_extension_oids() const
Definition pkix_types.h:903
RequestLimits & set_timeout(std::chrono::milliseconds t)
Definition http_util.h:114
RequestLimits & set_max_body_size(size_t n)
Definition http_util.h:119
static Response dummy_server_not_available_response()
Definition ocsp.h:296
static Response dummy_no_revocation_url_response()
Definition ocsp.h:304
bool registered_oid() const
Definition asn1_oid.cpp:153
BOTAN_FUTURE_EXPLICIT Path_Validation_Restrictions(bool require_rev=false, size_t minimum_key_strength=110, bool ocsp_all_intermediates=false, std::chrono::seconds max_ocsp_age=std::chrono::hours(24 *7), std::unique_ptr< Certificate_Store > trusted_ocsp_responders=nullptr, bool ignore_trusted_root_time_range=false, bool require_self_signed_trust_anchors=true, bool accept_ocsp_softfail=false)
bool require_revocation_information() const
Definition x509path.h:121
const std::set< std::string > & trusted_hashes() const
Definition x509path.h:132
std::chrono::seconds max_ocsp_age() const
Definition x509path.h:143
bool ignore_trusted_root_time_range() const
Definition x509path.h:163
bool require_self_signed_trust_anchors() const
Definition x509path.h:172
const Certificate_Store * trusted_ocsp_responders() const
Definition x509path.h:150
Certificate_Status_Code result() const
Definition x509path.h:226
Path_Validation_Result(CertificatePathStatusCodes status, std::vector< X509_Certificate > &&cert_chain)
static const char * status_string(Certificate_Status_Code code)
const X509_Certificate & trust_root() const
std::string result_string() const
std::string warnings_string() const
CertificatePathStatusCodes warnings() const
static std::vector< URI > filter_scheme(std::string_view scheme, std::span< const URI > uris)
Definition uri.cpp:367
const X509_DN & subject_dn() const
Definition x509cert.cpp:460
bool skip_revocation_check() const
Definition x509cert.cpp:452
const X509_Serial_Number & serial() const
Definition x509cert.cpp:444
const X509_Time & not_after() const
Definition x509cert.cpp:392
const std::vector< uint8_t > & authority_key_id() const
Definition x509cert.cpp:432
const std::vector< uint8_t > & subject_key_id() const
Definition x509cert.cpp:436
std::optional< size_t > path_length_constraint() const
Definition x509cert.cpp:499
const Extensions & v3_extensions() const
Definition x509cert.cpp:519
bool allowed_usage(Key_Constraints usage) const
Definition x509cert.cpp:528
const X509_DN & issuer_dn() const
Definition x509cert.cpp:456
const std::vector< uint8_t > & v2_issuer_key_id() const
Definition x509cert.cpp:400
uint32_t x509_version() const
Definition x509cert.cpp:380
bool is_self_signed() const
Definition x509cert.cpp:384
std::unique_ptr< Public_Key > subject_public_key() const
Definition x509cert.cpp:758
const std::vector< uint8_t > & v2_subject_key_id() const
Definition x509cert.cpp:404
const X509_Time & not_before() const
Definition x509cert.cpp:388
const std::vector< std::vector< std::pair< OID, ASN1_String > > > & rdns() const
Definition pkix_types.h:229
static size_t lookup_ub(const OID &oid)
const AlgorithmIdentifier & signature_algorithm() const
Definition x509_obj.cpp:73
std::pair< Certificate_Status_Code, std::string > verify_signature(const Public_Key &key) const
Definition x509_obj.cpp:130
bool check_signature(const Public_Key &key) const
Definition x509_obj.cpp:125
Response GET_sync(const URI &uri, const RequestLimits &limits)
Response POST_sync(const URI &uri, std::string_view content_type, const std::vector< uint8_t > &body, const RequestLimits &limits)
void merge_revocation_status(CertificatePathStatusCodes &chain_status, const CertificatePathStatusCodes &crl_status, const CertificatePathStatusCodes &ocsp_status, const Path_Validation_Restrictions &restrictions)
Certificate_Status_Code build_certificate_path(std::vector< X509_Certificate > &cert_path_out, const std::vector< Certificate_Store * > &trusted_certstores, const X509_Certificate &end_entity, const std::vector< X509_Certificate > &end_entity_extra, std::optional< size_t > max_paths=std::nullopt)
Certificate_Status_Code build_all_certificate_paths(std::vector< std::vector< X509_Certificate > > &cert_paths, const std::vector< Certificate_Store * > &trusted_certstores, const X509_Certificate &end_entity, const std::vector< X509_Certificate > &end_entity_extra, std::optional< size_t > max_paths=std::nullopt)
Certificate_Status_Code overall_status(const CertificatePathStatusCodes &cert_status)
CertificatePathStatusCodes check_ocsp(const std::vector< X509_Certificate > &cert_path, const std::vector< std::optional< OCSP::Response > > &ocsp_responses, const std::vector< Certificate_Store * > &certstores, std::chrono::system_clock::time_point ref_time, const Path_Validation_Restrictions &restrictions)
Definition x509path.cpp:711
CertificatePathStatusCodes check_chain(const std::vector< X509_Certificate > &cert_path, std::chrono::system_clock::time_point ref_time, std::string_view hostname, Usage_Type usage, const Path_Validation_Restrictions &restrictions)
Definition x509path.cpp:344
CertificatePathStatusCodes check_crl(const std::vector< X509_Certificate > &cert_path, const std::vector< std::optional< X509_CRL > > &crls, std::chrono::system_clock::time_point ref_time)
Definition x509path.cpp:744
std::vector< std::set< Certificate_Status_Code > > CertificatePathStatusCodes
Definition x509path.h:29
ASN1_Time X509_Time
Definition asn1_obj.h:27
Certificate_Status_Code
Definition pkix_enums.h:21
DistributionPointMatch distribution_point_match(const X509_CRL &crl, const X509_Certificate &cert)
Definition x509_crl.cpp:458
Path_Validation_Result x509_path_validate(const std::vector< X509_Certificate > &end_certs, const Path_Validation_Restrictions &restrictions, const std::vector< Certificate_Store * > &trusted_roots, std::string_view hostname, Usage_Type usage, std::chrono::system_clock::time_point ref_time, std::chrono::milliseconds ocsp_timeout, const std::vector< std::optional< OCSP::Response > > &ocsp_resp)
constexpr auto concat(Rs &&... ranges)
Definition concat_util.h:90
std::string to_string(ErrorType type)
Convert an ErrorType to string.
Definition exceptn.cpp:13