// Copyright 2016 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "net/cert/pki/ocsp.h" #include "net/cert/pki/string_util.h" #include "net/cert/pki/test_helpers.h" #include "net/der/encode_values.h" #include "testing/gtest/include/gtest/gtest.h" #include "third_party/boringssl/src/include/openssl/base64.h" #include "third_party/boringssl/src/include/openssl/pool.h" #include "url/gurl.h" namespace net { namespace { constexpr int64_t kOCSPAgeOneWeek = 7 * 24 * 60 * 60; std::string GetFilePath(const std::string& file_name) { return std::string("net/data/ocsp_unittest/") + file_name; } std::shared_ptr ParseCertificate( std::string_view data) { CertErrors errors; return ParsedCertificate::Create( bssl::UniquePtr(CRYPTO_BUFFER_new( reinterpret_cast(data.data()), data.size(), nullptr)), {}, &errors); } struct TestParams { const char* file_name; OCSPRevocationStatus expected_revocation_status; OCSPVerifyResult::ResponseStatus expected_response_status; }; class CheckOCSPTest : public ::testing::TestWithParam {}; const TestParams kTestParams[] = { {"good_response.pem", OCSPRevocationStatus::GOOD, OCSPVerifyResult::PROVIDED}, {"good_response_sha256.pem", OCSPRevocationStatus::GOOD, OCSPVerifyResult::PROVIDED}, {"no_response.pem", OCSPRevocationStatus::UNKNOWN, OCSPVerifyResult::NO_MATCHING_RESPONSE}, {"malformed_request.pem", OCSPRevocationStatus::UNKNOWN, OCSPVerifyResult::ERROR_RESPONSE}, {"bad_status.pem", OCSPRevocationStatus::UNKNOWN, OCSPVerifyResult::PARSE_RESPONSE_ERROR}, {"bad_ocsp_type.pem", OCSPRevocationStatus::UNKNOWN, OCSPVerifyResult::PARSE_RESPONSE_ERROR}, {"bad_signature.pem", OCSPRevocationStatus::UNKNOWN, OCSPVerifyResult::PROVIDED}, {"ocsp_sign_direct.pem", OCSPRevocationStatus::GOOD, OCSPVerifyResult::PROVIDED}, {"ocsp_sign_indirect.pem", OCSPRevocationStatus::GOOD, OCSPVerifyResult::PROVIDED}, {"ocsp_sign_indirect_missing.pem", OCSPRevocationStatus::UNKNOWN, OCSPVerifyResult::PROVIDED}, {"ocsp_sign_bad_indirect.pem", OCSPRevocationStatus::UNKNOWN, OCSPVerifyResult::PROVIDED}, {"ocsp_extra_certs.pem", OCSPRevocationStatus::GOOD, OCSPVerifyResult::PROVIDED}, {"has_version.pem", OCSPRevocationStatus::GOOD, OCSPVerifyResult::PROVIDED}, {"responder_name.pem", OCSPRevocationStatus::GOOD, OCSPVerifyResult::PROVIDED}, {"responder_id.pem", OCSPRevocationStatus::GOOD, OCSPVerifyResult::PROVIDED}, {"has_extension.pem", OCSPRevocationStatus::GOOD, OCSPVerifyResult::PROVIDED}, {"good_response_next_update.pem", OCSPRevocationStatus::GOOD, OCSPVerifyResult::PROVIDED}, {"revoke_response.pem", OCSPRevocationStatus::REVOKED, OCSPVerifyResult::PROVIDED}, {"revoke_response_reason.pem", OCSPRevocationStatus::REVOKED, OCSPVerifyResult::PROVIDED}, {"unknown_response.pem", OCSPRevocationStatus::UNKNOWN, OCSPVerifyResult::PROVIDED}, {"multiple_response.pem", OCSPRevocationStatus::UNKNOWN, OCSPVerifyResult::PROVIDED}, {"other_response.pem", OCSPRevocationStatus::UNKNOWN, OCSPVerifyResult::NO_MATCHING_RESPONSE}, {"has_single_extension.pem", OCSPRevocationStatus::GOOD, OCSPVerifyResult::PROVIDED}, {"has_critical_single_extension.pem", OCSPRevocationStatus::UNKNOWN, OCSPVerifyResult::UNHANDLED_CRITICAL_EXTENSION}, {"has_critical_response_extension.pem", OCSPRevocationStatus::UNKNOWN, OCSPVerifyResult::UNHANDLED_CRITICAL_EXTENSION}, {"has_critical_ct_extension.pem", OCSPRevocationStatus::GOOD, OCSPVerifyResult::PROVIDED}, {"missing_response.pem", OCSPRevocationStatus::UNKNOWN, OCSPVerifyResult::NO_MATCHING_RESPONSE}, }; // Parameterised test name generator for tests depending on RenderTextBackend. struct PrintTestName { std::string operator()(const testing::TestParamInfo& info) const { std::string_view name(info.param.file_name); // Strip ".pem" from the end as GTest names cannot contain period. name.remove_suffix(4); return std::string(name); } }; INSTANTIATE_TEST_SUITE_P(All, CheckOCSPTest, ::testing::ValuesIn(kTestParams), PrintTestName()); TEST_P(CheckOCSPTest, FromFile) { const TestParams& params = GetParam(); std::string ocsp_data; std::string ca_data; std::string cert_data; std::string request_data; const PemBlockMapping mappings[] = { {"OCSP RESPONSE", &ocsp_data}, {"CA CERTIFICATE", &ca_data}, {"CERTIFICATE", &cert_data}, {"OCSP REQUEST", &request_data}, }; ASSERT_TRUE(ReadTestDataFromPemFile(GetFilePath(params.file_name), mappings)); // Mar 5 00:00:00 2017 GMT int64_t kVerifyTime = 1488672000; // Test that CheckOCSP() works. OCSPVerifyResult::ResponseStatus response_status; OCSPRevocationStatus revocation_status = CheckOCSP(ocsp_data, cert_data, ca_data, kVerifyTime, kOCSPAgeOneWeek, &response_status); EXPECT_EQ(params.expected_revocation_status, revocation_status); EXPECT_EQ(params.expected_response_status, response_status); // Check that CreateOCSPRequest() works. std::shared_ptr cert = ParseCertificate(cert_data); ASSERT_TRUE(cert); std::shared_ptr issuer = ParseCertificate(ca_data); ASSERT_TRUE(issuer); std::vector encoded_request; ASSERT_TRUE(CreateOCSPRequest(cert.get(), issuer.get(), &encoded_request)); EXPECT_EQ(der::Input(encoded_request.data(), encoded_request.size()), der::Input(&request_data)); } std::string_view kGetURLTestParams[] = { "http://www.example.com/", "http://www.example.com/path/", "http://www.example.com/path", "http://www.example.com/path?query" "http://user:pass@www.example.com/path?query", }; class CreateOCSPGetURLTest : public ::testing::TestWithParam { }; INSTANTIATE_TEST_SUITE_P(All, CreateOCSPGetURLTest, ::testing::ValuesIn(kGetURLTestParams)); TEST_P(CreateOCSPGetURLTest, Basic) { std::string ca_data; std::string cert_data; std::string request_data; const PemBlockMapping mappings[] = { {"CA CERTIFICATE", &ca_data}, {"CERTIFICATE", &cert_data}, {"OCSP REQUEST", &request_data}, }; // Load one of the test files. (Doesn't really matter which one as // constructing the DER is tested elsewhere). ASSERT_TRUE( ReadTestDataFromPemFile(GetFilePath("good_response.pem"), mappings)); std::shared_ptr cert = ParseCertificate(cert_data); ASSERT_TRUE(cert); std::shared_ptr issuer = ParseCertificate(ca_data); ASSERT_TRUE(issuer); GURL url = CreateOCSPGetURL(cert.get(), issuer.get(), GetParam()); // Try to extract the encoded data and compare against |request_data|. // // A known answer output test would be better as this just reverses the logic // from the implementation file. std::string b64 = url.spec().substr(GetParam().size() + 1); // Hex un-escape the data. b64 = net::string_util::FindAndReplace(b64, "%2B", "+"); b64 = net::string_util::FindAndReplace(b64, "%2F", "/"); b64 = net::string_util::FindAndReplace(b64, "%3D", "="); // Base64 decode the data. size_t len; EXPECT_TRUE(EVP_DecodedLength(&len, b64.size())); std::vector decoded(len); EXPECT_TRUE(EVP_DecodeBase64(decoded.data(), &len, len, reinterpret_cast(b64.data()), b64.size())); std::string decoded_string(decoded.begin(), decoded.begin() + len); EXPECT_EQ(request_data, decoded_string); } } // namespace } // namespace net