OHTTP key config: allow parsing RFC 9458 length prefixes This code was parsing 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 parse both formats by first trying the new one and falling back to the old one. PiperOrigin-RevId: 945197351
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 f046ea1..3d3679f 100644 --- a/quiche/oblivious_http/common/oblivious_http_header_key_config.cc +++ b/quiche/oblivious_http/common/oblivious_http_header_key_config.cc
@@ -291,10 +291,18 @@ ObliviousHttpKeyConfigs::ParseConcatenatedKeys(absl::string_view key_config) { ConfigMap configs; PublicKeyMap keys; - auto reader = QuicheDataReader(key_config); + // First, try to parse the keys using the length-prefixed format from RFC + // 9458. + if (ReadKeyConfigsWithLengthPrefix(key_config, configs, keys).ok()) { + return ObliviousHttpKeyConfigs(std::move(configs), std::move(keys)); + } + // Otherwise, try parsing using the non-length-prefixed format from + // draft-ietf-ohai-ohttp-08, a precursor to RFC 9458. + configs.clear(); + keys.clear(); + QuicheDataReader reader(key_config); while (!reader.IsDoneReading()) { - absl::Status status = ReadSingleKeyConfig(reader, configs, keys); - if (!status.ok()) return status; + QUICHE_RETURN_IF_ERROR(ReadSingleKeyConfig(reader, configs, keys)); } return ObliviousHttpKeyConfigs(std::move(configs), std::move(keys)); } @@ -364,7 +372,8 @@ } absl::Status ObliviousHttpKeyConfigs::ReadSingleKeyConfig( - QuicheDataReader& reader, ConfigMap& configs, PublicKeyMap& keys) { + QuicheDataReader& reader, ConfigMap& configs, PublicKeyMap& keys, + bool skip_unknown_kems) { uint8_t key_id; if (!reader.ReadUInt8(&key_id)) { return absl::InvalidArgumentError("Failed to read key_id"); @@ -373,8 +382,14 @@ if (!reader.ReadUInt16(&kem_id)) { return absl::InvalidArgumentError("Failed to read kem_id"); } - QUICHE_ASSIGN_OR_RETURN(uint16_t key_length, KeyLength(kem_id)); - std::string key_str(key_length, '\0'); + absl::StatusOr<uint16_t> key_length = KeyLength(kem_id); + if (!key_length.ok()) { + if (skip_unknown_kems) { + return absl::OkStatus(); + } + return key_length.status(); + } + std::string key_str(*key_length, '\0'); if (!reader.ReadBytes(key_str.data(), key_str.size())) { return absl::InvalidArgumentError("Failed to read public key"); } @@ -420,6 +435,26 @@ return absl::OkStatus(); } +// static +absl::Status ObliviousHttpKeyConfigs::ReadKeyConfigsWithLengthPrefix( + absl::string_view key_configs, ConfigMap& configs, PublicKeyMap& keys) { + QuicheDataReader reader(key_configs); + while (!reader.IsDoneReading()) { + absl::string_view single_key_config; + if (!reader.ReadStringPiece16(&single_key_config)) { + return absl::InvalidArgumentError( + "Failed to read length-prefixed key config"); + } + QuicheDataReader single_reader(single_key_config); + QUICHE_RETURN_IF_ERROR(ReadSingleKeyConfig(single_reader, configs, keys, + /*skip_unknown_kems=*/true)); + } + if (configs.empty() || keys.empty()) { + return absl::InvalidArgumentError("No supported key configs found"); + } + return absl::OkStatus(); +} + // https://www.iana.org/assignments/hpke std::string ObliviousHttpKemIdToString(uint16_t kem_id) {
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 e20e67e..d3954b3 100644 --- a/quiche/oblivious_http/common/oblivious_http_header_key_config.h +++ b/quiche/oblivious_http/common/oblivious_http_header_key_config.h
@@ -228,7 +228,14 @@ static absl::Status ReadSingleKeyConfig(QuicheDataReader& reader, ConfigMap& configs, - PublicKeyMap& keys); + PublicKeyMap& keys, + bool skip_unknown_kems = false); + + // Reads key configs from a byte string formatted according to + // https://www.rfc-editor.org/rfc/rfc9458.html#section-3.1-2. Note that this + // can leave `configs` and `keys` in an invalid state when returning an error. + static absl::Status ReadKeyConfigsWithLengthPrefix( + absl::string_view key_configs, ConfigMap& configs, PublicKeyMap& keys); // A mapping from key_id to ObliviousHttpHeaderKeyConfig objects for that key. const ConfigMap configs_;
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 1f2cc64..938c4cd 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
@@ -277,6 +277,34 @@ HasKdfId(EVP_HPKE_HKDF_SHA256), HasAeadId(EVP_HPKE_AES_128_GCM))); } +TEST(ObliviousHttpKeyConfigs, TwoSimilarKeyConfigsWithLengthPrefix) { + std::string key; + ASSERT_TRUE(absl::HexStringToBytes( + // First key config. + "0029" // length of this key config + "4b" // key_id + "0020" // kem_id + "606162636465666768696a6b6c6d6e6f" // public_key + "707172737475767778797a7b7c7d7e7f" // public_key + "0004" // len(symmetric_algorithms) + "00010002" // HKDF_SHA256, AES_256_GCM + // Second key config. + "0029" // length of this key config + "4f" // key_id + "0020" // kem_id + "606162636465666768696a6b6c6d6e6f" // public_key + "707172737475767778797a7b7c7d7e7f" // public_key + "0004" // len(symmetric_algorithms) + "00010001", // HKDF_SHA256, AES_128_GCM + &key)); + EXPECT_THAT(ObliviousHttpKeyConfigs::ParseConcatenatedKeys(key), + IsOkAndHolds(Property(&ObliviousHttpKeyConfigs::NumKeys, 2))); + EXPECT_THAT( + ObliviousHttpKeyConfigs::ParseConcatenatedKeys(key)->PreferredConfig(), + AllOf(HasKeyId(0x4f), HasKemId(EVP_HPKE_DHKEM_X25519_HKDF_SHA256), + HasKdfId(EVP_HPKE_HKDF_SHA256), HasAeadId(EVP_HPKE_AES_128_GCM))); +} + TEST(ObliviousHttpKeyConfigs, RFCExample) { std::string key; ASSERT_TRUE(absl::HexStringToBytes( @@ -300,6 +328,29 @@ EXPECT_THAT(configs->DebugString(), HasSubstr("31e1f05a7401")); } +TEST(ObliviousHttpKeyConfigs, RFCExampleWithLengthPrefix) { + std::string key; + ASSERT_TRUE( + absl::HexStringToBytes("002d01002031e1f05a740102115220e9af918f738674aec95" + "f54db6e04eb705aae8e79815500080001000100010003", + &key)); + auto configs = ObliviousHttpKeyConfigs::ParseConcatenatedKeys(key); + QUICHE_ASSERT_OK(configs); + EXPECT_THAT(*configs, Property(&ObliviousHttpKeyConfigs::NumKeys, 1)); + EXPECT_THAT( + configs->PreferredConfig(), + AllOf(HasKeyId(0x01), HasKemId(EVP_HPKE_DHKEM_X25519_HKDF_SHA256), + HasKdfId(EVP_HPKE_HKDF_SHA256), HasAeadId(EVP_HPKE_AES_128_GCM))); + std::string expected_public_key; + ASSERT_TRUE(absl::HexStringToBytes( + "31e1f05a740102115220e9af918f738674aec95f54db6e04eb705aae8e798155", + &expected_public_key)); + EXPECT_THAT(configs->GetPublicKeyForId(configs->PreferredConfig().GetKeyId()), + IsOkAndHolds(expected_public_key)); + EXPECT_THAT(configs->DebugString(), HasSubstr("AES-128-GCM")); + EXPECT_THAT(configs->DebugString(), HasSubstr("31e1f05a7401")); +} + TEST(ObliviousHttpKeyConfigs, DuplicateKeyId) { std::string key; ASSERT_TRUE(absl::HexStringToBytes(