| Index: net/http/transport_security_state_unittest.cc
|
| diff --git a/net/http/transport_security_state_unittest.cc b/net/http/transport_security_state_unittest.cc
|
| index 20c5619555b49809c75dd4c8fe26238f6fedf622..f0a49814cb353ea022ecc1eee12c676a034d43a5 100644
|
| --- a/net/http/transport_security_state_unittest.cc
|
| +++ b/net/http/transport_security_state_unittest.cc
|
| @@ -240,6 +240,110 @@ void CheckHPKPReport(
|
| validated_certificate_chain, report_validated_certificate_chain));
|
| }
|
|
|
| +// Checks the following hold for |report| such that it is a valid Expect-Staple
|
| +// report:
|
| +// 1. |report| is a JSON dictionary.
|
| +// 2. The "hostname" and "port" fields match |host_port_pair|.
|
| +// 3. The "response-status" field matches |response_status|
|
| +// 4. The "ocsp-response" field is a base64-encoded verson of |ocsp_response|,
|
| +// and is not present when |ocsp_response| is empty.
|
| +// 5. The "cert-status" field matches |cert_status|, and is not present when
|
| +// |cert_status| is empty.
|
| +// 6. The "validated-chain" and "serverd-chain" fields match those in
|
| +// |ssl_info|, and are only present when |ssl_info.is_issued_by_known_root|
|
| +// is true.
|
| +void CheckSerializedExpectStapleReport(const std::string& report,
|
| + const HostPortPair& host_port_pair,
|
| + const SSLInfo& ssl_info,
|
| + const std::string& ocsp_response,
|
| + const std::string& response_status,
|
| + const std::string& cert_status) {
|
| + std::unique_ptr<base::Value> value(base::JSONReader::Read(report));
|
| + ASSERT_TRUE(value);
|
| + ASSERT_TRUE(value->IsType(base::Value::TYPE_DICTIONARY));
|
| +
|
| + base::DictionaryValue* report_dict;
|
| + ASSERT_TRUE(value->GetAsDictionary(&report_dict));
|
| +
|
| + std::string report_hostname;
|
| + EXPECT_TRUE(report_dict->GetString("hostname", &report_hostname));
|
| + EXPECT_EQ(host_port_pair.host(), report_hostname);
|
| +
|
| + int report_port;
|
| + EXPECT_TRUE(report_dict->GetInteger("port", &report_port));
|
| + EXPECT_EQ(host_port_pair.port(), report_port);
|
| +
|
| + std::string report_response_status;
|
| + EXPECT_TRUE(
|
| + report_dict->GetString("response-status", &report_response_status));
|
| + EXPECT_EQ(response_status, report_response_status);
|
| +
|
| + std::string report_ocsp_response;
|
| + bool has_ocsp_response =
|
| + report_dict->GetString("ocsp-response", &report_ocsp_response);
|
| +
|
| + if (!ocsp_response.empty()) {
|
| + EXPECT_TRUE(has_ocsp_response);
|
| + std::string decoded_ocsp_response;
|
| + EXPECT_TRUE(
|
| + base::Base64Decode(report_ocsp_response, &decoded_ocsp_response));
|
| + EXPECT_EQ(ocsp_response, decoded_ocsp_response);
|
| + } else {
|
| + EXPECT_FALSE(has_ocsp_response);
|
| + }
|
| +
|
| + std::string report_cert_status;
|
| + bool has_cert_status =
|
| + report_dict->GetString("cert-status", &report_cert_status);
|
| + if (!cert_status.empty()) {
|
| + EXPECT_TRUE(has_cert_status);
|
| + EXPECT_EQ(cert_status, report_cert_status);
|
| + } else {
|
| + EXPECT_FALSE(has_cert_status);
|
| + }
|
| +
|
| + base::ListValue* report_served_certificate_chain;
|
| + bool has_served_chain = report_dict->GetList(
|
| + "served-certificate-chain", &report_served_certificate_chain);
|
| +
|
| + base::ListValue* report_validated_certificate_chain;
|
| + bool has_validated_chain = report_dict->GetList(
|
| + "validated-certificate-chain", &report_validated_certificate_chain);
|
| +
|
| + if (ssl_info.is_issued_by_known_root) {
|
| + EXPECT_TRUE(has_served_chain);
|
| + EXPECT_NO_FATAL_FAILURE(CompareCertificateChainWithList(
|
| + ssl_info.unverified_cert, report_served_certificate_chain));
|
| +
|
| + EXPECT_TRUE(has_validated_chain);
|
| + EXPECT_NO_FATAL_FAILURE(CompareCertificateChainWithList(
|
| + ssl_info.cert, report_validated_certificate_chain));
|
| + } else {
|
| + EXPECT_FALSE(has_served_chain);
|
| + EXPECT_FALSE(has_validated_chain);
|
| + }
|
| +}
|
| +
|
| +// Set up |state| for ExpectStaple, call CheckExpectStaple(), and verify the
|
| +// serialized report caught by |reporter|.
|
| +void CheckExpectStapleReport(TransportSecurityState* state,
|
| + MockCertificateReportSender* reporter,
|
| + const SSLInfo& ssl_info,
|
| + const std::string& ocsp_response,
|
| + const std::string& response_status,
|
| + const std::string& cert_status) {
|
| + // Expect-Staple is preload list based, so we use the baked-in test hostname
|
| + // from the list ("preloaded-expect-staple.badssl.com").
|
| + HostPortPair host_port(kExpectStapleStaticHostname, 443);
|
| + state->SetReportSender(reporter);
|
| + state->CheckExpectStaple(host_port, ssl_info, ocsp_response);
|
| + EXPECT_EQ(GURL(kExpectStapleStaticReportURI), reporter->latest_report_uri());
|
| + std::string serialized_report = reporter->latest_report();
|
| + EXPECT_NO_FATAL_FAILURE(CheckSerializedExpectStapleReport(
|
| + serialized_report, host_port, ssl_info, ocsp_response, response_status,
|
| + cert_status));
|
| +}
|
| +
|
| } // namespace
|
|
|
| class TransportSecurityStateTest : public testing::Test {
|
| @@ -1902,6 +2006,190 @@ TEST_F(TransportSecurityStateTest, ExpectCTReporter) {
|
| EXPECT_EQ(GURL(kExpectCTStaticReportURI), reporter.report_uri());
|
| }
|
|
|
| +static const struct ExpectStapleErrorResponseData {
|
| + OCSPVerifyResult::ResponseStatus response_status;
|
| + std::string response_status_string;
|
| +} kExpectStapleReportData[] = {
|
| + {OCSPVerifyResult::MISSING, "MISSING"},
|
| + {OCSPVerifyResult::ERROR_RESPONSE, "ERROR_RESPONSE"},
|
| + {OCSPVerifyResult::BAD_PRODUCED_AT, "BAD_PRODUCED_AT"},
|
| + {OCSPVerifyResult::NO_MATCHING_RESPONSE, "NO_MATCHING_RESPONSE"},
|
| + {OCSPVerifyResult::INVALID_DATE, "INVALID_DATE"},
|
| + {OCSPVerifyResult::PARSE_RESPONSE_ERROR, "PARSE_RESPONSE_ERROR"},
|
| + {OCSPVerifyResult::PARSE_RESPONSE_DATA_ERROR, "PARSE_RESPONSE_DATA_ERROR"},
|
| +};
|
| +
|
| +class ExpectStapleErrorResponseTest
|
| + : public TransportSecurityStateTest,
|
| + public testing::WithParamInterface<ExpectStapleErrorResponseData> {};
|
| +
|
| +// For every |response_status| indicating an OCSP response was provided, but had
|
| +// some sort of parsing/validation error, test that the ExpectStaple report is
|
| +// serialized correctly.
|
| +TEST_P(ExpectStapleErrorResponseTest, CheckResponseStatusSerialization) {
|
| + TransportSecurityState state;
|
| + TransportSecurityStateTest::EnableStaticExpectStaple(&state);
|
| + MockCertificateReportSender reporter;
|
| + ExpectStapleErrorResponseData test = GetParam();
|
| +
|
| + std::string ocsp_response;
|
| + if (test.response_status != OCSPVerifyResult::MISSING)
|
| + ocsp_response = "dummy_response";
|
| +
|
| + // Two dummy certs to use as the server-sent and validated chains. The
|
| + // contents don't matter.
|
| + scoped_refptr<X509Certificate> cert1 =
|
| + ImportCertFromFile(GetTestCertsDirectory(), "test_mail_google_com.pem");
|
| + scoped_refptr<X509Certificate> cert2 =
|
| + ImportCertFromFile(GetTestCertsDirectory(), "expired_cert.pem");
|
| +
|
| + SSLInfo ssl_info;
|
| + ssl_info.cert = cert1;
|
| + ssl_info.unverified_cert = cert2;
|
| + ssl_info.ocsp_result.response_status = test.response_status;
|
| +
|
| + // Certificate chains should only be included when |is_issued_by_known_root|
|
| + // is true.
|
| + ssl_info.is_issued_by_known_root = true;
|
| + ASSERT_NO_FATAL_FAILURE(
|
| + CheckExpectStapleReport(&state, &reporter, ssl_info, ocsp_response,
|
| + test.response_status_string, std::string()));
|
| +
|
| + // No certificate chains should be included in the report.
|
| + ssl_info.is_issued_by_known_root = false;
|
| + ASSERT_NO_FATAL_FAILURE(
|
| + CheckExpectStapleReport(&state, &reporter, ssl_info, ocsp_response,
|
| + test.response_status_string, std::string()));
|
| +}
|
| +
|
| +INSTANTIATE_TEST_CASE_P(ExpectStaple,
|
| + ExpectStapleErrorResponseTest,
|
| + testing::ValuesIn(kExpectStapleReportData));
|
| +
|
| +static const struct ExpectStapleErrorCertStatusData {
|
| + OCSPRevocationStatus revocation_status;
|
| + std::string cert_status_string;
|
| +} kExpectStapleErrorCertStatusData[] = {
|
| + {OCSPRevocationStatus::REVOKED, "REVOKED"},
|
| + {OCSPRevocationStatus::UNKNOWN, "UNKNOWN"},
|
| +};
|
| +
|
| +class ExpectStapleErrorCertStatusTest
|
| + : public TransportSecurityStateTest,
|
| + public testing::WithParamInterface<ExpectStapleErrorCertStatusData> {};
|
| +
|
| +// Test that |revocation_status| is serialized into the |cert-status| field of
|
| +// the Expect-Staple report whenever |response_status| is PROVIDED and
|
| +// |revocation_status| != GOOD.
|
| +TEST_P(ExpectStapleErrorCertStatusTest, CheckCertStatusSerialization) {
|
| + TransportSecurityState state;
|
| + TransportSecurityStateTest::EnableStaticExpectStaple(&state);
|
| + MockCertificateReportSender reporter;
|
| + ExpectStapleErrorCertStatusData test = GetParam();
|
| + std::string ocsp_response = "dummy_response";
|
| +
|
| + // Two dummy certs to use as the server-sent and validated chains. The
|
| + // contents don't matter.
|
| + scoped_refptr<X509Certificate> cert1 =
|
| + ImportCertFromFile(GetTestCertsDirectory(), "test_mail_google_com.pem");
|
| + scoped_refptr<X509Certificate> cert2 =
|
| + ImportCertFromFile(GetTestCertsDirectory(), "expired_cert.pem");
|
| +
|
| + SSLInfo ssl_info;
|
| + ssl_info.cert = cert1;
|
| + ssl_info.unverified_cert = cert2;
|
| + // |response_status| must be set to PROVIDED for |revocation_status| to have
|
| + // meaning.
|
| + ssl_info.ocsp_result.response_status = OCSPVerifyResult::PROVIDED;
|
| + ssl_info.ocsp_result.revocation_status = test.revocation_status;
|
| +
|
| + // Certificate chains should only be included when |is_issued_by_known_root|
|
| + // is true.
|
| + ssl_info.is_issued_by_known_root = true;
|
| + ASSERT_NO_FATAL_FAILURE(CheckExpectStapleReport(&state, &reporter, ssl_info,
|
| + ocsp_response, "PROVIDED",
|
| + test.cert_status_string));
|
| +
|
| + // No certificate chains should be included in the report.
|
| + ssl_info.is_issued_by_known_root = false;
|
| + ASSERT_NO_FATAL_FAILURE(CheckExpectStapleReport(&state, &reporter, ssl_info,
|
| + ocsp_response, "PROVIDED",
|
| + test.cert_status_string));
|
| +};
|
| +
|
| +INSTANTIATE_TEST_CASE_P(ExpectStaple,
|
| + ExpectStapleErrorCertStatusTest,
|
| + testing::ValuesIn(kExpectStapleErrorCertStatusData));
|
| +
|
| +TEST_F(TransportSecurityStateTest, ExpectStapleDoesNotReportValidStaple) {
|
| + TransportSecurityState state;
|
| + TransportSecurityStateTest::EnableStaticExpectStaple(&state);
|
| + MockCertificateReportSender reporter;
|
| + state.SetReportSender(&reporter);
|
| +
|
| + // Baked-in preloaded Expect-Staple test hosts.
|
| + HostPortPair host_port(kExpectStapleStaticHostname, 443);
|
| +
|
| + // Two dummy certs to use as the server-sent and validated chains. The
|
| + // contents don't matter.
|
| + scoped_refptr<X509Certificate> cert1 =
|
| + ImportCertFromFile(GetTestCertsDirectory(), "test_mail_google_com.pem");
|
| + scoped_refptr<X509Certificate> cert2 =
|
| + ImportCertFromFile(GetTestCertsDirectory(), "expired_cert.pem");
|
| +
|
| + SSLInfo ssl_info;
|
| + ssl_info.cert = cert1;
|
| + ssl_info.unverified_cert = cert2;
|
| + ssl_info.ocsp_result.response_status = OCSPVerifyResult::PROVIDED;
|
| + ssl_info.ocsp_result.revocation_status = OCSPRevocationStatus::GOOD;
|
| +
|
| + std::string ocsp_response = "dummy response";
|
| +
|
| + ssl_info.is_issued_by_known_root = true;
|
| + state.CheckExpectStaple(host_port, ssl_info, ocsp_response);
|
| + EXPECT_EQ(GURL(), reporter.latest_report_uri());
|
| + EXPECT_TRUE(reporter.latest_report().empty());
|
| +
|
| + ssl_info.is_issued_by_known_root = false;
|
| + state.CheckExpectStaple(host_port, ssl_info, ocsp_response);
|
| + EXPECT_EQ(GURL(), reporter.latest_report_uri());
|
| + EXPECT_TRUE(reporter.latest_report().empty());
|
| +}
|
| +
|
| +TEST_F(TransportSecurityStateTest, ExpectStapleRequiresPreload) {
|
| + TransportSecurityState state;
|
| + TransportSecurityStateTest::EnableStaticExpectStaple(&state);
|
| + MockCertificateReportSender reporter;
|
| + state.SetReportSender(&reporter);
|
| +
|
| + HostPortPair host_port("not-preloaded.host.example", 443);
|
| +
|
| + // Two dummy certs to use as the server-sent and validated chains. The
|
| + // contents don't matter.
|
| + scoped_refptr<X509Certificate> cert1 =
|
| + ImportCertFromFile(GetTestCertsDirectory(), "test_mail_google_com.pem");
|
| + scoped_refptr<X509Certificate> cert2 =
|
| + ImportCertFromFile(GetTestCertsDirectory(), "expired_cert.pem");
|
| +
|
| + SSLInfo ssl_info;
|
| + ssl_info.cert = cert1;
|
| + ssl_info.unverified_cert = cert2;
|
| + ssl_info.ocsp_result.response_status = OCSPVerifyResult::MISSING;
|
| +
|
| + // Empty response
|
| + std::string ocsp_response;
|
| +
|
| + ssl_info.is_issued_by_known_root = true;
|
| + state.CheckExpectStaple(host_port, ssl_info, ocsp_response);
|
| + EXPECT_EQ(GURL(), reporter.latest_report_uri());
|
| + EXPECT_TRUE(reporter.latest_report().empty());
|
| +
|
| + ssl_info.is_issued_by_known_root = false;
|
| + state.CheckExpectStaple(host_port, ssl_info, ocsp_response);
|
| + EXPECT_EQ(GURL(), reporter.latest_report_uri());
|
| + EXPECT_TRUE(reporter.latest_report().empty());
|
| +}
|
| +
|
| // Tests that TransportSecurityState always consults the RequireCTDelegate,
|
| // if supplied.
|
| TEST_F(TransportSecurityStateTest, RequireCTConsultsDelegate) {
|
|
|