aboutsummaryrefslogtreecommitdiffstats
path: root/src/lib
diff options
context:
space:
mode:
authorJack Lloyd <[email protected]>2016-11-25 17:15:28 -0500
committerJack Lloyd <[email protected]>2016-11-25 17:15:28 -0500
commitce1c593c8f6258a5fa0df50f620e4bdde4e7d034 (patch)
treef27f17e6f24657d138dd1946314801d9415e4a6e /src/lib
parent4a849b7ebb329630ef03d5b3961d57c5f76cfa0b (diff)
parentcdb20d3599f38807f4495c9c705b5864928b2824 (diff)
Merge GH #653 OCSP and X.509 path validation refactor
Splits up path validation into several sub-functions for easier testing and creating customized validation code. Much improved OCSP handling and OCSP tests.
Diffstat (limited to 'src/lib')
-rw-r--r--src/lib/base/buf_comp.h7
-rw-r--r--src/lib/tls/credentials_manager.cpp49
-rw-r--r--src/lib/tls/credentials_manager.h19
-rw-r--r--src/lib/tls/tls_callbacks.cpp53
-rw-r--r--src/lib/tls/tls_callbacks.h59
-rw-r--r--src/lib/tls/tls_channel.cpp12
-rw-r--r--src/lib/tls/tls_client.cpp8
-rw-r--r--src/lib/tls/tls_policy.cpp8
-rw-r--r--src/lib/tls/tls_policy.h18
-rw-r--r--src/lib/tls/tls_server.cpp9
-rw-r--r--src/lib/utils/http_util/http_util.cpp14
-rw-r--r--src/lib/x509/cert_status.cpp100
-rw-r--r--src/lib/x509/cert_status.h75
-rw-r--r--src/lib/x509/certstor.cpp76
-rw-r--r--src/lib/x509/certstor.h53
-rw-r--r--src/lib/x509/certstor_sql/certstor_sql.cpp7
-rw-r--r--src/lib/x509/certstor_sql/certstor_sql.h3
-rw-r--r--src/lib/x509/info.txt3
-rw-r--r--src/lib/x509/ocsp.cpp228
-rw-r--r--src/lib/x509/ocsp.h71
-rw-r--r--src/lib/x509/ocsp_types.cpp18
-rw-r--r--src/lib/x509/ocsp_types.h5
-rw-r--r--src/lib/x509/x509cert.cpp34
-rw-r--r--src/lib/x509/x509cert.h13
-rw-r--r--src/lib/x509/x509path.cpp745
-rw-r--r--src/lib/x509/x509path.h209
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);
+
+}
}