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