Add GET request support to MASQUE client code

This was required to allow interop testing with another HTTP signature auth implementation that doesn't support MASQUE methods.

This CL also improves logging and fixes some bugs in the HTTP signature auth code.

PiperOrigin-RevId: 599998519
diff --git a/quiche/quic/masque/masque_client_bin.cc b/quiche/quic/masque/masque_client_bin.cc
index 2246c69..10a7c39 100644
--- a/quiche/quic/masque/masque_client_bin.cc
+++ b/quiche/quic/masque/masque_client_bin.cc
@@ -22,6 +22,7 @@
 #include "absl/strings/string_view.h"
 #include "openssl/curve25519.h"
 #include "quiche/quic/core/crypto/proof_verifier.h"
+#include "quiche/quic/core/http/quic_spdy_client_stream.h"
 #include "quiche/quic/core/io/quic_default_event_loop.h"
 #include "quiche/quic/core/io/quic_event_loop.h"
 #include "quiche/quic/core/quic_default_clock.h"
@@ -438,10 +439,18 @@
       quiche::GetQuicheCommandLineFlag(FLAGS_dns_on_client);
 
   for (size_t i = 1; i < urls.size(); ++i) {
-    if (!tools::SendEncapsulatedMasqueRequest(
-            masque_client.get(), event_loop.get(), urls[i],
-            disable_certificate_verification, address_family_for_lookup,
-            dns_on_client)) {
+    if (urls[i].starts_with("/")) {
+      QuicSpdyClientStream* stream =
+          masque_client->masque_client_session()->SendGetRequest(urls[i]);
+      while (stream->time_to_response_complete().IsInfinite()) {
+        event_loop->RunEventLoopOnce(QuicTime::Delta::FromMilliseconds(50));
+      }
+      // Print the response body to stdout.
+      std::cout << std::endl << stream->data() << std::endl;
+    } else if (!tools::SendEncapsulatedMasqueRequest(
+                   masque_client.get(), event_loop.get(), urls[i],
+                   disable_certificate_verification, address_family_for_lookup,
+                   dns_on_client)) {
       return 1;
     }
   }
diff --git a/quiche/quic/masque/masque_client_session.cc b/quiche/quic/masque/masque_client_session.cc
index ede64e6..842e454 100644
--- a/quiche/quic/masque/masque_client_session.cc
+++ b/quiche/quic/masque/masque_client_session.cc
@@ -171,6 +171,7 @@
   headers[":path"] = canonicalized_path;
   headers["connect-udp-version"] = "12";
   AddAdditionalHeaders(headers, url);
+  QUIC_DVLOG(1) << "Sending request headers: " << headers.DebugString();
   size_t bytes_sent =
       stream->SendRequest(std::move(headers), /*body=*/"", /*fin=*/false);
   if (bytes_sent == 0) {
@@ -218,6 +219,7 @@
   headers[":path"] = path;
   headers["connect-ip-version"] = "3";
   AddAdditionalHeaders(headers, url);
+  QUIC_DVLOG(1) << "Sending request headers: " << headers.DebugString();
   size_t bytes_sent =
       stream->SendRequest(std::move(headers), /*body=*/"", /*fin=*/false);
   if (bytes_sent == 0) {
@@ -267,6 +269,7 @@
   headers[":authority"] = authority;
   headers[":path"] = path;
   AddAdditionalHeaders(headers, url);
+  QUIC_DVLOG(1) << "Sending request headers: " << headers.DebugString();
   size_t bytes_sent =
       stream->SendRequest(std::move(headers), /*body=*/"", /*fin=*/false);
   if (bytes_sent == 0) {
@@ -486,8 +489,7 @@
     return false;
   }
   if (!SupportsH3Datagram()) {
-    QUIC_DLOG(ERROR) << "Refusing to use MASQUE without HTTP/3 Datagrams";
-    return false;
+    QUIC_DLOG(ERROR) << "Warning: MasqueClientSession without HTTP/3 Datagrams";
   }
   QUIC_DLOG(INFO) << "Using HTTP Datagram: " << http_datagram_support();
   owner_->OnSettingsReceived();
@@ -719,6 +721,47 @@
   signature_auth_public_key_ = public_key;
 }
 
+QuicSpdyClientStream* MasqueClientSession::SendGetRequest(
+    absl::string_view path) {
+  QuicSpdyClientStream* stream = CreateOutgoingBidirectionalStream();
+  if (stream == nullptr) {
+    // Stream flow control limits prevented us from opening a new stream.
+    QUIC_DLOG(ERROR) << "Failed to open GET stream";
+    return nullptr;
+  }
+
+  QuicUrl url(uri_template_);
+  std::string scheme = url.scheme();
+  std::string authority = url.HostPort();
+
+  QUIC_DLOG(INFO) << "Sending GET request on stream " << stream->id()
+                  << " scheme=\"" << scheme << "\" authority=\"" << authority
+                  << "\" path=\"" << path << "\"";
+
+  // Send the request.
+  spdy::Http2HeaderBlock headers;
+  headers[":method"] = "GET";
+  headers[":scheme"] = scheme;
+  headers[":authority"] = authority;
+  headers[":path"] = path;
+  AddAdditionalHeaders(headers, url);
+  QUIC_DVLOG(1) << "Sending request headers: " << headers.DebugString();
+  // Setting the stream visitor is required to enable reading of the response
+  // body from the stream.
+  stream->set_visitor(this);
+  size_t bytes_sent =
+      stream->SendRequest(std::move(headers), /*body=*/"", /*fin=*/true);
+  if (bytes_sent == 0) {
+    QUIC_DLOG(ERROR) << "Failed to send GET request";
+    return nullptr;
+  }
+  return stream;
+}
+
+void MasqueClientSession::OnClose(QuicSpdyStream* stream) {
+  QUIC_DVLOG(1) << "Closing stream " << stream->id();
+}
+
 std::optional<std::string> MasqueClientSession::ComputeSignatureAuthHeader(
     const QuicUrl& url) {
   if (signature_auth_private_key_.empty()) {
@@ -732,6 +775,9 @@
   std::string key_exporter_context = ComputeSignatureAuthContext(
       kEd25519SignatureScheme, signature_auth_key_id_,
       signature_auth_public_key_, scheme, host, port, realm);
+  QUIC_DVLOG(1) << "key_exporter_context: "
+                << absl::WebSafeBase64Escape(key_exporter_context);
+  QUICHE_DCHECK(!key_exporter_context.empty());
   if (!GetMutableCryptoStream()->ExportKeyingMaterial(
           kSignatureAuthLabel, key_exporter_context, kSignatureAuthExporterSize,
           &key_exporter_output)) {
@@ -741,10 +787,14 @@
   QUICHE_CHECK_EQ(key_exporter_output.size(), kSignatureAuthExporterSize);
   std::string signature_input =
       key_exporter_output.substr(0, kSignatureAuthSignatureInputSize);
+  QUIC_DVLOG(1) << "signature_input: "
+                << absl::WebSafeBase64Escape(signature_input);
   std::string verification = key_exporter_output.substr(
       kSignatureAuthSignatureInputSize, kSignatureAuthVerificationSize);
   std::string data_covered_by_signature =
       SignatureAuthDataCoveredBySignature(signature_input);
+  QUIC_DVLOG(1) << "data_covered_by_signature: "
+                << absl::WebSafeBase64Escape(data_covered_by_signature);
   uint8_t signature[ED25519_SIGNATURE_LEN];
   if (ED25519_sign(
           signature,
diff --git a/quiche/quic/masque/masque_client_session.h b/quiche/quic/masque/masque_client_session.h
index 7429f91..b4a9fff 100644
--- a/quiche/quic/masque/masque_client_session.h
+++ b/quiche/quic/masque/masque_client_session.h
@@ -38,7 +38,8 @@
 // frames for operation of the MASQUE protocol. Multiple end-to-end encapsulated
 // sessions can then coexist inside this session. Once these are created, they
 // need to be registered with this session.
-class QUIC_NO_EXPORT MasqueClientSession : public QuicSpdyClientSession {
+class QUIC_NO_EXPORT MasqueClientSession : public QuicSpdyClientSession,
+                                           public QuicSpdyStream::Visitor {
  public:
   // Interface meant to be implemented by the owner of the
   // MasqueClientSession instance.
@@ -168,6 +169,12 @@
     additional_headers_ = additional_headers;
   }
 
+  // Send a GET request to the MASQUE proxy itself.
+  QuicSpdyClientStream* SendGetRequest(absl::string_view path);
+
+  // QuicSpdyStream::Visitor
+  void OnClose(QuicSpdyStream* stream) override;
+
   // Set the signature auth key ID and private key. key_id MUST be non-empty,
   // private_key MUST be ED25519_PRIVATE_KEY_LEN bytes long and public_key MUST
   // be ED25519_PUBLIC_KEY_LEN bytes long.
diff --git a/quiche/quic/masque/masque_client_tools.cc b/quiche/quic/masque/masque_client_tools.cc
index 0cb1d59..2096a26 100644
--- a/quiche/quic/masque/masque_client_tools.cc
+++ b/quiche/quic/masque/masque_client_tools.cc
@@ -64,6 +64,10 @@
                                    bool disable_certificate_verification,
                                    int address_family_for_lookup,
                                    bool dns_on_client) {
+  if (!masque_client->masque_client_session()->SupportsH3Datagram()) {
+    QUIC_LOG(ERROR) << "Refusing to use MASQUE without datagram support";
+    return false;
+  }
   const QuicUrl url(url_string, "https");
   std::unique_ptr<ProofVerifier> proof_verifier;
   if (disable_certificate_verification) {
diff --git a/quiche/quic/masque/masque_server_session.cc b/quiche/quic/masque/masque_server_session.cc
index 4a125e5..8bca482 100644
--- a/quiche/quic/masque/masque_server_session.cc
+++ b/quiche/quic/masque/masque_server_session.cc
@@ -23,6 +23,7 @@
 #include "absl/cleanup/cleanup.h"
 #include "absl/strings/escaping.h"
 #include "absl/strings/numbers.h"
+#include "absl/strings/str_cat.h"
 #include "absl/strings/str_split.h"
 #include "absl/strings/string_view.h"
 #include "openssl/curve25519.h"
@@ -327,7 +328,7 @@
                                       "Unexpected public key in header");
   }
   std::string realm = "";
-  QuicUrl url(authority, scheme);
+  QuicUrl url(absl::StrCat(scheme, "://", authority, "/"));
   std::optional<std::string> key_exporter_context = ComputeSignatureAuthContext(
       kEd25519SignatureScheme, *key_id, *header_public_key, scheme, url.host(),
       url.port(), realm);
@@ -335,6 +336,9 @@
     return CreateBackendErrorResponse(
         "500", "Failed to generate key exporter context");
   }
+  QUIC_DVLOG(1) << "key_exporter_context: "
+                << absl::WebSafeBase64Escape(*key_exporter_context);
+  QUICHE_DCHECK(!key_exporter_context->empty());
   std::string key_exporter_output;
   if (!GetMutableCryptoStream()->ExportKeyingMaterial(
           kSignatureAuthLabel, *key_exporter_context,
@@ -344,14 +348,23 @@
   QUICHE_CHECK_EQ(key_exporter_output.size(), kSignatureAuthExporterSize);
   std::string signature_input =
       key_exporter_output.substr(0, kSignatureAuthSignatureInputSize);
+  QUIC_DVLOG(1) << "signature_input: "
+                << absl::WebSafeBase64Escape(signature_input);
   std::string expected_verification = key_exporter_output.substr(
       kSignatureAuthSignatureInputSize, kSignatureAuthVerificationSize);
   if (verification != expected_verification) {
-    return CreateBackendErrorResponse(kSignatureAuthStatus,
-                                      "Unexpected verification");
+    return CreateBackendErrorResponse(
+        kSignatureAuthStatus,
+        absl::StrCat("Unexpected verification, expected ",
+                     absl::WebSafeBase64Escape(expected_verification),
+                     " but got ", absl::WebSafeBase64Escape(*verification),
+                     " - key exporter context was ",
+                     absl::WebSafeBase64Escape(*key_exporter_context)));
   }
   std::string data_covered_by_signature =
       SignatureAuthDataCoveredBySignature(signature_input);
+  QUIC_DVLOG(1) << "data_covered_by_signature: "
+                << absl::WebSafeBase64Escape(data_covered_by_signature);
   if (*signature_scheme != kEd25519SignatureScheme) {
     return CreateBackendErrorResponse(kSignatureAuthStatus,
                                       "Unexpected signature scheme");
diff --git a/quiche/quic/masque/masque_utils.cc b/quiche/quic/masque/masque_utils.cc
index 10ebe71..6cffb3a 100644
--- a/quiche/quic/masque/masque_utils.cc
+++ b/quiche/quic/masque/masque_utils.cc
@@ -10,6 +10,7 @@
 #include <string>
 #include <utility>
 
+#include "absl/strings/escaping.h"
 #include "absl/strings/str_cat.h"
 #include "absl/strings/string_view.h"
 #include "quiche/quic/core/quic_config.h"
@@ -225,7 +226,10 @@
                                         absl::string_view scheme,
                                         absl::string_view host, uint16_t port,
                                         absl::string_view realm) {
-  std::string key_exporter_output;
+  QUIC_DVLOG(2) << "ComputeSignatureAuthContext: key_id=\"" << key_id
+                << "\" public_key=" << absl::WebSafeBase64Escape(public_key)
+                << " scheme=\"" << scheme << "\" host=\"" << host
+                << "\" port=" << port << " realm=\"" << realm << "\"";
   std::string key_exporter_context;
   key_exporter_context.resize(
       sizeof(signature_scheme) + QuicDataWriter::GetVarInt62Len(key_id.size()) +
@@ -244,7 +248,7 @@
       !writer.WriteStringPieceVarInt62(realm) || writer.remaining() != 0) {
     QUIC_LOG(FATAL) << "ComputeSignatureAuthContext failed";
   }
-  return key_exporter_output;
+  return key_exporter_context;
 }
 
 std::string SignatureAuthDataCoveredBySignature(