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
305} // namespace
306
307CertificatePathStatusCodes PKIX::check_ocsp(const std::vector<X509_Certificate>& cert_path,
308 const std::vector<std::optional<OCSP::Response>>& ocsp_responses,
309 const std::vector<Certificate_Store*>& certstores,
310 std::chrono::system_clock::time_point ref_time,
311 const Path_Validation_Restrictions& restrictions) {
312 if(cert_path.empty()) {
313 throw Invalid_Argument("PKIX::check_ocsp cert_path empty");
314 }
315
316 CertificatePathStatusCodes cert_status(cert_path.size() - 1);
317
318 for(size_t i = 0; i != cert_path.size() - 1; ++i) {
319 std::set<Certificate_Status_Code>& status = cert_status.at(i);
320
321 const X509_Certificate& subject = cert_path.at(i);
322 const X509_Certificate& ca = cert_path.at(i + 1);
323
324 if(i < ocsp_responses.size() && (ocsp_responses.at(i) != std::nullopt) &&
325 (ocsp_responses.at(i)->status() == OCSP::Response_Status_Code::Successful)) {
326 try {
327 const auto& ocsp_response = ocsp_responses.at(i);
328
329 if(auto dummy_status = ocsp_response->dummy_status()) {
330 // handle softfail conditions
331 status.insert(dummy_status.value());
332 } else if(auto signing_cert =
333 ocsp_response->find_signing_certificate(ca, restrictions.trusted_ocsp_responders());
334 !signing_cert) {
336 } else if(auto ocsp_signing_cert_status =
337 verify_ocsp_signing_cert(signing_cert.value(),
338 ca,
339 concat(ocsp_response->certificates(), cert_path),
340 certstores,
341 ref_time,
342 restrictions);
343 ocsp_signing_cert_status > Certificate_Status_Code::FIRST_ERROR_STATUS) {
344 status.insert(ocsp_signing_cert_status);
346 } else {
347 status.insert(ocsp_response->status_for(ca, subject, ref_time, restrictions.max_ocsp_age()));
348 }
349 } catch(Exception&) {
351 }
352 }
353 }
354
355 while(!cert_status.empty() && cert_status.back().empty()) {
356 cert_status.pop_back();
357 }
358
359 return cert_status;
360}
361
362CertificatePathStatusCodes PKIX::check_crl(const std::vector<X509_Certificate>& cert_path,
363 const std::vector<std::optional<X509_CRL>>& crls,
364 std::chrono::system_clock::time_point ref_time) {
365 if(cert_path.empty()) {
366 throw Invalid_Argument("PKIX::check_crl cert_path empty");
367 }
368
369 CertificatePathStatusCodes cert_status(cert_path.size());
370 const X509_Time validation_time(ref_time);
371
372 for(size_t i = 0; i != cert_path.size() - 1; ++i) {
373 std::set<Certificate_Status_Code>& status = cert_status.at(i);
374
375 if(i < crls.size() && crls[i].has_value()) {
376 const X509_Certificate& subject = cert_path.at(i);
377 const X509_Certificate& ca = cert_path.at(i + 1);
378
381 }
382
383 if(validation_time < crls[i]->this_update()) {
385 }
386
387 if(crls[i]->next_update().time_is_set() && validation_time > crls[i]->next_update()) {
389 }
390
391 auto ca_key = ca.subject_public_key();
392 if(crls[i]->check_signature(*ca_key) == false) {
394 }
395
397
398 if(crls[i]->is_revoked(subject)) {
400 }
401
402 const auto dp = subject.crl_distribution_points();
403 if(!dp.empty()) {
404 const auto crl_idp = crls[i]->crl_issuing_distribution_point();
405
406 if(std::find(dp.begin(), dp.end(), crl_idp) == dp.end()) {
408 }
409 }
410
411 for(const auto& extension : crls[i]->extensions().extensions()) {
412 // XXX this is wrong - the OID might be defined but the extention not full parsed
413 // for example see #1652
414
415 // is the extension critical and unknown?
416 if(extension.second && !extension.first->oid_of().registered_oid()) {
417 /* NIST Certificate Path Valiadation Testing document: "When an implementation does not recognize a critical extension in the
418 * crlExtensions field, it shall assume that identified certificates have been revoked and are no longer valid"
419 */
421 }
422 }
423 }
424 }
425
426 while(!cert_status.empty() && cert_status.back().empty()) {
427 cert_status.pop_back();
428 }
429
430 return cert_status;
431}
432
433CertificatePathStatusCodes PKIX::check_crl(const std::vector<X509_Certificate>& cert_path,
434 const std::vector<Certificate_Store*>& certstores,
435 std::chrono::system_clock::time_point ref_time) {
436 if(cert_path.empty()) {
437 throw Invalid_Argument("PKIX::check_crl cert_path empty");
438 }
439
440 if(certstores.empty()) {
441 throw Invalid_Argument("PKIX::check_crl certstores empty");
442 }
443
444 std::vector<std::optional<X509_CRL>> crls(cert_path.size());
445
446 for(size_t i = 0; i != cert_path.size(); ++i) {
447 for(auto* certstore : certstores) {
448 crls[i] = certstore->find_crl_for(cert_path[i]);
449 if(crls[i]) {
450 break;
451 }
452 }
453 }
454
455 return PKIX::check_crl(cert_path, crls, ref_time);
456}
457
458#if defined(BOTAN_HAS_ONLINE_REVOCATION_CHECKS)
459
460CertificatePathStatusCodes PKIX::check_ocsp_online(const std::vector<X509_Certificate>& cert_path,
461 const std::vector<Certificate_Store*>& trusted_certstores,
462 std::chrono::system_clock::time_point ref_time,
463 std::chrono::milliseconds timeout,
464 const Path_Validation_Restrictions& restrictions) {
465 if(cert_path.empty()) {
466 throw Invalid_Argument("PKIX::check_ocsp_online cert_path empty");
467 }
468
469 std::vector<std::future<std::optional<OCSP::Response>>> ocsp_response_futures;
470
471 size_t to_ocsp = 1;
472
473 if(restrictions.ocsp_all_intermediates()) {
474 to_ocsp = cert_path.size() - 1;
475 }
476 if(cert_path.size() == 1) {
477 to_ocsp = 0;
478 }
479
480 for(size_t i = 0; i < to_ocsp; ++i) {
481 const X509_Certificate& subject = cert_path.at(i);
482 const X509_Certificate& issuer = cert_path.at(i + 1);
483
484 if(subject.ocsp_responder().empty()) {
485 ocsp_response_futures.emplace_back(std::async(std::launch::deferred, [&]() -> std::optional<OCSP::Response> {
487 }));
488 } else {
489 ocsp_response_futures.emplace_back(std::async(std::launch::async, [&]() -> std::optional<OCSP::Response> {
490 OCSP::Request req(issuer, BigInt::from_bytes(subject.serial_number()));
491
492 HTTP::Response http;
493 try {
494 http = HTTP::POST_sync(subject.ocsp_responder(),
495 "application/ocsp-request",
496 req.BER_encode(),
497 /*redirects*/ 1,
498 timeout);
499 } catch(std::exception&) {
500 // log e.what() ?
501 }
502 if(http.status_code() != 200) {
504 }
505 // Check the MIME type?
506
507 return OCSP::Response(http.body());
508 }));
509 }
510 }
511
512 std::vector<std::optional<OCSP::Response>> ocsp_responses;
513 ocsp_responses.reserve(ocsp_response_futures.size());
514
515 for(auto& ocsp_response_future : ocsp_response_futures) {
516 ocsp_responses.push_back(ocsp_response_future.get());
517 }
518
519 return PKIX::check_ocsp(cert_path, ocsp_responses, trusted_certstores, ref_time, restrictions);
520}
521
522CertificatePathStatusCodes PKIX::check_crl_online(const std::vector<X509_Certificate>& cert_path,
523 const std::vector<Certificate_Store*>& certstores,
525 std::chrono::system_clock::time_point ref_time,
526 std::chrono::milliseconds timeout) {
527 if(cert_path.empty()) {
528 throw Invalid_Argument("PKIX::check_crl_online cert_path empty");
529 }
530 if(certstores.empty()) {
531 throw Invalid_Argument("PKIX::check_crl_online certstores empty");
532 }
533
534 std::vector<std::future<std::optional<X509_CRL>>> future_crls;
535 std::vector<std::optional<X509_CRL>> crls(cert_path.size());
536
537 for(size_t i = 0; i != cert_path.size(); ++i) {
538 const std::optional<X509_Certificate>& cert = cert_path.at(i);
539 for(auto* certstore : certstores) {
540 crls[i] = certstore->find_crl_for(*cert);
541 if(crls[i].has_value()) {
542 break;
543 }
544 }
545
546 // TODO: check if CRL is expired and re-request?
547
548 // Only request if we don't already have a CRL
549 if(crls[i]) {
550 /*
551 We already have a CRL, so just insert this empty one to hold a place in the vector
552 so that indexes match up
553 */
554 future_crls.emplace_back(std::future<std::optional<X509_CRL>>());
555 } else if(cert->crl_distribution_point().empty()) {
556 // Avoid creating a thread for this case
557 future_crls.emplace_back(std::async(std::launch::deferred, [&]() -> std::optional<X509_CRL> {
558 throw Not_Implemented("No CRL distribution point for this certificate");
559 }));
560 } else {
561 future_crls.emplace_back(std::async(std::launch::async, [&]() -> std::optional<X509_CRL> {
562 auto http = HTTP::GET_sync(cert->crl_distribution_point(),
563 /*redirects*/ 1,
564 timeout);
565
566 http.throw_unless_ok();
567 // check the mime type?
568 return X509_CRL(http.body());
569 }));
570 }
571 }
572
573 for(size_t i = 0; i != future_crls.size(); ++i) {
574 if(future_crls[i].valid()) {
575 try {
576 crls[i] = future_crls[i].get();
577 } catch(std::exception&) {
578 // crls[i] left null
579 // todo: log exception e.what() ?
580 }
581 }
582 }
583
584 auto crl_status = PKIX::check_crl(cert_path, crls, ref_time);
585
586 if(crl_store != nullptr) {
587 for(size_t i = 0; i != crl_status.size(); ++i) {
588 if(crl_status[i].contains(Certificate_Status_Code::VALID_CRL_CHECKED)) {
589 // better be non-null, we supposedly validated it
590 BOTAN_ASSERT_NOMSG(crls[i].has_value());
591 crl_store->add_crl(*crls[i]);
592 }
593 }
594 }
595
596 return crl_status;
597}
598
599#endif
600
601Certificate_Status_Code PKIX::build_certificate_path(std::vector<X509_Certificate>& cert_path,
602 const std::vector<Certificate_Store*>& trusted_certstores,
603 const X509_Certificate& end_entity,
604 const std::vector<X509_Certificate>& end_entity_extra) {
605 if(end_entity.is_self_signed()) {
607 }
608
609 /*
610 * This is an inelegant but functional way of preventing path loops
611 * (where C1 -> C2 -> C3 -> C1). We store a set of all the certificate
612 * fingerprints in the path. If there is a duplicate, we error out.
613 * TODO: save fingerprints in result struct? Maybe useful for blacklists, etc.
614 */
615 std::set<std::string> certs_seen;
616
617 cert_path.push_back(end_entity);
618 certs_seen.insert(end_entity.fingerprint("SHA-256"));
619
621 for(const auto& cert : end_entity_extra) {
622 ee_extras.add_certificate(cert);
623 }
624
625 // iterate until we reach a root or cannot find the issuer
626 for(;;) {
627 const X509_Certificate& last = cert_path.back();
628 const X509_DN issuer_dn = last.issuer_dn();
629 const std::vector<uint8_t> auth_key_id = last.authority_key_id();
630
631 std::optional<X509_Certificate> issuer;
632 bool trusted_issuer = false;
633
634 for(Certificate_Store* store : trusted_certstores) {
635 issuer = store->find_cert(issuer_dn, auth_key_id);
636 if(issuer) {
637 trusted_issuer = true;
638 break;
639 }
640 }
641
642 if(!issuer) {
643 // fall back to searching supplemental certs
644 issuer = ee_extras.find_cert(issuer_dn, auth_key_id);
645 }
646
647 if(!issuer) {
649 }
650
651 const std::string fprint = issuer->fingerprint("SHA-256");
652
653 if(certs_seen.contains(fprint)) {
654 // we already saw this certificate -> loop
656 }
657
658 certs_seen.insert(fprint);
659 cert_path.push_back(*issuer);
660
661 if(issuer->is_self_signed()) {
662 if(trusted_issuer) {
664 } else {
666 }
667 }
668 }
669}
670
671/**
672 * utilities for PKIX::build_all_certificate_paths
673 */
674namespace {
675// <certificate, trusted?>
676using cert_maybe_trusted = std::pair<std::optional<X509_Certificate>, bool>;
677} // namespace
678
679/**
680 * Build all possible certificate paths from the end certificate to self-signed trusted roots.
681 *
682 * All potentially valid paths are put into the cert_paths vector. If no potentially valid paths are found,
683 * one of the encountered errors is returned arbitrarily.
684 *
685 * todo add a path building function that returns detailed information on errors encountered while building
686 * the potentially numerous path candidates.
687 *
688 * Basically, a DFS is performed starting from the end certificate. A stack (vector) serves to control the DFS.
689 * At the beginning of each iteration, a pair is popped from the stack that contains (1) the next certificate
690 * to add to the path (2) a bool that indicates if the certificate is part of a trusted certstore. Ideally, we
691 * follow the unique issuer of the current certificate until a trusted root is reached. However, the issuer DN +
692 * authority key id need not be unique among the certificates used for building the path. In such a case,
693 * we consider all the matching issuers by pushing <IssuerCert, trusted?> on the stack for each of them.
694 *
695 */
696Certificate_Status_Code PKIX::build_all_certificate_paths(std::vector<std::vector<X509_Certificate>>& cert_paths_out,
697 const std::vector<Certificate_Store*>& trusted_certstores,
698 const std::optional<X509_Certificate>& end_entity,
699 const std::vector<X509_Certificate>& end_entity_extra) {
700 if(!cert_paths_out.empty()) {
701 throw Invalid_Argument("PKIX::build_all_certificate_paths: cert_paths_out must be empty");
702 }
703
704 if(end_entity->is_self_signed()) {
706 }
707
708 /*
709 * Pile up error messages
710 */
711 std::vector<Certificate_Status_Code> stats;
712
714 for(const auto& cert : end_entity_extra) {
715 ee_extras.add_certificate(cert);
716 }
717
718 /*
719 * This is an inelegant but functional way of preventing path loops
720 * (where C1 -> C2 -> C3 -> C1). We store a set of all the certificate
721 * fingerprints in the path. If there is a duplicate, we error out.
722 * TODO: save fingerprints in result struct? Maybe useful for blacklists, etc.
723 */
724 std::set<std::string> certs_seen;
725
726 // new certs are added and removed from the path during the DFS
727 // it is copied into cert_paths_out when we encounter a trusted root
728 std::vector<X509_Certificate> path_so_far;
729
730 // todo can we assume that the end certificate is not trusted?
731 std::vector<cert_maybe_trusted> stack = {{end_entity, false}};
732
733 while(!stack.empty()) {
734 std::optional<X509_Certificate> last = stack.back().first;
735 // found a deletion marker that guides the DFS, backtracing
736 if(last == std::nullopt) {
737 stack.pop_back();
738 std::string fprint = path_so_far.back().fingerprint("SHA-256");
739 certs_seen.erase(fprint);
740 path_so_far.pop_back();
741 }
742 // process next cert on the path
743 else {
744 const bool trusted = stack.back().second;
745 stack.pop_back();
746
747 // certificate already seen?
748 const std::string fprint = last->fingerprint("SHA-256");
749 if(certs_seen.count(fprint) == 1) {
751 // the current path ended in a loop
752 continue;
753 }
754
755 // the current path ends here
756 if(last->is_self_signed()) {
757 // found a trust anchor
758 if(trusted) {
759 cert_paths_out.push_back(path_so_far);
760 cert_paths_out.back().push_back(*last);
761
762 continue;
763 }
764 // found an untrustworthy root
765 else {
767 continue;
768 }
769 }
770
771 const X509_DN issuer_dn = last->issuer_dn();
772 const std::vector<uint8_t> auth_key_id = last->authority_key_id();
773
774 // search for trusted issuers
775 std::vector<X509_Certificate> trusted_issuers;
776 for(Certificate_Store* store : trusted_certstores) {
777 auto new_issuers = store->find_all_certs(issuer_dn, auth_key_id);
778 trusted_issuers.insert(trusted_issuers.end(), new_issuers.begin(), new_issuers.end());
779 }
780
781 // search the supplemental certs
782 std::vector<X509_Certificate> misc_issuers = ee_extras.find_all_certs(issuer_dn, auth_key_id);
783
784 // if we could not find any issuers, the current path ends here
785 if(trusted_issuers.empty() && misc_issuers.empty()) {
787 continue;
788 }
789
790 // push the latest certificate onto the path_so_far
791 path_so_far.push_back(*last);
792 certs_seen.emplace(fprint);
793
794 // push a deletion marker on the stack for backtracing later
795 stack.push_back({std::optional<X509_Certificate>(), false});
796
797 for(const auto& trusted_cert : trusted_issuers) {
798 stack.push_back({trusted_cert, true});
799 }
800
801 for(const auto& misc : misc_issuers) {
802 stack.push_back({misc, false});
803 }
804 }
805 }
806
807 // could not construct any potentially valid path
808 if(cert_paths_out.empty()) {
809 if(stats.empty()) {
810 throw Internal_Error("X509 path building failed for unknown reasons");
811 } else {
812 // arbitrarily return the first error
813 return stats[0];
814 }
815 } else {
817 }
818}
819
821 const CertificatePathStatusCodes& crl_status,
822 const CertificatePathStatusCodes& ocsp_status,
823 const Path_Validation_Restrictions& restrictions) {
824 if(chain_status.empty()) {
825 throw Invalid_Argument("PKIX::merge_revocation_status chain_status was empty");
826 }
827
828 for(size_t i = 0; i != chain_status.size() - 1; ++i) {
829 bool had_crl = false;
830 bool had_ocsp = false;
831
832 if(i < crl_status.size() && !crl_status[i].empty()) {
833 for(auto&& code : crl_status[i]) {
835 had_crl = true;
836 }
837 chain_status[i].insert(code);
838 }
839 }
840
841 if(i < ocsp_status.size() && !ocsp_status[i].empty()) {
842 for(auto&& code : ocsp_status[i]) {
843 // NO_REVOCATION_URL and OCSP_SERVER_NOT_AVAILABLE are softfail
847 had_ocsp = true;
848 }
849
850 chain_status[i].insert(code);
851 }
852 }
853
854 if(had_crl == false && had_ocsp == false) {
855 if((restrictions.require_revocation_information() && i == 0) ||
856 (restrictions.ocsp_all_intermediates() && i > 0)) {
857 chain_status[i].insert(Certificate_Status_Code::NO_REVOCATION_DATA);
858 }
859 }
860 }
861}
862
864 if(cert_status.empty()) {
865 throw Invalid_Argument("PKIX::overall_status empty cert status");
866 }
867
869
870 // take the "worst" error as overall
871 for(const std::set<Certificate_Status_Code>& s : cert_status) {
872 if(!s.empty()) {
873 auto worst = *s.rbegin();
874 // Leave informative OCSP/CRL confirmations on cert-level status only
876 overall_status = worst;
877 }
878 }
879 }
880 return overall_status;
881}
882
883Path_Validation_Result x509_path_validate(const std::vector<X509_Certificate>& end_certs,
884 const Path_Validation_Restrictions& restrictions,
885 const std::vector<Certificate_Store*>& trusted_roots,
886 std::string_view hostname,
887 Usage_Type usage,
888 std::chrono::system_clock::time_point ref_time,
889 std::chrono::milliseconds ocsp_timeout,
890 const std::vector<std::optional<OCSP::Response>>& ocsp_resp) {
891 if(end_certs.empty()) {
892 throw Invalid_Argument("x509_path_validate called with no subjects");
893 }
894
895 X509_Certificate end_entity = end_certs[0];
896 std::vector<X509_Certificate> end_entity_extra;
897 for(size_t i = 1; i < end_certs.size(); ++i) {
898 end_entity_extra.push_back(end_certs[i]);
899 }
900
901 std::vector<std::vector<X509_Certificate>> cert_paths;
902 Certificate_Status_Code path_building_result =
903 PKIX::build_all_certificate_paths(cert_paths, trusted_roots, end_entity, end_entity_extra);
904
905 // If we cannot successfully build a chain to a trusted self-signed root, stop now
906 if(path_building_result != Certificate_Status_Code::OK) {
907 return Path_Validation_Result(path_building_result);
908 }
909
910 std::vector<Path_Validation_Result> error_results;
911 // Try validating all the potentially valid paths and return the first one to validate properly
912 for(auto cert_path : cert_paths) {
913 CertificatePathStatusCodes status = PKIX::check_chain(cert_path, ref_time, hostname, usage, restrictions);
914
915 CertificatePathStatusCodes crl_status = PKIX::check_crl(cert_path, trusted_roots, ref_time);
916
917 CertificatePathStatusCodes ocsp_status;
918
919 if(!ocsp_resp.empty()) {
920 ocsp_status = PKIX::check_ocsp(cert_path, ocsp_resp, trusted_roots, ref_time, restrictions);
921 }
922
923 if(ocsp_status.empty() && ocsp_timeout != std::chrono::milliseconds(0)) {
924#if defined(BOTAN_TARGET_OS_HAS_THREADS) && defined(BOTAN_HAS_HTTP_UTIL)
925 ocsp_status = PKIX::check_ocsp_online(cert_path, trusted_roots, ref_time, ocsp_timeout, restrictions);
926#else
927 ocsp_status.resize(1);
928 ocsp_status[0].insert(Certificate_Status_Code::OCSP_NO_HTTP);
929#endif
930 }
931
932 PKIX::merge_revocation_status(status, crl_status, ocsp_status, restrictions);
933
934 Path_Validation_Result pvd(status, std::move(cert_path));
935 if(pvd.successful_validation()) {
936 return pvd;
937 } else {
938 error_results.push_back(std::move(pvd));
939 }
940 }
941 return error_results[0];
942}
943
945 const Path_Validation_Restrictions& restrictions,
946 const std::vector<Certificate_Store*>& trusted_roots,
947 std::string_view hostname,
948 Usage_Type usage,
949 std::chrono::system_clock::time_point when,
950 std::chrono::milliseconds ocsp_timeout,
951 const std::vector<std::optional<OCSP::Response>>& ocsp_resp) {
952 std::vector<X509_Certificate> certs;
953 certs.push_back(end_cert);
954 return x509_path_validate(certs, restrictions, trusted_roots, hostname, usage, when, ocsp_timeout, ocsp_resp);
955}
956
957Path_Validation_Result x509_path_validate(const std::vector<X509_Certificate>& end_certs,
958 const Path_Validation_Restrictions& restrictions,
959 const Certificate_Store& store,
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<Certificate_Store*> trusted_roots;
966 trusted_roots.push_back(const_cast<Certificate_Store*>(&store));
967
968 return x509_path_validate(end_certs, restrictions, trusted_roots, hostname, usage, when, ocsp_timeout, ocsp_resp);
969}
970
972 const Path_Validation_Restrictions& restrictions,
973 const Certificate_Store& store,
974 std::string_view hostname,
975 Usage_Type usage,
976 std::chrono::system_clock::time_point when,
977 std::chrono::milliseconds ocsp_timeout,
978 const std::vector<std::optional<OCSP::Response>>& ocsp_resp) {
979 std::vector<X509_Certificate> certs;
980 certs.push_back(end_cert);
981
982 std::vector<Certificate_Store*> trusted_roots;
983 trusted_roots.push_back(const_cast<Certificate_Store*>(&store));
984
985 return x509_path_validate(certs, restrictions, trusted_roots, hostname, usage, when, ocsp_timeout, ocsp_resp);
986}
987
989 size_t key_strength,
990 bool ocsp_intermediates,
991 std::chrono::seconds max_ocsp_age,
992 std::unique_ptr<Certificate_Store> trusted_ocsp_responders,
994 m_require_revocation_information(require_rev),
995 m_ocsp_all_intermediates(ocsp_intermediates),
996 m_minimum_key_strength(key_strength),
997 m_max_ocsp_age(max_ocsp_age),
998 m_trusted_ocsp_responders(std::move(trusted_ocsp_responders)),
999 m_ignore_trusted_root_time_range(ignore_trusted_root_time_range) {
1000 if(key_strength <= 80) {
1001 m_trusted_hashes.insert("SHA-1");
1002 }
1003
1004 m_trusted_hashes.insert("SHA-224");
1005 m_trusted_hashes.insert("SHA-256");
1006 m_trusted_hashes.insert("SHA-384");
1007 m_trusted_hashes.insert("SHA-512");
1008 m_trusted_hashes.insert("SHAKE-256(512)"); // Dilithium/ML-DSA
1009 m_trusted_hashes.insert("SHAKE-256(912)"); // Ed448
1010}
1011
1012namespace {
1013CertificatePathStatusCodes find_warnings(const CertificatePathStatusCodes& all_statuses) {
1015 for(const auto& status_set_i : all_statuses) {
1016 std::set<Certificate_Status_Code> warning_set_i;
1017 for(const auto& code : status_set_i) {
1020 warning_set_i.insert(code);
1021 }
1022 }
1023 warnings.push_back(warning_set_i);
1024 }
1025 return warnings;
1026}
1027} // namespace
1028
1030 std::vector<X509_Certificate>&& cert_chain) :
1031 m_all_status(std::move(status)),
1032 m_warnings(find_warnings(m_all_status)),
1033 m_cert_path(std::move(cert_chain)),
1034 m_overall(PKIX::overall_status(m_all_status)) {}
1035
1037 if(m_cert_path.empty()) {
1038 throw Invalid_State("Path_Validation_Result::trust_root no path set");
1039 }
1041 throw Invalid_State("Path_Validation_Result::trust_root meaningless with invalid status");
1042 }
1043
1044 return m_cert_path[m_cert_path.size() - 1];
1045}
1046
1051
1053 for(const auto& status_set_i : m_warnings) {
1054 if(!status_set_i.empty()) {
1055 return false;
1056 }
1057 }
1058 return true;
1059}
1060
1062 return m_warnings;
1063}
1064
1066 return status_string(result());
1067}
1068
1070 if(const char* s = to_string(code)) {
1071 return s;
1072 }
1073
1074 return "Unknown error";
1075}
1076
1078 const std::string sep(", ");
1079 std::ostringstream oss;
1080 for(size_t i = 0; i < m_warnings.size(); i++) {
1081 for(auto code : m_warnings[i]) {
1082 oss << "[" << std::to_string(i) << "] " << status_string(code) << sep;
1083 }
1084 }
1085
1086 std::string res = oss.str();
1087 // remove last sep
1088 if(res.size() >= sep.size()) {
1089 res = res.substr(0, res.size() - sep.size());
1090 }
1091 return res;
1092}
1093} // 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:75
std::optional< X509_Certificate > find_cert(const X509_DN &subject_dn, const std::vector< uint8_t > &key_id) const override
Definition certstor.cpp:55
void add_certificate(const X509_Certificate &cert)
Definition certstor.cpp:36
bool certificate_known(const X509_Certificate &cert) const
Definition certstor.h:70
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)
Definition x509path.cpp:988
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:820
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:601
Certificate_Status_Code overall_status(const CertificatePathStatusCodes &cert_status)
Definition x509path.cpp:863
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:307
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:696
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:362
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:883
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