Botan 3.6.1
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(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;
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(subject.path_limit() != Cert_Extension::NO_CERT_PATH_LIMIT && subject.path_limit() < max_path_length) {
237 max_path_length = subject.path_limit();
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(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,
524 Certificate_Store_In_Memory* crl_store,
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) {
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
713 Certificate_Store_In_Memory ee_extras;
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
822 const CertificatePathStatusCodes& ocsp,
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, had_ocsp = false;
830
831 if(i < crl.size() && !crl[i].empty()) {
832 for(auto&& code : crl[i]) {
834 had_crl = true;
835 }
836 chain_status[i].insert(code);
837 }
838 }
839
840 if(i < ocsp.size() && !ocsp[i].empty()) {
841 for(auto&& code : ocsp[i]) {
842 // NO_REVOCATION_URL and OCSP_SERVER_NOT_AVAILABLE are softfail
846 had_ocsp = true;
847 }
848
849 chain_status[i].insert(code);
850 }
851 }
852
853 if(had_crl == false && had_ocsp == false) {
854 if((restrictions.require_revocation_information() && i == 0) ||
855 (restrictions.ocsp_all_intermediates() && i > 0)) {
856 chain_status[i].insert(Certificate_Status_Code::NO_REVOCATION_DATA);
857 }
858 }
859 }
860}
861
863 if(cert_status.empty()) {
864 throw Invalid_Argument("PKIX::overall_status empty cert status");
865 }
866
868
869 // take the "worst" error as overall
870 for(const std::set<Certificate_Status_Code>& s : cert_status) {
871 if(!s.empty()) {
872 auto worst = *s.rbegin();
873 // Leave informative OCSP/CRL confirmations on cert-level status only
875 overall_status = worst;
876 }
877 }
878 }
879 return overall_status;
880}
881
882Path_Validation_Result x509_path_validate(const std::vector<X509_Certificate>& end_certs,
883 const Path_Validation_Restrictions& restrictions,
884 const std::vector<Certificate_Store*>& trusted_roots,
885 std::string_view hostname,
886 Usage_Type usage,
887 std::chrono::system_clock::time_point ref_time,
888 std::chrono::milliseconds ocsp_timeout,
889 const std::vector<std::optional<OCSP::Response>>& ocsp_resp) {
890 if(end_certs.empty()) {
891 throw Invalid_Argument("x509_path_validate called with no subjects");
892 }
893
894 X509_Certificate end_entity = end_certs[0];
895 std::vector<X509_Certificate> end_entity_extra;
896 for(size_t i = 1; i < end_certs.size(); ++i) {
897 end_entity_extra.push_back(end_certs[i]);
898 }
899
900 std::vector<std::vector<X509_Certificate>> cert_paths;
901 Certificate_Status_Code path_building_result =
902 PKIX::build_all_certificate_paths(cert_paths, trusted_roots, end_entity, end_entity_extra);
903
904 // If we cannot successfully build a chain to a trusted self-signed root, stop now
905 if(path_building_result != Certificate_Status_Code::OK) {
906 return Path_Validation_Result(path_building_result);
907 }
908
909 std::vector<Path_Validation_Result> error_results;
910 // Try validating all the potentially valid paths and return the first one to validate properly
911 for(auto cert_path : cert_paths) {
912 CertificatePathStatusCodes status = PKIX::check_chain(cert_path, ref_time, hostname, usage, restrictions);
913
914 CertificatePathStatusCodes crl_status = PKIX::check_crl(cert_path, trusted_roots, ref_time);
915
916 CertificatePathStatusCodes ocsp_status;
917
918 if(!ocsp_resp.empty()) {
919 ocsp_status = PKIX::check_ocsp(cert_path, ocsp_resp, trusted_roots, ref_time, restrictions);
920 }
921
922 if(ocsp_status.empty() && ocsp_timeout != std::chrono::milliseconds(0)) {
923#if defined(BOTAN_TARGET_OS_HAS_THREADS) && defined(BOTAN_HAS_HTTP_UTIL)
924 ocsp_status = PKIX::check_ocsp_online(cert_path, trusted_roots, ref_time, ocsp_timeout, restrictions);
925#else
926 ocsp_status.resize(1);
927 ocsp_status[0].insert(Certificate_Status_Code::OCSP_NO_HTTP);
928#endif
929 }
930
931 PKIX::merge_revocation_status(status, crl_status, ocsp_status, restrictions);
932
933 Path_Validation_Result pvd(status, std::move(cert_path));
934 if(pvd.successful_validation()) {
935 return pvd;
936 } else {
937 error_results.push_back(std::move(pvd));
938 }
939 }
940 return error_results[0];
941}
942
944 const Path_Validation_Restrictions& restrictions,
945 const std::vector<Certificate_Store*>& trusted_roots,
946 std::string_view hostname,
947 Usage_Type usage,
948 std::chrono::system_clock::time_point when,
949 std::chrono::milliseconds ocsp_timeout,
950 const std::vector<std::optional<OCSP::Response>>& ocsp_resp) {
951 std::vector<X509_Certificate> certs;
952 certs.push_back(end_cert);
953 return x509_path_validate(certs, restrictions, trusted_roots, hostname, usage, when, ocsp_timeout, ocsp_resp);
954}
955
956Path_Validation_Result x509_path_validate(const std::vector<X509_Certificate>& end_certs,
957 const Path_Validation_Restrictions& restrictions,
958 const Certificate_Store& store,
959 std::string_view hostname,
960 Usage_Type usage,
961 std::chrono::system_clock::time_point when,
962 std::chrono::milliseconds ocsp_timeout,
963 const std::vector<std::optional<OCSP::Response>>& ocsp_resp) {
964 std::vector<Certificate_Store*> trusted_roots;
965 trusted_roots.push_back(const_cast<Certificate_Store*>(&store));
966
967 return x509_path_validate(end_certs, restrictions, trusted_roots, hostname, usage, when, ocsp_timeout, ocsp_resp);
968}
969
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<X509_Certificate> certs;
979 certs.push_back(end_cert);
980
981 std::vector<Certificate_Store*> trusted_roots;
982 trusted_roots.push_back(const_cast<Certificate_Store*>(&store));
983
984 return x509_path_validate(certs, restrictions, trusted_roots, hostname, usage, when, ocsp_timeout, ocsp_resp);
985}
986
988 size_t key_strength,
989 bool ocsp_intermediates,
990 std::chrono::seconds max_ocsp_age,
991 std::unique_ptr<Certificate_Store> trusted_ocsp_responders,
992 bool ignore_trusted_root_time_range) :
993 m_require_revocation_information(require_rev),
994 m_ocsp_all_intermediates(ocsp_intermediates),
995 m_minimum_key_strength(key_strength),
996 m_max_ocsp_age(max_ocsp_age),
997 m_trusted_ocsp_responders(std::move(trusted_ocsp_responders)),
998 m_ignore_trusted_root_time_range(ignore_trusted_root_time_range) {
999 if(key_strength <= 80) {
1000 m_trusted_hashes.insert("SHA-1");
1001 }
1002
1003 m_trusted_hashes.insert("SHA-224");
1004 m_trusted_hashes.insert("SHA-256");
1005 m_trusted_hashes.insert("SHA-384");
1006 m_trusted_hashes.insert("SHA-512");
1007 m_trusted_hashes.insert("SHAKE-256(512)"); // Dilithium/ML-DSA
1008 m_trusted_hashes.insert("SHAKE-256(912)"); // Ed448
1009}
1010
1011namespace {
1012CertificatePathStatusCodes find_warnings(const CertificatePathStatusCodes& all_statuses) {
1014 for(const auto& status_set_i : all_statuses) {
1015 std::set<Certificate_Status_Code> warning_set_i;
1016 for(const auto& code : status_set_i) {
1019 warning_set_i.insert(code);
1020 }
1021 }
1022 warnings.push_back(warning_set_i);
1023 }
1024 return warnings;
1025}
1026} // namespace
1027
1029 std::vector<X509_Certificate>&& cert_chain) :
1030 m_all_status(std::move(status)),
1031 m_warnings(find_warnings(m_all_status)),
1032 m_cert_path(std::move(cert_chain)),
1033 m_overall(PKIX::overall_status(m_all_status)) {}
1034
1036 if(m_cert_path.empty()) {
1037 throw Invalid_State("Path_Validation_Result::trust_root no path set");
1038 }
1040 throw Invalid_State("Path_Validation_Result::trust_root meaningless with invalid status");
1041 }
1042
1043 return m_cert_path[m_cert_path.size() - 1];
1044}
1045
1050
1052 for(const auto& status_set_i : m_warnings) {
1053 if(!status_set_i.empty()) {
1054 return false;
1055 }
1056 }
1057 return true;
1058}
1059
1061 return m_warnings;
1062}
1063
1065 return status_string(result());
1066}
1067
1069 if(const char* s = to_string(code)) {
1070 return s;
1071 }
1072
1073 return "Unknown error";
1074}
1075
1077 const std::string sep(", ");
1078 std::ostringstream oss;
1079 for(size_t i = 0; i < m_warnings.size(); i++) {
1080 for(auto code : m_warnings[i]) {
1081 oss << "[" << std::to_string(i) << "] " << status_string(code) << sep;
1082 }
1083 }
1084
1085 std::string res = oss.str();
1086 // remove last sep
1087 if(res.size() >= sep.size()) {
1088 res = res.substr(0, res.size() - sep.size());
1089 }
1090 return res;
1091}
1092} // namespace Botan
#define BOTAN_ASSERT_NOMSG(expr)
Definition assert.h:59
const OID & oid() const
Definition asn1_obj.h:464
static BigInt from_bytes(std::span< const uint8_t > bytes)
Definition bigint.cpp:95
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:496
std::vector< std::pair< std::unique_ptr< Certificate_Extension >, bool > > extensions() const
Definition x509_ext.cpp:224
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
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:987
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:407
std::string fingerprint(std::string_view hash_name="SHA-1") const
Definition x509cert.cpp:615
const X509_DN & subject_dn() const
Definition x509cert.cpp:395
uint32_t path_limit() const
Definition x509cert.cpp:415
const X509_Time & not_after() const
Definition x509cert.cpp:339
const std::vector< uint8_t > & authority_key_id() const
Definition x509cert.cpp:375
const Extensions & v3_extensions() const
Definition x509cert.cpp:439
std::vector< std::string > crl_distribution_points() const
Definition x509cert.cpp:526
bool allowed_usage(Key_Constraints usage) const
Definition x509cert.cpp:448
const X509_DN & issuer_dn() const
Definition x509cert.cpp:391
const std::vector< uint8_t > & v2_issuer_key_id() const
Definition x509cert.cpp:347
uint32_t x509_version() const
Definition x509cert.cpp:327
bool is_self_signed() const
Definition x509cert.cpp:331
bool is_serial_negative() const
Definition x509cert.cpp:387
std::unique_ptr< Public_Key > subject_public_key() const
Definition x509cert.cpp:589
const std::vector< uint8_t > & v2_subject_key_id() const
Definition x509cert.cpp:351
const X509_Time & not_before() const
Definition x509cert.cpp:335
static size_t lookup_ub(const OID &oid)
const std::vector< std::pair< OID, ASN1_String > > & dn_info() const
Definition pkix_types.h:73
const AlgorithmIdentifier & signature_algorithm() const
Definition x509_obj.h:47
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:862
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)
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
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:882
constexpr auto concat(Rs &&... ranges)
Definition stl_util.h:263
Usage_Type
Definition x509cert.h:22
std::string to_string(ErrorType type)
Convert an ErrorType to string.
Definition exceptn.cpp:13
Certificate_Status_Code
Definition pkix_enums.h:20