blob: 7ac16ad7647a79c820edf026f3c87f815a4a7ebf [file] [log] [blame]
#ifndef QUICHE_OBLIVIOUS_HTTP_OBLIVIOUS_HTTP_CLIENT_H_
#define QUICHE_OBLIVIOUS_HTTP_OBLIVIOUS_HTTP_CLIENT_H_
#include <optional>
#include <string>
#include "absl/base/attributes.h"
#include "absl/base/nullability.h"
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/string_view.h"
#include "openssl/base.h"
#include "openssl/hpke.h"
#include "quiche/common/platform/api/quiche_export.h"
#include "quiche/common/quiche_data_reader.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 {
// 1. Facilitates client side to intiate OHttp request flow by initializing the
// HPKE public key obtained from server, and subsequently uses it to encrypt the
// Binary HTTP request payload.
// 2. After initializing this class with server's HPKE public key, users can
// call `CreateObliviousHttpRequest` which constructs OHTTP request of the input
// payload(Binary HTTP request).
// 3. Handles decryption of response (that's in the form of encrypted Binary
// HTTP response) that will be sent back from Server-to-Relay and
// Relay-to-client in HTTP POST body.
// 4. Handles BoringSSL HPKE context setup and bookkeeping.
// This class is immutable (except moves) and thus trivially thread-safe.
class QUICHE_EXPORT ObliviousHttpClient {
public:
static absl::StatusOr<ObliviousHttpClient> Create(
absl::string_view hpke_public_key,
const ObliviousHttpHeaderKeyConfig& ohttp_key_config);
// Copyable.
ObliviousHttpClient(const ObliviousHttpClient& other) = default;
ObliviousHttpClient& operator=(const ObliviousHttpClient& other) = default;
// Movable.
ObliviousHttpClient(ObliviousHttpClient&& other) = default;
ObliviousHttpClient& operator=(ObliviousHttpClient&& other) = default;
~ObliviousHttpClient() = default;
// After successful `Create`, callers will use the returned object to
// repeatedly call into this method in order to create Oblivious HTTP request
// with the initialized HPKE public key. Call sequence: Create ->
// CreateObliviousHttpRequest -> DecryptObliviousHttpResponse.
// Eg.,
// auto ohttp_client_object = ObliviousHttpClient::Create( <HPKE
// public key>, <OHTTP key configuration described in
// `oblivious_http_header_key_config.h`>);
// auto encrypted_request1 =
// ohttp_client_object.CreateObliviousHttpRequest("binary http string 1");
// auto encrypted_request2 =
// ohttp_client_object.CreateObliviousHttpRequest("binary http string 2");
absl::StatusOr<ObliviousHttpRequest> CreateObliviousHttpRequest(
std::string plaintext_data) const;
// After `CreateObliviousHttpRequest` operation, callers on client-side will
// extract `oblivious_http_request_context` from the returned object
// `ObliviousHttpRequest` and pass in to this method in order to decrypt the
// response that's received from Gateway for the given request at hand.
absl::StatusOr<ObliviousHttpResponse> DecryptObliviousHttpResponse(
std::string encrypted_data,
ObliviousHttpRequest::Context& oblivious_http_request_context) const;
private:
explicit ObliviousHttpClient(
std::string client_public_key,
const ObliviousHttpHeaderKeyConfig& ohttp_key_config);
std::string hpke_public_key_;
// Holds server's keyID and HPKE related IDs that's published under HPKE
// public Key configuration.
// https://www.rfc-editor.org/rfc/rfc9458.html#section-3
ObliviousHttpHeaderKeyConfig ohttp_key_config_;
};
// Manages a chunked Oblivious HTTP request and response.
// It's designed to continuously encrypt and send request chunks while
// decrypting and handling incoming response chunks. This object maintains an
// internal state, so it can only be used for one complete request-response
// cycle.
class QUICHE_EXPORT ChunkedObliviousHttpClient {
public:
// Creates a new ChunkedObliviousHttpClient. Does not take ownership of
// `chunk_handler`, which must refer to a valid handler that outlives this
// client. The `seed` parameter is used to initialize the HPKE sender context.
// If `seed` is empty, a random seed will be generated.
static absl::StatusOr<ChunkedObliviousHttpClient> Create(
absl::string_view hpke_public_key,
const ObliviousHttpHeaderKeyConfig& ohttp_key_config,
ObliviousHttpChunkHandler* absl_nonnull chunk_handler
ABSL_ATTRIBUTE_LIFETIME_BOUND,
absl::string_view seed = "");
// Encrypts the data as a single chunk. The first time this is called it will
// also include the HPKE context header data. If `is_final_chunk` is true,
// the chunk will be encrypted with a final AAD.
absl::StatusOr<std::string> EncryptRequestChunk(
absl::string_view plaintext_payload, bool is_final_chunk);
// Parses the `encrypted_data` into the corresponding chunks and decrypts
// them. This can be invoked multiple times as data arrives, incomplete chunks
// will be buffered. The first time it is called it will also decode the
// response nonce. On successful decryption, the chunk handler will be
// invoked. The `end_stream` parameter must be set to true if the
// `encrypted_data`
// contains the final portion of the final chunk.
absl::Status DecryptResponse(absl::string_view encrypted_data,
bool end_stream);
private:
enum class RequestMessageSection {
kHeader,
kChunk,
kEnd, // Set by end_stream or if there is an error.
};
enum class ResponseMessageSection {
kNonce,
kChunk,
kFinalChunk,
kEnd, // Set by end_stream or if there is an error.
};
// Does not take ownership of `chunk_handler`, which must refer to a valid
// handler that outlives this client.
explicit ChunkedObliviousHttpClient(
const ObliviousHttpHeaderKeyConfig& ohttp_key_config,
ObliviousHttpRequest::Context hpke_sender_context,
ObliviousHttpResponse::CommonAeadParamsResult aead_params,
ObliviousHttpChunkHandler* absl_nonnull chunk_handler
ABSL_ATTRIBUTE_LIFETIME_BOUND);
// Provides the response checkpoint from where to begin decryption. The
// returned string_view is owned by either the provided data or the buffer.
absl::string_view InitializeResponseCheckpoint(absl::string_view data);
// Carries out the decrypting logic starting from the checkpoint. Returns
// OutOfRangeError if there is not enough data to process the current
// section. When a section is fully processed, the checkpoint is updated.
absl::Status DecryptResponseCheckpoint(absl::string_view& response_checkpoint,
bool end_stream);
// Updates the checkpoint based on the current position of the reader.
void UpdateCheckpoint(const QuicheDataReader& reader,
absl::string_view& response_checkpoint) {
response_checkpoint = reader.PeekRemainingPayload();
}
// Buffers the response checkpoint.
void BufferResponseCheckpoint(absl::string_view response_checkpoint) {
if (response_buffer_ != response_checkpoint) {
response_buffer_.assign(response_checkpoint);
}
}
absl::StatusOr<std::string> EncryptRequestChunkImpl(
absl::string_view plaintext_payload, bool is_final_chunk);
// Holds server's keyID and HPKE related IDs that's published under HPKE
// public Key configuration.
// https://www.rfc-editor.org/rfc/rfc9458.html#section-3
ObliviousHttpHeaderKeyConfig ohttp_key_config_;
ObliviousHttpRequest::Context hpke_sender_context_;
ObliviousHttpResponse::CommonAeadParamsResult aead_params_;
// The handler to invoke when a chunk is decrypted successfully. Not owned.
ObliviousHttpChunkHandler& chunk_handler_;
RequestMessageSection request_current_section_ =
RequestMessageSection::kHeader;
ResponseMessageSection response_current_section_ =
ResponseMessageSection::kNonce;
// Holds the response data that could not be processed in the last call.
std::string response_buffer_;
// AEAD context data derived from the response nonce.
std::optional<ObliviousHttpResponse::AeadContextData> aead_context_data_;
// Counter to keep track of the number of response chunks decrypted and to
// generate the corresponding chunk nonce.
std::optional<ObliviousHttpResponse::ChunkCounter> response_chunk_counter_;
};
} // namespace quiche
#endif // QUICHE_OBLIVIOUS_HTTP_OBLIVIOUS_HTTP_CLIENT_H_