1 #include "quiche/oblivious_http/buffers/oblivious_http_response.h"
2
3 #include <stddef.h>
4 #include <stdint.h>
5
6 #include <algorithm>
7 #include <memory>
8 #include <string>
9 #include <utility>
10
11 #include "absl/status/status.h"
12 #include "absl/status/statusor.h"
13 #include "absl/strings/str_cat.h"
14 #include "absl/strings/string_view.h"
15 #include "openssl/aead.h"
16 #include "openssl/hkdf.h"
17 #include "openssl/hpke.h"
18 #include "quiche/common/platform/api/quiche_bug_tracker.h"
19 #include "quiche/common/quiche_crypto_logging.h"
20 #include "quiche/common/quiche_random.h"
21 #include "quiche/oblivious_http/common/oblivious_http_header_key_config.h"
22
23 namespace quiche {
24 namespace {
25 // Generate a random string.
random(QuicheRandom * quiche_random,char * dest,size_t len)26 void random(QuicheRandom* quiche_random, char* dest, size_t len) {
27 if (quiche_random == nullptr) {
28 quiche_random = QuicheRandom::GetInstance();
29 }
30 quiche_random->RandBytes(dest, len);
31 }
32 } // namespace
33
34 // Ctor.
ObliviousHttpResponse(std::string encrypted_data,std::string resp_plaintext)35 ObliviousHttpResponse::ObliviousHttpResponse(std::string encrypted_data,
36 std::string resp_plaintext)
37 : encrypted_data_(std::move(encrypted_data)),
38 response_plaintext_(std::move(resp_plaintext)) {}
39
40 // Response Decapsulation.
41 // 1. Extract resp_nonce
42 // 2. Build prk (pseudorandom key) using HKDF_Extract
43 // 3. Derive aead_key using HKDF_Labeled_Expand
44 // 4. Derive aead_nonce using HKDF_Labeled_Expand
45 // 5. Setup AEAD context and Decrypt.
46 // https://www.ietf.org/archive/id/draft-ietf-ohai-ohttp-03.html#section-4.2-4
47 absl::StatusOr<ObliviousHttpResponse>
CreateClientObliviousResponse(std::string encrypted_data,ObliviousHttpRequest::Context & oblivious_http_request_context,absl::string_view resp_label)48 ObliviousHttpResponse::CreateClientObliviousResponse(
49 std::string encrypted_data,
50 ObliviousHttpRequest::Context& oblivious_http_request_context,
51 absl::string_view resp_label) {
52 if (oblivious_http_request_context.hpke_context_ == nullptr) {
53 return absl::FailedPreconditionError(
54 "HPKE context wasn't initialized before proceeding with this Response "
55 "Decapsulation on Client-side.");
56 }
57 size_t expected_key_len = EVP_HPKE_KEM_enc_len(
58 EVP_HPKE_CTX_kem(oblivious_http_request_context.hpke_context_.get()));
59 if (oblivious_http_request_context.encapsulated_key_.size() !=
60 expected_key_len) {
61 return absl::InvalidArgumentError(absl::StrCat(
62 "Invalid len for encapsulated_key arg. Expected:", expected_key_len,
63 " Actual:", oblivious_http_request_context.encapsulated_key_.size()));
64 }
65 if (encrypted_data.empty()) {
66 return absl::InvalidArgumentError("Empty encrypted_data input param.");
67 }
68
69 absl::StatusOr<CommonAeadParamsResult> aead_params_st =
70 GetCommonAeadParams(oblivious_http_request_context);
71 if (!aead_params_st.ok()) {
72 return aead_params_st.status();
73 }
74
75 // secret_len = [max(Nn, Nk)] where Nk and Nn are the length of AEAD
76 // key and nonce associated with HPKE context.
77 // https://www.ietf.org/archive/id/draft-ietf-ohai-ohttp-03.html#section-4.2-2.1
78 size_t secret_len = aead_params_st.value().secret_len;
79 if (encrypted_data.size() < secret_len) {
80 return absl::InvalidArgumentError(
81 absl::StrCat("Invalid input response. Failed to parse required minimum "
82 "expected_len=",
83 secret_len, " bytes."));
84 }
85 // Extract response_nonce. Step 2
86 // https://www.ietf.org/archive/id/draft-ietf-ohai-ohttp-03.html#section-4.2-2.2
87 absl::string_view response_nonce =
88 absl::string_view(encrypted_data).substr(0, secret_len);
89 absl::string_view encrypted_response =
90 absl::string_view(encrypted_data).substr(secret_len);
91
92 // Steps (1, 3 to 5) + AEAD context SetUp before 6th step is performed in
93 // CommonOperations.
94 auto common_ops_st = CommonOperationsToEncapDecap(
95 response_nonce, oblivious_http_request_context, resp_label,
96 aead_params_st.value().aead_key_len,
97 aead_params_st.value().aead_nonce_len, aead_params_st.value().secret_len);
98 if (!common_ops_st.ok()) {
99 return common_ops_st.status();
100 }
101
102 std::string decrypted(encrypted_response.size(), '\0');
103 size_t decrypted_len;
104
105 // Decrypt with initialized AEAD context.
106 // response, error = Open(aead_key, aead_nonce, "", ct)
107 // https://www.ietf.org/archive/id/draft-ietf-ohai-ohttp-03.html#section-4.2-6
108 if (!EVP_AEAD_CTX_open(
109 common_ops_st.value().aead_ctx.get(),
110 reinterpret_cast<uint8_t*>(decrypted.data()), &decrypted_len,
111 decrypted.size(),
112 reinterpret_cast<const uint8_t*>(
113 common_ops_st.value().aead_nonce.data()),
114 aead_params_st.value().aead_nonce_len,
115 reinterpret_cast<const uint8_t*>(encrypted_response.data()),
116 encrypted_response.size(), nullptr, 0)) {
117 return SslErrorAsStatus(
118 "Failed to decrypt the response with derived AEAD key and nonce.");
119 }
120 decrypted.resize(decrypted_len);
121 ObliviousHttpResponse oblivious_response(std::move(encrypted_data),
122 std::move(decrypted));
123 return oblivious_response;
124 }
125
126 // Response Encapsulation.
127 // Follows the Ohttp spec section-4.2 (Encapsulation of Responses) Ref
128 // https://www.ietf.org/archive/id/draft-ietf-ohai-ohttp-03.html#section-4.2
129 // Use HPKE context from BoringSSL to export a secret and use it to Seal (AKA
130 // encrypt) the response back to the Sender(client)
131 absl::StatusOr<ObliviousHttpResponse>
CreateServerObliviousResponse(std::string plaintext_payload,ObliviousHttpRequest::Context & oblivious_http_request_context,absl::string_view response_label,QuicheRandom * quiche_random)132 ObliviousHttpResponse::CreateServerObliviousResponse(
133 std::string plaintext_payload,
134 ObliviousHttpRequest::Context& oblivious_http_request_context,
135 absl::string_view response_label, QuicheRandom* quiche_random) {
136 if (oblivious_http_request_context.hpke_context_ == nullptr) {
137 return absl::FailedPreconditionError(
138 "HPKE context wasn't initialized before proceeding with this Response "
139 "Encapsulation on Server-side.");
140 }
141 size_t expected_key_len = EVP_HPKE_KEM_enc_len(
142 EVP_HPKE_CTX_kem(oblivious_http_request_context.hpke_context_.get()));
143 if (oblivious_http_request_context.encapsulated_key_.size() !=
144 expected_key_len) {
145 return absl::InvalidArgumentError(absl::StrCat(
146 "Invalid len for encapsulated_key arg. Expected:", expected_key_len,
147 " Actual:", oblivious_http_request_context.encapsulated_key_.size()));
148 }
149 if (plaintext_payload.empty()) {
150 return absl::InvalidArgumentError("Empty plaintext_payload input param.");
151 }
152 absl::StatusOr<CommonAeadParamsResult> aead_params_st =
153 GetCommonAeadParams(oblivious_http_request_context);
154 if (!aead_params_st.ok()) {
155 return aead_params_st.status();
156 }
157 const size_t nonce_size = aead_params_st->secret_len;
158 const size_t max_encrypted_data_size =
159 nonce_size + plaintext_payload.size() +
160 EVP_AEAD_max_overhead(EVP_HPKE_AEAD_aead(EVP_HPKE_CTX_aead(
161 oblivious_http_request_context.hpke_context_.get())));
162 std::string encrypted_data(max_encrypted_data_size, '\0');
163 // response_nonce = random(max(Nn, Nk))
164 // https://www.ietf.org/archive/id/draft-ietf-ohai-ohttp-03.html#section-4.2-2.2
165 random(quiche_random, encrypted_data.data(), nonce_size);
166 absl::string_view response_nonce =
167 absl::string_view(encrypted_data).substr(0, nonce_size);
168
169 // Steps (1, 3 to 5) + AEAD context SetUp before 6th step is performed in
170 // CommonOperations.
171 auto common_ops_st = CommonOperationsToEncapDecap(
172 response_nonce, oblivious_http_request_context, response_label,
173 aead_params_st.value().aead_key_len,
174 aead_params_st.value().aead_nonce_len, aead_params_st.value().secret_len);
175 if (!common_ops_st.ok()) {
176 return common_ops_st.status();
177 }
178
179 // ct = Seal(aead_key, aead_nonce, "", response)
180 // https://www.ietf.org/archive/id/draft-ietf-ohai-ohttp-03.html#section-4.2-2.6
181 size_t ciphertext_len;
182 if (!EVP_AEAD_CTX_seal(
183 common_ops_st.value().aead_ctx.get(),
184 reinterpret_cast<uint8_t*>(encrypted_data.data() + nonce_size),
185 &ciphertext_len, encrypted_data.size() - nonce_size,
186 reinterpret_cast<const uint8_t*>(
187 common_ops_st.value().aead_nonce.data()),
188 aead_params_st.value().aead_nonce_len,
189 reinterpret_cast<const uint8_t*>(plaintext_payload.data()),
190 plaintext_payload.size(), nullptr, 0)) {
191 return SslErrorAsStatus(
192 "Failed to encrypt the payload with derived AEAD key.");
193 }
194 encrypted_data.resize(nonce_size + ciphertext_len);
195 if (nonce_size == 0 || ciphertext_len == 0) {
196 return absl::InternalError(absl::StrCat(
197 "ObliviousHttpResponse Object wasn't initialized with required fields.",
198 (nonce_size == 0 ? "Generated nonce is empty." : ""),
199 (ciphertext_len == 0 ? "Generated Encrypted payload is empty." : "")));
200 }
201 ObliviousHttpResponse oblivious_response(std::move(encrypted_data),
202 std::move(plaintext_payload));
203 return oblivious_response;
204 }
205
206 // Serialize.
207 // enc_response = concat(response_nonce, ct)
208 // https://www.ietf.org/archive/id/draft-ietf-ohai-ohttp-03.html#section-4.2-4
EncapsulateAndSerialize() const209 const std::string& ObliviousHttpResponse::EncapsulateAndSerialize() const {
210 return encrypted_data_;
211 }
212
213 // Decrypted blob.
GetPlaintextData() const214 const std::string& ObliviousHttpResponse::GetPlaintextData() const {
215 return response_plaintext_;
216 }
217
218 // This section mainly deals with common operations performed by both
219 // Sender(client) and Receiver(gateway) on ObliviousHttpResponse.
220
221 absl::StatusOr<ObliviousHttpResponse::CommonAeadParamsResult>
GetCommonAeadParams(ObliviousHttpRequest::Context & oblivious_http_request_context)222 ObliviousHttpResponse::GetCommonAeadParams(
223 ObliviousHttpRequest::Context& oblivious_http_request_context) {
224 const EVP_AEAD* evp_hpke_aead = EVP_HPKE_AEAD_aead(
225 EVP_HPKE_CTX_aead(oblivious_http_request_context.hpke_context_.get()));
226 if (evp_hpke_aead == nullptr) {
227 return absl::FailedPreconditionError(
228 "Key Configuration not supported by HPKE AEADs. Check your key "
229 "config.");
230 }
231 // Nk = [AEAD key len], is determined by BoringSSL.
232 const size_t aead_key_len = EVP_AEAD_key_length(evp_hpke_aead);
233 // Nn = [AEAD nonce len], is determined by BoringSSL.
234 const size_t aead_nonce_len = EVP_AEAD_nonce_length(evp_hpke_aead);
235 const size_t secret_len = std::max(aead_key_len, aead_nonce_len);
236 CommonAeadParamsResult result{evp_hpke_aead, aead_key_len, aead_nonce_len,
237 secret_len};
238 return result;
239 }
240
241 // Common Steps of AEAD key and AEAD nonce derivation common to both
242 // client(decapsulation) & Gateway(encapsulation) in handling
243 // Oblivious-Response. Ref Steps (1, 3-to-5, and setting up AEAD context in
244 // preparation for 6th step's Seal/Open) in spec.
245 // https://www.ietf.org/archive/id/draft-ietf-ohai-ohttp-03.html#section-4.2-4
246 absl::StatusOr<ObliviousHttpResponse::CommonOperationsResult>
CommonOperationsToEncapDecap(absl::string_view response_nonce,ObliviousHttpRequest::Context & oblivious_http_request_context,absl::string_view resp_label,const size_t aead_key_len,const size_t aead_nonce_len,const size_t secret_len)247 ObliviousHttpResponse::CommonOperationsToEncapDecap(
248 absl::string_view response_nonce,
249 ObliviousHttpRequest::Context& oblivious_http_request_context,
250 absl::string_view resp_label, const size_t aead_key_len,
251 const size_t aead_nonce_len, const size_t secret_len) {
252 if (response_nonce.empty()) {
253 return absl::InvalidArgumentError("Invalid input params.");
254 }
255 // secret = context.Export("message/bhttp response", Nk)
256 // Export secret of len [max(Nn, Nk)] where Nk and Nn are the length of AEAD
257 // key and nonce associated with context.
258 // https://www.ietf.org/archive/id/draft-ietf-ohai-ohttp-03.html#section-4.2-2.1
259 std::string secret(secret_len, '\0');
260 if (!EVP_HPKE_CTX_export(oblivious_http_request_context.hpke_context_.get(),
261 reinterpret_cast<uint8_t*>(secret.data()),
262 secret.size(),
263 reinterpret_cast<const uint8_t*>(resp_label.data()),
264 resp_label.size())) {
265 return SslErrorAsStatus("Failed to export secret.");
266 }
267
268 // salt = concat(enc, response_nonce)
269 // https://www.ietf.org/archive/id/draft-ietf-ohai-ohttp-03.html#section-4.2-2.3
270 std::string salt = absl::StrCat(
271 oblivious_http_request_context.encapsulated_key_, response_nonce);
272
273 // prk = Extract(salt, secret)
274 // https://www.ietf.org/archive/id/draft-ietf-ohai-ohttp-03.html#section-4.2-2.3
275 std::string pseudorandom_key(EVP_MAX_MD_SIZE, '\0');
276 size_t prk_len;
277 auto evp_md = EVP_HPKE_KDF_hkdf_md(
278 EVP_HPKE_CTX_kdf(oblivious_http_request_context.hpke_context_.get()));
279 if (evp_md == nullptr) {
280 QUICHE_BUG(Invalid Key Configuration
281 : Unsupported BoringSSL HPKE KDFs)
282 << "Update KeyConfig to support only BoringSSL HKDFs.";
283 return absl::FailedPreconditionError(
284 "Key Configuration not supported by BoringSSL HPKE KDFs. Check your "
285 "Key "
286 "Config.");
287 }
288 if (!HKDF_extract(
289 reinterpret_cast<uint8_t*>(pseudorandom_key.data()), &prk_len, evp_md,
290 reinterpret_cast<const uint8_t*>(secret.data()), secret_len,
291 reinterpret_cast<const uint8_t*>(salt.data()), salt.size())) {
292 return SslErrorAsStatus(
293 "Failed to derive pesudorandom key from salt and secret.");
294 }
295 pseudorandom_key.resize(prk_len);
296
297 // aead_key = Expand(prk, "key", Nk)
298 // https://www.ietf.org/archive/id/draft-ietf-ohai-ohttp-03.html#section-4.2-2.4
299 std::string aead_key(aead_key_len, '\0');
300 absl::string_view hkdf_info = ObliviousHttpHeaderKeyConfig::kKeyHkdfInfo;
301 // All currently supported KDFs are HKDF-based. See CheckKdfId in
302 // `ObliviousHttpHeaderKeyConfig`.
303 if (!HKDF_expand(reinterpret_cast<uint8_t*>(aead_key.data()), aead_key_len,
304 evp_md,
305 reinterpret_cast<const uint8_t*>(pseudorandom_key.data()),
306 prk_len, reinterpret_cast<const uint8_t*>(hkdf_info.data()),
307 hkdf_info.size())) {
308 return SslErrorAsStatus(
309 "Failed to expand AEAD key using pseudorandom key(prk).");
310 }
311
312 // aead_nonce = Expand(prk, "nonce", Nn)
313 // https://www.ietf.org/archive/id/draft-ietf-ohai-ohttp-03.html#section-4.2-2.5
314 std::string aead_nonce(aead_nonce_len, '\0');
315 hkdf_info = ObliviousHttpHeaderKeyConfig::kNonceHkdfInfo;
316 // All currently supported KDFs are HKDF-based. See CheckKdfId in
317 // `ObliviousHttpHeaderKeyConfig`.
318 if (!HKDF_expand(reinterpret_cast<uint8_t*>(aead_nonce.data()),
319 aead_nonce_len, evp_md,
320 reinterpret_cast<const uint8_t*>(pseudorandom_key.data()),
321 prk_len, reinterpret_cast<const uint8_t*>(hkdf_info.data()),
322 hkdf_info.size())) {
323 return SslErrorAsStatus(
324 "Failed to expand AEAD nonce using pseudorandom key(prk).");
325 }
326
327 const EVP_AEAD* evp_hpke_aead = EVP_HPKE_AEAD_aead(
328 EVP_HPKE_CTX_aead(oblivious_http_request_context.hpke_context_.get()));
329 if (evp_hpke_aead == nullptr) {
330 return absl::FailedPreconditionError(
331 "Key Configuration not supported by HPKE AEADs. Check your key "
332 "config.");
333 }
334
335 // Setup AEAD context for subsequent Seal/Open operation in response handling.
336 bssl::UniquePtr<EVP_AEAD_CTX> aead_ctx(EVP_AEAD_CTX_new(
337 evp_hpke_aead, reinterpret_cast<const uint8_t*>(aead_key.data()),
338 aead_key.size(), 0));
339 if (aead_ctx == nullptr) {
340 return SslErrorAsStatus("Failed to initialize AEAD context.");
341 }
342 if (!EVP_AEAD_CTX_init(aead_ctx.get(), evp_hpke_aead,
343 reinterpret_cast<const uint8_t*>(aead_key.data()),
344 aead_key.size(), 0, nullptr)) {
345 return SslErrorAsStatus(
346 "Failed to initialize AEAD context with derived key.");
347 }
348 CommonOperationsResult result{std::move(aead_ctx), std::move(aead_nonce)};
349 return result;
350 }
351
352 } // namespace quiche
353