| #include "quiche/oblivious_http/oblivious_http_client.h" |
| |
| #include <stdint.h> |
| |
| #include <memory> |
| #include <string> |
| #include <utility> |
| |
| #include "absl/status/status.h" |
| #include "absl/status/statusor.h" |
| #include "absl/strings/escaping.h" |
| #include "absl/strings/string_view.h" |
| #include "quiche/common/platform/api/quiche_test.h" |
| #include "quiche/common/platform/api/quiche_thread.h" |
| |
| namespace quiche { |
| |
| std::string GetHpkePrivateKey() { |
| // Dev/Test private key generated using Keystore. |
| 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() { |
| // Dev/Test public key generated using Keystore. |
| absl::string_view public_key = |
| "6d21cfe09fbea5122f9ebc2eb2a69fcc4f06408cd54aac934f012e76fcdcef62"; |
| std::string public_key_bytes; |
| EXPECT_TRUE(absl::HexStringToBytes(public_key, &public_key_bytes)); |
| return public_key_bytes; |
| } |
| |
| 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 ohttp_key_config.value(); |
| } |
| |
| 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; |
| } |
| |
| TEST(ObliviousHttpClient, TestEncapsulate) { |
| auto client = ObliviousHttpClient::Create( |
| GetHpkePublicKey(), |
| GetOhttpKeyConfig(8, EVP_HPKE_DHKEM_X25519_HKDF_SHA256, |
| EVP_HPKE_HKDF_SHA256, EVP_HPKE_AES_256_GCM)); |
| ASSERT_TRUE(client.ok()); |
| auto encrypted_req = client->CreateObliviousHttpRequest("test string 1"); |
| ASSERT_TRUE(encrypted_req.ok()); |
| auto serialized_encrypted_req = encrypted_req->EncapsulateAndSerialize(); |
| ASSERT_FALSE(serialized_encrypted_req.empty()); |
| } |
| |
| TEST(ObliviousHttpClient, TestEncryptingMultipleRequestsWithSingleInstance) { |
| auto client = ObliviousHttpClient::Create( |
| GetHpkePublicKey(), |
| GetOhttpKeyConfig(1, EVP_HPKE_DHKEM_X25519_HKDF_SHA256, |
| EVP_HPKE_HKDF_SHA256, EVP_HPKE_AES_256_GCM)); |
| ASSERT_TRUE(client.ok()); |
| auto ohttp_req_1 = client->CreateObliviousHttpRequest("test string 1"); |
| ASSERT_TRUE(ohttp_req_1.ok()); |
| auto serialized_ohttp_req_1 = ohttp_req_1->EncapsulateAndSerialize(); |
| ASSERT_FALSE(serialized_ohttp_req_1.empty()); |
| auto ohttp_req_2 = client->CreateObliviousHttpRequest("test string 2"); |
| ASSERT_TRUE(ohttp_req_2.ok()); |
| auto serialized_ohttp_req_2 = ohttp_req_2->EncapsulateAndSerialize(); |
| ASSERT_FALSE(serialized_ohttp_req_2.empty()); |
| EXPECT_NE(serialized_ohttp_req_1, serialized_ohttp_req_2); |
| } |
| |
| TEST(ObliviousHttpClient, TestInvalidHPKEKey) { |
| // Invalid public key. |
| EXPECT_EQ(ObliviousHttpClient::Create( |
| "Invalid HPKE key", |
| GetOhttpKeyConfig(50, EVP_HPKE_DHKEM_X25519_HKDF_SHA256, |
| EVP_HPKE_HKDF_SHA256, EVP_HPKE_AES_256_GCM)) |
| .status() |
| .code(), |
| absl::StatusCode::kInvalidArgument); |
| // Empty public key. |
| EXPECT_EQ(ObliviousHttpClient::Create( |
| /*hpke_public_key*/ "", |
| GetOhttpKeyConfig(50, EVP_HPKE_DHKEM_X25519_HKDF_SHA256, |
| EVP_HPKE_HKDF_SHA256, EVP_HPKE_AES_256_GCM)) |
| .status() |
| .code(), |
| absl::StatusCode::kInvalidArgument); |
| } |
| |
| TEST(ObliviousHttpClient, |
| TestTwoSamePlaintextsWillGenerateDifferentEncryptedPayloads) { |
| // Due to the nature of the encapsulated_key generated in HPKE being unique |
| // for every request, expect different encrypted payloads when encrypting same |
| // plaintexts. |
| auto client = ObliviousHttpClient::Create( |
| GetHpkePublicKey(), |
| GetOhttpKeyConfig(1, EVP_HPKE_DHKEM_X25519_HKDF_SHA256, |
| EVP_HPKE_HKDF_SHA256, EVP_HPKE_AES_256_GCM)); |
| ASSERT_TRUE(client.ok()); |
| auto encrypted_request_1 = |
| client->CreateObliviousHttpRequest("same plaintext"); |
| ASSERT_TRUE(encrypted_request_1.ok()); |
| auto serialized_encrypted_request_1 = |
| encrypted_request_1->EncapsulateAndSerialize(); |
| ASSERT_FALSE(serialized_encrypted_request_1.empty()); |
| auto encrypted_request_2 = |
| client->CreateObliviousHttpRequest("same plaintext"); |
| ASSERT_TRUE(encrypted_request_2.ok()); |
| auto serialized_encrypted_request_2 = |
| encrypted_request_2->EncapsulateAndSerialize(); |
| ASSERT_FALSE(serialized_encrypted_request_2.empty()); |
| EXPECT_NE(serialized_encrypted_request_1, serialized_encrypted_request_2); |
| } |
| |
| TEST(ObliviousHttpClient, TestObliviousResponseHandling) { |
| auto ohttp_key_config = |
| GetOhttpKeyConfig(1, EVP_HPKE_DHKEM_X25519_HKDF_SHA256, |
| EVP_HPKE_HKDF_SHA256, EVP_HPKE_AES_256_GCM); |
| auto encapsulate_req_on_client = |
| ObliviousHttpRequest::CreateClientObliviousRequest( |
| "test", GetHpkePublicKey(), ohttp_key_config); |
| ASSERT_TRUE(encapsulate_req_on_client.ok()); |
| auto decapsulate_req_on_gateway = |
| ObliviousHttpRequest::CreateServerObliviousRequest( |
| encapsulate_req_on_client->EncapsulateAndSerialize(), |
| *(ConstructHpkeKey(GetHpkePrivateKey(), ohttp_key_config)), |
| ohttp_key_config); |
| ASSERT_TRUE(decapsulate_req_on_gateway.ok()); |
| auto gateway_request_context = |
| std::move(decapsulate_req_on_gateway.value()).ReleaseContext(); |
| auto encapsulate_resp_on_gateway = |
| ObliviousHttpResponse::CreateServerObliviousResponse( |
| "test response", gateway_request_context); |
| ASSERT_TRUE(encapsulate_resp_on_gateway.ok()); |
| |
| auto client = |
| ObliviousHttpClient::Create(GetHpkePublicKey(), ohttp_key_config); |
| ASSERT_TRUE(client.ok()); |
| auto client_request_context = |
| std::move(encapsulate_req_on_client.value()).ReleaseContext(); |
| auto decapsulate_resp_on_client = client->DecryptObliviousHttpResponse( |
| encapsulate_resp_on_gateway->EncapsulateAndSerialize(), |
| client_request_context); |
| ASSERT_TRUE(decapsulate_resp_on_client.ok()); |
| EXPECT_EQ(decapsulate_resp_on_client->GetPlaintextData(), "test response"); |
| } |
| |
| TEST(ObliviousHttpClient, |
| DecryptResponseReceivedByTheClientUsingServersObliviousContext) { |
| auto ohttp_key_config = |
| GetOhttpKeyConfig(1, EVP_HPKE_DHKEM_X25519_HKDF_SHA256, |
| EVP_HPKE_HKDF_SHA256, EVP_HPKE_AES_256_GCM); |
| auto encapsulate_req_on_client = |
| ObliviousHttpRequest::CreateClientObliviousRequest( |
| "test", GetHpkePublicKey(), ohttp_key_config); |
| ASSERT_TRUE(encapsulate_req_on_client.ok()); |
| auto decapsulate_req_on_gateway = |
| ObliviousHttpRequest::CreateServerObliviousRequest( |
| encapsulate_req_on_client->EncapsulateAndSerialize(), |
| *(ConstructHpkeKey(GetHpkePrivateKey(), ohttp_key_config)), |
| ohttp_key_config); |
| ASSERT_TRUE(decapsulate_req_on_gateway.ok()); |
| auto gateway_request_context = |
| std::move(decapsulate_req_on_gateway.value()).ReleaseContext(); |
| auto encapsulate_resp_on_gateway = |
| ObliviousHttpResponse::CreateServerObliviousResponse( |
| "test response", gateway_request_context); |
| ASSERT_TRUE(encapsulate_resp_on_gateway.ok()); |
| |
| auto client = |
| ObliviousHttpClient::Create(GetHpkePublicKey(), ohttp_key_config); |
| ASSERT_TRUE(client.ok()); |
| auto decapsulate_resp_on_client = client->DecryptObliviousHttpResponse( |
| encapsulate_resp_on_gateway->EncapsulateAndSerialize(), |
| gateway_request_context); |
| ASSERT_TRUE(decapsulate_resp_on_client.ok()); |
| EXPECT_EQ(decapsulate_resp_on_client->GetPlaintextData(), "test response"); |
| } |
| |
| TEST(ObliviousHttpClient, TestWithMultipleThreads) { |
| class TestQuicheThread : public QuicheThread { |
| public: |
| TestQuicheThread(const ObliviousHttpClient& client, |
| std::string request_payload, |
| ObliviousHttpHeaderKeyConfig ohttp_key_config) |
| : QuicheThread("client_thread"), |
| client_(client), |
| request_payload_(request_payload), |
| ohttp_key_config_(ohttp_key_config) {} |
| |
| protected: |
| void Run() override { |
| auto encrypted_request = |
| client_.CreateObliviousHttpRequest(request_payload_); |
| ASSERT_TRUE(encrypted_request.ok()); |
| ASSERT_FALSE(encrypted_request->EncapsulateAndSerialize().empty()); |
| // Setup recipient and get encrypted response payload. |
| auto decapsulate_req_on_gateway = |
| ObliviousHttpRequest::CreateServerObliviousRequest( |
| encrypted_request->EncapsulateAndSerialize(), |
| *(ConstructHpkeKey(GetHpkePrivateKey(), ohttp_key_config_)), |
| ohttp_key_config_); |
| ASSERT_TRUE(decapsulate_req_on_gateway.ok()); |
| auto gateway_request_context = |
| std::move(decapsulate_req_on_gateway.value()).ReleaseContext(); |
| auto encapsulate_resp_on_gateway = |
| ObliviousHttpResponse::CreateServerObliviousResponse( |
| "test response", gateway_request_context); |
| ASSERT_TRUE(encapsulate_resp_on_gateway.ok()); |
| ASSERT_FALSE( |
| encapsulate_resp_on_gateway->EncapsulateAndSerialize().empty()); |
| auto client_request_context = |
| std::move(encrypted_request.value()).ReleaseContext(); |
| auto decrypted_response = client_.DecryptObliviousHttpResponse( |
| encapsulate_resp_on_gateway->EncapsulateAndSerialize(), |
| client_request_context); |
| ASSERT_TRUE(decrypted_response.ok()); |
| ASSERT_FALSE(decrypted_response->GetPlaintextData().empty()); |
| } |
| |
| private: |
| const ObliviousHttpClient& client_; |
| std::string request_payload_; |
| ObliviousHttpHeaderKeyConfig ohttp_key_config_; |
| }; |
| |
| auto ohttp_key_config = |
| GetOhttpKeyConfig(1, EVP_HPKE_DHKEM_X25519_HKDF_SHA256, |
| EVP_HPKE_HKDF_SHA256, EVP_HPKE_AES_256_GCM); |
| auto client = |
| ObliviousHttpClient::Create(GetHpkePublicKey(), ohttp_key_config); |
| |
| TestQuicheThread t1(*client, "test request 1", ohttp_key_config); |
| TestQuicheThread t2(*client, "test request 2", ohttp_key_config); |
| t1.Start(); |
| t2.Start(); |
| t1.Join(); |
| t2.Join(); |
| } |
| |
| } // namespace quiche |