blob: 2963ca25d52c11fa4eb4714854a14b35de76b118 [file] [log] [blame]
#include "quiche/oblivious_http/common/oblivious_http_header_key_config.h"
#include <cstdint>
#include "absl/memory/memory.h"
#include "absl/status/status.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "openssl/base.h"
#include "openssl/hpke.h"
#include "quiche/common/platform/api/quiche_logging.h"
#include "quiche/common/quiche_data_writer.h"
#include "quiche/common/quiche_endian.h"
namespace quiche {
namespace {
absl::StatusOr<const EVP_HPKE_KEM*> CheckKemId(uint16_t kem_id) {
switch (kem_id) {
case EVP_HPKE_DHKEM_X25519_HKDF_SHA256:
return EVP_hpke_x25519_hkdf_sha256();
default:
return absl::UnimplementedError("No support for this KEM ID.");
}
}
absl::StatusOr<const EVP_HPKE_KDF*> CheckKdfId(uint16_t kdf_id) {
switch (kdf_id) {
case EVP_HPKE_HKDF_SHA256:
return EVP_hpke_hkdf_sha256();
default:
return absl::UnimplementedError("No support for this KDF ID.");
}
}
absl::StatusOr<const EVP_HPKE_AEAD*> CheckAeadId(uint16_t aead_id) {
switch (aead_id) {
case EVP_HPKE_AES_128_GCM:
return EVP_hpke_aes_128_gcm();
case EVP_HPKE_AES_256_GCM:
return EVP_hpke_aes_256_gcm();
case EVP_HPKE_CHACHA20_POLY1305:
return EVP_hpke_chacha20_poly1305();
default:
return absl::UnimplementedError("No support for this AEAD ID.");
}
}
} // namespace
ObliviousHttpHeaderKeyConfig::ObliviousHttpHeaderKeyConfig(uint8_t key_id,
uint16_t kem_id,
uint16_t kdf_id,
uint16_t aead_id)
: key_id_(key_id), kem_id_(kem_id), kdf_id_(kdf_id), aead_id_(aead_id) {}
absl::StatusOr<ObliviousHttpHeaderKeyConfig>
ObliviousHttpHeaderKeyConfig::Create(uint8_t key_id, uint16_t kem_id,
uint16_t kdf_id, uint16_t aead_id) {
ObliviousHttpHeaderKeyConfig instance(key_id, kem_id, kdf_id, aead_id);
auto is_config_ok = instance.ValidateKeyConfig();
if (!is_config_ok.ok()) {
return is_config_ok;
}
return instance;
}
absl::Status ObliviousHttpHeaderKeyConfig::ValidateKeyConfig() const {
auto supported_kem = CheckKemId(kem_id_);
if (!supported_kem.ok()) {
return absl::InvalidArgumentError(
absl::StrCat("Unsupported KEM ID:", kem_id_));
}
auto supported_kdf = CheckKdfId(kdf_id_);
if (!supported_kdf.ok()) {
return absl::InvalidArgumentError(
absl::StrCat("Unsupported KDF ID:", kdf_id_));
}
auto supported_aead = CheckAeadId(aead_id_);
if (!supported_aead.ok()) {
return absl::InvalidArgumentError(
absl::StrCat("Unsupported AEAD ID:", aead_id_));
}
return absl::OkStatus();
}
const EVP_HPKE_KEM* ObliviousHttpHeaderKeyConfig::GetHpkeKem() const {
auto kem = CheckKemId(kem_id_);
QUICHE_CHECK_OK(kem.status());
return kem.value();
}
const EVP_HPKE_KDF* ObliviousHttpHeaderKeyConfig::GetHpkeKdf() const {
auto kdf = CheckKdfId(kdf_id_);
QUICHE_CHECK_OK(kdf.status());
return kdf.value();
}
const EVP_HPKE_AEAD* ObliviousHttpHeaderKeyConfig::GetHpkeAead() const {
auto aead = CheckAeadId(aead_id_);
QUICHE_CHECK_OK(aead.status());
return aead.value();
}
std::string ObliviousHttpHeaderKeyConfig::SerializeRecipientContextInfo()
const {
uint8_t zero_byte = 0x00;
int buf_len = kOhttpRequestLabel.size() + kHeaderLength + sizeof(zero_byte);
std::string info(buf_len, '\0');
QuicheDataWriter writer(info.size(), info.data());
QUICHE_CHECK(writer.WriteStringPiece(kOhttpRequestLabel));
QUICHE_CHECK(writer.WriteUInt8(zero_byte)); // Zero byte.
QUICHE_CHECK(writer.WriteUInt8(key_id_));
QUICHE_CHECK(writer.WriteUInt16(kem_id_));
QUICHE_CHECK(writer.WriteUInt16(kdf_id_));
QUICHE_CHECK(writer.WriteUInt16(aead_id_));
return info;
}
/**
* Follows IETF Ohttp spec, section 4.1 (Encapsulation of Requests).
* https://www.ietf.org/archive/id/draft-ietf-ohai-ohttp-03.html#section-4.1-10
*/
absl::Status ObliviousHttpHeaderKeyConfig::ParseOhttpPayloadHeader(
absl::string_view payload_bytes) const {
if (payload_bytes.empty()) {
return absl::InvalidArgumentError("Empty request payload.");
}
QuicheDataReader reader(payload_bytes);
return ParseOhttpPayloadHeader(reader);
}
absl::Status ObliviousHttpHeaderKeyConfig::ParseOhttpPayloadHeader(
QuicheDataReader& reader) const {
uint8_t key_id;
if (!reader.ReadUInt8(&key_id)) {
return absl::InvalidArgumentError("Failed to read key_id from header.");
}
if (key_id != key_id_) {
return absl::InvalidArgumentError(
absl::StrCat("KeyID in request:", static_cast<uint16_t>(key_id),
" doesn't match with server's public key "
"configuration KeyID:",
static_cast<uint16_t>(key_id_)));
}
uint16_t kem_id;
if (!reader.ReadUInt16(&kem_id)) {
return absl::InvalidArgumentError("Failed to read kem_id from header.");
}
if (kem_id != kem_id_) {
return absl::InvalidArgumentError(
absl::StrCat("Received Invalid kemID:", kem_id, " Expected:", kem_id_));
}
uint16_t kdf_id;
if (!reader.ReadUInt16(&kdf_id)) {
return absl::InvalidArgumentError("Failed to read kdf_id from header.");
}
if (kdf_id != kdf_id_) {
return absl::InvalidArgumentError(
absl::StrCat("Received Invalid kdfID:", kdf_id, " Expected:", kdf_id_));
}
uint16_t aead_id;
if (!reader.ReadUInt16(&aead_id)) {
return absl::InvalidArgumentError("Failed to read aead_id from header.");
}
if (aead_id != aead_id_) {
return absl::InvalidArgumentError(absl::StrCat(
"Received Invalid aeadID:", aead_id, " Expected:", aead_id_));
}
return absl::OkStatus();
}
absl::StatusOr<uint8_t>
ObliviousHttpHeaderKeyConfig::ParseKeyIdFromObliviousHttpRequestPayload(
absl::string_view payload_bytes) {
if (payload_bytes.empty()) {
return absl::InvalidArgumentError("Empty request payload.");
}
QuicheDataReader reader(payload_bytes);
uint8_t key_id;
if (!reader.ReadUInt8(&key_id)) {
return absl::InvalidArgumentError("Failed to read key_id from payload.");
}
return key_id;
}
std::string ObliviousHttpHeaderKeyConfig::SerializeOhttpPayloadHeader() const {
int buf_len =
sizeof(key_id_) + sizeof(kem_id_) + sizeof(kdf_id_) + sizeof(aead_id_);
std::string hdr(buf_len, '\0');
QuicheDataWriter writer(hdr.size(), hdr.data());
QUICHE_CHECK(writer.WriteUInt8(key_id_));
QUICHE_CHECK(writer.WriteUInt16(kem_id_)); // kemID
QUICHE_CHECK(writer.WriteUInt16(kdf_id_)); // kdfID
QUICHE_CHECK(writer.WriteUInt16(aead_id_)); // aeadID
return hdr;
}
absl::StatusOr<ObliviousHttpKeyConfigs>
ObliviousHttpKeyConfigs::ParseConcatenatedKeys(absl::string_view key_config) {
ConfigMap configs;
PublicKeyMap keys;
auto reader = QuicheDataReader(key_config);
while (!reader.IsDoneReading()) {
absl::Status status = ReadSingleKeyConfig(reader, configs, keys);
if (!status.ok()) return status;
}
return ObliviousHttpKeyConfigs(std::move(configs), std::move(keys));
}
ObliviousHttpHeaderKeyConfig ObliviousHttpKeyConfigs::PreferredConfig() const {
// configs_ is forced to have at least one object during construction.
return configs_.begin()->second.front();
}
absl::StatusOr<absl::string_view> ObliviousHttpKeyConfigs::GetPublicKeyForId(
uint8_t key_id) const {
auto key = public_keys_.find(key_id);
if (key == public_keys_.end()) {
return absl::NotFoundError("No public key found for key_id");
}
return key->second;
}
namespace {
// https://www.rfc-editor.org/rfc/rfc9180#section-7.1
// TODO(kmg): Switch to BoringSSL's EVP_HPKE_KEM_public_key_len()
// https://boringssl-review.googlesource.com/c/boringssl/+/54605
absl::StatusOr<uint16_t> KeyLength(uint16_t kem_id) {
switch (kem_id) {
case EVP_HPKE_DHKEM_X25519_HKDF_SHA256:
return 32;
default:
return absl::InvalidArgumentError(
"Unsupported kem_id; public key length is unknown.");
}
}
} // namespace
absl::Status ObliviousHttpKeyConfigs::ReadSingleKeyConfig(
QuicheDataReader& reader, ConfigMap& configs, PublicKeyMap& keys) {
uint8_t key_id;
uint16_t kem_id;
// First byte: key_id; next two bytes: kem_id.
if (!reader.ReadUInt8(&key_id) || !reader.ReadUInt16(&kem_id)) {
return absl::InvalidArgumentError("Invalid key_config!");
}
// Public key length depends on the kem_id.
auto maybe_key_length = KeyLength(kem_id);
if (!maybe_key_length.ok()) {
return maybe_key_length.status();
}
const int key_length = maybe_key_length.value();
std::string key_str(key_length, '\0');
if (!reader.ReadBytes(key_str.data(), key_length)) {
return absl::InvalidArgumentError("Invalid key_config!");
}
if (!keys.insert({key_id, std::move(key_str)}).second) {
return absl::InvalidArgumentError("Duplicate key_id's in key_config!");
}
// Extract the algorithms for this public key.
absl::string_view alg_bytes;
// Read the 16-bit length, then read that many bytes into alg_bytes.
if (!reader.ReadStringPiece16(&alg_bytes)) {
return absl::InvalidArgumentError("Invalid key_config!");
}
QuicheDataReader sub_reader(alg_bytes);
while (!sub_reader.IsDoneReading()) {
uint16_t kdf_id;
uint16_t aead_id;
if (!sub_reader.ReadUInt16(&kdf_id) || !sub_reader.ReadUInt16(&aead_id)) {
return absl::InvalidArgumentError("Invalid key_config!");
}
absl::StatusOr<ObliviousHttpHeaderKeyConfig> maybe_cfg =
ObliviousHttpHeaderKeyConfig::Create(key_id, kem_id, kdf_id, aead_id);
if (!maybe_cfg.ok()) {
// TODO(kmg): Add support to ignore key types in the server response that
// aren't supported by the client.
return maybe_cfg.status();
}
configs[key_id].emplace_back(std::move(maybe_cfg.value()));
}
return absl::OkStatus();
}
} // namespace quiche