Add mTLS support to masque_client

PiperOrigin-RevId: 813310519
diff --git a/quiche/quic/masque/masque_client.cc b/quiche/quic/masque/masque_client.cc
index da6611e..af14227 100644
--- a/quiche/quic/masque/masque_client.cc
+++ b/quiche/quic/masque/masque_client.cc
@@ -11,6 +11,7 @@
 
 #include "absl/memory/memory.h"
 #include "absl/strings/str_cat.h"
+#include "quiche/quic/core/crypto/client_proof_source.h"
 #include "quiche/quic/core/crypto/proof_verifier.h"
 #include "quiche/quic/core/io/quic_event_loop.h"
 #include "quiche/quic/core/quic_connection.h"
@@ -91,7 +92,8 @@
 // static
 std::unique_ptr<MasqueClient> MasqueClient::Create(
     const std::string& uri_template, MasqueMode masque_mode,
-    QuicEventLoop* event_loop, std::unique_ptr<ProofVerifier> proof_verifier) {
+    QuicEventLoop* event_loop, std::unique_ptr<ProofVerifier> proof_verifier,
+    std::unique_ptr<ClientProofSource> proof_source) {
   QuicUrl url(uri_template);
   std::string host = url.host();
   if (host.empty()) {
@@ -118,6 +120,9 @@
     QUIC_LOG(ERROR) << "Failed to create masque_client";
     return nullptr;
   }
+  if (proof_source != nullptr) {
+    masque_client->crypto_config()->set_proof_source(std::move(proof_source));
+  }
   if (!masque_client->Prepare(kDefaultMaxPacketSizeForTunnels)) {
     QUIC_LOG(ERROR) << "Failed to prepare MASQUE client to " << host << ":"
                     << port;
diff --git a/quiche/quic/masque/masque_client.h b/quiche/quic/masque/masque_client.h
index c0f5b76..5b8e0c1 100644
--- a/quiche/quic/masque/masque_client.h
+++ b/quiche/quic/masque/masque_client.h
@@ -8,6 +8,7 @@
 #include <memory>
 #include <string>
 
+#include "quiche/quic/core/crypto/client_proof_source.h"
 #include "quiche/quic/core/crypto/proof_verifier.h"
 #include "quiche/quic/core/io/quic_event_loop.h"
 #include "quiche/quic/core/quic_connection.h"
@@ -32,7 +33,8 @@
   // lookup.
   static std::unique_ptr<MasqueClient> Create(
       const std::string& uri_template, MasqueMode masque_mode,
-      QuicEventLoop* event_loop, std::unique_ptr<ProofVerifier> proof_verifier);
+      QuicEventLoop* event_loop, std::unique_ptr<ProofVerifier> proof_verifier,
+      std::unique_ptr<ClientProofSource> proof_source);
 
   // From QuicClient.
   std::unique_ptr<QuicSession> CreateQuicClientSession(
diff --git a/quiche/quic/masque/masque_client_bin.cc b/quiche/quic/masque/masque_client_bin.cc
index 0bd8eec..c7a4b3d 100644
--- a/quiche/quic/masque/masque_client_bin.cc
+++ b/quiche/quic/masque/masque_client_bin.cc
@@ -9,6 +9,7 @@
 
 #include <cstddef>
 #include <cstdint>
+#include <fstream>
 #include <iostream>
 #include <memory>
 #include <string>
@@ -21,6 +22,8 @@
 #include "absl/strings/str_split.h"
 #include "absl/strings/string_view.h"
 #include "openssl/curve25519.h"
+#include "quiche/quic/core/crypto/certificate_view.h"
+#include "quiche/quic/core/crypto/client_proof_source.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"
@@ -42,6 +45,7 @@
 #include "quiche/common/platform/api/quiche_command_line_flags.h"
 #include "quiche/common/platform/api/quiche_googleurl.h"
 #include "quiche/common/platform/api/quiche_logging.h"
+#include "quiche/common/platform/api/quiche_reference_counted.h"
 #include "quiche/common/platform/api/quiche_system_event_loop.h"
 
 DEFINE_QUICHE_COMMAND_LINE_FLAG(
@@ -89,6 +93,14 @@
     "If set to true, no URLs need to be specified and instead a TAP device "
     "is brought up for a MASQUE CONNECT-ETHERNET session.");
 
+DEFINE_QUICHE_COMMAND_LINE_FLAG(
+    std::string, client_cert_file, "",
+    "Path to the PEM-encoded client certificate chain.");
+
+DEFINE_QUICHE_COMMAND_LINE_FLAG(
+    std::string, client_cert_key_file, "",
+    "Path to the PEM/PKCS8-encoded client certificate private key.");
+
 namespace quic {
 
 namespace {
@@ -246,6 +258,43 @@
   int fd_ = -1;
 };
 
+std::unique_ptr<ClientProofSource> CreateClientProofSource(
+    const std::string& client_cert_file,
+    const std::string& client_cert_key_file) {
+  if (client_cert_file.empty() || client_cert_key_file.empty()) {
+    std::cerr << "Both client cert and client cert key need to be set."
+              << std::endl;
+    return nullptr;
+  }
+  std::ifstream cert_stream(client_cert_file, std::ios::binary);
+  std::vector<std::string> certs =
+      CertificateView::LoadPemFromStream(&cert_stream);
+  if (certs.empty()) {
+    std::cerr << "Failed to load client certs." << std::endl;
+    return nullptr;
+  }
+
+  std::ifstream key_stream(client_cert_key_file, std::ios::binary);
+  std::unique_ptr<CertificatePrivateKey> private_key =
+      CertificatePrivateKey::LoadPemFromStream(&key_stream);
+  if (private_key == nullptr) {
+    std::cerr << "Failed to load client cert key." << std::endl;
+    return nullptr;
+  }
+
+  auto proof_source = std::make_unique<DefaultClientProofSource>();
+  if (!proof_source->AddCertAndKey(
+          {"*"},
+          quiche::QuicheReferenceCountedPointer<ClientProofSource::Chain>(
+              new ClientProofSource::Chain(certs)),
+          std::move(*private_key))) {
+    std::cerr << "Failed to add client cert and key." << std::endl;
+    return nullptr;
+  }
+
+  return proof_source;
+}
+
 int RunMasqueClient(int argc, char* argv[]) {
   const char* usage =
       "Usage: masque_client [options] <proxy-url> <urls>..\n"
@@ -384,6 +433,10 @@
   }
   const bool dns_on_client =
       quiche::GetQuicheCommandLineFlag(FLAGS_dns_on_client);
+  std::string client_cert_file =
+      quiche::GetQuicheCommandLineFlag(FLAGS_client_cert_file);
+  std::string client_cert_key_file =
+      quiche::GetQuicheCommandLineFlag(FLAGS_client_cert_key_file);
   std::unique_ptr<QuicEventLoop> event_loop =
       GetDefaultEventLoop()->Create(QuicDefaultClock::Get());
 
@@ -411,15 +464,26 @@
     if (masque_clients.empty()) {
       std::string host = uri_template.substr(parsed_uri_template.host.begin,
                                              parsed_uri_template.host.len);
+
       std::unique_ptr<ProofVerifier> proof_verifier;
       if (disable_certificate_verification) {
         proof_verifier = std::make_unique<FakeProofVerifier>();
       } else {
         proof_verifier = CreateDefaultProofVerifier(host);
       }
-      masque_client =
-          MasqueClient::Create(uri_template, masque_mode, event_loop.get(),
-                               std::move(proof_verifier));
+
+      std::unique_ptr<ClientProofSource> proof_source;
+      if (!client_cert_file.empty() || !client_cert_key_file.empty()) {
+        proof_source =
+            CreateClientProofSource(client_cert_file, client_cert_key_file);
+        if (proof_source == nullptr) {
+          return 1;
+        }
+      }
+
+      masque_client = MasqueClient::Create(
+          uri_template, masque_mode, event_loop.get(),
+          std::move(proof_verifier), std::move(proof_source));
 
     } else {
       masque_client = tools::CreateAndConnectMasqueEncapsulatedClient(
diff --git a/quiche/quic/tools/quic_toy_client.cc b/quiche/quic/tools/quic_toy_client.cc
index 1857025..4d366f7 100644
--- a/quiche/quic/tools/quic_toy_client.cc
+++ b/quiche/quic/tools/quic_toy_client.cc
@@ -235,11 +235,14 @@
   }
 
   auto proof_source = std::make_unique<DefaultClientProofSource>();
-  proof_source->AddCertAndKey(
-      {"*"},
-      quiche::QuicheReferenceCountedPointer<ClientProofSource::Chain>(
-          new ClientProofSource::Chain(certs)),
-      std::move(*private_key));
+  if (!proof_source->AddCertAndKey(
+          {"*"},
+          quiche::QuicheReferenceCountedPointer<ClientProofSource::Chain>(
+              new ClientProofSource::Chain(certs)),
+          std::move(*private_key))) {
+    std::cerr << "Failed to add client cert and key." << std::endl;
+    return nullptr;
+  }
 
   return proof_source;
 }