Separate Masque OHTTP Client into its own library and update connection pool to support Private key and Public Cert via blobs PiperOrigin-RevId: 827478186
diff --git a/build/source_list.bzl b/build/source_list.bzl index 9fd77f0..b42f07a 100644 --- a/build/source_list.bzl +++ b/build/source_list.bzl
@@ -1016,6 +1016,7 @@ "quic/masque/masque_encapsulated_client.h", "quic/masque/masque_encapsulated_client_session.h", "quic/masque/masque_h2_connection.h", + "quic/masque/masque_ohttp_client.h", "quic/masque/masque_server.h", "quic/masque/masque_server_backend.h", "quic/masque/masque_server_session.h", @@ -1030,6 +1031,7 @@ "quic/masque/masque_encapsulated_client.cc", "quic/masque/masque_encapsulated_client_session.cc", "quic/masque/masque_h2_connection.cc", + "quic/masque/masque_ohttp_client.cc", "quic/masque/masque_server.cc", "quic/masque/masque_server_backend.cc", "quic/masque/masque_server_session.cc",
diff --git a/build/source_list.gni b/build/source_list.gni index be8a3b4..7d6d1d0 100644 --- a/build/source_list.gni +++ b/build/source_list.gni
@@ -1016,6 +1016,7 @@ "src/quiche/quic/masque/masque_encapsulated_client.h", "src/quiche/quic/masque/masque_encapsulated_client_session.h", "src/quiche/quic/masque/masque_h2_connection.h", + "src/quiche/quic/masque/masque_ohttp_client.h", "src/quiche/quic/masque/masque_server.h", "src/quiche/quic/masque/masque_server_backend.h", "src/quiche/quic/masque/masque_server_session.h", @@ -1030,6 +1031,7 @@ "src/quiche/quic/masque/masque_encapsulated_client.cc", "src/quiche/quic/masque/masque_encapsulated_client_session.cc", "src/quiche/quic/masque/masque_h2_connection.cc", + "src/quiche/quic/masque/masque_ohttp_client.cc", "src/quiche/quic/masque/masque_server.cc", "src/quiche/quic/masque/masque_server_backend.cc", "src/quiche/quic/masque/masque_server_session.cc",
diff --git a/build/source_list.json b/build/source_list.json index 41abf16..5e340a3 100644 --- a/build/source_list.json +++ b/build/source_list.json
@@ -1015,6 +1015,7 @@ "quiche/quic/masque/masque_encapsulated_client.h", "quiche/quic/masque/masque_encapsulated_client_session.h", "quiche/quic/masque/masque_h2_connection.h", + "quiche/quic/masque/masque_ohttp_client.h", "quiche/quic/masque/masque_server.h", "quiche/quic/masque/masque_server_backend.h", "quiche/quic/masque/masque_server_session.h", @@ -1029,6 +1030,7 @@ "quiche/quic/masque/masque_encapsulated_client.cc", "quiche/quic/masque/masque_encapsulated_client_session.cc", "quiche/quic/masque/masque_h2_connection.cc", + "quiche/quic/masque/masque_ohttp_client.cc", "quiche/quic/masque/masque_server.cc", "quiche/quic/masque/masque_server_backend.cc", "quiche/quic/masque/masque_server_session.cc",
diff --git a/quiche/quic/masque/masque_connection_pool.cc b/quiche/quic/masque/masque_connection_pool.cc index 8481f98..8ba6594 100644 --- a/quiche/quic/masque/masque_connection_pool.cc +++ b/quiche/quic/masque/masque_connection_pool.cc
@@ -375,4 +375,36 @@ return ctx; } +// static +absl::StatusOr<bssl::UniquePtr<SSL_CTX>> +MasqueConnectionPool::CreateSslCtxFromData( + const std::string& client_cert_pem_data, + const std::string& client_cert_key_data) { + bssl::UniquePtr<SSL_CTX> ctx(SSL_CTX_new(TLS_method())); + // Load public cert. + BIO* cert_bio = BIO_new_mem_buf(client_cert_pem_data.c_str(), -1); + QUICHE_CHECK(cert_bio); + X509* cert = PEM_read_bio_X509(cert_bio, nullptr, nullptr, nullptr); + QUICHE_CHECK(cert); + BIO_free(cert_bio); + int rv = SSL_CTX_use_certificate(ctx.get(), cert); + QUICHE_CHECK_EQ(rv, 1); + X509_free(cert); + + // Load private key. + BIO* key_bio = BIO_new_mem_buf(client_cert_key_data.c_str(), -1); + QUICHE_CHECK(key_bio); + EVP_PKEY* private_key = + PEM_read_bio_PrivateKey(key_bio, nullptr, nullptr, nullptr); + QUICHE_CHECK(private_key); + BIO_free(key_bio); + rv = SSL_CTX_use_PrivateKey(ctx.get(), private_key); + QUICHE_CHECK_EQ(rv, 1); + EVP_PKEY_free(private_key); + + SSL_CTX_set_min_proto_version(ctx.get(), TLS1_2_VERSION); + SSL_CTX_set_max_proto_version(ctx.get(), TLS1_3_VERSION); + return ctx; +} + } // namespace quic
diff --git a/quiche/quic/masque/masque_connection_pool.h b/quiche/quic/masque/masque_connection_pool.h index b70c391..89ab5ca 100644 --- a/quiche/quic/masque/masque_connection_pool.h +++ b/quiche/quic/masque/masque_connection_pool.h
@@ -66,6 +66,10 @@ const std::string& client_cert_file, const std::string& client_cert_key_file); + static absl::StatusOr<bssl::UniquePtr<SSL_CTX>> CreateSslCtxFromData( + const std::string& client_cert_pem_data, + const std::string& client_cert_key_data); + private: class ConnectionState : public QuicSocketEventListener { public:
diff --git a/quiche/quic/masque/masque_ohttp_client.cc b/quiche/quic/masque/masque_ohttp_client.cc new file mode 100644 index 0000000..0e08b59 --- /dev/null +++ b/quiche/quic/masque/masque_ohttp_client.cc
@@ -0,0 +1,284 @@ +// Copyright 2025 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 "quiche/quic/masque/masque_ohttp_client.h" + +#include <optional> +#include <ostream> +#include <string> +#include <utility> + +#include "absl/status/status.h" +#include "absl/status/statusor.h" +#include "absl/strings/match.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/string_view.h" +#include "openssl/base.h" +#include "quiche/quic/masque/masque_connection_pool.h" +#include "quiche/quic/tools/quic_url.h" +#include "quiche/binary_http/binary_http_message.h" +#include "quiche/common/platform/api/quiche_logging.h" +#include "quiche/common/quiche_status_utils.h" +#include "quiche/oblivious_http/buffers/oblivious_http_request.h" +#include "quiche/oblivious_http/buffers/oblivious_http_response.h" +#include "quiche/oblivious_http/common/oblivious_http_header_key_config.h" +#include "quiche/oblivious_http/oblivious_http_client.h" + +namespace quic { + +using ::quic::MasqueConnectionPool; +using ::quic::QuicUrl; +using ::quiche::BinaryHttpRequest; +using ::quiche::BinaryHttpResponse; +using ::quiche::ObliviousHttpClient; +using ::quiche::ObliviousHttpHeaderKeyConfig; +using ::quiche::ObliviousHttpKeyConfigs; +using ::quiche::ObliviousHttpRequest; +using ::quiche::ObliviousHttpResponse; +using RequestId = ::quic::MasqueConnectionPool::RequestId; +using Message = ::quic::MasqueConnectionPool::Message; + +absl::Status MasqueOhttpClient::Start() { + if (urls_.empty()) { + QUICHE_LOG(ERROR) << "No URLs to request"; + Abort(); + return absl::InvalidArgumentError("No URLs to request"); + } + absl::Status status = StartKeyFetch(urls_[0]); + if (!status.ok()) { + Abort(); + return status; + } + return absl::OkStatus(); +} +bool MasqueOhttpClient::IsDone() { + if (aborted_) { + return true; + } + if (!ohttp_client_.has_value()) { + // Key fetch request is still pending. + return false; + } + return pending_ohttp_requests_.empty(); +} + +absl::StatusOr<QuicUrl> ParseUrl(const std::string& url_string) { + QuicUrl url(url_string, "https"); + if (url.host().empty() && !absl::StrContains(url_string, "://")) { + url = QuicUrl(absl::StrCat("https://", url_string)); + } + if (url.host().empty()) { + return absl::InvalidArgumentError( + absl::StrCat("Failed to parse key URL ", url_string)); + } + return url; +} + +absl::Status MasqueOhttpClient::StartKeyFetch(const std::string& url_string) { + QuicUrl url(url_string, "https"); + if (url.host().empty() && !absl::StrContains(url_string, "://")) { + url = QuicUrl(absl::StrCat("https://", url_string)); + } + if (url.host().empty()) { + QUICHE_LOG(ERROR) << "Failed to parse key URL \"" << url_string << "\""; + return absl::InvalidArgumentError( + absl::StrCat("Failed to parse key URL ", url_string)); + } + Message request; + request.headers[":method"] = "GET"; + request.headers[":scheme"] = url.scheme(); + request.headers[":authority"] = url.HostPort(); + request.headers[":path"] = url.path(); + request.headers["accept"] = "application/ohttp-keys"; + request.headers["content-type"] = "application/ohttp-keys"; + absl::StatusOr<RequestId> request_id = connection_pool_.SendRequest(request); + if (!request_id.ok()) { + QUICHE_LOG(ERROR) << "Failed to send request: " << request_id.status(); + return request_id.status(); + } + key_fetch_request_id_ = *request_id; + return absl::OkStatus(); +} + +absl::Status MasqueOhttpClient::HandleKeyResponse( + const absl::StatusOr<Message>& response) { + key_fetch_request_id_ = std::nullopt; + + if (!response.ok()) { + QUICHE_LOG(ERROR) << "Failed to fetch key: " << response.status(); + return response.status(); + } + QUICHE_LOG(INFO) << "Received key response: " + << response->headers.DebugString(); + absl::StatusOr<ObliviousHttpKeyConfigs> key_configs = + ObliviousHttpKeyConfigs::ParseConcatenatedKeys(response->body); + if (!key_configs.ok()) { + QUICHE_LOG(ERROR) << "Failed to parse OHTTP keys: " << key_configs.status(); + Abort(); + return key_configs.status(); + } + QUICHE_LOG(INFO) << "Successfully got " << key_configs->NumKeys() + << " OHTTP keys: " << std::endl + << key_configs->DebugString(); + if (urls_.size() <= 2) { + QUICHE_LOG(INFO) << "No OHTTP URLs to request, exiting."; + Abort(); + return absl::InvalidArgumentError("No OHTTP URLs to request, exiting."); + } + relay_url_ = QuicUrl(urls_[1], "https"); + if (relay_url_.host().empty() && !absl::StrContains(urls_[1], "://")) { + relay_url_ = QuicUrl(absl::StrCat("https://", urls_[1])); + } + QUICHE_LOG(INFO) << "Using relay URL: " << relay_url_.ToString(); + ObliviousHttpHeaderKeyConfig key_config = key_configs->PreferredConfig(); + absl::StatusOr<absl::string_view> public_key = + key_configs->GetPublicKeyForId(key_config.GetKeyId()); + if (!public_key.ok()) { + QUICHE_LOG(ERROR) << "Failed to get public key for key ID " + << static_cast<int>(key_config.GetKeyId()) << ": " + << public_key.status(); + Abort(); + return public_key.status(); + } + absl::StatusOr<ObliviousHttpClient> ohttp_client = + ObliviousHttpClient::Create(*public_key, key_config); + if (!ohttp_client.ok()) { + QUICHE_LOG(ERROR) << "Failed to create OHTTP client: " + << ohttp_client.status(); + Abort(); + return ohttp_client.status(); + } + ohttp_client_.emplace(std::move(*ohttp_client)); + for (size_t i = 2; i < urls_.size(); ++i) { + QUICHE_RETURN_IF_ERROR(SendOhttpRequestForUrl(urls_[i])); + } + return absl::OkStatus(); +} + +absl::Status MasqueOhttpClient::SendOhttpRequestForUrl( + const std::string& url_string) { + QuicUrl url(url_string, "https"); + if (url.host().empty() && !absl::StrContains(url_string, "://")) { + url = QuicUrl(absl::StrCat("https://", url_string)); + } + if (url.host().empty()) { + return absl::InvalidArgumentError( + absl::StrCat("Failed to parse key URL ", url_string)); + } + BinaryHttpRequest::ControlData control_data; + control_data.method = post_data_.empty() ? "GET" : "POST"; + control_data.scheme = url.scheme(); + control_data.authority = url.HostPort(); + control_data.path = url.path(); + BinaryHttpRequest binary_request(control_data); + binary_request.set_body(post_data_); + absl::StatusOr<std::string> encoded_request = binary_request.Serialize(); + if (!encoded_request.ok()) { + return encoded_request.status(); + } + if (!ohttp_client_.has_value()) { + QUICHE_LOG(FATAL) << "Cannot send OHTTP request without OHTTP client"; + return absl::InternalError( + "Cannot send OHTTP request without OHTTP client"); + } + absl::StatusOr<ObliviousHttpRequest> ohttp_request = + ohttp_client_->CreateObliviousHttpRequest(*encoded_request); + if (!ohttp_request.ok()) { + QUICHE_LOG(ERROR) << "Failed to create OHTTP request: " + << ohttp_request.status(); + return ohttp_request.status(); + } + Message request; + request.headers[":method"] = "POST"; + request.headers[":scheme"] = relay_url_.scheme(); + request.headers[":authority"] = relay_url_.HostPort(); + request.headers[":path"] = relay_url_.path(); + request.headers["content-type"] = "message/ohttp-req"; + request.body = ohttp_request->EncapsulateAndSerialize(); + absl::StatusOr<RequestId> request_id = connection_pool_.SendRequest(request); + if (!request_id.ok()) { + QUICHE_LOG(ERROR) << "Failed to send request: " << request_id.status(); + return request_id.status(); + } + QUICHE_LOG(INFO) << "Sent OHTTP request for " << url_string; + auto context = std::move(*ohttp_request).ReleaseContext(); + pending_ohttp_requests_.insert({*request_id, std::move(context)}); + return absl::OkStatus(); +} + +absl::StatusOr<BinaryHttpResponse> MasqueOhttpClient::TryExtractBinaryResponse( + const RequestId request_id, quiche::ObliviousHttpRequest::Context& context, + const absl::StatusOr<Message>& response) { + if (response.ok()) { + if (!ohttp_client_.has_value()) { + QUICHE_LOG(FATAL) << "Received OHTTP response without OHTTP client"; + return absl::InternalError( + "Received OHTTP response without OHTTP client"); + } + absl::StatusOr<ObliviousHttpResponse> ohttp_response = + ohttp_client_->DecryptObliviousHttpResponse(response->body, context); + if (ohttp_response.ok()) { + QUICHE_LOG(INFO) << "Received OHTTP response for " << request_id; + absl::StatusOr<BinaryHttpResponse> binary_response = + BinaryHttpResponse::Create(ohttp_response->GetPlaintextData()); + if (binary_response.ok()) { + QUICHE_LOG(INFO) << "Successfully decoded OHTTP response:"; + QUICHE_LOG(INFO) << "Status: " << binary_response->status_code(); + for (const quiche::BinaryHttpMessage::Field& field : + binary_response->GetHeaderFields()) { + QUICHE_LOG(INFO) << field.name << ": " << field.value; + } + QUICHE_LOG(INFO) << "Body:" << std::endl << binary_response->body(); + return binary_response; + } else { + QUICHE_LOG(ERROR) << "Failed to parse binary response: " + << binary_response.status(); + return binary_response; + } + } else { + QUICHE_LOG(ERROR) << "Failed to decrypt OHTTP response: " + << ohttp_response.status(); + return ohttp_response.status(); + } + } else { + QUICHE_LOG(ERROR) << "OHTTP request " << request_id + << " failed: " << response.status(); + return response.status(); + } +} + +absl::Status MasqueOhttpClient::HandleOhttpResponse( + RequestId request_id, const absl::StatusOr<Message>& response) { + auto it = pending_ohttp_requests_.find(request_id); + if (it == pending_ohttp_requests_.end()) { + QUICHE_LOG(ERROR) << "Received unexpected response for unknown request " + << request_id; + Abort(); + return absl::InternalError( + "Received unexpected response for unknown request"); + } + absl::StatusOr<BinaryHttpResponse> binary_response = + TryExtractBinaryResponse(request_id, it->second, response); + absl::Status status = HandleBinaryResponse(binary_response); + pending_ohttp_requests_.erase(it); + return status; +} + +void MasqueOhttpClient::OnResponse(MasqueConnectionPool* /*pool*/, + RequestId request_id, + const absl::StatusOr<Message>& response) { + if (key_fetch_request_id_.has_value() && + *key_fetch_request_id_ == request_id) { + auto status = HandleKeyResponse(response); + if (!status.ok()) { + QUICHE_LOG(ERROR) << "Failed to handle key response: " << status; + } + } else { + auto status = HandleOhttpResponse(request_id, response); + if (!status.ok()) { + QUICHE_LOG(ERROR) << "Failed to handle OHTTP response: " << status; + } + } +} +} // namespace quic
diff --git a/quiche/quic/masque/masque_ohttp_client.h b/quiche/quic/masque/masque_ohttp_client.h new file mode 100644 index 0000000..f7a80cf --- /dev/null +++ b/quiche/quic/masque/masque_ohttp_client.h
@@ -0,0 +1,92 @@ +// Copyright 2025 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. + +#ifndef QUICHE_QUIC_MASQUE_MASQUE_OHTTP_CLIENT_H_ +#define QUICHE_QUIC_MASQUE_MASQUE_OHTTP_CLIENT_H_ + +#include <optional> +#include <string> +#include <vector> + +#include "absl/container/flat_hash_map.h" +#include "absl/status/status.h" +#include "absl/status/statusor.h" +#include "openssl/base.h" +#include "quiche/quic/core/io/quic_event_loop.h" +#include "quiche/quic/masque/masque_connection_pool.h" +#include "quiche/quic/tools/quic_url.h" +#include "quiche/binary_http/binary_http_message.h" +#include "quiche/common/platform/api/quiche_export.h" +#include "quiche/common/platform/api/quiche_logging.h" +#include "quiche/oblivious_http/buffers/oblivious_http_request.h" +#include "quiche/oblivious_http/oblivious_http_client.h" + +namespace quic { + +// A client that sends OHTTP requests through a relay/gateway to target URLs. +class QUICHE_EXPORT MasqueOhttpClient + : public quic::MasqueConnectionPool::Visitor { + public: + using RequestId = quic::MasqueConnectionPool::RequestId; + using Message = quic::MasqueConnectionPool::Message; + + explicit MasqueOhttpClient(quic::QuicEventLoop* event_loop, SSL_CTX* ssl_ctx, + std::vector<std::string> urls, + bool disable_certificate_verification, + int address_family_for_lookup, + const std::string& post_data) + : urls_(urls), + post_data_(post_data), + connection_pool_(event_loop, ssl_ctx, disable_certificate_verification, + address_family_for_lookup, this) {} + + // Starts fetching for the key and sends the OHTTP request. + absl::Status Start(); + + // Returns true if the client has completed all requests. + bool IsDone(); + + protected: + // From quic::MasqueConnectionPool::Visitor. + void OnResponse(quic::MasqueConnectionPool* /*pool*/, RequestId request_id, + const absl::StatusOr<Message>& response) override; + + // Fetch key from the key URL. + absl::Status StartKeyFetch(const std::string& url_string); + + // Handles the key response and starts the OHTTP request. + absl::Status HandleKeyResponse(const absl::StatusOr<Message>& response); + + // Sends the OHTTP request for the given URL. + absl::Status SendOhttpRequestForUrl(const std::string& url_string); + + // Signals the client to abort. + void Abort() { + QUICHE_LOG(INFO) << "Aborting"; + aborted_ = true; + } + absl::StatusOr<quiche::BinaryHttpResponse> TryExtractBinaryResponse( + RequestId request_id, quiche::ObliviousHttpRequest::Context& context, + const absl::StatusOr<Message>& response); + virtual absl::Status HandleOhttpResponse( + RequestId request_id, const absl::StatusOr<Message>& response); + virtual absl::Status HandleBinaryResponse( + const absl::StatusOr<quiche::BinaryHttpResponse>& binary_response) { + return binary_response.status(); + } + + private: + std::vector<std::string> urls_; + std::string post_data_; + quic::MasqueConnectionPool connection_pool_; + std::optional<RequestId> key_fetch_request_id_; + bool aborted_ = false; + std::optional<quiche::ObliviousHttpClient> ohttp_client_; + quic::QuicUrl relay_url_; + absl::flat_hash_map<RequestId, quiche::ObliviousHttpRequest::Context> + pending_ohttp_requests_; +}; +} // namespace quic + +#endif // QUICHE_QUIC_MASQUE_MASQUE_OHTTP_CLIENT_H_
diff --git a/quiche/quic/masque/masque_ohttp_client_bin.cc b/quiche/quic/masque/masque_ohttp_client_bin.cc index eaa3822..baa439f 100644 --- a/quiche/quic/masque/masque_ohttp_client_bin.cc +++ b/quiche/quic/masque/masque_ohttp_client_bin.cc
@@ -4,18 +4,11 @@ #include <stdbool.h> -#include <cstddef> #include <memory> -#include <optional> -#include <ostream> #include <string> -#include <utility> #include <vector> -#include "absl/container/flat_hash_map.h" #include "absl/status/statusor.h" -#include "absl/strings/match.h" -#include "absl/strings/str_cat.h" #include "absl/strings/string_view.h" #include "openssl/base.h" #include "quiche/quic/core/io/quic_default_event_loop.h" @@ -23,15 +16,10 @@ #include "quiche/quic/core/quic_default_clock.h" #include "quiche/quic/core/quic_time.h" #include "quiche/quic/masque/masque_connection_pool.h" -#include "quiche/quic/tools/quic_url.h" -#include "quiche/binary_http/binary_http_message.h" +#include "quiche/quic/masque/masque_ohttp_client.h" #include "quiche/common/platform/api/quiche_command_line_flags.h" #include "quiche/common/platform/api/quiche_logging.h" #include "quiche/common/platform/api/quiche_system_event_loop.h" -#include "quiche/oblivious_http/buffers/oblivious_http_request.h" -#include "quiche/oblivious_http/buffers/oblivious_http_response.h" -#include "quiche/oblivious_http/common/oblivious_http_header_key_config.h" -#include "quiche/oblivious_http/oblivious_http_client.h" DEFINE_QUICHE_COMMAND_LINE_FLAG( bool, disable_certificate_verification, false, @@ -52,251 +40,8 @@ std::string, post_data, "", "When set, the client will send a POST request with this data."); -using quiche::BinaryHttpRequest; -using quiche::BinaryHttpResponse; -using quiche::ObliviousHttpClient; -using quiche::ObliviousHttpHeaderKeyConfig; -using quiche::ObliviousHttpKeyConfigs; -using quiche::ObliviousHttpRequest; -using quiche::ObliviousHttpResponse; - namespace quic { namespace { - -class MasqueOhttpClient : public MasqueConnectionPool::Visitor { - public: - using RequestId = MasqueConnectionPool::RequestId; - using Message = MasqueConnectionPool::Message; - explicit MasqueOhttpClient(QuicEventLoop* event_loop, SSL_CTX* ssl_ctx, - std::vector<std::string> urls, - bool disable_certificate_verification, - int address_family_for_lookup, - const std::string& post_data) - : urls_(urls), - post_data_(post_data), - connection_pool_(event_loop, ssl_ctx, disable_certificate_verification, - address_family_for_lookup, this) {} - - bool Start() { - if (urls_.empty()) { - QUICHE_LOG(ERROR) << "No URLs to request"; - Abort(); - return false; - } - if (!StartKeyFetch(urls_[0])) { - Abort(); - return false; - } - return true; - } - bool IsDone() { - if (aborted_) { - return true; - } - if (!ohttp_client_.has_value()) { - // Key fetch request is still pending. - return false; - } - return pending_ohttp_requests_.empty(); - } - - // From MasqueConnectionPool::Visitor. - void OnResponse(MasqueConnectionPool* /*pool*/, RequestId request_id, - const absl::StatusOr<Message>& response) override { - if (key_fetch_request_id_.has_value() && - *key_fetch_request_id_ == request_id) { - key_fetch_request_id_ = std::nullopt; - HandleKeyResponse(response); - } else { - auto it = pending_ohttp_requests_.find(request_id); - if (it == pending_ohttp_requests_.end()) { - QUICHE_LOG(ERROR) << "Received unexpected response for unknown request " - << request_id; - Abort(); - return; - } - if (response.ok()) { - if (!ohttp_client_.has_value()) { - QUICHE_LOG(FATAL) << "Received OHTTP response without OHTTP client"; - return; - } - absl::StatusOr<ObliviousHttpResponse> ohttp_response = - ohttp_client_->DecryptObliviousHttpResponse(response->body, - it->second); - if (ohttp_response.ok()) { - QUICHE_LOG(INFO) << "Received OHTTP response for " << request_id; - absl::StatusOr<BinaryHttpResponse> binary_response = - BinaryHttpResponse::Create(ohttp_response->GetPlaintextData()); - if (binary_response.ok()) { - QUICHE_LOG(INFO) << "Successfully decoded OHTTP response:"; - QUICHE_LOG(INFO) << "Status: " << binary_response->status_code(); - for (const quiche::BinaryHttpMessage::Field& field : - binary_response->GetHeaderFields()) { - QUICHE_LOG(INFO) << field.name << ": " << field.value; - } - QUICHE_LOG(INFO) << "Body:" << std::endl << binary_response->body(); - } else { - QUICHE_LOG(ERROR) << "Failed to parse binary response: " - << binary_response.status(); - } - } else { - QUICHE_LOG(ERROR) << "Failed to decrypt OHTTP response: " - << ohttp_response.status(); - } - } else { - QUICHE_LOG(ERROR) << "OHTTP request " << request_id - << " failed: " << response.status(); - } - pending_ohttp_requests_.erase(it); - } - } - - private: - bool StartKeyFetch(const std::string& url_string) { - QuicUrl url(url_string, "https"); - if (url.host().empty() && !absl::StrContains(url_string, "://")) { - url = QuicUrl(absl::StrCat("https://", url_string)); - } - if (url.host().empty()) { - QUICHE_LOG(ERROR) << "Failed to parse key URL \"" << url_string << "\""; - return false; - } - Message request; - request.headers[":method"] = "GET"; - request.headers[":scheme"] = url.scheme(); - request.headers[":authority"] = url.HostPort(); - request.headers[":path"] = url.path(); - request.headers["accept"] = "application/ohttp-keys"; - request.headers["content-type"] = "application/ohttp-keys"; - absl::StatusOr<RequestId> request_id = - connection_pool_.SendRequest(request); - if (!request_id.ok()) { - QUICHE_LOG(ERROR) << "Failed to send request: " << request_id.status(); - return false; - } - key_fetch_request_id_ = *request_id; - return true; - } - - void HandleKeyResponse(const absl::StatusOr<Message>& response) { - if (!response.ok()) { - QUICHE_LOG(ERROR) << "Failed to fetch key: " << response.status(); - return; - } - QUICHE_LOG(INFO) << "Received key response: " - << response->headers.DebugString(); - absl::StatusOr<ObliviousHttpKeyConfigs> key_configs = - ObliviousHttpKeyConfigs::ParseConcatenatedKeys(response->body); - if (!key_configs.ok()) { - QUICHE_LOG(ERROR) << "Failed to parse OHTTP keys: " - << key_configs.status(); - Abort(); - return; - } - QUICHE_LOG(INFO) << "Successfully got " << key_configs->NumKeys() - << " OHTTP keys: " << std::endl - << key_configs->DebugString(); - if (urls_.size() <= 2) { - QUICHE_LOG(INFO) << "No OHTTP URLs to request, exiting."; - Abort(); - return; - } - relay_url_ = QuicUrl(urls_[1], "https"); - if (relay_url_.host().empty() && !absl::StrContains(urls_[1], "://")) { - relay_url_ = QuicUrl(absl::StrCat("https://", urls_[1])); - } - QUICHE_LOG(INFO) << "Using relay URL: " << relay_url_.ToString(); - ObliviousHttpHeaderKeyConfig key_config = key_configs->PreferredConfig(); - absl::StatusOr<absl::string_view> public_key = - key_configs->GetPublicKeyForId(key_config.GetKeyId()); - if (!public_key.ok()) { - QUICHE_LOG(ERROR) << "Failed to get public key for key ID " - << static_cast<int>(key_config.GetKeyId()) << ": " - << public_key.status(); - Abort(); - return; - } - absl::StatusOr<ObliviousHttpClient> ohttp_client = - ObliviousHttpClient::Create(*public_key, key_config); - if (!ohttp_client.ok()) { - QUICHE_LOG(ERROR) << "Failed to create OHTTP client: " - << ohttp_client.status(); - Abort(); - return; - } - ohttp_client_.emplace(std::move(*ohttp_client)); - for (size_t i = 2; i < urls_.size(); ++i) { - SendOhttpRequestForUrl(urls_[i]); - } - } - - void SendOhttpRequestForUrl(const std::string& url_string) { - QuicUrl url(url_string, "https"); - if (url.host().empty() && !absl::StrContains(url_string, "://")) { - url = QuicUrl(absl::StrCat("https://", url_string)); - } - if (url.host().empty()) { - QUICHE_LOG(ERROR) << "Failed to parse key URL \"" << url_string << "\""; - return; - } - BinaryHttpRequest::ControlData control_data; - control_data.method = post_data_.empty() ? "GET" : "POST"; - control_data.scheme = url.scheme(); - control_data.authority = url.HostPort(); - control_data.path = url.path(); - BinaryHttpRequest binary_request(control_data); - binary_request.set_body(post_data_); - absl::StatusOr<std::string> encoded_request = binary_request.Serialize(); - if (!encoded_request.ok()) { - QUICHE_LOG(ERROR) << "Failed to encode request: " - << encoded_request.status(); - return; - } - if (!ohttp_client_.has_value()) { - QUICHE_LOG(FATAL) << "Cannot send OHTTP request without OHTTP client"; - return; - } - absl::StatusOr<ObliviousHttpRequest> ohttp_request = - ohttp_client_->CreateObliviousHttpRequest(*encoded_request); - if (!ohttp_request.ok()) { - QUICHE_LOG(ERROR) << "Failed to create OHTTP request: " - << ohttp_request.status(); - return; - } - Message request; - request.headers[":method"] = "POST"; - request.headers[":scheme"] = relay_url_.scheme(); - request.headers[":authority"] = relay_url_.HostPort(); - request.headers[":path"] = relay_url_.path(); - request.headers["content-type"] = "message/ohttp-req"; - request.body = ohttp_request->EncapsulateAndSerialize(); - absl::StatusOr<RequestId> request_id = - connection_pool_.SendRequest(request); - if (!request_id.ok()) { - QUICHE_LOG(ERROR) << "Failed to send request: " << request_id.status(); - return; - } - QUICHE_LOG(INFO) << "Sent OHTTP request for " << url_string; - auto context = std::move(*ohttp_request).ReleaseContext(); - pending_ohttp_requests_.insert({*request_id, std::move(context)}); - } - - void Abort() { - QUICHE_LOG(INFO) << "Aborting"; - aborted_ = true; - } - - std::vector<std::string> urls_; - std::string post_data_; - MasqueConnectionPool connection_pool_; - std::optional<RequestId> key_fetch_request_id_; - bool aborted_ = false; - std::optional<ObliviousHttpClient> ohttp_client_; - QuicUrl relay_url_; - absl::flat_hash_map<RequestId, ObliviousHttpRequest::Context> - pending_ohttp_requests_; -}; - int RunMasqueOhttpClient(int argc, char* argv[]) { const char* usage = "Usage: masque_ohttp_client <key-url> <relay-url> <url>..."; @@ -315,7 +60,6 @@ QUICHE_LOG(ERROR) << "Failed to create SSL context: " << ssl_ctx.status(); return 1; } - const int address_family = quiche::GetQuicheCommandLineFlag(FLAGS_address_family); int address_family_for_lookup; @@ -336,7 +80,7 @@ MasqueOhttpClient masque_ohttp_client(event_loop.get(), ssl_ctx->get(), urls, disable_certificate_verification, address_family_for_lookup, post_data); - if (!masque_ohttp_client.Start()) { + if (!masque_ohttp_client.Start().ok()) { return 1; } while (!masque_ohttp_client.IsDone()) {