aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/cli/x509.cpp7
-rw-r--r--src/lib/x509/info.txt3
-rw-r--r--src/lib/x509/ocsp.cpp208
-rw-r--r--src/lib/x509/ocsp.h62
-rw-r--r--src/lib/x509/ocsp_types.cpp18
-rw-r--r--src/lib/x509/ocsp_types.h5
-rw-r--r--src/lib/x509/x509path.cpp696
-rw-r--r--src/lib/x509/x509path.h108
8 files changed, 748 insertions, 359 deletions
diff --git a/src/cli/x509.cpp b/src/cli/x509.cpp
index 25261a2d1..7ae980d76 100644
--- a/src/cli/x509.cpp
+++ b/src/cli/x509.cpp
@@ -104,7 +104,8 @@ class Cert_Info final : public Command
BOTAN_REGISTER_COMMAND("cert_info", Cert_Info);
-#if defined(BOTAN_HAS_OCSP)
+#if defined(BOTAN_HAS_OCSP) && defined(BOTAN_HAS_HTTP_UTIL)
+
class OCSP_Check final : public Command
{
public:
@@ -119,7 +120,7 @@ class OCSP_Check final : public Command
cas.add_certificate(issuer);
Botan::OCSP::Response resp = Botan::OCSP::online_check(issuer, subject, &cas);
- auto status = resp.status_for(issuer, subject);
+ auto status = resp.status_for(issuer, subject, std::chrono::system_clock::now());
if(status == Botan::Certificate_Status_Code::VERIFIED)
{
@@ -135,7 +136,7 @@ class OCSP_Check final : public Command
BOTAN_REGISTER_COMMAND("ocsp_check", OCSP_Check);
-#endif // OCSP
+#endif // OCSP && HTTP
class Cert_Verify final : public Command
{
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..af7126580 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,18 @@ 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]);
}
-}
-
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 +67,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 +79,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 +101,141 @@ 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(certs.empty())
+ 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;
+
+ 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;
+ }
+ }
+
+ if(m_key_hash.size() > 0)
+ {
+ signing_cert = trusted_roots[i]->find_cert_by_pubkey_sha1(m_key_hash);
+ if(signing_cert)
+ {
+ break;
+ }
}
+ }
- check_signature(tbs_bits, sig_algo, signature, trusted_roots, certs);
+ if(!signing_cert)
+ {
+ 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;
+ }
+ }
}
- response_outer.end_cons();
+ // TODO: this ignores m_certs
+
+ 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 +248,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 +269,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..b86b6a5e4 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,12 +72,36 @@ class BOTAN_DLL Response
Response() {}
/**
- * Creates an OCSP response.
- * @param trusted_roots trusted roots for the OCSP response
+ * Parses an OCSP response.
+ * @param request the OCSP request this is a respone to
* @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
+ */
+ 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
+ */
+ Certificate_Status_Code verify_signature(const X509_Certificate& issuer) const;
+
+ 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.
@@ -89,12 +116,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 +142,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/x509path.cpp b/src/lib/x509/x509path.cpp
index f0b07e5fc..946539bab 100644
--- a/src/lib/x509/x509path.cpp
+++ b/src/lib/x509/x509path.cpp
@@ -1,13 +1,12 @@
/*
* X.509 Certificate Path Validation
-* (C) 2010,2011,2012,2014 Jack Lloyd
+* (C) 2010,2011,2012,2014,2016 Jack Lloyd
*
* Botan is released under the Simplified BSD License (see license.txt)
*/
#include <botan/x509path.h>
#include <botan/ocsp.h>
-#include <botan/http_util.h>
#include <botan/parsing.h>
#include <botan/pubkey.h>
#include <botan/oids.h>
@@ -15,98 +14,58 @@
#include <chrono>
#include <vector>
#include <set>
-#include <future>
-namespace Botan {
-
-namespace {
-
-std::shared_ptr<const X509_Certificate>
-find_issuing_cert(const X509_Certificate& cert,
- Certificate_Store& end_certs,
- const std::vector<Certificate_Store*>& certstores)
- {
- const X509_DN issuer_dn = cert.issuer_dn();
- const std::vector<byte> auth_key_id = cert.authority_key_id();
-
- if(std::shared_ptr<const X509_Certificate> c = end_certs.find_cert(issuer_dn, auth_key_id))
- {
- if(*c != cert)
- return c;
- }
-
- for(size_t i = 0; i != certstores.size(); ++i)
- {
- if(std::shared_ptr<const X509_Certificate> c = certstores[i]->find_cert(issuer_dn, auth_key_id))
- return c;
- }
-
- return nullptr;
- }
-
-std::shared_ptr<const X509_CRL> find_crls_for(const X509_Certificate& cert,
- const std::vector<Certificate_Store*>& certstores)
- {
- for(size_t i = 0; i != certstores.size(); ++i)
- {
- if(std::shared_ptr<const X509_CRL> crl = certstores[i]->find_crl_for(cert))
- return crl;
- }
-
-#if 0
- const std::string crl_url = cert.crl_distribution_point();
- if(crl_url != "")
- {
- std::cout << "Downloading CRL " << crl_url << "\n";
- auto http = HTTP::GET_sync(crl_url);
-
- std::cout << http.status_message() << "\n";
-
- http.throw_unless_ok();
- // check the mime type
-
- std::unique_ptr<X509_CRL> crl(new X509_CRL(http.body()));
-
- return crl.release();
- }
+#if defined(BOTAN_TARGET_OS_HAS_THREADS) && defined(BOTAN_HAS_HTTP_UTIL)
+ #include <future>
+ #include <botan/http_util.h>
#endif
- return nullptr;
- }
+namespace Botan {
+/*
+* PKIX path validation
+*/
std::vector<std::set<Certificate_Status_Code>>
-check_chain(const std::vector<std::shared_ptr<const X509_Certificate>>& cert_path,
- const Path_Validation_Restrictions& restrictions,
- const std::vector<Certificate_Store*>& certstores,
- std::chrono::system_clock::time_point ref_time)
+PKIX::check_chain(const std::vector<std::shared_ptr<const X509_Certificate>>& cert_path,
+ std::chrono::system_clock::time_point ref_time,
+ const std::string& hostname,
+ Usage_Type usage,
+ size_t min_signature_algo_strength,
+ const std::set<std::string>& trusted_hashes)
{
- const std::set<std::string>& trusted_hashes = restrictions.trusted_hashes();
+ if(cert_path.empty())
+ throw Invalid_Argument("PKIX::check_chain cert_path empty");
const bool self_signed_ee_cert = (cert_path.size() == 1);
X509_Time validation_time(ref_time);
- std::vector<std::future<OCSP::Response>> ocsp_responses;
-
std::vector<std::set<Certificate_Status_Code>> cert_status(cert_path.size());
+ if(!hostname.empty() && !cert_path[0]->matches_dns_name(hostname))
+ cert_status[0].insert(Certificate_Status_Code::CERT_NAME_NOMATCH);
+
+ if(!cert_path[0]->allowed_usage(usage))
+ cert_status[0].insert(Certificate_Status_Code::INVALID_USAGE);
+
for(size_t i = 0; i != cert_path.size(); ++i)
{
std::set<Certificate_Status_Code>& status = cert_status.at(i);
const bool at_self_signed_root = (i == cert_path.size() - 1);
- std::shared_ptr<const X509_Certificate> subject = cert_path[i];
+ const std::shared_ptr<const X509_Certificate>& subject = cert_path[i];
+
+ const std::shared_ptr<const X509_Certificate>& issuer = cert_path[at_self_signed_root ? (i) : (i + 1)];
- std::shared_ptr<const X509_Certificate> issuer = cert_path[at_self_signed_root ? (i) : (i + 1)];
+ if(at_self_signed_root && (issuer->is_self_signed() == false))
+ {
+ status.insert(Certificate_Status_Code::CHAIN_LACKS_TRUST_ROOT);
+ }
- if(i == 0 || restrictions.ocsp_all_intermediates())
+ if(subject->issuer_dn() != issuer->subject_dn())
{
- // certstore[0] is treated as trusted for OCSP (FIXME)
- if(certstores.size() > 1)
- ocsp_responses.push_back(
- std::async(std::launch::async,
- OCSP::online_check, *issuer, *subject, certstores[0]));
+ status.insert(Certificate_Status_Code::CHAIN_NAME_MISMATCH);
}
// Check all certs for valid time range
@@ -128,21 +87,23 @@ check_chain(const std::vector<std::shared_ptr<const X509_Certificate>>& cert_pat
if(!issuer_key)
{
- status.insert(Certificate_Status_Code::SIGNATURE_ERROR);
+ status.insert(Certificate_Status_Code::CERT_PUBKEY_INVALID);
}
else
{
if(subject->check_signature(*issuer_key) == false)
+ {
status.insert(Certificate_Status_Code::SIGNATURE_ERROR);
+ }
- if(issuer_key->estimated_strength() < restrictions.minimum_key_strength())
+ if(issuer_key->estimated_strength() < min_signature_algo_strength)
status.insert(Certificate_Status_Code::SIGNATURE_METHOD_TOO_WEAK);
}
- // Allow untrusted hashes on self-signed roots
- if(!trusted_hashes.empty() && !at_self_signed_root)
+ // Ignore untrusted hashes on self-signed roots
+ if(trusted_hashes.size() > 0 && !at_self_signed_root)
{
- if(!trusted_hashes.count(subject->hash_used_for_signature()))
+ if(trusted_hashes.count(subject->hash_used_for_signature()) == 0)
status.insert(Certificate_Status_Code::UNTRUSTED_HASH);
}
@@ -154,6 +115,20 @@ check_chain(const std::vector<std::shared_ptr<const X509_Certificate>>& cert_pat
}
}
+ return cert_status;
+ }
+
+std::vector<std::set<Certificate_Status_Code>>
+PKIX::check_ocsp(const std::vector<std::shared_ptr<const X509_Certificate>>& cert_path,
+ const std::vector<std::shared_ptr<const OCSP::Response>>& ocsp_responses,
+ const std::vector<Certificate_Store*>& trusted_certstores,
+ std::chrono::system_clock::time_point ref_time)
+ {
+ if(cert_path.empty())
+ throw Invalid_Argument("PKIX::check_ocsp cert_path empty");
+
+ std::vector<std::set<Certificate_Status_Code>> cert_status(cert_path.size() - 1);
+
for(size_t i = 0; i != cert_path.size() - 1; ++i)
{
std::set<Certificate_Status_Code>& status = cert_status.at(i);
@@ -161,129 +136,459 @@ check_chain(const std::vector<std::shared_ptr<const X509_Certificate>>& cert_pat
std::shared_ptr<const X509_Certificate> subject = cert_path.at(i);
std::shared_ptr<const X509_Certificate> ca = cert_path.at(i+1);
- if(i < ocsp_responses.size())
+ if(i < ocsp_responses.size() && (ocsp_responses.at(i) != nullptr))
{
try
{
- OCSP::Response ocsp = ocsp_responses[i].get();
+ Certificate_Status_Code ocsp_signature_status = ocsp_responses.at(i)->check_signature(trusted_certstores, cert_path);
+
+ if(ocsp_signature_status == Certificate_Status_Code::OCSP_SIGNATURE_OK)
+ {
+ // Signature ok, so check the claimed status
+ Certificate_Status_Code ocsp_status = ocsp_responses.at(i)->status_for(*ca, *subject, ref_time);
+ status.insert(ocsp_status);
+ }
+ else
+ {
+ // Some signature problem
+ status.insert(ocsp_signature_status);
+ }
+ }
+ catch(Exception& e)
+ {
+ status.insert(Certificate_Status_Code::OCSP_RESPONSE_INVALID);
+ }
+ }
+ }
- auto ocsp_status = ocsp.status_for(*ca, *subject);
+ return cert_status;
+ }
- status.insert(ocsp_status);
+std::vector<std::set<Certificate_Status_Code>>
+PKIX::check_crl(const std::vector<std::shared_ptr<const X509_Certificate>>& cert_path,
+ const std::vector<std::shared_ptr<const X509_CRL>>& crls,
+ std::chrono::system_clock::time_point ref_time)
+ {
+ if(cert_path.empty())
+ throw Invalid_Argument("PKIX::check_crl cert_path empty");
- //std::cout << "OCSP status: " << Path_Validation_Result::status_string(ocsp_status) << "\n";
+ std::vector<std::set<Certificate_Status_Code>> cert_status(cert_path.size());
+ const X509_Time validation_time(ref_time);
- // Either way we have a definitive answer, no need to check CRLs
- if(ocsp_status == Certificate_Status_Code::CERT_IS_REVOKED)
- return cert_status;
- else if(ocsp_status == Certificate_Status_Code::OCSP_RESPONSE_GOOD)
- continue;
+ for(size_t i = 0; i != cert_path.size() - 1; ++i)
+ {
+ std::set<Certificate_Status_Code>& status = cert_status.at(i);
+
+ if(i < crls.size() && crls.at(i))
+ {
+ std::shared_ptr<const X509_Certificate> subject = cert_path.at(i);
+ std::shared_ptr<const X509_Certificate> ca = cert_path.at(i+1);
+
+ if(!ca->allowed_usage(CRL_SIGN))
+ status.insert(Certificate_Status_Code::CA_CERT_NOT_FOR_CRL_ISSUER);
+
+ if(validation_time < X509_Time(crls[i]->this_update()))
+ status.insert(Certificate_Status_Code::CRL_NOT_YET_VALID);
+
+ if(validation_time > X509_Time(crls[i]->next_update()))
+ status.insert(Certificate_Status_Code::CRL_HAS_EXPIRED);
+
+ if(crls[i]->check_signature(ca->subject_public_key()) == false)
+ status.insert(Certificate_Status_Code::CRL_BAD_SIGNATURE);
+
+ status.insert(Certificate_Status_Code::VALID_CRL_CHECKED);
+
+ if(crls[i]->is_revoked(*subject))
+ status.insert(Certificate_Status_Code::CERT_IS_REVOKED);
+ }
+ }
+
+ return cert_status;
+ }
+
+std::vector<std::set<Certificate_Status_Code>>
+PKIX::check_crl(const std::vector<std::shared_ptr<const X509_Certificate>>& cert_path,
+ const std::vector<Certificate_Store*>& certstores,
+ std::chrono::system_clock::time_point ref_time)
+ {
+ if(cert_path.empty())
+ throw Invalid_Argument("PKIX::check_crl cert_path empty");
+
+ if(certstores.empty())
+ throw Invalid_Argument("PKIX::check_crl certstores empty");
+
+ std::vector<std::shared_ptr<const X509_CRL>> crls(cert_path.size());
+
+ for(size_t i = 0; i != cert_path.size(); ++i)
+ {
+ BOTAN_ASSERT(cert_path[i] != nullptr, "Not null");
+ for(size_t c = 0; c != certstores.size(); ++c)
+ {
+ crls[i] = certstores[c]->find_crl_for(*cert_path.at(i));
+ if(crls[i])
+ break;
+ }
+ }
+
+ return PKIX::check_crl(cert_path, crls, ref_time);
+ }
+
+#if defined(BOTAN_TARGET_OS_HAS_THREADS) && defined(BOTAN_HAS_HTTP_UTIL)
+
+std::vector<std::set<Certificate_Status_Code>>
+PKIX::check_ocsp_online(const std::vector<std::shared_ptr<const X509_Certificate>>& cert_path,
+ const std::vector<Certificate_Store*>& trusted_certstores,
+ std::chrono::system_clock::time_point ref_time,
+ std::chrono::milliseconds timeout,
+ bool ocsp_check_intermediate_CAs)
+ {
+ if(cert_path.empty())
+ throw Invalid_Argument("PKIX::check_ocsp_online cert_path empty");
+
+ std::vector<std::future<std::shared_ptr<const OCSP::Response>>> ocsp_response_futures;
+
+ size_t to_ocsp = 1;
+
+ if(ocsp_check_intermediate_CAs)
+ to_ocsp = cert_path.size() - 1;
+ if(cert_path.size() == 1)
+ to_ocsp = 0;
+
+ for(size_t i = 0; i < to_ocsp; ++i)
+ {
+ const std::shared_ptr<const X509_Certificate>& subject = cert_path.at(i);
+ const std::shared_ptr<const X509_Certificate>& issuer = cert_path.at(i+1);
+
+ if(subject->ocsp_responder() == "")
+ {
+ ocsp_response_futures.emplace_back(std::async(std::launch::deferred, [&]{
+ throw Exception("No OCSP responder URL set for this certificate");
+ return std::shared_ptr<const OCSP::Response>();
+ }));
}
- catch(std::exception&)
+ else
{
- //std::cout << "OCSP error: " << e.what() << "\n";
+ ocsp_response_futures.emplace_back(std::async(std::launch::async, [&]{
+ OCSP::Request req(*issuer, *subject);
+
+ auto http = HTTP::POST_sync(subject->ocsp_responder(),
+ "application/ocsp-request",
+ req.BER_encode());
+
+ http.throw_unless_ok();
+ // Check the MIME type?
+
+ return std::make_shared<const OCSP::Response>(http.body());
+ }));
}
- }
+ }
- std::shared_ptr<const X509_CRL> crl_p = find_crls_for(*subject, certstores);
+ std::vector<std::shared_ptr<const OCSP::Response>> ocsp_responses(ocsp_response_futures.size());
- if(!crl_p)
+ for(size_t pass = 1; pass < 3; ++pass)
+ {
+ for(size_t i = 0; i < ocsp_response_futures.size(); ++i)
{
- if(restrictions.require_revocation_information())
- status.insert(Certificate_Status_Code::NO_REVOCATION_DATA);
- continue;
+ try
+ {
+ if(ocsp_responses[i] == nullptr && ocsp_response_futures[i].valid())
+ {
+ std::future_status status = ocsp_response_futures[i].wait_for(timeout);
+
+ if(status == std::future_status::ready ||
+ status == std::future_status::deferred)
+ {
+ ocsp_responses[i] = ocsp_response_futures[i].get();
+ }
+ }
+ }
+ catch(std::exception&)
+ {
+ // value is default initialized to null, no need to do anything
+ }
}
+ }
- const X509_CRL& crl = *crl_p;
+ return PKIX::check_ocsp(cert_path, ocsp_responses, trusted_certstores, ref_time);
+ }
- if(!ca->allowed_usage(CRL_SIGN))
- status.insert(Certificate_Status_Code::CA_CERT_NOT_FOR_CRL_ISSUER);
+std::vector<std::set<Certificate_Status_Code>>
+PKIX::check_crl_online(const std::vector<std::shared_ptr<const X509_Certificate>>& cert_path,
+ const std::vector<Certificate_Store*>& certstores,
+ std::chrono::system_clock::time_point ref_time,
+ std::chrono::milliseconds timeout)
+ {
+ if(cert_path.empty())
+ throw Invalid_Argument("PKIX::check_crl_online cert_path empty");
+ if(certstores.empty())
+ throw Invalid_Argument("PKIX::check_crl_online certstores empty");
- if(validation_time < X509_Time(crl.this_update()))
- status.insert(Certificate_Status_Code::CRL_NOT_YET_VALID);
+ std::vector<std::future<std::shared_ptr<const X509_CRL>>> future_crls;
+ std::vector<std::shared_ptr<const X509_CRL>> crls(cert_path.size());
- if(validation_time > X509_Time(crl.next_update()))
- status.insert(Certificate_Status_Code::CRL_HAS_EXPIRED);
+ for(size_t i = 0; i != cert_path.size(); ++i)
+ {
+ for(size_t c = 0; c != certstores.size(); ++i)
+ {
+ crls[i] = certstores[i]->find_crl_for(*cert_path[i]);
+ if(crls[i])
+ break;
+ }
- if(crl.check_signature(ca->subject_public_key()) == false)
- status.insert(Certificate_Status_Code::CRL_BAD_SIGNATURE);
+ // TODO: check if CRL is expired and re-request?
- if(crl.is_revoked(*subject))
- status.insert(Certificate_Status_Code::CERT_IS_REVOKED);
+ // Only request if we don't already have a CRL
+ if(crls[i])
+ {
+ /*
+ We already have a CRL, so just insert this empty one to hold a place in the vector
+ so that indexes match up
+ */
+ future_crls.emplace_back(std::future<std::shared_ptr<const X509_CRL>>());
+ }
+ else if(cert_path[i]->crl_distribution_point() == "")
+ {
+ // Avoid creating a thread for this case
+ future_crls.emplace_back(std::async(std::launch::deferred, [&]{
+ throw Exception("No CRL distribution point for this certificate");
+ return std::shared_ptr<const X509_CRL>();
+ }));
+ }
+ else
+ {
+ future_crls.emplace_back(std::async(std::launch::async, [&]() {
+ auto http = HTTP::GET_sync(cert_path[i]->crl_distribution_point());
+ http.throw_unless_ok();
+ // check the mime type?
+ return std::make_shared<const X509_CRL>(http.body());
+ }));
+ }
}
- if(self_signed_ee_cert)
- cert_status.back().insert(Certificate_Status_Code::CANNOT_ESTABLISH_TRUST);
+ for(size_t i = 0; i != future_crls.size(); ++i)
+ {
+ if(future_crls[i].valid())
+ {
+ try
+ {
+ std::future_status status = future_crls[i].wait_for(timeout);
+
+ if(status == std::future_status::ready)
+ {
+ crls[i] = future_crls[i].get();
+ }
+ }
+ catch(std::exception& e)
+ {
+ // crls[i] left null
+ }
+ }
+ }
- return cert_status;
+ return PKIX::check_crl(cert_path, crls, ref_time);
}
-}
+#endif
-Path_Validation_Result x509_path_validate(
- const std::vector<X509_Certificate>& end_certs,
- const Path_Validation_Restrictions& restrictions,
- const std::vector<Certificate_Store*>& certstores,
- const std::string& hostname,
- Usage_Type usage,
- std::chrono::system_clock::time_point validation_time)
+Certificate_Status_Code
+PKIX::build_certificate_path(std::vector<std::shared_ptr<const X509_Certificate>>& cert_path,
+ const std::vector<Certificate_Store*>& trusted_certstores,
+ const std::shared_ptr<const X509_Certificate>& end_entity,
+ const std::vector<std::shared_ptr<const X509_Certificate>>& end_entity_extra)
{
- if(end_certs.empty())
- throw Invalid_Argument("x509_path_validate called with no subjects");
-
- std::vector<std::shared_ptr<const X509_Certificate>> cert_path;
- std::vector<std::shared_ptr<const X509_Certificate>> end_certs_sharedptr;
- cert_path.push_back(std::make_shared<X509_Certificate>(end_certs[0]));
-
- for(auto c: end_certs)
- end_certs_sharedptr.push_back(std::make_shared<const X509_Certificate>(c));
+ if(end_entity->is_self_signed())
+ {
+ return Certificate_Status_Code::CANNOT_ESTABLISH_TRUST;
+ }
/*
* This is an inelegant but functional way of preventing path loops
* (where C1 -> C2 -> C3 -> C1). We store a set of all the certificate
* fingerprints in the path. If there is a duplicate, we error out.
+ * TODO: save fingerprints in result struct? Maybe useful for blacklists, etc.
*/
std::set<std::string> certs_seen;
- Certificate_Store_Overlay extra(end_certs_sharedptr);
+ cert_path.push_back(end_entity);
+ certs_seen.insert(end_entity->fingerprint("SHA-256"));
+
+ Certificate_Store_In_Memory ee_extras;
+ for(size_t i = 0; i != end_entity_extra.size(); ++i)
+ ee_extras.add_certificate(end_entity_extra[i]);
// iterate until we reach a root or cannot find the issuer
- while(!cert_path.back()->is_self_signed())
+ for(;;)
{
- std::shared_ptr<const X509_Certificate> cert = find_issuing_cert(*cert_path.back(), extra, certstores);
- if(!cert)
- return Path_Validation_Result(Certificate_Status_Code::CERT_ISSUER_NOT_FOUND);
+ const X509_Certificate& last = *cert_path.back();
+ const X509_DN issuer_dn = last.issuer_dn();
+ const std::vector<byte> auth_key_id = last.authority_key_id();
+
+ std::shared_ptr<const X509_Certificate> issuer;
+ bool trusted_issuer = false;
+
+ for(Certificate_Store* store : trusted_certstores)
+ {
+ issuer = store->find_cert(issuer_dn, auth_key_id);
+ if(issuer)
+ {
+ trusted_issuer = true;
+ break;
+ }
+ }
+
+ if(!issuer)
+ {
+ // fall back to searching supplemental certs
+ issuer = ee_extras.find_cert(issuer_dn, auth_key_id);
+ }
+
+ if(!issuer)
+ return Certificate_Status_Code::CERT_ISSUER_NOT_FOUND;
+
+ const std::string fprint = issuer->fingerprint("SHA-256");
+
+ if(certs_seen.count(fprint) > 0) // already seen?
+ return Certificate_Status_Code::CERT_CHAIN_LOOP;
- const std::string fprint = cert->fingerprint("SHA-256");
- if(certs_seen.count(fprint) > 0)
- return Path_Validation_Result(Certificate_Status_Code::CERT_CHAIN_LOOP);
certs_seen.insert(fprint);
- cert_path.push_back(cert);
+ cert_path.push_back(issuer);
+
+ if(issuer->is_self_signed())
+ {
+ if(trusted_issuer)
+ {
+ return Certificate_Status_Code::OK;
+ }
+ else
+ {
+ return Certificate_Status_Code::CANNOT_ESTABLISH_TRUST;
+ }
+ }
}
+ }
- std::vector<std::set<Certificate_Status_Code>> res =
- check_chain(cert_path, restrictions, certstores, validation_time);
+namespace {
- if(!hostname.empty() && !cert_path[0]->matches_dns_name(hostname))
- res[0].insert(Certificate_Status_Code::CERT_NAME_NOMATCH);
+void merge_results(std::vector<std::set<Certificate_Status_Code>>& results,
+ const std::vector<std::set<Certificate_Status_Code>>& crl,
+ const std::vector<std::set<Certificate_Status_Code>>& ocsp,
+ bool require_rev_on_end_entity,
+ bool require_rev_on_intermediates)
+ {
+ for(size_t i = 0; i != results.size() - 1; ++i)
+ {
+ bool had_crl = false, had_ocsp = false;
- if(!cert_path[0]->allowed_usage(usage))
- res[0].insert(Certificate_Status_Code::INVALID_USAGE);
+ if(i < crl.size() && crl[i].size() > 0)
+ {
+ for(auto&& code : crl[i])
+ {
+ if(code == Certificate_Status_Code::VALID_CRL_CHECKED)
+ {
+ had_crl = true;
+ }
+ results[i].insert(code);
+ }
+ }
+
+ if(i < ocsp.size() && ocsp[i].size() > 0)
+ {
+ for(auto&& code : ocsp[i])
+ {
+ if(code == Certificate_Status_Code::OCSP_RESPONSE_GOOD)
+ {
+ had_ocsp = true;
+ }
+
+ results[i].insert(code);
+ }
+ }
+
+ if(had_crl == false && had_ocsp == false)
+ {
+ if((require_rev_on_end_entity && i == 0) ||
+ (require_rev_on_intermediates && i > 0))
+ {
+ results[i].insert(Certificate_Status_Code::NO_REVOCATION_DATA);
+ }
+ }
+ }
+ }
+
+}
+
+Path_Validation_Result BOTAN_DLL x509_path_validate(
+ const std::vector<X509_Certificate>& end_certs,
+ const Path_Validation_Restrictions& restrictions,
+ const std::vector<Certificate_Store*>& trusted_roots,
+ const std::string& hostname,
+ Usage_Type usage,
+ std::chrono::system_clock::time_point ref_time,
+ std::chrono::milliseconds ocsp_timeout)
+ {
+ if(end_certs.empty())
+ throw Invalid_Argument("x509_path_validate called with no subjects");
+
+ std::shared_ptr<const X509_Certificate> end_entity(std::make_shared<const X509_Certificate>(end_certs[0]));
+ std::vector<std::shared_ptr<const X509_Certificate>> end_entity_extra;
+ for(size_t i = 1; i < end_certs.size(); ++i)
+ {
+ end_entity_extra.push_back(std::make_shared<const X509_Certificate>(end_certs[i]));
+ }
+
+ std::vector<std::shared_ptr<const X509_Certificate>> cert_path;
+ Certificate_Status_Code path_building_result =
+ PKIX::build_certificate_path(cert_path, trusted_roots, end_entity, end_entity_extra);
+
+ if(path_building_result != Certificate_Status_Code::OK)
+ {
+ return Path_Validation_Result(path_building_result);
+ }
- return Path_Validation_Result(res, std::move(cert_path));
+ std::vector<std::set<Certificate_Status_Code>> status =
+ PKIX::check_chain(cert_path, ref_time,
+ hostname, usage,
+ restrictions.minimum_key_strength(),
+ restrictions.trusted_hashes());
+
+ if(path_building_result != Certificate_Status_Code::OK)
+ status[0].insert(path_building_result);
+
+ std::vector<std::set<Certificate_Status_Code>> crl_status =
+ PKIX::check_crl(cert_path, trusted_roots, ref_time);
+
+ std::vector<std::set<Certificate_Status_Code>> ocsp_status;
+
+ if(ocsp_timeout != std::chrono::milliseconds(0))
+ {
+#if defined(BOTAN_TARGET_OS_HAS_THREADS) && defined(BOTAN_HAS_HTTP_UTIL)
+ ocsp_status = PKIX::check_ocsp_online(cert_path, trusted_roots, ref_time,
+ ocsp_timeout, restrictions.ocsp_all_intermediates());
+#else
+ ocsp_status.resize(1);
+ ocsp_status[0].insert(Certificate_Status_Code::OCSP_NO_HTTP);
+#endif
+ }
+
+ merge_results(status, crl_status, ocsp_status,
+ restrictions.require_revocation_information(),
+ restrictions.ocsp_all_intermediates());
+
+ return Path_Validation_Result(status, std::move(cert_path));
}
Path_Validation_Result x509_path_validate(
const X509_Certificate& end_cert,
const Path_Validation_Restrictions& restrictions,
- const std::vector<Certificate_Store*>& certstores,
+ const std::vector<Certificate_Store*>& trusted_roots,
const std::string& hostname,
Usage_Type usage,
- std::chrono::system_clock::time_point when)
+ std::chrono::system_clock::time_point when,
+ std::chrono::milliseconds ocsp_timeout)
{
std::vector<X509_Certificate> certs;
certs.push_back(end_cert);
- return x509_path_validate(certs, restrictions, certstores, hostname, usage, when);
+ return x509_path_validate(certs, restrictions, trusted_roots, hostname, usage, when, ocsp_timeout);
}
Path_Validation_Result x509_path_validate(
@@ -292,12 +597,13 @@ Path_Validation_Result x509_path_validate(
const Certificate_Store& store,
const std::string& hostname,
Usage_Type usage,
- std::chrono::system_clock::time_point when)
+ std::chrono::system_clock::time_point when,
+ std::chrono::milliseconds ocsp_timeout)
{
- std::vector<Certificate_Store*> certstores;
- certstores.push_back(const_cast<Certificate_Store*>(&store));
+ std::vector<Certificate_Store*> trusted_roots;
+ trusted_roots.push_back(const_cast<Certificate_Store*>(&store));
- return x509_path_validate(end_certs, restrictions, certstores, hostname, usage, when);
+ return x509_path_validate(end_certs, restrictions, trusted_roots, hostname, usage, when, ocsp_timeout);
}
Path_Validation_Result x509_path_validate(
@@ -306,22 +612,23 @@ Path_Validation_Result x509_path_validate(
const Certificate_Store& store,
const std::string& hostname,
Usage_Type usage,
- std::chrono::system_clock::time_point when)
+ std::chrono::system_clock::time_point when,
+ std::chrono::milliseconds ocsp_timeout)
{
std::vector<X509_Certificate> certs;
certs.push_back(end_cert);
- std::vector<Certificate_Store*> certstores;
- certstores.push_back(const_cast<Certificate_Store*>(&store));
+ std::vector<Certificate_Store*> trusted_roots;
+ trusted_roots.push_back(const_cast<Certificate_Store*>(&store));
- return x509_path_validate(certs, restrictions, certstores, hostname, usage, when);
+ return x509_path_validate(certs, restrictions, trusted_roots, hostname, usage, when, ocsp_timeout);
}
Path_Validation_Restrictions::Path_Validation_Restrictions(bool require_rev,
size_t key_strength,
- bool ocsp_all) :
+ bool ocsp_intermediates) :
m_require_revocation_information(require_rev),
- m_ocsp_all_intermediates(ocsp_all),
+ m_ocsp_all_intermediates(ocsp_intermediates),
m_minimum_key_strength(key_strength)
{
if(key_strength <= 80)
@@ -335,7 +642,7 @@ Path_Validation_Restrictions::Path_Validation_Restrictions(bool require_rev,
Path_Validation_Result::Path_Validation_Result(std::vector<std::set<Certificate_Status_Code>> status,
std::vector<std::shared_ptr<const X509_Certificate>>&& cert_chain) :
- m_overall(Certificate_Status_Code::VERIFIED),
+ m_overall(Certificate_Status_Code::OK),
m_all_status(status),
m_cert_path(cert_chain)
{
@@ -345,9 +652,11 @@ Path_Validation_Result::Path_Validation_Result(std::vector<std::set<Certificate_
if(!s.empty())
{
auto worst = *s.rbegin();
- // Leave OCSP confirmations on cert-level status only
- if(worst != Certificate_Status_Code::OCSP_RESPONSE_GOOD)
+ // Leave informative OCSP/CRL confirmations on cert-level status only
+ if(worst >= Certificate_Status_Code::FIRST_ERROR_STATUS)
+ {
m_overall = worst;
+ }
}
}
}
@@ -372,10 +681,9 @@ std::set<std::string> Path_Validation_Result::trusted_hashes() const
bool Path_Validation_Result::successful_validation() const
{
- if(result() == Certificate_Status_Code::VERIFIED ||
- result() == Certificate_Status_Code::OCSP_RESPONSE_GOOD)
- return true;
- return false;
+ return (result() == Certificate_Status_Code::VERIFIED ||
+ result() == Certificate_Status_Code::OCSP_RESPONSE_GOOD ||
+ result() == Certificate_Status_Code::VALID_CRL_CHECKED);
}
std::string Path_Validation_Result::result_string() const
@@ -385,68 +693,8 @@ std::string Path_Validation_Result::result_string() const
const char* Path_Validation_Result::status_string(Certificate_Status_Code code)
{
- switch(code)
- {
- case Certificate_Status_Code::VERIFIED:
- return "Verified";
- case Certificate_Status_Code::OCSP_RESPONSE_GOOD:
- return "OCSP response good";
- case Certificate_Status_Code::NO_REVOCATION_DATA:
- return "No revocation data";
- case Certificate_Status_Code::SIGNATURE_METHOD_TOO_WEAK:
- return "Signature method too weak";
- case Certificate_Status_Code::UNTRUSTED_HASH:
- return "Untrusted hash";
-
- case Certificate_Status_Code::CERT_NOT_YET_VALID:
- return "Certificate is not yet valid";
- case Certificate_Status_Code::CERT_HAS_EXPIRED:
- return "Certificate has expired";
- case Certificate_Status_Code::OCSP_NOT_YET_VALID:
- return "OCSP is not yet valid";
- case Certificate_Status_Code::OCSP_HAS_EXPIRED:
- return "OCSP has expired";
- case Certificate_Status_Code::CRL_NOT_YET_VALID:
- return "CRL is not yet valid";
- case Certificate_Status_Code::CRL_HAS_EXPIRED:
- return "CRL has expired";
-
- case Certificate_Status_Code::CERT_ISSUER_NOT_FOUND:
- return "Certificate issuer not found";
- case Certificate_Status_Code::CANNOT_ESTABLISH_TRUST:
- return "Cannot establish trust";
- case Certificate_Status_Code::CERT_CHAIN_LOOP:
- return "Loop in certificate chain";
-
- case Certificate_Status_Code::POLICY_ERROR:
- return "Policy error";
- case Certificate_Status_Code::INVALID_USAGE:
- return "Invalid usage";
- case Certificate_Status_Code::CERT_CHAIN_TOO_LONG:
- return "Certificate chain too long";
- case Certificate_Status_Code::CA_CERT_NOT_FOR_CERT_ISSUER:
- return "CA certificate not allowed to issue certs";
- case Certificate_Status_Code::CA_CERT_NOT_FOR_CRL_ISSUER:
- return "CA certificate not allowed to issue CRLs";
- case Certificate_Status_Code::OCSP_CERT_NOT_LISTED:
- return "OCSP cert not listed";
- case Certificate_Status_Code::OCSP_BAD_STATUS:
- return "OCSP bad status";
- case Certificate_Status_Code::CERT_NAME_NOMATCH:
- return "Certificate does not match provided name";
- case Certificate_Status_Code::NAME_CONSTRAINT_ERROR:
- return "Certificate does not pass name constraint";
- case Certificate_Status_Code::UNKNOWN_CRITICAL_EXTENSION:
- return "Unknown critical extension encountered";
-
- case Certificate_Status_Code::CERT_IS_REVOKED:
- return "Certificate is revoked";
- case Certificate_Status_Code::CRL_BAD_SIGNATURE:
- return "CRL bad signature";
- case Certificate_Status_Code::SIGNATURE_ERROR:
- return "Signature error";
- // intentionally no default so we are warned
- }
+ if(const char* s = to_string(code))
+ return s;
return "Unknown error";
}
diff --git a/src/lib/x509/x509path.h b/src/lib/x509/x509path.h
index f65652e59..414a877da 100644
--- a/src/lib/x509/x509path.h
+++ b/src/lib/x509/x509path.h
@@ -11,6 +11,8 @@
#include <botan/cert_status.h>
#include <botan/x509cert.h>
#include <botan/certstor.h>
+#include <botan/ocsp.h>
+#include <functional>
#include <set>
#include <chrono>
@@ -28,7 +30,8 @@ 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
+ * @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 +42,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.
@@ -106,6 +110,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 +156,121 @@ 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;
};
+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);
+
+/**
+* Perform certificate validation
+* @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 128 typically
+* @param trusted_hashes set of trusted hash functions,
+* empty means accept any hash we have an OID for
+*/
+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);
+
+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);
+
+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);
+
+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_TARGET_OS_HAS_THREADS) && defined(BOTAN_HAS_HTTP_UTIL)
+
+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);
+
+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,
+ std::chrono::system_clock::time_point ref_time,
+ std::chrono::milliseconds timeout);
+
+#endif
+
+}
+
/**
* 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 +280,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 +289,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 +300,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 +309,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));
}