Botan 3.11.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 Jack Lloyd
4* (C) 2017 Fabian Weissberg, Rohde & Schwarz Cybersecurity
5*
6* Botan is released under the Simplified BSD License (see license.txt)
7*/
8
9#include <botan/x509path.h>
10
11#include <botan/assert.h>
12#include <botan/ocsp.h>
13#include <botan/pk_keys.h>
14#include <botan/x509_ext.h>
15#include <botan/internal/concat_util.h>
16#include <algorithm>
17#include <chrono>
18#include <set>
19#include <sstream>
20#include <string>
21#include <vector>
22
23#if defined(BOTAN_HAS_ONLINE_REVOCATION_CHECKS)
24 #include <botan/internal/http_util.h>
25 #include <future>
26#endif
27
28namespace Botan {
29
30/*
31* PKIX path validation
32*/
33CertificatePathStatusCodes PKIX::check_chain(const std::vector<X509_Certificate>& cert_path,
34 std::chrono::system_clock::time_point ref_time,
35 std::string_view hostname,
36 Usage_Type usage,
37 const Path_Validation_Restrictions& restrictions) {
38 if(cert_path.empty()) {
39 throw Invalid_Argument("PKIX::check_chain cert_path empty");
40 }
41
42 const bool is_end_entity_trust_anchor = (cert_path.size() == 1);
43
44 const X509_Time validation_time(ref_time);
45
46 CertificatePathStatusCodes cert_status(cert_path.size());
47
48 // Before anything else verify the entire chain of signatures
49 for(size_t i = 0; i != cert_path.size(); ++i) {
50 std::set<Certificate_Status_Code>& status = cert_status.at(i);
51
52 const bool at_trust_anchor = (i == cert_path.size() - 1);
53
54 const X509_Certificate& subject = cert_path[i];
55
56 // If using intermediate CAs as trust anchors, the signature of the trust
57 // anchor cannot be verified since the issuer is not part of the
58 // certificate chain
59 if(!restrictions.require_self_signed_trust_anchors() && at_trust_anchor && !subject.is_self_signed()) {
60 continue;
61 }
62
63 const X509_Certificate& issuer = cert_path[at_trust_anchor ? (i) : (i + 1)];
64
65 // Check the signature algorithm is known
66 if(!subject.signature_algorithm().oid().registered_oid()) {
68 } else {
69 std::unique_ptr<Public_Key> issuer_key;
70 try {
71 issuer_key = issuer.subject_public_key();
72 } catch(...) {
74 }
75
76 if(issuer_key) {
77 if(issuer_key->estimated_strength() < restrictions.minimum_key_strength()) {
79 }
80
81 const auto sig_status = subject.verify_signature(*issuer_key);
82
83 if(sig_status.first != Certificate_Status_Code::VERIFIED) {
84 status.insert(sig_status.first);
85 } else {
86 // Signature is valid, check if hash used was acceptable
87 const std::string hash_used_for_signature = sig_status.second;
88 BOTAN_ASSERT_NOMSG(!hash_used_for_signature.empty());
89 const auto& trusted_hashes = restrictions.trusted_hashes();
90
91 // Ignore untrusted hashes on self-signed roots
92 if(!trusted_hashes.empty() && !at_trust_anchor) {
93 if(!trusted_hashes.contains(hash_used_for_signature)) {
95 }
96 }
97 }
98 }
99 }
100 }
101
102 // If any of the signatures were invalid, return immediately; we know the
103 // chain is invalid and signature failure is always considered the most
104 // critical result. This does mean other problems in the certificate (eg
105 // expired) will not be reported, but we'd have to assume any such data is
106 // anyway arbitrary considering we couldn't verify the signature chain
107
108 for(size_t i = 0; i != cert_path.size(); ++i) {
109 for(auto status : cert_status.at(i)) {
110 // This ignores errors relating to the key or hash being weak since
111 // these are somewhat advisory
112 if(static_cast<uint32_t>(status) >= 5000) {
113 return cert_status;
114 }
115 }
116 }
117
118 if(!hostname.empty() && !cert_path[0].matches_dns_name(hostname)) {
119 cert_status[0].insert(Certificate_Status_Code::CERT_NAME_NOMATCH);
120 }
121
122 if(!cert_path[0].allowed_usage(usage)) {
123 if(usage == Usage_Type::OCSP_RESPONDER) {
125 }
126 cert_status[0].insert(Certificate_Status_Code::INVALID_USAGE);
127 }
128
129 if(cert_path[0].has_constraints(Key_Constraints::KeyCertSign) && cert_path[0].is_CA_cert() == false) {
130 /*
131 "If the keyCertSign bit is asserted, then the cA bit in the
132 basic constraints extension (Section 4.2.1.9) MUST also be
133 asserted." - RFC 5280
134
135 We don't bother doing this check on the rest of the path since they
136 must have the cA bit asserted or the validation will fail anyway.
137 */
138 cert_status[0].insert(Certificate_Status_Code::INVALID_USAGE);
139 }
140
141 for(size_t i = 0; i != cert_path.size(); ++i) {
142 std::set<Certificate_Status_Code>& status = cert_status.at(i);
143
144 const bool at_trust_anchor = (i == cert_path.size() - 1);
145
146 const X509_Certificate& subject = cert_path[i];
147 const auto issuer = [&]() -> std::optional<X509_Certificate> {
148 if(!at_trust_anchor) {
149 return cert_path[i + 1];
150 } else if(subject.is_self_signed()) {
151 return cert_path[i];
152 } else {
153 return {}; // Non self-signed trust anchors have no checkable issuers.
154 }
155 }();
156
157 if(restrictions.require_self_signed_trust_anchors() && !issuer.has_value()) {
159 }
160
161 // This should never happen; it indicates a bug in path building
162 if(issuer.has_value() && subject.issuer_dn() != issuer->subject_dn()) {
164 }
165
166 // Check the serial number
167 if(subject.is_serial_negative()) {
169 }
170
171 // Check the subject's DN components' length
172
173 for(const auto& dn_pair : subject.subject_dn().dn_info()) {
174 const size_t dn_ub = X509_DN::lookup_ub(dn_pair.first);
175 // dn_pair = <OID,str>
176 if(dn_ub > 0 && dn_pair.second.size() > dn_ub) {
178 }
179 }
180
181 // If so configured, allow trust anchors outside the validity period with
182 // a warning rather than a hard error
183 const bool enforce_validity_period = !at_trust_anchor || !restrictions.ignore_trusted_root_time_range();
184 // Check all certs for valid time range
185 if(validation_time < subject.not_before()) {
186 if(enforce_validity_period) {
188 } else {
189 status.insert(Certificate_Status_Code::TRUSTED_CERT_NOT_YET_VALID); // only warn
190 }
191 }
192
193 if(validation_time > subject.not_after()) {
194 if(enforce_validity_period) {
196 } else {
197 status.insert(Certificate_Status_Code::TRUSTED_CERT_HAS_EXPIRED); // only warn
198 }
199 }
200
201 // Check issuer constraints
202 if(issuer.has_value() && !issuer->is_CA_cert() && !is_end_entity_trust_anchor) {
204 }
205
206 // Check cert extensions
207
208 if(subject.x509_version() == 1) {
209 if(subject.v2_issuer_key_id().empty() == false || subject.v2_subject_key_id().empty() == false) {
211 }
212 }
213
214 const Extensions& extensions = subject.v3_extensions();
215 const auto& extensions_vec = extensions.extensions();
216 if(subject.x509_version() < 3 && !extensions_vec.empty()) {
218 }
219
220 for(const auto& extension : extensions_vec) {
221 extension.first->validate(subject, issuer, cert_path, cert_status, i);
222 }
223
224 if(extensions_vec.size() != extensions.get_extension_oids().size()) {
226 }
227 }
228
229 // path len check
230 size_t max_path_length = cert_path.size();
231 for(size_t i = cert_path.size() - 1; i > 0; --i) {
232 std::set<Certificate_Status_Code>& status = cert_status.at(i);
233 const X509_Certificate& subject = cert_path[i];
234
235 /*
236 * If the certificate was not self-issued, verify that max_path_length is
237 * greater than zero and decrement max_path_length by 1.
238 */
239 if(subject.subject_dn() != subject.issuer_dn()) {
240 if(max_path_length > 0) {
241 max_path_length -= 1;
242 } else {
244 }
245 }
246
247 /*
248 * If pathLenConstraint is present in the certificate and is less than max_path_length,
249 * set max_path_length to the value of pathLenConstraint.
250 */
251 if(auto path_len_constraint = subject.path_length_constraint()) {
252 max_path_length = std::min(max_path_length, *path_len_constraint);
253 }
254 }
255
256 return cert_status;
257}
258
259namespace {
260
261Certificate_Status_Code verify_ocsp_signing_cert(const X509_Certificate& signing_cert,
262 const X509_Certificate& ca,
263 const std::vector<X509_Certificate>& extra_certs,
264 const std::vector<Certificate_Store*>& certstores,
265 std::chrono::system_clock::time_point ref_time,
266 const Path_Validation_Restrictions& restrictions) {
267 // RFC 6960 4.2.2.2
268 // [Applications] MUST reject the response if the certificate
269 // required to validate the signature on the response does not
270 // meet at least one of the following criteria:
271 //
272 // 1. Matches a local configuration of OCSP signing authority
273 // for the certificate in question, or
274 if(restrictions.trusted_ocsp_responders()->certificate_known(signing_cert)) {
276 }
277
278 // RFC 6960 4.2.2.2
279 //
280 // 2. Is the certificate of the CA that issued the certificate
281 // in question, or
282 if(signing_cert == ca) {
284 }
285
286 // RFC 6960 4.2.2.2
287 //
288 // 3. Includes a value of id-kp-OCSPSigning in an extended key
289 // usage extension and is issued by the CA that issued the
290 // certificate in question as stated above.
291
292 // TODO: Implement OCSP revocation check of OCSP signer certificate
293 // Note: This needs special care to prevent endless loops on specifically
294 // forged chains of OCSP responses referring to each other.
295 //
296 // Currently, we're disabling OCSP-based revocation checks by setting the
297 // timeout to 0. Additionally, the library's API would not allow an
298 // application to pass in the required "second order" OCSP responses. I.e.
299 // "second order" OCSP checks would need to rely on `check_ocsp_online()`
300 // which is not an option for some applications (e.g. that require a proxy
301 // for external HTTP requests).
302 const auto ocsp_timeout = std::chrono::milliseconds::zero();
303 const auto relaxed_restrictions =
304 Path_Validation_Restrictions(false /* do not enforce revocation data */,
305 restrictions.minimum_key_strength(),
306 false /* OCSP is not available, so don't try for intermediates */,
307 restrictions.trusted_hashes());
308
309 const auto validation_result = x509_path_validate(concat(std::vector{signing_cert}, extra_certs),
310 relaxed_restrictions,
311 certstores,
312 {} /* hostname */,
314 ref_time,
315 ocsp_timeout);
316
317 return validation_result.result();
318}
319
320std::set<Certificate_Status_Code> evaluate_ocsp_response(const OCSP::Response& ocsp_response,
321 const X509_Certificate& subject,
322 const X509_Certificate& ca,
323 const std::vector<X509_Certificate>& cert_path,
324 const std::vector<Certificate_Store*>& certstores,
325 std::chrono::system_clock::time_point ref_time,
326 const Path_Validation_Restrictions& restrictions) {
327 // Handle softfail conditions (eg. OCSP unavailable)
328 if(auto dummy_status = ocsp_response.dummy_status()) {
329 return {dummy_status.value()};
330 }
331
332 // Find the certificate that signed this OCSP response
333 auto signing_cert = ocsp_response.find_signing_certificate(ca, restrictions.trusted_ocsp_responders());
334 if(!signing_cert) {
336 }
337
338 // Verify the signing certificate is trusted
339 auto cert_status = verify_ocsp_signing_cert(
340 signing_cert.value(), ca, concat(ocsp_response.certificates(), cert_path), certstores, ref_time, restrictions);
343 }
344
345 // Verify the cryptographic signature on the OCSP response
346 auto sig_status = ocsp_response.verify_signature(signing_cert.value());
348 return {sig_status};
349 }
350
351 // All checks passed, return the certificate's revocation status
352 return {ocsp_response.status_for(ca, subject, ref_time, restrictions.max_ocsp_age())};
353}
354
355} // namespace
356
357CertificatePathStatusCodes PKIX::check_ocsp(const std::vector<X509_Certificate>& cert_path,
358 const std::vector<std::optional<OCSP::Response>>& ocsp_responses,
359 const std::vector<Certificate_Store*>& certstores,
360 std::chrono::system_clock::time_point ref_time,
361 const Path_Validation_Restrictions& restrictions) {
362 if(cert_path.empty()) {
363 throw Invalid_Argument("PKIX::check_ocsp cert_path empty");
364 }
365
366 CertificatePathStatusCodes cert_status(cert_path.size() - 1);
367
368 for(size_t i = 0; i != cert_path.size() - 1; ++i) {
369 const X509_Certificate& subject = cert_path.at(i);
370 const X509_Certificate& ca = cert_path.at(i + 1);
371
372 if(i < ocsp_responses.size() && ocsp_responses.at(i).has_value() &&
373 ocsp_responses.at(i)->status() == OCSP::Response_Status_Code::Successful) {
374 try {
375 cert_status.at(i) = evaluate_ocsp_response(
376 ocsp_responses.at(i).value(), subject, ca, cert_path, certstores, ref_time, restrictions);
377 } catch(Exception&) {
378 cert_status.at(i).insert(Certificate_Status_Code::OCSP_RESPONSE_INVALID);
379 }
380 }
381 }
382
383 while(!cert_status.empty() && cert_status.back().empty()) {
384 cert_status.pop_back();
385 }
386
387 return cert_status;
388}
389
390CertificatePathStatusCodes PKIX::check_crl(const std::vector<X509_Certificate>& cert_path,
391 const std::vector<std::optional<X509_CRL>>& crls,
392 std::chrono::system_clock::time_point ref_time) {
393 if(cert_path.empty()) {
394 throw Invalid_Argument("PKIX::check_crl cert_path empty");
395 }
396
397 CertificatePathStatusCodes cert_status(cert_path.size());
398 const X509_Time validation_time(ref_time);
399
400 for(size_t i = 0; i != cert_path.size() - 1; ++i) {
401 std::set<Certificate_Status_Code>& status = cert_status.at(i);
402
403 if(i < crls.size() && crls[i].has_value()) {
404 const X509_Certificate& subject = cert_path.at(i);
405 const X509_Certificate& ca = cert_path.at(i + 1);
406
409 }
410
411 if(validation_time < crls[i]->this_update()) {
413 }
414
415 if(crls[i]->next_update().time_is_set() && validation_time > crls[i]->next_update()) {
417 }
418
419 auto ca_key = ca.subject_public_key();
420 if(crls[i]->check_signature(*ca_key) == false) {
422 }
423
425
426 if(crls[i]->is_revoked(subject)) {
428 }
429
430 const auto dp = subject.crl_distribution_points();
431 if(!dp.empty()) {
432 const auto crl_idp = crls[i]->crl_issuing_distribution_point();
433
434 if(std::find(dp.begin(), dp.end(), crl_idp) == dp.end()) {
436 }
437 }
438
439 for(const auto& extension : crls[i]->extensions().extensions()) {
440 // XXX this is wrong - the OID might be defined but the extension not full parsed
441 // for example see #1652
442
443 // is the extension critical and unknown?
444 if(extension.second && !extension.first->oid_of().registered_oid()) {
445 /* NIST Certificate Path Valiadation Testing document: "When an implementation does not recognize a critical extension in the
446 * crlExtensions field, it shall assume that identified certificates have been revoked and are no longer valid"
447 */
449 }
450 }
451 }
452 }
453
454 while(!cert_status.empty() && cert_status.back().empty()) {
455 cert_status.pop_back();
456 }
457
458 return cert_status;
459}
460
461CertificatePathStatusCodes PKIX::check_crl(const std::vector<X509_Certificate>& cert_path,
462 const std::vector<Certificate_Store*>& certstores,
463 std::chrono::system_clock::time_point ref_time) {
464 if(cert_path.empty()) {
465 throw Invalid_Argument("PKIX::check_crl cert_path empty");
466 }
467
468 if(certstores.empty()) {
469 throw Invalid_Argument("PKIX::check_crl certstores empty");
470 }
471
472 std::vector<std::optional<X509_CRL>> crls(cert_path.size());
473
474 for(size_t i = 0; i != cert_path.size(); ++i) {
475 for(auto* certstore : certstores) {
476 crls[i] = certstore->find_crl_for(cert_path[i]);
477 if(crls[i]) {
478 break;
479 }
480 }
481 }
482
483 return PKIX::check_crl(cert_path, crls, ref_time);
484}
485
486#if defined(BOTAN_HAS_ONLINE_REVOCATION_CHECKS)
487
488CertificatePathStatusCodes PKIX::check_ocsp_online(const std::vector<X509_Certificate>& cert_path,
489 const std::vector<Certificate_Store*>& trusted_certstores,
490 std::chrono::system_clock::time_point ref_time,
491 std::chrono::milliseconds timeout,
492 const Path_Validation_Restrictions& restrictions) {
493 if(cert_path.empty()) {
494 throw Invalid_Argument("PKIX::check_ocsp_online cert_path empty");
495 }
496
497 std::vector<std::future<std::optional<OCSP::Response>>> ocsp_response_futures;
498
499 size_t to_ocsp = 1;
500
501 if(restrictions.ocsp_all_intermediates()) {
502 to_ocsp = cert_path.size() - 1;
503 }
504 if(cert_path.size() == 1) {
505 to_ocsp = 0;
506 }
507
508 for(size_t i = 0; i < to_ocsp; ++i) {
509 const X509_Certificate& subject = cert_path.at(i);
510 const X509_Certificate& issuer = cert_path.at(i + 1);
511
512 if(subject.ocsp_responder().empty()) {
513 ocsp_response_futures.emplace_back(std::async(std::launch::deferred, [&]() -> std::optional<OCSP::Response> {
515 }));
516 } else {
517 ocsp_response_futures.emplace_back(std::async(std::launch::async, [&]() -> std::optional<OCSP::Response> {
518 const OCSP::Request req(issuer, BigInt::from_bytes(subject.serial_number()));
519
520 HTTP::Response http;
521 try {
522 http = HTTP::POST_sync(subject.ocsp_responder(),
523 "application/ocsp-request",
524 req.BER_encode(),
525 /*redirects*/ 1,
526 timeout);
527 } catch(std::exception&) {
528 // log e.what() ?
529 }
530 if(http.status_code() != 200) {
532 }
533 // Check the MIME type?
534
535 return OCSP::Response(http.body());
536 }));
537 }
538 }
539
540 std::vector<std::optional<OCSP::Response>> ocsp_responses;
541 ocsp_responses.reserve(ocsp_response_futures.size());
542
543 for(auto& ocsp_response_future : ocsp_response_futures) {
544 ocsp_responses.push_back(ocsp_response_future.get());
545 }
546
547 return PKIX::check_ocsp(cert_path, ocsp_responses, trusted_certstores, ref_time, restrictions);
548}
549
550CertificatePathStatusCodes PKIX::check_crl_online(const std::vector<X509_Certificate>& cert_path,
551 const std::vector<Certificate_Store*>& certstores,
553 std::chrono::system_clock::time_point ref_time,
554 std::chrono::milliseconds timeout) {
555 if(cert_path.empty()) {
556 throw Invalid_Argument("PKIX::check_crl_online cert_path empty");
557 }
558 if(certstores.empty()) {
559 throw Invalid_Argument("PKIX::check_crl_online certstores empty");
560 }
561
562 std::vector<std::future<std::optional<X509_CRL>>> future_crls;
563 std::vector<std::optional<X509_CRL>> crls(cert_path.size());
564
565 for(size_t i = 0; i != cert_path.size(); ++i) {
566 const std::optional<X509_Certificate>& cert = cert_path.at(i);
567 for(auto* certstore : certstores) {
568 crls[i] = certstore->find_crl_for(*cert);
569 if(crls[i].has_value()) {
570 break;
571 }
572 }
573
574 // TODO: check if CRL is expired and re-request?
575
576 // Only request if we don't already have a CRL
577 if(crls[i]) {
578 /*
579 We already have a CRL, so just insert this empty one to hold a place in the vector
580 so that indexes match up
581 */
582 future_crls.emplace_back(std::future<std::optional<X509_CRL>>());
583 } else if(cert->crl_distribution_point().empty()) {
584 // Avoid creating a thread for this case
585 future_crls.emplace_back(std::async(std::launch::deferred, [&]() -> std::optional<X509_CRL> {
586 throw Not_Implemented("No CRL distribution point for this certificate");
587 }));
588 } else {
589 future_crls.emplace_back(std::async(std::launch::async, [&]() -> std::optional<X509_CRL> {
590 auto http = HTTP::GET_sync(cert->crl_distribution_point(),
591 /*redirects*/ 1,
592 timeout);
593
594 http.throw_unless_ok();
595 // check the mime type?
596 return X509_CRL(http.body());
597 }));
598 }
599 }
600
601 for(size_t i = 0; i != future_crls.size(); ++i) {
602 if(future_crls[i].valid()) {
603 try {
604 crls[i] = future_crls[i].get();
605 } catch(std::exception&) {
606 // crls[i] left null
607 // todo: log exception e.what() ?
608 }
609 }
610 }
611
612 auto crl_status = PKIX::check_crl(cert_path, crls, ref_time);
613
614 if(crl_store != nullptr) {
615 for(size_t i = 0; i != crl_status.size(); ++i) {
616 if(crl_status[i].contains(Certificate_Status_Code::VALID_CRL_CHECKED)) {
617 // better be non-null, we supposedly validated it
618 BOTAN_ASSERT_NOMSG(crls[i].has_value());
619 crl_store->add_crl(*crls[i]);
620 }
621 }
622 }
623
624 return crl_status;
625}
626
627#endif
628
629Certificate_Status_Code PKIX::build_certificate_path(std::vector<X509_Certificate>& cert_path,
630 const std::vector<Certificate_Store*>& trusted_certstores,
631 const X509_Certificate& end_entity,
632 const std::vector<X509_Certificate>& end_entity_extra) {
633 std::vector<std::vector<X509_Certificate>> all_cert_paths;
634 const auto build_all_paths_res =
635 build_all_certificate_paths(all_cert_paths, trusted_certstores, end_entity, end_entity_extra);
636
637 if(build_all_paths_res != Certificate_Status_Code::OK) {
638 return build_all_paths_res;
639 }
640 BOTAN_ASSERT_NOMSG(!all_cert_paths.empty());
641
642 // Paths ending in self-signed certificates are preferred.
643 const auto first_with_self_signed_anchor = std::ranges::find_if(all_cert_paths, [&](const auto& left_path) {
644 BOTAN_ASSERT_NOMSG(!left_path.empty());
645 return left_path.back().is_self_signed();
646 });
647 if(first_with_self_signed_anchor != all_cert_paths.end()) {
648 cert_path.insert(cert_path.end(), first_with_self_signed_anchor->begin(), first_with_self_signed_anchor->end());
649 } else {
650 cert_path.insert(cert_path.end(), all_cert_paths.front().begin(), all_cert_paths.front().end());
651 }
653}
654
655/**
656 * utilities for PKIX::build_all_certificate_paths
657 */
658namespace {
659// <certificate, trusted?>
660using cert_maybe_trusted = std::pair<std::optional<X509_Certificate>, bool>;
661} // namespace
662
663/**
664 * Build all possible certificate paths from the end certificate to self-signed trusted roots.
665 *
666 * All potentially valid paths are put into the cert_paths vector. If no potentially valid paths are found,
667 * one of the encountered errors is returned arbitrarily.
668 *
669 * todo add a path building function that returns detailed information on errors encountered while building
670 * the potentially numerous path candidates.
671 *
672 * Basically, a DFS is performed starting from the end certificate. A stack (vector) serves to control the DFS.
673 * At the beginning of each iteration, a pair is popped from the stack that contains (1) the next certificate
674 * to add to the path (2) a bool that indicates if the certificate is part of a trusted certstore. Ideally, we
675 * follow the unique issuer of the current certificate until a trusted root is reached. However, the issuer DN +
676 * authority key id need not be unique among the certificates used for building the path. In such a case,
677 * we consider all the matching issuers by pushing <IssuerCert, trusted?> on the stack for each of them.
678 *
679 */
680Certificate_Status_Code PKIX::build_all_certificate_paths(std::vector<std::vector<X509_Certificate>>& cert_paths_out,
681 const std::vector<Certificate_Store*>& trusted_certstores,
682 const X509_Certificate& end_entity,
683 const std::vector<X509_Certificate>& end_entity_extra) {
684 if(!cert_paths_out.empty()) {
685 throw Invalid_Argument("PKIX::build_all_certificate_paths: cert_paths_out must be empty");
686 }
687 if(std::ranges::any_of(trusted_certstores, [](auto* ptr) { return ptr == nullptr; })) {
688 throw Invalid_Argument("certificate store list must not contain nullptr");
689 }
690
691 auto cert_in_any_trusted_store = [&](const X509_Certificate& cert) {
692 return std::ranges::any_of(trusted_certstores,
693 [&](const Certificate_Store* store) { return store->certificate_known(cert); });
694 };
695
696 /*
697 * Pile up error messages
698 */
699 std::vector<Certificate_Status_Code> stats;
700
702 for(const auto& cert : end_entity_extra) {
703 if(!cert_in_any_trusted_store(cert)) {
704 ee_extras.add_certificate(cert);
705 }
706 }
707
708 /*
709 * This is an inelegant but functional way of preventing path loops
710 * (where C1 -> C2 -> C3 -> C1). We store a set of all the certificate
711 * fingerprints in the path. If there is a duplicate, we error out.
712 * TODO: save fingerprints in result struct? Maybe useful for blacklists, etc.
713 */
714 std::set<std::string> certs_seen;
715
716 // new certs are added and removed from the path during the DFS
717 // it is copied into cert_paths_out when we encounter a trusted root
718 std::vector<X509_Certificate> path_so_far;
719
720 std::vector<cert_maybe_trusted> stack = {{end_entity, cert_in_any_trusted_store(end_entity)}};
721
722 while(!stack.empty()) {
723 std::optional<X509_Certificate> last = stack.back().first;
724 // found a deletion marker that guides the DFS, backtracking
725 if(last == std::nullopt) {
726 stack.pop_back();
727 const std::string fprint = path_so_far.back().fingerprint("SHA-256");
728 certs_seen.erase(fprint);
729 path_so_far.pop_back();
730 }
731 // process next cert on the path
732 else {
733 const bool trusted = stack.back().second;
734 stack.pop_back();
735
736 // certificate already seen?
737 const std::string fprint = last->fingerprint("SHA-256");
738 if(certs_seen.count(fprint) == 1) {
740 // the current path ended in a loop
741 continue;
742 }
743
744 // A valid path has been discovered. It includes endpoints that may end
745 // with either a self-signed or a non-self-signed certificate. For
746 // certificates that are not self-signed, additional paths could
747 // potentially extend from the current one.
748 if(trusted) {
749 cert_paths_out.push_back(path_so_far);
750 cert_paths_out.back().push_back(*last);
751 } else if(last->is_self_signed()) {
753 continue;
754 }
755
756 const X509_DN issuer_dn = last->issuer_dn();
757 const std::vector<uint8_t> auth_key_id = last->authority_key_id();
758
759 // search for trusted issuers
760 std::vector<X509_Certificate> trusted_issuers;
761 for(const Certificate_Store* store : trusted_certstores) {
762 auto new_issuers = store->find_all_certs(issuer_dn, auth_key_id);
763 trusted_issuers.insert(trusted_issuers.end(), new_issuers.begin(), new_issuers.end());
764 }
765
766 // search the supplemental certs
767 const std::vector<X509_Certificate> misc_issuers = ee_extras.find_all_certs(issuer_dn, auth_key_id);
768
769 // if we could not find any issuers, the current path ends here
770 if(trusted_issuers.empty() && misc_issuers.empty()) {
772 continue;
773 }
774
775 // push the latest certificate onto the path_so_far
776 path_so_far.push_back(*last);
777 certs_seen.emplace(fprint);
778
779 // push a deletion marker on the stack for backtracking later
780 stack.push_back({std::optional<X509_Certificate>(), false});
781
782 for(const auto& trusted_cert : trusted_issuers) {
783 stack.push_back({trusted_cert, true});
784 }
785
786 for(const auto& misc : misc_issuers) {
787 stack.push_back({misc, false});
788 }
789 }
790 }
791
792 // could not construct any potentially valid path
793 if(cert_paths_out.empty()) {
794 if(stats.empty()) {
795 throw Internal_Error("X509 path building failed for unknown reasons");
796 } else {
797 // arbitrarily return the first error
798 return stats[0];
799 }
800 } else {
802 }
803}
804
806 const CertificatePathStatusCodes& crl_status,
807 const CertificatePathStatusCodes& ocsp_status,
808 const Path_Validation_Restrictions& restrictions) {
809 if(chain_status.empty()) {
810 throw Invalid_Argument("PKIX::merge_revocation_status chain_status was empty");
811 }
812
813 for(size_t i = 0; i != chain_status.size() - 1; ++i) {
814 bool had_crl = false;
815 bool had_ocsp = false;
816
817 if(i < crl_status.size() && !crl_status[i].empty()) {
818 for(auto&& code : crl_status[i]) {
820 had_crl = true;
821 }
822 chain_status[i].insert(code);
823 }
824 }
825
826 if(i < ocsp_status.size() && !ocsp_status[i].empty()) {
827 for(auto&& code : ocsp_status[i]) {
828 // NO_REVOCATION_URL and OCSP_SERVER_NOT_AVAILABLE are softfail
832 had_ocsp = true;
833 }
834
835 chain_status[i].insert(code);
836 }
837 }
838
839 if(had_crl == false && had_ocsp == false) {
840 if((restrictions.require_revocation_information() && i == 0) ||
841 (restrictions.ocsp_all_intermediates() && i > 0)) {
842 chain_status[i].insert(Certificate_Status_Code::NO_REVOCATION_DATA);
843 }
844 }
845 }
846}
847
849 if(cert_status.empty()) {
850 throw Invalid_Argument("PKIX::overall_status empty cert status");
851 }
852
854
855 // take the "worst" error as overall
856 for(const std::set<Certificate_Status_Code>& s : cert_status) {
857 if(!s.empty()) {
858 auto worst = *s.rbegin();
859 // Leave informative OCSP/CRL confirmations on cert-level status only
861 overall_status = worst;
862 }
863 }
864 }
865 return overall_status;
866}
867
868Path_Validation_Result x509_path_validate(const std::vector<X509_Certificate>& end_certs,
869 const Path_Validation_Restrictions& restrictions,
870 const std::vector<Certificate_Store*>& trusted_roots,
871 std::string_view hostname,
872 Usage_Type usage,
873 std::chrono::system_clock::time_point ref_time,
874 std::chrono::milliseconds ocsp_timeout,
875 const std::vector<std::optional<OCSP::Response>>& ocsp_resp) {
876 if(end_certs.empty()) {
877 throw Invalid_Argument("x509_path_validate called with no subjects");
878 }
879
880 const X509_Certificate& end_entity = end_certs[0];
881 std::vector<X509_Certificate> end_entity_extra;
882 for(size_t i = 1; i < end_certs.size(); ++i) {
883 end_entity_extra.push_back(end_certs[i]);
884 }
885
886 std::vector<std::vector<X509_Certificate>> cert_paths;
887 const Certificate_Status_Code path_building_result =
888 PKIX::build_all_certificate_paths(cert_paths, trusted_roots, end_entity, end_entity_extra);
889
890 // If we cannot successfully build a chain to a trusted self-signed root, stop now
891 if(path_building_result != Certificate_Status_Code::OK) {
892 return Path_Validation_Result(path_building_result);
893 }
894
895 // If we require trust anchors to be self-signed we need to filter all paths
896 // not ending in a self-signed certificate.
897 if(restrictions.require_self_signed_trust_anchors()) {
898 auto has_non_self_signed_trust_anchor = [](const auto& cert_path) {
899 return cert_path.empty() || !cert_path.back().is_self_signed();
900 };
901 std::erase_if(cert_paths, has_non_self_signed_trust_anchor);
902 }
903 if(cert_paths.empty()) {
905 }
906
907 std::vector<Path_Validation_Result> error_results;
908 // Try validating all the potentially valid paths and return the first one to validate properly
909 for(auto cert_path : cert_paths) {
910 CertificatePathStatusCodes status = PKIX::check_chain(cert_path, ref_time, hostname, usage, restrictions);
911
912 const CertificatePathStatusCodes crl_status = PKIX::check_crl(cert_path, trusted_roots, ref_time);
913
914 CertificatePathStatusCodes ocsp_status;
915
916 if(!ocsp_resp.empty()) {
917 ocsp_status = PKIX::check_ocsp(cert_path, ocsp_resp, trusted_roots, ref_time, restrictions);
918 }
919
920 if(ocsp_status.empty() && ocsp_timeout != std::chrono::milliseconds(0)) {
921#if defined(BOTAN_TARGET_OS_HAS_THREADS) && defined(BOTAN_HAS_HTTP_UTIL)
922 ocsp_status = PKIX::check_ocsp_online(cert_path, trusted_roots, ref_time, ocsp_timeout, restrictions);
923#else
924 ocsp_status.resize(1);
925 ocsp_status[0].insert(Certificate_Status_Code::OCSP_NO_HTTP);
926#endif
927 }
928
929 PKIX::merge_revocation_status(status, crl_status, ocsp_status, restrictions);
930
931 Path_Validation_Result pvd(status, std::move(cert_path));
932 if(pvd.successful_validation()) {
933 return pvd;
934 } else {
935 error_results.push_back(std::move(pvd));
936 }
937 }
938 return error_results[0];
939}
940
942 const Path_Validation_Restrictions& restrictions,
943 const std::vector<Certificate_Store*>& trusted_roots,
944 std::string_view hostname,
945 Usage_Type usage,
946 std::chrono::system_clock::time_point when,
947 std::chrono::milliseconds ocsp_timeout,
948 const std::vector<std::optional<OCSP::Response>>& ocsp_resp) {
949 std::vector<X509_Certificate> certs;
950 certs.push_back(end_cert);
951 return x509_path_validate(certs, restrictions, trusted_roots, hostname, usage, when, ocsp_timeout, ocsp_resp);
952}
953
954Path_Validation_Result x509_path_validate(const std::vector<X509_Certificate>& end_certs,
955 const Path_Validation_Restrictions& restrictions,
956 const Certificate_Store& store,
957 std::string_view hostname,
958 Usage_Type usage,
959 std::chrono::system_clock::time_point when,
960 std::chrono::milliseconds ocsp_timeout,
961 const std::vector<std::optional<OCSP::Response>>& ocsp_resp) {
962 std::vector<Certificate_Store*> trusted_roots;
963 trusted_roots.push_back(const_cast<Certificate_Store*>(&store));
964
965 return x509_path_validate(end_certs, restrictions, trusted_roots, hostname, usage, when, ocsp_timeout, ocsp_resp);
966}
967
969 const Path_Validation_Restrictions& restrictions,
970 const Certificate_Store& store,
971 std::string_view hostname,
972 Usage_Type usage,
973 std::chrono::system_clock::time_point when,
974 std::chrono::milliseconds ocsp_timeout,
975 const std::vector<std::optional<OCSP::Response>>& ocsp_resp) {
976 std::vector<X509_Certificate> certs;
977 certs.push_back(end_cert);
978
979 std::vector<Certificate_Store*> trusted_roots;
980 trusted_roots.push_back(const_cast<Certificate_Store*>(&store));
981
982 return x509_path_validate(certs, restrictions, trusted_roots, hostname, usage, when, ocsp_timeout, ocsp_resp);
983}
984
986 size_t key_strength,
987 bool ocsp_intermediates,
988 std::chrono::seconds max_ocsp_age,
989 std::unique_ptr<Certificate_Store> trusted_ocsp_responders,
992 m_require_revocation_information(require_rev),
993 m_ocsp_all_intermediates(ocsp_intermediates),
994 m_minimum_key_strength(key_strength),
995 m_max_ocsp_age(max_ocsp_age),
996 m_trusted_ocsp_responders(std::move(trusted_ocsp_responders)),
997 m_ignore_trusted_root_time_range(ignore_trusted_root_time_range),
998 m_require_self_signed_trust_anchors(require_self_signed_trust_anchors) {
999 if(key_strength <= 80) {
1000 m_trusted_hashes.insert("SHA-1");
1001 }
1002
1003 m_trusted_hashes.insert("SHA-224");
1004 m_trusted_hashes.insert("SHA-256");
1005 m_trusted_hashes.insert("SHA-384");
1006 m_trusted_hashes.insert("SHA-512");
1007 m_trusted_hashes.insert("SHAKE-256(512)"); // Dilithium/ML-DSA
1008 m_trusted_hashes.insert("SHAKE-256(912)"); // Ed448
1009}
1010
1011namespace {
1012CertificatePathStatusCodes find_warnings(const CertificatePathStatusCodes& all_statuses) {
1014 for(const auto& status_set_i : all_statuses) {
1015 std::set<Certificate_Status_Code> warning_set_i;
1016 for(const auto& code : status_set_i) {
1019 warning_set_i.insert(code);
1020 }
1021 }
1022 warnings.push_back(warning_set_i);
1023 }
1024 return warnings;
1025}
1026} // namespace
1027
1029 std::vector<X509_Certificate>&& cert_chain) :
1030 m_all_status(std::move(status)),
1031 m_warnings(find_warnings(m_all_status)),
1032 m_cert_path(std::move(cert_chain)),
1033 m_overall(PKIX::overall_status(m_all_status)) {}
1034
1036 if(m_cert_path.empty()) {
1037 throw Invalid_State("Path_Validation_Result::trust_root no path set");
1038 }
1040 throw Invalid_State("Path_Validation_Result::trust_root meaningless with invalid status");
1041 }
1042
1043 return m_cert_path[m_cert_path.size() - 1];
1044}
1045
1050
1052 for(const auto& status_set_i : m_warnings) {
1053 if(!status_set_i.empty()) {
1054 return false;
1055 }
1056 }
1057 return true;
1058}
1059
1061 return m_warnings;
1062}
1063
1065 return status_string(result());
1066}
1067
1069 if(const char* s = to_string(code)) {
1070 return s;
1071 }
1072
1073 return "Unknown error";
1074}
1075
1077 const std::string sep(", ");
1078 std::ostringstream oss;
1079 for(size_t i = 0; i < m_warnings.size(); i++) {
1080 for(auto code : m_warnings[i]) {
1081 oss << "[" << std::to_string(i) << "] " << status_string(code) << sep;
1082 }
1083 }
1084
1085 std::string res = oss.str();
1086 // remove last sep
1087 if(res.size() >= sep.size()) {
1088 res = res.substr(0, res.size() - sep.size());
1089 }
1090 return res;
1091}
1092} // namespace Botan
#define BOTAN_ASSERT_NOMSG(expr)
Definition assert.h:75
const OID & oid() const
Definition asn1_obj.h:407
static BigInt from_bytes(std::span< const uint8_t > bytes)
Definition bigint.cpp:83
std::vector< X509_Certificate > find_all_certs(const X509_DN &subject_dn, const std::vector< uint8_t > &key_id) const override
Definition certstor.cpp:77
void add_certificate(const X509_Certificate &cert)
Definition certstor.cpp:38
bool certificate_known(const X509_Certificate &cert) const
Definition certstor.h:80
const std::vector< OID > & get_extension_oids() const
Definition pkix_types.h:547
std::vector< std::pair< std::unique_ptr< Certificate_Extension >, bool > > extensions() const
Definition x509_ext.cpp:225
bool registered_oid() const
Definition asn1_oid.cpp:151
bool require_revocation_information() const
Definition x509path.h:108
const std::set< std::string > & trusted_hashes() const
Definition x509path.h:119
std::chrono::seconds max_ocsp_age() const
Definition x509path.h:130
bool ignore_trusted_root_time_range() const
Definition x509path.h:150
bool require_self_signed_trust_anchors() const
Definition x509path.h:159
const Certificate_Store * trusted_ocsp_responders() const
Definition x509path.h:137
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::seconds::zero(), std::unique_ptr< Certificate_Store > trusted_ocsp_responders=std::make_unique< Certificate_Store_In_Memory >(), bool ignore_trusted_root_time_range=false, bool require_self_signed_trust_anchors=true)
Definition x509path.cpp:985
Certificate_Status_Code result() const
Definition x509path.h:204
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
const X509_DN & subject_dn() const
Definition x509cert.cpp:414
const X509_Time & not_after() const
Definition x509cert.cpp:358
std::optional< size_t > path_length_constraint() const
Definition x509cert.cpp:442
const Extensions & v3_extensions() const
Definition x509cert.cpp:462
std::vector< std::string > crl_distribution_points() const
Definition x509cert.cpp:559
bool allowed_usage(Key_Constraints usage) const
Definition x509cert.cpp:471
const X509_DN & issuer_dn() const
Definition x509cert.cpp:410
const std::vector< uint8_t > & v2_issuer_key_id() const
Definition x509cert.cpp:366
uint32_t x509_version() const
Definition x509cert.cpp:346
bool is_self_signed() const
Definition x509cert.cpp:350
bool is_serial_negative() const
Definition x509cert.cpp:406
std::unique_ptr< Public_Key > subject_public_key() const
Definition x509cert.cpp:622
const std::vector< uint8_t > & v2_subject_key_id() const
Definition x509cert.cpp:370
const X509_Time & not_before() const
Definition x509cert.cpp:354
static size_t lookup_ub(const OID &oid)
const std::vector< std::pair< OID, ASN1_String > > & dn_info() const
Definition pkix_types.h:96
const AlgorithmIdentifier & signature_algorithm() const
Definition x509_obj.h:51
std::pair< Certificate_Status_Code, std::string > verify_signature(const Public_Key &key) const
Definition x509_obj.cpp:102
Response POST_sync(std::string_view url, std::string_view content_type, const std::vector< uint8_t > &body, size_t allowable_redirects, std::chrono::milliseconds timeout)
Response GET_sync(std::string_view url, size_t allowable_redirects, std::chrono::milliseconds timeout)
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)
Definition x509path.cpp:680
void merge_revocation_status(CertificatePathStatusCodes &chain_status, const CertificatePathStatusCodes &crl_status, const CertificatePathStatusCodes &ocsp_status, const Path_Validation_Restrictions &restrictions)
Definition x509path.cpp:805
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)
Definition x509path.cpp:629
Certificate_Status_Code overall_status(const CertificatePathStatusCodes &cert_status)
Definition x509path.cpp:848
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:357
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:33
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:390
std::vector< std::set< Certificate_Status_Code > > CertificatePathStatusCodes
Definition x509path.h:28
ASN1_Time X509_Time
Definition asn1_obj.h:23
Certificate_Status_Code
Definition pkix_enums.h:20
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)
Definition x509path.cpp:868
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