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(