| // Copyright 2018 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "content/browser/web_package/signed_exchange_cert_fetcher.h" |
| |
| #include "base/numerics/safe_conversions.h" |
| #include "base/strings/string_piece.h" |
| #include "content/common/throttling_url_loader.h" |
| #include "content/public/common/resource_type.h" |
| #include "content/public/common/shared_url_loader_factory.h" |
| #include "content/public/common/url_loader_throttle.h" |
| #include "ipc/ipc_message.h" |
| #include "mojo/public/cpp/system/simple_watcher.h" |
| #include "net/base/load_flags.h" |
| #include "net/http/http_status_code.h" |
| |
| namespace content { |
| |
| namespace { |
| |
| // Limit certificate messages to 100k, matching BoringSSL's default limit. |
| const size_t kMaxCertSizeForSignedExchange = 100 * 1024; |
| static size_t g_max_cert_size_for_signed_exchange = |
| kMaxCertSizeForSignedExchange; |
| |
| void ResetMaxCertSizeForTest() { |
| g_max_cert_size_for_signed_exchange = kMaxCertSizeForSignedExchange; |
| } |
| |
| const net::NetworkTrafficAnnotationTag kCertFetcherTrafficAnnotation = |
| net::DefineNetworkTrafficAnnotation("sigined_exchange_cert_fetcher", R"( |
| semantics { |
| sender: "Certificate Fetcher for Signed HTTP Exchanges" |
| description: |
| "Retrieves the X.509v3 certificates to verify the signed headers of " |
| "Signed HTTP Exchanges." |
| trigger: |
| "Navigating Chrome (ex: clicking on a link) to an URL and the server " |
| "returns a Signed HTTP Exchange." |
| data: "Arbitrary site-controlled data can be included in the URL." |
| destination: WEBSITE |
| } |
| policy { |
| cookies_allowed: NO |
| setting: |
| "This feature cannot be disabled by settings. This feature is not " |
| "enabled by default yet." |
| policy_exception_justification: "Not implemented." |
| } |
| comments: |
| "Chrome would be unable to handle Signed HTTP Exchanges without this " |
| "type of request." |
| )"); |
| |
| bool ConsumeByte(base::StringPiece* data, uint8_t* out) { |
| if (data->empty()) |
| return false; |
| *out = (*data)[0]; |
| data->remove_prefix(1); |
| return true; |
| } |
| |
| bool Consume2Bytes(base::StringPiece* data, uint16_t* out) { |
| if (data->size() < 2) |
| return false; |
| *out = (static_cast<uint8_t>((*data)[0]) << 8) | |
| static_cast<uint8_t>((*data)[1]); |
| data->remove_prefix(2); |
| return true; |
| } |
| |
| bool Consume3Bytes(base::StringPiece* data, uint32_t* out) { |
| if (data->size() < 3) |
| return false; |
| *out = (static_cast<uint8_t>((*data)[0]) << 16) | |
| (static_cast<uint8_t>((*data)[1]) << 8) | |
| static_cast<uint8_t>((*data)[2]); |
| data->remove_prefix(3); |
| return true; |
| } |
| |
| } // namespace |
| |
| // static |
| std::unique_ptr<SignedExchangeCertFetcher> |
| SignedExchangeCertFetcher::CreateAndStart( |
| scoped_refptr<SharedURLLoaderFactory> shared_url_loader_factory, |
| std::vector<std::unique_ptr<URLLoaderThrottle>> throttles, |
| const GURL& cert_url, |
| url::Origin request_initiator, |
| bool force_fetch, |
| CertificateCallback callback) { |
| std::unique_ptr<SignedExchangeCertFetcher> cert_fetcher( |
| new SignedExchangeCertFetcher( |
| std::move(shared_url_loader_factory), std::move(throttles), cert_url, |
| std::move(request_initiator), force_fetch, std::move(callback))); |
| cert_fetcher->Start(); |
| return cert_fetcher; |
| } |
| |
| // static |
| base::Optional<std::vector<base::StringPiece>> |
| SignedExchangeCertFetcher::GetCertChainFromMessage(base::StringPiece message) { |
| uint8_t cert_request_context_size = 0; |
| if (!ConsumeByte(&message, &cert_request_context_size)) { |
| DVLOG(1) << "Can't read certificate request request context size."; |
| return base::nullopt; |
| } |
| if (cert_request_context_size != 0) { |
| DVLOG(1) << "Invalid certificate request context size: " |
| << static_cast<int>(cert_request_context_size); |
| return base::nullopt; |
| } |
| uint32_t cert_list_size = 0; |
| if (!Consume3Bytes(&message, &cert_list_size)) { |
| DVLOG(1) << "Can't read certificate list size."; |
| return base::nullopt; |
| } |
| |
| if (cert_list_size != message.length()) { |
| DVLOG(1) << "Certificate list size error: cert_list_size=" << cert_list_size |
| << " remaining=" << message.length(); |
| return base::nullopt; |
| } |
| |
| std::vector<base::StringPiece> certs; |
| while (!message.empty()) { |
| uint32_t cert_data_size = 0; |
| if (!Consume3Bytes(&message, &cert_data_size)) { |
| DVLOG(1) << "Can't read certificate data size."; |
| return base::nullopt; |
| } |
| if (message.length() < cert_data_size) { |
| DVLOG(1) << "Certificate data size error: cert_data_size=" |
| << cert_data_size << " remaining=" << message.length(); |
| return base::nullopt; |
| } |
| certs.emplace_back(message.substr(0, cert_data_size)); |
| message.remove_prefix(cert_data_size); |
| |
| uint16_t extensions_size = 0; |
| if (!Consume2Bytes(&message, &extensions_size)) { |
| DVLOG(1) << "Can't read extensions size."; |
| return base::nullopt; |
| } |
| if (message.length() < extensions_size) { |
| DVLOG(1) << "Extensions size error: extensions_size=" << extensions_size |
| << " remaining=" << message.length(); |
| return base::nullopt; |
| } |
| message.remove_prefix(extensions_size); |
| } |
| return certs; |
| } |
| |
| SignedExchangeCertFetcher::SignedExchangeCertFetcher( |
| scoped_refptr<SharedURLLoaderFactory> shared_url_loader_factory, |
| std::vector<std::unique_ptr<URLLoaderThrottle>> throttles, |
| const GURL& cert_url, |
| url::Origin request_initiator, |
| bool force_fetch, |
| CertificateCallback callback) |
| : shared_url_loader_factory_(std::move(shared_url_loader_factory)), |
| throttles_(std::move(throttles)), |
| resource_request_(std::make_unique<network::ResourceRequest>()), |
| callback_(std::move(callback)) { |
| // TODO(https://crbug.com/803774): Revisit more ResourceRequest flags. |
| resource_request_->url = cert_url; |
| resource_request_->request_initiator = std::move(request_initiator); |
| resource_request_->resource_type = RESOURCE_TYPE_SUB_RESOURCE; |
| // Cert requests should not send credential informartion, because the default |
| // credentials mode of Fetch is "omit". |
| resource_request_->load_flags = net::LOAD_DO_NOT_SEND_AUTH_DATA | |
| net::LOAD_DO_NOT_SAVE_COOKIES | |
| net::LOAD_DO_NOT_SEND_COOKIES; |
| if (force_fetch) { |
| resource_request_->load_flags |= |
| net::LOAD_DISABLE_CACHE | net::LOAD_BYPASS_CACHE; |
| } |
| resource_request_->render_frame_id = MSG_ROUTING_NONE; |
| } |
| |
| SignedExchangeCertFetcher::~SignedExchangeCertFetcher() = default; |
| |
| void SignedExchangeCertFetcher::Start() { |
| url_loader_ = ThrottlingURLLoader::CreateLoaderAndStart( |
| std::move(shared_url_loader_factory_), std::move(throttles_), |
| 0 /* routing_id */, 0 /* request_id */, |
| network::mojom::kURLLoadOptionNone, resource_request_.get(), this, |
| kCertFetcherTrafficAnnotation, base::ThreadTaskRunnerHandle::Get()); |
| } |
| |
| void SignedExchangeCertFetcher::Abort() { |
| DCHECK(callback_); |
| url_loader_ = nullptr; |
| body_.reset(); |
| handle_watcher_ = nullptr; |
| body_string_.clear(); |
| std::move(callback_).Run(scoped_refptr<net::X509Certificate>()); |
| } |
| |
| void SignedExchangeCertFetcher::OnHandleReady(MojoResult result) { |
| const void* buffer = nullptr; |
| uint32_t num_bytes = 0; |
| MojoResult rv = |
| body_->BeginReadData(&buffer, &num_bytes, MOJO_READ_DATA_FLAG_NONE); |
| if (rv == MOJO_RESULT_OK) { |
| if (body_string_.size() + num_bytes > g_max_cert_size_for_signed_exchange) { |
| body_->EndReadData(num_bytes); |
| Abort(); |
| return; |
| } |
| body_string_.append(static_cast<const char*>(buffer), num_bytes); |
| body_->EndReadData(num_bytes); |
| } else if (rv == MOJO_RESULT_FAILED_PRECONDITION) { |
| OnDataComplete(); |
| } else { |
| DCHECK_EQ(MOJO_RESULT_SHOULD_WAIT, rv); |
| } |
| } |
| |
| void SignedExchangeCertFetcher::OnDataComplete() { |
| DCHECK(callback_); |
| url_loader_ = nullptr; |
| body_.reset(); |
| handle_watcher_ = nullptr; |
| base::Optional<std::vector<base::StringPiece>> der_certs = |
| GetCertChainFromMessage(body_string_); |
| if (!der_certs) { |
| body_string_.clear(); |
| std::move(callback_).Run(scoped_refptr<net::X509Certificate>()); |
| return; |
| } |
| scoped_refptr<net::X509Certificate> cert = |
| net::X509Certificate::CreateFromDERCertChain(*der_certs); |
| body_string_.clear(); |
| std::move(callback_).Run(std::move(cert)); |
| } |
| |
| // network::mojom::URLLoaderClient |
| void SignedExchangeCertFetcher::OnReceiveResponse( |
| const network::ResourceResponseHead& head, |
| const base::Optional<net::SSLInfo>& ssl_info, |
| network::mojom::DownloadedTempFilePtr downloaded_file) { |
| if (head.headers->response_code() != net::HTTP_OK) { |
| Abort(); |
| return; |
| } |
| if (head.content_length > 0) { |
| if (base::checked_cast<size_t>(head.content_length) > |
| g_max_cert_size_for_signed_exchange) { |
| Abort(); |
| return; |
| } |
| body_string_.reserve(head.content_length); |
| } |
| } |
| |
| void SignedExchangeCertFetcher::OnReceiveRedirect( |
| const net::RedirectInfo& redirect_info, |
| const network::ResourceResponseHead& head) { |
| // Currently the cert fetcher doesn't allow any redirects. |
| Abort(); |
| } |
| |
| void SignedExchangeCertFetcher::OnDataDownloaded(int64_t data_length, |
| int64_t encoded_length) { |
| // Cert fetching is not a downloading job. |
| NOTREACHED(); |
| } |
| |
| void SignedExchangeCertFetcher::OnUploadProgress( |
| int64_t current_position, |
| int64_t total_size, |
| OnUploadProgressCallback callback) { |
| // Cert fetching doesn't have request body. |
| NOTREACHED(); |
| } |
| |
| void SignedExchangeCertFetcher::OnReceiveCachedMetadata( |
| const std::vector<uint8_t>& data) { |
| // Cert fetching doesn't use cached metadata. |
| NOTREACHED(); |
| } |
| |
| void SignedExchangeCertFetcher::OnTransferSizeUpdated( |
| int32_t transfer_size_diff) { |
| // Do nothing. |
| } |
| |
| void SignedExchangeCertFetcher::OnStartLoadingResponseBody( |
| mojo::ScopedDataPipeConsumerHandle body) { |
| body_ = std::move(body); |
| handle_watcher_ = std::make_unique<mojo::SimpleWatcher>( |
| FROM_HERE, mojo::SimpleWatcher::ArmingPolicy::AUTOMATIC); |
| handle_watcher_->Watch( |
| body_.get(), MOJO_HANDLE_SIGNAL_READABLE, |
| base::BindRepeating(&SignedExchangeCertFetcher::OnHandleReady, |
| base::Unretained(this))); |
| } |
| |
| void SignedExchangeCertFetcher::OnComplete( |
| const network::URLLoaderCompletionStatus& status) { |
| if (!handle_watcher_) |
| Abort(); |
| } |
| |
| // static |
| base::ScopedClosureRunner SignedExchangeCertFetcher::SetMaxCertSizeForTest( |
| size_t max_cert_size) { |
| g_max_cert_size_for_signed_exchange = max_cert_size; |
| return base::ScopedClosureRunner(base::BindOnce(&ResetMaxCertSizeForTest)); |
| } |
| |
| } // namespace content |