diff options
author | Jack Lloyd <[email protected]> | 2018-03-04 07:49:40 -0500 |
---|---|---|
committer | Jack Lloyd <[email protected]> | 2018-03-04 07:49:40 -0500 |
commit | d983f6c8fa17b155873c446eb09f522247d7030f (patch) | |
tree | d66216400370b767d3bd34a16dc2636645d8cfd3 /src | |
parent | 4a1b5ceee146e314a1a65d789eba7a1750633cf1 (diff) | |
parent | 66f2068101325b904d317376c59389ad4b57408b (diff) |
Merge GH #1470 Use soft fail for OCSP
Diffstat (limited to 'src')
-rw-r--r-- | src/lib/x509/cert_status.cpp | 4 | ||||
-rw-r--r-- | src/lib/x509/cert_status.h | 2 | ||||
-rw-r--r-- | src/lib/x509/ocsp.cpp | 16 | ||||
-rw-r--r-- | src/lib/x509/ocsp.h | 8 | ||||
-rw-r--r-- | src/lib/x509/x509path.cpp | 50 | ||||
-rw-r--r-- | src/lib/x509/x509path.h | 3 | ||||
-rw-r--r-- | src/tests/test_ocsp.cpp | 33 | ||||
-rw-r--r-- | src/tests/test_x509_path.cpp | 37 |
8 files changed, 132 insertions, 21 deletions
diff --git a/src/lib/x509/cert_status.cpp b/src/lib/x509/cert_status.cpp index 2c0e74d36..79bcd1b07 100644 --- a/src/lib/x509/cert_status.cpp +++ b/src/lib/x509/cert_status.cpp @@ -26,6 +26,10 @@ const char* to_string(Certificate_Status_Code code) return "Certificate serial number is negative"; case Certificate_Status_Code::DN_TOO_LONG: return "Distinguished name too long"; + case Certificate_Status_Code::OSCP_NO_REVOCATION_URL: + return "OCSP URL not available"; + case Certificate_Status_Code::OSCP_SERVER_NOT_AVAILABLE: + return "OSCP server not available"; case Certificate_Status_Code::NO_REVOCATION_DATA: return "No revocation data"; diff --git a/src/lib/x509/cert_status.h b/src/lib/x509/cert_status.h index 76dc9252b..1c3a5de89 100644 --- a/src/lib/x509/cert_status.h +++ b/src/lib/x509/cert_status.h @@ -29,6 +29,8 @@ enum class Certificate_Status_Code { FIRST_WARNING_STATUS = 500, CERT_SERIAL_NEGATIVE = 500, DN_TOO_LONG = 501, + OSCP_NO_REVOCATION_URL = 502, + OSCP_SERVER_NOT_AVAILABLE = 503, // Errors FIRST_ERROR_STATUS = 1000, diff --git a/src/lib/x509/ocsp.cpp b/src/lib/x509/ocsp.cpp index 10449b019..751f858a5 100644 --- a/src/lib/x509/ocsp.cpp +++ b/src/lib/x509/ocsp.cpp @@ -87,9 +87,16 @@ std::string Request::base64_encode() const return Botan::base64_encode(BER_encode()); } +Response::Response(Certificate_Status_Code status) + { + 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_cons(SEQUENCE); size_t resp_status = 0; @@ -143,6 +150,9 @@ 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; + try { std::unique_ptr<Public_Key> pub_key(issuer.subject_public_key()); @@ -172,6 +182,9 @@ Certificate_Status_Code Response::verify_signature(const X509_Certificate& issue Certificate_Status_Code Response::check_signature(const std::vector<Certificate_Store*>& trusted_roots, const std::vector<std::shared_ptr<const X509_Certificate>>& ee_cert_path) const { + if (m_responses.empty()) + return m_dummy_response_status; + std::shared_ptr<const X509_Certificate> signing_cert; for(size_t i = 0; i != trusted_roots.size(); ++i) @@ -253,6 +266,9 @@ Certificate_Status_Code Response::status_for(const X509_Certificate& issuer, const X509_Certificate& subject, std::chrono::system_clock::time_point ref_time) const { + if (m_responses.empty()) + return m_dummy_response_status; + for(const auto& response : m_responses) { if(response.certid().is_id_for(issuer, subject)) diff --git a/src/lib/x509/ocsp.h b/src/lib/x509/ocsp.h index 1b780d63f..884b1c5b3 100644 --- a/src/lib/x509/ocsp.h +++ b/src/lib/x509/ocsp.h @@ -77,6 +77,12 @@ class BOTAN_PUBLIC_API(2,0) Response final Response() = default; /** + * Create a fake OCSP response from a given status code. + * @param status the status code the check functions will return + */ + Response(Certificate_Status_Code status); + + /** * Parses an OCSP response. * @param response_bits response bits received */ @@ -161,6 +167,8 @@ class BOTAN_PUBLIC_API(2,0) Response final std::vector<X509_Certificate> m_certs; std::vector<SingleResponse> m_responses; + + Certificate_Status_Code m_dummy_response_status; }; #if defined(BOTAN_HAS_HTTP_UTIL) diff --git a/src/lib/x509/x509path.cpp b/src/lib/x509/x509path.cpp index aa35a5457..f703bf028 100644 --- a/src/lib/x509/x509path.cpp +++ b/src/lib/x509/x509path.cpp @@ -371,26 +371,34 @@ PKIX::check_ocsp_online(const std::vector<std::shared_ptr<const X509_Certificate if(subject->ocsp_responder() == "") { ocsp_response_futures.emplace_back(std::async(std::launch::deferred, [&]() -> std::shared_ptr<const OCSP::Response> { - throw Exception("No OCSP responder URL set for this certificate"); + return std::make_shared<const OCSP::Response>(Certificate_Status_Code::OSCP_NO_REVOCATION_URL); })); - } - else - { - ocsp_response_futures.emplace_back(std::async(std::launch::async, [&]() -> std::shared_ptr<const OCSP::Response> { - OCSP::Request req(*issuer, BigInt::decode(subject->serial_number())); - - auto http = HTTP::POST_sync(subject->ocsp_responder(), - "application/ocsp-request", - req.BER_encode(), - /*redirects*/1, - timeout); - - http.throw_unless_ok(); - // Check the MIME type? - - return std::make_shared<const OCSP::Response>(http.body()); - })); - } + } + else + { + ocsp_response_futures.emplace_back(std::async(std::launch::async, [&]() -> std::shared_ptr<const OCSP::Response> { + OCSP::Request req(*issuer, BigInt::decode(subject->serial_number())); + + HTTP::Response http; + try + { + http = HTTP::POST_sync(subject->ocsp_responder(), + "application/ocsp-request", + req.BER_encode(), + /*redirects*/1, + timeout); + } + catch(std::exception& e) + { + // log e.what() ? + } + if (http.status_code() != 200) + return std::make_shared<const OCSP::Response>(Certificate_Status_Code::OSCP_SERVER_NOT_AVAILABLE); + // Check the MIME type? + + return std::make_shared<const OCSP::Response>(http.body()); + })); + } } std::vector<std::shared_ptr<const OCSP::Response>> ocsp_responses; @@ -773,7 +781,9 @@ void PKIX::merge_revocation_status(CertificatePathStatusCodes& chain_status, { for(auto&& code : ocsp[i]) { - if(code == Certificate_Status_Code::OCSP_RESPONSE_GOOD) + if(code == Certificate_Status_Code::OCSP_RESPONSE_GOOD || + code == Certificate_Status_Code::OSCP_NO_REVOCATION_URL || // softfail + code == Certificate_Status_Code::OSCP_SERVER_NOT_AVAILABLE) // softfail { had_ocsp = true; } diff --git a/src/lib/x509/x509path.h b/src/lib/x509/x509path.h index 65497b2cc..79ae02a10 100644 --- a/src/lib/x509/x509path.h +++ b/src/lib/x509/x509path.h @@ -204,6 +204,9 @@ class BOTAN_PUBLIC_API(2,0) Path_Validation_Result final * @param ocsp_timeout timeout for OCSP operations, 0 disables OCSP check * @param ocsp_resp additional OCSP responses to consider (eg from peer) * @return result of the path validation +* note: when enabled, OCSP check is softfail by default: if the OCSP server is not +* reachable, Path_Validation_Result::successful_validation() will return true. +* Hardfail OCSP check can be achieve by also calling Path_Validation_Result::no_warnings(). */ Path_Validation_Result BOTAN_PUBLIC_API(2,0) x509_path_validate( const std::vector<X509_Certificate>& end_certs, diff --git a/src/tests/test_ocsp.cpp b/src/tests/test_ocsp.cpp index dc3c6f47e..4b344891e 100644 --- a/src/tests/test_ocsp.cpp +++ b/src/tests/test_ocsp.cpp @@ -11,6 +11,7 @@ #include <botan/x509path.h> #include <botan/certstor.h> #include <botan/calendar.h> + #include <botan/cert_status.h> #include <fstream> #endif @@ -153,6 +154,37 @@ class OCSP_Tests final : public Test return result; } + Test::Result test_response_verification_softfail() + { + Test::Result result("OCSP request softfail check"); + + std::shared_ptr<const Botan::X509_Certificate> ee = load_test_X509_cert("x509/ocsp/randombit.pem"); + std::shared_ptr<const Botan::X509_Certificate> ca = load_test_X509_cert("x509/ocsp/letsencrypt.pem"); + std::shared_ptr<const Botan::X509_Certificate> trust_root = load_test_X509_cert("x509/ocsp/geotrust.pem"); + + const std::vector<std::shared_ptr<const Botan::X509_Certificate>> cert_path = { ee, ca, trust_root }; + + std::shared_ptr<const Botan::OCSP::Response> ocsp = + std::make_shared<const Botan::OCSP::Response>(Botan::Certificate_Status_Code::OSCP_NO_REVOCATION_URL); + + Botan::Certificate_Store_In_Memory certstore; + certstore.add_certificate(trust_root); + + // 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); + + 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::OSCP_NO_REVOCATION_URL)); + } + } + + return result; + } + #if defined(BOTAN_HAS_ONLINE_REVOCATION_CHECKS) Test::Result test_online_request() { @@ -193,6 +225,7 @@ class OCSP_Tests final : public Test results.push_back(test_response_parsing()); results.push_back(test_response_certificate_access()); results.push_back(test_response_verification()); + results.push_back(test_response_verification_softfail()); #if defined(BOTAN_HAS_ONLINE_REVOCATION_CHECKS) if(Test::run_online_tests()) diff --git a/src/tests/test_x509_path.cpp b/src/tests/test_x509_path.cpp index 7a9ff97bc..db74d6208 100644 --- a/src/tests/test_x509_path.cpp +++ b/src/tests/test_x509_path.cpp @@ -113,11 +113,46 @@ class X509test_Path_Validation_Tests final : public Test result.test_eq("test " + filename, path_result.result_string(), expected_result); result.test_eq("test no warnings string", path_result.warnings_string(), ""); - result.confirm("test no warnings", path_result.no_warnings() == true); + result.confirm("test no warnings", path_result.no_warnings()); result.end_timer(); results.push_back(result); } + // test softfail + { + Test::Result result("X509test path validation softfail"); + result.start_timer(); + + // this certificate must not have a OCSP URL + const std::string filename = "ValidAltName.pem"; + std::vector<Botan::X509_Certificate> certs = + load_cert_file(Test::data_file("x509/x509test/" + filename)); + if(certs.empty()) + { + throw Test_Error("Failed to read certs from " + filename); + } + + Botan::Path_Validation_Result path_result = Botan::x509_path_validate( + certs, restrictions, trusted, + "www.tls.test", Botan::Usage_Type::TLS_SERVER_AUTH, + validation_time, + /* activate check_ocsp_online */ std::chrono::milliseconds(1000), {}); + + if(path_result.successful_validation() && path_result.trust_root() != root) + { + path_result = Botan::Path_Validation_Result(Botan::Certificate_Status_Code::CANNOT_ESTABLISH_TRUST); + } + + // certificate verification succeed even if no OCSP URL (softfail) + result.confirm("test success", path_result.successful_validation()); + result.test_eq("test " + filename, path_result.result_string(), "Verified"); + // if softfail, there is warnings + result.confirm("test warnings", !path_result.no_warnings()); + result.test_eq("test warnings string", path_result.warnings_string(), "[0] OCSP URL not available"); + result.end_timer(); + results.push_back(result); + } + return results; } |