aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorJack Lloyd <[email protected]>2018-03-04 07:49:40 -0500
committerJack Lloyd <[email protected]>2018-03-04 07:49:40 -0500
commitd983f6c8fa17b155873c446eb09f522247d7030f (patch)
treed66216400370b767d3bd34a16dc2636645d8cfd3 /src
parent4a1b5ceee146e314a1a65d789eba7a1750633cf1 (diff)
parent66f2068101325b904d317376c59389ad4b57408b (diff)
Merge GH #1470 Use soft fail for OCSP
Diffstat (limited to 'src')
-rw-r--r--src/lib/x509/cert_status.cpp4
-rw-r--r--src/lib/x509/cert_status.h2
-rw-r--r--src/lib/x509/ocsp.cpp16
-rw-r--r--src/lib/x509/ocsp.h8
-rw-r--r--src/lib/x509/x509path.cpp50
-rw-r--r--src/lib/x509/x509path.h3
-rw-r--r--src/tests/test_ocsp.cpp33
-rw-r--r--src/tests/test_x509_path.cpp37
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;
}