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