• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2024 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "net/device_bound_sessions/registration_fetcher.h"
6 
7 #include <memory>
8 #include <utility>
9 #include <vector>
10 
11 #include "components/unexportable_keys/background_task_priority.h"
12 #include "components/unexportable_keys/unexportable_key_service.h"
13 #include "net/base/io_buffer.h"
14 #include "net/device_bound_sessions/registration_request_param.h"
15 #include "net/device_bound_sessions/session_binding_utils.h"
16 #include "net/device_bound_sessions/session_challenge_param.h"
17 #include "net/device_bound_sessions/session_json_utils.h"
18 #include "net/traffic_annotation/network_traffic_annotation.h"
19 #include "net/url_request/url_request.h"
20 #include "net/url_request/url_request_context.h"
21 
22 namespace net::device_bound_sessions {
23 
24 namespace {
25 
26 constexpr char kSessionIdHeaderName[] = "Sec-Session-Id";
27 constexpr char kJwtSessionHeaderName[] = "Sec-Session-Response";
28 constexpr net::NetworkTrafficAnnotationTag kRegistrationTrafficAnnotation =
29     net::DefineNetworkTrafficAnnotation("dbsc_registration", R"(
30         semantics {
31           sender: "Device Bound Session Credentials API"
32           description:
33             "Device Bound Session Credentials (DBSC) let a server create a "
34             "session with the local device. For more info see "
35             "https://github.com/WICG/dbsc."
36           trigger:
37             "Server sending a response with a Sec-Session-Registration header."
38           data: "A signed JWT with the new key created for this session."
39           destination: WEBSITE
40           last_reviewed: "2024-04-10"
41           user_data {
42             type: ACCESS_TOKEN
43           }
44           internal {
45             contacts {
46               email: "kristianm@chromium.org"
47             }
48             contacts {
49               email: "chrome-counter-abuse-alerts@google.com"
50             }
51           }
52         }
53         policy {
54           cookies_allowed: YES
55           cookies_store: "user"
56           setting: "There is no separate setting for this feature, but it will "
57             "follow the cookie settings."
58           policy_exception_justification: "Not implemented."
59         })");
60 
61 constexpr int kBufferSize = 4096;
62 
63 // New session registration doesn't block the user and can be done with a delay.
64 constexpr unexportable_keys::BackgroundTaskPriority kTaskPriority =
65     unexportable_keys::BackgroundTaskPriority::kBestEffort;
66 
OnDataSigned(crypto::SignatureVerifier::SignatureAlgorithm algorithm,unexportable_keys::UnexportableKeyService & unexportable_key_service,std::string header_and_payload,unexportable_keys::UnexportableKeyId key_id,base::OnceCallback<void (std::optional<RegistrationFetcher::RegistrationTokenResult>)> callback,unexportable_keys::ServiceErrorOr<std::vector<uint8_t>> result)67 void OnDataSigned(
68     crypto::SignatureVerifier::SignatureAlgorithm algorithm,
69     unexportable_keys::UnexportableKeyService& unexportable_key_service,
70     std::string header_and_payload,
71     unexportable_keys::UnexportableKeyId key_id,
72     base::OnceCallback<void(
73         std::optional<RegistrationFetcher::RegistrationTokenResult>)> callback,
74     unexportable_keys::ServiceErrorOr<std::vector<uint8_t>> result) {
75   if (!result.has_value()) {
76     std::move(callback).Run(std::nullopt);
77     return;
78   }
79 
80   const std::vector<uint8_t>& signature = result.value();
81   std::optional<std::string> registration_token =
82       AppendSignatureToHeaderAndPayload(header_and_payload, algorithm,
83                                         signature);
84   if (!registration_token.has_value()) {
85     std::move(callback).Run(std::nullopt);
86     return;
87   }
88 
89   std::move(callback).Run(RegistrationFetcher::RegistrationTokenResult(
90       registration_token.value(), key_id));
91 }
92 
93 void SignChallengeWithKey(
94     unexportable_keys::UnexportableKeyService& unexportable_key_service,
95     unexportable_keys::UnexportableKeyId& key_id,
96     const GURL& registration_url,
97     std::string_view challenge,
98     std::optional<std::string> authorization,
99     base::OnceCallback<
100         void(std::optional<RegistrationFetcher::RegistrationTokenResult>)>
101         callback) {
102   auto expected_algorithm = unexportable_key_service.GetAlgorithm(key_id);
103   auto expected_public_key =
104       unexportable_key_service.GetSubjectPublicKeyInfo(key_id);
105   if (!expected_algorithm.has_value() || !expected_public_key.has_value()) {
106     std::move(callback).Run(std::nullopt);
107     return;
108   }
109 
110   std::optional<std::string> optional_header_and_payload =
111       CreateKeyRegistrationHeaderAndPayload(
112           challenge, registration_url, expected_algorithm.value(),
113           expected_public_key.value(), base::Time::Now(),
114           std::move(authorization));
115 
116   if (!optional_header_and_payload.has_value()) {
117     std::move(callback).Run(std::nullopt);
118     return;
119   }
120 
121   std::string header_and_payload =
122       std::move(optional_header_and_payload.value());
123   unexportable_key_service.SignSlowlyAsync(
124       key_id, base::as_byte_span(header_and_payload), kTaskPriority,
125       base::BindOnce(&OnDataSigned, expected_algorithm.value(),
126                      std::ref(unexportable_key_service),
127                      std::move(header_and_payload), key_id,
128                      std::move(callback)));
129 }
130 
131 class RegistrationFetcherImpl : public URLRequest::Delegate {
132  public:
133   // URLRequest::Delegate
134 
135   void OnReceivedRedirect(URLRequest* request,
136                           const RedirectInfo& redirect_info,
137                           bool* defer_redirect) override {
138     if (!redirect_info.new_url.SchemeIsCryptographic()) {
139       request->Cancel();
140       OnResponseCompleted();
141       // *this is deleted here
142     }
143   }
144 
145   // TODO(kristianm): Look into if OnAuthRequired might need to be customize
146   // for DBSC
147 
148   // TODO(kristianm): Think about what to do for DBSC with
149   // OnCertificateRequested, leaning towards not supporting it but not sure.
150 
151   // Always cancel requests on SSL errors, this is the default implementation
152   // of OnSSLCertificateError.
153 
154   // This is always called unless the request is deleted before it is called.
155   void OnResponseStarted(URLRequest* request, int net_error) override {
156     if (net_error != OK) {
157       OnResponseCompleted();
158       // *this is deleted here
159       return;
160     }
161 
162     HttpResponseHeaders* headers = request->response_headers();
163     const int response_code = headers ? headers->response_code() : 0;
164 
165     if (response_code == 401) {
166       auto challenge_params =
167           device_bound_sessions::SessionChallengeParam::CreateIfValid(
168               fetcher_endpoint_, headers);
169       OnChallengeNeeded(std::move(challenge_params));
170       // *this is preserved here.
171       return;
172     }
173 
174     if (response_code < 200 || response_code >= 300) {
175       OnResponseCompleted();
176       // *this is deleted here
177       return;
178     }
179 
180     // Initiate the first read.
181     int bytes_read = request->Read(buf_.get(), kBufferSize);
182     if (bytes_read >= 0) {
183       OnReadCompleted(request, bytes_read);
184     } else if (bytes_read != ERR_IO_PENDING) {
185       OnResponseCompleted();
186       // *this is deleted here
187     }
188   }
189 
190   void OnReadCompleted(URLRequest* request, int bytes_read) override {
191     data_received_.append(buf_->data(), bytes_read);
192     while (bytes_read > 0) {
193       bytes_read = request->Read(buf_.get(), kBufferSize);
194       if (bytes_read > 0) {
195         data_received_.append(buf_->data(), bytes_read);
196       }
197     }
198 
199     if (bytes_read != ERR_IO_PENDING) {
200       OnResponseCompleted();
201       // *this is deleted here
202     }
203   }
204 
205   RegistrationFetcherImpl(
206       const GURL& fetcher_endpoint,
207       std::optional<std::string> session_identifier,
208       unexportable_keys::UnexportableKeyService& key_service,
209       const unexportable_keys::UnexportableKeyId& key_id,
210       const URLRequestContext* context,
211       const IsolationInfo& isolation_info,
212       RegistrationFetcher::RegistrationCompleteCallback callback)
213       : fetcher_endpoint_(fetcher_endpoint),
214         session_identifier_(std::move(session_identifier)),
215         key_service_(key_service),
216         key_id_(key_id),
217         context_(context),
218         isolation_info_(isolation_info),
219         callback_(std::move(callback)),
220         buf_(base::MakeRefCounted<IOBufferWithSize>(kBufferSize)) {}
221 
222   ~RegistrationFetcherImpl() override { CHECK(!callback_); }
223 
224   void Start(std::optional<std::string> challenge,
225              std::optional<std::string> authorization) {
226     if (challenge.has_value()) {
227       SignChallengeWithKey(
228           *key_service_, key_id_, fetcher_endpoint_, *challenge,
229           std::move(authorization),
230           base::BindOnce(&RegistrationFetcherImpl::OnRegistrationTokenCreated,
231                          base::Unretained(this)));
232       return;
233     }
234 
235     // Start a request to get a challenge with the session identifier.
236     // `RegistrationRequestParam::Create` guarantees `session_identifier_` is
237     // set when `challenge_` is missing.
238     if (session_identifier_.has_value()) {
239       request_ = CreateBaseRequest();
240       request_->Start();
241     }
242   }
243 
244  private:
245   void OnRegistrationTokenCreated(
246       std::optional<RegistrationFetcher::RegistrationTokenResult> result) {
247     if (!result) {
248       RunCallbackAndDeleteSelf(std::nullopt);
249       return;
250     }
251 
252     request_ = CreateBaseRequest();
253     request_->SetExtraRequestHeaderByName(
254         kJwtSessionHeaderName, result->registration_token, /*overwrite*/ true);
255     request_->Start();
256   }
257 
258   std::unique_ptr<net::URLRequest> CreateBaseRequest() {
259     std::unique_ptr<net::URLRequest> request = context_->CreateRequest(
260         fetcher_endpoint_, IDLE, this, kRegistrationTrafficAnnotation);
261     request->set_method("POST");
262     request->SetLoadFlags(LOAD_DISABLE_CACHE);
263     request->set_allow_credentials(true);
264 
265     request->set_site_for_cookies(isolation_info_.site_for_cookies());
266     // TODO(kristianm): Set initiator to the URL of the registration header.
267     request->set_initiator(url::Origin());
268     request->set_isolation_info(isolation_info_);
269 
270     if (session_identifier_.has_value()) {
271       request->SetExtraRequestHeaderByName(
272           kSessionIdHeaderName, *session_identifier_, /*overwrite*/ true);
273     }
274 
275     return request;
276   }
277 
278   void OnChallengeNeeded(
279       std::optional<std::vector<SessionChallengeParam>> challenge_params) {
280     if (!challenge_params || challenge_params->empty()) {
281       RunCallbackAndDeleteSelf(std::nullopt);
282       return;
283     }
284 
285     // TODO(kristianm): Log if there is more than one challenge
286     // TODO(kristianm): Handle if session identifiers don't match
287     const std::string& challenge = (*challenge_params)[0].challenge();
288     Start(challenge, std::nullopt);
289   }
290 
291   void OnResponseCompleted() {
292     if (!data_received_.empty()) {
293       std::optional<SessionParams> params =
294           ParseSessionInstructionJson(data_received_);
295       if (params) {
296         RunCallbackAndDeleteSelf(
297             std::make_optional<RegistrationFetcher::RegistrationCompleteParams>(
298                 std::move(*params), key_id_, request_->url(),
299                 std::move(session_identifier_)));
300         return;
301       }
302     }
303 
304     RunCallbackAndDeleteSelf(std::nullopt);
305   }
306 
307   // Running callback when fetching is complete or on error.
308   // Deletes `this` afterwards.
309   void RunCallbackAndDeleteSelf(
310       std::optional<RegistrationFetcher::RegistrationCompleteParams> params) {
311     std::move(callback_).Run(std::move(params));
312     delete this;
313   }
314 
315   // State passed in to constructor
316   GURL fetcher_endpoint_;
317   std::optional<std::string> session_identifier_;
318   const raw_ref<unexportable_keys::UnexportableKeyService> key_service_;
319   unexportable_keys::UnexportableKeyId key_id_;
320   raw_ptr<const URLRequestContext> context_;
321   IsolationInfo isolation_info_;
322   RegistrationFetcher::RegistrationCompleteCallback callback_;
323 
324   // Created to fetch data
325   std::unique_ptr<URLRequest> request_;
326   scoped_refptr<IOBuffer> buf_;
327   std::string data_received_;
328 };
329 
330 RegistrationFetcher::FetcherType g_mock_fetcher = nullptr;
331 
332 }  // namespace
333 
334 RegistrationFetcher::RegistrationCompleteParams::RegistrationCompleteParams(
335     SessionParams params,
336     unexportable_keys::UnexportableKeyId key_id,
337     const GURL& url,
338     std::optional<std::string> referral_session_identifier)
339     : params(std::move(params)),
340       key_id(std::move(key_id)),
341       url(url),
342       referral_session_identifier(std::move(referral_session_identifier)) {}
343 
344 RegistrationFetcher::RegistrationCompleteParams::RegistrationCompleteParams(
345     RegistrationFetcher::RegistrationCompleteParams&& other) noexcept = default;
346 RegistrationFetcher::RegistrationCompleteParams&
347 RegistrationFetcher::RegistrationCompleteParams::operator=(
348     RegistrationFetcher::RegistrationCompleteParams&& other) noexcept = default;
349 
350 RegistrationFetcher::RegistrationCompleteParams::~RegistrationCompleteParams() =
351     default;
352 
353 // static
354 void RegistrationFetcher::StartCreateTokenAndFetch(
355     RegistrationFetcherParam registration_params,
356     unexportable_keys::UnexportableKeyService& key_service,
357     // TODO(kristianm): Check the lifetime of context and make sure this use
358     // is safe.
359     const URLRequestContext* context,
360     const IsolationInfo& isolation_info,
361     RegistrationCompleteCallback callback) {
362   // Using mock fetcher for testing
363   if (g_mock_fetcher) {
364     std::move(callback).Run(g_mock_fetcher());
365     return;
366   }
367 
368   const auto supported_algos = registration_params.supported_algos();
369   auto request_params =
370       RegistrationRequestParam::Create(std::move(registration_params));
371   // `key_service` is created along with `SessionService` and will be valid
372   // until the browser ends, hence `std::ref` is safe here.
373   key_service.GenerateSigningKeySlowlyAsync(
374       supported_algos, kTaskPriority,
375       base::BindOnce(&RegistrationFetcher::StartFetchWithExistingKey,
376                      std::move(request_params), std::ref(key_service), context,
377                      isolation_info, std::move(callback)));
378 }
379 
380 // static
381 void RegistrationFetcher::StartFetchWithExistingKey(
382     RegistrationRequestParam request_params,
383     unexportable_keys::UnexportableKeyService& unexportable_key_service,
384     const URLRequestContext* context,
385     const IsolationInfo& isolation_info,
386     RegistrationFetcher::RegistrationCompleteCallback callback,
387     unexportable_keys::ServiceErrorOr<unexportable_keys::UnexportableKeyId>
388         key_id) {
389   // Using mock fetcher for testing.
390   if (g_mock_fetcher) {
391     std::move(callback).Run(g_mock_fetcher());
392     return;
393   }
394 
395   if (!key_id.has_value()) {
396     std::move(callback).Run(std::nullopt);
397     return;
398   }
399 
400   // RegistrationFetcherImpl manages its own lifetime.
401   RegistrationFetcherImpl* fetcher = new RegistrationFetcherImpl(
402       request_params.TakeRegistrationEndpoint(),
403       request_params.TakeSessionIdentifier(), unexportable_key_service,
404       key_id.value(), context, isolation_info, std::move(callback));
405 
406   fetcher->Start(request_params.TakeChallenge(),
407                  request_params.TakeAuthorization());
408 }
409 
410 void RegistrationFetcher::SetFetcherForTesting(FetcherType func) {
411   if (g_mock_fetcher) {
412     CHECK(!func);
413     g_mock_fetcher = nullptr;
414   } else {
415     g_mock_fetcher = func;
416   }
417 }
418 
419 void RegistrationFetcher::CreateTokenAsyncForTesting(
420     unexportable_keys::UnexportableKeyService& unexportable_key_service,
421     std::string challenge,
422     const GURL& registration_url,
423     std::optional<std::string> authorization,
424     base::OnceCallback<
425         void(std::optional<RegistrationFetcher::RegistrationTokenResult>)>
426         callback) {
427   static constexpr crypto::SignatureVerifier::SignatureAlgorithm
428       kSupportedAlgos[] = {crypto::SignatureVerifier::ECDSA_SHA256,
429                            crypto::SignatureVerifier::RSA_PKCS1_SHA256};
430   unexportable_key_service.GenerateSigningKeySlowlyAsync(
431       kSupportedAlgos, kTaskPriority,
432       base::BindOnce(
433           [](unexportable_keys::UnexportableKeyService& key_service,
434              const GURL& registration_url, const std::string& challenge,
435              std::optional<std::string>&& authorization,
436              base::OnceCallback<void(
437                  std::optional<RegistrationFetcher::RegistrationTokenResult>)>
438                  callback,
439              unexportable_keys::ServiceErrorOr<
440                  unexportable_keys::UnexportableKeyId> key_result) {
441             if (!key_result.has_value()) {
442               std::move(callback).Run(std::nullopt);
443               return;
444             }
445 
446             SignChallengeWithKey(key_service, key_result.value(),
447                                  registration_url, challenge,
448                                  std::move(authorization), std::move(callback));
449           },
450           std::ref(unexportable_key_service), registration_url,
451           std::move(challenge), std::move(authorization), std::move(callback)));
452 }
453 
454 }  // namespace net::device_bound_sessions
455