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