OHTTP key config: add ability to write length prefix

This code was writing the format from draft-ietf-ohai-ohttp-08 but the format was changed via https://github.com/ietf-wg-ohai/oblivious-http/pull/284 before the publication of RFC 9458. This change allows us to write both formats, while defaulting to the old format.

PiperOrigin-RevId: 945202457
diff --git a/quiche/oblivious_http/common/oblivious_http_header_key_config.cc b/quiche/oblivious_http/common/oblivious_http_header_key_config.cc
index 3d3679f..4091ba2 100644
--- a/quiche/oblivious_http/common/oblivious_http_header_key_config.cc
+++ b/quiche/oblivious_http/common/oblivious_http_header_key_config.cc
@@ -341,8 +341,8 @@
   return ObliviousHttpKeyConfigs(std::move(configs), std::move(keys));
 }
 
-absl::StatusOr<std::string> ObliviousHttpKeyConfigs::GenerateConcatenatedKeys()
-    const {
+absl::StatusOr<std::string> ObliviousHttpKeyConfigs::GenerateConcatenatedKeys(
+    bool with_length_prefix) const {
   std::string concatenated_keys;
   for (const auto& [key_id, ohttp_configs] : configs_) {
     QUICHE_ASSIGN_OR_RETURN(absl::string_view public_key,
@@ -350,6 +350,13 @@
     QUICHE_ASSIGN_OR_RETURN(
         std::string serialized,
         SerializeOhttpKeyWithPublicKey(key_id, public_key, ohttp_configs));
+    if (with_length_prefix) {
+      char length_prefix[sizeof(uint16_t)];
+      QuicheDataWriter writer(sizeof(uint16_t), length_prefix);
+      QUICHE_CHECK(writer.WriteUInt16(serialized.size()));
+      absl::StrAppend(&concatenated_keys,
+                      absl::string_view(length_prefix, sizeof(uint16_t)));
+    }
     absl::StrAppend(&concatenated_keys, std::move(serialized));
   }
   return concatenated_keys;
diff --git a/quiche/oblivious_http/common/oblivious_http_header_key_config.h b/quiche/oblivious_http/common/oblivious_http_header_key_config.h
index d3954b3..b04833e 100644
--- a/quiche/oblivious_http/common/oblivious_http_header_key_config.h
+++ b/quiche/oblivious_http/common/oblivious_http_header_key_config.h
@@ -197,7 +197,8 @@
 
   // Generates byte string corresponding to "application/ohttp-keys" media type.
   // https://www.rfc-editor.org/rfc/rfc9458.html#section-3
-  absl::StatusOr<std::string> GenerateConcatenatedKeys() const;
+  absl::StatusOr<std::string> GenerateConcatenatedKeys(
+      bool with_length_prefix = false) const;
 
   int NumKeys() const { return public_keys_.size(); }
 
diff --git a/quiche/oblivious_http/common/oblivious_http_header_key_config_test.cc b/quiche/oblivious_http/common/oblivious_http_header_key_config_test.cc
index 938c4cd..40b076d 100644
--- a/quiche/oblivious_http/common/oblivious_http_header_key_config_test.cc
+++ b/quiche/oblivious_http/common/oblivious_http_header_key_config_test.cc
@@ -427,6 +427,133 @@
               IsOkAndHolds(expected_preferred_public_key));
 }
 
+TEST(ObliviousHttpHeaderKeyConfigs,
+     RoundTripSingleKeyConfigWithoutLengthPrefix) {
+  auto initial_config = ObliviousHttpHeaderKeyConfig::Create(
+      123, EVP_HPKE_DHKEM_X25519_HKDF_SHA256, EVP_HPKE_HKDF_SHA256,
+      EVP_HPKE_CHACHA20_POLY1305);
+  QUICHE_ASSERT_OK(initial_config);
+  std::string test_public_key(
+      EVP_HPKE_KEM_public_key_len(initial_config->GetHpkeKem()), 'a');
+  auto configs =
+      ObliviousHttpKeyConfigs::Create(*initial_config, test_public_key);
+  QUICHE_ASSERT_OK(configs);
+  auto serialized_key =
+      configs->GenerateConcatenatedKeys(/*with_length_prefix=*/false);
+  QUICHE_ASSERT_OK(serialized_key);
+  auto parsed_configs =
+      ObliviousHttpKeyConfigs::ParseConcatenatedKeys(*serialized_key);
+  QUICHE_ASSERT_OK(parsed_configs);
+  EXPECT_EQ(parsed_configs->NumKeys(), 1);
+  EXPECT_THAT(parsed_configs->PreferredConfig(),
+              AllOf(HasKeyId(initial_config->GetKeyId()),
+                    HasKemId(initial_config->GetHpkeKemId()),
+                    HasKdfId(initial_config->GetHpkeKdfId()),
+                    HasAeadId(initial_config->GetHpkeAeadId())));
+  EXPECT_THAT(parsed_configs->GetPublicKeyForId(initial_config->GetKeyId()),
+              IsOkAndHolds(test_public_key));
+  EXPECT_THAT(
+      parsed_configs->GenerateConcatenatedKeys(/*with_length_prefix=*/false),
+      IsOkAndHolds(*serialized_key));
+}
+
+TEST(ObliviousHttpHeaderKeyConfigs, RoundTripSingleKeyConfigWithLengthPrefix) {
+  auto initial_config = ObliviousHttpHeaderKeyConfig::Create(
+      123, EVP_HPKE_DHKEM_X25519_HKDF_SHA256, EVP_HPKE_HKDF_SHA256,
+      EVP_HPKE_CHACHA20_POLY1305);
+  QUICHE_ASSERT_OK(initial_config);
+  std::string test_public_key(
+      EVP_HPKE_KEM_public_key_len(initial_config->GetHpkeKem()), 'a');
+  auto configs =
+      ObliviousHttpKeyConfigs::Create(*initial_config, test_public_key);
+  QUICHE_ASSERT_OK(configs);
+  auto serialized_key =
+      configs->GenerateConcatenatedKeys(/*with_length_prefix=*/true);
+  QUICHE_ASSERT_OK(serialized_key);
+  auto parsed_configs =
+      ObliviousHttpKeyConfigs::ParseConcatenatedKeys(*serialized_key);
+  QUICHE_ASSERT_OK(parsed_configs);
+  EXPECT_EQ(parsed_configs->NumKeys(), 1);
+  EXPECT_THAT(parsed_configs->PreferredConfig(),
+              AllOf(HasKeyId(initial_config->GetKeyId()),
+                    HasKemId(initial_config->GetHpkeKemId()),
+                    HasKdfId(initial_config->GetHpkeKdfId()),
+                    HasAeadId(initial_config->GetHpkeAeadId())));
+  EXPECT_THAT(parsed_configs->GetPublicKeyForId(initial_config->GetKeyId()),
+              IsOkAndHolds(test_public_key));
+  EXPECT_THAT(
+      parsed_configs->GenerateConcatenatedKeys(/*with_length_prefix=*/true),
+      IsOkAndHolds(*serialized_key));
+}
+
+TEST(ObliviousHttpHeaderKeyConfigs,
+     RoundTripMultipleKeyConfigsWithoutLengthPrefix) {
+  ObliviousHttpKeyConfigs::OhttpKeyConfig config1 = {
+      100,
+      EVP_HPKE_DHKEM_X25519_HKDF_SHA256,
+      std::string(32, 'a'),
+      {{EVP_HPKE_HKDF_SHA256, EVP_HPKE_AES_256_GCM}}};
+  ObliviousHttpKeyConfigs::OhttpKeyConfig config2 = {
+      200,
+      EVP_HPKE_DHKEM_X25519_HKDF_SHA256,
+      std::string(32, 'b'),
+      {{EVP_HPKE_HKDF_SHA256, EVP_HPKE_CHACHA20_POLY1305},
+       {EVP_HPKE_HKDF_SHA256, EVP_HPKE_AES_128_GCM}}};
+  auto configs = ObliviousHttpKeyConfigs::Create({config1, config2});
+  QUICHE_ASSERT_OK(configs);
+  auto serialized_key =
+      configs->GenerateConcatenatedKeys(/*with_length_prefix=*/false);
+  QUICHE_ASSERT_OK(serialized_key);
+  auto parsed_configs =
+      ObliviousHttpKeyConfigs::ParseConcatenatedKeys(*serialized_key);
+  QUICHE_ASSERT_OK(parsed_configs);
+  EXPECT_EQ(parsed_configs->NumKeys(), 2);
+  EXPECT_THAT(parsed_configs->PreferredConfig(),
+              AllOf(HasKeyId(200), HasKemId(EVP_HPKE_DHKEM_X25519_HKDF_SHA256),
+                    HasKdfId(EVP_HPKE_HKDF_SHA256)));
+  EXPECT_THAT(parsed_configs->GetPublicKeyForId(100),
+              IsOkAndHolds(std::string(32, 'a')));
+  EXPECT_THAT(parsed_configs->GetPublicKeyForId(200),
+              IsOkAndHolds(std::string(32, 'b')));
+  EXPECT_THAT(
+      parsed_configs->GenerateConcatenatedKeys(/*with_length_prefix=*/false),
+      IsOkAndHolds(*serialized_key));
+}
+
+TEST(ObliviousHttpHeaderKeyConfigs,
+     RoundTripMultipleKeyConfigsWithLengthPrefix) {
+  ObliviousHttpKeyConfigs::OhttpKeyConfig config1 = {
+      100,
+      EVP_HPKE_DHKEM_X25519_HKDF_SHA256,
+      std::string(32, 'a'),
+      {{EVP_HPKE_HKDF_SHA256, EVP_HPKE_AES_256_GCM}}};
+  ObliviousHttpKeyConfigs::OhttpKeyConfig config2 = {
+      200,
+      EVP_HPKE_DHKEM_X25519_HKDF_SHA256,
+      std::string(32, 'b'),
+      {{EVP_HPKE_HKDF_SHA256, EVP_HPKE_CHACHA20_POLY1305},
+       {EVP_HPKE_HKDF_SHA256, EVP_HPKE_AES_128_GCM}}};
+  auto configs = ObliviousHttpKeyConfigs::Create({config1, config2});
+  QUICHE_ASSERT_OK(configs);
+  auto serialized_key =
+      configs->GenerateConcatenatedKeys(/*with_length_prefix=*/true);
+  QUICHE_ASSERT_OK(serialized_key);
+  auto parsed_configs =
+      ObliviousHttpKeyConfigs::ParseConcatenatedKeys(*serialized_key);
+  QUICHE_ASSERT_OK(parsed_configs);
+  EXPECT_EQ(parsed_configs->NumKeys(), 2);
+  EXPECT_THAT(parsed_configs->PreferredConfig(),
+              AllOf(HasKeyId(200), HasKemId(EVP_HPKE_DHKEM_X25519_HKDF_SHA256),
+                    HasKdfId(EVP_HPKE_HKDF_SHA256)));
+  EXPECT_THAT(parsed_configs->GetPublicKeyForId(100),
+              IsOkAndHolds(std::string(32, 'a')));
+  EXPECT_THAT(parsed_configs->GetPublicKeyForId(200),
+              IsOkAndHolds(std::string(32, 'b')));
+  EXPECT_THAT(
+      parsed_configs->GenerateConcatenatedKeys(/*with_length_prefix=*/true),
+      IsOkAndHolds(*serialized_key));
+}
+
 TEST(ObliviousHttpHeaderKeyConfigs, TestCreateWithInvalidConfigs) {
   EXPECT_FALSE(ObliviousHttpKeyConfigs::Create({}).ok());
   EXPECT_FALSE(ObliviousHttpKeyConfigs::Create(