blob: e313bb828cb6ac78ef651901c243521b7a5795a7 [file] [log] [blame]
#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";
return absl::HexStringToBytes(hpke_key_hex);
}
std::string GetHpkePublicKey() {
absl::string_view public_key =
"6d21cfe09fbea5122f9ebc2eb2a69fcc4f06408cd54aac934f012e76fcdcef62";
return absl::HexStringToBytes(public_key);
}
std::string GetSeed() {
absl::string_view seed =
"52c4a758a802cd8b936eceea314432798d5baf2d7e9235dc084ab1b9cfa2f736";
return absl::HexStringToBytes(seed);
}
std::string GetSeededEncapsulatedKey() {
absl::string_view encapsulated_key =
"37fda3567bdbd628e88668c3c8d7e97d1d1253b6d4ea6d44c150f741f1bf4431";
return absl::HexStringToBytes(encapsulated_key);
}
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).
auto instance = ObliviousHttpRequest::CreateServerObliviousRequest(
absl::HexStringToBytes(kEncapsulatedRequest),
*(ConstructHpkeKey(absl::HexStringToBytes(kX25519SecretKey),
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";
auto oblivious_request_context = std::move(instance.value()).ReleaseContext();
EXPECT_EQ(oblivious_request_context.encapsulated_key_,
absl::HexStringToBytes(kExpectedEphemeralPublicKey));
// Binary HTTP message.
// https://www.ietf.org/archive/id/draft-ietf-ohai-ohttp-03.html#appendix-A-6
constexpr absl::string_view kExpectedBinaryHTTPMessage =
"00034745540568747470730b6578616d706c652e636f6d012f";
EXPECT_EQ(decrypted, absl::HexStringToBytes(kExpectedBinaryHTTPMessage));
}
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;
absl::string_view 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";
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),
absl::HexStringToBytes(expected_encrypted_request));
}
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");
}
} // namespace quiche