blob: 3bd9bba0943b063f612d81ff0336d8a2e49653a9 [file] [log] [blame]
#include "quiche/oblivious_http/oblivious_http_client.h"
#include <stddef.h>
#include <stdint.h>
#include <memory>
#include <string>
#include <utility>
#include "absl/base/attributes.h"
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "openssl/base.h"
#include "openssl/hpke.h"
#include "quiche/common/quiche_crypto_logging.h"
#include "quiche/common/quiche_data_reader.h"
#include "quiche/common/quiche_data_writer.h"
#include "quiche/common/quiche_status_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"
namespace quiche {
namespace {
constexpr uint64_t kFinalChunkIndicator = 0;
// Use BoringSSL's setup_sender API to validate whether the HPKE public key
// input provided by the user is valid.
absl::Status ValidateClientParameters(
absl::string_view hpke_public_key,
const ObliviousHttpHeaderKeyConfig& ohttp_key_config) {
// Initialize HPKE client context and check if context can be setup with the
// given public key to verify if the public key is indeed valid.
bssl::UniquePtr<EVP_HPKE_CTX> client_ctx(EVP_HPKE_CTX_new());
if (client_ctx == nullptr) {
return SslErrorAsStatus(
"Failed to initialize HPKE ObliviousHttpClient Context.");
}
// Setup the sender (client)
std::string encapsulated_key(EVP_HPKE_MAX_ENC_LENGTH, '\0');
size_t enc_len;
absl::string_view info = "verify if given HPKE public key is valid";
if (!EVP_HPKE_CTX_setup_sender(
client_ctx.get(), reinterpret_cast<uint8_t*>(encapsulated_key.data()),
&enc_len, encapsulated_key.size(), ohttp_key_config.GetHpkeKem(),
ohttp_key_config.GetHpkeKdf(), ohttp_key_config.GetHpkeAead(),
reinterpret_cast<const uint8_t*>(hpke_public_key.data()),
hpke_public_key.size(), reinterpret_cast<const uint8_t*>(info.data()),
info.size())) {
return SslErrorAsStatus(
"Failed to setup HPKE context with given public key param "
"hpke_public_key.");
}
return absl::OkStatus();
}
} // namespace
ObliviousHttpClient::ObliviousHttpClient(
std::string client_public_key,
const ObliviousHttpHeaderKeyConfig& ohttp_key_config)
: hpke_public_key_(std::move(client_public_key)),
ohttp_key_config_(ohttp_key_config) {}
// Initialize Bssl.
absl::StatusOr<ObliviousHttpClient> ObliviousHttpClient::Create(
absl::string_view hpke_public_key,
const ObliviousHttpHeaderKeyConfig& ohttp_key_config) {
if (hpke_public_key.empty()) {
return absl::InvalidArgumentError("Invalid/Empty HPKE public key.");
}
auto is_valid_input =
ValidateClientParameters(hpke_public_key, ohttp_key_config);
if (!is_valid_input.ok()) {
return absl::InvalidArgumentError(
absl::StrCat("Invalid input received in method parameters. ",
is_valid_input.message()));
}
return ObliviousHttpClient(std::string(hpke_public_key), ohttp_key_config);
}
absl::StatusOr<ObliviousHttpRequest>
ObliviousHttpClient::CreateObliviousHttpRequest(
std::string plaintext_data) const {
return ObliviousHttpRequest::CreateClientObliviousRequest(
std::move(plaintext_data), hpke_public_key_, ohttp_key_config_);
}
absl::StatusOr<ObliviousHttpResponse>
ObliviousHttpClient::DecryptObliviousHttpResponse(
std::string encrypted_data,
ObliviousHttpRequest::Context& oblivious_http_request_context) const {
return ObliviousHttpResponse::CreateClientObliviousResponse(
std::move(encrypted_data), oblivious_http_request_context);
}
ChunkedObliviousHttpClient::ChunkedObliviousHttpClient(
const ObliviousHttpHeaderKeyConfig& ohttp_key_config,
ObliviousHttpRequest::Context hpke_sender_context,
ObliviousHttpResponse::CommonAeadParamsResult aead_params,
ObliviousHttpChunkHandler* chunk_handler)
: ohttp_key_config_(ohttp_key_config),
hpke_sender_context_(std::move(hpke_sender_context)),
aead_params_(aead_params),
chunk_handler_(chunk_handler) {};
absl::StatusOr<ChunkedObliviousHttpClient> ChunkedObliviousHttpClient::Create(
absl::string_view hpke_public_key,
const ObliviousHttpHeaderKeyConfig& ohttp_key_config,
ObliviousHttpChunkHandler* chunk_handler, absl::string_view seed) {
if (hpke_public_key.empty()) {
return absl::InvalidArgumentError("Empty HPKE public key.");
}
if (chunk_handler == nullptr) {
return absl::InvalidArgumentError("Null chunk handler.");
}
absl::Status is_valid_input =
ValidateClientParameters(hpke_public_key, ohttp_key_config);
if (!is_valid_input.ok()) {
return absl::InvalidArgumentError(
absl::StrCat("Invalid input received in method parameters. ",
is_valid_input.message()));
}
QUICHE_ASSIGN_OR_RETURN(
ObliviousHttpRequest::Context hpke_sender_context,
ObliviousHttpRequest::CreateHpkeSenderContext(
hpke_public_key, ohttp_key_config, seed,
ObliviousHttpHeaderKeyConfig::kChunkedOhttpRequestLabel));
QUICHE_ASSIGN_OR_RETURN(
ObliviousHttpResponse::CommonAeadParamsResult aead_params,
ObliviousHttpResponse::GetCommonAeadParams(hpke_sender_context));
return ChunkedObliviousHttpClient(ohttp_key_config,
std::move(hpke_sender_context),
std::move(aead_params), chunk_handler);
}
absl::StatusOr<std::string> ChunkedObliviousHttpClient::EncryptRequestChunk(
absl::string_view plaintext_payload, bool is_final_chunk) {
if (request_current_section_ == RequestMessageSection::kEnd) {
return absl::InvalidArgumentError("Cannot encrypt in bad state.");
}
absl::StatusOr<std::string> request_chunk =
EncryptRequestChunkImpl(plaintext_payload, is_final_chunk);
if (!request_chunk.ok()) {
request_current_section_ = RequestMessageSection::kEnd;
}
return request_chunk;
}
absl::StatusOr<std::string> ChunkedObliviousHttpClient::EncryptRequestChunkImpl(
absl::string_view plaintext_payload, bool is_final_chunk) {
QUICHE_ASSIGN_OR_RETURN(
std::string encrypted_data,
ObliviousHttpRequest::EncryptChunk(plaintext_payload,
hpke_sender_context_, is_final_chunk));
std::string maybe_key_header_data = "";
if (request_current_section_ == RequestMessageSection::kHeader) {
maybe_key_header_data =
absl::StrCat(ohttp_key_config_.SerializeOhttpPayloadHeader(),
hpke_sender_context_.GetEncapsulatedKey());
request_current_section_ = RequestMessageSection::kChunk;
}
uint8_t chunk_var_int_length =
QuicheDataWriter::GetVarInt62Len(encrypted_data.size());
if (chunk_var_int_length == 0) {
return absl::InvalidArgumentError(
"Encrypted data is too large to be represented as a varint.");
}
uint64_t chunk_var_int = encrypted_data.size();
if (is_final_chunk) {
request_current_section_ = RequestMessageSection::kEnd;
chunk_var_int_length =
QuicheDataWriter::GetVarInt62Len(kFinalChunkIndicator);
if (chunk_var_int_length == 0) {
return absl::InvalidArgumentError(
"Final chunk indicator is too large to be represented as a varint.");
}
chunk_var_int = kFinalChunkIndicator;
}
std::string request_buffer(maybe_key_header_data.size() +
chunk_var_int_length + encrypted_data.size(),
'\0');
QuicheDataWriter writer(request_buffer.size(), request_buffer.data());
if (!writer.WriteStringPiece(maybe_key_header_data)) {
return absl::InternalError(
"Failed to write key header data to request buffer.");
}
if (!writer.WriteVarInt62(chunk_var_int)) {
return absl::InternalError(
"Failed to write encrypted chunk length to buffer.");
}
if (!writer.WriteStringPiece(encrypted_data)) {
return absl::InternalError(
"Failed to write encrypted chunk to request buffer.");
}
if (writer.remaining() != 0) {
return absl::InternalError("Failed to write all data.");
}
return request_buffer;
}
absl::string_view ChunkedObliviousHttpClient::InitializeResponseCheckpoint(
absl::string_view data) {
absl::string_view response_checkpoint = data;
// Prepend buffered data if present. This is the data from a previous call to
// DecryptResponse that could not finish because it needed this new data.
if (!response_buffer_.empty()) {
absl::StrAppend(&response_buffer_, data);
response_checkpoint = response_buffer_;
}
return response_checkpoint;
}
absl::Status ChunkedObliviousHttpClient::DecryptResponseCheckpoint(
absl::string_view& response_checkpoint, bool end_stream) {
QuicheDataReader reader(response_checkpoint);
switch (response_current_section_) {
case ResponseMessageSection::kEnd:
return absl::InternalError("Response is invalid.");
case ResponseMessageSection::kNonce: {
absl::string_view response_nonce;
if (!reader.ReadStringPiece(&response_nonce, aead_params_.secret_len)) {
return absl::OutOfRangeError("Not enough data to read response nonce.");
}
QUICHE_ASSIGN_OR_RETURN(
ObliviousHttpResponse::AeadContextData aead_context_data,
ObliviousHttpResponse::GetAeadContextData(
hpke_sender_context_, aead_params_,
ObliviousHttpHeaderKeyConfig::kChunkedOhttpResponseLabel,
response_nonce));
aead_context_data_.emplace(std::move(aead_context_data));
QUICHE_ASSIGN_OR_RETURN(
ObliviousHttpResponse::ChunkCounter response_chunk_counter,
ObliviousHttpResponse::ChunkCounter::Create(
aead_context_data_->aead_nonce));
response_chunk_counter_.emplace(std::move(response_chunk_counter));
UpdateCheckpoint(reader, response_checkpoint);
response_current_section_ = ResponseMessageSection::kChunk;
}
ABSL_FALLTHROUGH_INTENDED;
case ResponseMessageSection::kChunk: {
if (!aead_context_data_.has_value()) {
return absl::InternalError(
"HPKE context has not been derived from the response nonce.");
}
ObliviousHttpResponse::AeadContextData& aead_context_data =
*aead_context_data_;
if (!response_chunk_counter_.has_value()) {
return absl::InternalError("Chunk counter has not been initialized.");
}
ObliviousHttpResponse::ChunkCounter& response_chunk_counter =
*response_chunk_counter_;
bool is_final_chunk = false;
do {
if (response_chunk_counter.LimitExceeded()) {
return absl::InvalidArgumentError(
"Maximum number of chunks allowed in the response has been "
"exceeded.");
}
uint64_t length_or_final_chunk_indicator;
if (!reader.ReadVarInt62(&length_or_final_chunk_indicator)) {
return absl::OutOfRangeError("Not enough data to read chunk length.");
}
is_final_chunk =
length_or_final_chunk_indicator == kFinalChunkIndicator;
if (!is_final_chunk) {
absl::string_view chunk;
if (!reader.ReadStringPiece(&chunk,
length_or_final_chunk_indicator)) {
return absl::OutOfRangeError("Not enough data to read chunk.");
}
QUICHE_ASSIGN_OR_RETURN(
std::string decrypted_chunk,
ObliviousHttpResponse::DecryptChunk(
chunk, aead_context_data,
response_chunk_counter.GetChunkNonce(), is_final_chunk));
response_chunk_counter.Increment();
if (chunk_handler_ == nullptr) {
return absl::InternalError("Chunk handler is null.");
}
absl::Status handler_status =
chunk_handler_->OnDecryptedChunk(decrypted_chunk);
if (!handler_status.ok()) {
return handler_status;
}
}
UpdateCheckpoint(reader, response_checkpoint);
} while (!is_final_chunk);
response_current_section_ = ResponseMessageSection::kFinalChunk;
}
ABSL_FALLTHROUGH_INTENDED;
case ResponseMessageSection::kFinalChunk: {
if (!end_stream) {
return absl::OutOfRangeError("Not enough data to read final chunk.");
}
if (!aead_context_data_.has_value()) {
return absl::InternalError(
"HPKE context has not been derived from the response nonce.");
}
ObliviousHttpResponse::AeadContextData& aead_context_data =
*aead_context_data_;
if (!response_chunk_counter_.has_value()) {
return absl::InternalError("Chunk counter has not been initialized.");
}
ObliviousHttpResponse::ChunkCounter& response_chunk_counter =
*response_chunk_counter_;
QUICHE_ASSIGN_OR_RETURN(
std::string decrypted_chunk,
ObliviousHttpResponse::DecryptChunk(
reader.PeekRemainingPayload(), aead_context_data,
response_chunk_counter.GetChunkNonce(),
/*is_final_chunk=*/true));
if (chunk_handler_ == nullptr) {
return absl::InternalError("Chunk handler is null.");
}
absl::Status handler_status =
chunk_handler_->OnDecryptedChunk(decrypted_chunk);
if (!handler_status.ok()) {
return handler_status;
}
handler_status = chunk_handler_->OnChunksDone();
if (!handler_status.ok()) {
return handler_status;
}
}
return absl::OkStatus();
}
}
absl::Status ChunkedObliviousHttpClient::DecryptResponse(
absl::string_view encrypted_data, bool end_stream) {
if (response_current_section_ == ResponseMessageSection::kEnd) {
return absl::InternalError("Response is invalid.");
}
absl::string_view response_checkpoint =
InitializeResponseCheckpoint(encrypted_data);
absl::Status status =
DecryptResponseCheckpoint(response_checkpoint, end_stream);
if (end_stream) {
response_current_section_ = ResponseMessageSection::kEnd;
response_buffer_.clear();
return status;
}
if (absl::IsOutOfRange(status)) {
BufferResponseCheckpoint(response_checkpoint);
return absl::OkStatus();
}
if (!status.ok()) {
response_current_section_ = ResponseMessageSection::kEnd;
}
response_buffer_.clear();
return status;
}
} // namespace quiche