| // Copyright (c) 2013 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 "chrome/browser/ash/attestation/attestation_ca_client.h" |
| |
| #include <string> |
| |
| #include "base/bind.h" |
| #include "base/command_line.h" |
| #include "base/location.h" |
| #include "base/threading/thread_task_runner_handle.h" |
| #include "chrome/browser/browser_process.h" |
| #include "chrome/browser/net/system_network_context_manager.h" |
| #include "chromeos/dbus/constants/dbus_switches.h" |
| #include "mojo/public/cpp/bindings/receiver.h" |
| #include "net/base/load_flags.h" |
| #include "net/base/net_errors.h" |
| #include "net/base/network_isolation_key.h" |
| #include "net/http/http_status_code.h" |
| #include "services/network/public/cpp/resource_request.h" |
| #include "services/network/public/cpp/shared_url_loader_factory.h" |
| #include "services/network/public/cpp/simple_url_loader.h" |
| #include "services/network/public/mojom/network_context.mojom.h" |
| #include "services/network/public/mojom/proxy_lookup_client.mojom.h" |
| #include "services/network/public/mojom/url_response_head.mojom.h" |
| #include "url/gurl.h" |
| |
| namespace { |
| // Values for the attestation server switch. |
| const char kAttestationServerDefault[] = "default"; |
| const char kAttestationServerTest[] = "test"; |
| |
| // Endpoints for the default Google Privacy CA operations. |
| const char kDefaultEnrollRequestURL[] = |
| "https://chromeos-ca.gstatic.com/enroll"; |
| const char kDefaultCertificateRequestURL[] = |
| "https://chromeos-ca.gstatic.com/sign"; |
| |
| // Endpoints for the test Google Privacy CA operations. |
| const char kTestEnrollRequestURL[] = |
| "https://asbestos-qa.corp.google.com/enroll"; |
| const char kTestCertificateRequestURL[] = |
| "https://asbestos-qa.corp.google.com/sign"; |
| |
| const char kMimeContentType[] = "application/octet-stream"; |
| |
| } // namespace |
| |
| namespace ash { |
| namespace attestation { |
| |
| namespace { |
| |
| class CAProxyLookupClient : public network::mojom::ProxyLookupClient { |
| public: |
| // Not copyable nor movable. |
| CAProxyLookupClient(const CAProxyLookupClient&) = delete; |
| CAProxyLookupClient& operator=(const CAProxyLookupClient&) = delete; |
| CAProxyLookupClient(CAProxyLookupClient&&) = delete; |
| CAProxyLookupClient& operator=(CAProxyLookupClient&&) = delete; |
| |
| static void LookUpProxyForURL( |
| network::mojom::NetworkContext* network_context, |
| const GURL& url, |
| AttestationCAClient::ProxyPresenceCallback callback) { |
| // The created object will be deleted in `OnProxyLookupComplete()`. |
| CAProxyLookupClient* client = |
| new CAProxyLookupClient(network_context, url, std::move(callback)); |
| client->Start(network_context, url); |
| } |
| |
| // network::mojom::ProxyLookupClient: |
| void OnProxyLookupComplete( |
| int32_t net_error, |
| const absl::optional<net::ProxyInfo>& proxy_info) override { |
| LOG_IF(WARNING, !proxy_info.has_value()) |
| << " Error determining the proxy information: " << net_error; |
| // Assume there is a proxy if failing to get proxy information. |
| const bool has_proxy = !proxy_info.has_value() || !proxy_info->is_direct(); |
| receiver_.reset(); |
| std::move(callback_).Run(has_proxy); |
| delete this; |
| } |
| |
| private: |
| CAProxyLookupClient(network::mojom::NetworkContext* network_context, |
| const GURL& url, |
| AttestationCAClient::ProxyPresenceCallback callback) |
| : callback_(std::move(callback)) { |
| CHECK(network_context); |
| } |
| void Start(network::mojom::NetworkContext* network_context, const GURL& url) { |
| const net::NetworkIsolationKey network_isolation_key = |
| net::NetworkIsolationKey::CreateTransient(); |
| mojo::PendingRemote<network::mojom::ProxyLookupClient> proxy_lookup_client = |
| receiver_.BindNewPipeAndPassRemote(); |
| receiver_.set_disconnect_handler(base::BindOnce( |
| &CAProxyLookupClient::OnProxyLookupComplete, base::Unretained(this), |
| net::ERR_ABORTED, absl::nullopt)); |
| |
| network_context->LookUpProxyForURL(url, network_isolation_key, |
| std::move(proxy_lookup_client)); |
| } |
| |
| AttestationCAClient::ProxyPresenceCallback callback_; |
| |
| mojo::Receiver<network::mojom::ProxyLookupClient> receiver_{this}; |
| }; |
| |
| } // namespace |
| |
| static PrivacyCAType GetAttestationServerType() { |
| std::string value = |
| base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII( |
| chromeos::switches::kAttestationServer); |
| if (value.empty() || value == kAttestationServerDefault) { |
| return DEFAULT_PCA; |
| } |
| if (value == kAttestationServerTest) { |
| return TEST_PCA; |
| } |
| LOG(WARNING) << "Invalid attestation server value: " << value |
| << ". Using default."; |
| return DEFAULT_PCA; |
| } |
| |
| AttestationCAClient::AttestationCAClient() { |
| pca_type_ = GetAttestationServerType(); |
| } |
| |
| AttestationCAClient::~AttestationCAClient() {} |
| |
| void AttestationCAClient::SendEnrollRequest(const std::string& request, |
| DataCallback on_response) { |
| FetchURL( |
| GetType() == TEST_PCA ? kTestEnrollRequestURL : kDefaultEnrollRequestURL, |
| request, std::move(on_response)); |
| } |
| |
| void AttestationCAClient::SendCertificateRequest(const std::string& request, |
| DataCallback on_response) { |
| FetchURL(GetType() == TEST_PCA ? kTestCertificateRequestURL |
| : kDefaultCertificateRequestURL, |
| request, std::move(on_response)); |
| } |
| |
| void AttestationCAClient::OnURLLoadComplete( |
| std::list<std::unique_ptr<network::SimpleURLLoader>>::iterator it, |
| DataCallback callback, |
| std::unique_ptr<std::string> response_body) { |
| // Move the loader out of the active loaders list. |
| std::unique_ptr<network::SimpleURLLoader> url_loader = std::move(*it); |
| url_loaders_.erase(it); |
| |
| DCHECK(url_loader); |
| |
| if (url_loader->ResponseInfo() && url_loader->ResponseInfo()->headers) { |
| int response_code = url_loader->ResponseInfo()->headers->response_code(); |
| |
| if (response_code < 200 || response_code > 299) { |
| LOG(ERROR) << "Attestation CA sent an HTTP error response: " |
| << response_code; |
| std::move(callback).Run(false, ""); |
| return; |
| } |
| } |
| |
| if (!response_body) { |
| int net_error = url_loader->NetError(); |
| LOG(ERROR) << "Attestation CA request failed, error: " |
| << net::ErrorToString(net_error); |
| std::move(callback).Run(false, ""); |
| return; |
| } |
| |
| // Run the callback last because it may delete |this|. |
| std::move(callback).Run(true, *response_body); |
| } |
| |
| void AttestationCAClient::FetchURL(const std::string& url, |
| const std::string& request, |
| DataCallback on_response) { |
| const net::NetworkTrafficAnnotationTag traffic_annotation = |
| net::DefineNetworkTrafficAnnotation("attestation_ca_client", R"( |
| semantics { |
| sender: "Attestation CA client" |
| description: |
| "Sends requests to the Attestation CA as part of the remote " |
| "attestation feature, such as enrolling for remote attestation or " |
| "to obtain an attestation certificate." |
| trigger: |
| "Device enrollment, content protection or get an attestation " |
| "certificate for a hardware-protected key." |
| data: |
| "The data from AttestationCertificateRequest or from " |
| "AttestationEnrollmentRequest message from " |
| "cryptohome/attestation.proto. Some of the important data being " |
| "encrypted endorsement certificate, attestation identity public " |
| "key, PCR0 and PCR1 TPM values." |
| destination: GOOGLE_OWNED_SERVICE |
| } |
| policy { |
| cookies_allowed: NO |
| setting: |
| "The device setting DeviceAttestationEnabled can disable the " |
| "attestation requests and AttestationForContentProtectionEnabled " |
| "can disable the attestation for content protection. But they " |
| "cannot be controlled by a policy or through settings." |
| policy_exception_justification: "Not implemented." |
| })"); |
| auto resource_request = std::make_unique<network::ResourceRequest>(); |
| resource_request->url = GURL(url); |
| resource_request->method = "POST"; |
| resource_request->load_flags = net::LOAD_DISABLE_CACHE; |
| resource_request->credentials_mode = network::mojom::CredentialsMode::kOmit; |
| |
| auto url_loader = network::SimpleURLLoader::Create( |
| std::move(resource_request), traffic_annotation); |
| url_loader->AttachStringForUpload(request, kMimeContentType); |
| |
| auto* raw_url_loader = url_loader.get(); |
| url_loaders_.push_back(std::move(url_loader)); |
| |
| raw_url_loader->DownloadToStringOfUnboundedSizeUntilCrashAndDie( |
| g_browser_process->shared_url_loader_factory().get(), |
| base::BindOnce(&AttestationCAClient::OnURLLoadComplete, |
| base::Unretained(this), std::move(--url_loaders_.end()), |
| std::move(on_response))); |
| } |
| |
| PrivacyCAType AttestationCAClient::GetType() { |
| return pca_type_; |
| } |
| |
| void AttestationCAClient::CheckIfAnyProxyPresent( |
| ProxyPresenceCallback callback) { |
| GURL url(GetType() == TEST_PCA ? kTestEnrollRequestURL |
| : kDefaultEnrollRequestURL); |
| DCHECK(url.is_valid()); |
| |
| network::mojom::NetworkContext* network_context = nullptr; |
| // Uses the injected network context if present. |
| if (network_context_for_testing_ != nullptr) { |
| network_context = network_context_for_testing_; |
| } else if (!g_browser_process || |
| !g_browser_process->system_network_context_manager() || |
| !g_browser_process->system_network_context_manager() |
| ->GetContext()) { |
| LOG(DFATAL) << "No valid system network context."; |
| std::move(callback).Run(/*is_any_proxy_present=*/true); |
| return; |
| } else { |
| network_context = |
| g_browser_process->system_network_context_manager()->GetContext(); |
| } |
| |
| CAProxyLookupClient::LookUpProxyForURL(network_context, url.GetOrigin(), |
| std::move(callback)); |
| } |
| |
| } // namespace attestation |
| } // namespace ash |