Generate Concatenated Keys as per OHTTP Key Configuration encoding defined in RFC
https://www.ietf.org/archive/id/draft-ietf-ohai-ohttp-03.html#name-key-configuration-encoding
PiperOrigin-RevId: 501684207
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 2963ca2..b0103c2 100644
--- a/quiche/oblivious_http/common/oblivious_http_header_key_config.cc
+++ b/quiche/oblivious_http/common/oblivious_http_header_key_config.cc
@@ -1,13 +1,18 @@
#include "quiche/oblivious_http/common/oblivious_http_header_key_config.h"
+#include <algorithm>
#include <cstdint>
+#include <string>
+#include <vector>
#include "absl/memory/memory.h"
#include "absl/status/status.h"
+#include "absl/strings/escaping.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "openssl/base.h"
#include "openssl/hpke.h"
+#include "quiche/common/platform/api/quiche_bug_tracker.h"
#include "quiche/common/platform/api/quiche_logging.h"
#include "quiche/common/quiche_data_writer.h"
#include "quiche/common/quiche_endian.h"
@@ -15,6 +20,16 @@
namespace quiche {
namespace {
+// Size of KEM ID is 2 bytes. Refer to OHTTP Key Config in the spec,
+// https://www.ietf.org/archive/id/draft-ietf-ohai-ohttp-06.html#name-a-single-key-configuration
+constexpr size_t kSizeOfHpkeKemId = 2;
+
+// Size of Symmetric algorithms is 2 bytes(16 bits) each.
+// Refer to HPKE Symmetric Algorithms configuration in the spec,
+// https://www.ietf.org/archive/id/draft-ietf-ohai-ohttp-06.html#name-a-single-key-configuration
+constexpr size_t kSizeOfSymmetricAlgorithmHpkeKdfId = 2;
+constexpr size_t kSizeOfSymmetricAlgorithmHpkeAeadId = 2;
+
absl::StatusOr<const EVP_HPKE_KEM*> CheckKemId(uint16_t kem_id) {
switch (kem_id) {
case EVP_HPKE_DHKEM_X25519_HKDF_SHA256:
@@ -194,6 +209,130 @@
return hdr;
}
+namespace {
+// https://www.rfc-editor.org/rfc/rfc9180#section-7.1
+absl::StatusOr<uint16_t> KeyLength(uint16_t kem_id) {
+ auto supported_kem = CheckKemId(kem_id);
+ if (!supported_kem.ok()) {
+ return absl::InvalidArgumentError(absl::StrCat(
+ "Unsupported KEM ID:", kem_id, ". public key length is unknown."));
+ }
+ return EVP_HPKE_KEM_public_key_len(supported_kem.value());
+}
+
+absl::StatusOr<std::string> SerializeOhttpKeyWithPublicKey(
+ uint8_t key_id, absl::string_view public_key,
+ const std::vector<ObliviousHttpHeaderKeyConfig>& ohttp_configs) {
+ auto ohttp_config = ohttp_configs[0];
+ // Check if `ohttp_config` match spec's encoding guidelines.
+ static_assert(sizeof(ohttp_config.GetHpkeKemId()) == kSizeOfHpkeKemId &&
+ sizeof(ohttp_config.GetHpkeKdfId()) ==
+ kSizeOfSymmetricAlgorithmHpkeKdfId &&
+ sizeof(ohttp_config.GetHpkeAeadId()) ==
+ kSizeOfSymmetricAlgorithmHpkeAeadId,
+ "Size of HPKE IDs should match RFC specification.");
+
+ uint16_t symmetric_algs_length =
+ ohttp_configs.size() * (kSizeOfSymmetricAlgorithmHpkeKdfId +
+ kSizeOfSymmetricAlgorithmHpkeAeadId);
+ int buf_len = sizeof(key_id) + kSizeOfHpkeKemId + public_key.size() +
+ sizeof(symmetric_algs_length) + symmetric_algs_length;
+ std::string ohttp_key_configuration(buf_len, '\0');
+ QuicheDataWriter writer(ohttp_key_configuration.size(),
+ ohttp_key_configuration.data());
+ if (!writer.WriteUInt8(key_id)) {
+ return absl::InternalError("Failed to serialize OHTTP key.[key_id]");
+ }
+ if (!writer.WriteUInt16(ohttp_config.GetHpkeKemId())) {
+ return absl::InternalError(
+ "Failed to serialize OHTTP key.[kem_id]"); // kemID.
+ }
+ if (!writer.WriteStringPiece(public_key)) {
+ return absl::InternalError(
+ "Failed to serialize OHTTP key.[public_key]"); // Raw public key.
+ }
+ if (!writer.WriteUInt16(symmetric_algs_length)) {
+ return absl::InternalError(
+ "Failed to serialize OHTTP key.[symmetric_algs_length]");
+ }
+ for (const auto& item : ohttp_configs) {
+ // Check if KEM ID is the same for all the configs stored in `this` for
+ // given `key_id`.
+ if (item.GetHpkeKemId() != ohttp_config.GetHpkeKemId()) {
+ QUICHE_BUG(ohttp_key_configs_builder_parser)
+ << "ObliviousHttpKeyConfigs object cannot hold ConfigMap of "
+ "different KEM IDs:[ "
+ << item.GetHpkeKemId() << "," << ohttp_config.GetHpkeKemId()
+ << " ]for a given key_id:" << static_cast<uint16_t>(key_id);
+ }
+ if (!writer.WriteUInt16(item.GetHpkeKdfId())) {
+ return absl::InternalError(
+ "Failed to serialize OHTTP key.[kdf_id]"); // kdfID.
+ }
+ if (!writer.WriteUInt16(item.GetHpkeAeadId())) {
+ return absl::InternalError(
+ "Failed to serialize OHTTP key.[aead_id]"); // aeadID.
+ }
+ }
+ QUICHE_DCHECK_EQ(writer.remaining(), 0u);
+ return ohttp_key_configuration;
+}
+
+std::string GetDebugStringForFailedKeyConfig(
+ const ObliviousHttpKeyConfigs::OhttpKeyConfig& failed_key_config) {
+ std::string debug_string = "[ ";
+ absl::StrAppend(&debug_string,
+ "key_id:", static_cast<uint16_t>(failed_key_config.key_id),
+ " , kem_id:", failed_key_config.kem_id,
+ ". Printing HEX formatted public_key:",
+ absl::BytesToHexString(failed_key_config.public_key));
+ absl::StrAppend(&debug_string, ", symmetric_algorithms: { ");
+ for (const auto& symmetric_config : failed_key_config.symmetric_algorithms) {
+ absl::StrAppend(&debug_string, "{kdf_id: ", symmetric_config.kdf_id,
+ ", aead_id:", symmetric_config.aead_id, " }");
+ }
+ absl::StrAppend(&debug_string, " } ]");
+ return debug_string;
+}
+
+// Verifies if the `key_config` contains all valid combinations of [kem_id,
+// kdf_id, aead_id] that comprises Single Key configuration encoding as
+// specified in
+// https://www.ietf.org/archive/id/draft-ietf-ohai-ohttp-03.html#name-a-single-key-configuration.
+absl::Status StoreKeyConfigIfValid(
+ ObliviousHttpKeyConfigs::OhttpKeyConfig key_config,
+ absl::btree_map<uint8_t, std::vector<ObliviousHttpHeaderKeyConfig>,
+ std::greater<uint8_t>>& configs,
+ absl::flat_hash_map<uint8_t, std::string>& keys) {
+ if (!CheckKemId(key_config.kem_id).ok() ||
+ key_config.public_key.size() != KeyLength(key_config.kem_id).value()) {
+ QUICHE_LOG(ERROR) << "Failed to process: "
+ << GetDebugStringForFailedKeyConfig(key_config);
+ return absl::InvalidArgumentError(
+ absl::StrCat("Invalid key_config! [KEM ID:", key_config.kem_id, "]"));
+ }
+ for (const auto& symmetric_config : key_config.symmetric_algorithms) {
+ if (!CheckKdfId(symmetric_config.kdf_id).ok() ||
+ !CheckAeadId(symmetric_config.aead_id).ok()) {
+ QUICHE_LOG(ERROR) << "Failed to process: "
+ << GetDebugStringForFailedKeyConfig(key_config);
+ return absl::InvalidArgumentError(
+ absl::StrCat("Invalid key_config! [KDF ID:", symmetric_config.kdf_id,
+ ", AEAD ID:", symmetric_config.aead_id, "]"));
+ }
+ auto ohttp_config = ObliviousHttpHeaderKeyConfig::Create(
+ key_config.key_id, key_config.kem_id, symmetric_config.kdf_id,
+ symmetric_config.aead_id);
+ if (ohttp_config.ok()) {
+ configs[key_config.key_id].emplace_back(std::move(ohttp_config.value()));
+ }
+ }
+ keys.emplace(key_config.key_id, std::move(key_config.public_key));
+ return absl::OkStatus();
+}
+
+} // namespace
+
absl::StatusOr<ObliviousHttpKeyConfigs>
ObliviousHttpKeyConfigs::ParseConcatenatedKeys(absl::string_view key_config) {
ConfigMap configs;
@@ -206,6 +345,67 @@
return ObliviousHttpKeyConfigs(std::move(configs), std::move(keys));
}
+absl::StatusOr<ObliviousHttpKeyConfigs> ObliviousHttpKeyConfigs::Create(
+ absl::flat_hash_set<ObliviousHttpKeyConfigs::OhttpKeyConfig>
+ ohttp_key_configs) {
+ if (ohttp_key_configs.empty()) {
+ return absl::InvalidArgumentError("Empty input.");
+ }
+ ConfigMap configs_map;
+ PublicKeyMap keys_map;
+ for (auto& ohttp_key_config : ohttp_key_configs) {
+ auto result = StoreKeyConfigIfValid(std::move(ohttp_key_config),
+ configs_map, keys_map);
+ if (!result.ok()) {
+ return result;
+ }
+ }
+ auto oblivious_configs =
+ ObliviousHttpKeyConfigs(std::move(configs_map), std::move(keys_map));
+ return oblivious_configs;
+}
+
+absl::StatusOr<ObliviousHttpKeyConfigs> ObliviousHttpKeyConfigs::Create(
+ const ObliviousHttpHeaderKeyConfig& single_key_config,
+ absl::string_view public_key) {
+ if (public_key.empty()) {
+ return absl::InvalidArgumentError("Empty input.");
+ }
+
+ if (auto key_length = KeyLength(single_key_config.GetHpkeKemId());
+ public_key.size() != key_length.value()) {
+ return absl::InvalidArgumentError(absl::StrCat(
+ "Invalid key. Key size mismatch. Expected:", key_length.value(),
+ " Actual:", public_key.size()));
+ }
+
+ ConfigMap configs;
+ PublicKeyMap keys;
+ uint8_t key_id = single_key_config.GetKeyId();
+ keys.emplace(key_id, public_key);
+ configs[key_id].emplace_back(std::move(single_key_config));
+ return ObliviousHttpKeyConfigs(std::move(configs), std::move(keys));
+}
+
+absl::StatusOr<std::string> ObliviousHttpKeyConfigs::GenerateConcatenatedKeys()
+ const {
+ std::string concatenated_keys;
+ for (const auto& [key_id, ohttp_configs] : configs_) {
+ auto key = public_keys_.find(key_id);
+ if (key == public_keys_.end()) {
+ return absl::InternalError(
+ "Failed to serialize. No public key found for key_id");
+ }
+ auto serialized =
+ SerializeOhttpKeyWithPublicKey(key_id, key->second, ohttp_configs);
+ if (!serialized.ok()) {
+ return absl::InternalError("Failed to serialize OHTTP key configs.");
+ }
+ absl::StrAppend(&concatenated_keys, serialized.value());
+ }
+ return concatenated_keys;
+}
+
ObliviousHttpHeaderKeyConfig ObliviousHttpKeyConfigs::PreferredConfig() const {
// configs_ is forced to have at least one object during construction.
return configs_.begin()->second.front();
@@ -220,21 +420,6 @@
return key->second;
}
-namespace {
-// https://www.rfc-editor.org/rfc/rfc9180#section-7.1
-// TODO(kmg): Switch to BoringSSL's EVP_HPKE_KEM_public_key_len()
-// https://boringssl-review.googlesource.com/c/boringssl/+/54605
-absl::StatusOr<uint16_t> KeyLength(uint16_t kem_id) {
- switch (kem_id) {
- case EVP_HPKE_DHKEM_X25519_HKDF_SHA256:
- return 32;
- default:
- return absl::InvalidArgumentError(
- "Unsupported kem_id; public key length is unknown.");
- }
-}
-} // namespace
-
absl::Status ObliviousHttpKeyConfigs::ReadSingleKeyConfig(
QuicheDataReader& reader, ConfigMap& configs, PublicKeyMap& keys) {
uint8_t key_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 9c46f81..488561b 100644
--- a/quiche/oblivious_http/common/oblivious_http_header_key_config.h
+++ b/quiche/oblivious_http/common/oblivious_http_header_key_config.h
@@ -5,6 +5,7 @@
#include "absl/container/btree_map.h"
#include "absl/container/flat_hash_map.h"
+#include "absl/container/flat_hash_set.h"
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/string_view.h"
@@ -111,12 +112,74 @@
// ObliviousHttpKeyConfigs objects are immutable after construction.
class QUICHE_EXPORT ObliviousHttpKeyConfigs {
public:
+ // Below two structures follow the Single key configuration spec in OHTTP RFC.
+ // https://www.ietf.org/archive/id/draft-ietf-ohai-ohttp-06.html#name-a-single-key-configuration
+ struct SymmetricAlgorithmsConfig {
+ uint16_t kdf_id;
+ uint16_t aead_id;
+
+ bool operator==(const SymmetricAlgorithmsConfig& other) const {
+ return kdf_id == other.kdf_id && aead_id == other.aead_id;
+ }
+
+ template <typename H>
+ friend H AbslHashValue(H h, const SymmetricAlgorithmsConfig& sym_alg_cfg) {
+ return H::combine(std::move(h), sym_alg_cfg.kdf_id, sym_alg_cfg.aead_id);
+ }
+ };
+
+ struct OhttpKeyConfig {
+ uint8_t key_id;
+ uint16_t kem_id;
+ std::string public_key; // Raw byte string.
+ absl::flat_hash_set<SymmetricAlgorithmsConfig> symmetric_algorithms;
+
+ bool operator==(const OhttpKeyConfig& other) const {
+ return key_id == other.key_id && kem_id == other.kem_id &&
+ public_key == other.public_key &&
+ symmetric_algorithms == other.symmetric_algorithms;
+ }
+
+ template <typename H>
+ friend H AbslHashValue(H h, const OhttpKeyConfig& ohttp_key_cfg) {
+ return H::combine(std::move(h), ohttp_key_cfg.key_id,
+ ohttp_key_cfg.kem_id, ohttp_key_cfg.public_key,
+ ohttp_key_cfg.symmetric_algorithms);
+ }
+ };
+
// Parses the "application/ohttp-keys" media type, which is a byte string
// formatted according to the spec:
// https://www.ietf.org/archive/id/draft-ietf-ohai-ohttp-04.html#section-3
static absl::StatusOr<ObliviousHttpKeyConfigs> ParseConcatenatedKeys(
absl::string_view key_configs);
+ // Builds `ObliviousHttpKeyConfigs` with multiple key configurations, each
+ // made up of Single Key Configuration([{key_id, kem_id, public key},
+ // Set<SymmetricAlgos>]) encoding specified in section 3.
+ // https://www.ietf.org/archive/id/draft-ietf-ohai-ohttp-03.html#name-key-configuration-encoding
+ // @params: Set<{key_id, kem_id, public key, Set<HPKE Symmetric Algorithms>>.
+ // @return: When given all valid configs supported by BoringSSL, builds and
+ // returns `ObliviousHttpKeyConfigs`. If any one of the input configs are
+ // invalid or unsupported by BSSL, returns an error.
+ // @note: Subsequently, To get concatenated keys[contiguous byte string of
+ // keys], use `GenerateConcatenatedKeys()`. This output can inturn be parsed
+ // by `ObliviousHttpKeyConfigs::ParseConcatenatedKeys` on client side.
+ static absl::StatusOr<ObliviousHttpKeyConfigs> Create(
+ absl::flat_hash_set<OhttpKeyConfig> ohttp_key_configs);
+
+ // Builds `ObliviousHttpKeyConfigs` with given public_key and Single key
+ // configuration specified in `ObliviousHttpHeaderKeyConfig` object. After
+ // successful `Create`, clients can call `GenerateConcatenatedKeys()` to build
+ // the Single key config.
+ static absl::StatusOr<ObliviousHttpKeyConfigs> Create(
+ const ObliviousHttpHeaderKeyConfig& single_key_config,
+ absl::string_view public_key);
+
+ // Generates byte string corresponding to "application/ohttp-keys" media type.
+ // https://www.ietf.org/archive/id/draft-ietf-ohai-ohttp-04.html#section-3
+ absl::StatusOr<std::string> GenerateConcatenatedKeys() const;
+
int NumKeys() const { return public_keys_.size(); }
// Returns a preferred config to use. The preferred key is the key with
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 0227ee4..73e4c07 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
@@ -3,6 +3,7 @@
#include <cstdint>
#include "absl/strings/escaping.h"
+#include "absl/strings/str_cat.h"
#include "openssl/hpke.h"
#include "quiche/common/platform/api/quiche_logging.h"
#include "quiche/common/platform/api/quiche_test.h"
@@ -13,6 +14,8 @@
using ::testing::AllOf;
using ::testing::Property;
using ::testing::StrEq;
+using ::testing::UnorderedElementsAre;
+using ::testing::UnorderedElementsAreArray;
/**
* Build Request header.
@@ -30,6 +33,28 @@
return hdr;
}
+std::string GetSerializedKeyConfig(
+ ObliviousHttpKeyConfigs::OhttpKeyConfig& key_config) {
+ uint16_t symmetric_algs_length =
+ key_config.symmetric_algorithms.size() *
+ (sizeof(key_config.symmetric_algorithms.cbegin()->kdf_id) +
+ sizeof(key_config.symmetric_algorithms.cbegin()->aead_id));
+ int buf_len = sizeof(key_config.key_id) + sizeof(key_config.kem_id) +
+ key_config.public_key.size() + sizeof(symmetric_algs_length) +
+ symmetric_algs_length;
+ std::string ohttp_key(buf_len, '\0');
+ QuicheDataWriter writer(ohttp_key.size(), ohttp_key.data());
+ EXPECT_TRUE(writer.WriteUInt8(key_config.key_id));
+ EXPECT_TRUE(writer.WriteUInt16(key_config.kem_id));
+ EXPECT_TRUE(writer.WriteStringPiece(key_config.public_key));
+ EXPECT_TRUE(writer.WriteUInt16(symmetric_algs_length));
+ for (const auto& symmetric_alg : key_config.symmetric_algorithms) {
+ EXPECT_TRUE(writer.WriteUInt16(symmetric_alg.kdf_id));
+ EXPECT_TRUE(writer.WriteUInt16(symmetric_alg.aead_id));
+ }
+ return ohttp_key;
+}
+
TEST(ObliviousHttpHeaderKeyConfig, TestSerializeRecipientContextInfo) {
uint8_t key_id = 3;
uint16_t kem_id = EVP_HPKE_DHKEM_X25519_HKDF_SHA256;
@@ -198,5 +223,134 @@
EXPECT_FALSE(ObliviousHttpKeyConfigs::ParseConcatenatedKeys(key).ok());
}
+TEST(ObliviousHttpHeaderKeyConfigs, TestCreateWithSingleKeyConfig) {
+ auto instance = ObliviousHttpHeaderKeyConfig::Create(
+ 123, EVP_HPKE_DHKEM_X25519_HKDF_SHA256, EVP_HPKE_HKDF_SHA256,
+ EVP_HPKE_CHACHA20_POLY1305);
+ EXPECT_TRUE(instance.ok());
+ std::string test_public_key(
+ EVP_HPKE_KEM_public_key_len(instance->GetHpkeKem()), 'a');
+ auto configs =
+ ObliviousHttpKeyConfigs::Create(instance.value(), test_public_key);
+ EXPECT_TRUE(configs.ok());
+ auto serialized_key = configs->GenerateConcatenatedKeys();
+ EXPECT_TRUE(serialized_key.ok());
+ auto ohttp_configs =
+ ObliviousHttpKeyConfigs::ParseConcatenatedKeys(serialized_key.value());
+ EXPECT_TRUE(ohttp_configs.ok());
+ ASSERT_EQ(ohttp_configs->PreferredConfig().GetKeyId(), 123);
+ auto parsed_public_key = ohttp_configs->GetPublicKeyForId(123);
+ EXPECT_TRUE(parsed_public_key.ok());
+ EXPECT_EQ(parsed_public_key.value(), test_public_key);
+}
+
+TEST(ObliviousHttpHeaderKeyConfigs, TestCreateWithWithMultipleKeys) {
+ std::string expected_preferred_public_key(32, 'b');
+ 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,
+ expected_preferred_public_key,
+ {{EVP_HPKE_HKDF_SHA256, EVP_HPKE_CHACHA20_POLY1305}}};
+ auto configs = ObliviousHttpKeyConfigs::Create({config1, config2});
+ EXPECT_TRUE(configs.ok());
+ auto serialized_key = configs->GenerateConcatenatedKeys();
+ EXPECT_TRUE(serialized_key.ok());
+ ASSERT_EQ(serialized_key.value(),
+ absl::StrCat(GetSerializedKeyConfig(config2),
+ GetSerializedKeyConfig(config1)));
+ auto ohttp_configs =
+ ObliviousHttpKeyConfigs::ParseConcatenatedKeys(serialized_key.value());
+ EXPECT_TRUE(ohttp_configs.ok());
+ ASSERT_EQ(ohttp_configs->NumKeys(), 2);
+ EXPECT_THAT(configs->PreferredConfig(),
+ AllOf(HasKeyId(200), HasKemId(EVP_HPKE_DHKEM_X25519_HKDF_SHA256),
+ HasKdfId(EVP_HPKE_HKDF_SHA256),
+ HasAeadId(EVP_HPKE_CHACHA20_POLY1305)));
+ auto parsed_preferred_public_key = ohttp_configs->GetPublicKeyForId(
+ ohttp_configs->PreferredConfig().GetKeyId());
+ EXPECT_TRUE(parsed_preferred_public_key.ok());
+ EXPECT_EQ(parsed_preferred_public_key.value(), expected_preferred_public_key);
+}
+
+TEST(ObliviousHttpHeaderKeyConfigs, TestCreateWithInvalidConfigs) {
+ ASSERT_EQ(ObliviousHttpKeyConfigs::Create({}).status().code(),
+ absl::StatusCode::kInvalidArgument);
+ ASSERT_EQ(ObliviousHttpKeyConfigs::Create(
+ {{100, 2, std::string(32, 'a'), {{2, 3}, {4, 5}}},
+ {200, 6, std::string(32, 'b'), {{7, 8}, {9, 10}}}})
+ .status()
+ .code(),
+ absl::StatusCode::kInvalidArgument);
+
+ EXPECT_EQ(
+ ObliviousHttpKeyConfigs::Create(
+ {{123,
+ EVP_HPKE_DHKEM_X25519_HKDF_SHA256,
+ "invalid key length" /*expected length for given kem_id is 32*/,
+ {{EVP_HPKE_HKDF_SHA256, EVP_HPKE_AES_128_GCM}}}})
+ .status()
+ .code(),
+ absl::StatusCode::kInvalidArgument);
+}
+
+TEST(ObliviousHttpHeaderKeyConfigs,
+ TestCreateSingleKeyConfigWithInvalidConfig) {
+ const auto sample_ohttp_hdr_config = ObliviousHttpHeaderKeyConfig::Create(
+ 123, EVP_HPKE_DHKEM_X25519_HKDF_SHA256, EVP_HPKE_HKDF_SHA256,
+ EVP_HPKE_AES_128_GCM);
+ ASSERT_TRUE(sample_ohttp_hdr_config.ok());
+ ASSERT_EQ(ObliviousHttpKeyConfigs::Create(sample_ohttp_hdr_config.value(),
+ "" /*empty public_key*/)
+ .status()
+ .code(),
+ absl::StatusCode::kInvalidArgument);
+ EXPECT_EQ(ObliviousHttpKeyConfigs::Create(
+ sample_ohttp_hdr_config.value(),
+ "invalid key length" /*expected length for given kem_id is 32*/)
+ .status()
+ .code(),
+ absl::StatusCode::kInvalidArgument);
+}
+
+TEST(ObliviousHttpHeaderKeyConfigs, TestHashImplWithObliviousStruct) {
+ // Insert different symmetric algorithms 50 times.
+ absl::flat_hash_set<ObliviousHttpKeyConfigs::SymmetricAlgorithmsConfig>
+ symmetric_algs_set;
+ for (int i = 0; i < 50; ++i) {
+ symmetric_algs_set.insert({EVP_HPKE_HKDF_SHA256, EVP_HPKE_AES_128_GCM});
+ symmetric_algs_set.insert({EVP_HPKE_HKDF_SHA256, EVP_HPKE_AES_256_GCM});
+ symmetric_algs_set.insert(
+ {EVP_HPKE_HKDF_SHA256, EVP_HPKE_CHACHA20_POLY1305});
+ }
+ ASSERT_EQ(symmetric_algs_set.size(), 3);
+ EXPECT_THAT(symmetric_algs_set,
+ UnorderedElementsAreArray<
+ ObliviousHttpKeyConfigs::SymmetricAlgorithmsConfig>({
+ {EVP_HPKE_HKDF_SHA256, EVP_HPKE_AES_128_GCM},
+ {EVP_HPKE_HKDF_SHA256, EVP_HPKE_AES_256_GCM},
+ {EVP_HPKE_HKDF_SHA256, EVP_HPKE_CHACHA20_POLY1305},
+ }));
+
+ // Insert different Key configs 50 times.
+ absl::flat_hash_set<ObliviousHttpKeyConfigs::OhttpKeyConfig>
+ ohttp_key_configs_set;
+ ObliviousHttpKeyConfigs::OhttpKeyConfig expected_key_config{
+ 100,
+ EVP_HPKE_DHKEM_X25519_HKDF_SHA256,
+ std::string(32, 'c'),
+ {{EVP_HPKE_HKDF_SHA256, EVP_HPKE_AES_128_GCM},
+ {EVP_HPKE_HKDF_SHA256, EVP_HPKE_AES_256_GCM}}};
+ for (int i = 0; i < 50; ++i) {
+ ohttp_key_configs_set.insert(expected_key_config);
+ }
+ ASSERT_EQ(ohttp_key_configs_set.size(), 1);
+ EXPECT_THAT(ohttp_key_configs_set, UnorderedElementsAre(expected_key_config));
+}
+
} // namespace
} // namespace quiche