Add --request_trust_anchors to the QUIC toy client

This new flag lets us control the presence and contents of the `trust_anchors` TLS extension.

PiperOrigin-RevId: 854273831
diff --git a/quiche/quic/tools/quic_toy_client.cc b/quiche/quic/tools/quic_toy_client.cc
index 6ded104..d90c722 100644
--- a/quiche/quic/tools/quic_toy_client.cc
+++ b/quiche/quic/tools/quic_toy_client.cc
@@ -42,29 +42,46 @@
 
 #include "quiche/quic/tools/quic_toy_client.h"
 
+#include <cstddef>
+#include <cstdint>
 #include <fstream>
 #include <iostream>
 #include <memory>
+#include <optional>
 #include <string>
 #include <utility>
 #include <vector>
 
+#include "absl/status/status.h"
+#include "absl/status/statusor.h"
 #include "absl/strings/escaping.h"
+#include "absl/strings/str_cat.h"
 #include "absl/strings/str_split.h"
 #include "absl/strings/string_view.h"
+#include "openssl/base.h"
+#include "openssl/bytestring.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/crypto/quic_client_session_cache.h"
-#include "quiche/quic/core/quic_packets.h"
-#include "quiche/quic/core/quic_server_id.h"
-#include "quiche/quic/core/quic_utils.h"
+#include "quiche/quic/core/crypto/quic_crypto_client_config.h"
+#include "quiche/quic/core/quic_config.h"
+#include "quiche/quic/core/quic_connection_id.h"
+#include "quiche/quic/core/quic_constants.h"
+#include "quiche/quic/core/quic_error_codes.h"
+#include "quiche/quic/core/quic_tag.h"
+#include "quiche/quic/core/quic_time.h"
 #include "quiche/quic/core/quic_versions.h"
 #include "quiche/quic/platform/api/quic_default_proof_providers.h"
-#include "quiche/quic/platform/api/quic_ip_address.h"
+#include "quiche/quic/platform/api/quic_logging.h"
 #include "quiche/quic/platform/api/quic_socket_address.h"
 #include "quiche/quic/tools/fake_proof_verifier.h"
+#include "quiche/quic/tools/quic_spdy_client_base.h"
 #include "quiche/quic/tools/quic_url.h"
 #include "quiche/common/http/http_header_block.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_reference_counted.h"
 #include "quiche/common/quiche_text_utils.h"
 
 namespace {
@@ -171,6 +188,19 @@
     "default certificate for signing, if server requested client certs.");
 
 DEFINE_QUICHE_COMMAND_LINE_FLAG(
+    std::string, request_trust_anchors, "",
+    "A space-separated list of Trust Anchor IDs to request from the server via "
+    "the TLS trust_anchors extension, e.g. \"11129.9.1 11129.9.2\". When the "
+    "flag is not specified or its value is the empty string, the trust_anchors "
+    "extension will not be sent. When its value is \"EMPTY_TRUST_ANCHORS\", "
+    "the client will send the trust_anchors extension with an empty value. "
+    "Presently, Trust Anchor ID assignments are recorded here: "
+    // clang-format off
+    "<https://github.com/tlswg/tls-trust-anchor-ids/blob/main/assignments.md>."
+    // clang-format on
+);
+
+DEFINE_QUICHE_COMMAND_LINE_FLAG(
     bool, drop_response_body, false,
     "If true, drop response body immediately after it is received.");
 
@@ -251,6 +281,38 @@
   return proof_source;
 }
 
+// Parses `text_oid_list` as a list of ASN.1 relative OIDs representing Trust
+// Anchor IDs and serializes the corresponding TLS `trust_anchors` extension, as
+// defined by draft-ietf-tls-trust-anchor-ids-02. Returns an error if the list
+// is empty, any of the OIDs cannot be parsed, or if serialization fails. Note
+// that on success, the returned serialization elides the 16-bit length prefix
+// on the `TrustAnchorIDList` structure.
+absl::StatusOr<std::optional<std::string>> SerializeTrustAnchorIdsFromTextOids(
+    absl::string_view text_oid_list) {
+  const std::vector<absl::string_view> text_oids =
+      absl::StrSplit(text_oid_list, ' ', absl::SkipWhitespace());
+  if (text_oids.empty()) {
+    return absl::InvalidArgumentError(
+        "Expected a non-empty list of Trust Anchor IDs.");
+  }
+  bssl::ScopedCBB cbb;
+  if (!CBB_init(cbb.get(), 32)) {
+    return absl::InternalError("Failed to initialize CBB.");
+  }
+  for (absl::string_view text_oid : text_oids) {
+    CBB id;
+    if (!CBB_add_u8_length_prefixed(cbb.get(), &id) ||
+        !CBB_add_asn1_relative_oid_from_text(&id, text_oid.data(),
+                                             text_oid.size()) ||
+        !CBB_flush(cbb.get())) {
+      return absl::InvalidArgumentError(
+          absl::StrCat("Invalid Trust Anchor ID: ", text_oid));
+    }
+  }
+  return std::string(reinterpret_cast<const char*>(CBB_data(cbb.get())),
+                     CBB_len(cbb.get()));
+}
+
 }  // namespace
 
 QuicToyClient::QuicToyClient(ClientFactory* client_factory)
@@ -365,6 +427,24 @@
     client->crypto_config()->set_proof_source(std::move(proof_source));
   }
 
+  if (const std::string request_trust_anchors =
+          quiche::GetQuicheCommandLineFlag(FLAGS_request_trust_anchors);
+      !request_trust_anchors.empty()) {
+    if (request_trust_anchors == "EMPTY_TRUST_ANCHORS") {
+      client->crypto_config()->ssl_config().trust_anchor_ids = "";
+    } else {
+      absl::StatusOr<std::optional<std::string>> serialized =
+          SerializeTrustAnchorIdsFromTextOids(request_trust_anchors);
+      if (!serialized.ok()) {
+        std::cerr << "Failed to serialize trust_anchors extension: "
+                  << serialized.status() << std::endl;
+        return 1;
+      }
+      client->crypto_config()->ssl_config().trust_anchor_ids =
+          std::move(*serialized);
+    }
+  }
+
   int32_t initial_mtu = quiche::GetQuicheCommandLineFlag(FLAGS_initial_mtu);
   client->set_initial_max_packet_length(
       initial_mtu != 0 ? initial_mtu : quic::kDefaultMaxPacketSize);