aboutsummaryrefslogtreecommitdiffstats
path: root/src/lib/x509/x509path.cpp
diff options
context:
space:
mode:
authorJack Lloyd <[email protected]>2016-11-21 20:07:26 -0500
committerJack Lloyd <[email protected]>2016-11-23 08:31:07 -0500
commitcdd2e2babc0506d0c727aff06d1fc430cedbf695 (patch)
tree136e8ade4c31e4086245db2d87552bde1c584fe1 /src/lib/x509/x509path.cpp
parent7dbb31c5778ac1158fbf0979739f6c3c55a007f5 (diff)
Refactor X.509 path validation
Splits path building, path validation, CRL checks, and OCSP checks into distinct functions in namespace PKIX. The previous path validation APIs remain. Fixes to OCSP to store more information and to handle modern OCSP setups in at least some situations.
Diffstat (limited to 'src/lib/x509/x509path.cpp')
-rw-r--r--src/lib/x509/x509path.cpp696
1 files changed, 472 insertions, 224 deletions
diff --git a/src/lib/x509/x509path.cpp b/src/lib/x509/x509path.cpp
index f0b07e5fc..946539bab 100644
--- a/src/lib/x509/x509path.cpp
+++ b/src/lib/x509/x509path.cpp
@@ -1,13 +1,12 @@
/*
* X.509 Certificate Path Validation
-* (C) 2010,2011,2012,2014 Jack Lloyd
+* (C) 2010,2011,2012,2014,2016 Jack Lloyd
*
* Botan is released under the Simplified BSD License (see license.txt)
*/
#include <botan/x509path.h>
#include <botan/ocsp.h>
-#include <botan/http_util.h>
#include <botan/parsing.h>
#include <botan/pubkey.h>
#include <botan/oids.h>
@@ -15,98 +14,58 @@
#include <chrono>
#include <vector>
#include <set>
-#include <future>
-namespace Botan {
-
-namespace {
-
-std::shared_ptr<const X509_Certificate>
-find_issuing_cert(const X509_Certificate& cert,
- Certificate_Store& end_certs,
- const std::vector<Certificate_Store*>& certstores)
- {
- const X509_DN issuer_dn = cert.issuer_dn();
- const std::vector<byte> auth_key_id = cert.authority_key_id();
-
- if(std::shared_ptr<const X509_Certificate> c = end_certs.find_cert(issuer_dn, auth_key_id))
- {
- if(*c != cert)
- return c;
- }
-
- for(size_t i = 0; i != certstores.size(); ++i)
- {
- if(std::shared_ptr<const X509_Certificate> c = certstores[i]->find_cert(issuer_dn, auth_key_id))
- return c;
- }
-
- return nullptr;
- }
-
-std::shared_ptr<const X509_CRL> find_crls_for(const X509_Certificate& cert,
- const std::vector<Certificate_Store*>& certstores)
- {
- for(size_t i = 0; i != certstores.size(); ++i)
- {
- if(std::shared_ptr<const X509_CRL> crl = certstores[i]->find_crl_for(cert))
- return crl;
- }
-
-#if 0
- const std::string crl_url = cert.crl_distribution_point();
- if(crl_url != "")
- {
- std::cout << "Downloading CRL " << crl_url << "\n";
- auto http = HTTP::GET_sync(crl_url);
-
- std::cout << http.status_message() << "\n";
-
- http.throw_unless_ok();
- // check the mime type
-
- std::unique_ptr<X509_CRL> crl(new X509_CRL(http.body()));
-
- return crl.release();
- }
+#if defined(BOTAN_TARGET_OS_HAS_THREADS) && defined(BOTAN_HAS_HTTP_UTIL)
+ #include <future>
+ #include <botan/http_util.h>
#endif
- return nullptr;
- }
+namespace Botan {
+/*
+* PKIX path validation
+*/
std::vector<std::set<Certificate_Status_Code>>
-check_chain(const std::vector<std::shared_ptr<const X509_Certificate>>& cert_path,
- const Path_Validation_Restrictions& restrictions,
- const std::vector<Certificate_Store*>& certstores,
- std::chrono::system_clock::time_point ref_time)
+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)
{
- const std::set<std::string>& trusted_hashes = restrictions.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);
- std::vector<std::future<OCSP::Response>> ocsp_responses;
-
std::vector<std::set<Certificate_Status_Code>> 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);
+
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);
- std::shared_ptr<const X509_Certificate> subject = cert_path[i];
+ 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)];
- 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(i == 0 || restrictions.ocsp_all_intermediates())
+ if(subject->issuer_dn() != issuer->subject_dn())
{
- // certstore[0] is treated as trusted for OCSP (FIXME)
- if(certstores.size() > 1)
- ocsp_responses.push_back(
- std::async(std::launch::async,
- OCSP::online_check, *issuer, *subject, certstores[0]));
+ status.insert(Certificate_Status_Code::CHAIN_NAME_MISMATCH);
}
// Check all certs for valid time range
@@ -128,21 +87,23 @@ check_chain(const std::vector<std::shared_ptr<const X509_Certificate>>& cert_pat
if(!issuer_key)
{
- status.insert(Certificate_Status_Code::SIGNATURE_ERROR);
+ status.insert(Certificate_Status_Code::CERT_PUBKEY_INVALID);
}
else
{
if(subject->check_signature(*issuer_key) == false)
+ {
status.insert(Certificate_Status_Code::SIGNATURE_ERROR);
+ }
- if(issuer_key->estimated_strength() < restrictions.minimum_key_strength())
+ if(issuer_key->estimated_strength() < min_signature_algo_strength)
status.insert(Certificate_Status_Code::SIGNATURE_METHOD_TOO_WEAK);
}
- // Allow untrusted hashes on self-signed roots
- if(!trusted_hashes.empty() && !at_self_signed_root)
+ // 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()))
+ if(trusted_hashes.count(subject->hash_used_for_signature()) == 0)
status.insert(Certificate_Status_Code::UNTRUSTED_HASH);
}
@@ -154,6 +115,20 @@ check_chain(const std::vector<std::shared_ptr<const X509_Certificate>>& cert_pat
}
}
+ return cert_status;
+ }
+
+std::vector<std::set<Certificate_Status_Code>>
+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");
+
+ std::vector<std::set<Certificate_Status_Code>> 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);
@@ -161,129 +136,459 @@ check_chain(const std::vector<std::shared_ptr<const X509_Certificate>>& cert_pat
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())
+ if(i < ocsp_responses.size() && (ocsp_responses.at(i) != nullptr))
{
try
{
- OCSP::Response ocsp = ocsp_responses[i].get();
+ 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& e)
+ {
+ status.insert(Certificate_Status_Code::OCSP_RESPONSE_INVALID);
+ }
+ }
+ }
- auto ocsp_status = ocsp.status_for(*ca, *subject);
+ return cert_status;
+ }
- status.insert(ocsp_status);
+std::vector<std::set<Certificate_Status_Code>>
+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");
- //std::cout << "OCSP status: " << Path_Validation_Result::status_string(ocsp_status) << "\n";
+ std::vector<std::set<Certificate_Status_Code>> cert_status(cert_path.size());
+ const X509_Time validation_time(ref_time);
- // Either way we have a definitive answer, no need to check CRLs
- if(ocsp_status == Certificate_Status_Code::CERT_IS_REVOKED)
- return cert_status;
- else if(ocsp_status == Certificate_Status_Code::OCSP_RESPONSE_GOOD)
- continue;
+ 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 < X509_Time(crls[i]->this_update()))
+ status.insert(Certificate_Status_Code::CRL_NOT_YET_VALID);
+
+ if(validation_time > X509_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);
+ }
+ }
+
+ return cert_status;
+ }
+
+std::vector<std::set<Certificate_Status_Code>>
+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(cert_path[i] != nullptr, "Not null");
+ for(size_t c = 0; c != certstores.size(); ++c)
+ {
+ crls[i] = certstores[c]->find_crl_for(*cert_path.at(i));
+ if(crls[i])
+ break;
+ }
+ }
+
+ return PKIX::check_crl(cert_path, crls, ref_time);
+ }
+
+#if defined(BOTAN_TARGET_OS_HAS_THREADS) && defined(BOTAN_HAS_HTTP_UTIL)
+
+std::vector<std::set<Certificate_Status_Code>>
+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, [&]{
+ throw Exception("No OCSP responder URL set for this certificate");
+ return std::shared_ptr<const OCSP::Response>();
+ }));
}
- catch(std::exception&)
+ else
{
- //std::cout << "OCSP error: " << e.what() << "\n";
+ ocsp_response_futures.emplace_back(std::async(std::launch::async, [&]{
+ OCSP::Request req(*issuer, *subject);
+
+ auto http = HTTP::POST_sync(subject->ocsp_responder(),
+ "application/ocsp-request",
+ req.BER_encode());
+
+ http.throw_unless_ok();
+ // Check the MIME type?
+
+ return std::make_shared<const OCSP::Response>(http.body());
+ }));
}
- }
+ }
- std::shared_ptr<const X509_CRL> crl_p = find_crls_for(*subject, certstores);
+ std::vector<std::shared_ptr<const OCSP::Response>> ocsp_responses(ocsp_response_futures.size());
- if(!crl_p)
+ for(size_t pass = 1; pass < 3; ++pass)
+ {
+ for(size_t i = 0; i < ocsp_response_futures.size(); ++i)
{
- if(restrictions.require_revocation_information())
- status.insert(Certificate_Status_Code::NO_REVOCATION_DATA);
- continue;
+ try
+ {
+ if(ocsp_responses[i] == nullptr && ocsp_response_futures[i].valid())
+ {
+ std::future_status status = ocsp_response_futures[i].wait_for(timeout);
+
+ if(status == std::future_status::ready ||
+ status == std::future_status::deferred)
+ {
+ ocsp_responses[i] = ocsp_response_futures[i].get();
+ }
+ }
+ }
+ catch(std::exception&)
+ {
+ // value is default initialized to null, no need to do anything
+ }
}
+ }
- const X509_CRL& crl = *crl_p;
+ return PKIX::check_ocsp(cert_path, ocsp_responses, trusted_certstores, ref_time);
+ }
- if(!ca->allowed_usage(CRL_SIGN))
- status.insert(Certificate_Status_Code::CA_CERT_NOT_FOR_CRL_ISSUER);
+std::vector<std::set<Certificate_Status_Code>>
+PKIX::check_crl_online(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,
+ 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");
- if(validation_time < X509_Time(crl.this_update()))
- status.insert(Certificate_Status_Code::CRL_NOT_YET_VALID);
+ std::vector<std::future<std::shared_ptr<const X509_CRL>>> future_crls;
+ std::vector<std::shared_ptr<const X509_CRL>> crls(cert_path.size());
- if(validation_time > X509_Time(crl.next_update()))
- status.insert(Certificate_Status_Code::CRL_HAS_EXPIRED);
+ for(size_t i = 0; i != cert_path.size(); ++i)
+ {
+ for(size_t c = 0; c != certstores.size(); ++i)
+ {
+ crls[i] = certstores[i]->find_crl_for(*cert_path[i]);
+ if(crls[i])
+ break;
+ }
- if(crl.check_signature(ca->subject_public_key()) == false)
- status.insert(Certificate_Status_Code::CRL_BAD_SIGNATURE);
+ // TODO: check if CRL is expired and re-request?
- if(crl.is_revoked(*subject))
- status.insert(Certificate_Status_Code::CERT_IS_REVOKED);
+ // 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_path[i]->crl_distribution_point() == "")
+ {
+ // Avoid creating a thread for this case
+ future_crls.emplace_back(std::async(std::launch::deferred, [&]{
+ throw Exception("No CRL distribution point for this certificate");
+ return std::shared_ptr<const X509_CRL>();
+ }));
+ }
+ else
+ {
+ future_crls.emplace_back(std::async(std::launch::async, [&]() {
+ auto http = HTTP::GET_sync(cert_path[i]->crl_distribution_point());
+ http.throw_unless_ok();
+ // check the mime type?
+ return std::make_shared<const X509_CRL>(http.body());
+ }));
+ }
}
- if(self_signed_ee_cert)
- cert_status.back().insert(Certificate_Status_Code::CANNOT_ESTABLISH_TRUST);
+ for(size_t i = 0; i != future_crls.size(); ++i)
+ {
+ if(future_crls[i].valid())
+ {
+ try
+ {
+ std::future_status status = future_crls[i].wait_for(timeout);
+
+ if(status == std::future_status::ready)
+ {
+ crls[i] = future_crls[i].get();
+ }
+ }
+ catch(std::exception& e)
+ {
+ // crls[i] left null
+ }
+ }
+ }
- return cert_status;
+ return PKIX::check_crl(cert_path, crls, ref_time);
}
-}
+#endif
-Path_Validation_Result x509_path_validate(
- const std::vector<X509_Certificate>& end_certs,
- const Path_Validation_Restrictions& restrictions,
- const std::vector<Certificate_Store*>& certstores,
- const std::string& hostname,
- Usage_Type usage,
- std::chrono::system_clock::time_point validation_time)
+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_certs.empty())
- throw Invalid_Argument("x509_path_validate called with no subjects");
-
- std::vector<std::shared_ptr<const X509_Certificate>> cert_path;
- std::vector<std::shared_ptr<const X509_Certificate>> end_certs_sharedptr;
- cert_path.push_back(std::make_shared<X509_Certificate>(end_certs[0]));
-
- for(auto c: end_certs)
- end_certs_sharedptr.push_back(std::make_shared<const X509_Certificate>(c));
+ 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;
- Certificate_Store_Overlay extra(end_certs_sharedptr);
+ 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
- while(!cert_path.back()->is_self_signed())
+ for(;;)
{
- std::shared_ptr<const X509_Certificate> cert = find_issuing_cert(*cert_path.back(), extra, certstores);
- if(!cert)
- return Path_Validation_Result(Certificate_Status_Code::CERT_ISSUER_NOT_FOUND);
+ const X509_Certificate& last = *cert_path.back();
+ const X509_DN issuer_dn = last.issuer_dn();
+ const std::vector<byte> 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;
- const std::string fprint = cert->fingerprint("SHA-256");
- if(certs_seen.count(fprint) > 0)
- return Path_Validation_Result(Certificate_Status_Code::CERT_CHAIN_LOOP);
certs_seen.insert(fprint);
- cert_path.push_back(cert);
+ 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;
+ }
+ }
}
+ }
- std::vector<std::set<Certificate_Status_Code>> res =
- check_chain(cert_path, restrictions, certstores, validation_time);
+namespace {
- if(!hostname.empty() && !cert_path[0]->matches_dns_name(hostname))
- res[0].insert(Certificate_Status_Code::CERT_NAME_NOMATCH);
+void merge_results(std::vector<std::set<Certificate_Status_Code>>& results,
+ const std::vector<std::set<Certificate_Status_Code>>& crl,
+ const std::vector<std::set<Certificate_Status_Code>>& ocsp,
+ bool require_rev_on_end_entity,
+ bool require_rev_on_intermediates)
+ {
+ for(size_t i = 0; i != results.size() - 1; ++i)
+ {
+ bool had_crl = false, had_ocsp = false;
- if(!cert_path[0]->allowed_usage(usage))
- res[0].insert(Certificate_Status_Code::INVALID_USAGE);
+ if(i < crl.size() && crl[i].size() > 0)
+ {
+ for(auto&& code : crl[i])
+ {
+ if(code == Certificate_Status_Code::VALID_CRL_CHECKED)
+ {
+ had_crl = true;
+ }
+ results[i].insert(code);
+ }
+ }
+
+ if(i < ocsp.size() && ocsp[i].size() > 0)
+ {
+ for(auto&& code : ocsp[i])
+ {
+ if(code == Certificate_Status_Code::OCSP_RESPONSE_GOOD)
+ {
+ had_ocsp = true;
+ }
+
+ results[i].insert(code);
+ }
+ }
+
+ if(had_crl == false && had_ocsp == false)
+ {
+ if((require_rev_on_end_entity && i == 0) ||
+ (require_rev_on_intermediates && i > 0))
+ {
+ results[i].insert(Certificate_Status_Code::NO_REVOCATION_DATA);
+ }
+ }
+ }
+ }
+
+}
+
+Path_Validation_Result BOTAN_DLL 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)
+ {
+ 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::shared_ptr<const X509_Certificate>> cert_path;
+ Certificate_Status_Code path_building_result =
+ PKIX::build_certificate_path(cert_path, trusted_roots, end_entity, end_entity_extra);
+
+ if(path_building_result != Certificate_Status_Code::OK)
+ {
+ return Path_Validation_Result(path_building_result);
+ }
- return Path_Validation_Result(res, std::move(cert_path));
+ std::vector<std::set<Certificate_Status_Code>> status =
+ PKIX::check_chain(cert_path, ref_time,
+ hostname, usage,
+ restrictions.minimum_key_strength(),
+ restrictions.trusted_hashes());
+
+ if(path_building_result != Certificate_Status_Code::OK)
+ status[0].insert(path_building_result);
+
+ std::vector<std::set<Certificate_Status_Code>> crl_status =
+ PKIX::check_crl(cert_path, trusted_roots, ref_time);
+
+ std::vector<std::set<Certificate_Status_Code>> ocsp_status;
+
+ if(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
+ }
+
+ merge_results(status, crl_status, ocsp_status,
+ restrictions.require_revocation_information(),
+ restrictions.ocsp_all_intermediates());
+
+ return Path_Validation_Result(status, std::move(cert_path));
}
Path_Validation_Result x509_path_validate(
const X509_Certificate& end_cert,
const Path_Validation_Restrictions& restrictions,
- const std::vector<Certificate_Store*>& certstores,
+ const std::vector<Certificate_Store*>& trusted_roots,
const std::string& hostname,
Usage_Type usage,
- std::chrono::system_clock::time_point when)
+ std::chrono::system_clock::time_point when,
+ std::chrono::milliseconds ocsp_timeout)
{
std::vector<X509_Certificate> certs;
certs.push_back(end_cert);
- return x509_path_validate(certs, restrictions, certstores, hostname, usage, when);
+ return x509_path_validate(certs, restrictions, trusted_roots, hostname, usage, when, ocsp_timeout);
}
Path_Validation_Result x509_path_validate(
@@ -292,12 +597,13 @@ Path_Validation_Result x509_path_validate(
const Certificate_Store& store,
const std::string& hostname,
Usage_Type usage,
- std::chrono::system_clock::time_point when)
+ std::chrono::system_clock::time_point when,
+ std::chrono::milliseconds ocsp_timeout)
{
- std::vector<Certificate_Store*> certstores;
- certstores.push_back(const_cast<Certificate_Store*>(&store));
+ std::vector<Certificate_Store*> trusted_roots;
+ trusted_roots.push_back(const_cast<Certificate_Store*>(&store));
- return x509_path_validate(end_certs, restrictions, certstores, hostname, usage, when);
+ return x509_path_validate(end_certs, restrictions, trusted_roots, hostname, usage, when, ocsp_timeout);
}
Path_Validation_Result x509_path_validate(
@@ -306,22 +612,23 @@ Path_Validation_Result x509_path_validate(
const Certificate_Store& store,
const std::string& hostname,
Usage_Type usage,
- std::chrono::system_clock::time_point when)
+ std::chrono::system_clock::time_point when,
+ std::chrono::milliseconds ocsp_timeout)
{
std::vector<X509_Certificate> certs;
certs.push_back(end_cert);
- std::vector<Certificate_Store*> certstores;
- certstores.push_back(const_cast<Certificate_Store*>(&store));
+ std::vector<Certificate_Store*> trusted_roots;
+ trusted_roots.push_back(const_cast<Certificate_Store*>(&store));
- return x509_path_validate(certs, restrictions, certstores, hostname, usage, when);
+ return x509_path_validate(certs, restrictions, trusted_roots, hostname, usage, when, ocsp_timeout);
}
Path_Validation_Restrictions::Path_Validation_Restrictions(bool require_rev,
size_t key_strength,
- bool ocsp_all) :
+ bool ocsp_intermediates) :
m_require_revocation_information(require_rev),
- m_ocsp_all_intermediates(ocsp_all),
+ m_ocsp_all_intermediates(ocsp_intermediates),
m_minimum_key_strength(key_strength)
{
if(key_strength <= 80)
@@ -335,7 +642,7 @@ Path_Validation_Restrictions::Path_Validation_Restrictions(bool require_rev,
Path_Validation_Result::Path_Validation_Result(std::vector<std::set<Certificate_Status_Code>> status,
std::vector<std::shared_ptr<const X509_Certificate>>&& cert_chain) :
- m_overall(Certificate_Status_Code::VERIFIED),
+ m_overall(Certificate_Status_Code::OK),
m_all_status(status),
m_cert_path(cert_chain)
{
@@ -345,9 +652,11 @@ Path_Validation_Result::Path_Validation_Result(std::vector<std::set<Certificate_
if(!s.empty())
{
auto worst = *s.rbegin();
- // Leave OCSP confirmations on cert-level status only
- if(worst != Certificate_Status_Code::OCSP_RESPONSE_GOOD)
+ // Leave informative OCSP/CRL confirmations on cert-level status only
+ if(worst >= Certificate_Status_Code::FIRST_ERROR_STATUS)
+ {
m_overall = worst;
+ }
}
}
}
@@ -372,10 +681,9 @@ std::set<std::string> Path_Validation_Result::trusted_hashes() const
bool Path_Validation_Result::successful_validation() const
{
- if(result() == Certificate_Status_Code::VERIFIED ||
- result() == Certificate_Status_Code::OCSP_RESPONSE_GOOD)
- return true;
- return false;
+ return (result() == Certificate_Status_Code::VERIFIED ||
+ result() == Certificate_Status_Code::OCSP_RESPONSE_GOOD ||
+ result() == Certificate_Status_Code::VALID_CRL_CHECKED);
}
std::string Path_Validation_Result::result_string() const
@@ -385,68 +693,8 @@ std::string Path_Validation_Result::result_string() const
const char* Path_Validation_Result::status_string(Certificate_Status_Code code)
{
- switch(code)
- {
- case Certificate_Status_Code::VERIFIED:
- return "Verified";
- case Certificate_Status_Code::OCSP_RESPONSE_GOOD:
- return "OCSP response good";
- case Certificate_Status_Code::NO_REVOCATION_DATA:
- return "No revocation data";
- case Certificate_Status_Code::SIGNATURE_METHOD_TOO_WEAK:
- return "Signature method too weak";
- case Certificate_Status_Code::UNTRUSTED_HASH:
- return "Untrusted hash";
-
- case Certificate_Status_Code::CERT_NOT_YET_VALID:
- return "Certificate is not yet valid";
- case Certificate_Status_Code::CERT_HAS_EXPIRED:
- return "Certificate has expired";
- case Certificate_Status_Code::OCSP_NOT_YET_VALID:
- return "OCSP is not yet valid";
- case Certificate_Status_Code::OCSP_HAS_EXPIRED:
- return "OCSP has expired";
- case Certificate_Status_Code::CRL_NOT_YET_VALID:
- return "CRL is not yet valid";
- case Certificate_Status_Code::CRL_HAS_EXPIRED:
- return "CRL has expired";
-
- case Certificate_Status_Code::CERT_ISSUER_NOT_FOUND:
- return "Certificate issuer not found";
- case Certificate_Status_Code::CANNOT_ESTABLISH_TRUST:
- return "Cannot establish trust";
- case Certificate_Status_Code::CERT_CHAIN_LOOP:
- return "Loop in certificate chain";
-
- case Certificate_Status_Code::POLICY_ERROR:
- return "Policy error";
- case Certificate_Status_Code::INVALID_USAGE:
- return "Invalid usage";
- case Certificate_Status_Code::CERT_CHAIN_TOO_LONG:
- return "Certificate chain too long";
- case Certificate_Status_Code::CA_CERT_NOT_FOR_CERT_ISSUER:
- return "CA certificate not allowed to issue certs";
- case Certificate_Status_Code::CA_CERT_NOT_FOR_CRL_ISSUER:
- return "CA certificate not allowed to issue CRLs";
- case Certificate_Status_Code::OCSP_CERT_NOT_LISTED:
- return "OCSP cert not listed";
- case Certificate_Status_Code::OCSP_BAD_STATUS:
- return "OCSP bad status";
- case Certificate_Status_Code::CERT_NAME_NOMATCH:
- return "Certificate does not match provided name";
- case Certificate_Status_Code::NAME_CONSTRAINT_ERROR:
- return "Certificate does not pass name constraint";
- case Certificate_Status_Code::UNKNOWN_CRITICAL_EXTENSION:
- return "Unknown critical extension encountered";
-
- case Certificate_Status_Code::CERT_IS_REVOKED:
- return "Certificate is revoked";
- case Certificate_Status_Code::CRL_BAD_SIGNATURE:
- return "CRL bad signature";
- case Certificate_Status_Code::SIGNATURE_ERROR:
- return "Signature error";
- // intentionally no default so we are warned
- }
+ if(const char* s = to_string(code))
+ return s;
return "Unknown error";
}