diff --git a/doc/migration_guide.rst b/doc/migration_guide.rst index dbdbc301c82..6b13f783302 100644 --- a/doc/migration_guide.rst +++ b/doc/migration_guide.rst @@ -97,6 +97,14 @@ should either call a specific function on ``X509_Certificate`` which returns the same information, or lacking that, iterate over the result of ``X509_Certificate::v3_extensions``. +OCSP Response Validation +------------------------ + +After mitigating CVE-2022-43705 the OCSP response signature validation was refactored. +This led to the removal of the `OCSP::Response::check_signature()` method. If you +must validate OCSP responses directly in your application please use the new method +`OCSP::Response::find_signing_certificate()` and `OCSP::Response::verify_signature()`. + Use of ``enum class`` -------------------------------- diff --git a/src/build-data/oids.txt b/src/build-data/oids.txt index 1ee95676f3a..d12158f47d8 100644 --- a/src/build-data/oids.txt +++ b/src/build-data/oids.txt @@ -296,6 +296,7 @@ 1.3.6.1.5.5.7.48.1 = PKIX.OCSP 1.3.6.1.5.5.7.48.1.1 = PKIX.OCSP.BasicResponse +1.3.6.1.5.5.7.48.1.5 = PKIX.OCSP.NoCheck 1.3.6.1.5.5.7.48.2 = PKIX.CertificateAuthorityIssuers 1.3.6.1.4.1.311.20.2.2 = Microsoft SmartcardLogon diff --git a/src/cli/x509.cpp b/src/cli/x509.cpp index 52874ce50cb..1f701fcc19f 100644 --- a/src/cli/x509.cpp +++ b/src/cli/x509.cpp @@ -228,7 +228,7 @@ class OCSP_Check final : public Command Botan::Certificate_Store_In_Memory cas; cas.add_certificate(issuer); - Botan::OCSP::Response resp = Botan::OCSP::online_check(issuer, subject, &cas, timeout); + Botan::OCSP::Response resp = Botan::OCSP::online_check(issuer, subject, timeout); auto status = resp.status_for(issuer, subject, std::chrono::system_clock::now()); diff --git a/src/lib/asn1/oid_maps.cpp b/src/lib/asn1/oid_maps.cpp index 567b32e1fd4..85769a8b98d 100644 --- a/src/lib/asn1/oid_maps.cpp +++ b/src/lib/asn1/oid_maps.cpp @@ -1,7 +1,7 @@ /* * OID maps * -* This file was automatically generated by src/scripts/oids.py on 2022-01-10 +* This file was automatically generated by ./src/scripts/oids.py on 2022-09-19 * * All manual edits to this file will be lost. Edit the script * then regenerate this source file. @@ -187,6 +187,7 @@ std::unordered_map OIDS::load_oid2str_map() { "1.3.6.1.5.5.7.3.9", "PKIX.OCSPSigning" }, { "1.3.6.1.5.5.7.48.1", "PKIX.OCSP" }, { "1.3.6.1.5.5.7.48.1.1", "PKIX.OCSP.BasicResponse" }, + { "1.3.6.1.5.5.7.48.1.5", "PKIX.OCSP.NoCheck" }, { "1.3.6.1.5.5.7.48.2", "PKIX.CertificateAuthorityIssuers" }, { "1.3.6.1.5.5.7.8.5", "PKIX.XMPPAddr" }, { "2.16.840.1.101.3.4.1.2", "AES-128/CBC" }, @@ -391,6 +392,7 @@ std::unordered_map OIDS::load_str2oid_map() { "PKIX.IPsecUser", OID({1,3,6,1,5,5,7,3,7}) }, { "PKIX.OCSP", OID({1,3,6,1,5,5,7,48,1}) }, { "PKIX.OCSP.BasicResponse", OID({1,3,6,1,5,5,7,48,1,1}) }, + { "PKIX.OCSP.NoCheck", OID({1,3,6,1,5,5,7,48,1,5}) }, { "PKIX.OCSPSigning", OID({1,3,6,1,5,5,7,3,9}) }, { "PKIX.ServerAuth", OID({1,3,6,1,5,5,7,3,1}) }, { "PKIX.TimeStamping", OID({1,3,6,1,5,5,7,3,8}) }, diff --git a/src/lib/x509/cert_status.cpp b/src/lib/x509/cert_status.cpp index e22d6e1be1e..5248b5704d5 100644 --- a/src/lib/x509/cert_status.cpp +++ b/src/lib/x509/cert_status.cpp @@ -29,6 +29,8 @@ const char* to_string(Certificate_Status_Code code) return "OCSP URL not available"; case Certificate_Status_Code::OCSP_SERVER_NOT_AVAILABLE: return "OCSP server not available"; + case Certificate_Status_Code::OCSP_ISSUER_NOT_TRUSTED: + return "OCSP issuer is not trustworthy"; case Certificate_Status_Code::NO_REVOCATION_DATA: return "No revocation data"; diff --git a/src/lib/x509/ocsp.cpp b/src/lib/x509/ocsp.cpp index 27eb74b8c9d..7441f69f2bf 100644 --- a/src/lib/x509/ocsp.cpp +++ b/src/lib/x509/ocsp.cpp @@ -15,6 +15,8 @@ #include #include +#include + #if defined(BOTAN_HAS_HTTP_UTIL) #include #endif @@ -89,16 +91,14 @@ std::string Request::base64_encode() const } Response::Response(Certificate_Status_Code status) + : m_status(Response_Status_Code::Successful) + , m_dummy_response_status(status) { - m_status = Response_Status_Code::Successful; - m_dummy_response_status = status; } Response::Response(const uint8_t response_bits[], size_t response_bits_len) : m_response_bits(response_bits, response_bits + response_bits_len) { - m_dummy_response_status = Certificate_Status_Code::OCSP_RESPONSE_INVALID; - BER_Decoder response_outer = BER_Decoder(m_response_bits).start_sequence(); size_t resp_status = 0; @@ -154,8 +154,14 @@ Response::Response(const uint8_t response_bits[], size_t response_bits_len) : Certificate_Status_Code Response::verify_signature(const X509_Certificate& issuer) const { - if (m_responses.empty()) - return m_dummy_response_status; + if(m_dummy_response_status) + return m_dummy_response_status.value(); + + if(m_signer_name.empty() && m_key_hash.empty()) + return Certificate_Status_Code::OCSP_RESPONSE_INVALID; + + if(!is_issued_by(issuer)) + return Certificate_Status_Code::OCSP_ISSUER_NOT_FOUND; try { @@ -183,96 +189,52 @@ Certificate_Status_Code Response::verify_signature(const X509_Certificate& issue } } -Certificate_Status_Code Response::check_signature(const std::vector& trusted_roots, - const std::vector& ee_cert_path) const - { - if (m_responses.empty()) - return m_dummy_response_status; - std::optional signing_cert; +std::optional +Response::find_signing_certificate(const X509_Certificate& issuer_certificate, + const Certificate_Store* trusted_ocsp_responders) const + { + using namespace std::placeholders; - for(const auto& trusted_root : trusted_roots) + // Check whether the CA issuing the certificate in question also signed this + if(is_issued_by(issuer_certificate)) { - if(m_signer_name.empty() && m_key_hash.empty()) - return Certificate_Status_Code::OCSP_RESPONSE_INVALID; - - if(!m_signer_name.empty()) - { - signing_cert = trusted_root->find_cert(m_signer_name, std::vector()); - if(signing_cert) - { - break; - } - } - - if(!m_key_hash.empty()) - { - signing_cert = trusted_root->find_cert_by_pubkey_sha1(m_key_hash); - if(signing_cert) - { - break; - } - } + return issuer_certificate; } - if(!signing_cert && ee_cert_path.size() > 1) + // Then try to find a delegated responder certificate in the stapled certs + auto match = std::find_if(m_certs.begin(), m_certs.end(), std::bind(&Response::is_issued_by, this, _1)); + if(match != m_certs.end()) { - // End entity cert is not allowed to sign their own OCSP request :) - for(size_t i = 1; i < ee_cert_path.size(); ++i) - { - // Check all CA certificates in the (assumed validated) EE cert path - if(!m_signer_name.empty() && ee_cert_path[i].subject_dn() == m_signer_name) - { - signing_cert = ee_cert_path[i]; - break; - } - - if(!m_key_hash.empty() && ee_cert_path[i].subject_public_key_bitstring_sha1() == m_key_hash) - { - signing_cert = ee_cert_path[i]; - break; - } - } + return *match; } - if(!signing_cert && !m_certs.empty()) + // Last resort: check the additionally provides trusted OCSP responders + if(trusted_ocsp_responders) { - for(const auto& cert : m_certs) + std::optional signing_cert; + if(!m_key_hash.empty() && (signing_cert = trusted_ocsp_responders->find_cert_by_pubkey_sha1(m_key_hash))) { - // Check all CA certificates in the (assumed validated) EE cert path - if(!m_signer_name.empty() && cert.subject_dn() == m_signer_name) - { - signing_cert = cert; - break; - } - - if(!m_key_hash.empty() && cert.subject_public_key_bitstring_sha1() == m_key_hash) - { - signing_cert = cert; - break; - } + return signing_cert; } - } - - if(!signing_cert) - return Certificate_Status_Code::OCSP_ISSUER_NOT_FOUND; - if(!signing_cert->allowed_usage(CRL_SIGN) && - !signing_cert->allowed_extended_usage("PKIX.OCSPSigning")) - { - return Certificate_Status_Code::OCSP_RESPONSE_MISSING_KEYUSAGE; + if(!m_signer_name.empty() && (signing_cert = trusted_ocsp_responders->find_cert(m_signer_name, {}))) + { + return signing_cert; + } } - return this->verify_signature(*signing_cert); + return std::nullopt; } + Certificate_Status_Code Response::status_for(const X509_Certificate& issuer, const X509_Certificate& subject, std::chrono::system_clock::time_point ref_time, std::chrono::seconds max_age) const { - if(m_responses.empty()) - { return m_dummy_response_status; } + if(m_dummy_response_status) + { return m_dummy_response_status.value(); } for(const auto& response : m_responses) { @@ -309,7 +271,6 @@ Certificate_Status_Code Response::status_for(const X509_Certificate& issuer, Response online_check(const X509_Certificate& issuer, const BigInt& subject_serial, const std::string& ocsp_responder, - Certificate_Store* trusted_roots, std::chrono::milliseconds timeout) { if(ocsp_responder.empty()) @@ -327,21 +288,12 @@ Response online_check(const X509_Certificate& issuer, // Check the MIME type? - OCSP::Response response(http.body()); - - std::vector trusted_roots_vec; - trusted_roots_vec.push_back(trusted_roots); - - if(trusted_roots) - response.check_signature(trusted_roots_vec); - - return response; + return OCSP::Response(http.body()); } Response online_check(const X509_Certificate& issuer, const X509_Certificate& subject, - Certificate_Store* trusted_roots, std::chrono::milliseconds timeout) { if(subject.issuer_dn() != issuer.subject_dn()) @@ -350,7 +302,6 @@ Response online_check(const X509_Certificate& issuer, return online_check(issuer, BigInt::decode(subject.serial_number()), subject.ocsp_responder(), - trusted_roots, timeout); } diff --git a/src/lib/x509/ocsp.h b/src/lib/x509/ocsp.h index 6060b1852b2..a4b9079c80e 100644 --- a/src/lib/x509/ocsp.h +++ b/src/lib/x509/ocsp.h @@ -12,7 +12,9 @@ #include #include #include + #include +#include namespace Botan { @@ -159,23 +161,31 @@ class BOTAN_PUBLIC_API(2,0) Response final size_t response_bits_len); /** - * Check signature and return status - * The optional cert_path is the (already validated!) certificate path of - * the end entity which is being inquired about - * @param trust_roots list of certstores containing trusted roots - * @param cert_path optionally, the (already verified!) certificate path for the certificate - * this is an OCSP response for. This is necessary to find the correct intermediate CA in - * some cases. + * Find the certificate that signed this OCSP response from all possible + * candidates and taking the attached certificates into account. + * + * @param issuer_certificate is the issuer of the certificate in question + * @param trusted_ocsp_responders optionally, a certificate store containing + * additionally trusted responder certificates + * + * @return the certificate that signed this response or std::nullopt if not found */ - Certificate_Status_Code check_signature(const std::vector& trust_roots, - const std::vector& cert_path = {}) const; + std::optional + find_signing_certificate(const X509_Certificate& issuer_certificate, + const Certificate_Store* trusted_ocsp_responders = nullptr) const; /** - * Verify that issuer's key signed this response - * @param issuer certificate of issuer - * @return if signature valid OCSP_SIGNATURE_OK else an error code + * Check signature of the OCSP response. + * + * Note: It is the responsibility of the caller to verify that signing + * certificate is trustworthy and authorized to do so. + * + * @param signing_certificate the certificate that signed this response + * (@sa Response::find_signing_certificate). + * + * @return status code indicating the validity of the signature */ - Certificate_Status_Code verify_signature(const X509_Certificate& issuer) const; + Certificate_Status_Code verify_signature(const X509_Certificate& signing_certificate) const; /** * @return the status of the response @@ -225,6 +235,18 @@ class BOTAN_PUBLIC_API(2,0) Response final */ const std::vector &certificates() const { return m_certs; } + /** + * @return the dummy response if this is a 'fake' OCSP response otherwise std::nullopt + */ + std::optional dummy_status() const { return m_dummy_response_status; } + + private: + bool is_issued_by(const X509_Certificate& candidate) const + { + return (!m_signer_name.empty() && candidate.subject_dn() == m_signer_name) || + (!m_key_hash.empty() && candidate.subject_public_key_bitstring_sha1() == m_key_hash); + } + private: Response_Status_Code m_status; std::vector m_response_bits; @@ -238,39 +260,35 @@ class BOTAN_PUBLIC_API(2,0) Response final std::vector m_responses; - Certificate_Status_Code m_dummy_response_status; + std::optional m_dummy_response_status; }; #if defined(BOTAN_HAS_HTTP_UTIL) /** -* Makes an online OCSP request via HTTP and returns the OCSP response. +* Makes an online OCSP request via HTTP and returns the (unverified) OCSP response. * @param issuer issuer certificate * @param subject_serial the subject's serial number * @param ocsp_responder the OCSP responder to query -* @param trusted_roots trusted roots for the OCSP response * @param timeout a timeout on the HTTP request * @return OCSP response */ -BOTAN_PUBLIC_API(2,1) +BOTAN_PUBLIC_API(3,0) Response online_check(const X509_Certificate& issuer, const BigInt& subject_serial, const std::string& ocsp_responder, - Certificate_Store* trusted_roots, std::chrono::milliseconds timeout = std::chrono::milliseconds(3000)); /** -* Makes an online OCSP request via HTTP and returns the OCSP response. +* Makes an online OCSP request via HTTP and returns the (unverified) OCSP response. * @param issuer issuer certificate * @param subject subject certificate -* @param trusted_roots trusted roots for the OCSP response * @param timeout a timeout on the HTTP request * @return OCSP response */ -BOTAN_PUBLIC_API(2,0) +BOTAN_PUBLIC_API(3,0) Response online_check(const X509_Certificate& issuer, const X509_Certificate& subject, - Certificate_Store* trusted_roots, std::chrono::milliseconds timeout = std::chrono::milliseconds(3000)); #endif diff --git a/src/lib/x509/pkix_enums.h b/src/lib/x509/pkix_enums.h index 80e22cf62a8..8263abd3f25 100644 --- a/src/lib/x509/pkix_enums.h +++ b/src/lib/x509/pkix_enums.h @@ -38,6 +38,7 @@ enum class Certificate_Status_Code { UNTRUSTED_HASH = 1001, NO_REVOCATION_DATA = 1002, NO_MATCHING_CRLDP = 1003, + OCSP_ISSUER_NOT_TRUSTED = 1004, // Time problems CERT_NOT_YET_VALID = 2000, diff --git a/src/lib/x509/x509_ext.cpp b/src/lib/x509/x509_ext.cpp index a3cdb0611e3..1b82107fd20 100644 --- a/src/lib/x509/x509_ext.cpp +++ b/src/lib/x509/x509_ext.cpp @@ -848,6 +848,11 @@ void CRL_Issuing_Distribution_Point::decode_inner(const std::vector& bu BER_Decoder(buf).decode(m_distribution_point).verify_end(); } +void OCSP_NoCheck::decode_inner(const std::vector& buf) + { + BER_Decoder(buf).verify_end(); + } + std::vector Unknown_Extension::encode_inner() const { return m_bytes; diff --git a/src/lib/x509/x509_ext.h b/src/lib/x509/x509_ext.h index b81548d199f..ffaa2a63ab6 100644 --- a/src/lib/x509/x509_ext.h +++ b/src/lib/x509/x509_ext.h @@ -452,6 +452,35 @@ class CRL_Issuing_Distribution_Point final : public Certificate_Extension CRL_Distribution_Points::Distribution_Point m_distribution_point; }; +/** +* OCSP NoCheck Extension +* +* RFC6960 4.2.2.2.1 +* A CA may specify that an OCSP client can trust a responder for the +* lifetime of the responder's certificate. The CA does so by +* including the extension id-pkix-ocsp-nocheck. +* +* In other words: OCSP responder certificates with this extension do not need +* to be validated against some revocation info. +*/ +class OCSP_NoCheck final : public Certificate_Extension + { + public: + OCSP_NoCheck() = default; + + std::unique_ptr copy() const override { return std::make_unique(); } + static OID static_oid() { return OID("1.3.6.1.5.5.7.48.1.5"); } + OID oid_of() const override { return static_oid(); } + + private: + std::string oid_name() const override + { return "PKIX.OCSP.NoCheck"; } + + bool should_encode() const override { return true; } + std::vector encode_inner() const override { return {}; } + void decode_inner(const std::vector&) override; + }; + /** * An unknown X.509 extension * Will add a failure to the path validation result, if critical diff --git a/src/lib/x509/x509cert.cpp b/src/lib/x509/x509cert.cpp index 02d8c80b928..59d41963263 100644 --- a/src/lib/x509/x509cert.cpp +++ b/src/lib/x509/x509cert.cpp @@ -558,7 +558,7 @@ bool X509_Certificate::allowed_usage(Usage_Type usage) const return (allowed_usage(DIGITAL_SIGNATURE) || allowed_usage(KEY_AGREEMENT)) && allowed_extended_usage("PKIX.ClientAuth"); case Usage_Type::OCSP_RESPONDER: - return (allowed_usage(DIGITAL_SIGNATURE) || allowed_usage(NON_REPUDIATION)) && allowed_extended_usage("PKIX.OCSPSigning"); + return (allowed_usage(DIGITAL_SIGNATURE) || allowed_usage(NON_REPUDIATION)) && has_ex_constraint("PKIX.OCSPSigning"); case Usage_Type::CERTIFICATE_AUTHORITY: return is_CA_cert(); @@ -580,6 +580,11 @@ bool X509_Certificate::has_constraints(Key_Constraints constraints) const return ((this->constraints() & constraints) != 0); } +bool X509_Certificate::has_ex_constraint(const std::string& ex_constraint) const + { + return has_ex_constraint(OID::from_string(ex_constraint)); + } + bool X509_Certificate::has_ex_constraint(const OID& usage) const { const std::vector& ex = extended_key_usage(); diff --git a/src/lib/x509/x509cert.h b/src/lib/x509/x509cert.h index a2e2218b374..021d82af907 100644 --- a/src/lib/x509/x509cert.h +++ b/src/lib/x509/x509cert.h @@ -239,6 +239,12 @@ class BOTAN_PUBLIC_API(2,0) X509_Certificate : public X509_Object */ bool has_constraints(Key_Constraints constraints) const; + /** + * Returns true if and only if OID @param ex_constraint is + * included in the extended key extension. + */ + bool has_ex_constraint(const std::string& ex_constraint) const; + /** * Returns true if and only if OID @param ex_constraint is * included in the extended key extension. diff --git a/src/lib/x509/x509path.cpp b/src/lib/x509/x509path.cpp index 70cf078fabe..5d57cbbc1e9 100644 --- a/src/lib/x509/x509path.cpp +++ b/src/lib/x509/x509path.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -33,8 +34,7 @@ PKIX::check_chain(const std::vector& 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& trusted_hashes) + const Path_Validation_Restrictions& restrictions) { if(cert_path.empty()) throw Invalid_Argument("PKIX::check_chain cert_path empty"); @@ -49,7 +49,11 @@ PKIX::check_chain(const std::vector& cert_path, cert_status[0].insert(Certificate_Status_Code::CERT_NAME_NOMATCH); if(!cert_path[0].allowed_usage(usage)) + { + if(usage == Usage_Type::OCSP_RESPONDER) + cert_status[0].insert(Certificate_Status_Code::OCSP_RESPONSE_MISSING_KEYUSAGE); cert_status[0].insert(Certificate_Status_Code::INVALID_USAGE); + } if(cert_path[0].is_CA_cert() == false && cert_path[0].has_constraints(KEY_CERT_SIGN)) @@ -134,11 +138,12 @@ PKIX::check_chain(const std::vector& cert_path, if(sig_status != Certificate_Status_Code::VERIFIED) status.insert(sig_status); - if(issuer_key->estimated_strength() < min_signature_algo_strength) + if(issuer_key->estimated_strength() < restrictions.minimum_key_strength()) status.insert(Certificate_Status_Code::SIGNATURE_METHOD_TOO_WEAK); } // Ignore untrusted hashes on self-signed roots + const auto& trusted_hashes = restrictions.trusted_hashes(); if(!trusted_hashes.empty() && !at_self_signed_root) { if(trusted_hashes.count(subject.hash_used_for_signature()) == 0) @@ -209,12 +214,76 @@ PKIX::check_chain(const std::vector& cert_path, return cert_status; } +namespace { + +Certificate_Status_Code verify_ocsp_signing_cert( + const X509_Certificate& signing_cert, + const X509_Certificate& ca, + const std::vector& extra_certs, + const std::vector& certstores, + std::chrono::system_clock::time_point ref_time, + const Path_Validation_Restrictions& restrictions) + { + // RFC 6960 4.2.2.2 + // [Applications] MUST reject the response if the certificate + // required to validate the signature on the response does not + // meet at least one of the following criteria: + // + // 1. Matches a local configuration of OCSP signing authority + // for the certificate in question, or + if(restrictions.trusted_ocsp_responders()->certificate_known(signing_cert)) + return Certificate_Status_Code::OK; + + // RFC 6960 4.2.2.2 + // + // 2. Is the certificate of the CA that issued the certificate + // in question, or + if(signing_cert == ca) + return Certificate_Status_Code::OK; + + // RFC 6960 4.2.2.2 + // + // 3. Includes a value of id-kp-OCSPSigning in an extended key + // usage extension and is issued by the CA that issued the + // certificate in question as stated above. + + // TODO: Implement OCSP revocation check of OCSP signer certificate + // Note: This needs special care to prevent endless loops on specifically + // forged chains of OCSP responses referring to each other. + // + // Currently, we're disabling OCSP-based revocation checks by setting the + // timeout to 0. Additionally, the library's API would not allow an + // application to pass in the required "second order" OCSP responses. I.e. + // "second order" OCSP checks would need to rely on `check_ocsp_online()` + // which is not an option for some applications (e.g. that require a proxy + // for external HTTP requests). + const auto ocsp_timeout = std::chrono::milliseconds::zero(); + const auto relaxed_restrictions = + Path_Validation_Restrictions(false /* do not enforce revocation data */, + restrictions.minimum_key_strength(), + false /* OCSP is not available, so don't try for intermediates */, + restrictions.trusted_hashes()); + + const auto validation_result = x509_path_validate( + concat(std::vector{signing_cert}, extra_certs), + relaxed_restrictions, + certstores, + {} /* hostname */, + Botan::Usage_Type::OCSP_RESPONDER, + ref_time, + ocsp_timeout); + + return validation_result.result(); + } + +} + CertificatePathStatusCodes PKIX::check_ocsp(const std::vector& cert_path, const std::vector>& ocsp_responses, - const std::vector& trusted_certstores, + const std::vector& certstores, std::chrono::system_clock::time_point ref_time, - std::chrono::seconds max_ocsp_age) + const Path_Validation_Restrictions& restrictions) { if(cert_path.empty()) throw Invalid_Argument("PKIX::check_ocsp cert_path empty"); @@ -233,18 +302,27 @@ PKIX::check_ocsp(const std::vector& cert_path, { try { - Certificate_Status_Code ocsp_signature_status = ocsp_responses.at(i)->check_signature(trusted_certstores, cert_path); + const auto& ocsp_response = ocsp_responses.at(i); - if(ocsp_signature_status == Certificate_Status_Code::OCSP_SIGNATURE_OK) + if(auto dummy_status = ocsp_response->dummy_status()) + { + // handle softfail conditions + status.insert(dummy_status.value()); + } + else if(auto signing_cert = ocsp_response->find_signing_certificate(ca, restrictions.trusted_ocsp_responders()); + !signing_cert) + { + status.insert(Certificate_Status_Code::OCSP_ISSUER_NOT_FOUND); + } + else if(auto ocsp_signing_cert_status = verify_ocsp_signing_cert(signing_cert.value(), ca, concat(ocsp_response->certificates(), cert_path), certstores, ref_time, restrictions); + ocsp_signing_cert_status > Certificate_Status_Code::FIRST_ERROR_STATUS) { - // Signature ok, so check the claimed status - Certificate_Status_Code ocsp_status = ocsp_responses.at(i)->status_for(ca, subject, ref_time, max_ocsp_age); - status.insert(ocsp_status); + status.insert(ocsp_signing_cert_status); + status.insert(Certificate_Status_Code::OCSP_ISSUER_NOT_TRUSTED); } else { - // Some signature problem - status.insert(ocsp_signature_status); + status.insert(ocsp_response->status_for(ca, subject, ref_time, restrictions.max_ocsp_age())); } } catch(Exception&) @@ -363,8 +441,7 @@ PKIX::check_ocsp_online(const std::vector& cert_path, const std::vector& trusted_certstores, std::chrono::system_clock::time_point ref_time, std::chrono::milliseconds timeout, - bool ocsp_check_intermediate_CAs, - std::chrono::seconds max_ocsp_age) + const Path_Validation_Restrictions& restrictions) { if(cert_path.empty()) throw Invalid_Argument("PKIX::check_ocsp_online cert_path empty"); @@ -373,7 +450,7 @@ PKIX::check_ocsp_online(const std::vector& cert_path, size_t to_ocsp = 1; - if(ocsp_check_intermediate_CAs) + if(restrictions.ocsp_all_intermediates()) to_ocsp = cert_path.size() - 1; if(cert_path.size() == 1) to_ocsp = 0; @@ -424,7 +501,7 @@ PKIX::check_ocsp_online(const std::vector& cert_path, ocsp_responses.push_back(ocsp_response_future.get()); } - return PKIX::check_ocsp(cert_path, ocsp_responses, trusted_certstores, ref_time, max_ocsp_age); + return PKIX::check_ocsp(cert_path, ocsp_responses, trusted_certstores, ref_time, restrictions); } CertificatePathStatusCodes @@ -771,8 +848,7 @@ PKIX::build_all_certificate_paths(std::vector>& ce void PKIX::merge_revocation_status(CertificatePathStatusCodes& chain_status, const CertificatePathStatusCodes& crl, const CertificatePathStatusCodes& ocsp, - bool require_rev_on_end_entity, - bool require_rev_on_intermediates) + const Path_Validation_Restrictions& restrictions) { if(chain_status.empty()) throw Invalid_Argument("PKIX::merge_revocation_status chain_status was empty"); @@ -810,8 +886,8 @@ void PKIX::merge_revocation_status(CertificatePathStatusCodes& chain_status, if(had_crl == false && had_ocsp == false) { - if((require_rev_on_end_entity && i == 0) || - (require_rev_on_intermediates && i > 0)) + if((restrictions.require_revocation_information() && i == 0) || + (restrictions.ocsp_all_intermediates() && i > 0)) { chain_status[i].insert(Certificate_Status_Code::NO_REVOCATION_DATA); } @@ -879,9 +955,7 @@ Path_Validation_Result x509_path_validate( { CertificatePathStatusCodes status = PKIX::check_chain(cert_path, ref_time, - hostname, usage, - restrictions.minimum_key_strength(), - restrictions.trusted_hashes()); + hostname, usage, restrictions); CertificatePathStatusCodes crl_status = PKIX::check_crl(cert_path, trusted_roots, ref_time); @@ -890,23 +964,21 @@ Path_Validation_Result x509_path_validate( if(!ocsp_resp.empty()) { - ocsp_status = PKIX::check_ocsp(cert_path, ocsp_resp, trusted_roots, ref_time, restrictions.max_ocsp_age()); + ocsp_status = PKIX::check_ocsp(cert_path, ocsp_resp, trusted_roots, ref_time, restrictions); } if(ocsp_status.empty() && 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()); + ocsp_timeout, restrictions); #else ocsp_status.resize(1); ocsp_status[0].insert(Certificate_Status_Code::OCSP_NO_HTTP); #endif } - PKIX::merge_revocation_status(status, crl_status, ocsp_status, - restrictions.require_revocation_information(), - restrictions.ocsp_all_intermediates()); + PKIX::merge_revocation_status(status, crl_status, ocsp_status, restrictions); Path_Validation_Result pvd(status, std::move(cert_path)); if(pvd.successful_validation()) @@ -974,11 +1046,13 @@ Path_Validation_Result x509_path_validate( Path_Validation_Restrictions::Path_Validation_Restrictions(bool require_rev, size_t key_strength, bool ocsp_intermediates, - std::chrono::seconds max_ocsp_age) : + std::chrono::seconds max_ocsp_age, + std::unique_ptr trusted_ocsp_responders) : m_require_revocation_information(require_rev), m_ocsp_all_intermediates(ocsp_intermediates), m_minimum_key_strength(key_strength), - m_max_ocsp_age(max_ocsp_age) + m_max_ocsp_age(max_ocsp_age), + m_trusted_ocsp_responders(std::move(trusted_ocsp_responders)) { if(key_strength <= 80) { m_trusted_hashes.insert("SHA-160"); } diff --git a/src/lib/x509/x509path.h b/src/lib/x509/x509path.h index 9aee1497334..b125126c640 100644 --- a/src/lib/x509/x509path.h +++ b/src/lib/x509/x509path.h @@ -49,11 +49,14 @@ class BOTAN_PUBLIC_API(2,0) Path_Validation_Restrictions final * well as end entity (if OCSP enabled in path validation request) * @param max_ocsp_age maximum age of OCSP responses w/o next_update. * If zero, there is no maximum age + * @param trusted_ocsp_responders certificate store containing certificates + * of trusted OCSP responders (additionally to the CA's responders) */ Path_Validation_Restrictions(bool require_rev = false, size_t minimum_key_strength = 110, bool ocsp_all_intermediates = false, - std::chrono::seconds max_ocsp_age = std::chrono::seconds::zero()); + std::chrono::seconds max_ocsp_age = std::chrono::seconds::zero(), + std::unique_ptr trusted_ocsp_responders = std::make_unique()); /** * @param require_rev if true, revocation information is required @@ -67,17 +70,21 @@ class BOTAN_PUBLIC_API(2,0) Path_Validation_Restrictions final * rejected. * @param max_ocsp_age maximum age of OCSP responses w/o next_update. * If zero, there is no maximum age + * @param trusted_ocsp_responders certificate store containing certificates + * of trusted OCSP responders (additionally to the CA's responders) */ Path_Validation_Restrictions(bool require_rev, size_t minimum_key_strength, bool ocsp_all_intermediates, const std::set& trusted_hashes, - std::chrono::seconds max_ocsp_age = std::chrono::seconds::zero()) : + std::chrono::seconds max_ocsp_age = std::chrono::seconds::zero(), + std::unique_ptr trusted_ocsp_responders = std::make_unique()) : m_require_revocation_information(require_rev), m_ocsp_all_intermediates(ocsp_all_intermediates), m_trusted_hashes(trusted_hashes), m_minimum_key_strength(minimum_key_strength), - m_max_ocsp_age(max_ocsp_age) {} + m_max_ocsp_age(max_ocsp_age), + m_trusted_ocsp_responders(std::move(trusted_ocsp_responders)) {} /** * @return whether revocation information is required @@ -111,12 +118,21 @@ class BOTAN_PUBLIC_API(2,0) Path_Validation_Restrictions final std::chrono::seconds max_ocsp_age() const { return m_max_ocsp_age; } + /** + * Certificates in this store are trusted to sign OCSP responses + * additionally to the CA's responder certificates. + * @return certificate store containing trusted OCSP responder certs + */ + const Certificate_Store* trusted_ocsp_responders() const + { return m_trusted_ocsp_responders.get(); } + private: bool m_require_revocation_information; bool m_ocsp_all_intermediates; std::set m_trusted_hashes; size_t m_minimum_key_strength; std::chrono::seconds m_max_ocsp_age; + std::unique_ptr m_trusted_ocsp_responders; }; /** @@ -337,22 +353,17 @@ BOTAN_PUBLIC_API(2,0) build_certificate_path(std::vector& cert * against (normally current system clock) * @param hostname the hostname * @param usage end entity usage checks -* @param min_signature_algo_strength 80 or 110 typically -* Note 80 allows 1024 bit RSA and SHA-1. 110 allows 2048 bit RSA and SHA-2. -* Using 128 requires ECC (P-256) or ~3000 bit RSA keys. -* @param trusted_hashes set of trusted hash functions, empty means accept any -* hash we have an OID for +* @param restrictions the relevant path validation restrictions object * @return vector of results on per certificate in the path, each containing a set of * results. If all codes in the set are < Certificate_Status_Code::FIRST_ERROR_STATUS, * then the result for that certificate is successful. If all results are */ CertificatePathStatusCodes -BOTAN_PUBLIC_API(2,0) check_chain(const std::vector& cert_path, +BOTAN_PUBLIC_API(3,0) check_chain(const std::vector& 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& trusted_hashes); + const Path_Validation_Restrictions& restrictions); /** * Check OCSP responses for revocation information @@ -361,16 +372,15 @@ BOTAN_PUBLIC_API(2,0) check_chain(const std::vector& cert_path * @param certstores trusted roots * @param ref_time whatever time you want to perform the validation against * (normally current system clock) -* @param max_ocsp_age maximum age of OCSP responses w/o next_update. If zero, -* there is no maximum age +* @param restrictions the relevant path validation restrictions object * @return revocation status */ CertificatePathStatusCodes -BOTAN_PUBLIC_API(2, 0) check_ocsp(const std::vector& cert_path, +BOTAN_PUBLIC_API(3, 0) check_ocsp(const std::vector& cert_path, const std::vector>& ocsp_responses, const std::vector& certstores, std::chrono::system_clock::time_point ref_time, - std::chrono::seconds max_ocsp_age = std::chrono::seconds::zero()); + const Path_Validation_Restrictions& restrictions); /** * Check CRLs for revocation information @@ -411,19 +421,15 @@ BOTAN_PUBLIC_API(2,0) check_crl(const std::vector& cert_path, * (normally current system clock) * @param timeout for timing out the responses, though actually this function * may block for up to timeout*cert_path.size()*C for some small C. -* @param ocsp_check_intermediate_CAs if true also performs OCSP on any intermediate -* CA certificates. If false, only does OCSP on the end entity cert. -* @param max_ocsp_age maximum age of OCSP responses w/o next_update. If zero, -* there is no maximum age +* @param restrictions the relevant path validation restrictions object * @return revocation status */ CertificatePathStatusCodes -BOTAN_PUBLIC_API(2, 0) check_ocsp_online(const std::vector& cert_path, +BOTAN_PUBLIC_API(3, 0) check_ocsp_online(const std::vector& cert_path, const std::vector& trusted_certstores, std::chrono::system_clock::time_point ref_time, std::chrono::milliseconds timeout, - bool ocsp_check_intermediate_CAs, - std::chrono::seconds max_ocsp_age = std::chrono::seconds::zero()); + const Path_Validation_Restrictions& restrictions); /** * Check CRL using online (HTTP) access. Current version creates a thread and @@ -459,14 +465,12 @@ Certificate_Status_Code BOTAN_PUBLIC_API(2,0) overall_status(const CertificatePa * @param chain_status the certificate status * @param crl_status results from check_crl * @param ocsp_status results from check_ocsp -* @param require_rev_on_end_entity require valid CRL or OCSP on end-entity cert -* @param require_rev_on_intermediates require valid CRL or OCSP on all intermediate certificates +* @param restrictions the relevant path validation restrictions object */ -void BOTAN_PUBLIC_API(2,0) merge_revocation_status(CertificatePathStatusCodes& chain_status, +void BOTAN_PUBLIC_API(3,0) merge_revocation_status(CertificatePathStatusCodes& chain_status, const CertificatePathStatusCodes& crl_status, const CertificatePathStatusCodes& ocsp_status, - bool require_rev_on_end_entity, - bool require_rev_on_intermediates); + const Path_Validation_Restrictions& restrictions); } diff --git a/src/scripts/mychain_creater.sh b/src/scripts/mychain_creater.sh new file mode 100644 index 00000000000..e7010600acb --- /dev/null +++ b/src/scripts/mychain_creater.sh @@ -0,0 +1,224 @@ +#!/bin/sh + +# Helper script to generate a certificate chain +# alternative certificates might sign the OCSP responses. +# +# (C) 2022 Jack Lloyd +# (C) 2022 René Meusel (Rohde & Schwarz Cybersecurity) +# +# Botan is released under the Simplified BSD License (see license.txt) + +if [ $(date "+%y%m%d") != "220922" ]; then + echo "You should use a time machine to run this script..." + echo "Use libfaketime to set the system clock back to the 22nd of September 2022. This recreates the certificates with the same timestamps as used in the tests and saves you from re-setting the validation reference dates." + echo + echo "Like so (path is for Ubuntu, might vary):" + echo " LD_PRELOAD=/usr/lib/x86_64-linux-gnu/faketime/libfaketime.so.1 FAKETIME=\"2022-09-22 12:00:00\" $0 $@" + exit 1 +fi + +set -ex + +PREFIX="mychain_" + +ROOTkey="root.key" +ROOTcsr="root.csr" +ROOTcert="${PREFIX}root.pem" +ROOTindex="root_index.txt" +ROOTconf="root.conf" + +INTkey="int.key" +INTcsr="int.csr" +INTcert="${PREFIX}int.pem" +INTindex="int_index.txt" +INTconf="int.conf" + +DELRESPkey="int_ocsp_delegate_responder.key" +DELRESPcsr="int_ocsp_delegate_responder.csr" +DELRESPcert="${PREFIX}int_ocsp_delegate_responder.pem" +DELRESPconf="int_ocsp_delegate_responder.conf" + +DELRESPnoOCSPcsr="int_ocsp_delegate_responder_no_ocsp_key_usage.csr" +DELRESPnoOCSPcert="${PREFIX}int_ocsp_delegate_responder_no_ocsp_key_usage.pem" +DELRESPnoOCSPconf="int_ocsp_delegate_responder_no_ocsp_key_usage.conf" + +EEkey="ee.key" +EEcsr="ee.csr" +EEcert="${PREFIX}ee.pem" +EEconf="ee.conf" + +# +# Create the Root CA +# +cat > $ROOTconf < $INTconf < $DELRESPconf < $DELRESPnoOCSPconf < $EEconf < $CAindex + elif [ "$subjectStatus" = "revoked" ]; then + formatted_currentdate=$(date "+%y%m%d%H%M%S") + echo "R\t${formatted_enddate}Z\t${formatted_currentdate}Z\t${serial}\tunknown\t${subject}" > $CAindex + else + echo "Don't understand OCSP response status: $subjectStatus" + exit 1 + fi + + if [ "$stapling" = "no_staple" ]; then + staple="-resp_no_certs" + else + staple="" + fi + + # generate an OCSP response using the just-created certificate + openssl ocsp -issuer $caCert -cert $subjectCert -reqout $ocspReq -text -no_nonce + openssl ocsp -reqin $ocspReq -rsigner $responderCert -rkey $responderKey -CA $caCert -index $CAindex -ndays 30 -respout $ocspResponse $staple -text +} + +# (Malformed) OCSP response for Intermediate signed by Intermediate itself +create_ocsp_response $INTcert $ROOTcert $INTcert $INTkey "valid" "${PREFIX}ocsp_for_int_self_signed.der" "no_staple" + +# (Malformed) OCSP response for End Entity signed by Root certificate +create_ocsp_response $EEcert $INTcert $ROOTcert $ROOTkey "valid" "${PREFIX}ocsp_for_ee_root_signed.der" "no_staple" + +# OCSP response for End Entity signed by Intermediate certificate +create_ocsp_response $EEcert $INTcert $INTcert $INTkey "valid" "${PREFIX}ocsp_for_ee.der" "no_staple" + +# OCSP response for End Entity signed by Delegate Responder of Intermediate certificate +create_ocsp_response $EEcert $INTcert $DELRESPcert $DELRESPkey "valid" "${PREFIX}ocsp_for_ee_delegate_signed.der" "staple" + +# OCSP response for End Entity signed by Delegate Responder of Intermediate certificate that does not have sufficient key usage flags +create_ocsp_response $EEcert $INTcert $DELRESPnoOCSPcert $DELRESPkey "valid" "${PREFIX}ocsp_for_ee_delegate_signed_malformed.der" "staple" diff --git a/src/scripts/randombit_ocsp_forger.sh b/src/scripts/randombit_ocsp_forger.sh new file mode 100755 index 00000000000..84a7893b05e --- /dev/null +++ b/src/scripts/randombit_ocsp_forger.sh @@ -0,0 +1,119 @@ +#!/bin/sh + +# Helper script to generate forged OCSP responses +# that are signed by a random self-signed CA that +# has no authority to judge the revocation status +# of the given certificate. +# +# (C) 2022 Jack Lloyd +# (C) 2022 René Meusel (Rohde & Schwarz Cybersecurity) +# +# Botan is released under the Simplified BSD License (see license.txt) + +if [ $# -ne 3 ]; then + echo "Usage: $0 " + exit 1 +fi + +if [ $(date "+%y%m%d") != "161118" ]; then + echo "You need a time machine to run this script..." + echo "Use libfaketime to set the system clock back to the 18th of November 2016" + echo "Like so (path is for Ubuntu, might vary):" + echo " LD_PRELOAD=/usr/lib/x86_64-linux-gnu/faketime/libfaketime.so.1 FAKETIME=\"2016-11-18 12:00:00\" $0 $@" + exit 1 +fi + +set -ex + +Vcert="$1" +Vissuer="$2" +Vstatus="$3" + +RQ="req.der" +RP="forged_response.der" +RPnocerts="forged_response_nocerts.der" + +RPcakey="ca.key" +RPcacert="ca.pem" + +RPcsrconf="cert_csr.conf" +RPcsr="cert.csr" +RPcertconf="cert.conf" +RPkey="cert.key" +RPcert="cert.pem" +RPindex="index.txt" + +# create a forged Certificate Authority +openssl req -x509 \ + -sha256 -days 356 \ + -nodes \ + -newkey rsa:2048 \ + -subj "/CN=Forged OCSP CA/C=DE/L=Berlin" \ + -keyout $RPcakey -out $RPcacert + +# create a self-signed certificate +openssl genrsa -out $RPkey 2048 + +cat > $RPcsrconf < $RPcertconf < $RPindex +elif [ "$Vstatus" = "revoked" ]; then + formatted_currentdate=$(date "+%y%m%d%H%M%S") + echo "R\t${formatted_enddate}Z\t${formatted_currentdate}Z\t${serial}\tunknown\t${subject}" > $RPindex +else + echo "Don't understand OCSP response status: $Vstatus" + exit 1 +fi + +# generate an OCSP response using the just-created certificate +openssl ocsp -issuer $Vissuer -cert $Vcert -reqout $RQ -text -no_nonce +openssl ocsp -reqin $RQ -rsigner $RPcert -rkey $RPkey -CA $Vissuer -index $RPindex -ndays 5 -respout $RP -text +openssl ocsp -reqin $RQ -rsigner $RPcert -rkey $RPkey -CA $Vissuer -index $RPindex -ndays 5 -respout $RPnocerts -resp_no_certs -text diff --git a/src/tests/data/x509/ocsp/bdr-int-ocsp-resp.der b/src/tests/data/x509/ocsp/bdr-int-ocsp-resp.der new file mode 100644 index 00000000000..ac70762e276 Binary files /dev/null and b/src/tests/data/x509/ocsp/bdr-int-ocsp-resp.der differ diff --git a/src/tests/data/x509/ocsp/bdr-int.pem b/src/tests/data/x509/ocsp/bdr-int.pem new file mode 100644 index 00000000000..299fb22d705 --- /dev/null +++ b/src/tests/data/x509/ocsp/bdr-int.pem @@ -0,0 +1,35 @@ +-----BEGIN CERTIFICATE----- +MIIGHzCCBQegAwIBAgIDD+SOMA0GCSqGSIb3DQEBCwUAMFAxCzAJBgNVBAYTAkRF +MRUwEwYDVQQKDAxELVRydXN0IEdtYkgxKjAoBgNVBAMMIUQtVFJVU1QgUm9vdCBD +bGFzcyAzIENBIDIgRVYgMjAwOTAeFw0xNjExMTYwOTQ2MTlaFw0yOTExMDUwODUw +NDZaMF4xCzAJBgNVBAYTAkRFMRUwEwYDVQQKEwxELVRydXN0IEdtYkgxHzAdBgNV +BAMTFkQtVFJVU1QgQ0EgMi0yIEVWIDIwMTYxFzAVBgNVBGETDk5UUkRFLUhSQjc0 +MzQ2MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4PbGGVT4nCH+CzaZ +kZDWqXWiXbBu3UEpSPBmAKDepwkoc7b13vVlRrvehBm11yUNzaN7thsqB+VXEyF4 +OuxkCJkZRCJUfrS1zdnZXptf361oahCX+ch2E4Hdedeet45mypwKsD7FqSdz01KY +o6wFQMnnZsRQtamilglAgT03iTUf+Yn8a5msV7fscpfkLUGCtjeM2eWgfZ2I0pqi +m3DYrQU5/8in6DtZLrIAgZpnQsgJiB3glx0YcBXs5YZR9bfhOP71nLvM+9vkxNR4 +V90SOnwEzCbj5VforuNgP0sptC2TSPiqNG9sgdySBobz9aO5ryqG21GXMcfFp0vC +z2kxrwIDAQABo4IC8jCCAu4wHwYDVR0jBBgwFoAU05SKTGITKhkuzK9yin0215oc +3GcwggElBggrBgEFBQcBAQSCARcwggETMDcGCCsGAQUFBzABhitodHRwOi8vcm9v +dC1jMy1jYTItZXYtMjAwOS5vY3NwLmQtdHJ1c3QubmV0MFAGCCsGAQUFBzAChkRo +dHRwOi8vd3d3LmQtdHJ1c3QubmV0L2NnaS1iaW4vRC1UUlVTVF9Sb290X0NsYXNz +XzNfQ0FfMl9FVl8yMDA5LmNydDCBhQYIKwYBBQUHMAKGeWxkYXA6Ly9kaXJlY3Rv +cnkuZC10cnVzdC5uZXQvQ049RC1UUlVTVCUyMFJvb3QlMjBDbGFzcyUyMDMlMjBD +QSUyMDIlMjBFViUyMDIwMDksTz1ELVRydXN0JTIwR21iSCxDPURFP2NBQ2VydGlm +aWNhdGU/YmFzZT8wfwYDVR0gBHgwdjAJBgcEAIvsQAEEMA0GCysGAQQBpTQCgRYE +MFoGCysGAQQBpTQCgUoBMEswSQYIKwYBBQUHAgEWPWh0dHA6Ly93d3cuZC10cnVz +dC5uZXQvaW50ZXJuZXQvZmlsZXMvRC1UUlVTVF9DU01fUEtJX0NQUy5wZGYwgd0G +A1UdHwSB1TCB0jCBh6CBhKCBgYZ/bGRhcDovL2RpcmVjdG9yeS5kLXRydXN0Lm5l +dC9DTj1ELVRSVVNUJTIwUm9vdCUyMENsYXNzJTIwMyUyMENBJTIwMiUyMEVWJTIw +MjAwOSxPPUQtVHJ1c3QlMjBHbWJILEM9REU/Y2VydGlmaWNhdGVyZXZvY2F0aW9u +bGlzdDBGoESgQoZAaHR0cDovL2NybC5kLXRydXN0Lm5ldC9jcmwvZC10cnVzdF9y +b290X2NsYXNzXzNfY2FfMl9ldl8yMDA5LmNybDAdBgNVHQ4EFgQUIa9qJphx6SYK +1duhjPfbpp2lJVwwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQAw +DQYJKoZIhvcNAQELBQADggEBAEITrEZFU4bOy+274S2THOe9lewgYy+5OYh/Wr7Q +WzRi/bMU6GRtag9fCnIsXon3+2wKGL22JgjI+WnZa5TRiazUOdtOjCEuwxXXMYH/ +PaBBb/BXmfGlEHGHL/ljNQauOrsfIQXXDYTfZk9jwLQgPmF54Ulm6oLsUrvYp1nq +4jSAyWOY+mcxFlGgZPt5jdL1DSkzdLtdWfGs+1USqmx/IBZLfCwavdk0Dm5fwQSG +iI+av54kU0E4ziDEOJ25rfiOBGqjh+4NFegAaQlTeVp1zOCjtKCf9YWDS8BgJT+O +Ri2UKV/O8WaWZ3qRLuVavpng14sx4oa8FLM9sKBWvI+H5XU= +-----END CERTIFICATE----- diff --git a/src/tests/data/x509/ocsp/bdr-ocsp-resp.der b/src/tests/data/x509/ocsp/bdr-ocsp-resp.der new file mode 100644 index 00000000000..423c2b170b8 Binary files /dev/null and b/src/tests/data/x509/ocsp/bdr-ocsp-resp.der differ diff --git a/src/tests/data/x509/ocsp/bdr-ocsp-responder.pem b/src/tests/data/x509/ocsp/bdr-ocsp-responder.pem new file mode 100644 index 00000000000..4ea71835526 --- /dev/null +++ b/src/tests/data/x509/ocsp/bdr-ocsp-responder.pem @@ -0,0 +1,32 @@ +-----BEGIN CERTIFICATE----- +MIIFlDCCBHygAwIBAgIQS46011w0OehAK/Gn1tYrmDANBgkqhkiG9w0BAQsFADBe +MQswCQYDVQQGEwJERTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMR8wHQYDVQQDExZE +LVRSVVNUIENBIDItMiBFViAyMDE2MRcwFQYDVQRhEw5OVFJERS1IUkI3NDM0NjAe +Fw0xOTA5MTcxMjU2MTdaFw0yOTExMDUwODUwNDZaMGIxCzAJBgNVBAYTAkRFMRUw +EwYDVQQKEwxELVRydXN0IEdtYkgxIzAhBgNVBAMTGkQtVFJVU1QgT0NTUCA0IDIt +MiBFViAyMDE2MRcwFQYDVQRhEw5OVFJERS1IUkI3NDM0NjCCASIwDQYJKoZIhvcN +AQEBBQADggEPADCCAQoCggEBANYwqY+taeXwfZqX4BKqsGtUc6rH/pmN/ReCBYMc +OcmamyTyxNUayZcCGGy3d7ykHQYtSFQHt0gMxx0BHkoVqVj0XHGSZ4krXPPqEKxS +hS2lY6kQkb/J58P9w0u6O9O98Oa9+LaDdY+kT9k+IcCTSyPNMa6IitERG/RP+rHy +ZYpB5gNQy2UvcYpEgDeCdu/qJEVbUfTPATME2HB4D20eYm2ShWqwTWfZ5wnttUkT +33HdlWDo0JFWhA0FK1CHGNPiWF5qzZNr/CfL2LikkTzYCf7B3pTg/zrEc5H0SiCz +TXlR8a6NYH0vh8SB1uFVN/gfB4ik6dsk08yls5wfyx3ts4MCAwEAAaOCAkgwggJE +MBMGA1UdJQQMMAoGCCsGAQUFBwMJMB8GA1UdIwQYMBaAFCGvaiaYcekmCtXboYz3 +26adpSVcMIHNBggrBgEFBQcBAQSBwDCBvTBFBggrBgEFBQcwAoY5aHR0cDovL3d3 +dy5kLXRydXN0Lm5ldC9jZ2ktYmluL0QtVFJVU1RfQ0FfMi0yX0VWXzIwMTYuY3J0 +MHQGCCsGAQUFBzAChmhsZGFwOi8vZGlyZWN0b3J5LmQtdHJ1c3QubmV0L0NOPUQt +VFJVU1QlMjBDQSUyMDItMiUyMEVWJTIwMjAxNixPPUQtVHJ1c3QlMjBHbWJILEM9 +REU/Y0FDZXJ0aWZpY2F0ZT9iYXNlPzCB+wYDVR0fBIHzMIHwMIHtoIHqoIHnhm5s +ZGFwOi8vZGlyZWN0b3J5LmQtdHJ1c3QubmV0L0NOPUQtVFJVU1QlMjBDQSUyMDIt +MiUyMEVWJTIwMjAxNixPPUQtVHJ1c3QlMjBHbWJILEM9REU/Y2VydGlmaWNhdGVy +ZXZvY2F0aW9ubGlzdIY1aHR0cDovL2NybC5kLXRydXN0Lm5ldC9jcmwvZC10cnVz +dF9jYV8yLTJfZXZfMjAxNi5jcmyGPmh0dHA6Ly9jZG4uZC10cnVzdC1jbG91ZGNy +bC5uZXQvY3JsL2QtdHJ1c3RfY2FfMi0yX2V2XzIwMTYuY3JsMB0GA1UdDgQWBBQp +U019kWfhYSNlymjVo9J6/BicfjAOBgNVHQ8BAf8EBAMCBkAwDwYJKwYBBQUHMAEF +BAIFADANBgkqhkiG9w0BAQsFAAOCAQEAl7SIJfJ6rN6fqljRwClDwC+4nrr1jE2M +Q8m5EyEg3pu37incASW7sgZfguTKFqjiPnbuDOlCvoNVaZRU3y6dHm2YPP6sGc/s +NByO71HIJVZQ3vXz6JXDKeu0oHfONgWqtiLYXJ91MXsak6XLp8mTKQ0lXlENlIzM +nYG+LTnLLbNIVHoM8TJpid+U7z8+Z40tve17jXqOv4IfgqS5GPN/RkwtxEeyauVf +2LdUc0yCQOq8SVR7yIrcbvFyI9PUKcfy0eHSH9cC570mzQgGDu6jU3L1IA1a6TLq +gkcgAszklWDPbLdBLs51cO5Svc3AS77b9xjXrPVzKHxyFFQaxJuwpA== +-----END CERTIFICATE----- diff --git a/src/tests/data/x509/ocsp/bdr-root.pem b/src/tests/data/x509/ocsp/bdr-root.pem new file mode 100644 index 00000000000..0a1a2b2dd69 --- /dev/null +++ b/src/tests/data/x509/ocsp/bdr-root.pem @@ -0,0 +1,25 @@ +-----BEGIN CERTIFICATE----- +MIIEQzCCAyugAwIBAgIDCYP0MA0GCSqGSIb3DQEBCwUAMFAxCzAJBgNVBAYTAkRF +MRUwEwYDVQQKDAxELVRydXN0IEdtYkgxKjAoBgNVBAMMIUQtVFJVU1QgUm9vdCBD +bGFzcyAzIENBIDIgRVYgMjAwOTAeFw0wOTExMDUwODUwNDZaFw0yOTExMDUwODUw +NDZaMFAxCzAJBgNVBAYTAkRFMRUwEwYDVQQKDAxELVRydXN0IEdtYkgxKjAoBgNV +BAMMIUQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgRVYgMjAwOTCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBAJnxhDRwui+3MKCOvXwEz75ivJn9gpfSegpn +ljgJ9hBOlSJzmY3aFS3nBfwZcyK3jpgAvDw9rKFs+9Z5JUut8Mxk2og+KbgPCdM0 +3TP1YtHhzRnp7hhPTFiu4h7WDFsVWtg6uMQYZB7jM7K1iXdODL/ZlGsTl28So/6Z +qQTMFexgaDbtCHu39b+T7WYxg4zGcTSHThfqr4uRjRxWQa4iN1438h3Z0S0NL2lR +p75mpoo6Kr3HGrHhFPC+Oh25z1uxav60sUYgovseO3Dvk5h9jHOW8sXvhXCtKSb8 +HgQ+HKDYD8tSg2J87otTlZCpV6LqYQXY+U3EJ/pure3511H3a6UCAwEAAaOCASQw +ggEgMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFNOUikxiEyoZLsyvcop9Ntea +HNxnMA4GA1UdDwEB/wQEAwIBBjCB3QYDVR0fBIHVMIHSMIGHoIGEoIGBhn9sZGFw +Oi8vZGlyZWN0b3J5LmQtdHJ1c3QubmV0L0NOPUQtVFJVU1QlMjBSb290JTIwQ2xh +c3MlMjAzJTIwQ0ElMjAyJTIwRVYlMjAyMDA5LE89RC1UcnVzdCUyMEdtYkgsQz1E +RT9jZXJ0aWZpY2F0ZXJldm9jYXRpb25saXN0MEagRKBChkBodHRwOi8vd3d3LmQt +dHJ1c3QubmV0L2NybC9kLXRydXN0X3Jvb3RfY2xhc3NfM19jYV8yX2V2XzIwMDku +Y3JsMA0GCSqGSIb3DQEBCwUAA4IBAQA07XtaPKSUiO8aEXUHL7P+PPoeUSbrh/Yp +3uDx1MYkCenBz1UbtDDZzhr+BlGmFaQt77JLvyAoJUnRpjZ3NOhk31KxEcdzes05 +nsKtjHEh8lprr988TlWvsoRlFIm5d8sqMb7Po23Pb0iUMkZv53GMoKaEGTcH8gNF +CSuGdXzfX2lXANtu2KZyIktQ1HWYVt+3GP9DQ1CuekR78HlR10M9p9OB0/DJT7na +xpeG0ILD5EJt/rDiZE4OJudANCa1CInXCGNjOCd1HjPqbqjdn5lPdE2BiYBL3ZqX +KVwvvoFBuYz/6n1gBp7N1z3TLqMVvKjmJuVvw9y4AyHqnxbxLFS1 +-----END CERTIFICATE----- diff --git a/src/tests/data/x509/ocsp/bdr.pem b/src/tests/data/x509/ocsp/bdr.pem new file mode 100644 index 00000000000..604defc867f --- /dev/null +++ b/src/tests/data/x509/ocsp/bdr.pem @@ -0,0 +1,80 @@ +-----BEGIN CERTIFICATE----- +MIIOhDCCDWygAwIBAgIQR2P0PtEycYOZmkQw8teHNzANBgkqhkiG9w0BAQsFADBe +MQswCQYDVQQGEwJERTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMR8wHQYDVQQDExZE +LVRSVVNUIENBIDItMiBFViAyMDE2MRcwFQYDVQRhEw5OVFJERS1IUkI3NDM0NjAe +Fw0yMjAzMjUwODE1NDVaFw0yMzAzMjgwNzE1NDVaMIIBIjELMAkGA1UEBhMCREUx +HTAbBgNVBAoTFEJ1bmRlc2RydWNrZXJlaSBHbWJIMQswCQYDVQQLEwJJVDEbMBkG +A1UEAxMSYnVuZGVzZHJ1Y2tlcmVpLmRlMQ8wDQYDVQQHEwZCZXJsaW4xEzARBgsr +BgEEAYI3PAIBAxMCREUxDjAMBgNVBBEMBTEwOTY5MR0wGwYDVQQPDBRQcml2YXRl +IE9yZ2FuaXphdGlvbjEcMBoGA1UECRMTS29tbWFuZGFudGVuc3RyLiAxODEUMBIG +A1UEBRMLSFJCIDcwNzY0IEIxFzAVBgsrBgEEAYI3PAIBAQwGQmVybGluMRcwFQYL +KwYBBAGCNzwCAQIMBkJlcmxpbjEPMA0GA1UECBMGQmVybGluMIICIjANBgkqhkiG +9w0BAQEFAAOCAg8AMIICCgKCAgEA3B1Rp2V3DQSrr57KSDLsZ6mUJ0Y9LWhcLvTo +b84DN1Y/U9ZCyGGJ2hYiDnwPpcIHFfp1v+jUaiE8Km4VA6tkG8o6Y5w2BMM9Ej20 +z2kwOtVdSh1wdC9zinmuGwjshmw2eSvr1C77y3jN6P1qDyjACdAQ6SM8hKV5JxFz +g0+UAN0lO51C9v61EXjteByo6ikDEGnjFc+fC5kQGGJGRy4+I1vfgIsYri1LhGOS +86xH9o4RejCiM5Az4wfMgzobmeizsugAljxXcwMpVM8jA/rzUyRUqAwjsIcC4qFt +K1tj7vQy9bpUN3xWc6VDvZjFOat/z551I6JM6kPshN5DoW6O0s3H7BoxSx0N69UA ++zb/Fefk/oy6BR4jwwvJboHjaOpliZUC/2uXOd2pp4/MCyhILz2ikRr6EMD7qCDd +9QFabRFjKe1GzKs0Uh6ewlrX1IHs4REmmf6f5+gCeBWGrwGAWhm69Pdbv2NgfS4t +OYob8Z2APvr+QsVsuwch7bcX99wp67gaw1Cgtsz4iAKLw73Aza6dJxoH6cC5x6PD +Fkpoo6sXYNVovVBPVDuq5Wnd+qvSBsjzlzILUQCfuVn+CcttYzFKMMX2LHvSSRhB +A64iOouMS/sWGvdamvqrlzUpKoeIpJhPit0D23xNq48LpEaHs3CZFsvung29Z9Vg +8flG6h0CAwEAAaOCCXYwgglyMIIBKwYDVR0RBIIBIjCCAR6CBmJkci5kZYIed3d3 +LnN1cHBvcnQuYnVuZGVzZHJ1Y2tlcmVpLmRlgg53d3cuc2lnbi1tZS5kZYIWd3d3 +LmJ1bmRlc2RydWNrZXJlaS5kZYIXd3d3LmJ1bmRlc2RydWNrZXJlaS5jb22CCnd3 +dy5iZHIuZGWCGnN1cHBvcnQuYnVuZGVzZHJ1Y2tlcmVpLmRlggpzaWduLW1lLmRl +ghpzZXJ2aWNlLmJ1bmRlc2RydWNrZXJlaS5kZYIdaW50ZXJha3Rpdi5idW5kZXNk +cnVja2VyZWkuZGWCG2hlbHBkZXNrLmJ1bmRlc2RydWNrZXJlaS5kZYISYnVuZGVz +ZHJ1Y2tlcmVpLmRlghNidW5kZXNkcnVja2VyZWkuY29tMB0GA1UdDgQWBBShj278 +UTgPVPGLerrSzyQ18D831TAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIw +DgYDVR0PAQH/BAQDAgWgMIIBCQYIKwYBBQUHAQEEgfwwgfkwOgYIKwYBBQUHMAGG +Lmh0dHA6Ly9kLXRydXN0LWNhLTItMi1ldi0yMDE2Lm9jc3AuZC10cnVzdC5uZXQw +RQYIKwYBBQUHMAKGOWh0dHA6Ly93d3cuZC10cnVzdC5uZXQvY2dpLWJpbi9ELVRS +VVNUX0NBXzItMl9FVl8yMDE2LmNydDB0BggrBgEFBQcwAoZobGRhcDovL2RpcmVj +dG9yeS5kLXRydXN0Lm5ldC9DTj1ELVRSVVNUJTIwQ0ElMjAyLTIlMjBFViUyMDIw +MTYsTz1ELVRydXN0JTIwR21iSCxDPURFP2NBQ2VydGlmaWNhdGU/YmFzZT8wgfsG +A1UdHwSB8zCB8DCB7aCB6qCB54ZubGRhcDovL2RpcmVjdG9yeS5kLXRydXN0Lm5l +dC9DTj1ELVRSVVNUJTIwQ0ElMjAyLTIlMjBFViUyMDIwMTYsTz1ELVRydXN0JTIw +R21iSCxDPURFP2NlcnRpZmljYXRlcmV2b2NhdGlvbmxpc3SGNWh0dHA6Ly9jcmwu +ZC10cnVzdC5uZXQvY3JsL2QtdHJ1c3RfY2FfMi0yX2V2XzIwMTYuY3Jshj5odHRw +Oi8vY2RuLmQtdHJ1c3QtY2xvdWRjcmwubmV0L2NybC9kLXRydXN0X2NhXzItMl9l +dl8yMDE2LmNybDCB5wYIKwYBBQUHAQMEgdowgdcwCAYGBACORgEBMIG1BgYEAI5G +AQUwgaowUxZNaHR0cDovL3d3dy5kLXRydXN0Lm5ldC9pbnRlcm5ldC9maWxlcy9E +LVRSVVNUX1BLSV9EaXNjbG9zdXJlX1N0YXRlbWVudF9kZS5wZGYTAmRlMFMWTWh0 +dHA6Ly93d3cuZC10cnVzdC5uZXQvaW50ZXJuZXQvZmlsZXMvRC1UUlVTVF9QS0lf +RGlzY2xvc3VyZV9TdGF0ZW1lbnRfZW4ucGRmEwJlbjATBgYEAI5GAQYwCQYHBACO +RgEGAzCBiQYDVR0gBIGBMH8wCQYHBACL7EABBDAHBgVngQwBATANBgsrBgEEAaU0 +AoEWBDBaBgsrBgEEAaU0AoFKATBLMEkGCCsGAQUFBwIBFj1odHRwOi8vd3d3LmQt +dHJ1c3QubmV0L2ludGVybmV0L2ZpbGVzL0QtVFJVU1RfQ1NNX1BLSV9DUFMucGRm +MB8GA1UdIwQYMBaAFCGvaiaYcekmCtXboYz326adpSVcMIIETwYKKwYBBAHWeQIE +AgSCBD8EggQ7BDkAdwCt9776fP8QyIudPZwePhhqtGcpXc+xDCTKhYY069yCigAA +AX/AJVyRAAAEAwBIMEYCIQDHYr2J0KhX9Qw2DZcpukdrMtTPrSkQTG3WQ+9TJbfv +fAIhAIsgHLLnR3DBqqikp7qjOg2ge3rhLKae4EcfJ5OYH3bzAHcAs3N3B+GEUPhj +htYFqdwRCUp5LbFnDAuH3PADDnk2pZoAAAF/wCVdigAABAMASDBGAiEAv5hGLqwU +NARYcml1ScV/JumKME8Gh/+KFLd76xi69cICIQC5aK3LduJomzCkxLZecyDhIghV +zNwsNbB1XQY9TBepLAB2AOg+0No+9QY1MudXKLyJa8kD08vREWvs62nhd31tBr1u +AAABf8AlXP8AAAQDAEcwRQIgPkK0U2XQA0b4SS89AFPNRFo3TdcdNm90Z8015UBb +MpcCIQDMUkJimfKU5IvKyO7D8ibgsJSHE+NASD15Pixf8L25+wB2AFWB1MIWkDYB +SuoLm1c8U/DA5Dh4cCUIFy+jqh0HE9MMAAABf8AlXY8AAAQDAEcwRQIhAKX9i888 +VPeAjIztEESfZ8Izy051gTTSl9D1GBH7Z810AiBrBtrXTu+V39yPAfIK7YBpgsvS +C0vB8MCe1Q1nR5KK+gB3AHoyjFTYty22IOo44FIe6YQWcDIThU070ivBOlejUutS +AAABf8AlXTsAAAQDAEgwRgIhAPeWQ8o/CaW5HpEA3UkszILAlsKnixEHRDGFMl8q +GN+rAiEAmBQ7TBG8Xgru2e5c3GdUXecmDVjwI/G1ZthSFmMvNlgAdwBvU3asMfAx +GdiZAKRRFf93FRwR2QLBACkGjbIImjfZEwAAAX/AJV4ZAAAEAwBIMEYCIQC1HlH1 +MYjMp1pvFYmNXafGXvJ6oIiGiUtd1kHRtCt76QIhALj7dbiBFP4b9elj5kYMDPT0 +PAoflviX/f8klXtTFG27AHUA6H6nZgvCbPYALvVyXT/g4zG5OTu5L79Y6zuQSdr1 +Q1oAAAF/wCVhLwAABAMARjBEAiBszHijSAeBf3cec2LQgegrIJ3I4P9EQX28ZQ4S +yTvmDgIgMvxYYvRNu7+RY4AnFAZAhN9eX4WwXLrEdPOPhhxs0TwAdAA1zxkbv7Fs +V78PrUxtQsu7ticgJlHqP+Eq76gDwzvWTAAAAX/AJWAHAAAEAwBFMEMCIBhApuJO +EqEb0oq/6VWxM6jz2dbD7+ZjBDuvOioO/Cf9Ah9/QAHSUTU043F7VdV/REB12XGY +a63YqZJJeeIgTuZDAHYAtz77JN+cTbp18jnFulj0bF38Qs96nzXEnh0JgSXttJkA +AAF/wCVfSQAABAMARzBFAiEA4036r9QS+ngcG4FMBUc1Z36BywbwF00pHprDpNMQ +KhUCIGzTcK+3DnLBJOxwScoow/EJq39GmZV1sZz93r/d5qNdMA0GCSqGSIb3DQEB +CwUAA4IBAQBZvYmRmtu1gQLCA+QqN5C7ftPr0ioULQPBsmX8gHmQ1iHPrBrC99Ef +UD0//QB8V/aWqbt1NNdFXXEslN0V591m13uF7cp27SUjxNFwkPG2oypoqNM42I0U +136fs26VzFbLe/MLNLTiiTkIp4HfSnLoactqvWapU9X6pzRk3CoKbaGHkPpIn467 +6uq08dss4+W9DROLZynwuswtxhLdk4pi82mnIs0t8A+ZOHwKPrQ9zi8Mtc7T9xPY +PuGpbWQMTGKzCOjki81OvuD0ZU//hfHIM8Nh3Fb1LQ3ZRMudYdW+noIaW4FQRY2W +Pr1qU9fkcHml0htVexwhF6m1x5HZ332p +-----END CERTIFICATE----- diff --git a/src/tests/data/x509/ocsp/mychain_ee.pem b/src/tests/data/x509/ocsp/mychain_ee.pem new file mode 100644 index 00000000000..23b036320d4 --- /dev/null +++ b/src/tests/data/x509/ocsp/mychain_ee.pem @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDXDCCAkSgAwIBAgIUQCu8J4C8/lLpesu+yxmEcgboaKwwDQYJKoZIhvcNAQEL +BQAwOTEZMBcGA1UEAwwQTXkgT0NTUCBMb2NhbCBDQTELMAkGA1UEBhMCREUxDzAN +BgNVBAcMBkJlcmxpbjAeFw0yMjA5MjIxMDAwMDBaFw0yMzA5MjIxMDAwMDBaMDsx +GzAZBgNVBAMMEk15IE9DU1AgRW5kIEVudGl0eTELMAkGA1UEBhMCREUxDzANBgNV +BAcMBkJlcmxpbjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJjSgFM7 +veKLCerCj8LbDH2eyE/wsgt75EugNON2xcuxdnZKXl9kQP/lq2tjQF9VUKvUr7C0 +4BDTyXjg+0RnH8EUp2fooDsrJu9k1i+lDWFtAYAYYrYYxGMFzCC/h+GBD0FCFBwL +3gpZwPitoDga6jtPbtv/RwFMuPy7b0KUpNMkVAeaT/KVmqc/l+SgLqDEZciiMcaC +GG1rkMnElR7c/0lg5xNITXS1t1Z9bHbpO7lH5xDoFcSTEhOcDdFkN923sbfTT3m9 +4oKHYFDUSoAc+Y2jbwbDK+g6MyCwIiwdyUF+Kgv1fdacWxZMmr2aOA5CX/1+ZeX1 +97ameyxkyA2DZfsCAwEAAaNaMFgwHwYDVR0jBBgwFoAUm5xBswA2BO40FM22Q/5K +RR6PtNMwCQYDVR0TBAIwADALBgNVHQ8EBAMCB4AwHQYDVR0OBBYEFKnd1WBRL4H/ +IhofGjvLhtGSNh9lMA0GCSqGSIb3DQEBCwUAA4IBAQCoYIhu/w1Hp2aByrbV7Plm +aUhBJovJHqa3KixgSrV6Td6URaCSGHAiAFj1j0/dqzKL7QHMZYs43JRODuABAjsn +SktrpuoA+FILuSZXMm3UFEqNNJzFTwZLC3lSpxT1zvQ4PDgx4xFTMi9pyvGnDHjt +jkPLuLfjgI5PShcIB0Hd6yS07pBFdg/Dr3fCSU7OBAC4o44ubUa5kASvX35zjWoj +NulehNs+aa6Fm7qqSt4mz24qvnOG3SyYpkNKeu/FQjaKXV35A0tGN2ibEHSn0JBp +rZqzfegU5UuZrpGs3xUVeIH+rQdW8uBlllXP38djJ7mLb/3b1vLWakljPqAOOlsm +-----END CERTIFICATE----- diff --git a/src/tests/data/x509/ocsp/mychain_int.pem b/src/tests/data/x509/ocsp/mychain_int.pem new file mode 100644 index 00000000000..f9bd11adac1 --- /dev/null +++ b/src/tests/data/x509/ocsp/mychain_int.pem @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDcTCCAlmgAwIBAgIUP4pG/mdA98wntpdjLKKDv50V4twwDQYJKoZIhvcNAQEL +BQAwODEYMBYGA1UEAwwPTXkgT0NTUCBSb290IENBMQswCQYDVQQGEwJERTEPMA0G +A1UEBwwGQmVybGluMB4XDTIyMDkyMjEwMDAwMFoXDTIzMDkyMjEwMDAwMFowOTEZ +MBcGA1UEAwwQTXkgT0NTUCBMb2NhbCBDQTELMAkGA1UEBhMCREUxDzANBgNVBAcM +BkJlcmxpbjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAODHbsMfDefJ +fryKz8fY8AfCE8uAr5CzJ2gXQcIQd0rBJTPQxaxZ950+QbSfPAPAeFa1OWRc/Xby +3DXvtQ3yt879mvxAvsdvlUYOsOOi8b9tap3vLVSc668BJwByNwBZmAF6ByKsC4Yj +wwH7rfekE2KU89LzH0wWDJOybo/N62kXuzt23dO4uUXJat6ZlEghmzhAzHyfFdeD +H8V/7x7c6iQBFz0NSCeo/gzFzVNO0jKbyvScQmfLOvbwTm91nXPs6MWICzsNOliJ +vtfkJsqheq7dVX9HdLfh/1tdFx1WaPhtVf3VTolPGTs9w7hh6uaWsHZpFTbiK0Mc +DeNQkoX1ikcCAwEAAaNyMHAwHwYDVR0jBBgwFoAUk/nNvUAjr/JUj93s2WMZm25Y +zHUwDAYDVR0TBAUwAwEB/zALBgNVHQ8EBAMCAYYwEwYDVR0lBAwwCgYIKwYBBQUH +AwkwHQYDVR0OBBYEFJucQbMANgTuNBTNtkP+SkUej7TTMA0GCSqGSIb3DQEBCwUA +A4IBAQCN1CeGhYZr/ghM45N0auoTJ+U5lAah3g6c7lGS6x6+XyaueI+Pxy0wC/1C +UCjEbErD44utxk+816uUnhmUOqSrDejV0xxPnQYokziOw8flLKm8/Y5ngQ14VshX +oJZMdaQywe3Je34b6t/BZZaZx/dtXHtkDTBdBgOXiv/O7JMDqEQzFb8uC3MPpM1b +TtC58Rtvh8nhy5ieig/uaXBIwcyc4ujlllzjmwV1yNg6iY1QVj3GMRsxvI1ZFkaZ +eZnbFNqwx5ZLL61c/cBV8pG47DKSqBhV9osWCK/vc6WHYcwyYBJ5YIuykl/zs41o +knoudoJ3BFGS9PaFZZQCA78WnYsd +-----END CERTIFICATE----- diff --git a/src/tests/data/x509/ocsp/mychain_int_ocsp_delegate_responder.pem b/src/tests/data/x509/ocsp/mychain_int_ocsp_delegate_responder.pem new file mode 100644 index 00000000000..6e65f1cf071 --- /dev/null +++ b/src/tests/data/x509/ocsp/mychain_int_ocsp_delegate_responder.pem @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDgjCCAmqgAwIBAgIUFkhEhhLqatMopup9/noUhdFx5EkwDQYJKoZIhvcNAQEL +BQAwOTEZMBcGA1UEAwwQTXkgT0NTUCBMb2NhbCBDQTELMAkGA1UEBhMCREUxDzAN +BgNVBAcMBkJlcmxpbjAeFw0yMjA5MjIxMDAwMDBaFw0yMjEwMDcxMDAwMDBaMDox +GjAYBgNVBAMMEU15IE9DU1AgUmVzcG9uZGVyMQswCQYDVQQGEwJERTEPMA0GA1UE +BwwGQmVybGluMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3N3l6YZy +pFShnQWmMyplXu3JaDjUxlrNwEs6Dn5flUC0eAZVN+uaXws+tG//wV487n+OXnyh +Zgz1Mt97J1wYhw7R9bQakPrmkYztrTmTKemS70sWjrsH0Od/S851sv3qAGylWiKb +1n0SawRPo6T5bvYADwwGESRKmWwOwPIv2KdsZ3kUhN9aPj06CMVJIRYVennZRt4X +4tcpgpB/eBp4/iEmfe3BzrFgf9YJG4qcbM84lULGLOnVNuUbbEIlBe75U71OR8dV +El65LSMAVDQovjTV3mdQcLQNOiNnBlNDDaJEi590ki59qnFbJO0Zsf7a/rpHz/4J +LqK8b2by8KoFpwIDAQABo4GAMH4wHwYDVR0jBBgwFoAUm5xBswA2BO40FM22Q/5K +RR6PtNMwCQYDVR0TBAIwADALBgNVHQ8EBAMCAYIwEwYDVR0lBAwwCgYIKwYBBQUH +AwkwDwYJKwYBBQUHMAEFBAIFADAdBgNVHQ4EFgQU7cI/PXYEpWH9l6Bnu36V6Nzv +/MowDQYJKoZIhvcNAQELBQADggEBALv6KUJ0I/Kd/4ofDQHcgrHOe3u26zs1LC5J +X9ZMoLRwN2LbzwWogIg3DEYqLAr0whpiDDcueVQVxK0rYrI1kWAZYi/wkmdOI5D7 +GNtHxdMty62XgOLb4LwGmEMQ7SLH2GgEAgKjJAIVJ5TMlxH8NV2/hrQhmXDpkZc/ ++6I881LDW2273p8vKXxYnI1EFTdCVa9XnNJr3U+yhC9plf+gSr51iXyQf9MPdZ91 +fg187LQkn6oIRtKL7yZMAajemcTkU8avoF1+EX01Z5nu/v2Hgtp2VFDKvCrud+e/ +iKFLYBlsnqfNyZt4n3PAxDP5ziZ5adH2ELCvPDkJ3nneAhvP5So= +-----END CERTIFICATE----- diff --git a/src/tests/data/x509/ocsp/mychain_int_ocsp_delegate_responder_no_ocsp_key_usage.pem b/src/tests/data/x509/ocsp/mychain_int_ocsp_delegate_responder_no_ocsp_key_usage.pem new file mode 100644 index 00000000000..0cac96b7b3f --- /dev/null +++ b/src/tests/data/x509/ocsp/mychain_int_ocsp_delegate_responder_no_ocsp_key_usage.pem @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDbDCCAlSgAwIBAgIUXMtIzKLTZ3oKE92bXWKGdS16GCYwDQYJKoZIhvcNAQEL +BQAwOTEZMBcGA1UEAwwQTXkgT0NTUCBMb2NhbCBDQTELMAkGA1UEBhMCREUxDzAN +BgNVBAcMBkJlcmxpbjAeFw0yMjA5MjIxMDAwMDBaFw0yMjEwMDcxMDAwMDBaMDox +GjAYBgNVBAMMEU15IE9DU1AgUmVzcG9uZGVyMQswCQYDVQQGEwJERTEPMA0GA1UE +BwwGQmVybGluMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3N3l6YZy +pFShnQWmMyplXu3JaDjUxlrNwEs6Dn5flUC0eAZVN+uaXws+tG//wV487n+OXnyh +Zgz1Mt97J1wYhw7R9bQakPrmkYztrTmTKemS70sWjrsH0Od/S851sv3qAGylWiKb +1n0SawRPo6T5bvYADwwGESRKmWwOwPIv2KdsZ3kUhN9aPj06CMVJIRYVennZRt4X +4tcpgpB/eBp4/iEmfe3BzrFgf9YJG4qcbM84lULGLOnVNuUbbEIlBe75U71OR8dV +El65LSMAVDQovjTV3mdQcLQNOiNnBlNDDaJEi590ki59qnFbJO0Zsf7a/rpHz/4J +LqK8b2by8KoFpwIDAQABo2swaTAfBgNVHSMEGDAWgBSbnEGzADYE7jQUzbZD/kpF +Ho+00zAJBgNVHRMEAjAAMAsGA1UdDwQEAwIHgDAPBgkrBgEFBQcwAQUEAgUAMB0G +A1UdDgQWBBTtwj89dgSlYf2XoGe7fpXo3O/8yjANBgkqhkiG9w0BAQsFAAOCAQEA +V17YOra6yl0wTjt6QbQDXxm5m02CpW3EZs8x1M8yZadWXK9dJ6mo7vetqF3nnOzd +TxesfAWigkrSZjR7HHHXXO5S9OjFLEyft+Xbx9+t8216Lbqk7WierREz1C21yCpn +B76DiQRXqY2lEm1cpgkZeSc+SSfoN4oOyXCb/r+sgEebXGHrQhqgdFAWq3BmF6U+ +VyIXG7PiJGoTmlJ9gfkr0+Y0MxNpTIr6OPc9H6+N4mYPhcj/9emTcj6R+0PvfAZC +GRc8U3fCW3UdPOTE28f86ZvMduavCZCSU4m74nZnzY6eKR83KNCjB0gJzYhwqcRm +f9I5C+O6SocALwTGGYkMbg== +-----END CERTIFICATE----- diff --git a/src/tests/data/x509/ocsp/mychain_ocsp_for_ee.der b/src/tests/data/x509/ocsp/mychain_ocsp_for_ee.der new file mode 100644 index 00000000000..1b6b837960e Binary files /dev/null and b/src/tests/data/x509/ocsp/mychain_ocsp_for_ee.der differ diff --git a/src/tests/data/x509/ocsp/mychain_ocsp_for_ee_delegate_signed.der b/src/tests/data/x509/ocsp/mychain_ocsp_for_ee_delegate_signed.der new file mode 100644 index 00000000000..1afcf7607f2 Binary files /dev/null and b/src/tests/data/x509/ocsp/mychain_ocsp_for_ee_delegate_signed.der differ diff --git a/src/tests/data/x509/ocsp/mychain_ocsp_for_ee_delegate_signed_malformed.der b/src/tests/data/x509/ocsp/mychain_ocsp_for_ee_delegate_signed_malformed.der new file mode 100644 index 00000000000..6b9fe3bea23 Binary files /dev/null and b/src/tests/data/x509/ocsp/mychain_ocsp_for_ee_delegate_signed_malformed.der differ diff --git a/src/tests/data/x509/ocsp/mychain_ocsp_for_ee_root_signed.der b/src/tests/data/x509/ocsp/mychain_ocsp_for_ee_root_signed.der new file mode 100644 index 00000000000..0dc398db54a Binary files /dev/null and b/src/tests/data/x509/ocsp/mychain_ocsp_for_ee_root_signed.der differ diff --git a/src/tests/data/x509/ocsp/mychain_ocsp_for_int_self_signed.der b/src/tests/data/x509/ocsp/mychain_ocsp_for_int_self_signed.der new file mode 100644 index 00000000000..5a1867ca1f1 Binary files /dev/null and b/src/tests/data/x509/ocsp/mychain_ocsp_for_int_self_signed.der differ diff --git a/src/tests/data/x509/ocsp/mychain_root.pem b/src/tests/data/x509/ocsp/mychain_root.pem new file mode 100644 index 00000000000..192d71b8094 --- /dev/null +++ b/src/tests/data/x509/ocsp/mychain_root.pem @@ -0,0 +1,22 @@ +-----BEGIN CERTIFICATE----- +MIIDsDCCApigAwIBAgIUWMiulqiEbnZwrB5iI7tL724j7qswDQYJKoZIhvcNAQEL +BQAwODEYMBYGA1UEAwwPTXkgT0NTUCBSb290IENBMQswCQYDVQQGEwJERTEPMA0G +A1UEBwwGQmVybGluMB4XDTIyMDkyMjEwMDAwMFoXDTIzMDkyMjEwMDAwMFowODEY +MBYGA1UEAwwPTXkgT0NTUCBSb290IENBMQswCQYDVQQGEwJERTEPMA0GA1UEBwwG +QmVybGluMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAukN6nemvQcIX +zq+/DKFsJWjQif6sTP2zXfZtpw445LagOt8T9fGFgv6BSNTFp/TMRatQMAfZteH8 +ExzInhOIatwZgOKfG5tE+OH+tOuo9JrgWQRMGrhCV4fClDOv3sPAvduYm00muazD +HeusESr/ykoA3HmJpS62EeOvMsY991TGSoTUSPLXJOyVTT5EcHdLrmosIBNx4nN9 +8xN5ENbhz/lZa3z1+NEtruMhDY5s13POVgpXRCZmgyhl6uCl0HZOYPfoWZwbZfuh +S6U9s0C+JMRjcz1fLyBW2dgsWG6TRSsF6R83DkFQx/9kazfjv/mOqLMw1irT3K0E +wtsxe0aKfQIDAQABo4GxMIGuMF0GA1UdIwRWMFShPKQ6MDgxGDAWBgNVBAMMD015 +IE9DU1AgUm9vdCBDQTELMAkGA1UEBhMCREUxDzANBgNVBAcMBkJlcmxpboIUWMiu +lqiEbnZwrB5iI7tL724j7qswDAYDVR0TBAUwAwEB/zALBgNVHQ8EBAMCAYYwEwYD +VR0lBAwwCgYIKwYBBQUHAwkwHQYDVR0OBBYEFJP5zb1AI6/yVI/d7NljGZtuWMx1 +MA0GCSqGSIb3DQEBCwUAA4IBAQCdGyFlbaBkoLgLwM2q91VcLUHAp54Gp6vvLavq +p+65K7sdzzFj/6P9p6Dsa0BJ3bXba0pfJ10f9nFHOIFISb0Aptmm34XjBwvUckbb +LYDU7InmyS5aeAIxK9+G7TllLfSslPQJspSxWWZkp3cY4Ys7bGidb1ad620F2cMe +I2c09zhQuySbLDgaCc2Hg9Z3trb6S91Mmk6P+fQMzqq0XkfUOqzmEm2D7lFb3G76 +DI6CouYjoIYndVEN6oVVIcD+01Emxssy60aO6wS5MaM8TbcCdx3ZxYCdKj6YcdRf +XhEN1KonHRKP71iZrlw/W+GfVvt1dJx5V5fqh3mGZVvk7Sc7 +-----END CERTIFICATE----- diff --git a/src/tests/data/x509/ocsp/randombit_ocsp_forged_responder.pem b/src/tests/data/x509/ocsp/randombit_ocsp_forged_responder.pem new file mode 100644 index 00000000000..9381a3eefe3 --- /dev/null +++ b/src/tests/data/x509/ocsp/randombit_ocsp_forged_responder.pem @@ -0,0 +1,23 @@ +-----BEGIN CERTIFICATE----- +MIID1TCCAr2gAwIBAgIUQi+O3XGTkbU8ihDwXOrV18vdTvMwDQYJKoZIhvcNAQEL +BQAwNzEXMBUGA1UEAwwORm9yZ2VkIE9DU1AgQ0ExCzAJBgNVBAYTAkRFMQ8wDQYD +VQQHDAZCZXJsaW4wHhcNMTYxMTE4MTEwMDAwWhcNMTcxMTE4MTEwMDAwWjB+MQsw +CQYDVQQGEwJERTEPMA0GA1UECAwGQmVybGluMQ8wDQYDVQQHDAZCZXJsaW4xFDAS +BgNVBAoMC0hhY2tlcnNwYWNlMRowGAYDVQQLDBFPQ1NQIEJyZWFraW5nIExhYjEb +MBkGA1UEAwwSRm9yZ2VkIE9DU1AgU2lnbmVyMIIBIjANBgkqhkiG9w0BAQEFAAOC +AQ8AMIIBCgKCAQEAq72Y4p9gCPcoNELOB5i104jhbzbEWfcXhAdXkmufOFFVVveq +HbiGx5GLi46cJATjSQoOL86Jwgp/v0nZukfQFIsWJGjG3eDQnMBGaAH9+SZh+udP +dhcuOvFqvFBkKk6rMIcW0Tqx2ixZUG7275JrqjEyNUjAGA9fRSkGoWyca/P6QCjE +sgAMr82n0XahLi7VVL0v/DcRK7h9slJJbG9UBmHuwPYU5C5Z9iQKCh3JZ3oOgO4d +OuAGXrRm69znN5jlkBxgowJbgPn4Xp2QyAZl2A0/mou3U9WuVGDOUDLRL1UbCv/T +VyX/WyUsAV54apAkxM9Hd5yZermoIZ7gPCv40wIDAQABo4GRMIGOMB8GA1UdIwQY +MBaAFE4W+nR1DcTuZYBY/YXQinJ1Y5PjMAkGA1UdEwQCMAAwCwYDVR0PBAQDAgeA +MBMGA1UdJQQMMAoGCCsGAQUFBwMJMB8GA1UdEQQYMBaCFG9jc3AuaGFja2Vyc3Bh +Y2Uub3JnMB0GA1UdDgQWBBQAUq7vwa5MkBmRG9GuRC7N2F97BjANBgkqhkiG9w0B +AQsFAAOCAQEAzS/5VHLcyTkvnodS18mlkp6r4fKkxhrLR2cyGhQPqwEqkq+l4U8k +UMnem31+XoVHt8nN7N0+aOCna7xhvxzWDQioahG4oSxW3R0FNbO4+HXEBkUqbJQo +JaVxSc4vXYjXgLvvhcSAbwfg7o3jInHszCLWoEpNEWGI0Un/ngJX0E8H374LiPnd +Z7W8bNvqRgbpbZJmrgVfm2T3NIWlMYCB8GqyZMA/uLUtxkv25LTCsCTGKhn/ZQoI +XxCZ4OvZDbxLmGj+5GsgJUHVKVhDomo0fJQh+KrMw+0IyjFVjjyroN6d1A3JPmbL +dKUfISvTkfDCj67y8iASBRCOEs7EB4JzSg== +-----END CERTIFICATE----- diff --git a/src/tests/data/x509/ocsp/randombit_ocsp_forged_revoked.der b/src/tests/data/x509/ocsp/randombit_ocsp_forged_revoked.der new file mode 100644 index 00000000000..f31272e65f6 Binary files /dev/null and b/src/tests/data/x509/ocsp/randombit_ocsp_forged_revoked.der differ diff --git a/src/tests/data/x509/ocsp/randombit_ocsp_forged_valid.der b/src/tests/data/x509/ocsp/randombit_ocsp_forged_valid.der new file mode 100644 index 00000000000..e89ada44070 Binary files /dev/null and b/src/tests/data/x509/ocsp/randombit_ocsp_forged_valid.der differ diff --git a/src/tests/data/x509/ocsp/randombit_ocsp_forged_valid_nocerts.der b/src/tests/data/x509/ocsp/randombit_ocsp_forged_valid_nocerts.der new file mode 100644 index 00000000000..1abef32ff88 Binary files /dev/null and b/src/tests/data/x509/ocsp/randombit_ocsp_forged_valid_nocerts.der differ diff --git a/src/tests/test_ocsp.cpp b/src/tests/test_ocsp.cpp index 825e795e294..1d51ec3ba0f 100644 --- a/src/tests/test_ocsp.cpp +++ b/src/tests/test_ocsp.cpp @@ -1,10 +1,11 @@ /* * (C) 2016 Jack Lloyd +* (C) 2022 René Meusel, Rohde & Schwarz Cybersecurity * * Botan is released under the Simplified BSD License (see license.txt) */ -#include "tests.h" +#include "botan/build.h" #if defined(BOTAN_HAS_OCSP) #include @@ -14,6 +15,8 @@ #include #endif +#include "tests.h" + namespace Botan_Tests { #if defined(BOTAN_HAS_OCSP) && defined(BOTAN_HAS_RSA) && defined(BOTAN_HAS_EMSA_PKCS1) && defined(BOTAN_TARGET_OS_HAS_FILESYSTEM) @@ -127,6 +130,45 @@ class OCSP_Tests final : public Test return result; } + static Test::Result test_response_find_signing_certificate() + { + Test::Result result("OCSP response finding signature certificates"); + + const std::optional nullopt_cert; + + // OCSP response is signed by the issuing CA itself + auto randombit_ocsp = load_test_OCSP_resp("x509/ocsp/randombit_ocsp.der"); + auto randombit_ca = load_test_X509_cert("x509/ocsp/letsencrypt.pem"); + + // OCSP response is signed by an authorized responder certificate + // issued by the issuing CA and embedded in the response + auto bdr_ocsp = load_test_OCSP_resp("x509/ocsp/bdr-ocsp-resp.der"); + auto bdr_responder = load_test_X509_cert("x509/ocsp/bdr-ocsp-responder.pem"); + auto bdr_ca = load_test_X509_cert("x509/ocsp/bdr-int.pem"); + + // Dummy OCSP response is not signed at all + auto dummy_ocsp = Botan::OCSP::Response(Botan::Certificate_Status_Code::OCSP_SERVER_NOT_AVAILABLE); + + // OCSP response is signed by 3rd party responder certificate that is + // not included in the OCSP response itself + // See `src/scripts/randombit_ocsp_forger.sh` for a helper script to recreate those. + auto randombit_alt_resp_ocsp = load_test_OCSP_resp("x509/ocsp/randombit_ocsp_forged_valid_nocerts.der"); + auto randombit_alt_resp_cert = load_test_X509_cert("x509/ocsp/randombit_ocsp_forged_responder.pem"); + + result.test_is_eq("Dummy has no signing certificate", dummy_ocsp.find_signing_certificate(Botan::X509_Certificate()), nullopt_cert); + + result.test_is_eq("CA is returned as signing certificate", randombit_ocsp.find_signing_certificate(randombit_ca), std::optional(randombit_ca)); + result.test_is_eq("No signer certificate is returned when signer couldn't be determined", randombit_ocsp.find_signing_certificate(bdr_ca), nullopt_cert); + + result.test_is_eq("Delegated responder certificate is returned for further validation", bdr_ocsp.find_signing_certificate(bdr_ca), std::optional(bdr_responder)); + + result.test_is_eq("Delegated responder without stapled certs does not find signer without user-provided certs", randombit_alt_resp_ocsp.find_signing_certificate(randombit_ca), nullopt_cert); + auto trusted_responders = std::make_unique(randombit_alt_resp_cert); + result.test_is_eq("Delegated responder returns user-provided cert", randombit_alt_resp_ocsp.find_signing_certificate(randombit_ca, trusted_responders.get()), std::optional(randombit_alt_resp_cert)); + + return result; + } + static Test::Result test_response_verification_with_next_update_without_max_age() { Test::Result result("OCSP request check with next_update w/o max_age"); @@ -145,7 +187,7 @@ class OCSP_Tests final : public Test auto check_ocsp = [&](const std::chrono::system_clock::time_point valid_time, const Botan::Certificate_Status_Code expected) { - const auto ocsp_status = Botan::PKIX::check_ocsp(cert_path, { ocsp }, { &certstore }, valid_time); + const auto ocsp_status = Botan::PKIX::check_ocsp(cert_path, { ocsp }, { &certstore }, valid_time, Botan::Path_Validation_Restrictions()); return result.test_eq("Expected size of ocsp_status", ocsp_status.size(), 1) && result.test_eq("Expected size of ocsp_status[0]", ocsp_status[0].size(), 1) && @@ -186,7 +228,8 @@ class OCSP_Tests final : public Test auto check_ocsp = [&](const std::chrono::system_clock::time_point valid_time, const Botan::Certificate_Status_Code expected) { - const auto ocsp_status = Botan::PKIX::check_ocsp(cert_path, { ocsp }, { &certstore }, valid_time, max_age); + Botan::Path_Validation_Restrictions pvr(false, 110, false, max_age); + const auto ocsp_status = Botan::PKIX::check_ocsp(cert_path, { ocsp }, { &certstore }, valid_time, pvr); return result.test_eq("Expected size of ocsp_status", ocsp_status.size(), 1) && result.test_eq("Expected size of ocsp_status[0]", ocsp_status[0].size(), 1) && @@ -227,7 +270,8 @@ class OCSP_Tests final : public Test auto check_ocsp = [&](const std::chrono::system_clock::time_point valid_time, const Botan::Certificate_Status_Code expected) { - const auto ocsp_status = Botan::PKIX::check_ocsp(cert_path, { ocsp }, { &certstore }, valid_time, max_age); + Botan::Path_Validation_Restrictions pvr(false, 110, false, max_age); + const auto ocsp_status = Botan::PKIX::check_ocsp(cert_path, { ocsp }, { &certstore }, valid_time, pvr); return result.test_eq("Expected size of ocsp_status", ocsp_status.size(), 1) && result.test_eq("Expected size of ocsp_status[0]", ocsp_status[0].size(), 1) && @@ -263,7 +307,7 @@ class OCSP_Tests final : public Test auto check_ocsp = [&](const std::chrono::system_clock::time_point valid_time, const Botan::Certificate_Status_Code expected) { - const auto ocsp_status = Botan::PKIX::check_ocsp(cert_path, { ocsp }, { &certstore }, valid_time); + const auto ocsp_status = Botan::PKIX::check_ocsp(cert_path, { ocsp }, { &certstore }, valid_time, Botan::Path_Validation_Restrictions()); return result.test_eq("Expected size of ocsp_status", ocsp_status.size(), 1) && result.test_eq("Expected size of ocsp_status[0]", ocsp_status[0].size(), 1) && @@ -298,13 +342,13 @@ class OCSP_Tests final : public Test // Some arbitrary time within the validity period of the test certs const auto valid_time = Botan::calendar_point(2016, 11, 20, 8, 30, 0).to_std_timepoint(); - const auto ocsp_status = Botan::PKIX::check_ocsp(cert_path, { ocsp }, { &certstore }, valid_time); + const auto ocsp_status = Botan::PKIX::check_ocsp(cert_path, { ocsp }, { &certstore }, valid_time, Botan::Path_Validation_Restrictions()); if(result.test_eq("Expected size of ocsp_status", ocsp_status.size(), 1)) { if(result.test_eq("Expected size of ocsp_status[0]", ocsp_status[0].size(), 1)) { - result.confirm("Status warning", ocsp_status[0].count(Botan::Certificate_Status_Code::OCSP_NO_REVOCATION_URL) > 0); + result.test_gt("Status warning", ocsp_status[0].count(Botan::Certificate_Status_Code::OCSP_NO_REVOCATION_URL), 0); } } @@ -326,7 +370,7 @@ class OCSP_Tests final : public Test const auto ocsp_timeout = std::chrono::milliseconds(3000); const auto now = std::chrono::system_clock::now(); - auto ocsp_status = Botan::PKIX::check_ocsp_online(cert_path, { &certstore }, now, ocsp_timeout, false); + auto ocsp_status = Botan::PKIX::check_ocsp_online(cert_path, { &certstore }, now, ocsp_timeout, Botan::Path_Validation_Restrictions()); if(result.test_eq("Expected size of ocsp_status", ocsp_status.size(), 1)) { @@ -342,6 +386,55 @@ class OCSP_Tests final : public Test } #endif + static Test::Result test_response_verification_with_additionally_trusted_responder() + { + Test::Result result("OCSP response with user-defined (additional) responder certificate"); + + // OCSP response is signed by 3rd party responder certificate that is + // not included in the OCSP response itself + // See `src/scripts/randombit_ocsp_forger.sh` for a helper script to recreate those. + auto ocsp = load_test_OCSP_resp("x509/ocsp/randombit_ocsp_forged_valid_nocerts.der"); + auto responder = load_test_X509_cert("x509/ocsp/randombit_ocsp_forged_responder.pem"); + auto ca = load_test_X509_cert("x509/ocsp/letsencrypt.pem"); + + std::optional nullopt_cert; + + Botan::Certificate_Store_In_Memory trusted_responders; + + // without providing the 3rd party responder certificate no issuer will be found + result.test_is_eq("cannot find signing certificate without trusted responders", + ocsp.find_signing_certificate(ca), nullopt_cert); + result.test_is_eq("cannot find signing certificate without additional help", + ocsp.find_signing_certificate(ca, &trusted_responders), nullopt_cert); + + // add the 3rd party responder certificate to the list of trusted OCSP responder certs + // to find the issuer certificate of this response + trusted_responders.add_certificate(responder); + result.test_is_eq("the responder certificate is returned when it is trusted", + ocsp.find_signing_certificate(ca, &trusted_responders), std::optional(responder)); + + result.test_is_eq("the responder's signature checks out", + ocsp.verify_signature(responder), Botan::Certificate_Status_Code::OCSP_SIGNATURE_OK); + + return result; + } + + static Test::Result test_responder_cert_with_nocheck_extension() + { + Test::Result result("BDr's OCSP response contains certificate featuring NoCheck extension"); + + auto ocsp = load_test_OCSP_resp("x509/ocsp/bdr-ocsp-resp.der"); + const bool contains_cert_with_nocheck = + std::find_if(ocsp.certificates().cbegin(), ocsp.certificates().cend(), + [](const auto& cert) { + return cert.v3_extensions().extension_set(Botan::OID::from_string("PKIX.OCSP.NoCheck")); + }) != ocsp.certificates().end(); + + result.confirm("Contains NoCheck extension", contains_cert_with_nocheck); + + return result; + } + public: std::vector run() override { @@ -350,11 +443,14 @@ class OCSP_Tests final : public Test results.push_back(test_request_encoding()); results.push_back(test_response_parsing()); results.push_back(test_response_certificate_access()); + results.push_back(test_response_find_signing_certificate()); results.push_back(test_response_verification_with_next_update_without_max_age()); results.push_back(test_response_verification_with_next_update_with_max_age()); results.push_back(test_response_verification_without_next_update_with_max_age()); results.push_back(test_response_verification_without_next_update_without_max_age()); results.push_back(test_response_verification_softfail()); + results.push_back(test_response_verification_with_additionally_trusted_responder()); + results.push_back(test_responder_cert_with_nocheck_extension()); #if defined(BOTAN_HAS_ONLINE_REVOCATION_CHECKS) if(Test::options().run_online_tests()) diff --git a/src/tests/test_x509_path.cpp b/src/tests/test_x509_path.cpp index 3151ecf77b2..2f86ee7b650 100644 --- a/src/tests/test_x509_path.cpp +++ b/src/tests/test_x509_path.cpp @@ -1,5 +1,6 @@ /* * (C) 2006,2011,2012,2014,2015 Jack Lloyd +* (C) 2022 René Meusel, Rohde & Schwarz Cybersecurity * * Botan is released under the Simplified BSD License (see license.txt) */ @@ -68,6 +69,18 @@ std::map read_results(const std::string& results_file, return m; } +std::set flatten(const Botan::CertificatePathStatusCodes& codes) + { + std::set result; + + for(const auto& statuses : codes) + { + result.insert(statuses.begin(), statuses.end()); + } + + return result; + } + class X509test_Path_Validation_Tests final : public Test { public: @@ -1079,12 +1092,184 @@ class Path_Validation_With_OCSP_Tests final : public Test return result; } + static Test::Result validate_with_ocsp_with_authorized_responder() + { + Test::Result result("path check with ocsp response from authorized responder certificate"); + Botan::Certificate_Store_In_Memory trusted; + + auto restrictions = Botan::Path_Validation_Restrictions(true, // require revocation info + 110, // minimum key strength + true); // OCSP for all intermediates + + auto ee = load_test_X509_cert("x509/ocsp/bdr.pem"); + auto ca = load_test_X509_cert("x509/ocsp/bdr-int.pem"); + auto trust_root = load_test_X509_cert("x509/ocsp/bdr-root.pem"); + + // These OCSP responses are signed by an authorized OCSP responder + // certificate issued by `ca` and `trust_root` respectively. Note that + // the responder certificates contain the "OCSP No Check" extension, + // meaning that they themselves do not need a revocation check via OCSP. + auto ocsp_ee = load_test_OCSP_resp("x509/ocsp/bdr-ocsp-resp.der"); + auto ocsp_ca = load_test_OCSP_resp("x509/ocsp/bdr-int-ocsp-resp.der"); + + trusted.add_certificate(trust_root); + const std::vector cert_path = { ee, ca, trust_root }; + + auto check_path = [&](const std::chrono::system_clock::time_point valid_time, + const Botan::Certificate_Status_Code expected) + { + const auto path_result = Botan::x509_path_validate(cert_path, restrictions, trusted, "", Botan::Usage_Type::UNSPECIFIED, + valid_time, std::chrono::milliseconds(0), {ocsp_ee, ocsp_ca}); + + return result.confirm(std::string("Status: '") + Botan::to_string(expected) + + "' should match '" + Botan::to_string(path_result.result()) + "'", + path_result.result()==expected); + }; + + check_path(Botan::calendar_point(2022, 9, 18, 16, 30, 0).to_std_timepoint(), + Botan::Certificate_Status_Code::OCSP_NOT_YET_VALID); + check_path(Botan::calendar_point(2022, 9, 19, 16, 30, 0).to_std_timepoint(), + Botan::Certificate_Status_Code::OK); + check_path(Botan::calendar_point(2022, 9, 20, 16, 30, 0).to_std_timepoint(), + Botan::Certificate_Status_Code::OCSP_HAS_EXPIRED); + + return result; + } + + static Test::Result validate_with_ocsp_with_authorized_responder_without_keyusage() + { + Test::Result result("path check with ocsp response from authorized responder certificate (without sufficient key usage)"); + Botan::Certificate_Store_In_Memory trusted; + + auto restrictions = Botan::Path_Validation_Restrictions(true, // require revocation info + 110, // minimum key strength + false); // OCSP for all intermediates + + // See `src/scripts/mychain_creater.sh` if you need to recreate those + auto ee = load_test_X509_cert("x509/ocsp/mychain_ee.pem"); + auto ca = load_test_X509_cert("x509/ocsp/mychain_int.pem"); + auto trust_root = load_test_X509_cert("x509/ocsp/mychain_root.pem"); + + auto ocsp_ee_delegate = load_test_OCSP_resp("x509/ocsp/mychain_ocsp_for_ee_delegate_signed.der").value(); + auto ocsp_ee_delegate_malformed = load_test_OCSP_resp("x509/ocsp/mychain_ocsp_for_ee_delegate_signed_malformed.der").value(); + + trusted.add_certificate(trust_root); + const std::vector cert_path = { ee, ca, trust_root }; + + auto check_path = [&](const std::chrono::system_clock::time_point valid_time, + const Botan::OCSP::Response& ocsp_ee, + const Botan::Certificate_Status_Code expected, + const std::optional also_expected = std::nullopt) + { + const auto path_result = Botan::x509_path_validate(cert_path, restrictions, trusted, "", Botan::Usage_Type::UNSPECIFIED, + valid_time, std::chrono::milliseconds(0), {ocsp_ee}); + + result.test_is_eq("should result in expected validation status code", + path_result.result(), expected); + if(also_expected) + { + result.confirm("Secondary error is also present", flatten(path_result.all_statuses()).count(also_expected.value()) > 0); + } + }; + + check_path( + Botan::calendar_point(2022, 9, 22, 23, 30, 0).to_std_timepoint(), + ocsp_ee_delegate, + Botan::Certificate_Status_Code::VERIFIED); + check_path( + Botan::calendar_point(2022, 10, 8, 23, 30, 0).to_std_timepoint(), + ocsp_ee_delegate, + Botan::Certificate_Status_Code::CERT_HAS_EXPIRED, + Botan::Certificate_Status_Code::OCSP_ISSUER_NOT_TRUSTED); + check_path( + Botan::calendar_point(2022, 9, 22, 23, 30, 0).to_std_timepoint(), + ocsp_ee_delegate_malformed, + Botan::Certificate_Status_Code::OCSP_RESPONSE_MISSING_KEYUSAGE, + Botan::Certificate_Status_Code::OCSP_ISSUER_NOT_TRUSTED); + + return result; + } + + static Test::Result validate_with_forged_ocsp_using_self_signed_cert() + { + Test::Result result("path check with forged ocsp using self-signed certificate"); + Botan::Certificate_Store_In_Memory trusted; + + auto restrictions = Botan::Path_Validation_Restrictions(true, // require revocation info + 110, // minimum key strength + false); // OCSP for all intermediates + + auto ee = load_test_X509_cert("x509/ocsp/randombit.pem"); + auto ca = load_test_X509_cert("x509/ocsp/letsencrypt.pem"); + auto trust_root = load_test_X509_cert("x509/ocsp/identrust.pem"); + trusted.add_certificate(trust_root); + + const std::vector cert_path = { ee, ca, trust_root }; + + auto check_path = [&](const std::string &forged_ocsp, + const Botan::Certificate_Status_Code expected, + const Botan::Certificate_Status_Code also_expected) + { + auto ocsp = load_test_OCSP_resp(forged_ocsp); + const auto path_result = Botan::x509_path_validate(cert_path, restrictions, trusted, "", Botan::Usage_Type::UNSPECIFIED, + Botan::calendar_point(2016, 11, 18, 12, 30, 0).to_std_timepoint(), std::chrono::milliseconds(0), {ocsp}); + + result.test_is_eq("Path validation with forged OCSP response should fail with", + path_result.result(), expected); + result.confirm("Secondary error is also present", flatten(path_result.all_statuses()).count(also_expected) > 0); + result.test_note(std::string("Failed with: ") + Botan::to_string(path_result.result())); + }; + + // In both cases the path validation should detect the forged OCSP + // response and generate an appropriate error. By no means it should + // follow the unauthentic OCSP response. + check_path("x509/ocsp/randombit_ocsp_forged_valid.der", Botan::Certificate_Status_Code::CERT_ISSUER_NOT_FOUND, Botan::Certificate_Status_Code::OCSP_ISSUER_NOT_TRUSTED); + check_path("x509/ocsp/randombit_ocsp_forged_revoked.der", Botan::Certificate_Status_Code::CERT_ISSUER_NOT_FOUND, Botan::Certificate_Status_Code::OCSP_ISSUER_NOT_TRUSTED); + + return result; + } + + static Test::Result validate_with_ocsp_self_signed_by_intermediate_cert() + { + Test::Result result("path check with ocsp response for intermediate that is (maliciously) self-signed by the intermediate"); + Botan::Certificate_Store_In_Memory trusted; + + auto restrictions = Botan::Path_Validation_Restrictions(true, // require revocation info + 110, // minimum key strength + true); // OCSP for all intermediates + + // See `src/scripts/mychain_creater.sh` if you need to recreate those + auto ee = load_test_X509_cert("x509/ocsp/mychain_ee.pem"); + auto ca = load_test_X509_cert("x509/ocsp/mychain_int.pem"); + auto trust_root = load_test_X509_cert("x509/ocsp/mychain_root.pem"); + + // this OCSP response for EE is valid (signed by intermediate cert) + auto ocsp_ee = load_test_OCSP_resp("x509/ocsp/mychain_ocsp_for_ee.der"); + + // this OCSP response for Intermediate is malicious (signed by intermediate itself) + auto ocsp_ca = load_test_OCSP_resp("x509/ocsp/mychain_ocsp_for_int_self_signed.der"); + + trusted.add_certificate(trust_root); + const std::vector cert_path = { ee, ca, trust_root }; + + const auto path_result = Botan::x509_path_validate(cert_path, restrictions, trusted, "", Botan::Usage_Type::UNSPECIFIED, + Botan::calendar_point(2022, 9, 22, 22, 30, 0).to_std_timepoint(), std::chrono::milliseconds(0), {ocsp_ee, ocsp_ca}); + result.confirm("should reject intermediate OCSP response", path_result.result() == Botan::Certificate_Status_Code::OCSP_ISSUER_NOT_FOUND); + result.test_note(std::string("Failed with: ") + Botan::to_string(path_result.result())); + + return result; + } + std::vector run() override { return {validate_with_ocsp_with_next_update_without_max_age(), validate_with_ocsp_with_next_update_with_max_age(), validate_with_ocsp_without_next_update_without_max_age(), - validate_with_ocsp_without_next_update_with_max_age()}; + validate_with_ocsp_without_next_update_with_max_age(), + validate_with_ocsp_with_authorized_responder(), + validate_with_ocsp_with_authorized_responder_without_keyusage(), + validate_with_forged_ocsp_using_self_signed_cert(), + validate_with_ocsp_self_signed_by_intermediate_cert()}; } }; @@ -1167,7 +1352,7 @@ class XMSS_Path_Validation_Tests final : public Test auto valid_time = Botan::calendar_point(2019, 10, 8, 4, 45, 0).to_std_timepoint(); auto status = Botan::PKIX::overall_status(Botan::PKIX::check_chain(cert_path, valid_time, - "", Botan::Usage_Type::UNSPECIFIED, restrictions.minimum_key_strength(), restrictions.trusted_hashes())); + "", Botan::Usage_Type::UNSPECIFIED, restrictions)); result.test_eq("Cert validation status", Botan::to_string(status), "Verified"); return result; diff --git a/src/tests/tests.h b/src/tests/tests.h index 98a7a07ced3..0ef4c4b6535 100644 --- a/src/tests/tests.h +++ b/src/tests/tests.h @@ -134,6 +134,41 @@ class Test_Options bool m_abort_on_first_fail; }; +namespace detail { + +template +constexpr bool has_Botan_to_string = false; +template +constexpr bool has_Botan_to_string< + T, + std::void_t()))> +> = true; + +template +constexpr bool has_std_to_string = false; +template +constexpr bool has_std_to_string< + T, + std::void_t()))> +> = true; + +template +constexpr bool has_ostream_operator = false; +template +constexpr bool has_ostream_operator< + T, + std::void_t(), std::declval()))> +> = true; + +template +struct is_optional : std::false_type { }; +template +struct is_optional> : std::true_type { }; +template +constexpr bool is_optional_v = is_optional::value; + +} // namespace detail + /** * A code location consisting of the source file path and a line */ @@ -305,7 +340,7 @@ class Test } else { - out << " produced unexpected result '" << produced << "' expected '" << expected << "'"; + out << " produced unexpected result '" << to_string(produced) << "' expected '" << to_string(expected) << "'"; return test_failure(out.str()); } } @@ -542,6 +577,26 @@ class Test void set_code_location(CodeLocation where) { m_where = std::move(where); } const std::optional& code_location() const { return m_where; } + private: + template + std::string to_string(const T& v) + { + if constexpr(detail::is_optional_v) + return (v.has_value()) ? to_string(v.value()) : std::string("std::nullopt"); + else if constexpr(detail::has_Botan_to_string) + return Botan::to_string(v); + else if constexpr(detail::has_ostream_operator) + { + std::ostringstream oss; + oss << v; + return oss.str(); + } + else if constexpr(detail::has_std_to_string) + return std::to_string(v); + else + return ""; + } + private: std::string m_who; std::optional m_where;