1 #include "quiche/oblivious_http/common/oblivious_http_header_key_config.h"
2
3 #include <algorithm>
4 #include <cstdint>
5 #include <string>
6 #include <vector>
7
8 #include "absl/memory/memory.h"
9 #include "absl/status/status.h"
10 #include "absl/strings/escaping.h"
11 #include "absl/strings/str_cat.h"
12 #include "absl/strings/string_view.h"
13 #include "openssl/base.h"
14 #include "openssl/hpke.h"
15 #include "quiche/common/platform/api/quiche_bug_tracker.h"
16 #include "quiche/common/platform/api/quiche_logging.h"
17 #include "quiche/common/quiche_data_writer.h"
18 #include "quiche/common/quiche_endian.h"
19
20 namespace quiche {
21 namespace {
22
23 // Size of KEM ID is 2 bytes. Refer to OHTTP Key Config in the spec,
24 // https://www.ietf.org/archive/id/draft-ietf-ohai-ohttp-06.html#name-a-single-key-configuration
25 constexpr size_t kSizeOfHpkeKemId = 2;
26
27 // Size of Symmetric algorithms is 2 bytes(16 bits) each.
28 // Refer to HPKE Symmetric Algorithms configuration in the spec,
29 // https://www.ietf.org/archive/id/draft-ietf-ohai-ohttp-06.html#name-a-single-key-configuration
30 constexpr size_t kSizeOfSymmetricAlgorithmHpkeKdfId = 2;
31 constexpr size_t kSizeOfSymmetricAlgorithmHpkeAeadId = 2;
32
CheckKemId(uint16_t kem_id)33 absl::StatusOr<const EVP_HPKE_KEM*> CheckKemId(uint16_t kem_id) {
34 switch (kem_id) {
35 case EVP_HPKE_DHKEM_X25519_HKDF_SHA256:
36 return EVP_hpke_x25519_hkdf_sha256();
37 default:
38 return absl::UnimplementedError("No support for this KEM ID.");
39 }
40 }
41
CheckKdfId(uint16_t kdf_id)42 absl::StatusOr<const EVP_HPKE_KDF*> CheckKdfId(uint16_t kdf_id) {
43 switch (kdf_id) {
44 case EVP_HPKE_HKDF_SHA256:
45 return EVP_hpke_hkdf_sha256();
46 default:
47 return absl::UnimplementedError("No support for this KDF ID.");
48 }
49 }
50
CheckAeadId(uint16_t aead_id)51 absl::StatusOr<const EVP_HPKE_AEAD*> CheckAeadId(uint16_t aead_id) {
52 switch (aead_id) {
53 case EVP_HPKE_AES_128_GCM:
54 return EVP_hpke_aes_128_gcm();
55 case EVP_HPKE_AES_256_GCM:
56 return EVP_hpke_aes_256_gcm();
57 case EVP_HPKE_CHACHA20_POLY1305:
58 return EVP_hpke_chacha20_poly1305();
59 default:
60 return absl::UnimplementedError("No support for this AEAD ID.");
61 }
62 }
63
64 } // namespace
65
ObliviousHttpHeaderKeyConfig(uint8_t key_id,uint16_t kem_id,uint16_t kdf_id,uint16_t aead_id)66 ObliviousHttpHeaderKeyConfig::ObliviousHttpHeaderKeyConfig(uint8_t key_id,
67 uint16_t kem_id,
68 uint16_t kdf_id,
69 uint16_t aead_id)
70 : key_id_(key_id), kem_id_(kem_id), kdf_id_(kdf_id), aead_id_(aead_id) {}
71
72 absl::StatusOr<ObliviousHttpHeaderKeyConfig>
Create(uint8_t key_id,uint16_t kem_id,uint16_t kdf_id,uint16_t aead_id)73 ObliviousHttpHeaderKeyConfig::Create(uint8_t key_id, uint16_t kem_id,
74 uint16_t kdf_id, uint16_t aead_id) {
75 ObliviousHttpHeaderKeyConfig instance(key_id, kem_id, kdf_id, aead_id);
76 auto is_config_ok = instance.ValidateKeyConfig();
77 if (!is_config_ok.ok()) {
78 return is_config_ok;
79 }
80 return instance;
81 }
82
ValidateKeyConfig() const83 absl::Status ObliviousHttpHeaderKeyConfig::ValidateKeyConfig() const {
84 auto supported_kem = CheckKemId(kem_id_);
85 if (!supported_kem.ok()) {
86 return absl::InvalidArgumentError(
87 absl::StrCat("Unsupported KEM ID:", kem_id_));
88 }
89 auto supported_kdf = CheckKdfId(kdf_id_);
90 if (!supported_kdf.ok()) {
91 return absl::InvalidArgumentError(
92 absl::StrCat("Unsupported KDF ID:", kdf_id_));
93 }
94 auto supported_aead = CheckAeadId(aead_id_);
95 if (!supported_aead.ok()) {
96 return absl::InvalidArgumentError(
97 absl::StrCat("Unsupported AEAD ID:", aead_id_));
98 }
99 return absl::OkStatus();
100 }
101
GetHpkeKem() const102 const EVP_HPKE_KEM* ObliviousHttpHeaderKeyConfig::GetHpkeKem() const {
103 auto kem = CheckKemId(kem_id_);
104 QUICHE_CHECK_OK(kem.status());
105 return kem.value();
106 }
GetHpkeKdf() const107 const EVP_HPKE_KDF* ObliviousHttpHeaderKeyConfig::GetHpkeKdf() const {
108 auto kdf = CheckKdfId(kdf_id_);
109 QUICHE_CHECK_OK(kdf.status());
110 return kdf.value();
111 }
GetHpkeAead() const112 const EVP_HPKE_AEAD* ObliviousHttpHeaderKeyConfig::GetHpkeAead() const {
113 auto aead = CheckAeadId(aead_id_);
114 QUICHE_CHECK_OK(aead.status());
115 return aead.value();
116 }
117
SerializeRecipientContextInfo(absl::string_view request_label) const118 std::string ObliviousHttpHeaderKeyConfig::SerializeRecipientContextInfo(
119 absl::string_view request_label) const {
120 uint8_t zero_byte = 0x00;
121 int buf_len = request_label.size() + kHeaderLength + sizeof(zero_byte);
122 std::string info(buf_len, '\0');
123 QuicheDataWriter writer(info.size(), info.data());
124 QUICHE_CHECK(writer.WriteStringPiece(request_label));
125 QUICHE_CHECK(writer.WriteUInt8(zero_byte)); // Zero byte.
126 QUICHE_CHECK(writer.WriteUInt8(key_id_));
127 QUICHE_CHECK(writer.WriteUInt16(kem_id_));
128 QUICHE_CHECK(writer.WriteUInt16(kdf_id_));
129 QUICHE_CHECK(writer.WriteUInt16(aead_id_));
130 return info;
131 }
132
133 /**
134 * Follows IETF Ohttp spec, section 4.1 (Encapsulation of Requests).
135 * https://www.ietf.org/archive/id/draft-ietf-ohai-ohttp-03.html#section-4.1-10
136 */
ParseOhttpPayloadHeader(absl::string_view payload_bytes) const137 absl::Status ObliviousHttpHeaderKeyConfig::ParseOhttpPayloadHeader(
138 absl::string_view payload_bytes) const {
139 if (payload_bytes.empty()) {
140 return absl::InvalidArgumentError("Empty request payload.");
141 }
142 QuicheDataReader reader(payload_bytes);
143 return ParseOhttpPayloadHeader(reader);
144 }
145
ParseOhttpPayloadHeader(QuicheDataReader & reader) const146 absl::Status ObliviousHttpHeaderKeyConfig::ParseOhttpPayloadHeader(
147 QuicheDataReader& reader) const {
148 uint8_t key_id;
149 if (!reader.ReadUInt8(&key_id)) {
150 return absl::InvalidArgumentError("Failed to read key_id from header.");
151 }
152 if (key_id != key_id_) {
153 return absl::InvalidArgumentError(
154 absl::StrCat("KeyID in request:", static_cast<uint16_t>(key_id),
155 " doesn't match with server's public key "
156 "configuration KeyID:",
157 static_cast<uint16_t>(key_id_)));
158 }
159 uint16_t kem_id;
160 if (!reader.ReadUInt16(&kem_id)) {
161 return absl::InvalidArgumentError("Failed to read kem_id from header.");
162 }
163 if (kem_id != kem_id_) {
164 return absl::InvalidArgumentError(
165 absl::StrCat("Received Invalid kemID:", kem_id, " Expected:", kem_id_));
166 }
167 uint16_t kdf_id;
168 if (!reader.ReadUInt16(&kdf_id)) {
169 return absl::InvalidArgumentError("Failed to read kdf_id from header.");
170 }
171 if (kdf_id != kdf_id_) {
172 return absl::InvalidArgumentError(
173 absl::StrCat("Received Invalid kdfID:", kdf_id, " Expected:", kdf_id_));
174 }
175 uint16_t aead_id;
176 if (!reader.ReadUInt16(&aead_id)) {
177 return absl::InvalidArgumentError("Failed to read aead_id from header.");
178 }
179 if (aead_id != aead_id_) {
180 return absl::InvalidArgumentError(absl::StrCat(
181 "Received Invalid aeadID:", aead_id, " Expected:", aead_id_));
182 }
183 return absl::OkStatus();
184 }
185
186 absl::StatusOr<uint8_t>
ParseKeyIdFromObliviousHttpRequestPayload(absl::string_view payload_bytes)187 ObliviousHttpHeaderKeyConfig::ParseKeyIdFromObliviousHttpRequestPayload(
188 absl::string_view payload_bytes) {
189 if (payload_bytes.empty()) {
190 return absl::InvalidArgumentError("Empty request payload.");
191 }
192 QuicheDataReader reader(payload_bytes);
193 uint8_t key_id;
194 if (!reader.ReadUInt8(&key_id)) {
195 return absl::InvalidArgumentError("Failed to read key_id from payload.");
196 }
197 return key_id;
198 }
199
SerializeOhttpPayloadHeader() const200 std::string ObliviousHttpHeaderKeyConfig::SerializeOhttpPayloadHeader() const {
201 int buf_len =
202 sizeof(key_id_) + sizeof(kem_id_) + sizeof(kdf_id_) + sizeof(aead_id_);
203 std::string hdr(buf_len, '\0');
204 QuicheDataWriter writer(hdr.size(), hdr.data());
205 QUICHE_CHECK(writer.WriteUInt8(key_id_));
206 QUICHE_CHECK(writer.WriteUInt16(kem_id_)); // kemID
207 QUICHE_CHECK(writer.WriteUInt16(kdf_id_)); // kdfID
208 QUICHE_CHECK(writer.WriteUInt16(aead_id_)); // aeadID
209 return hdr;
210 }
211
212 namespace {
213 // https://www.rfc-editor.org/rfc/rfc9180#section-7.1
KeyLength(uint16_t kem_id)214 absl::StatusOr<uint16_t> KeyLength(uint16_t kem_id) {
215 auto supported_kem = CheckKemId(kem_id);
216 if (!supported_kem.ok()) {
217 return absl::InvalidArgumentError(absl::StrCat(
218 "Unsupported KEM ID:", kem_id, ". public key length is unknown."));
219 }
220 return EVP_HPKE_KEM_public_key_len(supported_kem.value());
221 }
222
SerializeOhttpKeyWithPublicKey(uint8_t key_id,absl::string_view public_key,const std::vector<ObliviousHttpHeaderKeyConfig> & ohttp_configs)223 absl::StatusOr<std::string> SerializeOhttpKeyWithPublicKey(
224 uint8_t key_id, absl::string_view public_key,
225 const std::vector<ObliviousHttpHeaderKeyConfig>& ohttp_configs) {
226 auto ohttp_config = ohttp_configs[0];
227 // Check if `ohttp_config` match spec's encoding guidelines.
228 static_assert(sizeof(ohttp_config.GetHpkeKemId()) == kSizeOfHpkeKemId &&
229 sizeof(ohttp_config.GetHpkeKdfId()) ==
230 kSizeOfSymmetricAlgorithmHpkeKdfId &&
231 sizeof(ohttp_config.GetHpkeAeadId()) ==
232 kSizeOfSymmetricAlgorithmHpkeAeadId,
233 "Size of HPKE IDs should match RFC specification.");
234
235 uint16_t symmetric_algs_length =
236 ohttp_configs.size() * (kSizeOfSymmetricAlgorithmHpkeKdfId +
237 kSizeOfSymmetricAlgorithmHpkeAeadId);
238 int buf_len = sizeof(key_id) + kSizeOfHpkeKemId + public_key.size() +
239 sizeof(symmetric_algs_length) + symmetric_algs_length;
240 std::string ohttp_key_configuration(buf_len, '\0');
241 QuicheDataWriter writer(ohttp_key_configuration.size(),
242 ohttp_key_configuration.data());
243 if (!writer.WriteUInt8(key_id)) {
244 return absl::InternalError("Failed to serialize OHTTP key.[key_id]");
245 }
246 if (!writer.WriteUInt16(ohttp_config.GetHpkeKemId())) {
247 return absl::InternalError(
248 "Failed to serialize OHTTP key.[kem_id]"); // kemID.
249 }
250 if (!writer.WriteStringPiece(public_key)) {
251 return absl::InternalError(
252 "Failed to serialize OHTTP key.[public_key]"); // Raw public key.
253 }
254 if (!writer.WriteUInt16(symmetric_algs_length)) {
255 return absl::InternalError(
256 "Failed to serialize OHTTP key.[symmetric_algs_length]");
257 }
258 for (const auto& item : ohttp_configs) {
259 // Check if KEM ID is the same for all the configs stored in `this` for
260 // given `key_id`.
261 if (item.GetHpkeKemId() != ohttp_config.GetHpkeKemId()) {
262 QUICHE_BUG(ohttp_key_configs_builder_parser)
263 << "ObliviousHttpKeyConfigs object cannot hold ConfigMap of "
264 "different KEM IDs:[ "
265 << item.GetHpkeKemId() << "," << ohttp_config.GetHpkeKemId()
266 << " ]for a given key_id:" << static_cast<uint16_t>(key_id);
267 }
268 if (!writer.WriteUInt16(item.GetHpkeKdfId())) {
269 return absl::InternalError(
270 "Failed to serialize OHTTP key.[kdf_id]"); // kdfID.
271 }
272 if (!writer.WriteUInt16(item.GetHpkeAeadId())) {
273 return absl::InternalError(
274 "Failed to serialize OHTTP key.[aead_id]"); // aeadID.
275 }
276 }
277 QUICHE_DCHECK_EQ(writer.remaining(), 0u);
278 return ohttp_key_configuration;
279 }
280
GetDebugStringForFailedKeyConfig(const ObliviousHttpKeyConfigs::OhttpKeyConfig & failed_key_config)281 std::string GetDebugStringForFailedKeyConfig(
282 const ObliviousHttpKeyConfigs::OhttpKeyConfig& failed_key_config) {
283 std::string debug_string = "[ ";
284 absl::StrAppend(&debug_string,
285 "key_id:", static_cast<uint16_t>(failed_key_config.key_id),
286 " , kem_id:", failed_key_config.kem_id,
287 ". Printing HEX formatted public_key:",
288 absl::BytesToHexString(failed_key_config.public_key));
289 absl::StrAppend(&debug_string, ", symmetric_algorithms: { ");
290 for (const auto& symmetric_config : failed_key_config.symmetric_algorithms) {
291 absl::StrAppend(&debug_string, "{kdf_id: ", symmetric_config.kdf_id,
292 ", aead_id:", symmetric_config.aead_id, " }");
293 }
294 absl::StrAppend(&debug_string, " } ]");
295 return debug_string;
296 }
297
298 // Verifies if the `key_config` contains all valid combinations of [kem_id,
299 // kdf_id, aead_id] that comprises Single Key configuration encoding as
300 // specified in
301 // https://www.ietf.org/archive/id/draft-ietf-ohai-ohttp-03.html#name-a-single-key-configuration.
StoreKeyConfigIfValid(ObliviousHttpKeyConfigs::OhttpKeyConfig key_config,absl::btree_map<uint8_t,std::vector<ObliviousHttpHeaderKeyConfig>,std::greater<uint8_t>> & configs,absl::flat_hash_map<uint8_t,std::string> & keys)302 absl::Status StoreKeyConfigIfValid(
303 ObliviousHttpKeyConfigs::OhttpKeyConfig key_config,
304 absl::btree_map<uint8_t, std::vector<ObliviousHttpHeaderKeyConfig>,
305 std::greater<uint8_t>>& configs,
306 absl::flat_hash_map<uint8_t, std::string>& keys) {
307 if (!CheckKemId(key_config.kem_id).ok() ||
308 key_config.public_key.size() != KeyLength(key_config.kem_id).value()) {
309 QUICHE_LOG(ERROR) << "Failed to process: "
310 << GetDebugStringForFailedKeyConfig(key_config);
311 return absl::InvalidArgumentError(
312 absl::StrCat("Invalid key_config! [KEM ID:", key_config.kem_id, "]"));
313 }
314 for (const auto& symmetric_config : key_config.symmetric_algorithms) {
315 if (!CheckKdfId(symmetric_config.kdf_id).ok() ||
316 !CheckAeadId(symmetric_config.aead_id).ok()) {
317 QUICHE_LOG(ERROR) << "Failed to process: "
318 << GetDebugStringForFailedKeyConfig(key_config);
319 return absl::InvalidArgumentError(
320 absl::StrCat("Invalid key_config! [KDF ID:", symmetric_config.kdf_id,
321 ", AEAD ID:", symmetric_config.aead_id, "]"));
322 }
323 auto ohttp_config = ObliviousHttpHeaderKeyConfig::Create(
324 key_config.key_id, key_config.kem_id, symmetric_config.kdf_id,
325 symmetric_config.aead_id);
326 if (ohttp_config.ok()) {
327 configs[key_config.key_id].emplace_back(std::move(ohttp_config.value()));
328 }
329 }
330 keys.emplace(key_config.key_id, std::move(key_config.public_key));
331 return absl::OkStatus();
332 }
333
334 } // namespace
335
336 absl::StatusOr<ObliviousHttpKeyConfigs>
ParseConcatenatedKeys(absl::string_view key_config)337 ObliviousHttpKeyConfigs::ParseConcatenatedKeys(absl::string_view key_config) {
338 ConfigMap configs;
339 PublicKeyMap keys;
340 auto reader = QuicheDataReader(key_config);
341 while (!reader.IsDoneReading()) {
342 absl::Status status = ReadSingleKeyConfig(reader, configs, keys);
343 if (!status.ok()) return status;
344 }
345 return ObliviousHttpKeyConfigs(std::move(configs), std::move(keys));
346 }
347
Create(absl::flat_hash_set<ObliviousHttpKeyConfigs::OhttpKeyConfig> ohttp_key_configs)348 absl::StatusOr<ObliviousHttpKeyConfigs> ObliviousHttpKeyConfigs::Create(
349 absl::flat_hash_set<ObliviousHttpKeyConfigs::OhttpKeyConfig>
350 ohttp_key_configs) {
351 if (ohttp_key_configs.empty()) {
352 return absl::InvalidArgumentError("Empty input.");
353 }
354 ConfigMap configs_map;
355 PublicKeyMap keys_map;
356 for (auto& ohttp_key_config : ohttp_key_configs) {
357 auto result = StoreKeyConfigIfValid(std::move(ohttp_key_config),
358 configs_map, keys_map);
359 if (!result.ok()) {
360 return result;
361 }
362 }
363 auto oblivious_configs =
364 ObliviousHttpKeyConfigs(std::move(configs_map), std::move(keys_map));
365 return oblivious_configs;
366 }
367
Create(const ObliviousHttpHeaderKeyConfig & single_key_config,absl::string_view public_key)368 absl::StatusOr<ObliviousHttpKeyConfigs> ObliviousHttpKeyConfigs::Create(
369 const ObliviousHttpHeaderKeyConfig& single_key_config,
370 absl::string_view public_key) {
371 if (public_key.empty()) {
372 return absl::InvalidArgumentError("Empty input.");
373 }
374
375 if (auto key_length = KeyLength(single_key_config.GetHpkeKemId());
376 public_key.size() != key_length.value()) {
377 return absl::InvalidArgumentError(absl::StrCat(
378 "Invalid key. Key size mismatch. Expected:", key_length.value(),
379 " Actual:", public_key.size()));
380 }
381
382 ConfigMap configs;
383 PublicKeyMap keys;
384 uint8_t key_id = single_key_config.GetKeyId();
385 keys.emplace(key_id, public_key);
386 configs[key_id].emplace_back(std::move(single_key_config));
387 return ObliviousHttpKeyConfigs(std::move(configs), std::move(keys));
388 }
389
GenerateConcatenatedKeys() const390 absl::StatusOr<std::string> ObliviousHttpKeyConfigs::GenerateConcatenatedKeys()
391 const {
392 std::string concatenated_keys;
393 for (const auto& [key_id, ohttp_configs] : configs_) {
394 auto key = public_keys_.find(key_id);
395 if (key == public_keys_.end()) {
396 return absl::InternalError(
397 "Failed to serialize. No public key found for key_id");
398 }
399 auto serialized =
400 SerializeOhttpKeyWithPublicKey(key_id, key->second, ohttp_configs);
401 if (!serialized.ok()) {
402 return absl::InternalError("Failed to serialize OHTTP key configs.");
403 }
404 absl::StrAppend(&concatenated_keys, serialized.value());
405 }
406 return concatenated_keys;
407 }
408
PreferredConfig() const409 ObliviousHttpHeaderKeyConfig ObliviousHttpKeyConfigs::PreferredConfig() const {
410 // configs_ is forced to have at least one object during construction.
411 return configs_.begin()->second.front();
412 }
413
GetPublicKeyForId(uint8_t key_id) const414 absl::StatusOr<absl::string_view> ObliviousHttpKeyConfigs::GetPublicKeyForId(
415 uint8_t key_id) const {
416 auto key = public_keys_.find(key_id);
417 if (key == public_keys_.end()) {
418 return absl::NotFoundError("No public key found for key_id");
419 }
420 return key->second;
421 }
422
ReadSingleKeyConfig(QuicheDataReader & reader,ConfigMap & configs,PublicKeyMap & keys)423 absl::Status ObliviousHttpKeyConfigs::ReadSingleKeyConfig(
424 QuicheDataReader& reader, ConfigMap& configs, PublicKeyMap& keys) {
425 uint8_t key_id;
426 uint16_t kem_id;
427 // First byte: key_id; next two bytes: kem_id.
428 if (!reader.ReadUInt8(&key_id) || !reader.ReadUInt16(&kem_id)) {
429 return absl::InvalidArgumentError("Invalid key_config!");
430 }
431
432 // Public key length depends on the kem_id.
433 auto maybe_key_length = KeyLength(kem_id);
434 if (!maybe_key_length.ok()) {
435 return maybe_key_length.status();
436 }
437 const int key_length = maybe_key_length.value();
438 std::string key_str(key_length, '\0');
439 if (!reader.ReadBytes(key_str.data(), key_length)) {
440 return absl::InvalidArgumentError("Invalid key_config!");
441 }
442 if (!keys.insert({key_id, std::move(key_str)}).second) {
443 return absl::InvalidArgumentError("Duplicate key_id's in key_config!");
444 }
445
446 // Extract the algorithms for this public key.
447 absl::string_view alg_bytes;
448 // Read the 16-bit length, then read that many bytes into alg_bytes.
449 if (!reader.ReadStringPiece16(&alg_bytes)) {
450 return absl::InvalidArgumentError("Invalid key_config!");
451 }
452 QuicheDataReader sub_reader(alg_bytes);
453 while (!sub_reader.IsDoneReading()) {
454 uint16_t kdf_id;
455 uint16_t aead_id;
456 if (!sub_reader.ReadUInt16(&kdf_id) || !sub_reader.ReadUInt16(&aead_id)) {
457 return absl::InvalidArgumentError("Invalid key_config!");
458 }
459
460 absl::StatusOr<ObliviousHttpHeaderKeyConfig> maybe_cfg =
461 ObliviousHttpHeaderKeyConfig::Create(key_id, kem_id, kdf_id, aead_id);
462 if (!maybe_cfg.ok()) {
463 // TODO(kmg): Add support to ignore key types in the server response that
464 // aren't supported by the client.
465 return maybe_cfg.status();
466 }
467 configs[key_id].emplace_back(std::move(maybe_cfg.value()));
468 }
469 return absl::OkStatus();
470 }
471
472 } // namespace quiche
473