| OLD | NEW |
| 1 // Copyright 2016 The Chromium Authors. All rights reserved. | 1 // Copyright 2016 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "chrome/browser/ssl/chrome_expect_ct_reporter.h" | 5 #include "chrome/browser/ssl/chrome_expect_ct_reporter.h" |
| 6 | 6 |
| 7 #include <string> | 7 #include <string> |
| 8 | 8 |
| 9 #include "base/base64.h" | 9 #include "base/base64.h" |
| 10 #include "base/command_line.h" | 10 #include "base/command_line.h" |
| 11 #include "base/json/json_reader.h" | 11 #include "base/json/json_reader.h" |
| 12 #include "base/message_loop/message_loop.h" | 12 #include "base/message_loop/message_loop.h" |
| 13 #include "base/run_loop.h" | 13 #include "base/run_loop.h" |
| 14 #include "base/test/histogram_tester.h" | 14 #include "base/test/histogram_tester.h" |
| 15 #include "base/test/scoped_feature_list.h" | 15 #include "base/test/scoped_feature_list.h" |
| 16 #include "base/values.h" | 16 #include "base/values.h" |
| 17 #include "chrome/common/chrome_features.h" | 17 #include "chrome/common/chrome_features.h" |
| 18 #include "content/public/test/test_browser_thread_bundle.h" | 18 #include "content/public/test/test_browser_thread_bundle.h" |
| 19 #include "net/cert/ct_serialization.h" | |
| 20 #include "net/cert/signed_certificate_timestamp_and_status.h" | 19 #include "net/cert/signed_certificate_timestamp_and_status.h" |
| 21 #include "net/test/cert_test_util.h" | 20 #include "net/test/cert_test_util.h" |
| 22 #include "net/test/test_data_directory.h" | 21 #include "net/test/test_data_directory.h" |
| 23 #include "net/test/url_request/url_request_failed_job.h" | 22 #include "net/test/url_request/url_request_failed_job.h" |
| 24 #include "net/traffic_annotation/network_traffic_annotation_test_helper.h" | 23 #include "net/traffic_annotation/network_traffic_annotation_test_helper.h" |
| 25 #include "net/url_request/report_sender.h" | 24 #include "net/url_request/report_sender.h" |
| 26 #include "net/url_request/url_request_filter.h" | 25 #include "net/url_request/url_request_filter.h" |
| 27 #include "net/url_request/url_request_test_util.h" | 26 #include "net/url_request/url_request_test_util.h" |
| 28 #include "testing/gtest/include/gtest/gtest.h" | 27 #include "testing/gtest/include/gtest/gtest.h" |
| 29 #include "url/gurl.h" | 28 #include "url/gurl.h" |
| (...skipping 74 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 104 EXPECT_EQ(pem_encoded_chain[i], cert_pem); | 103 EXPECT_EQ(pem_encoded_chain[i], cert_pem); |
| 105 } | 104 } |
| 106 } | 105 } |
| 107 | 106 |
| 108 // Converts the string value of a reported SCT's origin to a | 107 // Converts the string value of a reported SCT's origin to a |
| 109 // net::ct::SignedCertificateTimestamp::Origin value. | 108 // net::ct::SignedCertificateTimestamp::Origin value. |
| 110 net::ct::SignedCertificateTimestamp::Origin SCTOriginStringToOrigin( | 109 net::ct::SignedCertificateTimestamp::Origin SCTOriginStringToOrigin( |
| 111 const std::string& origin_string) { | 110 const std::string& origin_string) { |
| 112 if (origin_string == "embedded") | 111 if (origin_string == "embedded") |
| 113 return net::ct::SignedCertificateTimestamp::SCT_EMBEDDED; | 112 return net::ct::SignedCertificateTimestamp::SCT_EMBEDDED; |
| 114 if (origin_string == "tls-extension") | 113 if (origin_string == "from-tls-extension") |
| 115 return net::ct::SignedCertificateTimestamp::SCT_FROM_TLS_EXTENSION; | 114 return net::ct::SignedCertificateTimestamp::SCT_FROM_TLS_EXTENSION; |
| 116 if (origin_string == "ocsp") | 115 if (origin_string == "from-ocsp-response") |
| 117 return net::ct::SignedCertificateTimestamp::SCT_FROM_OCSP_RESPONSE; | 116 return net::ct::SignedCertificateTimestamp::SCT_FROM_OCSP_RESPONSE; |
| 118 NOTREACHED(); | 117 NOTREACHED(); |
| 119 return net::ct::SignedCertificateTimestamp::SCT_EMBEDDED; | 118 return net::ct::SignedCertificateTimestamp::SCT_EMBEDDED; |
| 120 } | 119 } |
| 121 | 120 |
| 122 // Checks that an SCT |sct| appears with status |status| in |report_list|, a | 121 // Checks that an SCT |sct| appears (with the format determined by |
| 123 // list of SCTs from an Expect-CT report. | 122 // |status|) in |report_list|, a list of SCTs from an Expect CT |
| 124 ::testing::AssertionResult FindSCTInReportList( | 123 // report. |status| determines the format in that only certain fields |
| 125 const scoped_refptr<net::ct::SignedCertificateTimestamp>& expected_sct, | 124 // are reported for certain verify statuses; SCTs from unknown logs |
| 126 net::ct::SCTVerifyStatus expected_status, | 125 // contain very little information, for example, to avoid compromising |
| 126 // privacy. |
| 127 void FindSCTInReportList( |
| 128 const scoped_refptr<net::ct::SignedCertificateTimestamp>& sct, |
| 129 net::ct::SCTVerifyStatus status, |
| 127 const base::ListValue& report_list) { | 130 const base::ListValue& report_list) { |
| 128 std::string expected_serialized_sct; | 131 bool found = false; |
| 129 net::ct::EncodeSignedCertificateTimestamp(expected_sct, | 132 for (size_t i = 0; !found && i < report_list.GetSize(); i++) { |
| 130 &expected_serialized_sct); | 133 const base::DictionaryValue* report_sct; |
| 134 ASSERT_TRUE(report_list.GetDictionary(i, &report_sct)); |
| 131 | 135 |
| 132 for (size_t i = 0; i < report_list.GetSize(); i++) { | 136 std::string origin; |
| 133 const base::DictionaryValue* report_sct; | 137 ASSERT_TRUE(report_sct->GetString("origin", &origin)); |
| 134 if (!report_list.GetDictionary(i, &report_sct)) { | |
| 135 return ::testing::AssertionFailure() | |
| 136 << "Failed to get dictionary value from report SCT list"; | |
| 137 } | |
| 138 | 138 |
| 139 std::string serialized_sct; | 139 switch (status) { |
| 140 EXPECT_TRUE(report_sct->GetString("serialized_sct", &serialized_sct)); | 140 case net::ct::SCT_STATUS_LOG_UNKNOWN: |
| 141 std::string decoded_serialized_sct; | 141 // SCTs from unknown logs only have an origin. |
| 142 EXPECT_TRUE(base::Base64Decode(serialized_sct, &decoded_serialized_sct)); | 142 EXPECT_FALSE(report_sct->HasKey("sct")); |
| 143 if (decoded_serialized_sct != expected_serialized_sct) | 143 EXPECT_FALSE(report_sct->HasKey("id")); |
| 144 continue; | 144 if (SCTOriginStringToOrigin(origin) == sct->origin) |
| 145 found = true; |
| 146 break; |
| 145 | 147 |
| 146 std::string source; | |
| 147 EXPECT_TRUE(report_sct->GetString("source", &source)); | |
| 148 EXPECT_EQ(expected_sct->origin, SCTOriginStringToOrigin(source)); | |
| 149 | |
| 150 std::string report_status; | |
| 151 EXPECT_TRUE(report_sct->GetString("status", &report_status)); | |
| 152 switch (expected_status) { | |
| 153 case net::ct::SCT_STATUS_LOG_UNKNOWN: | |
| 154 EXPECT_EQ("unknown", report_status); | |
| 155 break; | |
| 156 case net::ct::SCT_STATUS_INVALID_SIGNATURE: | 148 case net::ct::SCT_STATUS_INVALID_SIGNATURE: |
| 157 case net::ct::SCT_STATUS_INVALID_TIMESTAMP: { | 149 case net::ct::SCT_STATUS_INVALID_TIMESTAMP: { |
| 158 EXPECT_EQ("invalid", report_status); | 150 // Invalid SCTs have a log id and an origin and nothing else. |
| 151 EXPECT_FALSE(report_sct->HasKey("sct")); |
| 152 std::string id_base64; |
| 153 ASSERT_TRUE(report_sct->GetString("id", &id_base64)); |
| 154 std::string id; |
| 155 ASSERT_TRUE(base::Base64Decode(id_base64, &id)); |
| 156 if (SCTOriginStringToOrigin(origin) == sct->origin && id == sct->log_id) |
| 157 found = true; |
| 159 break; | 158 break; |
| 160 } | 159 } |
| 160 |
| 161 case net::ct::SCT_STATUS_OK: { | 161 case net::ct::SCT_STATUS_OK: { |
| 162 EXPECT_EQ("valid", report_status); | 162 // Valid SCTs have the full SCT. |
| 163 const base::DictionaryValue* report_sct_object; |
| 164 ASSERT_TRUE(report_sct->GetDictionary("sct", &report_sct_object)); |
| 165 int version; |
| 166 ASSERT_TRUE(report_sct_object->GetInteger("sct_version", &version)); |
| 167 std::string id_base64; |
| 168 ASSERT_TRUE(report_sct_object->GetString("id", &id_base64)); |
| 169 std::string id; |
| 170 ASSERT_TRUE(base::Base64Decode(id_base64, &id)); |
| 171 std::string extensions_base64; |
| 172 ASSERT_TRUE( |
| 173 report_sct_object->GetString("extensions", &extensions_base64)); |
| 174 std::string extensions; |
| 175 ASSERT_TRUE(base::Base64Decode(extensions_base64, &extensions)); |
| 176 std::string signature_data_base64; |
| 177 ASSERT_TRUE( |
| 178 report_sct_object->GetString("signature", &signature_data_base64)); |
| 179 std::string signature_data; |
| 180 ASSERT_TRUE(base::Base64Decode(signature_data_base64, &signature_data)); |
| 181 |
| 182 if (version == sct->version && |
| 183 SCTOriginStringToOrigin(origin) == sct->origin && |
| 184 id == sct->log_id && extensions == sct->extensions && |
| 185 signature_data == sct->signature.signature_data) { |
| 186 found = true; |
| 187 } |
| 163 break; | 188 break; |
| 164 } | 189 } |
| 165 case net::ct::SCT_STATUS_NONE: | 190 default: |
| 166 NOTREACHED(); | 191 NOTREACHED(); |
| 167 } | 192 } |
| 168 return ::testing::AssertionSuccess(); | |
| 169 } | 193 } |
| 170 | 194 EXPECT_TRUE(found); |
| 171 return ::testing::AssertionFailure() << "Failed to find SCT in report list"; | |
| 172 } | 195 } |
| 173 | 196 |
| 174 // Checks that all |expected_scts| appears in the given lists of SCTs | 197 // Checks that all |expected_scts| appears in the given lists of SCTs |
| 175 // from an Expect CT report. | 198 // from an Expect CT report. |
| 176 void CheckReportSCTs( | 199 void CheckReportSCTs( |
| 177 const net::SignedCertificateTimestampAndStatusList& expected_scts, | 200 const net::SignedCertificateTimestampAndStatusList& expected_scts, |
| 178 const base::ListValue& scts) { | 201 const base::ListValue& unknown_scts, |
| 179 EXPECT_EQ(expected_scts.size(), scts.GetSize()); | 202 const base::ListValue& invalid_scts, |
| 203 const base::ListValue& valid_scts) { |
| 204 EXPECT_EQ( |
| 205 expected_scts.size(), |
| 206 unknown_scts.GetSize() + invalid_scts.GetSize() + valid_scts.GetSize()); |
| 180 for (const auto& expected_sct : expected_scts) { | 207 for (const auto& expected_sct : expected_scts) { |
| 181 ASSERT_TRUE( | 208 switch (expected_sct.status) { |
| 182 FindSCTInReportList(expected_sct.sct, expected_sct.status, scts)); | 209 case net::ct::SCT_STATUS_LOG_UNKNOWN: |
| 210 ASSERT_NO_FATAL_FAILURE(FindSCTInReportList( |
| 211 expected_sct.sct, net::ct::SCT_STATUS_LOG_UNKNOWN, unknown_scts)); |
| 212 break; |
| 213 case net::ct::SCT_STATUS_INVALID_SIGNATURE: |
| 214 ASSERT_NO_FATAL_FAILURE(FindSCTInReportList( |
| 215 expected_sct.sct, net::ct::SCT_STATUS_INVALID_SIGNATURE, |
| 216 invalid_scts)); |
| 217 break; |
| 218 case net::ct::SCT_STATUS_INVALID_TIMESTAMP: |
| 219 ASSERT_NO_FATAL_FAILURE(FindSCTInReportList( |
| 220 expected_sct.sct, net::ct::SCT_STATUS_INVALID_TIMESTAMP, |
| 221 invalid_scts)); |
| 222 break; |
| 223 case net::ct::SCT_STATUS_OK: |
| 224 ASSERT_NO_FATAL_FAILURE(FindSCTInReportList( |
| 225 expected_sct.sct, net::ct::SCT_STATUS_OK, valid_scts)); |
| 226 break; |
| 227 default: |
| 228 NOTREACHED(); |
| 229 } |
| 183 } | 230 } |
| 184 } | 231 } |
| 185 | 232 |
| 186 // Checks that the |serialized_report| deserializes properly and | 233 // Checks that the |serialized_report| deserializes properly and |
| 187 // contains the correct information (hostname, port, served and | 234 // contains the correct information (hostname, port, served and |
| 188 // validated certificate chains, SCTs) for the given |host_port| and | 235 // validated certificate chains, SCTs) for the given |host_port| and |
| 189 // |ssl_info|. | 236 // |ssl_info|. |
| 190 void CheckExpectCTReport(const std::string& serialized_report, | 237 void CheckExpectCTReport(const std::string& serialized_report, |
| 191 const net::HostPortPair& host_port, | 238 const net::HostPortPair& host_port, |
| 192 const std::string expiration_time, | 239 const std::string expiration_time, |
| 193 const net::SSLInfo& ssl_info) { | 240 const net::SSLInfo& ssl_info) { |
| 194 std::unique_ptr<base::Value> value(base::JSONReader::Read(serialized_report)); | 241 std::unique_ptr<base::Value> value(base::JSONReader::Read(serialized_report)); |
| 195 ASSERT_TRUE(value); | 242 ASSERT_TRUE(value); |
| 196 ASSERT_TRUE(value->IsType(base::Value::Type::DICTIONARY)); | 243 ASSERT_TRUE(value->IsType(base::Value::Type::DICTIONARY)); |
| 197 | 244 |
| 198 base::DictionaryValue* outer_report_dict; | |
| 199 ASSERT_TRUE(value->GetAsDictionary(&outer_report_dict)); | |
| 200 | |
| 201 base::DictionaryValue* report_dict; | 245 base::DictionaryValue* report_dict; |
| 202 ASSERT_TRUE( | 246 ASSERT_TRUE(value->GetAsDictionary(&report_dict)); |
| 203 outer_report_dict->GetDictionary("expect-ct-report", &report_dict)); | |
| 204 | 247 |
| 205 std::string report_hostname; | 248 std::string report_hostname; |
| 206 EXPECT_TRUE(report_dict->GetString("hostname", &report_hostname)); | 249 EXPECT_TRUE(report_dict->GetString("hostname", &report_hostname)); |
| 207 EXPECT_EQ(host_port.host(), report_hostname); | 250 EXPECT_EQ(host_port.host(), report_hostname); |
| 208 int report_port; | 251 int report_port; |
| 209 EXPECT_TRUE(report_dict->GetInteger("port", &report_port)); | 252 EXPECT_TRUE(report_dict->GetInteger("port", &report_port)); |
| 210 EXPECT_EQ(host_port.port(), report_port); | 253 EXPECT_EQ(host_port.port(), report_port); |
| 211 | 254 |
| 212 std::string expiration; | 255 std::string expiration; |
| 213 EXPECT_TRUE(report_dict->GetString("effective-expiration-date", &expiration)); | 256 EXPECT_TRUE(report_dict->GetString("effective-expiration-date", &expiration)); |
| 214 EXPECT_EQ(expiration_time, expiration); | 257 EXPECT_EQ(expiration_time, expiration); |
| 215 | 258 |
| 216 const base::ListValue* report_served_certificate_chain = nullptr; | 259 const base::ListValue* report_served_certificate_chain = nullptr; |
| 217 ASSERT_TRUE(report_dict->GetList("served-certificate-chain", | 260 ASSERT_TRUE(report_dict->GetList("served-certificate-chain", |
| 218 &report_served_certificate_chain)); | 261 &report_served_certificate_chain)); |
| 219 ASSERT_NO_FATAL_FAILURE(CheckReportCertificateChain( | 262 ASSERT_NO_FATAL_FAILURE(CheckReportCertificateChain( |
| 220 ssl_info.unverified_cert, *report_served_certificate_chain)); | 263 ssl_info.unverified_cert, *report_served_certificate_chain)); |
| 221 | 264 |
| 222 const base::ListValue* report_validated_certificate_chain = nullptr; | 265 const base::ListValue* report_validated_certificate_chain = nullptr; |
| 223 ASSERT_TRUE(report_dict->GetList("validated-certificate-chain", | 266 ASSERT_TRUE(report_dict->GetList("validated-certificate-chain", |
| 224 &report_validated_certificate_chain)); | 267 &report_validated_certificate_chain)); |
| 225 ASSERT_NO_FATAL_FAILURE(CheckReportCertificateChain( | 268 ASSERT_NO_FATAL_FAILURE(CheckReportCertificateChain( |
| 226 ssl_info.cert, *report_validated_certificate_chain)); | 269 ssl_info.cert, *report_validated_certificate_chain)); |
| 227 | 270 |
| 228 const base::ListValue* report_scts = nullptr; | 271 const base::ListValue* report_unknown_scts = nullptr; |
| 229 ASSERT_TRUE(report_dict->GetList("scts", &report_scts)); | 272 ASSERT_TRUE(report_dict->GetList("unknown-scts", &report_unknown_scts)); |
| 273 const base::ListValue* report_invalid_scts = nullptr; |
| 274 ASSERT_TRUE(report_dict->GetList("invalid-scts", &report_invalid_scts)); |
| 275 const base::ListValue* report_valid_scts = nullptr; |
| 276 ASSERT_TRUE(report_dict->GetList("valid-scts", &report_valid_scts)); |
| 230 | 277 |
| 231 ASSERT_NO_FATAL_FAILURE( | 278 ASSERT_NO_FATAL_FAILURE(CheckReportSCTs( |
| 232 CheckReportSCTs(ssl_info.signed_certificate_timestamps, *report_scts)); | 279 ssl_info.signed_certificate_timestamps, *report_unknown_scts, |
| 280 *report_invalid_scts, *report_valid_scts)); |
| 233 } | 281 } |
| 234 | 282 |
| 235 // A test network delegate that allows the user to specify a callback to | 283 // A test network delegate that allows the user to specify a callback to |
| 236 // be run whenever a net::URLRequest is destroyed. | 284 // be run whenever a net::URLRequest is destroyed. |
| 237 class TestExpectCTNetworkDelegate : public net::NetworkDelegateImpl { | 285 class TestExpectCTNetworkDelegate : public net::NetworkDelegateImpl { |
| 238 public: | 286 public: |
| 239 TestExpectCTNetworkDelegate() | 287 TestExpectCTNetworkDelegate() |
| 240 : url_request_destroyed_callback_(base::Bind(&base::DoNothing)) {} | 288 : url_request_destroyed_callback_(base::Bind(&base::DoNothing)) {} |
| 241 | 289 |
| 242 void set_url_request_destroyed_callback(const base::Closure& callback) { | 290 void set_url_request_destroyed_callback(const base::Closure& callback) { |
| (...skipping 216 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 459 EXPECT_FALSE(sender->latest_serialized_report().empty()); | 507 EXPECT_FALSE(sender->latest_serialized_report().empty()); |
| 460 EXPECT_EQ("application/json; charset=utf-8", sender->latest_content_type()); | 508 EXPECT_EQ("application/json; charset=utf-8", sender->latest_content_type()); |
| 461 ASSERT_NO_FATAL_FAILURE( | 509 ASSERT_NO_FATAL_FAILURE( |
| 462 CheckExpectCTReport(sender->latest_serialized_report(), host_port, | 510 CheckExpectCTReport(sender->latest_serialized_report(), host_port, |
| 463 kExpirationTimeStr, ssl_info)); | 511 kExpirationTimeStr, ssl_info)); |
| 464 | 512 |
| 465 histograms.ExpectTotalCount(kFailureHistogramName, 0); | 513 histograms.ExpectTotalCount(kFailureHistogramName, 0); |
| 466 histograms.ExpectTotalCount(kSendHistogramName, 1); | 514 histograms.ExpectTotalCount(kSendHistogramName, 1); |
| 467 histograms.ExpectBucketCount(kSendHistogramName, true, 1); | 515 histograms.ExpectBucketCount(kSendHistogramName, true, 1); |
| 468 } | 516 } |
| OLD | NEW |