| #include "quiche/oblivious_http/buffers/oblivious_http_request.h" | 
 |  | 
 | #include <stddef.h> | 
 |  | 
 | #include <cstdint> | 
 | #include <memory> | 
 | #include <string> | 
 | #include <utility> | 
 |  | 
 | #include "absl/status/status.h" | 
 | #include "absl/status/statusor.h" | 
 | #include "absl/strings/escaping.h" | 
 | #include "absl/strings/str_cat.h" | 
 | #include "absl/strings/string_view.h" | 
 | #include "openssl/hkdf.h" | 
 | #include "openssl/hpke.h" | 
 | #include "quiche/common/platform/api/quiche_test.h" | 
 | #include "quiche/common/quiche_data_reader.h" | 
 | #include "quiche/oblivious_http/common/oblivious_http_header_key_config.h" | 
 |  | 
 | namespace quiche { | 
 |  | 
 | namespace { | 
 | const uint32_t kHeaderLength = ObliviousHttpHeaderKeyConfig::kHeaderLength; | 
 | std::string GetHpkePrivateKey() { | 
 |   absl::string_view hpke_key_hex = | 
 |       "b77431ecfa8f4cfc30d6e467aafa06944dffe28cb9dd1409e33a3045f5adc8a1"; | 
 |   std::string hpke_key_bytes; | 
 |   EXPECT_TRUE(absl::HexStringToBytes(hpke_key_hex, &hpke_key_bytes)); | 
 |   return hpke_key_bytes; | 
 | } | 
 |  | 
 | std::string GetHpkePublicKey() { | 
 |   absl::string_view public_key = | 
 |       "6d21cfe09fbea5122f9ebc2eb2a69fcc4f06408cd54aac934f012e76fcdcef62"; | 
 |   std::string public_key_bytes; | 
 |   EXPECT_TRUE(absl::HexStringToBytes(public_key, &public_key_bytes)); | 
 |   return public_key_bytes; | 
 | } | 
 |  | 
 | std::string GetAlternativeHpkePublicKey() { | 
 |   absl::string_view public_key = | 
 |       "6d21cfe09fbea5122f9ebc2eb2a69fcc4f06408cd54aac934f012e76fcdcef63"; | 
 |   std::string public_key_bytes; | 
 |   EXPECT_TRUE(absl::HexStringToBytes(public_key, &public_key_bytes)); | 
 |   return public_key_bytes; | 
 | } | 
 |  | 
 | std::string GetSeed() { | 
 |   absl::string_view seed = | 
 |       "52c4a758a802cd8b936eceea314432798d5baf2d7e9235dc084ab1b9cfa2f736"; | 
 |   std::string seed_bytes; | 
 |   EXPECT_TRUE(absl::HexStringToBytes(seed, &seed_bytes)); | 
 |   return seed_bytes; | 
 | } | 
 |  | 
 | std::string GetSeededEncapsulatedKey() { | 
 |   absl::string_view encapsulated_key = | 
 |       "37fda3567bdbd628e88668c3c8d7e97d1d1253b6d4ea6d44c150f741f1bf4431"; | 
 |   std::string encapsulated_key_bytes; | 
 |   EXPECT_TRUE( | 
 |       absl::HexStringToBytes(encapsulated_key, &encapsulated_key_bytes)); | 
 |   return encapsulated_key_bytes; | 
 | } | 
 |  | 
 | bssl::UniquePtr<EVP_HPKE_KEY> ConstructHpkeKey( | 
 |     absl::string_view hpke_key, | 
 |     const ObliviousHttpHeaderKeyConfig &ohttp_key_config) { | 
 |   bssl::UniquePtr<EVP_HPKE_KEY> bssl_hpke_key(EVP_HPKE_KEY_new()); | 
 |   EXPECT_NE(bssl_hpke_key, nullptr); | 
 |   EXPECT_TRUE(EVP_HPKE_KEY_init( | 
 |       bssl_hpke_key.get(), ohttp_key_config.GetHpkeKem(), | 
 |       reinterpret_cast<const uint8_t *>(hpke_key.data()), hpke_key.size())); | 
 |   return bssl_hpke_key; | 
 | } | 
 |  | 
 | const ObliviousHttpHeaderKeyConfig GetOhttpKeyConfig(uint8_t key_id, | 
 |                                                      uint16_t kem_id, | 
 |                                                      uint16_t kdf_id, | 
 |                                                      uint16_t aead_id) { | 
 |   auto ohttp_key_config = | 
 |       ObliviousHttpHeaderKeyConfig::Create(key_id, kem_id, kdf_id, aead_id); | 
 |   EXPECT_TRUE(ohttp_key_config.ok()); | 
 |   return std::move(ohttp_key_config.value()); | 
 | } | 
 | }  // namespace | 
 |  | 
 | // Direct test example from OHttp spec. | 
 | // https://www.ietf.org/archive/id/draft-ietf-ohai-ohttp-03.html#appendix-A | 
 | TEST(ObliviousHttpRequest, TestDecapsulateWithSpecAppendixAExample) { | 
 |   auto ohttp_key_config = | 
 |       GetOhttpKeyConfig(/*key_id=*/1, EVP_HPKE_DHKEM_X25519_HKDF_SHA256, | 
 |                         EVP_HPKE_HKDF_SHA256, EVP_HPKE_AES_128_GCM); | 
 |  | 
 |   // X25519 Secret key (priv key). | 
 |   // https://www.ietf.org/archive/id/draft-ietf-ohai-ohttp-03.html#appendix-A-2 | 
 |   constexpr absl::string_view kX25519SecretKey = | 
 |       "3c168975674b2fa8e465970b79c8dcf09f1c741626480bd4c6162fc5b6a98e1a"; | 
 |  | 
 |   // Encapsulated request. | 
 |   // https://www.ietf.org/archive/id/draft-ietf-ohai-ohttp-03.html#appendix-A-14 | 
 |   constexpr absl::string_view kEncapsulatedRequest = | 
 |       "010020000100014b28f881333e7c164ffc499ad9796f877f4e1051ee6d31bad19dec96c2" | 
 |       "08b4726374e469135906992e1268c594d2a10c695d858c40a026e7965e7d86b83dd440b2" | 
 |       "c0185204b4d63525"; | 
 |  | 
 |   // Initialize Request obj to Decapsulate (decrypt). | 
 |   std::string encapsulated_request_bytes; | 
 |   ASSERT_TRUE(absl::HexStringToBytes(kEncapsulatedRequest, | 
 |                                      &encapsulated_request_bytes)); | 
 |   std::string x25519_secret_key_bytes; | 
 |   ASSERT_TRUE( | 
 |       absl::HexStringToBytes(kX25519SecretKey, &x25519_secret_key_bytes)); | 
 |   auto instance = ObliviousHttpRequest::CreateServerObliviousRequest( | 
 |       encapsulated_request_bytes, | 
 |       *(ConstructHpkeKey(x25519_secret_key_bytes, ohttp_key_config)), | 
 |       ohttp_key_config); | 
 |   ASSERT_TRUE(instance.ok()); | 
 |   auto decrypted = instance->GetPlaintextData(); | 
 |  | 
 |   // Encapsulated/Ephemeral public key. | 
 |   // https://www.ietf.org/archive/id/draft-ietf-ohai-ohttp-03.html#appendix-A-10 | 
 |   constexpr absl::string_view kExpectedEphemeralPublicKey = | 
 |       "4b28f881333e7c164ffc499ad9796f877f4e1051ee6d31bad19dec96c208b472"; | 
 |   std::string expected_ephemeral_public_key_bytes; | 
 |   ASSERT_TRUE(absl::HexStringToBytes(kExpectedEphemeralPublicKey, | 
 |                                      &expected_ephemeral_public_key_bytes)); | 
 |   auto oblivious_request_context = std::move(instance.value()).ReleaseContext(); | 
 |   EXPECT_EQ(oblivious_request_context.encapsulated_key_, | 
 |             expected_ephemeral_public_key_bytes); | 
 |  | 
 |   // Binary HTTP message. | 
 |   // https://www.ietf.org/archive/id/draft-ietf-ohai-ohttp-03.html#appendix-A-6 | 
 |   constexpr absl::string_view kExpectedBinaryHTTPMessage = | 
 |       "00034745540568747470730b6578616d706c652e636f6d012f"; | 
 |   std::string expected_binary_http_message_bytes; | 
 |   ASSERT_TRUE(absl::HexStringToBytes(kExpectedBinaryHTTPMessage, | 
 |                                      &expected_binary_http_message_bytes)); | 
 |   EXPECT_EQ(decrypted, expected_binary_http_message_bytes); | 
 | } | 
 |  | 
 | TEST(ObliviousHttpRequest, TestEncapsulatedRequestStructure) { | 
 |   uint8_t test_key_id = 7; | 
 |   uint16_t test_kem_id = EVP_HPKE_DHKEM_X25519_HKDF_SHA256; | 
 |   uint16_t test_kdf_id = EVP_HPKE_HKDF_SHA256; | 
 |   uint16_t test_aead_id = EVP_HPKE_AES_256_GCM; | 
 |   std::string plaintext = "test"; | 
 |   auto instance = ObliviousHttpRequest::CreateClientObliviousRequest( | 
 |       plaintext, GetHpkePublicKey(), | 
 |       GetOhttpKeyConfig(test_key_id, test_kem_id, test_kdf_id, test_aead_id)); | 
 |   ASSERT_TRUE(instance.ok()); | 
 |   auto payload_bytes = instance->EncapsulateAndSerialize(); | 
 |   EXPECT_GE(payload_bytes.size(), kHeaderLength); | 
 |   // Parse header. | 
 |   QuicheDataReader reader(payload_bytes); | 
 |   uint8_t key_id; | 
 |   EXPECT_TRUE(reader.ReadUInt8(&key_id)); | 
 |   EXPECT_EQ(key_id, test_key_id); | 
 |   uint16_t kem_id; | 
 |   EXPECT_TRUE(reader.ReadUInt16(&kem_id)); | 
 |   EXPECT_EQ(kem_id, test_kem_id); | 
 |   uint16_t kdf_id; | 
 |   EXPECT_TRUE(reader.ReadUInt16(&kdf_id)); | 
 |   EXPECT_EQ(kdf_id, test_kdf_id); | 
 |   uint16_t aead_id; | 
 |   EXPECT_TRUE(reader.ReadUInt16(&aead_id)); | 
 |   EXPECT_EQ(aead_id, test_aead_id); | 
 |   auto client_request_context = std::move(instance.value()).ReleaseContext(); | 
 |   auto client_encapsulated_key = client_request_context.encapsulated_key_; | 
 |   EXPECT_EQ(client_encapsulated_key.size(), X25519_PUBLIC_VALUE_LEN); | 
 |   auto enc_key_plus_ciphertext = payload_bytes.substr(kHeaderLength); | 
 |   auto packed_encapsulated_key = | 
 |       enc_key_plus_ciphertext.substr(0, X25519_PUBLIC_VALUE_LEN); | 
 |   EXPECT_EQ(packed_encapsulated_key, client_encapsulated_key); | 
 |   auto ciphertext = enc_key_plus_ciphertext.substr(X25519_PUBLIC_VALUE_LEN); | 
 |   EXPECT_GE(ciphertext.size(), plaintext.size()); | 
 | } | 
 |  | 
 | TEST(ObliviousHttpRequest, TestDeterministicSeededOhttpRequest) { | 
 |   auto ohttp_key_config = | 
 |       GetOhttpKeyConfig(4, EVP_HPKE_DHKEM_X25519_HKDF_SHA256, | 
 |                         EVP_HPKE_HKDF_SHA256, EVP_HPKE_AES_256_GCM); | 
 |   auto encapsulated = ObliviousHttpRequest::CreateClientWithSeedForTesting( | 
 |       "test", GetHpkePublicKey(), ohttp_key_config, GetSeed()); | 
 |   ASSERT_TRUE(encapsulated.ok()); | 
 |   auto encapsulated_request = encapsulated->EncapsulateAndSerialize(); | 
 |   auto ohttp_request_context = std::move(encapsulated.value()).ReleaseContext(); | 
 |   EXPECT_EQ(ohttp_request_context.encapsulated_key_, | 
 |             GetSeededEncapsulatedKey()); | 
 |   absl::string_view expected_encrypted_request = | 
 |       "9f37cfed07d0111ecd2c34f794671759bcbd922a"; | 
 |   std::string expected_encrypted_request_bytes; | 
 |   ASSERT_TRUE(absl::HexStringToBytes(expected_encrypted_request, | 
 |                                      &expected_encrypted_request_bytes)); | 
 |   EXPECT_NE(ohttp_request_context.hpke_context_, nullptr); | 
 |   size_t encapsulated_key_len = EVP_HPKE_KEM_enc_len( | 
 |       EVP_HPKE_CTX_kem(ohttp_request_context.hpke_context_.get())); | 
 |   int encrypted_payload_offset = kHeaderLength + encapsulated_key_len; | 
 |   EXPECT_EQ(encapsulated_request.substr(encrypted_payload_offset), | 
 |             expected_encrypted_request_bytes); | 
 | } | 
 |  | 
 | TEST(ObliviousHttpRequest, | 
 |      TestSeededEncapsulatedKeySamePlaintextsSameCiphertexts) { | 
 |   auto ohttp_key_config = | 
 |       GetOhttpKeyConfig(8, EVP_HPKE_DHKEM_X25519_HKDF_SHA256, | 
 |                         EVP_HPKE_HKDF_SHA256, EVP_HPKE_AES_256_GCM); | 
 |   auto req_with_same_plaintext_1 = | 
 |       ObliviousHttpRequest::CreateClientWithSeedForTesting( | 
 |           "same plaintext", GetHpkePublicKey(), ohttp_key_config, GetSeed()); | 
 |   ASSERT_TRUE(req_with_same_plaintext_1.ok()); | 
 |   auto ciphertext_1 = req_with_same_plaintext_1->EncapsulateAndSerialize(); | 
 |   auto req_with_same_plaintext_2 = | 
 |       ObliviousHttpRequest::CreateClientWithSeedForTesting( | 
 |           "same plaintext", GetHpkePublicKey(), ohttp_key_config, GetSeed()); | 
 |   ASSERT_TRUE(req_with_same_plaintext_2.ok()); | 
 |   auto ciphertext_2 = req_with_same_plaintext_2->EncapsulateAndSerialize(); | 
 |   EXPECT_EQ(ciphertext_1, ciphertext_2); | 
 | } | 
 |  | 
 | TEST(ObliviousHttpRequest, | 
 |      TestSeededEncapsulatedKeyDifferentPlaintextsDifferentCiphertexts) { | 
 |   auto ohttp_key_config = | 
 |       GetOhttpKeyConfig(8, EVP_HPKE_DHKEM_X25519_HKDF_SHA256, | 
 |                         EVP_HPKE_HKDF_SHA256, EVP_HPKE_AES_256_GCM); | 
 |   auto req_with_different_plaintext_1 = | 
 |       ObliviousHttpRequest::CreateClientWithSeedForTesting( | 
 |           "different 1", GetHpkePublicKey(), ohttp_key_config, GetSeed()); | 
 |   ASSERT_TRUE(req_with_different_plaintext_1.ok()); | 
 |   auto ciphertext_1 = req_with_different_plaintext_1->EncapsulateAndSerialize(); | 
 |   auto req_with_different_plaintext_2 = | 
 |       ObliviousHttpRequest::CreateClientWithSeedForTesting( | 
 |           "different 2", GetHpkePublicKey(), ohttp_key_config, GetSeed()); | 
 |   ASSERT_TRUE(req_with_different_plaintext_2.ok()); | 
 |   auto ciphertext_2 = req_with_different_plaintext_2->EncapsulateAndSerialize(); | 
 |   EXPECT_NE(ciphertext_1, ciphertext_2); | 
 | } | 
 |  | 
 | TEST(ObliviousHttpRequest, TestInvalidInputsOnClientSide) { | 
 |   auto ohttp_key_config = | 
 |       GetOhttpKeyConfig(30, EVP_HPKE_DHKEM_X25519_HKDF_SHA256, | 
 |                         EVP_HPKE_HKDF_SHA256, EVP_HPKE_AES_256_GCM); | 
 |   // Empty plaintext. | 
 |   EXPECT_EQ(ObliviousHttpRequest::CreateClientObliviousRequest( | 
 |                 /*plaintext_payload*/ "", GetHpkePublicKey(), ohttp_key_config) | 
 |                 .status() | 
 |                 .code(), | 
 |             absl::StatusCode::kInvalidArgument); | 
 |   // Empty HPKE public key. | 
 |   EXPECT_EQ(ObliviousHttpRequest::CreateClientObliviousRequest( | 
 |                 "some plaintext", | 
 |                 /*hpke_public_key*/ "", ohttp_key_config) | 
 |                 .status() | 
 |                 .code(), | 
 |             absl::StatusCode::kInvalidArgument); | 
 | } | 
 |  | 
 | TEST(ObliviousHttpRequest, TestInvalidInputsOnServerSide) { | 
 |   auto ohttp_key_config = | 
 |       GetOhttpKeyConfig(4, EVP_HPKE_DHKEM_X25519_HKDF_SHA256, | 
 |                         EVP_HPKE_HKDF_SHA256, EVP_HPKE_AES_256_GCM); | 
 |   // Empty encrypted payload. | 
 |   EXPECT_EQ(ObliviousHttpRequest::CreateServerObliviousRequest( | 
 |                 /*encrypted_data*/ "", | 
 |                 *(ConstructHpkeKey(GetHpkePrivateKey(), ohttp_key_config)), | 
 |                 ohttp_key_config) | 
 |                 .status() | 
 |                 .code(), | 
 |             absl::StatusCode::kInvalidArgument); | 
 |   // Empty EVP_HPKE_KEY struct. | 
 |   EXPECT_EQ(ObliviousHttpRequest::CreateServerObliviousRequest( | 
 |                 absl::StrCat(ohttp_key_config.SerializeOhttpPayloadHeader(), | 
 |                              GetSeededEncapsulatedKey(), | 
 |                              "9f37cfed07d0111ecd2c34f794671759bcbd922a"), | 
 |                 /*gateway_key*/ {}, ohttp_key_config) | 
 |                 .status() | 
 |                 .code(), | 
 |             absl::StatusCode::kInvalidArgument); | 
 | } | 
 |  | 
 | TEST(ObliviousHttpRequest, EndToEndTestForRequest) { | 
 |   auto ohttp_key_config = | 
 |       GetOhttpKeyConfig(5, EVP_HPKE_DHKEM_X25519_HKDF_SHA256, | 
 |                         EVP_HPKE_HKDF_SHA256, EVP_HPKE_AES_256_GCM); | 
 |   auto encapsulate = ObliviousHttpRequest::CreateClientObliviousRequest( | 
 |       "test", GetHpkePublicKey(), ohttp_key_config); | 
 |   ASSERT_TRUE(encapsulate.ok()); | 
 |   auto oblivious_request = encapsulate->EncapsulateAndSerialize(); | 
 |   auto decapsulate = ObliviousHttpRequest::CreateServerObliviousRequest( | 
 |       oblivious_request, | 
 |       *(ConstructHpkeKey(GetHpkePrivateKey(), ohttp_key_config)), | 
 |       ohttp_key_config); | 
 |   ASSERT_TRUE(decapsulate.ok()); | 
 |   auto decrypted = decapsulate->GetPlaintextData(); | 
 |   EXPECT_EQ(decrypted, "test"); | 
 | } | 
 |  | 
 | TEST(ObliviousHttpRequest, EndToEndTestForRequestWithWrongKey) { | 
 |   auto ohttp_key_config = | 
 |       GetOhttpKeyConfig(5, EVP_HPKE_DHKEM_X25519_HKDF_SHA256, | 
 |                         EVP_HPKE_HKDF_SHA256, EVP_HPKE_AES_256_GCM); | 
 |   auto encapsulate = ObliviousHttpRequest::CreateClientObliviousRequest( | 
 |       "test", GetAlternativeHpkePublicKey(), ohttp_key_config); | 
 |   ASSERT_TRUE(encapsulate.ok()); | 
 |   auto oblivious_request = encapsulate->EncapsulateAndSerialize(); | 
 |   auto decapsulate = ObliviousHttpRequest::CreateServerObliviousRequest( | 
 |       oblivious_request, | 
 |       *(ConstructHpkeKey(GetHpkePrivateKey(), ohttp_key_config)), | 
 |       ohttp_key_config); | 
 |   EXPECT_EQ(decapsulate.status().code(), absl::StatusCode::kInvalidArgument); | 
 | } | 
 | }  // namespace quiche |