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 ea97a369bce366c0b119a40346ac64873cf9e38f..ac97e5d5bcf239dcc110d0ccccfa77f65ec8a8e2 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) { |