diff options
author | Jack Lloyd <[email protected]> | 2019-06-14 10:16:01 -0400 |
---|---|---|
committer | Jack Lloyd <[email protected]> | 2019-06-14 10:16:01 -0400 |
commit | 94e6668396d3c80818f46900bf76b2723a552300 (patch) | |
tree | 34fbb0b1f94de9f8b49c337a35308b1dad370c32 | |
parent | 2d94a980cc69ff2ccd0374ca31dd41d2208b2c65 (diff) | |
parent | b291f968ea17ce6fb998cc2ea0ba833984340ff2 (diff) |
Merge GH #1995 Allow setting max OCSP response age during verification
-rw-r--r-- | src/lib/x509/x509path.cpp | 21 | ||||
-rw-r--r-- | src/lib/x509/x509path.h | 46 | ||||
-rw-r--r-- | src/tests/test_x509_path.cpp | 181 |
3 files changed, 226 insertions, 22 deletions
diff --git a/src/lib/x509/x509path.cpp b/src/lib/x509/x509path.cpp index a889aa4d4..d0604e1c6 100644 --- a/src/lib/x509/x509path.cpp +++ b/src/lib/x509/x509path.cpp @@ -205,7 +205,7 @@ PKIX::check_ocsp(const std::vector<std::shared_ptr<const X509_Certificate>>& cer 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, - std::chrono::seconds max_age) + std::chrono::seconds max_ocsp_age) { if(cert_path.empty()) throw Invalid_Argument("PKIX::check_ocsp cert_path empty"); @@ -228,7 +228,7 @@ PKIX::check_ocsp(const std::vector<std::shared_ptr<const X509_Certificate>>& cer 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, max_age); + Certificate_Status_Code ocsp_status = ocsp_responses.at(i)->status_for(*ca, *subject, ref_time, max_ocsp_age); status.insert(ocsp_status); } else @@ -351,7 +351,8 @@ PKIX::check_ocsp_online(const std::vector<std::shared_ptr<const X509_Certificate const std::vector<Certificate_Store*>& trusted_certstores, std::chrono::system_clock::time_point ref_time, std::chrono::milliseconds timeout, - bool ocsp_check_intermediate_CAs) + bool ocsp_check_intermediate_CAs, + std::chrono::seconds max_ocsp_age) { if(cert_path.empty()) throw Invalid_Argument("PKIX::check_ocsp_online cert_path empty"); @@ -410,7 +411,7 @@ PKIX::check_ocsp_online(const std::vector<std::shared_ptr<const X509_Certificate ocsp_responses.push_back(ocsp_response_futures[i].get()); } - return PKIX::check_ocsp(cert_path, ocsp_responses, trusted_certstores, ref_time); + return PKIX::check_ocsp(cert_path, ocsp_responses, trusted_certstores, ref_time, max_ocsp_age); } CertificatePathStatusCodes @@ -876,7 +877,7 @@ Path_Validation_Result x509_path_validate( if(ocsp_resp.size() > 0) { - ocsp_status = PKIX::check_ocsp(cert_path, ocsp_resp, trusted_roots, ref_time); + ocsp_status = PKIX::check_ocsp(cert_path, ocsp_resp, trusted_roots, ref_time, restrictions.max_ocsp_age()); } if(ocsp_status.empty() && ocsp_timeout != std::chrono::milliseconds(0)) @@ -958,14 +959,16 @@ Path_Validation_Result x509_path_validate( } Path_Validation_Restrictions::Path_Validation_Restrictions(bool require_rev, - size_t key_strength, - bool ocsp_intermediates) : + size_t key_strength, + bool ocsp_intermediates, + std::chrono::seconds max_ocsp_age) : m_require_revocation_information(require_rev), m_ocsp_all_intermediates(ocsp_intermediates), - m_minimum_key_strength(key_strength) + m_minimum_key_strength(key_strength), + m_max_ocsp_age(max_ocsp_age) { if(key_strength <= 80) - m_trusted_hashes.insert("SHA-160"); + { m_trusted_hashes.insert("SHA-160"); } m_trusted_hashes.insert("SHA-224"); m_trusted_hashes.insert("SHA-256"); diff --git a/src/lib/x509/x509path.h b/src/lib/x509/x509path.h index 3e457b970..ed2cbd592 100644 --- a/src/lib/x509/x509path.h +++ b/src/lib/x509/x509path.h @@ -47,10 +47,13 @@ class BOTAN_PUBLIC_API(2,0) Path_Validation_Restrictions final * 128 bit strength requires ~3k bit RSA or P-256 * @param ocsp_all_intermediates Make OCSP requests for all CAs as * 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 */ Path_Validation_Restrictions(bool require_rev = false, size_t minimum_key_strength = 110, - bool ocsp_all_intermediates = false); + bool ocsp_all_intermediates = false, + std::chrono::seconds max_ocsp_age = std::chrono::seconds::zero()); /** * @param require_rev if true, revocation information is required @@ -62,15 +65,19 @@ class BOTAN_PUBLIC_API(2,0) Path_Validation_Restrictions final * @param trusted_hashes a set of trusted hashes. Any signatures * created using a hash other than one of these will be * rejected. + * @param max_ocsp_age maximum age of OCSP responses w/o next_update. + * If zero, there is no maximum age */ Path_Validation_Restrictions(bool require_rev, size_t minimum_key_strength, bool ocsp_all_intermediates, - const std::set<std::string>& trusted_hashes) : + const std::set<std::string>& trusted_hashes, + std::chrono::seconds max_ocsp_age = std::chrono::seconds::zero()) : 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_minimum_key_strength(minimum_key_strength), + m_max_ocsp_age(max_ocsp_age) {} /** * @return whether revocation information is required @@ -97,11 +104,19 @@ class BOTAN_PUBLIC_API(2,0) Path_Validation_Restrictions final size_t minimum_key_strength() const { return m_minimum_key_strength; } + /** + * @return maximum age of OCSP responses w/o next_update. + * If zero, there is no maximum age + */ + std::chrono::seconds max_ocsp_age() const + { return m_max_ocsp_age; } + private: bool m_require_revocation_information; bool m_ocsp_all_intermediates; std::set<std::string> m_trusted_hashes; size_t m_minimum_key_strength; + std::chrono::seconds m_max_ocsp_age; }; /** @@ -346,14 +361,16 @@ BOTAN_PUBLIC_API(2,0) check_chain(const std::vector<std::shared_ptr<const X509_C * @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 * @return revocation status */ CertificatePathStatusCodes -BOTAN_PUBLIC_API(2,0) 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::chrono::seconds max_age = std::chrono::seconds::zero()); +BOTAN_PUBLIC_API(2, 0) 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::chrono::seconds max_ocsp_age = std::chrono::seconds::zero()); /** * Check CRLs for revocation information @@ -396,14 +413,17 @@ BOTAN_PUBLIC_API(2,0) check_crl(const std::vector<std::shared_ptr<const X509_Cer * 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 * @return revocation status */ CertificatePathStatusCodes -BOTAN_PUBLIC_API(2,0) 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); +BOTAN_PUBLIC_API(2, 0) 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::chrono::seconds max_ocsp_age = std::chrono::seconds::zero()); /** * Check CRL using online (HTTP) access. Current version creates a thread and diff --git a/src/tests/test_x509_path.cpp b/src/tests/test_x509_path.cpp index 71118ab2f..8715f3f54 100644 --- a/src/tests/test_x509_path.cpp +++ b/src/tests/test_x509_path.cpp @@ -714,6 +714,187 @@ std::vector<Test::Result> BSI_Path_Validation_Tests::run() BOTAN_REGISTER_TEST("x509_path_bsi", BSI_Path_Validation_Tests); +class Path_Validation_With_OCSP_Tests final : public Test + { + public: + Botan::X509_Certificate load_test_X509_cert(const std::string& path) + { + return Botan::X509_Certificate(Test::data_file(path)); + } + + std::shared_ptr<const Botan::OCSP::Response> load_test_OCSP_resp(const std::string& path) + { + return std::make_shared<const Botan::OCSP::Response>(Test::read_binary_data_file(path)); + } + + Test::Result validate_with_ocsp_with_next_update_without_max_age() + { + Test::Result result("path check with ocsp with next_update w/o max_age"); + Botan::Certificate_Store_In_Memory trusted; + + auto restrictions = Botan::Path_Validation_Restrictions(false, 110, false); + + 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<Botan::X509_Certificate> cert_path = { ee, ca, trust_root }; + + std::shared_ptr<const Botan::OCSP::Response> ocsp = load_test_OCSP_resp("x509/ocsp/randombit_ocsp.der"); + + 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}); + + 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(2016, 11, 11, 12, 30, 0).to_std_timepoint(), + Botan::Certificate_Status_Code::OCSP_NOT_YET_VALID); + check_path(Botan::calendar_point(2016, 11, 18, 12, 30, 0).to_std_timepoint(), + Botan::Certificate_Status_Code::OK); + check_path(Botan::calendar_point(2016, 11, 20, 8, 30, 0).to_std_timepoint(), + Botan::Certificate_Status_Code::OK); + check_path(Botan::calendar_point(2016, 11, 28, 8, 30, 0).to_std_timepoint(), + Botan::Certificate_Status_Code::OCSP_HAS_EXPIRED); + + return result; + } + + Test::Result validate_with_ocsp_with_next_update_with_max_age() + { + Test::Result result("path check with ocsp with next_update with max_age"); + Botan::Certificate_Store_In_Memory trusted; + + auto restrictions = Botan::Path_Validation_Restrictions(false, 110, false, + std::chrono::minutes(59)); + + 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<Botan::X509_Certificate> cert_path = { ee, ca, trust_root }; + + std::shared_ptr<const Botan::OCSP::Response> ocsp = load_test_OCSP_resp("x509/ocsp/randombit_ocsp.der"); + + 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}); + + 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(2016, 11, 11, 12, 30, 0).to_std_timepoint(), + Botan::Certificate_Status_Code::OCSP_NOT_YET_VALID); + check_path(Botan::calendar_point(2016, 11, 18, 12, 30, 0).to_std_timepoint(), + Botan::Certificate_Status_Code::OK); + check_path(Botan::calendar_point(2016, 11, 20, 8, 30, 0).to_std_timepoint(), + Botan::Certificate_Status_Code::OK); + check_path(Botan::calendar_point(2016, 11, 28, 8, 30, 0).to_std_timepoint(), + Botan::Certificate_Status_Code::OCSP_HAS_EXPIRED); + + return result; + } + + Test::Result validate_with_ocsp_without_next_update_without_max_age() + { + Test::Result result("path check with ocsp w/o next_update w/o max_age"); + Botan::Certificate_Store_In_Memory trusted; + + auto restrictions = Botan::Path_Validation_Restrictions(false, 110, false); + + auto ee = load_test_X509_cert("x509/ocsp/patrickschmidt.pem"); + auto ca = load_test_X509_cert("x509/ocsp/bdrive_encryption.pem"); + auto trust_root = load_test_X509_cert("x509/ocsp/bdrive_root.pem"); + + trusted.add_certificate(trust_root); + + const std::vector<Botan::X509_Certificate> cert_path = { ee, ca, trust_root }; + + std::shared_ptr<const Botan::OCSP::Response> ocsp = load_test_OCSP_resp("x509/ocsp/patrickschmidt_ocsp.der"); + + 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}); + + 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(2019, 5, 28, 7, 0, 0).to_std_timepoint(), + Botan::Certificate_Status_Code::OCSP_NOT_YET_VALID); + check_path(Botan::calendar_point(2019, 5, 28, 7, 30, 0).to_std_timepoint(), + Botan::Certificate_Status_Code::OK); + check_path(Botan::calendar_point(2019, 5, 28, 8, 0, 0).to_std_timepoint(), + Botan::Certificate_Status_Code::OK); + + return result; + } + + Test::Result validate_with_ocsp_without_next_update_with_max_age() + { + Test::Result result("path check with ocsp w/o next_update with max_age"); + Botan::Certificate_Store_In_Memory trusted; + + auto restrictions = Botan::Path_Validation_Restrictions(false, 110, false, + std::chrono::minutes(59)); + + auto ee = load_test_X509_cert("x509/ocsp/patrickschmidt.pem"); + auto ca = load_test_X509_cert("x509/ocsp/bdrive_encryption.pem"); + auto trust_root = load_test_X509_cert("x509/ocsp/bdrive_root.pem"); + + trusted.add_certificate(trust_root); + + const std::vector<Botan::X509_Certificate> cert_path = { ee, ca, trust_root }; + + std::shared_ptr<const Botan::OCSP::Response> ocsp = load_test_OCSP_resp("x509/ocsp/patrickschmidt_ocsp.der"); + + 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}); + + 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(2019, 5, 28, 7, 0, 0).to_std_timepoint(), + Botan::Certificate_Status_Code::OCSP_NOT_YET_VALID); + check_path(Botan::calendar_point(2019, 5, 28, 7, 30, 0).to_std_timepoint(), + Botan::Certificate_Status_Code::OK); + check_path(Botan::calendar_point(2019, 5, 28, 8, 0, 0).to_std_timepoint(), + Botan::Certificate_Status_Code::OCSP_IS_TOO_OLD); + + return result; + } + + std::vector<Test::Result> 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()}; + } + + }; + +BOTAN_REGISTER_TEST("x509_path_with_ocsp", Path_Validation_With_OCSP_Tests); + #endif } |