| #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 |