1 // Copyright 2018 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 
5 #include "content/browser/web_package/signed_exchange_cert_fetcher.h"
6 
7 #include "base/bind.h"
8 #include "base/feature_list.h"
9 #include "base/format_macros.h"
10 #include "base/metrics/histogram_macros.h"
11 #include "base/numerics/safe_conversions.h"
12 #include "base/strings/string_piece.h"
13 #include "base/strings/stringprintf.h"
14 #include "base/trace_event/trace_event.h"
15 #include "content/browser/data_url_loader_factory.h"
16 #include "content/browser/loader/single_request_url_loader_factory.h"
17 #include "content/browser/web_package/signed_exchange_consts.h"
18 #include "content/browser/web_package/signed_exchange_devtools_proxy.h"
19 #include "content/browser/web_package/signed_exchange_utils.h"
20 #include "ipc/ipc_message.h"
21 #include "mojo/public/cpp/system/simple_watcher.h"
22 #include "net/base/ip_endpoint.h"
23 #include "net/base/load_flags.h"
24 #include "net/http/http_request_headers.h"
25 #include "net/http/http_status_code.h"
26 #include "services/network/public/cpp/shared_url_loader_factory.h"
27 #include "third_party/blink/public/common/loader/throttling_url_loader.h"
28 #include "third_party/blink/public/common/loader/url_loader_throttle.h"
29 #include "third_party/blink/public/mojom/loader/resource_load_info.mojom-shared.h"
30 
31 namespace content {
32 
33 namespace {
34 
35 constexpr char kCertChainMimeType[] = "application/cert-chain+cbor";
36 
37 // Limit certificate messages to 100k, matching BoringSSL's default limit.
38 const size_t kMaxCertSizeForSignedExchange = 100 * 1024;
39 static size_t g_max_cert_size_for_signed_exchange =
40     kMaxCertSizeForSignedExchange;
41 
ResetMaxCertSizeForTest()42 void ResetMaxCertSizeForTest() {
43   g_max_cert_size_for_signed_exchange = kMaxCertSizeForSignedExchange;
44 }
45 
46 const net::NetworkTrafficAnnotationTag kCertFetcherTrafficAnnotation =
47     net::DefineNetworkTrafficAnnotation("sigined_exchange_cert_fetcher", R"(
48     semantics {
49       sender: "Certificate Fetcher for Signed HTTP Exchanges"
50       description:
51         "Retrieves the X.509v3 certificates to verify the signed headers of "
52         "Signed HTTP Exchanges."
53       trigger:
54         "Navigating Chrome (ex: clicking on a link) to an URL and the server "
55         "returns a Signed HTTP Exchange."
56       data: "Arbitrary site-controlled data can be included in the URL."
57       destination: WEBSITE
58     }
59     policy {
60       cookies_allowed: NO
61       setting:
62         "This feature cannot be disabled by settings. This feature is not "
63         "enabled by default yet."
64       policy_exception_justification: "Not implemented."
65     }
66     comments:
67       "Chrome would be unable to handle Signed HTTP Exchanges without this "
68       "type of request."
69     )");
70 
71 }  // namespace
72 
73 // static
74 std::unique_ptr<SignedExchangeCertFetcher>
CreateAndStart(scoped_refptr<network::SharedURLLoaderFactory> shared_url_loader_factory,std::vector<std::unique_ptr<blink::URLLoaderThrottle>> throttles,const GURL & cert_url,bool force_fetch,CertificateCallback callback,SignedExchangeDevToolsProxy * devtools_proxy,const base::Optional<base::UnguessableToken> & throttling_profile_id,net::IsolationInfo isolation_info)75 SignedExchangeCertFetcher::CreateAndStart(
76     scoped_refptr<network::SharedURLLoaderFactory> shared_url_loader_factory,
77     std::vector<std::unique_ptr<blink::URLLoaderThrottle>> throttles,
78     const GURL& cert_url,
79     bool force_fetch,
80     CertificateCallback callback,
81     SignedExchangeDevToolsProxy* devtools_proxy,
82     const base::Optional<base::UnguessableToken>& throttling_profile_id,
83     net::IsolationInfo isolation_info) {
84   TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("loading"),
85                "SignedExchangeCertFetcher::CreateAndStart");
86   std::unique_ptr<SignedExchangeCertFetcher> cert_fetcher(
87       new SignedExchangeCertFetcher(
88           std::move(shared_url_loader_factory), std::move(throttles), cert_url,
89           force_fetch, std::move(callback), devtools_proxy,
90           throttling_profile_id, std::move(isolation_info)));
91   cert_fetcher->Start();
92   return cert_fetcher;
93 }
94 
95 // https://wicg.github.io/webpackage/loading.html#handling-cert-url
SignedExchangeCertFetcher(scoped_refptr<network::SharedURLLoaderFactory> shared_url_loader_factory,std::vector<std::unique_ptr<blink::URLLoaderThrottle>> throttles,const GURL & cert_url,bool force_fetch,CertificateCallback callback,SignedExchangeDevToolsProxy * devtools_proxy,const base::Optional<base::UnguessableToken> & throttling_profile_id,net::IsolationInfo isolation_info)96 SignedExchangeCertFetcher::SignedExchangeCertFetcher(
97     scoped_refptr<network::SharedURLLoaderFactory> shared_url_loader_factory,
98     std::vector<std::unique_ptr<blink::URLLoaderThrottle>> throttles,
99     const GURL& cert_url,
100     bool force_fetch,
101     CertificateCallback callback,
102     SignedExchangeDevToolsProxy* devtools_proxy,
103     const base::Optional<base::UnguessableToken>& throttling_profile_id,
104     net::IsolationInfo isolation_info)
105     : shared_url_loader_factory_(std::move(shared_url_loader_factory)),
106       throttles_(std::move(throttles)),
107       resource_request_(std::make_unique<network::ResourceRequest>()),
108       callback_(std::move(callback)),
109       devtools_proxy_(devtools_proxy) {
110   // TODO(https://crbug.com/803774): Revisit more ResourceRequest flags.
111   resource_request_->url = cert_url;
112   // |request_initiator| is used for cookie checks, but cert requests don't use
113   // cookies. So just set an opaque Origin.
114   resource_request_->request_initiator = url::Origin();
115   resource_request_->resource_type =
116       static_cast<int>(blink::mojom::ResourceType::kSubResource);
117   // Cert requests should not send credential informartion, because the default
118   // credentials mode of Fetch is "omit".
119   resource_request_->credentials_mode = network::mojom::CredentialsMode::kOmit;
120   resource_request_->headers.SetHeader(net::HttpRequestHeaders::kAccept,
121                                        kCertChainMimeType);
122   if (force_fetch) {
123     resource_request_->load_flags |=
124         net::LOAD_DISABLE_CACHE | net::LOAD_BYPASS_CACHE;
125   }
126   resource_request_->render_frame_id = MSG_ROUTING_NONE;
127   if (devtools_proxy_) {
128     cert_request_id_ = base::UnguessableToken::Create();
129     resource_request_->enable_load_timing = true;
130   }
131   resource_request_->throttling_profile_id = throttling_profile_id;
132   if (!isolation_info.IsEmpty()) {
133     resource_request_->trusted_params =
134         network::ResourceRequest::TrustedParams();
135     resource_request_->trusted_params->isolation_info = isolation_info;
136   }
137 }
138 
139 SignedExchangeCertFetcher::~SignedExchangeCertFetcher() = default;
140 
Start()141 void SignedExchangeCertFetcher::Start() {
142   if (devtools_proxy_) {
143     DCHECK(cert_request_id_);
144     devtools_proxy_->CertificateRequestSent(*cert_request_id_,
145                                             *resource_request_);
146   }
147   // When NetworkService enabled, data URL is not handled by the passed
148   // URLRequestContext's SharedURLLoaderFactory.
149   if (resource_request_->url.SchemeIs(url::kDataScheme)) {
150     shared_url_loader_factory_ =
151         base::MakeRefCounted<SingleRequestURLLoaderFactory>(
152             base::BindOnce(&SignedExchangeCertFetcher::OnDataURLRequest,
153                            base::Unretained(this)));
154   }
155   url_loader_ = blink::ThrottlingURLLoader::CreateLoaderAndStart(
156       std::move(shared_url_loader_factory_), std::move(throttles_),
157       0 /* routing_id */,
158       signed_exchange_utils::MakeRequestID() /* request_id */,
159       network::mojom::kURLLoadOptionNone, resource_request_.get(), this,
160       kCertFetcherTrafficAnnotation, base::ThreadTaskRunnerHandle::Get());
161 }
162 
Abort()163 void SignedExchangeCertFetcher::Abort() {
164   TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("loading"),
165                "SignedExchangeCertFetcher::Abort");
166   MaybeNotifyCompletionToDevtools(
167       network::URLLoaderCompletionStatus(net::ERR_ABORTED));
168   DCHECK(callback_);
169   url_loader_ = nullptr;
170   body_.reset();
171   handle_watcher_ = nullptr;
172   body_string_.clear();
173   devtools_proxy_ = nullptr;
174   std::move(callback_).Run(SignedExchangeLoadResult::kCertFetchError, nullptr,
175                            net::IPAddress());
176 }
177 
OnHandleReady(MojoResult result)178 void SignedExchangeCertFetcher::OnHandleReady(MojoResult result) {
179   TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("loading"),
180                "SignedExchangeCertFetcher::OnHandleReady");
181   const void* buffer = nullptr;
182   uint32_t num_bytes = 0;
183   MojoResult rv =
184       body_->BeginReadData(&buffer, &num_bytes, MOJO_READ_DATA_FLAG_NONE);
185   if (rv == MOJO_RESULT_OK) {
186     if (body_string_.size() + num_bytes > g_max_cert_size_for_signed_exchange) {
187       body_->EndReadData(num_bytes);
188       signed_exchange_utils::ReportErrorAndTraceEvent(
189           devtools_proxy_,
190           "The response body size of certificate message exceeds the limit.");
191       Abort();
192       return;
193     }
194     body_string_.append(static_cast<const char*>(buffer), num_bytes);
195     body_->EndReadData(num_bytes);
196   } else if (rv == MOJO_RESULT_FAILED_PRECONDITION) {
197     OnDataComplete();
198   } else {
199     DCHECK_EQ(MOJO_RESULT_SHOULD_WAIT, rv);
200   }
201 }
202 
OnDataComplete()203 void SignedExchangeCertFetcher::OnDataComplete() {
204   TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("loading"),
205                "SignedExchangeCertFetcher::OnDataComplete");
206   DCHECK(callback_);
207   url_loader_ = nullptr;
208   body_.reset();
209   handle_watcher_ = nullptr;
210 
211   // Notify the completion to the devtools here because |this| may be deleted
212   // before OnComplete() is called.
213   MaybeNotifyCompletionToDevtools(network::URLLoaderCompletionStatus(net::OK));
214 
215   std::unique_ptr<SignedExchangeCertificateChain> cert_chain =
216       SignedExchangeCertificateChain::Parse(
217           base::as_bytes(base::make_span(body_string_)), devtools_proxy_);
218   body_string_.clear();
219   if (!cert_chain) {
220     signed_exchange_utils::ReportErrorAndTraceEvent(
221         devtools_proxy_, "Failed to get certificate chain from message.");
222     std::move(callback_).Run(SignedExchangeLoadResult::kCertParseError, nullptr,
223                              cert_server_ip_address_);
224     return;
225   }
226   std::move(callback_).Run(SignedExchangeLoadResult::kSuccess,
227                            std::move(cert_chain), cert_server_ip_address_);
228 }
229 
230 // network::mojom::URLLoaderClient
OnReceiveResponse(network::mojom::URLResponseHeadPtr head)231 void SignedExchangeCertFetcher::OnReceiveResponse(
232     network::mojom::URLResponseHeadPtr head) {
233   TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("loading"),
234                "SignedExchangeCertFetcher::OnReceiveResponse");
235   if (devtools_proxy_) {
236     DCHECK(cert_request_id_);
237     devtools_proxy_->CertificateResponseReceived(*cert_request_id_,
238                                                  resource_request_->url, *head);
239   }
240 
241   cert_server_ip_address_ = head->remote_endpoint.address();
242 
243   // |headers| is null when loading data URL.
244   if (head->headers && head->headers->response_code() != net::HTTP_OK) {
245     signed_exchange_utils::ReportErrorAndTraceEvent(
246         devtools_proxy_, base::StringPrintf("Invalid reponse code: %d",
247                                             head->headers->response_code()));
248     Abort();
249     return;
250   }
251 
252   // https://wicg.github.io/webpackage/draft-yasskin-http-origin-signed-responses.html#cert-chain-format
253   // "The resource at a signature's cert-url MUST have the
254   // application/cert-chain+cbor content type" [spec text]
255   if (head->mime_type != kCertChainMimeType) {
256     signed_exchange_utils::ReportErrorAndTraceEvent(
257         devtools_proxy_,
258         base::StringPrintf(
259             "Content type of cert-url must be application/cert-chain+cbor. "
260             "Actual content type: %s",
261             head->mime_type.c_str()));
262     Abort();
263     return;
264   }
265 
266   if (head->content_length > 0) {
267     if (base::checked_cast<size_t>(head->content_length) >
268         g_max_cert_size_for_signed_exchange) {
269       signed_exchange_utils::ReportErrorAndTraceEvent(
270           devtools_proxy_,
271           base::StringPrintf("Invalid content length: %" PRIu64,
272                              head->content_length));
273       Abort();
274       return;
275     }
276     body_string_.reserve(head->content_length);
277   }
278 
279   UMA_HISTOGRAM_BOOLEAN("SignedExchange.CertificateFetch.CacheHit",
280                         head->was_fetched_via_cache);
281 }
282 
OnReceiveRedirect(const net::RedirectInfo & redirect_info,network::mojom::URLResponseHeadPtr head)283 void SignedExchangeCertFetcher::OnReceiveRedirect(
284     const net::RedirectInfo& redirect_info,
285     network::mojom::URLResponseHeadPtr head) {
286   TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("loading"),
287                "SignedExchangeCertFetcher::OnReceiveRedirect");
288   // Currently the cert fetcher doesn't allow any redirects.
289   Abort();
290 }
291 
OnUploadProgress(int64_t current_position,int64_t total_size,OnUploadProgressCallback callback)292 void SignedExchangeCertFetcher::OnUploadProgress(
293     int64_t current_position,
294     int64_t total_size,
295     OnUploadProgressCallback callback) {
296   // Cert fetching doesn't have request body.
297   NOTREACHED();
298 }
299 
OnReceiveCachedMetadata(mojo_base::BigBuffer data)300 void SignedExchangeCertFetcher::OnReceiveCachedMetadata(
301     mojo_base::BigBuffer data) {
302   // Cert fetching doesn't use cached metadata.
303   NOTREACHED();
304 }
305 
OnTransferSizeUpdated(int32_t transfer_size_diff)306 void SignedExchangeCertFetcher::OnTransferSizeUpdated(
307     int32_t transfer_size_diff) {
308   // Do nothing.
309 }
310 
OnStartLoadingResponseBody(mojo::ScopedDataPipeConsumerHandle body)311 void SignedExchangeCertFetcher::OnStartLoadingResponseBody(
312     mojo::ScopedDataPipeConsumerHandle body) {
313   TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("loading"),
314                "SignedExchangeCertFetcher::OnStartLoadingResponseBody");
315   body_ = std::move(body);
316   handle_watcher_ = std::make_unique<mojo::SimpleWatcher>(
317       FROM_HERE, mojo::SimpleWatcher::ArmingPolicy::AUTOMATIC,
318       base::SequencedTaskRunnerHandle::Get());
319   handle_watcher_->Watch(
320       body_.get(), MOJO_HANDLE_SIGNAL_READABLE,
321       base::BindRepeating(&SignedExchangeCertFetcher::OnHandleReady,
322                           base::Unretained(this)));
323 }
324 
OnComplete(const network::URLLoaderCompletionStatus & status)325 void SignedExchangeCertFetcher::OnComplete(
326     const network::URLLoaderCompletionStatus& status) {
327   TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("loading"),
328                "SignedExchangeCertFetcher::OnComplete");
329   MaybeNotifyCompletionToDevtools(status);
330   if (!handle_watcher_)
331     Abort();
332 }
333 
OnDataURLRequest(const network::ResourceRequest & resource_request,mojo::PendingReceiver<network::mojom::URLLoader> url_loader_receiver,mojo::PendingRemote<network::mojom::URLLoaderClient> url_loader_client_remote)334 void SignedExchangeCertFetcher::OnDataURLRequest(
335     const network::ResourceRequest& resource_request,
336     mojo::PendingReceiver<network::mojom::URLLoader> url_loader_receiver,
337     mojo::PendingRemote<network::mojom::URLLoaderClient>
338         url_loader_client_remote) {
339   mojo::Remote<network::mojom::URLLoaderFactory> factory(
340       DataURLLoaderFactory::Create());
341   factory->CreateLoaderAndStart(
342       std::move(url_loader_receiver), 0, 0, 0, resource_request,
343       std::move(url_loader_client_remote),
344       net::MutableNetworkTrafficAnnotationTag(kCertFetcherTrafficAnnotation));
345 }
346 
MaybeNotifyCompletionToDevtools(const network::URLLoaderCompletionStatus & status)347 void SignedExchangeCertFetcher::MaybeNotifyCompletionToDevtools(
348     const network::URLLoaderCompletionStatus& status) {
349   if (!devtools_proxy_ || has_notified_completion_to_devtools_)
350     return;
351   DCHECK(cert_request_id_);
352   devtools_proxy_->CertificateRequestCompleted(*cert_request_id_, status);
353   has_notified_completion_to_devtools_ = true;
354 }
355 
356 // static
SetMaxCertSizeForTest(size_t max_cert_size)357 base::ScopedClosureRunner SignedExchangeCertFetcher::SetMaxCertSizeForTest(
358     size_t max_cert_size) {
359   g_max_cert_size_for_signed_exchange = max_cert_size;
360   return base::ScopedClosureRunner(base::BindOnce(&ResetMaxCertSizeForTest));
361 }
362 
363 }  // namespace content
364