diff options
Diffstat (limited to 'src/lib')
26 files changed, 1347 insertions, 549 deletions
diff --git a/src/lib/base/buf_comp.h b/src/lib/base/buf_comp.h index d0793b84b..264b16bd0 100644 --- a/src/lib/base/buf_comp.h +++ b/src/lib/base/buf_comp.h @@ -101,6 +101,13 @@ class BOTAN_DLL Buffered_Computation return output; } + std::vector<byte> final_stdvec() + { + std::vector<byte> output(output_length()); + final_result(output.data()); + return output; + } + template<typename Alloc> void final(std::vector<byte, Alloc>& out) { diff --git a/src/lib/tls/credentials_manager.cpp b/src/lib/tls/credentials_manager.cpp index 650d922ce..a42fb5789 100644 --- a/src/lib/tls/credentials_manager.cpp +++ b/src/lib/tls/credentials_manager.cpp @@ -93,53 +93,4 @@ Credentials_Manager::trusted_certificate_authorities( return std::vector<Certificate_Store*>(); } -namespace { - -bool cert_in_some_store(const std::vector<Certificate_Store*>& trusted_CAs, - const X509_Certificate& trust_root) - { - for(auto CAs : trusted_CAs) - if(CAs->certificate_known(trust_root)) - return true; - return false; - } - -Usage_Type choose_leaf_usage(const std::string& ctx) - { - // These are reversed because ctx is denoting the current perspective - if(ctx == "tls-client") - return Usage_Type::TLS_SERVER_AUTH; - else if(ctx == "tls-server") - return Usage_Type::TLS_CLIENT_AUTH; - else - return Usage_Type::UNSPECIFIED; - } - -} - -void Credentials_Manager::verify_certificate_chain( - const std::string& type, - const std::string& purported_hostname, - const std::vector<X509_Certificate>& cert_chain) - { - if(cert_chain.empty()) - throw Invalid_Argument("Certificate chain was empty"); - - auto trusted_CAs = trusted_certificate_authorities(type, purported_hostname); - - Path_Validation_Restrictions restrictions; - - Path_Validation_Result result = x509_path_validate(cert_chain, - restrictions, - trusted_CAs, - purported_hostname, - choose_leaf_usage(type)); - - if(!result.successful_validation()) - throw Exception("Certificate validation failure: " + result.result_string()); - - if(!cert_in_some_store(trusted_CAs, result.trust_root())) - throw Exception("Certificate chain roots in unknown/untrusted CA"); - } - } diff --git a/src/lib/tls/credentials_manager.h b/src/lib/tls/credentials_manager.h index 96e840d13..0e2fe0dea 100644 --- a/src/lib/tls/credentials_manager.h +++ b/src/lib/tls/credentials_manager.h @@ -44,25 +44,6 @@ class BOTAN_DLL Credentials_Manager const std::string& context); /** - * Check the certificate chain is valid up to a trusted root, and - * optionally (if hostname != "") that the hostname given is - * consistent with the leaf certificate. - * - * This function should throw an exception derived from - * std::exception with an informative what() result if the - * certificate chain cannot be verified. - - * @param type specifies the type of operation occurring - * @param hostname specifies the purported hostname - * @param cert_chain specifies a certificate chain leading to a - * trusted root CA certificate. - */ - virtual void verify_certificate_chain( - const std::string& type, - const std::string& hostname, - const std::vector<X509_Certificate>& cert_chain); - - /** * Return a cert chain we can use, ordered from leaf to root, * or else an empty vector. * diff --git a/src/lib/tls/tls_callbacks.cpp b/src/lib/tls/tls_callbacks.cpp new file mode 100644 index 000000000..e95b1c0f7 --- /dev/null +++ b/src/lib/tls/tls_callbacks.cpp @@ -0,0 +1,53 @@ +/* +* TLS Callbacks +* (C) 2016 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include <botan/tls_callbacks.h> +#include <botan/tls_policy.h> +#include <botan/x509path.h> +#include <botan/ocsp.h> +#include <botan/certstor.h> + +namespace Botan { + +TLS::Callbacks::~Callbacks() {} + +void TLS::Callbacks::tls_inspect_handshake_msg(const Handshake_Message&) + { + // default is no op + } + +std::string TLS::Callbacks::tls_server_choose_app_protocol(const std::vector<std::string>&) + { + return ""; + } + +void TLS::Callbacks::tls_verify_cert_chain( + const std::vector<X509_Certificate>& cert_chain, + const std::vector<Certificate_Store*>& trusted_roots, + Usage_Type usage, + const std::string& hostname, + const TLS::Policy& policy) + { + if(cert_chain.empty()) + throw Invalid_Argument("Certificate chain was empty"); + + Path_Validation_Restrictions restrictions(true, policy.minimum_signature_strength()); + + Path_Validation_Result result = + x509_path_validate(cert_chain, + restrictions, + trusted_roots, + (usage == Usage_Type::TLS_SERVER_AUTH ? hostname : ""), + usage, + std::chrono::system_clock::now(), + tls_verify_cert_chain_ocsp_timeout()); + + if(!result.successful_validation()) + throw Exception("Certificate validation failure: " + result.result_string()); + } + +} diff --git a/src/lib/tls/tls_callbacks.h b/src/lib/tls/tls_callbacks.h index f81071a05..c5fe32f29 100644 --- a/src/lib/tls/tls_callbacks.h +++ b/src/lib/tls/tls_callbacks.h @@ -11,11 +11,22 @@ #include <botan/tls_session.h> #include <botan/tls_alert.h> + namespace Botan { +class Certificate_Store; +class X509_Certificate; + +namespace OCSP { + +class Response; + +} + namespace TLS { class Handshake_Message; +class Policy; /** * Encapsulates the callbacks that a TLS channel will make which are due to @@ -53,7 +64,7 @@ class BOTAN_DLL Callbacks virtual void tls_record_received(u64bit seq_no, const uint8_t data[], size_t size) = 0; /** - * Mandary callback: alert received + * Mandatory callback: alert received * Called when an alert is received from the peer * If fatal, the connection is closing. If not fatal, the connection may * still be closing (depending on the error and the peer). @@ -81,6 +92,52 @@ class BOTAN_DLL Callbacks virtual void tls_session_activated() {} /** + * Optional callback with default impl: verify cert chain + * + * Default implementation performs a standard PKIX validation + * and initiates network OCSP request for end-entity cert. + * Override to provide different behavior. + * + * Check the certificate chain is valid up to a trusted root, and + * optionally (if hostname != "") that the hostname given is + * consistent with the leaf certificate. + * + * This function should throw an exception derived from + * std::exception with an informative what() result if the + * certificate chain cannot be verified. + * + * @param cert_chain specifies a certificate chain leading to a + * trusted root CA certificate. + * @param trusted_roots the list of trusted certificates + + * @param usage what this cert chain is being used for + * Usage_Type::TLS_SERVER_AUTH for server chains, + * Usage_Type::TLS_CLIENT_AUTH for client chains, + * Usage_Type::UNSPECIFIED for other uses + * @param hostname when authenticating a server, this is the hostname + * the client requested (eg via SNI). When authenticating a client, + * this is the server name the client is authenticating *to*. + * Empty in other cases or if no hostname was used. + * @param policy the TLS policy associated with the session being authenticated + * using the certificate chain + */ + virtual void tls_verify_cert_chain( + const std::vector<X509_Certificate>& cert_chain, + const std::vector<Certificate_Store*>& trusted_roots, + Usage_Type usage, + const std::string& hostname, + const TLS::Policy& policy); + + /** + * Called by default `tls_verify_cert_chain` to get the timeout to use for OCSP + * requests. Return 0 to disable online OCSP checks. + */ + virtual std::chrono::milliseconds tls_verify_cert_chain_ocsp_timeout() const + { + return std::chrono::milliseconds(0); + } + + /** * Optional callback: inspect handshake message * Throw an exception to abort the handshake. * Default simply ignores the message. diff --git a/src/lib/tls/tls_channel.cpp b/src/lib/tls/tls_channel.cpp index 95b151ad2..c8fe407e2 100644 --- a/src/lib/tls/tls_channel.cpp +++ b/src/lib/tls/tls_channel.cpp @@ -19,18 +19,6 @@ namespace Botan { namespace TLS { -Callbacks::~Callbacks() {} - -void Callbacks::tls_inspect_handshake_msg(const Handshake_Message&) - { - // default is no op - } - -std::string Callbacks::tls_server_choose_app_protocol(const std::vector<std::string>&) - { - return ""; - } - size_t TLS::Channel::IO_BUF_DEFAULT_SIZE = 10*1024; Channel::Channel(Callbacks& callbacks, diff --git a/src/lib/tls/tls_client.cpp b/src/lib/tls/tls_client.cpp index 183886c66..185084734 100644 --- a/src/lib/tls/tls_client.cpp +++ b/src/lib/tls/tls_client.cpp @@ -391,7 +391,13 @@ void Client::process_handshake_msg(const Handshake_State* active_state, try { - m_creds.verify_certificate_chain("tls-client", m_info.hostname(), server_certs); + auto trusted_CAs = m_creds.trusted_certificate_authorities("tls-client", m_info.hostname()); + + callbacks().tls_verify_cert_chain(server_certs, + trusted_CAs, + Usage_Type::TLS_SERVER_AUTH, + m_info.hostname(), + policy()); } catch(std::exception& e) { diff --git a/src/lib/tls/tls_policy.cpp b/src/lib/tls/tls_policy.cpp index 49a8ad1fc..4bd071d0b 100644 --- a/src/lib/tls/tls_policy.cpp +++ b/src/lib/tls/tls_policy.cpp @@ -156,6 +156,11 @@ size_t Policy::minimum_ecdh_group_size() const return 255; } +size_t Policy::minimum_signature_strength() const + { + return 110; + } + size_t Policy::minimum_rsa_bits() const { /* Default assumption is all end-entity certificates should @@ -466,6 +471,7 @@ void Policy::print(std::ostream& o) const o << "minimum_dh_group_size = " << minimum_dh_group_size() << '\n'; o << "minimum_ecdh_group_size = " << minimum_ecdh_group_size() << '\n'; o << "minimum_rsa_bits = " << minimum_rsa_bits() << '\n'; + o << "minimum_signature_strength = " << minimum_signature_strength() << '\n'; } std::vector<std::string> Strict_Policy::allowed_ciphers() const @@ -485,7 +491,7 @@ std::vector<std::string> Strict_Policy::allowed_macs() const std::vector<std::string> Strict_Policy::allowed_key_exchange_methods() const { - return { "ECDH" }; + return { "CECPQ1", "ECDH" }; } bool Strict_Policy::allow_tls10() const { return false; } diff --git a/src/lib/tls/tls_policy.h b/src/lib/tls/tls_policy.h index efef7e1f7..519139fff 100644 --- a/src/lib/tls/tls_policy.h +++ b/src/lib/tls/tls_policy.h @@ -57,6 +57,15 @@ class BOTAN_DLL Policy */ virtual std::vector<std::string> allowed_signature_methods() const; + /** + * The minimum signature strength we will accept + * Returning 80 allows RSA 1024 and SHA-1. Values larger than 80 disable SHA-1 support. + * Returning 110 allows RSA 2048. + * Return 128 to force ECC (P-256) or large (~3000 bit) RSA keys. + * Default is 110 + */ + virtual size_t minimum_signature_strength() const; + bool allowed_signature_method(const std::string& sig_method) const; /** @@ -301,7 +310,9 @@ class BOTAN_DLL NSA_Suite_B_128 : public Policy std::vector<std::string> allowed_ecc_curves() const override { return std::vector<std::string>({"secp256r1"}); } - + + size_t minimum_signature_strength() const override { return 128; } + bool allow_tls10() const override { return false; } bool allow_tls11() const override { return false; } bool allow_tls12() const override { return true; } @@ -419,7 +430,10 @@ class BOTAN_DLL Text_Policy : public Policy size_t minimum_rsa_bits() const override { return get_len("minimum_rsa_bits", Policy::minimum_rsa_bits()); } - + + size_t minimum_signature_strength() const override + { return get_len("minimum_signature_strength", Policy::minimum_signature_strength()); } + bool hide_unknown_users() const override { return get_bool("hide_unknown_users", Policy::hide_unknown_users()); } diff --git a/src/lib/tls/tls_server.cpp b/src/lib/tls/tls_server.cpp index 5e3b222f1..4e07b5f7c 100644 --- a/src/lib/tls/tls_server.cpp +++ b/src/lib/tls/tls_server.cpp @@ -523,7 +523,14 @@ void Server::process_certificate_verify_msg(Server_Handshake_State& pending_stat try { - m_creds.verify_certificate_chain ( "tls-server", "", client_certs ); + const std::string sni_hostname = pending_state.client_hello()->sni_hostname(); + auto trusted_CAs = m_creds.trusted_certificate_authorities("tls-server", sni_hostname); + + callbacks().tls_verify_cert_chain(client_certs, + trusted_CAs, + Usage_Type::TLS_CLIENT_AUTH, + sni_hostname, + policy()); } catch ( std::exception& e ) { diff --git a/src/lib/utils/http_util/http_util.cpp b/src/lib/utils/http_util/http_util.cpp index 970b90238..f5e5042e8 100644 --- a/src/lib/utils/http_util/http_util.cpp +++ b/src/lib/utils/http_util/http_util.cpp @@ -26,6 +26,8 @@ #include <sys/socket.h> #include <netdb.h> #include <unistd.h> +#else + //#warning "No network support enabled in http_util" #endif namespace Botan { @@ -101,7 +103,7 @@ std::string http_transact(const std::string& hostname, ssize_t sent = ::write(fd, &message[sent_so_far], left); if(sent < 0) - throw HTTP_Error("HTTP server hung up on us"); + throw HTTP_Error("write to HTTP server failed, error '" + std::string(::strerror(errno)) + "'"); else sent_so_far += static_cast<size_t>(sent); } @@ -113,7 +115,7 @@ std::string http_transact(const std::string& hostname, ssize_t got = ::read(fd, buf.data(), buf.size()); if(got < 0) - throw HTTP_Error("HTTP server hung up on us"); + throw HTTP_Error("read from HTTP server failed, error '" + std::string(::strerror(errno)) + "'"); else if(got > 0) oss.write(buf.data(), static_cast<std::streamsize>(got)); else @@ -122,8 +124,7 @@ std::string http_transact(const std::string& hostname, return oss.str(); #else - throw HTTP_Error("Cannot connect to " + hostname + - ": network code disabled in build"); + throw HTTP_Error("Cannot connect to " + hostname + ": network code disabled in build"); #endif } @@ -167,9 +168,12 @@ Response http_sync(http_exch_fn http_transact, const std::vector<byte>& body, size_t allowable_redirects) { + if(url.empty()) + throw HTTP_Error("URL empty"); + const auto protocol_host_sep = url.find("://"); if(protocol_host_sep == std::string::npos) - throw HTTP_Error("Invalid URL " + url); + throw HTTP_Error("Invalid URL '" + url + "'"); const auto host_loc_sep = url.find('/', protocol_host_sep + 3); diff --git a/src/lib/x509/cert_status.cpp b/src/lib/x509/cert_status.cpp new file mode 100644 index 000000000..76a102aef --- /dev/null +++ b/src/lib/x509/cert_status.cpp @@ -0,0 +1,100 @@ +/* +* (C) 2016 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include <botan/cert_status.h> + +namespace Botan { + +//static +const char* to_string(Certificate_Status_Code code) + { + switch(code) + { + case Certificate_Status_Code::VERIFIED: + return "Verified"; + case Certificate_Status_Code::OCSP_RESPONSE_GOOD: + return "OCSP response accepted as affirming unrevoked status for certificate"; + case Certificate_Status_Code::OCSP_SIGNATURE_OK: + return "Signature on OCSP response was found valid"; + case Certificate_Status_Code::VALID_CRL_CHECKED: + return "Valid CRL examined"; + + 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 "Hash function used is considered too weak for security"; + + 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 response has expired"; + case Certificate_Status_Code::CRL_NOT_YET_VALID: + return "CRL response 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::CHAIN_LACKS_TRUST_ROOT: + return "Certificate chain does not end in a CA certificate"; + case Certificate_Status_Code::CHAIN_NAME_MISMATCH: + return "Certificate issuer does not match subject of issuing cert"; + + case Certificate_Status_Code::POLICY_ERROR: + return "Certificate policy error"; + case Certificate_Status_Code::INVALID_USAGE: + return "Certificate does not allow the requested 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::OCSP_SIGNATURE_ERROR: + return "OCSP signature error"; + case Certificate_Status_Code::OCSP_ISSUER_NOT_FOUND: + return "Unable to find certificate issusing OCSP response"; + case Certificate_Status_Code::OCSP_RESPONSE_MISSING_KEYUSAGE: + return "OCSP issuer's keyusage prohibits OCSP"; + case Certificate_Status_Code::OCSP_RESPONSE_INVALID: + return "OCSP parsing valid"; + case Certificate_Status_Code::OCSP_NO_HTTP: + return "OCSP requests not available, no HTTP support compiled in"; + 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"; + case Certificate_Status_Code::CERT_PUBKEY_INVALID: + return "Certificate public key invalid"; + // intentionally no default so we are warned + } + + return nullptr; + } + +} diff --git a/src/lib/x509/cert_status.h b/src/lib/x509/cert_status.h index b69bd1832..8f514c092 100644 --- a/src/lib/x509/cert_status.h +++ b/src/lib/x509/cert_status.h @@ -1,5 +1,5 @@ /* -* Result enums +* Path validation result enums * (C) 2013 Jack Lloyd * * Botan is released under the Simplified BSD License (see license.txt) @@ -8,56 +8,79 @@ #ifndef BOTAN_X509_PATH_RESULT_H__ #define BOTAN_X509_PATH_RESULT_H__ +#include <botan/build.h> + namespace Botan { /** * Certificate validation status code */ enum class Certificate_Status_Code { - VERIFIED = 0x00000000, - OCSP_RESPONSE_GOOD, - NO_REVOCATION_DATA, + OK = 0, + VERIFIED = 0, + + // Revocation status + OCSP_RESPONSE_GOOD = 1, + OCSP_SIGNATURE_OK = 2, + VALID_CRL_CHECKED = 3, + OCSP_NO_HTTP = 4, + + // Errors + FIRST_ERROR_STATUS = 1000, - // Local policy failures SIGNATURE_METHOD_TOO_WEAK = 1000, - UNTRUSTED_HASH, + UNTRUSTED_HASH = 1001, + NO_REVOCATION_DATA = 1002, // Time problems CERT_NOT_YET_VALID = 2000, - CERT_HAS_EXPIRED, - OCSP_NOT_YET_VALID, - OCSP_HAS_EXPIRED, - CRL_NOT_YET_VALID, - CRL_HAS_EXPIRED, + CERT_HAS_EXPIRED = 2001, + OCSP_NOT_YET_VALID = 2002, + OCSP_HAS_EXPIRED = 2003, + CRL_NOT_YET_VALID = 2004, + CRL_HAS_EXPIRED = 2005, // Chain generation problems CERT_ISSUER_NOT_FOUND = 3000, - CANNOT_ESTABLISH_TRUST, - - CERT_CHAIN_LOOP, + CANNOT_ESTABLISH_TRUST = 3001, + CERT_CHAIN_LOOP = 3002, + CHAIN_LACKS_TRUST_ROOT = 3003, + CHAIN_NAME_MISMATCH = 3004, // Validation errors POLICY_ERROR = 4000, - INVALID_USAGE, - CERT_CHAIN_TOO_LONG, - CA_CERT_NOT_FOR_CERT_ISSUER, - NAME_CONSTRAINT_ERROR, + INVALID_USAGE = 4001, + CERT_CHAIN_TOO_LONG = 4002, + CA_CERT_NOT_FOR_CERT_ISSUER = 4003, + NAME_CONSTRAINT_ERROR = 4004, // Revocation errors - CA_CERT_NOT_FOR_CRL_ISSUER, - OCSP_CERT_NOT_LISTED, - OCSP_BAD_STATUS, + CA_CERT_NOT_FOR_CRL_ISSUER = 4005, + OCSP_CERT_NOT_LISTED = 4006, + OCSP_BAD_STATUS = 4007, - CERT_NAME_NOMATCH, - - UNKNOWN_CRITICAL_EXTENSION, + // Other problems + CERT_NAME_NOMATCH = 4008, + UNKNOWN_CRITICAL_EXTENSION = 4009, + OCSP_SIGNATURE_ERROR = 4501, + OCSP_ISSUER_NOT_FOUND = 4502, + OCSP_RESPONSE_MISSING_KEYUSAGE = 4503, + OCSP_RESPONSE_INVALID = 4504, // Hard failures CERT_IS_REVOKED = 5000, - CRL_BAD_SIGNATURE, - SIGNATURE_ERROR, + CRL_BAD_SIGNATURE = 5001, + SIGNATURE_ERROR = 5002, + CERT_PUBKEY_INVALID = 5003, }; +/** +* Convert a status code to a human readable diagnostic message +* @param code the certifcate status +* @return string literal constant, or nullptr if code unknown +*/ +BOTAN_DLL const char* to_string(Certificate_Status_Code code); + } #endif diff --git a/src/lib/x509/certstor.cpp b/src/lib/x509/certstor.cpp index 24cd84de7..1f7275675 100644 --- a/src/lib/x509/certstor.cpp +++ b/src/lib/x509/certstor.cpp @@ -7,6 +7,7 @@ #include <botan/certstor.h> #include <botan/internal/filesystem.h> +#include <botan/hash.h> namespace Botan { @@ -23,7 +24,18 @@ void Certificate_Store_In_Memory::add_certificate(const X509_Certificate& cert) return; } - m_certs.push_back(std::make_shared<X509_Certificate>(cert)); + m_certs.push_back(std::make_shared<const X509_Certificate>(cert)); + } + +void Certificate_Store_In_Memory::add_certificate(std::shared_ptr<const X509_Certificate> cert) + { + for(size_t i = 0; i != m_certs.size(); ++i) + { + if(*m_certs[i] == *cert) + return; + } + + m_certs.push_back(cert); } std::vector<X509_DN> Certificate_Store_In_Memory::all_subjects() const @@ -34,57 +46,70 @@ std::vector<X509_DN> Certificate_Store_In_Memory::all_subjects() const return subjects; } -namespace { - -template<typename T> std::shared_ptr<const X509_Certificate> -cert_search(const X509_DN& subject_dn, const std::vector<byte>& key_id, - const std::vector<std::shared_ptr<T>>& certs) +Certificate_Store_In_Memory::find_cert(const X509_DN& subject_dn, + const std::vector<byte>& key_id) const { - for(size_t i = 0; i != certs.size(); ++i) + for(size_t i = 0; i != m_certs.size(); ++i) { // Only compare key ids if set in both call and in the cert if(key_id.size()) { - std::vector<byte> skid = certs[i]->subject_key_id(); + std::vector<byte> skid = m_certs[i]->subject_key_id(); if(skid.size() && skid != key_id) // no match continue; } - if(certs[i]->subject_dn() == subject_dn) - return certs[i]; + if(m_certs[i]->subject_dn() == subject_dn) + return m_certs[i]; } return std::shared_ptr<const X509_Certificate>(); } -} std::shared_ptr<const X509_Certificate> -Certificate_Store_In_Memory::find_cert(const X509_DN& subject_dn, - const std::vector<byte>& key_id) const +Certificate_Store_In_Memory::find_cert_by_pubkey_sha1(const std::vector<byte>& key_hash) const { - return cert_search(subject_dn, key_id, m_certs); + if(key_hash.size() != 20) + throw Invalid_Argument("Certificate_Store_In_Memory::find_cert_by_pubkey_sha1 invalid hash"); + + for(size_t i = 0; i != m_certs.size(); ++i) + { + const std::vector<byte> hash_i = m_certs[i]->subject_public_key_bitstring_sha1(); + if(key_hash == hash_i) + { + return m_certs[i]; + } + } + + return nullptr; } void Certificate_Store_In_Memory::add_crl(const X509_CRL& crl) { - X509_DN crl_issuer = crl.issuer_dn(); + std::shared_ptr<const X509_CRL> crl_s = std::make_shared<const X509_CRL>(crl); + return add_crl(crl_s); + } + +void Certificate_Store_In_Memory::add_crl(std::shared_ptr<const X509_CRL> crl) + { + X509_DN crl_issuer = crl->issuer_dn(); for(size_t i = 0; i != m_crls.size(); ++i) { // Found an update of a previously existing one; replace it if(m_crls[i]->issuer_dn() == crl_issuer) { - if(m_crls[i]->this_update() <= crl.this_update()) - m_crls[i] = std::make_shared<X509_CRL>(crl); + if(m_crls[i]->this_update() <= crl->this_update()) + m_crls[i] = crl; return; } } // Totally new CRL, add to the list - m_crls.push_back(std::make_shared<X509_CRL>(crl)); + m_crls.push_back(crl); } std::shared_ptr<const X509_CRL> Certificate_Store_In_Memory::find_crl_for(const X509_Certificate& subject) const @@ -134,19 +159,4 @@ Certificate_Store_In_Memory::Certificate_Store_In_Memory(const std::string& dir) } #endif -std::shared_ptr<const X509_Certificate> -Certificate_Store_Overlay::find_cert(const X509_DN& subject_dn, - const std::vector<byte>& key_id) const - { - return cert_search(subject_dn, key_id, m_certs); - } - -std::vector<X509_DN> Certificate_Store_Overlay::all_subjects() const - { - std::vector<X509_DN> subjects; - for(size_t i = 0; i != m_certs.size(); ++i) - subjects.push_back(m_certs[i]->subject_dn()); - return subjects; - } - } diff --git a/src/lib/x509/certstor.h b/src/lib/x509/certstor.h index 56176739b..ba71334c5 100644 --- a/src/lib/x509/certstor.h +++ b/src/lib/x509/certstor.h @@ -31,6 +31,15 @@ class BOTAN_DLL Certificate_Store find_cert(const X509_DN& subject_dn, const std::vector<byte>& key_id) const = 0; /** + * Find a certificate by searching for one with a matching SHA-1 hash of + * public key. Used for OCSP. + * @param key_hash SHA-1 hash of the subject's public key + * @return a matching certificate or nullptr otherwise + */ + virtual std::shared_ptr<const X509_Certificate> + find_cert_by_pubkey_sha1(const std::vector<byte>& key_hash) const = 0; + + /** * Finds a CRL for the given certificate * @param subject the subject certificate * @return the CRL for subject or nullptr otherwise @@ -79,12 +88,24 @@ class BOTAN_DLL Certificate_Store_In_Memory : public Certificate_Store void add_certificate(const X509_Certificate& cert); /** + * Add a certificate already in a shared_ptr to the store. + * @param cert certificate to be added + */ + void add_certificate(std::shared_ptr<const X509_Certificate> cert); + + /** * Add a certificate revocation list (CRL) to the store. * @param crl CRL to be added */ void add_crl(const X509_CRL& crl); /** + * Add a certificate revocation list (CRL) to the store as a shared_ptr + * @param crl CRL to be added + */ + void add_crl(std::shared_ptr<const X509_CRL> crl); + + /** * @return DNs for all certificates managed by the store */ std::vector<X509_DN> all_subjects() const override; @@ -96,39 +117,19 @@ class BOTAN_DLL Certificate_Store_In_Memory : public Certificate_Store const X509_DN& subject_dn, const std::vector<byte>& key_id) const override; + std::shared_ptr<const X509_Certificate> + find_cert_by_pubkey_sha1(const std::vector<byte>& key_hash) const override; + /** * Finds a CRL for the given certificate */ std::shared_ptr<const X509_CRL> find_crl_for(const X509_Certificate& subject) const override; private: // TODO: Add indexing on the DN and key id to avoid linear search - std::vector<std::shared_ptr<X509_Certificate>> m_certs; - std::vector<std::shared_ptr<X509_CRL>> m_crls; - }; - -/** -* FIXME add doc -*/ -class BOTAN_DLL Certificate_Store_Overlay : public Certificate_Store - { - public: - explicit Certificate_Store_Overlay(const std::vector<std::shared_ptr<const X509_Certificate>>& certs) : - m_certs(certs) {} - - /** - * @return DNs for all certificates managed by the store - */ - std::vector<X509_DN> all_subjects() const override; - - /** - * Find a certificate by Subject DN and (optionally) key identifier - */ - std::shared_ptr<const X509_Certificate> find_cert( - const X509_DN& subject_dn, - const std::vector<byte>& key_id) const override; - private: - const std::vector<std::shared_ptr<const X509_Certificate>>& m_certs; + std::vector<std::shared_ptr<const X509_Certificate>> m_certs; + std::vector<std::shared_ptr<const X509_CRL>> m_crls; }; } + #endif diff --git a/src/lib/x509/certstor_sql/certstor_sql.cpp b/src/lib/x509/certstor_sql/certstor_sql.cpp index dfb8c5d78..4dceae305 100644 --- a/src/lib/x509/certstor_sql/certstor_sql.cpp +++ b/src/lib/x509/certstor_sql/certstor_sql.cpp @@ -78,6 +78,13 @@ Certificate_Store_In_SQL::find_cert(const X509_DN& subject_dn, const std::vector return cert; } +std::shared_ptr<const X509_Certificate> +Certificate_Store_In_SQL::find_cert_by_pubkey_sha1(const std::vector<byte>& /*key_hash*/) const + { + // TODO! + return nullptr; + } + std::shared_ptr<const X509_CRL> Certificate_Store_In_SQL::find_crl_for(const X509_Certificate& subject) const { diff --git a/src/lib/x509/certstor_sql/certstor_sql.h b/src/lib/x509/certstor_sql/certstor_sql.h index 0025884f9..0f493c56b 100644 --- a/src/lib/x509/certstor_sql/certstor_sql.h +++ b/src/lib/x509/certstor_sql/certstor_sql.h @@ -41,6 +41,9 @@ class BOTAN_DLL Certificate_Store_In_SQL : public Certificate_Store virtual std::shared_ptr<const X509_Certificate> find_cert(const X509_DN& subject_dn, const std::vector<byte>& key_id) const override; + std::shared_ptr<const X509_Certificate> + find_cert_by_pubkey_sha1(const std::vector<byte>& key_hash) const override; + /** * Returns all subject DNs known to the store instance. */ diff --git a/src/lib/x509/info.txt b/src/lib/x509/info.txt index be1e879c3..7e6afc5ad 100644 --- a/src/lib/x509/info.txt +++ b/src/lib/x509/info.txt @@ -1,10 +1,9 @@ define X509_CERTIFICATES 20151023 -define OCSP 20131128 +define OCSP 20161118 <requires> asn1 datastor -http_util pubkey sha1 </requires> diff --git a/src/lib/x509/ocsp.cpp b/src/lib/x509/ocsp.cpp index 761c5b436..fb7b718b6 100644 --- a/src/lib/x509/ocsp.cpp +++ b/src/lib/x509/ocsp.cpp @@ -14,7 +14,10 @@ #include <botan/base64.h> #include <botan/pubkey.h> #include <botan/x509path.h> -#include <botan/http_util.h> + +#if defined(BOTAN_HAS_HTTP_UTIL) + #include <botan/http_util.h> +#endif namespace Botan { @@ -22,6 +25,7 @@ namespace OCSP { namespace { +// TODO: should this be in a header somewhere? void decode_optional_list(BER_Decoder& ber, ASN1_Tag tag, std::vector<X509_Certificate>& output) @@ -44,65 +48,20 @@ void decode_optional_list(BER_Decoder& ber, } } -void check_signature(const std::vector<byte>& tbs_response, - const AlgorithmIdentifier& sig_algo, - const std::vector<byte>& signature, - const X509_Certificate& cert) - { - std::unique_ptr<Public_Key> pub_key(cert.subject_public_key()); - - const std::vector<std::string> sig_info = - split_on(OIDS::lookup(sig_algo.oid), '/'); - - if(sig_info.size() != 2 || sig_info[0] != pub_key->algo_name()) - throw Exception("Information in OCSP response does not match cert"); - - std::string padding = sig_info[1]; - Signature_Format format = - (pub_key->message_parts() >= 2) ? DER_SEQUENCE : IEEE_1363; - - PK_Verifier verifier(*pub_key, padding, format); - - if(!verifier.verify_message(ASN1::put_in_sequence(tbs_response), signature)) - throw Exception("Signature on OCSP response does not verify"); - } +} -void check_signature(const std::vector<byte>& tbs_response, - const AlgorithmIdentifier& sig_algo, - const std::vector<byte>& signature, - const Certificate_Store& trusted_roots, - const std::vector<X509_Certificate>& certs) +Request::Request(const X509_Certificate& issuer_cert, + const X509_Certificate& subject_cert) : + m_issuer(issuer_cert), + m_subject(subject_cert), + m_certid(m_issuer, m_subject) { - if(certs.size() < 1) - throw Invalid_Argument("Short cert chain for check_signature"); - - if(trusted_roots.certificate_known(certs[0])) - return check_signature(tbs_response, sig_algo, signature, certs[0]); - - // Otherwise attempt to chain the signing cert to a trust root - - if(!certs[0].allowed_extended_usage("PKIX.OCSPSigning")) - throw Exception("OCSP response cert does not allow OCSP signing"); - - auto result = x509_path_validate(certs, Path_Validation_Restrictions(), trusted_roots); - - if(!result.successful_validation()) - throw Exception("Certificate validation failure: " + result.result_string()); - - if(!trusted_roots.certificate_known(result.trust_root())) // not needed anymore? - throw Exception("Certificate chain roots in unknown/untrusted CA"); - - const std::vector<std::shared_ptr<const X509_Certificate>>& cert_path = result.cert_path(); - - check_signature(tbs_response, sig_algo, signature, *cert_path[0]); + if(subject_cert.issuer_dn() != issuer_cert.subject_dn()) + throw Invalid_Argument("Invalid cert pair to OCSP::Request (mismatched issuer,subject args?)"); } -} - std::vector<byte> Request::BER_encode() const { - CertID certid(m_issuer, m_subject); - return DER_Encoder().start_cons(SEQUENCE) .start_cons(SEQUENCE) .start_explicit(0) @@ -110,7 +69,7 @@ std::vector<byte> Request::BER_encode() const .end_explicit() .start_cons(SEQUENCE) .start_cons(SEQUENCE) - .encode(certid) + .encode(m_certid) .end_cons() .end_cons() .end_cons() @@ -122,8 +81,7 @@ std::string Request::base64_encode() const return Botan::base64_encode(BER_encode()); } -Response::Response(const Certificate_Store& trusted_roots, - const std::vector<byte>& response_bits) +Response::Response(const std::vector<byte>& response_bits) { BER_Decoder response_outer = BER_Decoder(response_bits).start_cons(SEQUENCE); @@ -145,71 +103,159 @@ Response::Response(const Certificate_Store& trusted_roots, BER_Decoder basicresponse = BER_Decoder(response_bytes.get_next_octet_string()).start_cons(SEQUENCE); - std::vector<byte> tbs_bits; - AlgorithmIdentifier sig_algo; - std::vector<byte> signature; - std::vector<X509_Certificate> certs; - basicresponse.start_cons(SEQUENCE) - .raw_bytes(tbs_bits) + .raw_bytes(m_tbs_bits) .end_cons() - .decode(sig_algo) - .decode(signature, BIT_STRING); - decode_optional_list(basicresponse, ASN1_Tag(0), certs); + .decode(m_sig_algo) + .decode(m_signature, BIT_STRING); + decode_optional_list(basicresponse, ASN1_Tag(0), m_certs); size_t responsedata_version = 0; - X509_DN name; - std::vector<byte> key_hash; - X509_Time produced_at; Extensions extensions; - BER_Decoder(tbs_bits) + BER_Decoder(m_tbs_bits) .decode_optional(responsedata_version, ASN1_Tag(0), ASN1_Tag(CONSTRUCTED | CONTEXT_SPECIFIC)) - .decode_optional(name, ASN1_Tag(1), + .decode_optional(m_signer_name, ASN1_Tag(1), ASN1_Tag(CONSTRUCTED | CONTEXT_SPECIFIC)) - .decode_optional_string(key_hash, OCTET_STRING, 2, + .decode_optional_string(m_key_hash, OCTET_STRING, 2, ASN1_Tag(CONSTRUCTED | CONTEXT_SPECIFIC)) - .decode(produced_at) + .decode(m_produced_at) .decode_list(m_responses) .decode_optional(extensions, ASN1_Tag(1), ASN1_Tag(CONSTRUCTED | CONTEXT_SPECIFIC)); + } + + response_outer.end_cons(); + } + +Certificate_Status_Code Response::verify_signature(const X509_Certificate& issuer) const + { + try + { + std::unique_ptr<Public_Key> pub_key(issuer.subject_public_key()); + + const std::vector<std::string> sig_info = + split_on(OIDS::lookup(m_sig_algo.oid), '/'); + + if(sig_info.size() != 2 || sig_info[0] != pub_key->algo_name()) + return Certificate_Status_Code::OCSP_RESPONSE_INVALID; + + std::string padding = sig_info[1]; + Signature_Format format = (pub_key->message_parts() >= 2) ? DER_SEQUENCE : IEEE_1363; - if(certs.empty()) + PK_Verifier verifier(*pub_key, padding, format); + + if(verifier.verify_message(ASN1::put_in_sequence(m_tbs_bits), m_signature)) + return Certificate_Status_Code::OCSP_SIGNATURE_OK; + else + return Certificate_Status_Code::OCSP_SIGNATURE_ERROR; + } + catch(Exception&) + { + return Certificate_Status_Code::OCSP_SIGNATURE_ERROR; + } + } + +Certificate_Status_Code Response::check_signature(const std::vector<Certificate_Store*>& trusted_roots, + const std::vector<std::shared_ptr<const X509_Certificate>>& ee_cert_path) const + { + std::shared_ptr<const X509_Certificate> signing_cert; + + for(size_t i = 0; i != trusted_roots.size(); ++i) + { + if(m_signer_name.empty() && m_key_hash.empty()) + return Certificate_Status_Code::OCSP_RESPONSE_INVALID; + + if(!m_signer_name.empty()) { - if(auto cert = trusted_roots.find_cert(name, std::vector<byte>())) - certs.push_back(*cert); - else - throw Exception("Could not find certificate that signed OCSP response"); + signing_cert = trusted_roots[i]->find_cert(m_signer_name, std::vector<byte>()); + if(signing_cert) + { + break; + } } - check_signature(tbs_bits, sig_algo, signature, trusted_roots, certs); + if(m_key_hash.size() > 0) + { + signing_cert = trusted_roots[i]->find_cert_by_pubkey_sha1(m_key_hash); + if(signing_cert) + { + break; + } + } } - response_outer.end_cons(); + if(!signing_cert && ee_cert_path.size() > 1) + { + // End entity cert is not allowed to sign their own OCSP request :) + for(size_t i = 1; i < ee_cert_path.size(); ++i) + { + // Check all CA certificates in the (assumed validated) EE cert path + if(!m_signer_name.empty() && ee_cert_path[i]->subject_dn() == m_signer_name) + { + signing_cert = ee_cert_path[i]; + break; + } + + if(m_key_hash.size() > 0 && ee_cert_path[i]->subject_public_key_bitstring_sha1() == m_key_hash) + { + signing_cert = ee_cert_path[i]; + break; + } + } + } + + if(!signing_cert && m_certs.size() > 0) + { + for(size_t i = 0; i < m_certs.size(); ++i) + { + // Check all CA certificates in the (assumed validated) EE cert path + if(!m_signer_name.empty() && m_certs[i].subject_dn() == m_signer_name) + { + signing_cert = std::make_shared<const X509_Certificate>(m_certs[i]); + break; + } + + if(m_key_hash.size() > 0 && m_certs[i].subject_public_key_bitstring_sha1() == m_key_hash) + { + signing_cert = std::make_shared<const X509_Certificate>(m_certs[i]); + break; + } + } + } + + if(!signing_cert) + return Certificate_Status_Code::OCSP_ISSUER_NOT_FOUND; + + if(!signing_cert->allowed_extended_usage("PKIX.OCSPSigning")) + return Certificate_Status_Code::OCSP_RESPONSE_MISSING_KEYUSAGE; + + return this->verify_signature(*signing_cert); } Certificate_Status_Code Response::status_for(const X509_Certificate& issuer, - const X509_Certificate& subject) const + const X509_Certificate& subject, + std::chrono::system_clock::time_point ref_time) const { for(const auto& response : m_responses) { if(response.certid().is_id_for(issuer, subject)) { - X509_Time current_time(std::chrono::system_clock::now()); + X509_Time x509_ref_time(ref_time); if(response.cert_status() == 1) return Certificate_Status_Code::CERT_IS_REVOKED; - if(response.this_update() > current_time) + if(response.this_update() > x509_ref_time) return Certificate_Status_Code::OCSP_NOT_YET_VALID; - if(response.next_update().time_is_set() && current_time > response.next_update()) + if(response.next_update().time_is_set() && x509_ref_time > response.next_update()) return Certificate_Status_Code::OCSP_HAS_EXPIRED; if(response.cert_status() == 0) @@ -222,9 +268,11 @@ Certificate_Status_Code Response::status_for(const X509_Certificate& issuer, return Certificate_Status_Code::OCSP_CERT_NOT_LISTED; } +#if defined(BOTAN_HAS_HTTP_UTIL) + Response online_check(const X509_Certificate& issuer, const X509_Certificate& subject, - const Certificate_Store* trusted_roots) + Certificate_Store* trusted_roots) { const std::string responder_url = subject.ocsp_responder(); @@ -241,11 +289,19 @@ Response online_check(const X509_Certificate& issuer, // Check the MIME type? - OCSP::Response response(*trusted_roots, http.body()); + OCSP::Response response(http.body()); + + std::vector<Certificate_Store*> trusted_roots_vec; + trusted_roots_vec.push_back(trusted_roots); + + if(trusted_roots) + response.check_signature(trusted_roots_vec); return response; } +#endif + } } diff --git a/src/lib/x509/ocsp.h b/src/lib/x509/ocsp.h index fe1796984..64e86b82f 100644 --- a/src/lib/x509/ocsp.h +++ b/src/lib/x509/ocsp.h @@ -29,10 +29,7 @@ class BOTAN_DLL Request * @param subject_cert subject certificate */ Request(const X509_Certificate& issuer_cert, - const X509_Certificate& subject_cert) : - m_issuer(issuer_cert), - m_subject(subject_cert) - {} + const X509_Certificate& subject_cert); /** * @return BER-encoded OCSP request @@ -53,12 +50,18 @@ class BOTAN_DLL Request * @return subject certificate */ const X509_Certificate& subject() const { return m_subject; } + + const std::vector<byte>& issuer_key_hash() const + { return m_certid.issuer_key_hash(); } private: X509_Certificate m_issuer, m_subject; + CertID m_certid; }; /** -* An OCSP response. +* OCSP response. +* +* Note this class is only usable as an OCSP client */ class BOTAN_DLL Response { @@ -69,17 +72,50 @@ class BOTAN_DLL Response Response() {} /** - * Creates an OCSP response. - * @param trusted_roots trusted roots for the OCSP response + * Parses an OCSP response. * @param response_bits response bits received */ - Response(const Certificate_Store& trusted_roots, - const std::vector<byte>& response_bits); + Response(const std::vector<byte>& response_bits); + + /** + * Check signature and return status + * The optional cert_path is the (already validated!) certificate path of + * the end entity which is being inquired about + * @param trust_roots list of certstores containing trusted roots + * @param cert_path optionally, the (already verified!) certificate path for the certificate + * this is an OCSP response for. This is necessary to find the correct intermediate CA in + * some cases. + */ + Certificate_Status_Code check_signature(const std::vector<Certificate_Store*>& trust_roots, + const std::vector<std::shared_ptr<const X509_Certificate>>& cert_path = {}) const; + + /** + * Verify that issuer's key signed this response + * @param issuer certificate of issuer + * @return if signature valid OCSP_SIGNATURE_OK else an error code + */ + Certificate_Status_Code verify_signature(const X509_Certificate& issuer) const; + + /** + * @return the time this OCSP response was supposedly produced at + */ + const X509_Time& produced_at() const { return m_produced_at; } + + /** + * @return DN of signer, if provided in response (may be empty) + */ + const X509_DN& signer_name() const { return m_signer_name; } + + /** + * @return key hash, if provided in response (may be empty) + */ + const std::vector<byte>& signer_key_hash() const { return m_key_hash; } /** * Searches the OCSP response for issuer and subject certificate. * @param issuer issuer certificate * @param subject subject certificate + * @param ref_time the reference time * @return OCSP status code, possible values: * CERT_IS_REVOKED, * OCSP_NOT_YET_VALID, @@ -89,12 +125,23 @@ class BOTAN_DLL Response * OCSP_CERT_NOT_LISTED */ Certificate_Status_Code status_for(const X509_Certificate& issuer, - const X509_Certificate& subject) const; + const X509_Certificate& subject, + std::chrono::system_clock::time_point ref_time = std::chrono::system_clock::now()) const; private: + X509_Time m_produced_at; + X509_DN m_signer_name; + std::vector<byte> m_key_hash; + std::vector<byte> m_tbs_bits; + AlgorithmIdentifier m_sig_algo; + std::vector<byte> m_signature; + std::vector<X509_Certificate> m_certs; + std::vector<SingleResponse> m_responses; }; +#if defined(BOTAN_HAS_HTTP_UTIL) + /** * Makes an online OCSP request via HTTP and returns the OCSP response. * @param issuer issuer certificate @@ -104,7 +151,9 @@ class BOTAN_DLL Response */ BOTAN_DLL Response online_check(const X509_Certificate& issuer, const X509_Certificate& subject, - const Certificate_Store* trusted_roots); + Certificate_Store* trusted_roots); + +#endif } diff --git a/src/lib/x509/ocsp_types.cpp b/src/lib/x509/ocsp_types.cpp index d470c2fa1..c9d349a4b 100644 --- a/src/lib/x509/ocsp_types.cpp +++ b/src/lib/x509/ocsp_types.cpp @@ -26,25 +26,11 @@ CertID::CertID(const X509_Certificate& issuer, std::unique_ptr<HashFunction> hash(HashFunction::create("SHA-160")); m_hash_id = AlgorithmIdentifier(hash->name(), AlgorithmIdentifier::USE_NULL_PARAM); - m_issuer_key_hash = unlock(hash->process(extract_key_bitstr(issuer))); + m_issuer_key_hash = unlock(hash->process(issuer.subject_public_key_bitstring())); m_issuer_dn_hash = unlock(hash->process(subject.raw_issuer_dn())); m_subject_serial = BigInt::decode(subject.serial_number()); } -std::vector<byte> CertID::extract_key_bitstr(const X509_Certificate& cert) const - { - const auto key_bits = cert.subject_public_key_bits(); - - AlgorithmIdentifier public_key_algid; - std::vector<byte> public_key_bitstr; - - BER_Decoder(key_bits) - .decode(public_key_algid) - .decode(public_key_bitstr, BIT_STRING); - - return public_key_bitstr; - } - bool CertID::is_id_for(const X509_Certificate& issuer, const X509_Certificate& subject) const { @@ -58,7 +44,7 @@ bool CertID::is_id_for(const X509_Certificate& issuer, if(m_issuer_dn_hash != unlock(hash->process(subject.raw_issuer_dn()))) return false; - if(m_issuer_key_hash != unlock(hash->process(extract_key_bitstr(issuer)))) + if(m_issuer_key_hash != unlock(hash->process(issuer.subject_public_key_bitstring()))) return false; } catch(...) diff --git a/src/lib/x509/ocsp_types.h b/src/lib/x509/ocsp_types.h index 6df8ac17f..40fbb85a8 100644 --- a/src/lib/x509/ocsp_types.h +++ b/src/lib/x509/ocsp_types.h @@ -30,9 +30,10 @@ class BOTAN_DLL CertID final : public ASN1_Object void encode_into(class DER_Encoder& to) const override; void decode_from(class BER_Decoder& from) override; - private: - std::vector<byte> extract_key_bitstr(const X509_Certificate& cert) const; + const std::vector<byte>& issuer_key_hash() const { return m_issuer_key_hash; } + + private: AlgorithmIdentifier m_hash_id; std::vector<byte> m_issuer_dn_hash; std::vector<byte> m_issuer_key_hash; diff --git a/src/lib/x509/x509cert.cpp b/src/lib/x509/x509cert.cpp index f56495a79..52802a8e4 100644 --- a/src/lib/x509/x509cert.cpp +++ b/src/lib/x509/x509cert.cpp @@ -143,11 +143,14 @@ void X509_Certificate::force_decode() m_issuer.add("X509.Certificate.v2.key_id", v2_issuer_key_id); m_subject.add("X509.Certificate.v2.key_id", v2_subject_key_id); - m_subject.add("X509.Certificate.public_key", - hex_encode(public_key.value)); + m_subject.add("X509.Certificate.public_key", hex_encode(public_key.value)); - std::unique_ptr<Public_Key> pub_key(subject_public_key()); - m_self_signed = (dn_subject == dn_issuer) && check_signature(*pub_key); + m_self_signed = false; + if(dn_subject == dn_issuer) + { + std::unique_ptr<Public_Key> pub_key(subject_public_key()); + m_self_signed = check_signature(*pub_key); + } if(m_self_signed && version == 0) { @@ -221,6 +224,29 @@ std::vector<byte> X509_Certificate::subject_public_key_bits() const return hex_decode(m_subject.get1("X509.Certificate.public_key")); } +std::vector<byte> X509_Certificate::subject_public_key_bitstring() const + { + // TODO: cache this + const std::vector<byte> key_bits = subject_public_key_bits(); + + AlgorithmIdentifier public_key_algid; + std::vector<byte> public_key_bitstr; + + BER_Decoder(key_bits) + .decode(public_key_algid) + .decode(public_key_bitstr, BIT_STRING); + + return public_key_bitstr; + } + +std::vector<byte> X509_Certificate::subject_public_key_bitstring_sha1() const + { + // TODO: cache this value + std::unique_ptr<HashFunction> hash(HashFunction::create("SHA-1")); + hash->update(this->subject_public_key_bitstring()); + return hash->final_stdvec(); + } + /* * Check if the certificate is for a CA */ diff --git a/src/lib/x509/x509cert.h b/src/lib/x509/x509cert.h index acdba7e02..5cf7c81fa 100644 --- a/src/lib/x509/x509cert.h +++ b/src/lib/x509/x509cert.h @@ -49,6 +49,19 @@ class BOTAN_DLL X509_Certificate : public X509_Object std::vector<byte> subject_public_key_bits() const; /** + * Get the bit string of the public key associated with this certificate + * @return subject public key of this certificate + */ + std::vector<byte> subject_public_key_bitstring() const; + + /** + * Get the SHA-1 bit string of the public key associated with this certificate. + * This is used for OCSP among other protocols + * @return hash of subject public key of this certificate + */ + std::vector<byte> subject_public_key_bitstring_sha1() const; + + /** * Get the certificate's issuer distinguished name (DN). * @return issuer DN of this certificate */ diff --git a/src/lib/x509/x509path.cpp b/src/lib/x509/x509path.cpp index f0b07e5fc..beda83eed 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_HAS_ONLINE_REVOCATION_CHECKS) + #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,501 @@ 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); + } + } + } + + while(cert_status.size() > 0 && cert_status.back().empty()) + cert_status.pop_back(); - 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); + + 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); - // 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; + 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); + } + } + + while(cert_status.size() > 0 && cert_status.back().empty()) + cert_status.pop_back(); + + 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_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) + +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); + } + +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, + 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"); - if(!ca->allowed_usage(CRL_SIGN)) - status.insert(Certificate_Status_Code::CA_CERT_NOT_FOR_CRL_ISSUER); + 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.this_update())) - status.insert(Certificate_Status_Code::CRL_NOT_YET_VALID); + 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(validation_time > X509_Time(crl.next_update())) - status.insert(Certificate_Status_Code::CRL_HAS_EXPIRED); + // 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_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(crl.check_signature(ca->subject_public_key()) == false) - status.insert(Certificate_Status_Code::CRL_BAD_SIGNATURE); + 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(crl.is_revoked(*subject)) - status.insert(Certificate_Status_Code::CERT_IS_REVOKED); + if(status == std::future_status::ready) + { + crls[i] = future_crls[i].get(); + } + } + catch(std::exception& e) + { + // crls[i] left null + } + } } - if(self_signed_ee_cert) - cert_status.back().insert(Certificate_Status_Code::CANNOT_ESTABLISH_TRUST); + const std::vector<std::set<Certificate_Status_Code>> crl_status = PKIX::check_crl(cert_path, crls, ref_time); - return cert_status; + 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 -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); +void PKIX::merge_revocation_status(std::vector<std::set<Certificate_Status_Code>>& chain_status, + 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) + { + if(chain_status.empty()) + throw Invalid_Argument("PKIX::merge_revocation_status chain_status was empty"); - if(!hostname.empty() && !cert_path[0]->matches_dns_name(hostname)) - res[0].insert(Certificate_Status_Code::CERT_NAME_NOMATCH); + for(size_t i = 0; i != chain_status.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; + } + chain_status[i].insert(code); + } + } - return Path_Validation_Result(res, std::move(cert_path)); + if(i < ocsp.size() && ocsp[i].size() > 0) + { + for(auto&& code : ocsp[i]) + { + if(code == Certificate_Status_Code::OCSP_RESPONSE_GOOD) + { + 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 std::vector<std::set<Certificate_Status_Code>>& 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 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 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<std::set<Certificate_Status_Code>> status = + PKIX::check_chain(cert_path, ref_time, + hostname, usage, + restrictions.minimum_key_strength(), + restrictions.trusted_hashes()); + + 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 + } + + PKIX::merge_revocation_status(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 +639,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 +654,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,21 +684,10 @@ 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_all_status(status), - m_cert_path(cert_chain) + m_cert_path(cert_chain), + m_overall(PKIX::overall_status(m_all_status)) { - // take the "worst" error as overall - for(const auto& s : m_all_status) - { - if(!s.empty()) - { - auto worst = *s.rbegin(); - // Leave OCSP confirmations on cert-level status only - if(worst != Certificate_Status_Code::OCSP_RESPONSE_GOOD) - m_overall = worst; - } - } } const X509_Certificate& Path_Validation_Result::trust_root() const @@ -372,10 +710,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 +722,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"; } diff --git a/src/lib/x509/x509path.h b/src/lib/x509/x509path.h index f65652e59..e8d90b4a9 100644 --- a/src/lib/x509/x509path.h +++ b/src/lib/x509/x509path.h @@ -11,9 +11,15 @@ #include <botan/cert_status.h> #include <botan/x509cert.h> #include <botan/certstor.h> +#include <botan/ocsp.h> +#include <functional> #include <set> #include <chrono> +#if defined(BOTAN_TARGET_OS_HAS_THREADS) && defined(BOTAN_HAS_HTTP_UTIL) + #define BOTAN_HAS_ONLINE_REVOCATION_CHECKS +#endif + namespace Botan { /** @@ -28,7 +34,11 @@ class BOTAN_DLL Path_Validation_Restrictions * operations, eg 80 means 2^80) of a signature. Signatures * weaker than this are rejected. If more than 80, SHA-1 * signatures are also rejected. - * @param ocsp_all_intermediates + * 80 bit strength requires 1024 bit RSA + * 110 bit strength requires 2048 bit RSA + * Using 128 requires ECC (P-256) or ~3000 bit RSA keys. + * @param ocsp_all_intermediates Make OCSP requests for all CAs as + * well as end entity (if OCSP enabled in path validation request) */ Path_Validation_Restrictions(bool require_rev = false, size_t minimum_key_strength = 80, @@ -39,7 +49,8 @@ class BOTAN_DLL Path_Validation_Restrictions * @param minimum_key_strength is the minimum strength (in terms of * operations, eg 80 means 2^80) of a signature. Signatures * weaker than this are rejected. - * @param ocsp_all_intermediates + * @param ocsp_all_intermediates Make OCSP requests for all CAs as + * well as end entity (if OCSP enabled in path validation request) * @param trusted_hashes a set of trusted hashes. Any signatures * created using a hash other than one of these will be * rejected. @@ -60,7 +71,8 @@ class BOTAN_DLL Path_Validation_Restrictions { return m_require_revocation_information; } /** - * FIXME add doc + * @return whether all intermediate CAs should also be OCSPed. If false + * then only end entity OCSP is required/requested. */ bool ocsp_all_intermediates() const { return m_ocsp_all_intermediates; } @@ -106,6 +118,7 @@ class BOTAN_DLL Path_Validation_Result /** * @return the full path from subject to trust root + * This path may be empty */ const std::vector<std::shared_ptr<const X509_Certificate>>& cert_path() const { return m_cert_path; } @@ -151,52 +164,50 @@ class BOTAN_DLL Path_Validation_Result explicit Path_Validation_Result(Certificate_Status_Code status) : m_overall(status) {} private: - friend 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*>& certstores); - - Certificate_Status_Code m_overall; std::vector<std::set<Certificate_Status_Code>> m_all_status; std::vector<std::shared_ptr<const X509_Certificate>> m_cert_path; + Certificate_Status_Code m_overall; }; - /** * PKIX Path Validation * @param end_certs certificate chain to validate * @param restrictions path validation restrictions -* @param certstores list of certificate stores that contain trusted certificates +* @param trusted_roots list of certificate stores that contain trusted certificates * @param hostname if not empty, compared against the DNS name in end_certs[0] * @param usage if not set to UNSPECIFIED, compared against the key usage in end_certs[0] * @param validation_time what reference time to use for validation +* @param ocsp_timeout timeoutput for OCSP operations, 0 disables OCSP check * @return result of the path validation */ 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*>& certstores, + const std::vector<Certificate_Store*>& trusted_roots, const std::string& hostname = "", Usage_Type usage = Usage_Type::UNSPECIFIED, - std::chrono::system_clock::time_point validation_time = std::chrono::system_clock::now()); + std::chrono::system_clock::time_point validation_time = std::chrono::system_clock::now(), + std::chrono::milliseconds ocsp_timeout = std::chrono::milliseconds(0)); /** * PKIX Path Validation * @param end_cert certificate to validate * @param restrictions path validation restrictions -* @param certstores list of stores that contain trusted certificates +* @param trusted_roots list of stores that contain trusted certificates * @param hostname if not empty, compared against the DNS name in end_cert * @param usage if not set to UNSPECIFIED, compared against the key usage in end_cert * @param validation_time what reference time to use for validation +* @param ocsp_timeout timeoutput for OCSP operations, 0 disables OCSP check * @return result of the path validation */ Path_Validation_Result BOTAN_DLL 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 = Usage_Type::UNSPECIFIED, - std::chrono::system_clock::time_point validation_time = std::chrono::system_clock::now()); + std::chrono::system_clock::time_point validation_time = std::chrono::system_clock::now(), + std::chrono::milliseconds ocsp_timeout = std::chrono::milliseconds(0)); /** * PKIX Path Validation @@ -206,6 +217,7 @@ Path_Validation_Result BOTAN_DLL x509_path_validate( * @param hostname if not empty, compared against the DNS name in end_cert * @param usage if not set to UNSPECIFIED, compared against the key usage in end_cert * @param validation_time what reference time to use for validation +* @param ocsp_timeout timeoutput for OCSP operations, 0 disables OCSP check * @return result of the path validation */ Path_Validation_Result BOTAN_DLL x509_path_validate( @@ -214,7 +226,8 @@ Path_Validation_Result BOTAN_DLL x509_path_validate( const Certificate_Store& store, const std::string& hostname = "", Usage_Type usage = Usage_Type::UNSPECIFIED, - std::chrono::system_clock::time_point validation_time = std::chrono::system_clock::now()); + std::chrono::system_clock::time_point validation_time = std::chrono::system_clock::now(), + std::chrono::milliseconds ocsp_timeout = std::chrono::milliseconds(0)); /** * PKIX Path Validation @@ -224,6 +237,7 @@ Path_Validation_Result BOTAN_DLL x509_path_validate( * @param hostname if not empty, compared against the DNS name in end_certs[0] * @param usage if not set to UNSPECIFIED, compared against the key usage in end_certs[0] * @param validation_time what reference time to use for validation +* @param ocsp_timeout timeoutput for OCSP operations, 0 disables OCSP check * @return result of the path validation */ Path_Validation_Result BOTAN_DLL x509_path_validate( @@ -232,7 +246,166 @@ Path_Validation_Result BOTAN_DLL x509_path_validate( const Certificate_Store& store, const std::string& hostname = "", Usage_Type usage = Usage_Type::UNSPECIFIED, - std::chrono::system_clock::time_point validation_time = std::chrono::system_clock::now()); + std::chrono::system_clock::time_point validation_time = std::chrono::system_clock::now(), + std::chrono::milliseconds ocsp_timeout = std::chrono::milliseconds(0)); + + +/** +* namespace PKIX holds the building blocks that are called by x509_path_validate. +* This allows custom validation logic to be written by applications and makes +* for easier testing, but unless you're positive you know what you're doing you +* probably want to just call x509_path_validate instead. +*/ +namespace PKIX { + +/** +* Build certificate path +* @param cert_path_out output parameter, cert_path will be appended to this vector +* @param trusted_certstores list of certificate stores that contain trusted certificates +* @param end_entity the cert to be validated +* @param end_entity_extra optional list of additional untrusted certs for path building +* @return result of the path building operation (OK or error) +*/ +Certificate_Status_Code +BOTAN_DLL build_certificate_path(std::vector<std::shared_ptr<const X509_Certificate>>& cert_path_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); + +/** +* Check the certificate chain, but not any revocation data +* +* @param cert_path path built by build_certificate_path with OK result +* @param ref_time whatever time you want to perform the validation +* against (normally current system clock) +* @param hostname the hostname +* @param usage end entity usage checks +* @param min_signature_algo_strength 80 or 110 typically +* Note 80 allows 1024 bit RSA and SHA-1. 110 allows 2048 bit RSA and SHA-2. +* Using 128 requires ECC (P-256) or ~3000 bit RSA keys. +* @param trusted_hashes set of trusted hash functions, empty means accept any +* hash we have an OID for +* @return vector of results on per certificate in the path, each containing a set of +* results. If all codes in the set are < Certificate_Status_Code::FIRST_ERROR_STATUS, +* then the result for that certificate is successful. If all results are +*/ +std::vector<std::set<Certificate_Status_Code>> +BOTAN_DLL 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); + +/** +* Check OCSP responses for revocation information +* @param cert_path path already validated by check_chain +* @param ocsp_responses the OCSP responses to consider +* @param certstores trusted roots +* @param ref_time whatever time you want to perform the validation against +* (normally current system clock) +* @return revocation status +*/ +std::vector<std::set<Certificate_Status_Code>> +BOTAN_DLL 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*>& certstores, + std::chrono::system_clock::time_point ref_time); + +/** +* Check CRLs for revocation infomration +* @param cert_path path already validated by check_chain +* @param crls the list of CRLs to check, it is assumed that crls[i] (if not null) +* is the associated CRL for the subject in cert_path[i]. +* @param ref_time whatever time you want to perform the validation against +* (normally current system clock) +* @return revocation status +*/ +std::vector<std::set<Certificate_Status_Code>> +BOTAN_DLL 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); + +/** +* Check CRLs for revocation infomration +* @param cert_path path already validated by check_chain +* @param certstores a list of certificate stores to query for the CRL +* @param ref_time whatever time you want to perform the validation against +* (normally current system clock) +* @return revocation status +*/ +std::vector<std::set<Certificate_Status_Code>> +BOTAN_DLL 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 defined(BOTAN_HAS_ONLINE_REVOCATION_CHECKS) + +/** +* Check OCSP using online (HTTP) access. Current version creates a thread and +* network connection per OCSP request made. +* +* @param cert_path path already validated by check_chain +* @param trusted_certstores a list of certstores with trusted certs +* @param ref_time whatever time you want to perform the validation against +* (normally current system clock) +* @param timeout for timing out the responses, though actually this function +* may block for up to timeout*cert_path.size()*C for some small C. +* @param ocsp_check_intermediate_CAs if true also performs OCSP on any intermediate +* CA certificates. If false, only does OCSP on the end entity cert. +* @return revocation status +*/ +std::vector<std::set<Certificate_Status_Code>> +BOTAN_DLL 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); + +/** +* Check CRL using online (HTTP) access. Current version creates a thread and +* network connection per CRL access. + +* @param cert_path path already validated by check_chain +* @param trusted_certstores a list of certstores with trusted certs +* @param certstore_to_recv_crls optional (nullptr to disable), all CRLs +* retreived will be saved to this cert store. +* @param ref_time whatever time you want to perform the validation against +* (normally current system clock) +* @param timeout for timing out the responses, though actually this function +* may block for up to timeout*cert_path.size()*C for some small C. +* @return revocation status +*/ +std::vector<std::set<Certificate_Status_Code>> +BOTAN_DLL check_crl_online(const std::vector<std::shared_ptr<const X509_Certificate>>& cert_path, + const std::vector<Certificate_Store*>& trusted_certstores, + Certificate_Store_In_Memory* certstore_to_recv_crls, + std::chrono::system_clock::time_point ref_time, + std::chrono::milliseconds timeout); + +#endif + +/** +* Find overall status (OK, error) of a validation +* @param cert_status result of merge_revocation_status or check_chain +*/ +Certificate_Status_Code BOTAN_DLL overall_status(const std::vector<std::set<Certificate_Status_Code>>& cert_status); + +/** +* Merge the results from CRL and/or OCSP checks into chain_status +* @param chain_status the certificate status +* @param crl_status results from check_crl +* @param ocsp_status results from check_ocsp +* @param require_rev_on_end_entity require valid CRL or OCSP on end-entity cert +* @param require_rev_on_intermediates require valid CRL or OCSP on all intermediate certificates +*/ +void BOTAN_DLL merge_revocation_status(std::vector<std::set<Certificate_Status_Code>>& chain_status, + const std::vector<std::set<Certificate_Status_Code>>& crl_status, + const std::vector<std::set<Certificate_Status_Code>>& ocsp_status, + bool require_rev_on_end_entity, + bool require_rev_on_intermediates); + +} } |