blob: c76007a99b20783569118382e8e106eb2ce75335 [file] [log] [blame]
#include "quiche/oblivious_http/buffers/oblivious_http_response.h"
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include <algorithm>
#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/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/buffers/oblivious_http_request.h"
#include "quiche/oblivious_http/common/oblivious_http_header_key_config.h"
namespace quiche {
namespace {
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 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;
}
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_CTX> GetSeededClientContext(uint8_t key_id,
uint16_t kem_id,
uint16_t kdf_id,
uint16_t aead_id) {
bssl::UniquePtr<EVP_HPKE_CTX> client_ctx(EVP_HPKE_CTX_new());
std::string encapsulated_key(EVP_HPKE_MAX_ENC_LENGTH, '\0');
size_t enc_len;
std::string info = GetOhttpKeyConfig(key_id, kem_id, kdf_id, aead_id)
.SerializeRecipientContextInfo();
EXPECT_TRUE(EVP_HPKE_CTX_setup_sender_with_seed_for_testing(
client_ctx.get(), reinterpret_cast<uint8_t *>(encapsulated_key.data()),
&enc_len, encapsulated_key.size(), EVP_hpke_x25519_hkdf_sha256(),
EVP_hpke_hkdf_sha256(), EVP_hpke_aes_256_gcm(),
reinterpret_cast<const uint8_t *>(GetHpkePublicKey().data()),
GetHpkePublicKey().size(), reinterpret_cast<const uint8_t *>(info.data()),
info.size(), reinterpret_cast<const uint8_t *>(GetSeed().data()),
GetSeed().size()));
encapsulated_key.resize(enc_len);
EXPECT_EQ(encapsulated_key, GetSeededEncapsulatedKey());
return client_ctx;
}
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;
}
ObliviousHttpRequest SetUpObliviousHttpContext(uint8_t key_id, uint16_t kem_id,
uint16_t kdf_id,
uint16_t aead_id,
std::string plaintext) {
auto ohttp_key_config = GetOhttpKeyConfig(key_id, kem_id, kdf_id, aead_id);
auto client_request_encapsulate =
ObliviousHttpRequest::CreateClientWithSeedForTesting(
std::move(plaintext), GetHpkePublicKey(), ohttp_key_config,
GetSeed());
EXPECT_TRUE(client_request_encapsulate.ok());
auto oblivious_request =
client_request_encapsulate->EncapsulateAndSerialize();
auto server_request_decapsulate =
ObliviousHttpRequest::CreateServerObliviousRequest(
oblivious_request,
*(ConstructHpkeKey(GetHpkePrivateKey(), ohttp_key_config)),
ohttp_key_config);
EXPECT_TRUE(server_request_decapsulate.ok());
return std::move(server_request_decapsulate.value());
}
// QuicheRandom implementation.
// Just fills the buffer with repeated chars that's initialized in seed.
class TestQuicheRandom : public QuicheRandom {
public:
TestQuicheRandom(char seed) : seed_(seed) {}
~TestQuicheRandom() override {}
void RandBytes(void *data, size_t len) override { memset(data, seed_, len); }
uint64_t RandUint64() override {
uint64_t random_int;
memset(&random_int, seed_, sizeof(random_int));
return random_int;
}
void InsecureRandBytes(void *data, size_t len) override {
return RandBytes(data, len);
}
uint64_t InsecureRandUint64() override { return RandUint64(); }
private:
char seed_;
};
size_t GetResponseNonceLength(const EVP_HPKE_CTX &hpke_context) {
EXPECT_NE(&hpke_context, nullptr);
const EVP_AEAD *evp_hpke_aead =
EVP_HPKE_AEAD_aead(EVP_HPKE_CTX_aead(&hpke_context));
EXPECT_NE(evp_hpke_aead, nullptr);
// Nk = [AEAD key len], is determined by BSSL.
const size_t aead_key_len = EVP_AEAD_key_length(evp_hpke_aead);
// Nn = [AEAD nonce len], is determined by BSSL.
const size_t aead_nonce_len = EVP_AEAD_nonce_length(evp_hpke_aead);
const size_t secret_len = std::max(aead_key_len, aead_nonce_len);
return secret_len;
}
TEST(ObliviousHttpResponse, TestDecapsulateReceivedResponse) {
// Construct encrypted payload with plaintext: "test response"
absl::string_view encrypted_response =
"39d5b03c02c97e216df444e4681007105974d4df1585aae05e7b53f3ccdb55d51f711d48"
"eeefbc1a555d6d928e35df33fd23c23846fa7b083e30692f7b";
std::string encrypted_response_bytes;
ASSERT_TRUE(
absl::HexStringToBytes(encrypted_response, &encrypted_response_bytes));
auto oblivious_context =
SetUpObliviousHttpContext(4, EVP_HPKE_DHKEM_X25519_HKDF_SHA256,
EVP_HPKE_HKDF_SHA256, EVP_HPKE_AES_256_GCM,
"test")
.ReleaseContext();
auto decapsulated = ObliviousHttpResponse::CreateClientObliviousResponse(
std::move(encrypted_response_bytes), oblivious_context);
EXPECT_TRUE(decapsulated.ok());
auto decrypted = decapsulated->GetPlaintextData();
EXPECT_EQ(decrypted, "test response");
}
} // namespace
TEST(ObliviousHttpResponse, EndToEndTestForResponse) {
auto oblivious_ctx = ObliviousHttpRequest::Context(
GetSeededClientContext(5, EVP_HPKE_DHKEM_X25519_HKDF_SHA256,
EVP_HPKE_HKDF_SHA256, EVP_HPKE_AES_256_GCM),
GetSeededEncapsulatedKey());
auto server_response_encapsulate =
ObliviousHttpResponse::CreateServerObliviousResponse("test response",
oblivious_ctx);
EXPECT_TRUE(server_response_encapsulate.ok());
auto oblivious_response =
server_response_encapsulate->EncapsulateAndSerialize();
auto client_response_encapsulate =
ObliviousHttpResponse::CreateClientObliviousResponse(oblivious_response,
oblivious_ctx);
auto decrypted = client_response_encapsulate->GetPlaintextData();
EXPECT_EQ(decrypted, "test response");
}
TEST(ObliviousHttpResponse, TestEncapsulateWithQuicheRandom) {
auto random = TestQuicheRandom('z');
auto server_seeded_request = SetUpObliviousHttpContext(
6, EVP_HPKE_DHKEM_X25519_HKDF_SHA256, EVP_HPKE_HKDF_SHA256,
EVP_HPKE_AES_256_GCM, "test");
auto server_request_context =
std::move(server_seeded_request).ReleaseContext();
auto server_response_encapsulate =
ObliviousHttpResponse::CreateServerObliviousResponse(
"test response", server_request_context,
ObliviousHttpHeaderKeyConfig::kOhttpResponseLabel, &random);
EXPECT_TRUE(server_response_encapsulate.ok());
std::string response_nonce =
server_response_encapsulate->EncapsulateAndSerialize().substr(
0, GetResponseNonceLength(*(server_request_context.hpke_context_)));
EXPECT_EQ(response_nonce,
std::string(
GetResponseNonceLength(*(server_request_context.hpke_context_)),
'z'));
absl::string_view expected_encrypted_response =
"2a3271ac4e6a501f51d0264d3dd7d0bc8a06973b58e89c26d6dac06144";
std::string expected_encrypted_response_bytes;
ASSERT_TRUE(absl::HexStringToBytes(expected_encrypted_response,
&expected_encrypted_response_bytes));
EXPECT_EQ(
server_response_encapsulate->EncapsulateAndSerialize().substr(
GetResponseNonceLength(*(server_request_context.hpke_context_))),
expected_encrypted_response_bytes);
}
struct EncryptChunkTestParams {
ObliviousHttpRequest::Context context;
ObliviousHttpResponse::CommonAeadParamsResult aead_params;
ObliviousHttpResponse::AeadContextData aead_context_data;
};
absl::StatusOr<EncryptChunkTestParams> SetUpEncryptChunkTest() {
// 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);
absl::StatusOr<ObliviousHttpResponse::CommonAeadParamsResult> aead_params =
ObliviousHttpResponse::GetCommonAeadParams(*context);
EXPECT_TRUE(aead_params.ok());
auto response_nonce = "bcce7f4cb921309ba5d62edf1769ef09";
std::string response_nonce_bytes;
EXPECT_TRUE(absl::HexStringToBytes(response_nonce, &response_nonce_bytes));
auto aead_context_data = ObliviousHttpResponse::GetAeadContextData(
*context, *aead_params,
ObliviousHttpHeaderKeyConfig::kChunkedOhttpResponseLabel,
response_nonce_bytes);
QUICHE_EXPECT_OK(aead_context_data);
return EncryptChunkTestParams{
.context = std::move(*context),
.aead_params = std::move(*aead_params),
.aead_context_data = std::move(*aead_context_data)};
}
TEST(ObliviousHttpResponse, TestEncryptChunks) {
auto test_params = SetUpEncryptChunkTest();
QUICHE_EXPECT_OK(test_params);
auto& [context, aead_params, aead_context_data] = *test_params;
std::string plaintext_payload = "01";
std::string plaintext_payload_bytes;
EXPECT_TRUE(
absl::HexStringToBytes(plaintext_payload, &plaintext_payload_bytes));
std::string chunk_nonce = "fead854635d2d5527d64f546";
std::string chunk_nonce_bytes;
EXPECT_TRUE(absl::HexStringToBytes(chunk_nonce, &chunk_nonce_bytes));
auto encrypted_chunk = ObliviousHttpResponse::EncryptChunk(
context, aead_context_data, plaintext_payload_bytes, chunk_nonce_bytes,
/*is_final_chunk=*/false);
QUICHE_EXPECT_OK(encrypted_chunk);
std::string encrypted_chunk_hex = absl::BytesToHexString(*encrypted_chunk);
EXPECT_EQ(encrypted_chunk_hex, "79bf1cc87fa0e2c02de4546945aa3d1e48");
plaintext_payload = "40c8";
EXPECT_TRUE(
absl::HexStringToBytes(plaintext_payload, &plaintext_payload_bytes));
chunk_nonce = "fead854635d2d5527d64f547";
EXPECT_TRUE(absl::HexStringToBytes(chunk_nonce, &chunk_nonce_bytes));
encrypted_chunk = ObliviousHttpResponse::EncryptChunk(
context, aead_context_data, plaintext_payload_bytes, chunk_nonce_bytes,
/*is_final_chunk=*/false);
QUICHE_EXPECT_OK(encrypted_chunk);
encrypted_chunk_hex = absl::BytesToHexString(*encrypted_chunk);
EXPECT_EQ(encrypted_chunk_hex, "b348b5bd4c594c16b6170b07b475845d1f32");
chunk_nonce = "fead854635d2d5527d64f544";
EXPECT_TRUE(absl::HexStringToBytes(chunk_nonce, &chunk_nonce_bytes));
encrypted_chunk = ObliviousHttpResponse::EncryptChunk(
context, aead_context_data, /*plaintext_payload=*/"", chunk_nonce_bytes,
/*is_final_chunk=*/true);
QUICHE_EXPECT_OK(encrypted_chunk);
encrypted_chunk_hex = absl::BytesToHexString(*encrypted_chunk);
EXPECT_EQ(encrypted_chunk_hex, "ed9d8a796617a5b27265f4d73247f639");
}
TEST(OblviousHttpResponse, EncryptNonFinalChunkWithEmptyPayloadError) {
auto test_params = SetUpEncryptChunkTest();
QUICHE_EXPECT_OK(test_params);
auto& [context, aead_params, aead_context_data] = *test_params;
EXPECT_EQ(ObliviousHttpResponse::EncryptChunk(context, aead_context_data,
/*plaintext_payload=*/"", "",
/*is_final_chunk=*/false)
.status()
.code(),
absl::StatusCode::kInvalidArgument);
}
TEST(OblviousHttpResponse, EncryptChunkWithEmptyNonceError) {
auto test_params = SetUpEncryptChunkTest();
QUICHE_EXPECT_OK(test_params);
auto& [context, aead_params, aead_context_data] = *test_params;
EXPECT_EQ(ObliviousHttpResponse::EncryptChunk(context, aead_context_data,
/*plaintext_payload=*/"111", "",
/*is_final_chunk=*/false)
.status()
.code(),
absl::StatusCode::kInvalidArgument);
}
TEST(ChunkCounter, EmptyNonceIsInvalid) {
EXPECT_EQ(ObliviousHttpResponse::ChunkCounter::Create("").status().code(),
absl::StatusCode::kInvalidArgument);
}
TEST(ChunkCounter, GetChunkNonce) {
// Chunk nonces from
// https://www.ietf.org/archive/id/draft-ietf-ohai-chunked-ohttp-05.html#appendix-A
std::string nonce_hex = "fead854635d2d5527d64f546";
std::string nonce;
EXPECT_TRUE(absl::HexStringToBytes(nonce_hex, &nonce));
auto chunk_counter = ObliviousHttpResponse::ChunkCounter::Create(nonce);
EXPECT_TRUE(chunk_counter.ok());
std::string expected_chunk_nonce_hex = "fead854635d2d5527d64f546";
std::string chunk_nonce;
EXPECT_TRUE(absl::HexStringToBytes(expected_chunk_nonce_hex, &chunk_nonce));
EXPECT_EQ(chunk_counter->GetChunkNonce(), chunk_nonce);
chunk_counter->Increment();
expected_chunk_nonce_hex = "fead854635d2d5527d64f547";
EXPECT_TRUE(absl::HexStringToBytes(expected_chunk_nonce_hex, &chunk_nonce));
EXPECT_EQ(chunk_counter->GetChunkNonce(), chunk_nonce);
chunk_counter->Increment();
expected_chunk_nonce_hex = "fead854635d2d5527d64f544";
EXPECT_TRUE(absl::HexStringToBytes(expected_chunk_nonce_hex, &chunk_nonce));
EXPECT_EQ(chunk_counter->GetChunkNonce(), chunk_nonce);
}
TEST(ChunkCounter, LimitExceeded) {
std::string nonce_hex = "00";
std::string nonce;
EXPECT_TRUE(absl::HexStringToBytes(nonce_hex, &nonce));
auto chunk_counter = ObliviousHttpResponse::ChunkCounter::Create(nonce);
EXPECT_TRUE(chunk_counter.ok());
for (int i = 0; i < 256; ++i) {
EXPECT_FALSE(chunk_counter->LimitExceeded());
chunk_counter->Increment();
}
// Counter limit reached at 2^(nonce_size * 8)
EXPECT_TRUE(chunk_counter->LimitExceeded());
}
} // namespace quiche