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