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