expect_ct_reporter.cc 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294
  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
  3. // found in the LICENSE file.
  4. #include "services/network/expect_ct_reporter.h"
  5. #include <set>
  6. #include <string>
  7. #include "base/base64.h"
  8. #include "base/bind.h"
  9. #include "base/command_line.h"
  10. #include "base/feature_list.h"
  11. #include "base/json/json_writer.h"
  12. #include "base/strings/string_number_conversions.h"
  13. #include "base/strings/string_split.h"
  14. #include "base/strings/string_util.h"
  15. #include "base/time/time.h"
  16. #include "base/time/time_to_iso8601.h"
  17. #include "base/values.h"
  18. #include "net/base/isolation_info.h"
  19. #include "net/base/load_flags.h"
  20. #include "net/cert/ct_serialization.h"
  21. #include "net/cert/x509_certificate.h"
  22. #include "net/http/http_request_headers.h"
  23. #include "net/traffic_annotation/network_traffic_annotation.h"
  24. #include "net/url_request/report_sender.h"
  25. #include "net/url_request/url_request_context.h"
  26. #include "services/network/public/cpp/features.h"
  27. namespace network {
  28. namespace {
  29. // Returns true if |request| contains any of the |allowed_values| in a response
  30. // header field named |header|. |allowed_values| are expected to be lower-case
  31. // and the check is case-insensitive.
  32. bool HasHeaderValues(net::URLRequest* request,
  33. const std::string& header,
  34. const std::set<std::string>& allowed_values) {
  35. std::string response_headers;
  36. request->GetResponseHeaderByName(header, &response_headers);
  37. const std::vector<std::string> response_values = base::SplitString(
  38. response_headers, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
  39. for (const auto& value : response_values) {
  40. for (const auto& allowed : allowed_values) {
  41. if (base::ToLowerASCII(allowed) == base::ToLowerASCII(value)) {
  42. return true;
  43. }
  44. }
  45. }
  46. return false;
  47. }
  48. base::Value::List GetPEMEncodedChainAsList(
  49. const net::X509Certificate* cert_chain) {
  50. if (!cert_chain)
  51. return base::Value::List();
  52. base::Value::List result;
  53. std::vector<std::string> pem_encoded_chain;
  54. cert_chain->GetPEMEncodedChain(&pem_encoded_chain);
  55. for (std::string& cert : pem_encoded_chain)
  56. result.Append(std::move(cert));
  57. return result;
  58. }
  59. std::string SCTOriginToString(
  60. net::ct::SignedCertificateTimestamp::Origin origin) {
  61. switch (origin) {
  62. case net::ct::SignedCertificateTimestamp::SCT_EMBEDDED:
  63. return "embedded";
  64. case net::ct::SignedCertificateTimestamp::SCT_FROM_TLS_EXTENSION:
  65. return "tls-extension";
  66. case net::ct::SignedCertificateTimestamp::SCT_FROM_OCSP_RESPONSE:
  67. return "ocsp";
  68. case net::ct::SignedCertificateTimestamp::SCT_ORIGIN_MAX:
  69. NOTREACHED();
  70. }
  71. return "";
  72. }
  73. bool AddSCT(const net::SignedCertificateTimestampAndStatus& sct,
  74. base::Value::List* list) {
  75. base::Value::Dict list_item;
  76. // Chrome implements RFC6962, not 6962-bis, so the reports contain v1 SCTs.
  77. list_item.Set("version", 1);
  78. std::string status;
  79. switch (sct.status) {
  80. case net::ct::SCT_STATUS_LOG_UNKNOWN:
  81. status = "unknown";
  82. break;
  83. case net::ct::SCT_STATUS_INVALID_SIGNATURE:
  84. case net::ct::SCT_STATUS_INVALID_TIMESTAMP:
  85. status = "invalid";
  86. break;
  87. case net::ct::SCT_STATUS_OK:
  88. status = "valid";
  89. break;
  90. case net::ct::SCT_STATUS_NONE:
  91. NOTREACHED();
  92. }
  93. list_item.Set("status", status);
  94. list_item.Set("source", SCTOriginToString(sct.sct->origin));
  95. std::string serialized_sct;
  96. if (!net::ct::EncodeSignedCertificateTimestamp(sct.sct, &serialized_sct))
  97. return false;
  98. std::string encoded_serialized_sct;
  99. base::Base64Encode(serialized_sct, &encoded_serialized_sct);
  100. list_item.Set("serialized_sct", encoded_serialized_sct);
  101. list->Append(base::Value(std::move(list_item)));
  102. return true;
  103. }
  104. constexpr net::NetworkTrafficAnnotationTag kExpectCTReporterTrafficAnnotation =
  105. net::DefineNetworkTrafficAnnotation("expect_ct_reporter", R"(
  106. semantics {
  107. sender: "Expect-CT reporting for Certificate Transparency reporting"
  108. description:
  109. "Websites can opt in to have Chrome send reports to them when "
  110. "Chrome observes connections to that website that do not meet "
  111. "Chrome's Certificate Transparency policy. Websites can use this "
  112. "feature to discover misconfigurations that prevent them from "
  113. "complying with Chrome's Certificate Transparency policy."
  114. trigger: "Website request."
  115. data:
  116. "The time of the request, the hostname and port being requested, "
  117. "the certificate chain, and the Signed Certificate Timestamps "
  118. "observed on the connection."
  119. destination: OTHER
  120. }
  121. policy {
  122. cookies_allowed: NO
  123. setting: "This feature cannot be disabled by settings."
  124. policy_exception_justification:
  125. "Not implemented, this is a feature that websites can opt into and "
  126. "thus there is no Chrome-wide policy to disable it."
  127. })");
  128. } // namespace
  129. ExpectCTReporter::ExpectCTReporter(
  130. net::URLRequestContext* request_context,
  131. const base::RepeatingClosure& success_callback,
  132. const base::RepeatingClosure& failure_callback)
  133. : report_sender_(new net::ReportSender(request_context,
  134. kExpectCTReporterTrafficAnnotation)),
  135. request_context_(request_context),
  136. success_callback_(success_callback),
  137. failure_callback_(failure_callback) {}
  138. ExpectCTReporter::~ExpectCTReporter() {}
  139. void ExpectCTReporter::OnExpectCTFailed(
  140. const net::HostPortPair& host_port_pair,
  141. const GURL& report_uri,
  142. base::Time expiration,
  143. const net::X509Certificate* validated_certificate_chain,
  144. const net::X509Certificate* served_certificate_chain,
  145. const net::SignedCertificateTimestampAndStatusList&
  146. signed_certificate_timestamps,
  147. const net::NetworkIsolationKey& network_isolation_key) {
  148. if (report_uri.is_empty())
  149. return;
  150. if (!base::FeatureList::IsEnabled(features::kExpectCTReporting))
  151. return;
  152. base::Value::Dict outer_report;
  153. base::Value::Dict report;
  154. report.Set("hostname", host_port_pair.host());
  155. report.Set("port", host_port_pair.port());
  156. report.Set("date-time", base::TimeToISO8601(base::Time::Now()));
  157. report.Set("effective-expiration-date", base::TimeToISO8601(expiration));
  158. report.Set("served-certificate-chain",
  159. GetPEMEncodedChainAsList(served_certificate_chain));
  160. report.Set("validated-certificate-chain",
  161. GetPEMEncodedChainAsList(validated_certificate_chain));
  162. base::Value::List scts;
  163. for (const auto& sct_and_status : signed_certificate_timestamps) {
  164. if (!AddSCT(sct_and_status, &scts))
  165. LOG(ERROR) << "Failed to add signed certificate timestamp to list";
  166. }
  167. report.Set("scts", std::move(scts));
  168. outer_report.Set("expect-ct-report", std::move(report));
  169. std::string serialized_report;
  170. if (!base::JSONWriter::Write(outer_report, &serialized_report)) {
  171. LOG(ERROR) << "Failed to serialize Expect CT report";
  172. return;
  173. }
  174. SendPreflight(report_uri, serialized_report, network_isolation_key);
  175. }
  176. void ExpectCTReporter::OnResponseStarted(net::URLRequest* request,
  177. int net_error) {
  178. auto preflight_it = inflight_preflights_.find(request);
  179. DCHECK(inflight_preflights_.end() != inflight_preflights_.find(request));
  180. PreflightInProgress* preflight = preflight_it->second.get();
  181. const int response_code =
  182. net_error == net::OK ? request->GetResponseCode() : -1;
  183. // Check that the preflight succeeded: it must have an HTTP OK status code,
  184. // with the following headers:
  185. // - Access-Control-Allow-Origin: * or null
  186. // - Access-Control-Allow-Headers: * or Content-Type
  187. // Note that * is allowed here as the credentials mode is never 'include'.
  188. // Access-Control-Allow-Methods is not checked, as the preflight is always
  189. // for a POST method, which is safelisted.
  190. if (response_code == -1 || response_code < 200 || response_code > 299) {
  191. OnReportFailure(preflight->report_uri, net_error, response_code);
  192. inflight_preflights_.erase(request);
  193. // Do not use |preflight| after this point, since it has been erased above.
  194. return;
  195. }
  196. if (!HasHeaderValues(request, "Access-Control-Allow-Origin", {"*", "null"}) ||
  197. !HasHeaderValues(request, "Access-Control-Allow-Headers",
  198. {"*", "content-type"})) {
  199. OnReportFailure(preflight->report_uri, net_error, response_code);
  200. inflight_preflights_.erase(request);
  201. // Do not use |preflight| after this point, since it has been erased above.
  202. return;
  203. }
  204. report_sender_->Send(preflight->report_uri,
  205. "application/expect-ct-report+json; charset=utf-8",
  206. preflight->serialized_report,
  207. preflight->network_isolation_key, success_callback_,
  208. // Since |this| owns the |report_sender_|, it's safe to
  209. // use base::Unretained here: |report_sender_| will be
  210. // destroyed before |this|.
  211. base::BindRepeating(&ExpectCTReporter::OnReportFailure,
  212. base::Unretained(this)));
  213. inflight_preflights_.erase(request);
  214. }
  215. void ExpectCTReporter::OnReadCompleted(net::URLRequest* request,
  216. int bytes_read) {
  217. NOTREACHED();
  218. }
  219. ExpectCTReporter::PreflightInProgress::PreflightInProgress(
  220. std::unique_ptr<net::URLRequest> request,
  221. const std::string& serialized_report,
  222. const GURL& report_uri,
  223. const net::NetworkIsolationKey& network_isolation_key)
  224. : request(std::move(request)),
  225. serialized_report(serialized_report),
  226. report_uri(report_uri),
  227. network_isolation_key(network_isolation_key) {}
  228. ExpectCTReporter::PreflightInProgress::~PreflightInProgress() {}
  229. void ExpectCTReporter::SendPreflight(
  230. const GURL& report_uri,
  231. const std::string& serialized_report,
  232. const net::NetworkIsolationKey& network_isolation_key) {
  233. std::unique_ptr<net::URLRequest> url_request =
  234. request_context_->CreateRequest(report_uri, net::DEFAULT_PRIORITY, this,
  235. kExpectCTReporterTrafficAnnotation);
  236. url_request->SetLoadFlags(net::LOAD_BYPASS_CACHE | net::LOAD_DISABLE_CACHE);
  237. url_request->set_allow_credentials(false);
  238. url_request->set_method(net::HttpRequestHeaders::kOptionsMethod);
  239. url_request->set_isolation_info(net::IsolationInfo::CreatePartial(
  240. net::IsolationInfo::RequestType::kOther, network_isolation_key));
  241. net::HttpRequestHeaders extra_headers;
  242. extra_headers.SetHeader("Origin", "null");
  243. extra_headers.SetHeader("Access-Control-Request-Method",
  244. net::HttpRequestHeaders::kPostMethod);
  245. extra_headers.SetHeader("Access-Control-Request-Headers", "content-type");
  246. url_request->SetExtraRequestHeaders(extra_headers);
  247. net::URLRequest* raw_request = url_request.get();
  248. inflight_preflights_[raw_request] = std::make_unique<PreflightInProgress>(
  249. std::move(url_request), serialized_report, report_uri,
  250. network_isolation_key);
  251. raw_request->Start();
  252. }
  253. void ExpectCTReporter::OnReportFailure(const GURL& report_uri,
  254. int net_error,
  255. int http_response_code) {
  256. if (!failure_callback_.is_null())
  257. failure_callback_.Run();
  258. }
  259. } // namespace network