| #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/hpke.h" |
| #include "quiche/common/platform/api/quiche_test.h" |
| #include "quiche/common/test_tools/quiche_test_utils.h" |
| #include "quiche/oblivious_http/buffers/oblivious_http_request.h" |
| #include "quiche/oblivious_http/buffers/oblivious_http_response.h" |
| #include "quiche/oblivious_http/common/oblivious_http_chunk_handler.h" |
| #include "quiche/oblivious_http/common/oblivious_http_header_key_config.h" |
| #include "quiche/oblivious_http/oblivious_http_client.h" |
| #include "quiche/oblivious_http/oblivious_http_gateway.h" |
| |
| namespace quiche { |
| namespace { |
| |
| constexpr absl::string_view kRequestTestPayload = "request test payload"; |
| constexpr absl::string_view kResponseTestPayload = "response test payload"; |
| constexpr absl::string_view kHpkePrivateKeyHex = |
| "b77431ecfa8f4cfc30d6e467aafa06944dffe28cb9dd1409e33a3045f5adc8a1"; |
| constexpr absl::string_view kHpkePublicKeyHex = |
| "6d21cfe09fbea5122f9ebc2eb2a69fcc4f06408cd54aac934f012e76fcdcef62"; |
| |
| absl::StatusOr<std::string> GetHpkeKey(absl::string_view hpke_key_hex) { |
| std::string hpke_key_bytes; |
| if (!absl::HexStringToBytes(hpke_key_hex, &hpke_key_bytes)) { |
| return absl::InvalidArgumentError("Invalid HPKE key."); |
| } |
| return hpke_key_bytes; |
| } |
| |
| class TestChunkHandler : public ObliviousHttpChunkHandler { |
| public: |
| absl::Status OnDecryptedChunk(absl::string_view decrypted_chunk) override { |
| absl::StrAppend(&concatenated_decrypted_chunks_, decrypted_chunk); |
| return absl::OkStatus(); |
| } |
| absl::Status OnChunksDone() override { |
| on_chunks_done_called_ = true; |
| return absl::OkStatus(); |
| } |
| std::string GetConcatenatedDecryptedChunks() const { |
| return concatenated_decrypted_chunks_; |
| } |
| bool AreChunksDone() const { return on_chunks_done_called_; } |
| |
| private: |
| bool on_chunks_done_called_ = false; |
| std::string concatenated_decrypted_chunks_; |
| }; |
| |
| TEST(ObliviousHttpIntegrationTest, ClientAndGatewayRequestResponse) { |
| absl::StatusOr<ObliviousHttpHeaderKeyConfig> key_config = |
| ObliviousHttpHeaderKeyConfig::Create( |
| /*key_id=*/1, /*kem_id=*/EVP_HPKE_DHKEM_X25519_HKDF_SHA256, |
| /*kdf_id=*/EVP_HPKE_HKDF_SHA256, /*aead_id=*/ |
| EVP_HPKE_AES_128_GCM); |
| QUICHE_ASSERT_OK(key_config); |
| absl::StatusOr<std::string> hpke_public_key = GetHpkeKey(kHpkePublicKeyHex); |
| QUICHE_ASSERT_OK(hpke_public_key); |
| absl::StatusOr<std::string> hpke_private_key = GetHpkeKey(kHpkePrivateKeyHex); |
| QUICHE_ASSERT_OK(hpke_private_key); |
| |
| absl::StatusOr<ObliviousHttpClient> client = |
| ObliviousHttpClient::Create(hpke_public_key.value(), key_config.value()); |
| QUICHE_ASSERT_OK(client); |
| absl::StatusOr<ObliviousHttpGateway> gateway = ObliviousHttpGateway::Create( |
| hpke_private_key.value(), key_config.value()); |
| QUICHE_ASSERT_OK(gateway); |
| |
| // Request encrypt/decrypt |
| absl::StatusOr<ObliviousHttpRequest> request = |
| client->CreateObliviousHttpRequest(std::string(kRequestTestPayload)); |
| QUICHE_ASSERT_OK(request); |
| request = |
| gateway->DecryptObliviousHttpRequest(request->EncapsulateAndSerialize()); |
| QUICHE_ASSERT_OK(request); |
| |
| EXPECT_EQ(request->GetPlaintextData(), kRequestTestPayload); |
| |
| // Response encrypt/decrypt |
| ObliviousHttpRequest::Context request_context = |
| std::move(*request).ReleaseContext(); |
| absl::StatusOr<ObliviousHttpResponse> response = |
| gateway->CreateObliviousHttpResponse(std::string(kResponseTestPayload), |
| request_context); |
| QUICHE_ASSERT_OK(response); |
| |
| response = client->DecryptObliviousHttpResponse( |
| response->EncapsulateAndSerialize(), request_context); |
| QUICHE_ASSERT_OK(response); |
| |
| EXPECT_EQ(response->GetPlaintextData(), kResponseTestPayload); |
| } |
| |
| TEST(ObliviousHttpIntegrationTest, |
| ChunkedClientAndChunkedGatewayRequestResponseChunks) { |
| absl::StatusOr<ObliviousHttpHeaderKeyConfig> key_config = |
| ObliviousHttpHeaderKeyConfig::Create( |
| /*key_id=*/1, /*kem_id=*/EVP_HPKE_DHKEM_X25519_HKDF_SHA256, |
| /*kdf_id=*/EVP_HPKE_HKDF_SHA256, /*aead_id=*/ |
| EVP_HPKE_AES_128_GCM); |
| QUICHE_ASSERT_OK(key_config); |
| absl::StatusOr<std::string> hpke_public_key = GetHpkeKey(kHpkePublicKeyHex); |
| QUICHE_ASSERT_OK(hpke_public_key); |
| absl::StatusOr<std::string> hpke_private_key = GetHpkeKey(kHpkePrivateKeyHex); |
| QUICHE_ASSERT_OK(hpke_private_key); |
| |
| TestChunkHandler client_chunk_handler; |
| absl::StatusOr<ChunkedObliviousHttpClient> chunked_client = |
| ChunkedObliviousHttpClient::Create( |
| hpke_public_key.value(), key_config.value(), &client_chunk_handler); |
| QUICHE_ASSERT_OK(chunked_client); |
| |
| TestChunkHandler gateway_chunk_handler; |
| absl::StatusOr<ChunkedObliviousHttpGateway> gateway = |
| ChunkedObliviousHttpGateway::Create( |
| hpke_private_key.value(), key_config.value(), &gateway_chunk_handler); |
| QUICHE_ASSERT_OK(gateway); |
| |
| // Encrypt request in chunks |
| absl::string_view request_part1 = |
| kRequestTestPayload.substr(0, kRequestTestPayload.length() / 2); |
| absl::string_view request_part2 = |
| kRequestTestPayload.substr(kRequestTestPayload.length() / 2); |
| |
| absl::StatusOr<std::string> req_chunk1_encrypted = |
| chunked_client->EncryptRequestChunk(request_part1, |
| /*is_final_chunk=*/false); |
| QUICHE_ASSERT_OK(req_chunk1_encrypted); |
| absl::StatusOr<std::string> req_chunk2_encrypted = |
| chunked_client->EncryptRequestChunk(request_part2, |
| /*is_final_chunk=*/false); |
| QUICHE_ASSERT_OK(req_chunk2_encrypted); |
| absl::StatusOr<std::string> req_chunk3_encrypted = |
| chunked_client->EncryptRequestChunk(/*plaintext_payload=*/"", |
| /*is_final_chunk=*/true); |
| QUICHE_ASSERT_OK(req_chunk3_encrypted); |
| |
| // Decrypt request in chunks |
| QUICHE_ASSERT_OK(gateway->DecryptRequest(req_chunk1_encrypted.value(), |
| /*end_stream=*/false)); |
| QUICHE_ASSERT_OK(gateway->DecryptRequest(req_chunk2_encrypted.value(), |
| /*end_stream=*/false)); |
| QUICHE_ASSERT_OK(gateway->DecryptRequest(req_chunk3_encrypted.value(), |
| /*end_stream=*/true)); |
| EXPECT_TRUE(gateway_chunk_handler.AreChunksDone()); |
| EXPECT_EQ(gateway_chunk_handler.GetConcatenatedDecryptedChunks(), |
| kRequestTestPayload); |
| |
| // Encrypt response in chunks |
| absl::string_view response_part1 = |
| kResponseTestPayload.substr(0, kResponseTestPayload.length() / 2); |
| absl::string_view response_part2 = |
| kResponseTestPayload.substr(kResponseTestPayload.length() / 2); |
| |
| absl::StatusOr<std::string> resp_chunk1_encrypted = |
| gateway->EncryptResponse(response_part1, /*is_final_chunk=*/false); |
| QUICHE_ASSERT_OK(resp_chunk1_encrypted); |
| absl::StatusOr<std::string> resp_chunk2_encrypted = |
| gateway->EncryptResponse(response_part2, /*is_final_chunk=*/false); |
| QUICHE_ASSERT_OK(resp_chunk2_encrypted); |
| absl::StatusOr<std::string> resp_chunk3_encrypted = gateway->EncryptResponse( |
| /*plaintext_payload=*/"", /*is_final_chunk=*/true); |
| QUICHE_ASSERT_OK(resp_chunk3_encrypted); |
| |
| // Decrypt response in chunks |
| QUICHE_ASSERT_OK( |
| chunked_client->DecryptResponse(resp_chunk1_encrypted.value(), |
| /*end_stream=*/false)); |
| QUICHE_ASSERT_OK( |
| chunked_client->DecryptResponse(resp_chunk2_encrypted.value(), |
| /*end_stream=*/false)); |
| QUICHE_ASSERT_OK( |
| chunked_client->DecryptResponse(resp_chunk3_encrypted.value(), |
| /*end_stream=*/true)); |
| EXPECT_TRUE(client_chunk_handler.AreChunksDone()); |
| EXPECT_EQ(client_chunk_handler.GetConcatenatedDecryptedChunks(), |
| kResponseTestPayload); |
| } |
| |
| } // namespace |
| } // namespace quiche |