blob: 5cb7b20cfc4baddcbc53d5d01306d03001161cc2 [file] [log] [blame]
#include "quiche/oblivious_http/oblivious_http_gateway.h"
#include <stdint.h>
#include <memory>
#include <string>
#include <utility>
#include "absl/base/attributes.h"
#include "absl/memory/memory.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/aead.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_endian.h"
#include "quiche/common/quiche_random.h"
#include "quiche/oblivious_http/buffers/oblivious_http_request.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;
}
// Constructor.
ObliviousHttpGateway::ObliviousHttpGateway(
bssl::UniquePtr<EVP_HPKE_KEY> recipient_key,
const ObliviousHttpHeaderKeyConfig& ohttp_key_config,
QuicheRandom* quiche_random)
: server_hpke_key_(std::move(recipient_key)),
ohttp_key_config_(ohttp_key_config),
quiche_random_(quiche_random) {}
absl::StatusOr<bssl::UniquePtr<EVP_HPKE_KEY>> CreateServerRecipientKey(
absl::string_view hpke_private_key,
const ObliviousHttpHeaderKeyConfig& ohttp_key_config) {
if (hpke_private_key.empty()) {
return absl::InvalidArgumentError("Invalid/Empty HPKE private key.");
}
// Initialize HPKE key and context.
bssl::UniquePtr<EVP_HPKE_KEY> recipient_key(EVP_HPKE_KEY_new());
if (recipient_key == nullptr) {
return SslErrorAsStatus(
"Failed to initialize ObliviousHttpGateway/Server's Key.");
}
if (!EVP_HPKE_KEY_init(
recipient_key.get(), ohttp_key_config.GetHpkeKem(),
reinterpret_cast<const uint8_t*>(hpke_private_key.data()),
hpke_private_key.size())) {
return SslErrorAsStatus("Failed to import HPKE private key.");
}
return recipient_key;
}
// Initialize ObliviousHttpGateway(Recipient/Server) context.
absl::StatusOr<ObliviousHttpGateway> ObliviousHttpGateway::Create(
absl::string_view hpke_private_key,
const ObliviousHttpHeaderKeyConfig& ohttp_key_config,
QuicheRandom* quiche_random) {
absl::StatusOr<bssl::UniquePtr<EVP_HPKE_KEY>> recipient_key =
CreateServerRecipientKey(hpke_private_key, ohttp_key_config);
if (!recipient_key.ok()) {
return recipient_key.status();
}
if (quiche_random == nullptr) quiche_random = QuicheRandom::GetInstance();
return ObliviousHttpGateway(std::move(*recipient_key), ohttp_key_config,
quiche_random);
}
absl::StatusOr<ObliviousHttpRequest>
ObliviousHttpGateway::DecryptObliviousHttpRequest(
absl::string_view encrypted_data, absl::string_view request_label) const {
return ObliviousHttpRequest::CreateServerObliviousRequest(
encrypted_data, *(server_hpke_key_), ohttp_key_config_, request_label);
}
absl::StatusOr<ObliviousHttpResponse>
ObliviousHttpGateway::CreateObliviousHttpResponse(
std::string plaintext_data,
ObliviousHttpRequest::Context& oblivious_http_request_context,
absl::string_view response_label) const {
return ObliviousHttpResponse::CreateServerObliviousResponse(
std::move(plaintext_data), oblivious_http_request_context, response_label,
quiche_random_);
}
// Constructor.
ChunkedObliviousHttpGateway::ChunkedObliviousHttpGateway(
bssl::UniquePtr<EVP_HPKE_KEY> recipient_key,
const ObliviousHttpHeaderKeyConfig& ohttp_key_config,
ObliviousHttpChunkHandler& chunk_handler, QuicheRandom* quiche_random)
: server_hpke_key_(std::move(recipient_key)),
ohttp_key_config_(ohttp_key_config),
chunk_handler_(chunk_handler),
quiche_random_(quiche_random) {}
absl::StatusOr<ChunkedObliviousHttpGateway> ChunkedObliviousHttpGateway::Create(
absl::string_view hpke_private_key,
const ObliviousHttpHeaderKeyConfig& ohttp_key_config,
ObliviousHttpChunkHandler& chunk_handler, QuicheRandom* quiche_random) {
absl::StatusOr<bssl::UniquePtr<EVP_HPKE_KEY>> recipient_key =
CreateServerRecipientKey(hpke_private_key, ohttp_key_config);
if (!recipient_key.ok()) {
return recipient_key.status();
}
if (quiche_random == nullptr) {
quiche_random = QuicheRandom::GetInstance();
}
return ChunkedObliviousHttpGateway(std::move(*recipient_key),
ohttp_key_config, chunk_handler,
quiche_random);
}
void ChunkedObliviousHttpGateway::InitializeRequestCheckpoint(
absl::string_view data) {
request_checkpoint_view_ = data;
// Prepend buffered data if present. This is the data from a previous call to
// DecryptRequest that could not finish because it needed this new data.
if (!request_buffer_.empty()) {
if (!data.empty()) {
absl::StrAppend(&request_buffer_, data);
}
request_checkpoint_view_ = request_buffer_;
}
}
absl::Status ChunkedObliviousHttpGateway::DecryptRequestCheckpoint(
bool end_stream) {
QuicheDataReader reader(request_checkpoint_view_);
switch (request_current_section_) {
case RequestMessageSection::kEnd:
return absl::InternalError("Request is invalid.");
case RequestMessageSection::kHeader: {
// Check there is enough data for the chunked request header.
// https://www.ietf.org/archive/id/draft-ietf-ohai-chunked-ohttp-05.html#name-request-format
if (reader.PeekRemainingPayload().size() <
ObliviousHttpHeaderKeyConfig::kHeaderLength +
EVP_HPKE_KEM_enc_len(EVP_HPKE_KEY_kem(server_hpke_key_.get()))) {
return absl::OutOfRangeError("Not enough data to read header.");
}
absl::StatusOr<ObliviousHttpRequest::Context> context =
ObliviousHttpRequest::DecodeEncapsulatedRequestHeader(
reader, *server_hpke_key_, ohttp_key_config_,
ObliviousHttpHeaderKeyConfig::kChunkedOhttpRequestLabel);
if (!context.ok()) {
return context.status();
}
oblivious_http_request_context_ = std::move(*context);
SaveCheckpoint(reader);
request_current_section_ = RequestMessageSection::kChunk;
}
ABSL_FALLTHROUGH_INTENDED;
case RequestMessageSection::kChunk: {
uint64_t length_or_final_chunk_indicator;
do {
if (!reader.ReadVarInt62(&length_or_final_chunk_indicator)) {
return absl::OutOfRangeError("Not enough data to read chunk length.");
}
absl::string_view chunk;
if (length_or_final_chunk_indicator != kFinalChunkIndicator) {
if (!reader.ReadStringPiece(&chunk,
length_or_final_chunk_indicator)) {
return absl::OutOfRangeError("Not enough data to read chunk.");
}
if (!oblivious_http_request_context_.has_value()) {
return absl::InternalError(
"HPKE context has not been derived from an encrypted request.");
}
absl::StatusOr<std::string> decrypted_chunk =
ObliviousHttpRequest::DecryptChunk(
*oblivious_http_request_context_, chunk,
/*is_final_chunk=*/false);
if (!decrypted_chunk.ok()) {
return decrypted_chunk.status();
}
absl::Status handle_chunk_status =
chunk_handler_.OnDecryptedChunk(*decrypted_chunk);
if (!handle_chunk_status.ok()) {
return handle_chunk_status;
}
}
SaveCheckpoint(reader);
} while (length_or_final_chunk_indicator != kFinalChunkIndicator);
request_current_section_ = RequestMessageSection::kFinalChunk;
}
ABSL_FALLTHROUGH_INTENDED;
case RequestMessageSection::kFinalChunk: {
if (!end_stream) {
return absl::OutOfRangeError("Not enough data to read final chunk.");
}
if (!oblivious_http_request_context_.has_value()) {
return absl::InternalError(
"HPKE context has not been derived from an encrypted request.");
}
absl::StatusOr<std::string> decrypted_chunk =
ObliviousHttpRequest::DecryptChunk(*oblivious_http_request_context_,
reader.PeekRemainingPayload(),
/*is_final_chunk=*/true);
if (!decrypted_chunk.ok()) {
return decrypted_chunk.status();
}
absl::Status handle_chunk_status =
chunk_handler_.OnDecryptedChunk(*decrypted_chunk);
if (!handle_chunk_status.ok()) {
return handle_chunk_status;
}
handle_chunk_status = chunk_handler_.OnChunksDone();
if (!handle_chunk_status.ok()) {
return handle_chunk_status;
}
}
}
return absl::OkStatus();
}
absl::Status ChunkedObliviousHttpGateway::DecryptRequest(absl::string_view data,
bool end_stream) {
if (request_current_section_ == RequestMessageSection::kEnd) {
return absl::InternalError("Decrypting is marked as invalid.");
}
InitializeRequestCheckpoint(data);
absl::Status status = DecryptRequestCheckpoint(end_stream);
if (end_stream) {
request_current_section_ = RequestMessageSection::kEnd;
if (absl::IsOutOfRange(status)) {
// OutOfRange only used internally for buffering, so return
// InvalidArgument if this is the end of the stream.
status = absl::InvalidArgumentError(status.message());
}
return status;
}
if (absl::IsOutOfRange(status)) {
BufferRequestCheckpoint();
return absl::OkStatus();
}
if (!status.ok()) {
request_current_section_ = RequestMessageSection::kEnd;
}
request_buffer_.clear();
return status;
}
absl::StatusOr<std::string> ChunkedObliviousHttpGateway::EncryptResponse(
absl::string_view plaintext_payload, bool is_final_chunk) {
if (response_current_section_ == ResponseMessageSection::kEnd) {
return absl::InvalidArgumentError("Encrypting is marked as invalid.");
}
absl::StatusOr<std::string> response_chunk =
EncryptResponseChunk(plaintext_payload, is_final_chunk);
if (!response_chunk.ok()) {
response_current_section_ = ResponseMessageSection::kEnd;
}
return response_chunk;
}
absl::StatusOr<std::string> ChunkedObliviousHttpGateway::EncryptResponseChunk(
absl::string_view plaintext_payload, bool is_final_chunk) {
if (response_chunk_counter_.has_value() &&
response_chunk_counter_->LimitExceeded()) {
return absl::InternalError(
"Response chunk counter has exceeded the maximum allowed value.");
}
if (!oblivious_http_request_context_.has_value()) {
return absl::InternalError(
"HPKE context has not been derived from an encrypted request.");
}
if (!aead_context_data_.has_value()) {
absl::StatusOr<ObliviousHttpResponse::CommonAeadParamsResult> aead_params =
ObliviousHttpResponse::GetCommonAeadParams(
*oblivious_http_request_context_);
if (!aead_params.ok()) {
return aead_params.status();
}
// secret_len represents max(Nn, Nk))
response_nonce_ = std::string(aead_params->secret_len, '\0');
quiche_random_->RandBytes(response_nonce_.data(), response_nonce_.size());
auto aead_context_data = ObliviousHttpResponse::GetAeadContextData(
*oblivious_http_request_context_, *aead_params,
ObliviousHttpHeaderKeyConfig::kChunkedOhttpResponseLabel,
response_nonce_);
if (!aead_context_data.ok()) {
return aead_context_data.status();
}
aead_context_data_.emplace(std::move(*aead_context_data));
auto response_chunk_counter = ObliviousHttpResponse::ChunkCounter::Create(
aead_context_data_->aead_nonce);
if (!response_chunk_counter.ok()) {
return response_chunk_counter.status();
}
response_chunk_counter_.emplace(std::move(*response_chunk_counter));
}
if (!response_chunk_counter_.has_value()) {
return absl::InternalError(
"Response chunk counter has not been initialized.");
}
absl::StatusOr<std::string> encrypted_data =
ObliviousHttpResponse::EncryptChunk(
*oblivious_http_request_context_, *aead_context_data_,
plaintext_payload, response_chunk_counter_->GetChunkNonce(),
is_final_chunk);
if (!encrypted_data.ok()) {
return encrypted_data.status();
}
absl::string_view maybe_nonce;
if (response_current_section_ == ResponseMessageSection::kNonce) {
maybe_nonce = response_nonce_;
response_current_section_ = ResponseMessageSection::kChunk;
}
uint8_t chunk_var_int_length =
QuicheDataWriter::GetVarInt62Len(encrypted_data->size());
uint64_t chunk_var_int = encrypted_data->size();
if (is_final_chunk) {
response_current_section_ = ResponseMessageSection::kEnd;
chunk_var_int_length =
QuicheDataWriter::GetVarInt62Len(kFinalChunkIndicator);
// encrypted_data is guaranteed to be non-empty, so chunk_var_int_length
// should never be 0.
if (chunk_var_int_length == 0) {
return absl::InvalidArgumentError(
"Encrypted data is too large to be represented as a varint.");
}
chunk_var_int = kFinalChunkIndicator;
}
std::string response_buffer(
maybe_nonce.size() + chunk_var_int_length + encrypted_data->size(), '\0');
QuicheDataWriter writer(response_buffer.size(), response_buffer.data());
if (!writer.WriteStringPiece(maybe_nonce)) {
return absl::InternalError("Failed to write response nonce to buffer.");
}
if (!writer.WriteVarInt62(chunk_var_int)) {
return absl::InternalError("Failed to write chunk to buffer.");
}
if (!writer.WriteStringPiece(*encrypted_data)) {
return absl::InternalError("Failed to write encrypted data to buffer.");
}
if (writer.remaining() != 0) {
return absl::InternalError("Failed to write all data.");
}
response_chunk_counter_->Increment();
return response_buffer;
}
} // namespace quiche