| #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/common/test_tools/quiche_test_utils.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; | |
| } | |
| 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 | |
| TEST(ObliviousHttpRequest, DecodeEncapsulatedRequestHeader) { | |
| auto ohttp_key_config = | |
| GetOhttpKeyConfig(/*key_id=*/1, EVP_HPKE_DHKEM_X25519_HKDF_SHA256, | |
| EVP_HPKE_HKDF_SHA256, EVP_HPKE_AES_128_GCM); | |
| auto hpke_key = ConstructHpkeKey(GetHpkePrivateKey(), ohttp_key_config); | |
| std::string payload = | |
| "010020000100014b28f881333e7c164ffc499ad9796f877f4e1051ee6d31bad1" | |
| "9dec96c208b4726374e469135906992e1268c594d2a10c695d858c40a026e796" | |
| "5e7d86b83dd440b2c0185204b4d63525"; | |
| std::string payload_bytes; | |
| ASSERT_TRUE(absl::HexStringToBytes(payload, &payload_bytes)); | |
| QuicheDataReader reader(payload_bytes); | |
| QUICHE_EXPECT_OK(ObliviousHttpRequest::DecodeEncapsulatedRequestHeader( | |
| reader, *hpke_key, ohttp_key_config, "test")); | |
| } | |
| TEST(ObliviousHttpRequest, DecodeEncapsulatedRequestHeaderInvalidInputs) { | |
| auto ohttp_key_config = | |
| GetOhttpKeyConfig(1, EVP_HPKE_DHKEM_X25519_HKDF_SHA256, | |
| EVP_HPKE_HKDF_SHA256, EVP_HPKE_AES_128_GCM); | |
| auto hpke_key = ConstructHpkeKey(GetHpkePrivateKey(), ohttp_key_config); | |
| std::string payload = ""; | |
| QuicheDataReader reader(payload); | |
| // Empty payload. | |
| EXPECT_EQ(ObliviousHttpRequest::DecodeEncapsulatedRequestHeader( | |
| reader, *hpke_key, ohttp_key_config, "test") | |
| .status() | |
| .code(), | |
| absl::StatusCode::kInvalidArgument); | |
| payload = | |
| "010020000100014b28f881333e7c164ffc499ad9796f877f4e1051ee6d31bad1" | |
| "9dec96c208b4726374e469135906992e1268c594d2a10c695d858c40a026e796" | |
| "5e7d86b83dd440b2c0185204b4d63525"; | |
| std::string payload_bytes; | |
| ASSERT_TRUE(absl::HexStringToBytes(payload, &payload_bytes)); | |
| QuicheDataReader reader2(payload_bytes); | |
| // Empty key | |
| EXPECT_EQ(ObliviousHttpRequest::DecodeEncapsulatedRequestHeader( | |
| reader2, {}, ohttp_key_config, "test") | |
| .status() | |
| .code(), | |
| absl::StatusCode::kInvalidArgument); | |
| // Valid key header but missing encapsulated secret. | |
| payload = "010020000100014b"; | |
| ASSERT_TRUE(absl::HexStringToBytes(payload, &payload_bytes)); | |
| QuicheDataReader reader3(payload_bytes); | |
| // Missing encapsulated secret. | |
| EXPECT_EQ(ObliviousHttpRequest::DecodeEncapsulatedRequestHeader( | |
| reader3, *hpke_key, ohttp_key_config, "test") | |
| .status() | |
| .code(), | |
| absl::StatusCode::kFailedPrecondition); | |
| } | |
| TEST(ObliviousHttpRequest, DecryptChunks) { | |
| // Example from | |
| // https://www.ietf.org/archive/id/draft-ietf-ohai-chunked-ohttp-05.html#appendix-A | |
| auto ohttp_key_config = | |
| GetOhttpKeyConfig(1, EVP_HPKE_DHKEM_X25519_HKDF_SHA256, | |
| EVP_HPKE_HKDF_SHA256, EVP_HPKE_AES_128_GCM); | |
| std::string kX25519SecretKey = | |
| "1c190d72acdbe4dbc69e680503bb781a932c70a12c8f3754434c67d8640d8698"; | |
| std::string x25519_secret_key_bytes; | |
| EXPECT_TRUE( | |
| absl::HexStringToBytes(kX25519SecretKey, &x25519_secret_key_bytes)); | |
| auto hpke_key = ConstructHpkeKey(x25519_secret_key_bytes, ohttp_key_config); | |
| std::string encapsulated_request_headers = | |
| "01002000010001" | |
| "8811eb457e100811c40a0aa71340a1b81d804bb986f736f2f566a7199761a032"; | |
| std::string encapsulated_request_headers_bytes; | |
| EXPECT_TRUE(absl::HexStringToBytes(encapsulated_request_headers, | |
| &encapsulated_request_headers_bytes)); | |
| QuicheDataReader reader(encapsulated_request_headers_bytes); | |
| auto context = ObliviousHttpRequest::DecodeEncapsulatedRequestHeader( | |
| reader, *hpke_key, ohttp_key_config, | |
| ObliviousHttpHeaderKeyConfig::kChunkedOhttpRequestLabel); | |
| QUICHE_EXPECT_OK(context); | |
| // Encrypted chunks from | |
| // https://www.ietf.org/archive/id/draft-ietf-ohai-chunked-ohttp-05.html#appendix-A | |
| std::string decrypted_payload = ""; | |
| std::string encrypted_non_final_chunks[] = { | |
| "2ad24942d4d692563012f2980c8fef437a336b9b2fc938ef77a5834f", | |
| "2e33d8fd25577afe31bd1c79d094f76b6250ae6549b473ecd950501311"}; | |
| for (const auto& encrypted_chunk : encrypted_non_final_chunks) { | |
| std::string encrypted_chunk_bytes; | |
| EXPECT_TRUE( | |
| absl::HexStringToBytes(encrypted_chunk, &encrypted_chunk_bytes)); | |
| auto decrypted_chunk = ObliviousHttpRequest::DecryptChunk( | |
| *context, encrypted_chunk_bytes, /*is_final_chunk=*/false); | |
| QUICHE_EXPECT_OK(decrypted_chunk); | |
| absl::StrAppend(&decrypted_payload, *decrypted_chunk); | |
| auto debug = absl::BytesToHexString(*decrypted_chunk); | |
| EXPECT_NE(debug, encrypted_chunk); | |
| } | |
| std::string final_encrypted_chunk = "1c6c1395d0ef7c1022297966307b8a7f"; | |
| std::string final_encrypted_chunk_bytes; | |
| EXPECT_TRUE(absl::HexStringToBytes(final_encrypted_chunk, | |
| &final_encrypted_chunk_bytes)); | |
| auto decrypted_final_chunk = ObliviousHttpRequest::DecryptChunk( | |
| *context, final_encrypted_chunk_bytes, /*is_final_chunk=*/true); | |
| QUICHE_EXPECT_OK(decrypted_final_chunk); | |
| absl::StrAppend(&decrypted_payload, *decrypted_final_chunk); | |
| std::string decrypted_payload_hex = absl::BytesToHexString(decrypted_payload); | |
| EXPECT_EQ(decrypted_payload_hex, | |
| "00034745540568747470730b6578616d706c652e636f6d012f"); | |
| } | |
| TEST(ObliviousHttpRequest, DecryptChunkHandleError) { | |
| auto ohttp_key_config = | |
| GetOhttpKeyConfig(1, EVP_HPKE_DHKEM_X25519_HKDF_SHA256, | |
| EVP_HPKE_HKDF_SHA256, EVP_HPKE_AES_128_GCM); | |
| std::string kX25519SecretKey = | |
| "1c190d72acdbe4dbc69e680503bb781a932c70a12c8f3754434c67d8640d8698"; | |
| std::string x25519_secret_key_bytes; | |
| EXPECT_TRUE( | |
| absl::HexStringToBytes(kX25519SecretKey, &x25519_secret_key_bytes)); | |
| auto hpke_key = ConstructHpkeKey(x25519_secret_key_bytes, ohttp_key_config); | |
| std::string encapsulated_request_headers = | |
| "01002000010001" | |
| "8811eb457e100811c40a0aa71340a1b81d804bb986f736f2f566a7199761a032"; | |
| std::string encapsulated_request_headers_bytes; | |
| EXPECT_TRUE(absl::HexStringToBytes(encapsulated_request_headers, | |
| &encapsulated_request_headers_bytes)); | |
| QuicheDataReader reader(encapsulated_request_headers_bytes); | |
| auto context = ObliviousHttpRequest::DecodeEncapsulatedRequestHeader( | |
| reader, *hpke_key, ohttp_key_config, | |
| ObliviousHttpHeaderKeyConfig::kChunkedOhttpRequestLabel); | |
| QUICHE_EXPECT_OK(context); | |
| // Encrypted chunks from | |
| // https://www.ietf.org/archive/id/draft-ietf-ohai-chunked-ohttp-05.html#appendix-A | |
| std::string decrypted_payload = ""; | |
| // Last byte altered to make the chunk invalid. | |
| std::string encrypted_chunk = | |
| "2ad24942d4d692563012f2980c8fef437a336b9b2fc938ef77a5834e"; | |
| std::string encrypted_chunk_bytes; | |
| EXPECT_TRUE(absl::HexStringToBytes(encrypted_chunk, &encrypted_chunk_bytes)); | |
| auto decrypted_chunk = ObliviousHttpRequest::DecryptChunk( | |
| *context, encrypted_chunk_bytes, /*is_final_chunk=*/false); | |
| EXPECT_EQ(decrypted_chunk.status().code(), | |
| absl::StatusCode::kInvalidArgument); | |
| } | |
| // Direct test example from RFC. | |
| // https://www.rfc-editor.org/rfc/rfc9458.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.rfc-editor.org/rfc/rfc9458.html#appendix-A-2 | |
| constexpr absl::string_view kX25519SecretKey = | |
| "3c168975674b2fa8e465970b79c8dcf09f1c741626480bd4c6162fc5b6a98e1a"; | |
| // Encapsulated request. | |
| // https://www.rfc-editor.org/rfc/rfc9458.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.rfc-editor.org/rfc/rfc9458.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.rfc-editor.org/rfc/rfc9458.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); | |
| } | |
| TEST(ObliviousHttpRequest, CreateHpkeSenderContext) { | |
| auto ohttp_key_config = | |
| GetOhttpKeyConfig(1, EVP_HPKE_DHKEM_X25519_HKDF_SHA256, | |
| EVP_HPKE_HKDF_SHA256, EVP_HPKE_AES_128_GCM); | |
| absl::StatusOr<ObliviousHttpRequest::Context> context = | |
| ObliviousHttpRequest::CreateHpkeSenderContext( | |
| GetHpkePublicKey(), ohttp_key_config, GetSeed(), | |
| ObliviousHttpHeaderKeyConfig::kChunkedOhttpRequestLabel); | |
| QUICHE_EXPECT_OK(context); | |
| } | |
| TEST(ObliviousHttpRequest, EncryptChunkAndDecryptChunkSuccess) { | |
| ObliviousHttpHeaderKeyConfig ohttp_key_config = | |
| GetOhttpKeyConfig(1, EVP_HPKE_DHKEM_X25519_HKDF_SHA256, | |
| EVP_HPKE_HKDF_SHA256, EVP_HPKE_AES_128_GCM); | |
| absl::StatusOr<ObliviousHttpRequest::Context> sender_ctx = | |
| ObliviousHttpRequest::CreateHpkeSenderContext( | |
| GetHpkePublicKey(), ohttp_key_config, GetSeed(), | |
| ObliviousHttpHeaderKeyConfig::kOhttpRequestLabel); | |
| QUICHE_EXPECT_OK(sender_ctx); | |
| if (!sender_ctx.ok()) { | |
| return; | |
| } | |
| std::string plaintext_chunk = "test_chunk"; | |
| absl::StatusOr<std::string> encrypted_chunk = | |
| ObliviousHttpRequest::EncryptChunk(plaintext_chunk, *sender_ctx, | |
| /*is_final_chunk=*/false); | |
| QUICHE_EXPECT_OK(encrypted_chunk); | |
| if (!encrypted_chunk.ok()) { | |
| return; | |
| } | |
| bssl::UniquePtr<EVP_HPKE_KEY> hpke_key = | |
| ConstructHpkeKey(GetHpkePrivateKey(), ohttp_key_config); | |
| std::string request_header = ohttp_key_config.SerializeOhttpPayloadHeader(); | |
| std::string oblivious_http_request_header = | |
| absl::StrCat(request_header, sender_ctx->GetEncapsulatedKey()); | |
| QuicheDataReader reader(oblivious_http_request_header); | |
| auto receiver_ctx = ObliviousHttpRequest::DecodeEncapsulatedRequestHeader( | |
| reader, *hpke_key, ohttp_key_config, | |
| ObliviousHttpHeaderKeyConfig::kOhttpRequestLabel); | |
| QUICHE_EXPECT_OK(receiver_ctx); | |
| if (!receiver_ctx.ok()) { | |
| return; | |
| } | |
| auto decrypted_chunk = ObliviousHttpRequest::DecryptChunk( | |
| *receiver_ctx, *encrypted_chunk, /*is_final_chunk=*/false); | |
| QUICHE_EXPECT_OK(decrypted_chunk); | |
| if (!decrypted_chunk.ok()) { | |
| return; | |
| } | |
| EXPECT_EQ(*decrypted_chunk, plaintext_chunk); | |
| } | |
| } // namespace quiche |