diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/cli/x509.cpp | 7 | ||||
-rw-r--r-- | src/lib/x509/info.txt | 3 | ||||
-rw-r--r-- | src/lib/x509/ocsp.cpp | 208 | ||||
-rw-r--r-- | src/lib/x509/ocsp.h | 62 | ||||
-rw-r--r-- | src/lib/x509/ocsp_types.cpp | 18 | ||||
-rw-r--r-- | src/lib/x509/ocsp_types.h | 5 | ||||
-rw-r--r-- | src/lib/x509/x509path.cpp | 696 | ||||
-rw-r--r-- | src/lib/x509/x509path.h | 108 |
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)); } |