• 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 <string>
9 #include <utility>
10 
11 #include "base/functional/bind.h"
12 #include "base/functional/callback.h"
13 #include "base/run_loop.h"
14 #include "base/test/scoped_feature_list.h"
15 #include "base/test/test_future.h"
16 #include "components/unexportable_keys/unexportable_key_service.h"
17 #include "components/unexportable_keys/unexportable_key_service_impl.h"
18 #include "components/unexportable_keys/unexportable_key_task_manager.h"
19 #include "crypto/scoped_mock_unexportable_key_provider.h"
20 #include "net/base/features.h"
21 #include "net/base/network_anonymization_key.h"
22 #include "net/base/schemeful_site.h"
23 #include "net/cookies/cookie_access_result.h"
24 #include "net/cookies/cookie_store.h"
25 #include "net/cookies/cookie_store_test_callbacks.h"
26 #include "net/device_bound_sessions/registration_request_param.h"
27 #include "net/http/http_request_headers.h"
28 #include "net/http/http_response_headers.h"
29 #include "net/http/http_status_code.h"
30 #include "net/socket/socket_test_util.h"
31 #include "net/test/embedded_test_server/embedded_test_server.h"
32 #include "net/test/embedded_test_server/http_request.h"
33 #include "net/test/embedded_test_server/http_response.h"
34 #include "net/test/test_with_task_environment.h"
35 #include "net/url_request/url_request_context.h"
36 #include "net/url_request/url_request_context_builder.h"
37 #include "net/url_request/url_request_test_util.h"
38 #include "testing/gmock/include/gmock/gmock.h"
39 #include "testing/gtest/include/gtest/gtest.h"
40 #include "url/gurl.h"
41 #include "url/origin.h"
42 
43 namespace net::device_bound_sessions {
44 
45 namespace {
46 
47 using ::testing::ElementsAre;
48 
49 constexpr char kBasicValidJson[] =
50     R"({
51   "session_identifier": "session_id",
52   "scope": {
53     "include_site": true,
54     "scope_specification" : [
55       {
56         "type": "include",
57         "domain": "trusted.example.com",
58         "path": "/only_trusted_path"
59       }
60     ]
61   },
62   "credentials": [{
63     "type": "cookie",
64     "name": "auth_cookie",
65     "attributes": "Domain=example.com; Path=/; Secure; SameSite=None"
66   }]
67 })";
68 
69 constexpr char kSessionIdentifier[] = "session_id";
70 constexpr char kRedirectPath[] = "/redirect";
71 constexpr char kChallenge[] = "test_challenge";
72 const GURL kRegistrationUrl = GURL("https://www.example.test/startsession");
73 constexpr unexportable_keys::BackgroundTaskPriority kTaskPriority =
74     unexportable_keys::BackgroundTaskPriority::kBestEffort;
CreateAlgArray()75 std::vector<crypto::SignatureVerifier::SignatureAlgorithm> CreateAlgArray() {
76   return {crypto::SignatureVerifier::SignatureAlgorithm::ECDSA_SHA256,
77           crypto::SignatureVerifier::SignatureAlgorithm::RSA_PKCS1_SHA256};
78 }
79 
80 class RegistrationTest : public TestWithTaskEnvironment {
81  protected:
RegistrationTest()82   RegistrationTest()
83       : server_(test_server::EmbeddedTestServer::TYPE_HTTPS),
84         context_(CreateTestURLRequestContextBuilder()->Build()),
85         unexportable_key_service_(task_manager_) {}
86 
unexportable_key_service()87   unexportable_keys::UnexportableKeyService& unexportable_key_service() {
88     return unexportable_key_service_;
89   }
90 
GetBasicParam(std::optional<GURL> url=std::nullopt)91   RegistrationFetcherParam GetBasicParam(
92       std::optional<GURL> url = std::nullopt) {
93     if (!url) {
94       url = server_.GetURL("/");
95     }
96 
97     return RegistrationFetcherParam::CreateInstanceForTesting(
98         *url, CreateAlgArray(), std::string(kChallenge),
99         /*authorization=*/std::nullopt);
100   }
101 
CreateKeyAndRunCallback(base::OnceCallback<void (unexportable_keys::ServiceErrorOr<unexportable_keys::UnexportableKeyId>)> callback)102   void CreateKeyAndRunCallback(
103       base::OnceCallback<void(unexportable_keys::ServiceErrorOr<
104                               unexportable_keys::UnexportableKeyId>)>
105           callback) {
106     unexportable_key_service_.GenerateSigningKeySlowlyAsync(
107         CreateAlgArray(), kTaskPriority, std::move(callback));
108   }
109 
110   test_server::EmbeddedTestServer server_;
111   std::unique_ptr<URLRequestContext> context_;
112 
113   const url::Origin kOrigin = url::Origin::Create(GURL("https://origin/"));
114   unexportable_keys::UnexportableKeyTaskManager task_manager_{
115       crypto::UnexportableKeyProvider::Config()};
116   unexportable_keys::UnexportableKeyServiceImpl unexportable_key_service_;
117 };
118 
119 class TestRegistrationCallback {
120  public:
121   TestRegistrationCallback() = default;
122 
callback()123   RegistrationFetcher::RegistrationCompleteCallback callback() {
124     return base::BindOnce(&TestRegistrationCallback::OnRegistrationComplete,
125                           base::Unretained(this));
126   }
127 
WaitForCall()128   void WaitForCall() {
129     if (called_) {
130       return;
131     }
132 
133     base::RunLoop run_loop;
134 
135     waiting_ = true;
136     closure_ = run_loop.QuitClosure();
137     run_loop.Run();
138   }
139 
outcome()140   std::optional<RegistrationFetcher::RegistrationCompleteParams> outcome() {
141     EXPECT_TRUE(called_);
142     return std::move(outcome_);
143   }
144 
145  private:
OnRegistrationComplete(std::optional<RegistrationFetcher::RegistrationCompleteParams> params)146   void OnRegistrationComplete(
147       std::optional<RegistrationFetcher::RegistrationCompleteParams> params) {
148     EXPECT_FALSE(called_);
149 
150     called_ = true;
151     outcome_ = std::move(params);
152 
153     if (waiting_) {
154       waiting_ = false;
155       std::move(closure_).Run();
156     }
157   }
158 
159   bool called_ = false;
160   std::optional<RegistrationFetcher::RegistrationCompleteParams> outcome_ =
161       std::nullopt;
162 
163   bool waiting_ = false;
164   base::OnceClosure closure_;
165 };
166 
ReturnResponse(HttpStatusCode code,std::string_view response_text,const test_server::HttpRequest & request)167 std::unique_ptr<test_server::HttpResponse> ReturnResponse(
168     HttpStatusCode code,
169     std::string_view response_text,
170     const test_server::HttpRequest& request) {
171   auto response = std::make_unique<test_server::BasicHttpResponse>();
172   response->set_code(code);
173   response->set_content_type("application/json");
174   response->set_content(response_text);
175   return response;
176 }
177 
ReturnUnauthorized(const test_server::HttpRequest & request)178 std::unique_ptr<test_server::HttpResponse> ReturnUnauthorized(
179     const test_server::HttpRequest& request) {
180   auto response = std::make_unique<test_server::BasicHttpResponse>();
181   response->set_code(HTTP_UNAUTHORIZED);
182   response->AddCustomHeader("Sec-Session-Challenge", R"("challenge")");
183   return response;
184 }
185 
ReturnTextResponse(const test_server::HttpRequest & request)186 std::unique_ptr<test_server::HttpResponse> ReturnTextResponse(
187     const test_server::HttpRequest& request) {
188   auto response = std::make_unique<test_server::BasicHttpResponse>();
189   response->set_code(HTTP_OK);
190   response->set_content_type("text/plain");
191   response->set_content("some content");
192   return response;
193 }
194 
ReturnInvalidResponse(const test_server::HttpRequest & request)195 std::unique_ptr<test_server::HttpResponse> ReturnInvalidResponse(
196     const test_server::HttpRequest& request) {
197   return std::make_unique<test_server::RawHttpResponse>(
198       "", "Not a valid HTTP response.");
199 }
200 
201 class UnauthorizedThenSuccessResponseContainer {
202  public:
UnauthorizedThenSuccessResponseContainer(int unauthorize_response_times)203   UnauthorizedThenSuccessResponseContainer(int unauthorize_response_times)
204       : run_times(0), error_respose_times(unauthorize_response_times) {}
205 
Return(const test_server::HttpRequest & request)206   std::unique_ptr<test_server::HttpResponse> Return(
207       const test_server::HttpRequest& request) {
208     if (run_times++ < error_respose_times) {
209       return ReturnUnauthorized(request);
210     }
211     return ReturnResponse(HTTP_OK, kBasicValidJson, request);
212   }
213 
214  private:
215   int run_times;
216   int error_respose_times;
217 };
218 
TEST_F(RegistrationTest,BasicSuccess)219 TEST_F(RegistrationTest, BasicSuccess) {
220   crypto::ScopedMockUnexportableKeyProvider scoped_mock_key_provider_;
221   server_.RegisterRequestHandler(
222       base::BindRepeating(&ReturnResponse, HTTP_OK, kBasicValidJson));
223   ASSERT_TRUE(server_.Start());
224 
225   TestRegistrationCallback callback;
226   RegistrationFetcher::StartCreateTokenAndFetch(
227       GetBasicParam(), unexportable_key_service(), context_.get(),
228       IsolationInfo::CreateTransient(), callback.callback());
229   callback.WaitForCall();
230   std::optional<RegistrationFetcher::RegistrationCompleteParams> out_params =
231       callback.outcome();
232   ASSERT_TRUE(out_params);
233   EXPECT_TRUE(out_params->params.scope.include_site);
234   EXPECT_THAT(out_params->params.scope.specifications,
235               ElementsAre(SessionParams::Scope::Specification(
236                   SessionParams::Scope::Specification::Type::kInclude,
237                   "trusted.example.com", "/only_trusted_path")));
238   EXPECT_THAT(
239       out_params->params.credentials,
240       ElementsAre(SessionParams::Credential(
241           "auth_cookie", "Domain=example.com; Path=/; Secure; SameSite=None")));
242 }
243 
TEST_F(RegistrationTest,NoScopeJson)244 TEST_F(RegistrationTest, NoScopeJson) {
245   constexpr char kTestingJson[] =
246       R"({
247   "session_identifier": "session_id",
248   "credentials": [{
249     "type": "cookie",
250     "name": "auth_cookie",
251     "attributes": "Domain=example.com; Path=/; Secure; SameSite=None"
252   }]
253 })";
254   crypto::ScopedMockUnexportableKeyProvider scoped_mock_key_provider_;
255   server_.RegisterRequestHandler(
256       base::BindRepeating(&ReturnResponse, HTTP_OK, kTestingJson));
257   ASSERT_TRUE(server_.Start());
258 
259   TestRegistrationCallback callback;
260   RegistrationFetcher::StartCreateTokenAndFetch(
261       GetBasicParam(), unexportable_key_service(), context_.get(),
262       IsolationInfo::CreateTransient(), callback.callback());
263   callback.WaitForCall();
264   std::optional<RegistrationFetcher::RegistrationCompleteParams> out_params =
265       callback.outcome();
266   ASSERT_TRUE(out_params);
267   EXPECT_FALSE(out_params->params.scope.include_site);
268   EXPECT_TRUE(out_params->params.scope.specifications.empty());
269   EXPECT_THAT(
270       out_params->params.credentials,
271       ElementsAre(SessionParams::Credential(
272           "auth_cookie", "Domain=example.com; Path=/; Secure; SameSite=None")));
273 }
274 
TEST_F(RegistrationTest,NoSessionIdJson)275 TEST_F(RegistrationTest, NoSessionIdJson) {
276   constexpr char kTestingJson[] =
277       R"({
278   "credentials": [{
279     "type": "cookie",
280     "name": "auth_cookie",
281     "attributes": "Domain=example.com; Path=/; Secure; SameSite=None"
282   }]
283 })";
284   crypto::ScopedMockUnexportableKeyProvider scoped_mock_key_provider_;
285   server_.RegisterRequestHandler(
286       base::BindRepeating(&ReturnResponse, HTTP_OK, kTestingJson));
287   ASSERT_TRUE(server_.Start());
288 
289   TestRegistrationCallback callback;
290   RegistrationFetcher::StartCreateTokenAndFetch(
291       GetBasicParam(), unexportable_key_service(), context_.get(),
292       IsolationInfo::CreateTransient(), callback.callback());
293   callback.WaitForCall();
294   std::optional<RegistrationFetcher::RegistrationCompleteParams> out_params =
295       callback.outcome();
296   ASSERT_FALSE(out_params);
297 }
298 
TEST_F(RegistrationTest,SpecificationNotDictJson)299 TEST_F(RegistrationTest, SpecificationNotDictJson) {
300   constexpr char kTestingJson[] =
301       R"({
302   "session_identifier": "session_id",
303   "scope": {
304     "include_site": true,
305     "scope_specification" : [
306       "type", "domain", "path"
307     ]
308   },
309   "credentials": [{
310     "type": "cookie",
311     "name": "auth_cookie",
312     "attributes": "Domain=example.com; Path=/; Secure; SameSite=None"
313   }]
314 })";
315 
316   crypto::ScopedMockUnexportableKeyProvider scoped_mock_key_provider_;
317   server_.RegisterRequestHandler(
318       base::BindRepeating(&ReturnResponse, HTTP_OK, kTestingJson));
319   ASSERT_TRUE(server_.Start());
320 
321   TestRegistrationCallback callback;
322   RegistrationFetcher::StartCreateTokenAndFetch(
323       GetBasicParam(), unexportable_key_service(), context_.get(),
324       IsolationInfo::CreateTransient(), callback.callback());
325   callback.WaitForCall();
326   std::optional<RegistrationFetcher::RegistrationCompleteParams> out_params =
327       callback.outcome();
328   ASSERT_TRUE(out_params);
329   EXPECT_TRUE(out_params->params.scope.include_site);
330   EXPECT_TRUE(out_params->params.scope.specifications.empty());
331   EXPECT_THAT(
332       out_params->params.credentials,
333       ElementsAre(SessionParams::Credential(
334           "auth_cookie", "Domain=example.com; Path=/; Secure; SameSite=None")));
335 }
336 
TEST_F(RegistrationTest,OneMissingPath)337 TEST_F(RegistrationTest, OneMissingPath) {
338   constexpr char kTestingJson[] =
339       R"({
340   "session_identifier": "session_id",
341   "scope": {
342     "include_site": true,
343     "scope_specification" : [
344       {
345         "type": "include",
346         "domain": "trusted.example.com"
347       },
348       {
349         "type": "exclude",
350         "domain": "new.example.com",
351         "path": "/only_trusted_path"
352       }
353     ]
354   },
355   "credentials": [{
356     "type": "cookie",
357     "name": "other_cookie",
358     "attributes": "Domain=example.com; Path=/; Secure; SameSite=None"
359   }]
360 })";
361 
362   crypto::ScopedMockUnexportableKeyProvider scoped_mock_key_provider_;
363   server_.RegisterRequestHandler(
364       base::BindRepeating(&ReturnResponse, HTTP_OK, kTestingJson));
365   ASSERT_TRUE(server_.Start());
366 
367   TestRegistrationCallback callback;
368   RegistrationFetcher::StartCreateTokenAndFetch(
369       GetBasicParam(), unexportable_key_service(), context_.get(),
370       IsolationInfo::CreateTransient(), callback.callback());
371   callback.WaitForCall();
372   std::optional<RegistrationFetcher::RegistrationCompleteParams> out_params =
373       callback.outcome();
374   ASSERT_TRUE(out_params);
375   EXPECT_TRUE(out_params->params.scope.include_site);
376 
377   EXPECT_THAT(out_params->params.scope.specifications,
378               ElementsAre(SessionParams::Scope::Specification(
379                   SessionParams::Scope::Specification::Type::kExclude,
380                   "new.example.com", "/only_trusted_path")));
381 
382   EXPECT_THAT(out_params->params.credentials,
383               ElementsAre(SessionParams::Credential(
384                   "other_cookie",
385                   "Domain=example.com; Path=/; Secure; SameSite=None")));
386 }
387 
TEST_F(RegistrationTest,OneSpecTypeInvalid)388 TEST_F(RegistrationTest, OneSpecTypeInvalid) {
389   constexpr char kTestingJson[] =
390       R"({
391   "session_identifier": "session_id",
392   "scope": {
393     "include_site": true,
394     "scope_specification" : [
395       {
396         "type": "invalid",
397         "domain": "trusted.example.com",
398         "path": "/only_trusted_path"
399       },
400       {
401         "type": "exclude",
402         "domain": "new.example.com",
403         "path": "/only_trusted_path"
404       }
405     ]
406   },
407   "credentials": [{
408     "type": "cookie",
409     "name": "auth_cookie",
410     "attributes": "Domain=example.com; Path=/; Secure; SameSite=None"
411   }]
412 })";
413 
414   crypto::ScopedMockUnexportableKeyProvider scoped_mock_key_provider_;
415   server_.RegisterRequestHandler(
416       base::BindRepeating(&ReturnResponse, HTTP_OK, kTestingJson));
417   ASSERT_TRUE(server_.Start());
418 
419   TestRegistrationCallback callback;
420   RegistrationFetcher::StartCreateTokenAndFetch(
421       GetBasicParam(), unexportable_key_service(), context_.get(),
422       IsolationInfo::CreateTransient(), callback.callback());
423   callback.WaitForCall();
424   std::optional<RegistrationFetcher::RegistrationCompleteParams> out_params =
425       callback.outcome();
426   ASSERT_TRUE(out_params);
427   EXPECT_TRUE(out_params->params.scope.include_site);
428 
429   EXPECT_THAT(out_params->params.scope.specifications,
430               ElementsAre(SessionParams::Scope::Specification(
431                   SessionParams::Scope::Specification::Type::kExclude,
432                   "new.example.com", "/only_trusted_path")));
433 
434   EXPECT_THAT(
435       out_params->params.credentials,
436       ElementsAre(SessionParams::Credential(
437           "auth_cookie", "Domain=example.com; Path=/; Secure; SameSite=None")));
438 }
439 
TEST_F(RegistrationTest,InvalidTypeSpecList)440 TEST_F(RegistrationTest, InvalidTypeSpecList) {
441   constexpr char kTestingJson[] =
442       R"({
443   "session_identifier": "session_id",
444   "scope": {
445     "include_site": true,
446     "scope_specification" : "missing"
447   },
448   "credentials": [{
449     "type": "cookie",
450     "name": "auth_cookie",
451     "attributes": "Domain=example.com; Path=/; Secure; SameSite=None"
452   }]
453 })";
454 
455   crypto::ScopedMockUnexportableKeyProvider scoped_mock_key_provider_;
456   server_.RegisterRequestHandler(
457       base::BindRepeating(&ReturnResponse, HTTP_OK, kTestingJson));
458   ASSERT_TRUE(server_.Start());
459 
460   TestRegistrationCallback callback;
461   RegistrationFetcher::StartCreateTokenAndFetch(
462       GetBasicParam(), unexportable_key_service(), context_.get(),
463       IsolationInfo::CreateTransient(), callback.callback());
464   callback.WaitForCall();
465   std::optional<RegistrationFetcher::RegistrationCompleteParams> out_params =
466       callback.outcome();
467   ASSERT_TRUE(out_params);
468   EXPECT_TRUE(out_params->params.scope.include_site);
469   EXPECT_TRUE(out_params->params.scope.specifications.empty());
470 }
471 
TEST_F(RegistrationTest,TypeIsNotCookie)472 TEST_F(RegistrationTest, TypeIsNotCookie) {
473   constexpr char kTestingJson[] =
474       R"({
475   "session_identifier": "session_id",
476   "credentials": [{
477     "type": "sync auth",
478     "name": "auth_cookie",
479     "attributes": "Domain=example.com; Path=/; Secure; SameSite=None"
480   }]
481 })";
482 
483   crypto::ScopedMockUnexportableKeyProvider scoped_mock_key_provider_;
484   server_.RegisterRequestHandler(
485       base::BindRepeating(&ReturnResponse, HTTP_OK, kTestingJson));
486   ASSERT_TRUE(server_.Start());
487 
488   TestRegistrationCallback callback;
489   RegistrationFetcher::StartCreateTokenAndFetch(
490       GetBasicParam(), unexportable_key_service(), context_.get(),
491       IsolationInfo::CreateTransient(), callback.callback());
492   callback.WaitForCall();
493   std::optional<RegistrationFetcher::RegistrationCompleteParams> out_params =
494       callback.outcome();
495   EXPECT_EQ(callback.outcome(), std::nullopt);
496 }
497 
TEST_F(RegistrationTest,TwoTypesCookie_NotCookie)498 TEST_F(RegistrationTest, TwoTypesCookie_NotCookie) {
499   constexpr char kTestingJson[] =
500       R"({
501   "session_identifier": "session_id",
502   "credentials": [
503     {
504       "type": "cookie",
505       "name": "auth_cookie",
506       "attributes": "Domain=example.com; Path=/; Secure; SameSite=None"
507     },
508     {
509       "type": "sync auth",
510       "name": "auth_cookie",
511       "attributes": "Domain=example.com; Path=/; Secure; SameSite=None"
512     }
513   ]
514 })";
515 
516   crypto::ScopedMockUnexportableKeyProvider scoped_mock_key_provider_;
517   server_.RegisterRequestHandler(
518       base::BindRepeating(&ReturnResponse, HTTP_OK, kTestingJson));
519   ASSERT_TRUE(server_.Start());
520 
521   TestRegistrationCallback callback;
522   RegistrationFetcher::StartCreateTokenAndFetch(
523       GetBasicParam(), unexportable_key_service(), context_.get(),
524       IsolationInfo::CreateTransient(), callback.callback());
525   callback.WaitForCall();
526   std::optional<RegistrationFetcher::RegistrationCompleteParams> out_params =
527       callback.outcome();
528   ASSERT_TRUE(out_params);
529   EXPECT_THAT(
530       out_params->params.credentials,
531       ElementsAre(SessionParams::Credential(
532           "auth_cookie", "Domain=example.com; Path=/; Secure; SameSite=None")));
533 }
534 
TEST_F(RegistrationTest,TwoTypesNotCookie_Cookie)535 TEST_F(RegistrationTest, TwoTypesNotCookie_Cookie) {
536   constexpr char kTestingJson[] =
537       R"({
538   "session_identifier": "session_id",
539   "credentials": [
540     {
541       "type": "sync auth",
542       "name": "auth_cookie",
543       "attributes": "Domain=example.com; Path=/; Secure; SameSite=None"
544     },
545     {
546       "type": "cookie",
547       "name": "auth_cookie",
548       "attributes": "Domain=example.com; Path=/; Secure; SameSite=None"
549     }
550   ]
551 })";
552 
553   crypto::ScopedMockUnexportableKeyProvider scoped_mock_key_provider_;
554   server_.RegisterRequestHandler(
555       base::BindRepeating(&ReturnResponse, HTTP_OK, kTestingJson));
556   ASSERT_TRUE(server_.Start());
557 
558   TestRegistrationCallback callback;
559   RegistrationFetcher::StartCreateTokenAndFetch(
560       GetBasicParam(), unexportable_key_service(), context_.get(),
561       IsolationInfo::CreateTransient(), callback.callback());
562   callback.WaitForCall();
563   std::optional<RegistrationFetcher::RegistrationCompleteParams> out_params =
564       callback.outcome();
565   ASSERT_TRUE(out_params);
566   EXPECT_THAT(
567       out_params->params.credentials,
568       ElementsAre(SessionParams::Credential(
569           "auth_cookie", "Domain=example.com; Path=/; Secure; SameSite=None")));
570 }
571 
TEST_F(RegistrationTest,CredEntryWithoutDict)572 TEST_F(RegistrationTest, CredEntryWithoutDict) {
573   constexpr char kTestingJson[] =
574       R"({
575   "session_identifier": "session_id",
576   "credentials": [{
577     "type": "cookie",
578     "name": "auth_cookie",
579     "attributes": "Domain=example.com; Path=/; Secure; SameSite=None"
580   },
581   "test"]
582 })";
583 
584   crypto::ScopedMockUnexportableKeyProvider scoped_mock_key_provider_;
585   server_.RegisterRequestHandler(
586       base::BindRepeating(&ReturnResponse, HTTP_OK, kTestingJson));
587   ASSERT_TRUE(server_.Start());
588 
589   TestRegistrationCallback callback;
590   RegistrationFetcher::StartCreateTokenAndFetch(
591       GetBasicParam(), unexportable_key_service(), context_.get(),
592       IsolationInfo::CreateTransient(), callback.callback());
593   callback.WaitForCall();
594   std::optional<RegistrationFetcher::RegistrationCompleteParams> out_params =
595       callback.outcome();
596   ASSERT_TRUE(out_params);
597   EXPECT_THAT(
598       out_params->params.credentials,
599       ElementsAre(SessionParams::Credential(
600           "auth_cookie", "Domain=example.com; Path=/; Secure; SameSite=None")));
601 }
602 
TEST_F(RegistrationTest,ReturnTextFile)603 TEST_F(RegistrationTest, ReturnTextFile) {
604   crypto::ScopedMockUnexportableKeyProvider scoped_mock_key_provider_;
605   server_.RegisterRequestHandler(base::BindRepeating(&ReturnTextResponse));
606   ASSERT_TRUE(server_.Start());
607 
608   TestRegistrationCallback callback;
609   RegistrationFetcherParam params = GetBasicParam();
610   RegistrationFetcher::StartCreateTokenAndFetch(
611       std::move(params), unexportable_key_service(), context_.get(),
612       IsolationInfo::CreateTransient(), callback.callback());
613   callback.WaitForCall();
614   EXPECT_EQ(callback.outcome(), std::nullopt);
615 }
616 
TEST_F(RegistrationTest,ReturnInvalidJson)617 TEST_F(RegistrationTest, ReturnInvalidJson) {
618   std::string invalid_json = "*{}";
619   crypto::ScopedMockUnexportableKeyProvider scoped_mock_key_provider_;
620   server_.RegisterRequestHandler(
621       base::BindRepeating(&ReturnResponse, HTTP_OK, invalid_json));
622   ASSERT_TRUE(server_.Start());
623 
624   TestRegistrationCallback callback;
625   RegistrationFetcherParam params = GetBasicParam();
626   RegistrationFetcher::StartCreateTokenAndFetch(
627       std::move(params), unexportable_key_service(), context_.get(),
628       IsolationInfo::CreateTransient(), callback.callback());
629   callback.WaitForCall();
630   EXPECT_EQ(callback.outcome(), std::nullopt);
631 }
632 
TEST_F(RegistrationTest,ReturnEmptyJson)633 TEST_F(RegistrationTest, ReturnEmptyJson) {
634   std::string empty_json = "{}";
635   crypto::ScopedMockUnexportableKeyProvider scoped_mock_key_provider_;
636   server_.RegisterRequestHandler(
637       base::BindRepeating(&ReturnResponse, HTTP_OK, empty_json));
638   ASSERT_TRUE(server_.Start());
639 
640   TestRegistrationCallback callback;
641   RegistrationFetcherParam params = GetBasicParam();
642   RegistrationFetcher::StartCreateTokenAndFetch(
643       std::move(params), unexportable_key_service(), context_.get(),
644       IsolationInfo::CreateTransient(), callback.callback());
645   callback.WaitForCall();
646   EXPECT_EQ(callback.outcome(), std::nullopt);
647 }
648 
TEST_F(RegistrationTest,NetworkErrorServerShutdown)649 TEST_F(RegistrationTest, NetworkErrorServerShutdown) {
650   crypto::ScopedMockUnexportableKeyProvider scoped_mock_key_provider_;
651   ASSERT_TRUE(server_.Start());
652   GURL url = server_.GetURL("/");
653   ASSERT_TRUE(server_.ShutdownAndWaitUntilComplete());
654 
655   TestRegistrationCallback callback;
656   RegistrationFetcherParam params = GetBasicParam(url);
657   RegistrationFetcher::StartCreateTokenAndFetch(
658       std::move(params), unexportable_key_service(), context_.get(),
659       IsolationInfo::CreateTransient(), callback.callback());
660   callback.WaitForCall();
661 
662   EXPECT_EQ(callback.outcome(), std::nullopt);
663 }
664 
TEST_F(RegistrationTest,NetworkErrorInvalidResponse)665 TEST_F(RegistrationTest, NetworkErrorInvalidResponse) {
666   crypto::ScopedMockUnexportableKeyProvider scoped_mock_key_provider_;
667   server_.RegisterRequestHandler(base::BindRepeating(&ReturnInvalidResponse));
668   ASSERT_TRUE(server_.Start());
669 
670   TestRegistrationCallback callback;
671   RegistrationFetcherParam params = GetBasicParam();
672   RegistrationFetcher::StartCreateTokenAndFetch(
673       std::move(params), unexportable_key_service(), context_.get(),
674       IsolationInfo::CreateTransient(), callback.callback());
675   callback.WaitForCall();
676 
677   EXPECT_EQ(callback.outcome(), std::nullopt);
678 }
679 
TEST_F(RegistrationTest,ServerError500)680 TEST_F(RegistrationTest, ServerError500) {
681   crypto::ScopedMockUnexportableKeyProvider scoped_mock_key_provider_;
682   server_.RegisterRequestHandler(base::BindRepeating(
683       &ReturnResponse, HTTP_INTERNAL_SERVER_ERROR, kBasicValidJson));
684   ASSERT_TRUE(server_.Start());
685 
686   TestRegistrationCallback callback;
687   RegistrationFetcherParam params = GetBasicParam();
688   RegistrationFetcher::StartCreateTokenAndFetch(
689       std::move(params), unexportable_key_service(), context_.get(),
690       IsolationInfo::CreateTransient(), callback.callback());
691   callback.WaitForCall();
692 
693   EXPECT_EQ(callback.outcome(), std::nullopt);
694 }
695 
TEST_F(RegistrationTest,ServerErrorReturnOne401ThenSuccess)696 TEST_F(RegistrationTest, ServerErrorReturnOne401ThenSuccess) {
697   crypto::ScopedMockUnexportableKeyProvider scoped_mock_key_provider_;
698 
699   auto* container = new UnauthorizedThenSuccessResponseContainer(1);
700   server_.RegisterRequestHandler(
701       base::BindRepeating(&UnauthorizedThenSuccessResponseContainer::Return,
702                           base::Owned(container)));
703   ASSERT_TRUE(server_.Start());
704 
705   TestRegistrationCallback callback;
706   RegistrationFetcherParam params = GetBasicParam();
707   RegistrationFetcher::StartCreateTokenAndFetch(
708       std::move(params), unexportable_key_service(), context_.get(),
709       IsolationInfo::CreateTransient(), callback.callback());
710   callback.WaitForCall();
711 
712   std::optional<RegistrationFetcher::RegistrationCompleteParams> out_params =
713       callback.outcome();
714   ASSERT_TRUE(out_params);
715   EXPECT_TRUE(out_params->params.scope.include_site);
716   EXPECT_THAT(out_params->params.scope.specifications,
717               ElementsAre(SessionParams::Scope::Specification(
718                   SessionParams::Scope::Specification::Type::kInclude,
719                   "trusted.example.com", "/only_trusted_path")));
720   EXPECT_THAT(
721       out_params->params.credentials,
722       ElementsAre(SessionParams::Credential(
723           "auth_cookie", "Domain=example.com; Path=/; Secure; SameSite=None")));
724 }
725 
ReturnRedirect(const std::string & location,const test_server::HttpRequest & request)726 std::unique_ptr<test_server::HttpResponse> ReturnRedirect(
727     const std::string& location,
728     const test_server::HttpRequest& request) {
729   if (request.relative_url != "/") {
730     return nullptr;
731   }
732 
733   auto response = std::make_unique<test_server::BasicHttpResponse>();
734   response->set_code(HTTP_FOUND);
735   response->AddCustomHeader("Location", location);
736   response->set_content("Redirected");
737   response->set_content_type("text/plain");
738   return std::move(response);
739 }
740 
CheckRedirect(bool * redirect_followed_out,const test_server::HttpRequest & request)741 std::unique_ptr<test_server::HttpResponse> CheckRedirect(
742     bool* redirect_followed_out,
743     const test_server::HttpRequest& request) {
744   if (request.relative_url != kRedirectPath) {
745     return nullptr;
746   }
747 
748   *redirect_followed_out = true;
749   return ReturnResponse(HTTP_OK, kBasicValidJson, request);
750 }
751 
TEST_F(RegistrationTest,FollowHttpsRedirect)752 TEST_F(RegistrationTest, FollowHttpsRedirect) {
753   crypto::ScopedMockUnexportableKeyProvider scoped_mock_key_provider_;
754   bool followed = false;
755   server_.RegisterRequestHandler(
756       base::BindRepeating(&ReturnRedirect, kRedirectPath));
757   server_.RegisterRequestHandler(
758       base::BindRepeating(&CheckRedirect, &followed));
759   ASSERT_TRUE(server_.Start());
760 
761   TestRegistrationCallback callback;
762   RegistrationFetcherParam params = GetBasicParam();
763   RegistrationFetcher::StartCreateTokenAndFetch(
764       std::move(params), unexportable_key_service(), context_.get(),
765       IsolationInfo::CreateTransient(), callback.callback());
766   callback.WaitForCall();
767 
768   EXPECT_TRUE(followed);
769   EXPECT_NE(callback.outcome(), std::nullopt);
770 }
771 
TEST_F(RegistrationTest,DontFollowHttpRedirect)772 TEST_F(RegistrationTest, DontFollowHttpRedirect) {
773   crypto::ScopedMockUnexportableKeyProvider scoped_mock_key_provider_;
774   bool followed = false;
775   test_server::EmbeddedTestServer http_server_;
776   ASSERT_TRUE(http_server_.Start());
777   const GURL target = http_server_.GetURL(kRedirectPath);
778 
779   server_.RegisterRequestHandler(
780       base::BindRepeating(&ReturnRedirect, target.spec()));
781   server_.RegisterRequestHandler(
782       base::BindRepeating(&CheckRedirect, &followed));
783   ASSERT_TRUE(server_.Start());
784 
785   TestRegistrationCallback callback;
786   RegistrationFetcherParam params = GetBasicParam();
787   RegistrationFetcher::StartCreateTokenAndFetch(
788       std::move(params), unexportable_key_service(), context_.get(),
789       IsolationInfo::CreateTransient(), callback.callback());
790   callback.WaitForCall();
791 
792   EXPECT_FALSE(followed);
793   EXPECT_EQ(callback.outcome(), std::nullopt);
794 }
795 
TEST_F(RegistrationTest,FailOnSslErrorExpired)796 TEST_F(RegistrationTest, FailOnSslErrorExpired) {
797   crypto::ScopedMockUnexportableKeyProvider scoped_mock_key_provider_;
798   server_.RegisterRequestHandler(
799       base::BindRepeating(&ReturnResponse, HTTP_OK, kBasicValidJson));
800   server_.SetSSLConfig(net::EmbeddedTestServer::CERT_EXPIRED);
801   ASSERT_TRUE(server_.Start());
802 
803   TestRegistrationCallback callback;
804   RegistrationFetcherParam params = GetBasicParam();
805   RegistrationFetcher::StartCreateTokenAndFetch(
806       std::move(params), unexportable_key_service(), context_.get(),
807       IsolationInfo::CreateTransient(), callback.callback());
808 
809   callback.WaitForCall();
810   EXPECT_EQ(callback.outcome(), std::nullopt);
811 }
812 
ReturnResponseForRefreshRequest(const test_server::HttpRequest & request)813 std::unique_ptr<test_server::HttpResponse> ReturnResponseForRefreshRequest(
814     const test_server::HttpRequest& request) {
815   auto response = std::make_unique<test_server::BasicHttpResponse>();
816 
817   auto resp_iter = request.headers.find("Sec-Session-Response");
818   std::string session_response =
819       resp_iter != request.headers.end() ? resp_iter->second : "";
820   if (session_response.empty()) {
821     const auto session_iter = request.headers.find("Sec-Session-Id");
822     EXPECT_TRUE(session_iter != request.headers.end() &&
823                 !session_iter->second.empty());
824 
825     response->set_code(HTTP_UNAUTHORIZED);
826     response->AddCustomHeader("Sec-Session-Challenge",
827                               R"("test_challenge";id="session_id")");
828     return response;
829   }
830 
831   response->set_code(HTTP_OK);
832   response->set_content_type("application/json");
833   response->set_content(kBasicValidJson);
834   return response;
835 }
836 
TEST_F(RegistrationTest,BasicSuccessForExistingKey)837 TEST_F(RegistrationTest, BasicSuccessForExistingKey) {
838   crypto::ScopedMockUnexportableKeyProvider scoped_mock_key_provider_;
839   server_.RegisterRequestHandler(
840       base::BindRepeating(&ReturnResponse, HTTP_OK, kBasicValidJson));
841   ASSERT_TRUE(server_.Start());
842 
843   TestRegistrationCallback callback;
844   auto isolation_info = IsolationInfo::CreateTransient();
845   auto request_param = RegistrationRequestParam::CreateForTesting(
846       server_.base_url(), kSessionIdentifier, kChallenge);
847   CreateKeyAndRunCallback(base::BindOnce(
848       &RegistrationFetcher::StartFetchWithExistingKey, std::move(request_param),
849       std::ref(unexportable_key_service()), context_.get(),
850       std::ref(isolation_info), callback.callback()));
851 
852   callback.WaitForCall();
853   std::optional<RegistrationFetcher::RegistrationCompleteParams> out_params =
854       callback.outcome();
855   ASSERT_TRUE(out_params);
856   EXPECT_TRUE(out_params->params.scope.include_site);
857   EXPECT_THAT(out_params->params.scope.specifications,
858               ElementsAre(SessionParams::Scope::Specification(
859                   SessionParams::Scope::Specification::Type::kInclude,
860                   "trusted.example.com", "/only_trusted_path")));
861   EXPECT_THAT(
862       out_params->params.credentials,
863       ElementsAre(SessionParams::Credential(
864           "auth_cookie", "Domain=example.com; Path=/; Secure; SameSite=None")));
865 }
866 
TEST_F(RegistrationTest,FetchRegistrationWithCachedChallenge)867 TEST_F(RegistrationTest, FetchRegistrationWithCachedChallenge) {
868   crypto::ScopedMockUnexportableKeyProvider scoped_mock_key_provider_;
869   server_.RegisterRequestHandler(
870       base::BindRepeating(&ReturnResponseForRefreshRequest));
871   ASSERT_TRUE(server_.Start());
872 
873   TestRegistrationCallback callback;
874   auto request_param = RegistrationRequestParam::CreateForTesting(
875       server_.base_url(), kSessionIdentifier, kChallenge);
876   auto isolation_info = IsolationInfo::CreateTransient();
877   CreateKeyAndRunCallback(base::BindOnce(
878       &RegistrationFetcher::StartFetchWithExistingKey, std::move(request_param),
879       std::ref(unexportable_key_service()), context_.get(),
880       std::ref(isolation_info), callback.callback()));
881 
882   callback.WaitForCall();
883   std::optional<RegistrationFetcher::RegistrationCompleteParams> out_params =
884       callback.outcome();
885   ASSERT_TRUE(out_params);
886   EXPECT_TRUE(out_params->params.scope.include_site);
887   EXPECT_THAT(out_params->params.scope.specifications,
888               ElementsAre(SessionParams::Scope::Specification(
889                   SessionParams::Scope::Specification::Type::kInclude,
890                   "trusted.example.com", "/only_trusted_path")));
891   EXPECT_THAT(
892       out_params->params.credentials,
893       ElementsAre(SessionParams::Credential(
894           "auth_cookie", "Domain=example.com; Path=/; Secure; SameSite=None")));
895 }
896 
TEST_F(RegistrationTest,FetchRegitrationAndChallengeRequired)897 TEST_F(RegistrationTest, FetchRegitrationAndChallengeRequired) {
898   crypto::ScopedMockUnexportableKeyProvider scoped_mock_key_provider_;
899   server_.RegisterRequestHandler(
900       base::BindRepeating(&ReturnResponseForRefreshRequest));
901   ASSERT_TRUE(server_.Start());
902 
903   TestRegistrationCallback callback;
904   auto request_param = RegistrationRequestParam::CreateForTesting(
905       server_.base_url(), kSessionIdentifier, std::nullopt);
906   auto isolation_info = IsolationInfo::CreateTransient();
907   CreateKeyAndRunCallback(base::BindOnce(
908       &RegistrationFetcher::StartFetchWithExistingKey, std::move(request_param),
909       std::ref(unexportable_key_service()), context_.get(),
910       std::ref(isolation_info), callback.callback()));
911 
912   callback.WaitForCall();
913   std::optional<RegistrationFetcher::RegistrationCompleteParams> out_params =
914       callback.outcome();
915   ASSERT_TRUE(out_params);
916   EXPECT_TRUE(out_params->params.scope.include_site);
917   EXPECT_THAT(out_params->params.scope.specifications,
918               ElementsAre(SessionParams::Scope::Specification(
919                   SessionParams::Scope::Specification::Type::kInclude,
920                   "trusted.example.com", "/only_trusted_path")));
921   EXPECT_THAT(
922       out_params->params.credentials,
923       ElementsAre(SessionParams::Credential(
924           "auth_cookie", "Domain=example.com; Path=/; Secure; SameSite=None")));
925 }
926 
927 class RegistrationTokenHelperTest : public testing::Test {
928  public:
RegistrationTokenHelperTest()929   RegistrationTokenHelperTest() : unexportable_key_service_(task_manager_) {}
930 
unexportable_key_service()931   unexportable_keys::UnexportableKeyService& unexportable_key_service() {
932     return unexportable_key_service_;
933   }
934 
RunBackgroundTasks()935   void RunBackgroundTasks() { task_environment_.RunUntilIdle(); }
936 
937  private:
938   base::test::TaskEnvironment task_environment_{
939       base::test::TaskEnvironment::ThreadPoolExecutionMode::
940           QUEUED};  // QUEUED - tasks don't run until `RunUntilIdle()` is
941                     // called.
942   unexportable_keys::UnexportableKeyTaskManager task_manager_{
943       crypto::UnexportableKeyProvider::Config()};
944   unexportable_keys::UnexportableKeyServiceImpl unexportable_key_service_;
945 };
946 
TEST_F(RegistrationTokenHelperTest,CreateSuccess)947 TEST_F(RegistrationTokenHelperTest, CreateSuccess) {
948   crypto::ScopedMockUnexportableKeyProvider scoped_mock_key_provider_;
949   base::test::TestFuture<
950       std::optional<RegistrationFetcher::RegistrationTokenResult>>
951       future;
952   RegistrationFetcher::CreateTokenAsyncForTesting(
953       unexportable_key_service(), "test_challenge",
954       GURL("https://accounts.example.test.com/Register"),
955       /*authorization=*/std::nullopt, future.GetCallback());
956   RunBackgroundTasks();
957   ASSERT_TRUE(future.Get().has_value());
958 }
959 
TEST_F(RegistrationTokenHelperTest,CreateFail)960 TEST_F(RegistrationTokenHelperTest, CreateFail) {
961   crypto::ScopedNullUnexportableKeyProvider scoped_null_key_provider_;
962   base::test::TestFuture<
963       std::optional<RegistrationFetcher::RegistrationTokenResult>>
964       future;
965   RegistrationFetcher::CreateTokenAsyncForTesting(
966       unexportable_key_service(), "test_challenge",
967       GURL("https://https://accounts.example.test/Register"),
968       /*authorization=*/std::nullopt, future.GetCallback());
969   RunBackgroundTasks();
970   EXPECT_FALSE(future.Get().has_value());
971 }
972 
973 }  // namespace
974 
975 }  // namespace net::device_bound_sessions
976