aboutsummaryrefslogtreecommitdiffstats
path: root/src/libs/3rdparty/botan/src/lib/x509/x509path.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/libs/3rdparty/botan/src/lib/x509/x509path.cpp')
-rw-r--r--src/libs/3rdparty/botan/src/lib/x509/x509path.cpp1070
1 files changed, 1070 insertions, 0 deletions
diff --git a/src/libs/3rdparty/botan/src/lib/x509/x509path.cpp b/src/libs/3rdparty/botan/src/lib/x509/x509path.cpp
new file mode 100644
index 0000000000..e73fe12b60
--- /dev/null
+++ b/src/libs/3rdparty/botan/src/lib/x509/x509path.cpp
@@ -0,0 +1,1070 @@
+/*
+* X.509 Certificate Path Validation
+* (C) 2010,2011,2012,2014,2016 Jack Lloyd
+* (C) 2017 Fabian Weissberg, Rohde & Schwarz Cybersecurity
+*
+* Botan is released under the Simplified BSD License (see license.txt)
+*/
+
+#include <botan/x509path.h>
+#include <botan/x509_ext.h>
+#include <botan/pk_keys.h>
+#include <botan/ocsp.h>
+#include <botan/oids.h>
+#include <algorithm>
+#include <chrono>
+#include <vector>
+#include <set>
+#include <string>
+#include <sstream>
+
+#if defined(BOTAN_HAS_ONLINE_REVOCATION_CHECKS)
+ #include <future>
+ #include <botan/http_util.h>
+#endif
+
+namespace Botan {
+
+/*
+* PKIX path validation
+*/
+CertificatePathStatusCodes
+PKIX::check_chain(const std::vector<std::shared_ptr<const X509_Certificate>>& cert_path,
+ std::chrono::system_clock::time_point ref_time,
+ const std::string& hostname,
+ Usage_Type usage,
+ size_t min_signature_algo_strength,
+ const std::set<std::string>& trusted_hashes)
+ {
+ if(cert_path.empty())
+ throw Invalid_Argument("PKIX::check_chain cert_path empty");
+
+ const bool self_signed_ee_cert = (cert_path.size() == 1);
+
+ X509_Time validation_time(ref_time);
+
+ CertificatePathStatusCodes cert_status(cert_path.size());
+
+ if(!hostname.empty() && !cert_path[0]->matches_dns_name(hostname))
+ cert_status[0].insert(Certificate_Status_Code::CERT_NAME_NOMATCH);
+
+ if(!cert_path[0]->allowed_usage(usage))
+ cert_status[0].insert(Certificate_Status_Code::INVALID_USAGE);
+
+ if(cert_path[0]->is_CA_cert() == false &&
+ cert_path[0]->has_constraints(KEY_CERT_SIGN))
+ {
+ /*
+ "If the keyCertSign bit is asserted, then the cA bit in the
+ basic constraints extension (Section 4.2.1.9) MUST also be
+ asserted." - RFC 5280
+
+ We don't bother doing this check on the rest of the path since they
+ must have the cA bit asserted or the validation will fail anyway.
+ */
+ cert_status[0].insert(Certificate_Status_Code::INVALID_USAGE);
+ }
+
+ for(size_t i = 0; i != cert_path.size(); ++i)
+ {
+ std::set<Certificate_Status_Code>& status = cert_status.at(i);
+
+ const bool at_self_signed_root = (i == cert_path.size() - 1);
+
+ const std::shared_ptr<const X509_Certificate>& subject = cert_path[i];
+
+ const std::shared_ptr<const X509_Certificate>& issuer = cert_path[at_self_signed_root ? (i) : (i + 1)];
+
+ if(at_self_signed_root && (issuer->is_self_signed() == false))
+ {
+ status.insert(Certificate_Status_Code::CHAIN_LACKS_TRUST_ROOT);
+ }
+
+ if(subject->issuer_dn() != issuer->subject_dn())
+ {
+ status.insert(Certificate_Status_Code::CHAIN_NAME_MISMATCH);
+ }
+
+ // Check the serial number
+ if(subject->is_serial_negative())
+ {
+ status.insert(Certificate_Status_Code::CERT_SERIAL_NEGATIVE);
+ }
+
+ // Check the subject's DN components' length
+
+ for(const auto& dn_pair : subject->subject_dn().dn_info())
+ {
+ const size_t dn_ub = X509_DN::lookup_ub(dn_pair.first);
+ // dn_pair = <OID,str>
+ if(dn_ub > 0 && dn_pair.second.size() > dn_ub)
+ {
+ status.insert(Certificate_Status_Code::DN_TOO_LONG);
+ }
+ }
+
+ // Check all certs for valid time range
+ if(validation_time < subject->not_before())
+ status.insert(Certificate_Status_Code::CERT_NOT_YET_VALID);
+
+ if(validation_time > subject->not_after())
+ status.insert(Certificate_Status_Code::CERT_HAS_EXPIRED);
+
+ // Check issuer constraints
+ if(!issuer->is_CA_cert() && !self_signed_ee_cert)
+ status.insert(Certificate_Status_Code::CA_CERT_NOT_FOR_CERT_ISSUER);
+
+ std::unique_ptr<Public_Key> issuer_key(issuer->subject_public_key());
+
+ // Check the signature algorithm
+ if(OIDS::lookup(subject->signature_algorithm().oid).empty())
+ {
+ status.insert(Certificate_Status_Code::SIGNATURE_ALGO_UNKNOWN);
+ }
+ // only perform the following checks if the signature algorithm is known
+ else
+ {
+ if(!issuer_key)
+ {
+ status.insert(Certificate_Status_Code::CERT_PUBKEY_INVALID);
+ }
+ else
+ {
+ const Certificate_Status_Code sig_status = subject->verify_signature(*issuer_key);
+
+ if(sig_status != Certificate_Status_Code::VERIFIED)
+ status.insert(sig_status);
+
+ if(issuer_key->estimated_strength() < min_signature_algo_strength)
+ status.insert(Certificate_Status_Code::SIGNATURE_METHOD_TOO_WEAK);
+ }
+
+ // Ignore untrusted hashes on self-signed roots
+ if(trusted_hashes.size() > 0 && !at_self_signed_root)
+ {
+ if(trusted_hashes.count(subject->hash_used_for_signature()) == 0)
+ status.insert(Certificate_Status_Code::UNTRUSTED_HASH);
+ }
+ }
+
+ // Check cert extensions
+ Extensions extensions = subject->v3_extensions();
+ const auto& extensions_vec = extensions.extensions();
+ if(subject->x509_version() < 3 && !extensions_vec.empty())
+ {
+ status.insert(Certificate_Status_Code::EXT_IN_V1_V2_CERT);
+ }
+ for(auto& extension : extensions_vec)
+ {
+ extension.first->validate(*subject, *issuer, cert_path, cert_status, i);
+ }
+ if(extensions.extensions().size() != extensions.get_extension_oids().size())
+ {
+ status.insert(Certificate_Status_Code::DUPLICATE_CERT_EXTENSION);
+ }
+ }
+
+ // path len check
+ size_t max_path_length = cert_path.size();
+ for(size_t i = cert_path.size() - 1; i > 0 ; --i)
+ {
+ std::set<Certificate_Status_Code>& status = cert_status.at(i);
+ const std::shared_ptr<const X509_Certificate>& subject = cert_path[i];
+
+ /*
+ * If the certificate was not self-issued, verify that max_path_length is
+ * greater than zero and decrement max_path_length by 1.
+ */
+ if(subject->subject_dn() != subject->issuer_dn())
+ {
+ if(max_path_length > 0)
+ {
+ --max_path_length;
+ }
+ else
+ {
+ status.insert(Certificate_Status_Code::CERT_CHAIN_TOO_LONG);
+ }
+ }
+
+ /*
+ * If pathLenConstraint is present in the certificate and is less than max_path_length,
+ * set max_path_length to the value of pathLenConstraint.
+ */
+ if(subject->path_limit() != Cert_Extension::NO_CERT_PATH_LIMIT && subject->path_limit() < max_path_length)
+ {
+ max_path_length = subject->path_limit();
+ }
+ }
+
+ return cert_status;
+ }
+
+CertificatePathStatusCodes
+PKIX::check_ocsp(const std::vector<std::shared_ptr<const X509_Certificate>>& cert_path,
+ const std::vector<std::shared_ptr<const OCSP::Response>>& ocsp_responses,
+ const std::vector<Certificate_Store*>& trusted_certstores,
+ std::chrono::system_clock::time_point ref_time)
+ {
+ if(cert_path.empty())
+ throw Invalid_Argument("PKIX::check_ocsp cert_path empty");
+
+ CertificatePathStatusCodes cert_status(cert_path.size() - 1);
+
+ for(size_t i = 0; i != cert_path.size() - 1; ++i)
+ {
+ std::set<Certificate_Status_Code>& status = cert_status.at(i);
+
+ std::shared_ptr<const X509_Certificate> subject = cert_path.at(i);
+ std::shared_ptr<const X509_Certificate> ca = cert_path.at(i+1);
+
+ if(i < ocsp_responses.size() && (ocsp_responses.at(i) != nullptr))
+ {
+ try
+ {
+ Certificate_Status_Code ocsp_signature_status = ocsp_responses.at(i)->check_signature(trusted_certstores, cert_path);
+
+ if(ocsp_signature_status == Certificate_Status_Code::OCSP_SIGNATURE_OK)
+ {
+ // Signature ok, so check the claimed status
+ Certificate_Status_Code ocsp_status = ocsp_responses.at(i)->status_for(*ca, *subject, ref_time);
+ status.insert(ocsp_status);
+ }
+ else
+ {
+ // Some signature problem
+ status.insert(ocsp_signature_status);
+ }
+ }
+ catch(Exception&)
+ {
+ status.insert(Certificate_Status_Code::OCSP_RESPONSE_INVALID);
+ }
+ }
+ }
+
+ while(cert_status.size() > 0 && cert_status.back().empty())
+ cert_status.pop_back();
+
+ return cert_status;
+ }
+
+CertificatePathStatusCodes
+PKIX::check_crl(const std::vector<std::shared_ptr<const X509_Certificate>>& cert_path,
+ const std::vector<std::shared_ptr<const X509_CRL>>& crls,
+ std::chrono::system_clock::time_point ref_time)
+ {
+ if(cert_path.empty())
+ throw Invalid_Argument("PKIX::check_crl cert_path empty");
+
+ CertificatePathStatusCodes cert_status(cert_path.size());
+ const X509_Time validation_time(ref_time);
+
+ for(size_t i = 0; i != cert_path.size() - 1; ++i)
+ {
+ std::set<Certificate_Status_Code>& status = cert_status.at(i);
+
+ if(i < crls.size() && crls.at(i))
+ {
+ std::shared_ptr<const X509_Certificate> subject = cert_path.at(i);
+ std::shared_ptr<const X509_Certificate> ca = cert_path.at(i+1);
+
+ if(!ca->allowed_usage(CRL_SIGN))
+ status.insert(Certificate_Status_Code::CA_CERT_NOT_FOR_CRL_ISSUER);
+
+ if(validation_time < crls[i]->this_update())
+ status.insert(Certificate_Status_Code::CRL_NOT_YET_VALID);
+
+ if(validation_time > crls[i]->next_update())
+ status.insert(Certificate_Status_Code::CRL_HAS_EXPIRED);
+
+ if(crls[i]->check_signature(ca->subject_public_key()) == false)
+ status.insert(Certificate_Status_Code::CRL_BAD_SIGNATURE);
+
+ status.insert(Certificate_Status_Code::VALID_CRL_CHECKED);
+
+ if(crls[i]->is_revoked(*subject))
+ status.insert(Certificate_Status_Code::CERT_IS_REVOKED);
+
+ std::string dp = subject->crl_distribution_point();
+ if(!dp.empty())
+ {
+ if(dp != crls[i]->crl_issuing_distribution_point())
+ {
+ status.insert(Certificate_Status_Code::NO_MATCHING_CRLDP);
+ }
+ }
+
+ for(const auto& extension : crls[i]->extensions().extensions())
+ {
+ // is the extension critical and unknown?
+ if(extension.second && OIDS::lookup(extension.first->oid_of()) == "")
+ {
+ /* NIST Certificate Path Valiadation Testing document: "When an implementation does not recognize a critical extension in the
+ * crlExtensions field, it shall assume that identified certificates have been revoked and are no longer valid"
+ */
+ status.insert(Certificate_Status_Code::CERT_IS_REVOKED);
+ }
+ }
+
+ }
+ }
+
+ while(cert_status.size() > 0 && cert_status.back().empty())
+ cert_status.pop_back();
+
+ return cert_status;
+ }
+
+CertificatePathStatusCodes
+PKIX::check_crl(const std::vector<std::shared_ptr<const X509_Certificate>>& cert_path,
+ const std::vector<Certificate_Store*>& certstores,
+ std::chrono::system_clock::time_point ref_time)
+ {
+ if(cert_path.empty())
+ throw Invalid_Argument("PKIX::check_crl cert_path empty");
+
+ if(certstores.empty())
+ throw Invalid_Argument("PKIX::check_crl certstores empty");
+
+ std::vector<std::shared_ptr<const X509_CRL>> crls(cert_path.size());
+
+ for(size_t i = 0; i != cert_path.size(); ++i)
+ {
+ BOTAN_ASSERT_NONNULL(cert_path[i]);
+ for(size_t c = 0; c != certstores.size(); ++c)
+ {
+ crls[i] = certstores[c]->find_crl_for(*cert_path[i]);
+ if(crls[i])
+ break;
+ }
+ }
+
+ return PKIX::check_crl(cert_path, crls, ref_time);
+ }
+
+#if defined(BOTAN_HAS_ONLINE_REVOCATION_CHECKS)
+
+CertificatePathStatusCodes
+PKIX::check_ocsp_online(const std::vector<std::shared_ptr<const X509_Certificate>>& cert_path,
+ const std::vector<Certificate_Store*>& trusted_certstores,
+ std::chrono::system_clock::time_point ref_time,
+ std::chrono::milliseconds timeout,
+ bool ocsp_check_intermediate_CAs)
+ {
+ if(cert_path.empty())
+ throw Invalid_Argument("PKIX::check_ocsp_online cert_path empty");
+
+ std::vector<std::future<std::shared_ptr<const OCSP::Response>>> ocsp_response_futures;
+
+ size_t to_ocsp = 1;
+
+ if(ocsp_check_intermediate_CAs)
+ to_ocsp = cert_path.size() - 1;
+ if(cert_path.size() == 1)
+ to_ocsp = 0;
+
+ for(size_t i = 0; i < to_ocsp; ++i)
+ {
+ const std::shared_ptr<const X509_Certificate>& subject = cert_path.at(i);
+ const std::shared_ptr<const X509_Certificate>& issuer = cert_path.at(i+1);
+
+ if(subject->ocsp_responder() == "")
+ {
+ ocsp_response_futures.emplace_back(std::async(std::launch::deferred, [&]() -> std::shared_ptr<const OCSP::Response> {
+ return std::make_shared<const OCSP::Response>(Certificate_Status_Code::OSCP_NO_REVOCATION_URL);
+ }));
+ }
+ else
+ {
+ ocsp_response_futures.emplace_back(std::async(std::launch::async, [&]() -> std::shared_ptr<const OCSP::Response> {
+ OCSP::Request req(*issuer, BigInt::decode(subject->serial_number()));
+
+ HTTP::Response http;
+ try
+ {
+ http = HTTP::POST_sync(subject->ocsp_responder(),
+ "application/ocsp-request",
+ req.BER_encode(),
+ /*redirects*/1,
+ timeout);
+ }
+ catch(std::exception& e)
+ {
+ // log e.what() ?
+ }
+ if (http.status_code() != 200)
+ return std::make_shared<const OCSP::Response>(Certificate_Status_Code::OSCP_SERVER_NOT_AVAILABLE);
+ // Check the MIME type?
+
+ return std::make_shared<const OCSP::Response>(http.body());
+ }));
+ }
+ }
+
+ std::vector<std::shared_ptr<const OCSP::Response>> ocsp_responses;
+
+ for(size_t i = 0; i < ocsp_response_futures.size(); ++i)
+ {
+ ocsp_responses.push_back(ocsp_response_futures[i].get());
+ }
+
+ return PKIX::check_ocsp(cert_path, ocsp_responses, trusted_certstores, ref_time);
+ }
+
+CertificatePathStatusCodes
+PKIX::check_crl_online(const std::vector<std::shared_ptr<const X509_Certificate>>& cert_path,
+ const std::vector<Certificate_Store*>& certstores,
+ Certificate_Store_In_Memory* crl_store,
+ std::chrono::system_clock::time_point ref_time,
+ std::chrono::milliseconds timeout)
+ {
+ if(cert_path.empty())
+ throw Invalid_Argument("PKIX::check_crl_online cert_path empty");
+ if(certstores.empty())
+ throw Invalid_Argument("PKIX::check_crl_online certstores empty");
+
+ std::vector<std::future<std::shared_ptr<const X509_CRL>>> future_crls;
+ std::vector<std::shared_ptr<const X509_CRL>> crls(cert_path.size());
+
+ for(size_t i = 0; i != cert_path.size(); ++i)
+ {
+ const std::shared_ptr<const X509_Certificate>& cert = cert_path.at(i);
+ for(size_t c = 0; c != certstores.size(); ++c)
+ {
+ crls[i] = certstores[c]->find_crl_for(*cert);
+ if(crls[i])
+ break;
+ }
+
+ // TODO: check if CRL is expired and re-request?
+
+ // Only request if we don't already have a CRL
+ if(crls[i])
+ {
+ /*
+ We already have a CRL, so just insert this empty one to hold a place in the vector
+ so that indexes match up
+ */
+ future_crls.emplace_back(std::future<std::shared_ptr<const X509_CRL>>());
+ }
+ else if(cert->crl_distribution_point() == "")
+ {
+ // Avoid creating a thread for this case
+ future_crls.emplace_back(std::async(std::launch::deferred, [&]() -> std::shared_ptr<const X509_CRL> {
+ throw Exception("No CRL distribution point for this certificate");
+ }));
+ }
+ else
+ {
+ future_crls.emplace_back(std::async(std::launch::async, [&]() -> std::shared_ptr<const X509_CRL> {
+ auto http = HTTP::GET_sync(cert->crl_distribution_point(),
+ /*redirects*/ 1, timeout);
+
+ http.throw_unless_ok();
+ // check the mime type?
+ return std::make_shared<const X509_CRL>(http.body());
+ }));
+ }
+ }
+
+ for(size_t i = 0; i != future_crls.size(); ++i)
+ {
+ if(future_crls[i].valid())
+ {
+ try
+ {
+ crls[i] = future_crls[i].get();
+ }
+ catch(std::exception& e)
+ {
+ // crls[i] left null
+ // todo: log exception e.what() ?
+ }
+ }
+ }
+
+ const CertificatePathStatusCodes crl_status = PKIX::check_crl(cert_path, crls, ref_time);
+
+ if(crl_store)
+ {
+ for(size_t i = 0; i != crl_status.size(); ++i)
+ {
+ if(crl_status[i].count(Certificate_Status_Code::VALID_CRL_CHECKED))
+ {
+ // better be non-null, we supposedly validated it
+ BOTAN_ASSERT_NONNULL(crls[i]);
+ crl_store->add_crl(crls[i]);
+ }
+ }
+ }
+
+ return crl_status;
+ }
+
+#endif
+
+Certificate_Status_Code
+PKIX::build_certificate_path(std::vector<std::shared_ptr<const X509_Certificate>>& cert_path,
+ const std::vector<Certificate_Store*>& trusted_certstores,
+ const std::shared_ptr<const X509_Certificate>& end_entity,
+ const std::vector<std::shared_ptr<const X509_Certificate>>& end_entity_extra)
+ {
+ if(end_entity->is_self_signed())
+ {
+ return Certificate_Status_Code::CANNOT_ESTABLISH_TRUST;
+ }
+
+ /*
+ * This is an inelegant but functional way of preventing path loops
+ * (where C1 -> C2 -> C3 -> C1). We store a set of all the certificate
+ * fingerprints in the path. If there is a duplicate, we error out.
+ * TODO: save fingerprints in result struct? Maybe useful for blacklists, etc.
+ */
+ std::set<std::string> certs_seen;
+
+ cert_path.push_back(end_entity);
+ certs_seen.insert(end_entity->fingerprint("SHA-256"));
+
+ Certificate_Store_In_Memory ee_extras;
+ for(size_t i = 0; i != end_entity_extra.size(); ++i)
+ ee_extras.add_certificate(end_entity_extra[i]);
+
+ // iterate until we reach a root or cannot find the issuer
+ for(;;)
+ {
+ const X509_Certificate& last = *cert_path.back();
+ const X509_DN issuer_dn = last.issuer_dn();
+ const std::vector<uint8_t> auth_key_id = last.authority_key_id();
+
+ std::shared_ptr<const X509_Certificate> issuer;
+ bool trusted_issuer = false;
+
+ for(Certificate_Store* store : trusted_certstores)
+ {
+ issuer = store->find_cert(issuer_dn, auth_key_id);
+ if(issuer)
+ {
+ trusted_issuer = true;
+ break;
+ }
+ }
+
+ if(!issuer)
+ {
+ // fall back to searching supplemental certs
+ issuer = ee_extras.find_cert(issuer_dn, auth_key_id);
+ }
+
+ if(!issuer)
+ return Certificate_Status_Code::CERT_ISSUER_NOT_FOUND;
+
+ const std::string fprint = issuer->fingerprint("SHA-256");
+
+ if(certs_seen.count(fprint) > 0) // already seen?
+ {
+ return Certificate_Status_Code::CERT_CHAIN_LOOP;
+ }
+
+ certs_seen.insert(fprint);
+ cert_path.push_back(issuer);
+
+ if(issuer->is_self_signed())
+ {
+ if(trusted_issuer)
+ {
+ return Certificate_Status_Code::OK;
+ }
+ else
+ {
+ return Certificate_Status_Code::CANNOT_ESTABLISH_TRUST;
+ }
+ }
+ }
+ }
+
+/**
+ * utilities for PKIX::build_all_certificate_paths
+ */
+namespace
+{
+// <certificate, trusted?>
+using cert_maybe_trusted = std::pair<std::shared_ptr<const X509_Certificate>,bool>;
+}
+
+/**
+ * Build all possible certificate paths from the end certificate to self-signed trusted roots.
+ *
+ * All potentially valid paths are put into the cert_paths vector. If no potentially valid paths are found,
+ * one of the encountered errors is returned arbitrarily.
+ *
+ * todo add a path building function that returns detailed information on errors encountered while building
+ * the potentially numerous path candidates.
+ *
+ * Basically, a DFS is performed starting from the end certificate. A stack (vector) serves to control the DFS.
+ * At the beginning of each iteration, a pair is popped from the stack that contains (1) the next certificate
+ * to add to the path (2) a bool that indicates if the certificate is part of a trusted certstore. Ideally, we
+ * follow the unique issuer of the current certificate until a trusted root is reached. However, the issuer DN +
+ * authority key id need not be unique among the certificates used for building the path. In such a case,
+ * we consider all the matching issuers by pushing <IssuerCert, trusted?> on the stack for each of them.
+ *
+ */
+Certificate_Status_Code
+PKIX::build_all_certificate_paths(std::vector<std::vector<std::shared_ptr<const X509_Certificate>>>& cert_paths_out,
+ const std::vector<Certificate_Store*>& trusted_certstores,
+ const std::shared_ptr<const X509_Certificate>& end_entity,
+ const std::vector<std::shared_ptr<const X509_Certificate>>& end_entity_extra)
+ {
+ if(!cert_paths_out.empty())
+ {
+ throw Invalid_Argument("PKIX::build_all_certificate_paths: cert_paths_out must be empty");
+ }
+
+ if(end_entity->is_self_signed())
+ {
+ return Certificate_Status_Code::CANNOT_ESTABLISH_TRUST;
+ }
+
+ /*
+ * Pile up error messages
+ */
+ std::vector<Certificate_Status_Code> stats;
+
+ Certificate_Store_In_Memory ee_extras;
+ for(size_t i = 0; i != end_entity_extra.size(); ++i)
+ {
+ ee_extras.add_certificate(end_entity_extra[i]);
+ }
+
+ /*
+ * This is an inelegant but functional way of preventing path loops
+ * (where C1 -> C2 -> C3 -> C1). We store a set of all the certificate
+ * fingerprints in the path. If there is a duplicate, we error out.
+ * TODO: save fingerprints in result struct? Maybe useful for blacklists, etc.
+ */
+ std::set<std::string> certs_seen;
+
+ // new certs are added and removed from the path during the DFS
+ // it is copied into cert_paths_out when we encounter a trusted root
+ std::vector<std::shared_ptr<const X509_Certificate>> path_so_far;
+
+ // todo can we assume that the end certificate is not trusted?
+ std::vector<cert_maybe_trusted> stack = { {end_entity, false} };
+
+ while(!stack.empty())
+ {
+ // found a deletion marker that guides the DFS, backtracing
+ if(stack.back().first == nullptr)
+ {
+ stack.pop_back();
+ std::string fprint = path_so_far.back()->fingerprint("SHA-256");
+ certs_seen.erase(fprint);
+ path_so_far.pop_back();
+ }
+ // process next cert on the path
+ else
+ {
+ std::shared_ptr<const X509_Certificate> last = stack.back().first;
+ bool trusted = stack.back().second;
+ stack.pop_back();
+
+ // certificate already seen?
+ const std::string fprint = last->fingerprint("SHA-256");
+ if(certs_seen.count(fprint) == 1)
+ {
+ stats.push_back(Certificate_Status_Code::CERT_CHAIN_LOOP);
+ // the current path ended in a loop
+ continue;
+ }
+
+ // the current path ends here
+ if(last->is_self_signed())
+ {
+ // found a trust anchor
+ if(trusted)
+ {
+ cert_paths_out.push_back(path_so_far);
+ cert_paths_out.back().push_back(last);
+
+ continue;
+ }
+ // found an untrustworthy root
+ else
+ {
+ stats.push_back(Certificate_Status_Code::CANNOT_ESTABLISH_TRUST);
+ continue;
+ }
+ }
+
+ const X509_DN issuer_dn = last->issuer_dn();
+ const std::vector<uint8_t> auth_key_id = last->authority_key_id();
+
+ // search for trusted issuers
+ std::vector<std::shared_ptr<const X509_Certificate>> trusted_issuers;
+ for(Certificate_Store* store : trusted_certstores)
+ {
+ auto new_issuers = store->find_all_certs(issuer_dn, auth_key_id);
+ trusted_issuers.insert(trusted_issuers.end(), new_issuers.begin(), new_issuers.end());
+ }
+
+ // search the supplemental certs
+ std::vector<std::shared_ptr<const X509_Certificate>> misc_issuers =
+ ee_extras.find_all_certs(issuer_dn, auth_key_id);
+
+ // if we could not find any issuers, the current path ends here
+ if(trusted_issuers.size() + misc_issuers.size() == 0)
+ {
+ stats.push_back(Certificate_Status_Code::CERT_ISSUER_NOT_FOUND);
+ continue;
+ }
+
+ // push the latest certificate onto the path_so_far
+ path_so_far.push_back(last);
+ certs_seen.emplace(fprint);
+
+ // push a deletion marker on the stack for backtracing later
+ stack.push_back({std::shared_ptr<const X509_Certificate>(nullptr),false});
+
+ for(const auto trusted_cert : trusted_issuers)
+ {
+ stack.push_back({trusted_cert,true});
+ }
+
+ for(const auto misc : misc_issuers)
+ {
+ stack.push_back({misc,false});
+ }
+ }
+ }
+
+ // could not construct any potentially valid path
+ if(cert_paths_out.empty())
+ {
+ if(stats.empty())
+ throw Exception("X509 path building failed for unknown reasons");
+ else
+ // arbitrarily return the first error
+ return stats[0];
+ }
+ else
+ {
+ return Certificate_Status_Code::OK;
+ }
+ }
+
+
+void PKIX::merge_revocation_status(CertificatePathStatusCodes& chain_status,
+ const CertificatePathStatusCodes& crl,
+ const CertificatePathStatusCodes& ocsp,
+ bool require_rev_on_end_entity,
+ bool require_rev_on_intermediates)
+ {
+ if(chain_status.empty())
+ throw Invalid_Argument("PKIX::merge_revocation_status chain_status was empty");
+
+ for(size_t i = 0; i != chain_status.size() - 1; ++i)
+ {
+ bool had_crl = false, had_ocsp = false;
+
+ if(i < crl.size() && crl[i].size() > 0)
+ {
+ for(auto&& code : crl[i])
+ {
+ if(code == Certificate_Status_Code::VALID_CRL_CHECKED)
+ {
+ had_crl = true;
+ }
+ chain_status[i].insert(code);
+ }
+ }
+
+ if(i < ocsp.size() && ocsp[i].size() > 0)
+ {
+ for(auto&& code : ocsp[i])
+ {
+ if(code == Certificate_Status_Code::OCSP_RESPONSE_GOOD ||
+ code == Certificate_Status_Code::OSCP_NO_REVOCATION_URL || // softfail
+ code == Certificate_Status_Code::OSCP_SERVER_NOT_AVAILABLE) // softfail
+ {
+ had_ocsp = true;
+ }
+
+ chain_status[i].insert(code);
+ }
+ }
+
+ if(had_crl == false && had_ocsp == false)
+ {
+ if((require_rev_on_end_entity && i == 0) ||
+ (require_rev_on_intermediates && i > 0))
+ {
+ chain_status[i].insert(Certificate_Status_Code::NO_REVOCATION_DATA);
+ }
+ }
+ }
+ }
+
+Certificate_Status_Code PKIX::overall_status(const CertificatePathStatusCodes& cert_status)
+ {
+ if(cert_status.empty())
+ throw Invalid_Argument("PKIX::overall_status empty cert status");
+
+ Certificate_Status_Code overall_status = Certificate_Status_Code::OK;
+
+ // take the "worst" error as overall
+ for(const std::set<Certificate_Status_Code>& s : cert_status)
+ {
+ if(!s.empty())
+ {
+ auto worst = *s.rbegin();
+ // Leave informative OCSP/CRL confirmations on cert-level status only
+ if(worst >= Certificate_Status_Code::FIRST_ERROR_STATUS && worst > overall_status)
+ {
+ overall_status = worst;
+ }
+ }
+ }
+ return overall_status;
+ }
+
+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,
+ const std::string& hostname,
+ Usage_Type usage,
+ std::chrono::system_clock::time_point ref_time,
+ std::chrono::milliseconds ocsp_timeout,
+ const std::vector<std::shared_ptr<const OCSP::Response>>& ocsp_resp)
+ {
+ if(end_certs.empty())
+ {
+ throw Invalid_Argument("x509_path_validate called with no subjects");
+ }
+
+ std::shared_ptr<const X509_Certificate> end_entity(std::make_shared<const X509_Certificate>(end_certs[0]));
+ std::vector<std::shared_ptr<const X509_Certificate>> end_entity_extra;
+ for(size_t i = 1; i < end_certs.size(); ++i)
+ {
+ end_entity_extra.push_back(std::make_shared<const X509_Certificate>(end_certs[i]));
+ }
+
+ std::vector<std::vector<std::shared_ptr<const X509_Certificate>>> cert_paths;
+ Certificate_Status_Code path_building_result = PKIX::build_all_certificate_paths(cert_paths, trusted_roots, end_entity, end_entity_extra);
+
+ // If we cannot successfully build a chain to a trusted self-signed root, stop now
+ if(path_building_result != Certificate_Status_Code::OK)
+ {
+ return Path_Validation_Result(path_building_result);
+ }
+
+ std::vector<Path_Validation_Result> error_results;
+ // Try validating all the potentially valid paths and return the first one to validate properly
+ for(auto cert_path : cert_paths)
+ {
+ CertificatePathStatusCodes status =
+ PKIX::check_chain(cert_path, ref_time,
+ hostname, usage,
+ restrictions.minimum_key_strength(),
+ restrictions.trusted_hashes());
+
+ CertificatePathStatusCodes crl_status =
+ PKIX::check_crl(cert_path, trusted_roots, ref_time);
+
+ CertificatePathStatusCodes ocsp_status;
+
+ if(ocsp_resp.size() > 0)
+ {
+ ocsp_status = PKIX::check_ocsp(cert_path, ocsp_resp, trusted_roots, ref_time);
+ }
+
+ if(ocsp_status.empty() && ocsp_timeout != std::chrono::milliseconds(0))
+ {
+#if defined(BOTAN_TARGET_OS_HAS_THREADS) && defined(BOTAN_HAS_HTTP_UTIL)
+ ocsp_status = PKIX::check_ocsp_online(cert_path, trusted_roots, ref_time,
+ ocsp_timeout, restrictions.ocsp_all_intermediates());
+#else
+ ocsp_status.resize(1);
+ ocsp_status[0].insert(Certificate_Status_Code::OCSP_NO_HTTP);
+#endif
+ }
+
+ PKIX::merge_revocation_status(status, crl_status, ocsp_status,
+ restrictions.require_revocation_information(),
+ restrictions.ocsp_all_intermediates());
+
+ Path_Validation_Result pvd(status, std::move(cert_path));
+ if(pvd.successful_validation())
+ {
+ return pvd;
+ }
+ else
+ {
+ error_results.push_back(std::move(pvd));
+ }
+ }
+ return error_results[0];
+ }
+
+Path_Validation_Result x509_path_validate(
+ const X509_Certificate& end_cert,
+ const Path_Validation_Restrictions& restrictions,
+ const std::vector<Certificate_Store*>& trusted_roots,
+ const std::string& hostname,
+ Usage_Type usage,
+ std::chrono::system_clock::time_point when,
+ std::chrono::milliseconds ocsp_timeout,
+ const std::vector<std::shared_ptr<const OCSP::Response>>& ocsp_resp)
+ {
+ std::vector<X509_Certificate> certs;
+ certs.push_back(end_cert);
+ return x509_path_validate(certs, restrictions, trusted_roots, hostname, usage, when, ocsp_timeout, ocsp_resp);
+ }
+
+Path_Validation_Result x509_path_validate(
+ const std::vector<X509_Certificate>& end_certs,
+ const Path_Validation_Restrictions& restrictions,
+ const Certificate_Store& store,
+ const std::string& hostname,
+ Usage_Type usage,
+ std::chrono::system_clock::time_point when,
+ std::chrono::milliseconds ocsp_timeout,
+ const std::vector<std::shared_ptr<const OCSP::Response>>& ocsp_resp)
+ {
+ std::vector<Certificate_Store*> trusted_roots;
+ trusted_roots.push_back(const_cast<Certificate_Store*>(&store));
+
+ return x509_path_validate(end_certs, restrictions, trusted_roots, hostname, usage, when, ocsp_timeout, ocsp_resp);
+ }
+
+Path_Validation_Result x509_path_validate(
+ const X509_Certificate& end_cert,
+ const Path_Validation_Restrictions& restrictions,
+ const Certificate_Store& store,
+ const std::string& hostname,
+ Usage_Type usage,
+ std::chrono::system_clock::time_point when,
+ std::chrono::milliseconds ocsp_timeout,
+ const std::vector<std::shared_ptr<const OCSP::Response>>& ocsp_resp)
+ {
+ std::vector<X509_Certificate> certs;
+ certs.push_back(end_cert);
+
+ std::vector<Certificate_Store*> trusted_roots;
+ trusted_roots.push_back(const_cast<Certificate_Store*>(&store));
+
+ return x509_path_validate(certs, restrictions, trusted_roots, hostname, usage, when, ocsp_timeout, ocsp_resp);
+ }
+
+Path_Validation_Restrictions::Path_Validation_Restrictions(bool require_rev,
+ size_t key_strength,
+ bool ocsp_intermediates) :
+ m_require_revocation_information(require_rev),
+ m_ocsp_all_intermediates(ocsp_intermediates),
+ m_minimum_key_strength(key_strength)
+ {
+ if(key_strength <= 80)
+ m_trusted_hashes.insert("SHA-160");
+
+ m_trusted_hashes.insert("SHA-224");
+ m_trusted_hashes.insert("SHA-256");
+ m_trusted_hashes.insert("SHA-384");
+ m_trusted_hashes.insert("SHA-512");
+ }
+
+namespace {
+CertificatePathStatusCodes find_warnings(const CertificatePathStatusCodes& all_statuses)
+ {
+ CertificatePathStatusCodes warnings;
+ for(const auto& status_set_i : all_statuses)
+ {
+ std::set<Certificate_Status_Code> warning_set_i;
+ for(const auto& code : status_set_i)
+ {
+ if(code >= Certificate_Status_Code::FIRST_WARNING_STATUS &&
+ code < Certificate_Status_Code::FIRST_ERROR_STATUS)
+ {
+ warning_set_i.insert(code);
+ }
+ }
+ warnings.push_back(warning_set_i);
+ }
+ return warnings;
+ }
+}
+
+Path_Validation_Result::Path_Validation_Result(CertificatePathStatusCodes status,
+ std::vector<std::shared_ptr<const X509_Certificate>>&& cert_chain) :
+ m_all_status(status),
+ m_warnings(find_warnings(m_all_status)),
+ m_cert_path(cert_chain),
+ m_overall(PKIX::overall_status(m_all_status))
+ {
+ }
+
+const X509_Certificate& Path_Validation_Result::trust_root() const
+ {
+ if(m_cert_path.empty())
+ throw Exception("Path_Validation_Result::trust_root no path set");
+ if(result() != Certificate_Status_Code::VERIFIED)
+ throw Exception("Path_Validation_Result::trust_root meaningless with invalid status");
+
+ return *m_cert_path[m_cert_path.size()-1];
+ }
+
+std::set<std::string> Path_Validation_Result::trusted_hashes() const
+ {
+ std::set<std::string> hashes;
+ for(size_t i = 0; i != m_cert_path.size(); ++i)
+ hashes.insert(m_cert_path[i]->hash_used_for_signature());
+ return hashes;
+ }
+
+bool Path_Validation_Result::successful_validation() const
+ {
+ return (result() == Certificate_Status_Code::VERIFIED ||
+ result() == Certificate_Status_Code::OCSP_RESPONSE_GOOD ||
+ result() == Certificate_Status_Code::VALID_CRL_CHECKED);
+ }
+
+bool Path_Validation_Result::no_warnings() const
+ {
+ for(auto status_set_i : m_warnings)
+ if(!status_set_i.empty())
+ return false;
+ return true;
+ }
+
+CertificatePathStatusCodes Path_Validation_Result::warnings() const
+ {
+ return m_warnings;
+ }
+
+std::string Path_Validation_Result::result_string() const
+ {
+ return status_string(result());
+ }
+
+const char* Path_Validation_Result::status_string(Certificate_Status_Code code)
+ {
+ if(const char* s = to_string(code))
+ return s;
+
+ return "Unknown error";
+ }
+
+std::string Path_Validation_Result::warnings_string() const
+ {
+ const std::string sep(", ");
+ std::string res;
+ for(size_t i = 0; i < m_warnings.size(); i++)
+ {
+ for(auto code : m_warnings[i])
+ res += "[" + std::to_string(i) + "] " + status_string(code) + sep;
+ }
+ // remove last sep
+ if(res.size() >= sep.size())
+ res = res.substr(0, res.size() - sep.size());
+ return res;
+ }
+}