1 // Copyright 2020 The Chromium Authors. All rights reserved.
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 "services/network/trust_tokens/trust_token_request_redemption_helper.h"
6
7 #include <memory>
8
9 #include "base/callback.h"
10 #include "base/no_destructor.h"
11 #include "base/test/task_environment.h"
12 #include "net/base/load_flags.h"
13 #include "net/base/request_priority.h"
14 #include "net/http/http_response_headers.h"
15 #include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
16 #include "net/url_request/url_request.h"
17 #include "net/url_request/url_request_test_util.h"
18 #include "services/network/public/cpp/trust_token_http_headers.h"
19 #include "services/network/public/cpp/trust_token_parameterization.h"
20 #include "services/network/public/mojom/url_response_head.mojom.h"
21 #include "services/network/trust_tokens/proto/public.pb.h"
22 #include "services/network/trust_tokens/test/trust_token_test_util.h"
23 #include "services/network/trust_tokens/trust_token_key_commitment_getter.h"
24 #include "services/network/trust_tokens/trust_token_parameterization.h"
25 #include "services/network/trust_tokens/trust_token_store.h"
26 #include "testing/gmock/include/gmock/gmock.h"
27 #include "testing/gtest/include/gtest/gtest.h"
28
29 namespace network {
30
31 namespace {
32
33 using TrustTokenRequestRedemptionHelperTest = TrustTokenRequestHelperTest;
34
35 using ::testing::_;
36 using ::testing::Optional;
37 using ::testing::Property;
38 using ::testing::Return;
39
40 // FixedKeyCommitmentGetter returns the provided commitment result when
41 // |Get| is called by the tested code.
42 class FixedKeyCommitmentGetter : public TrustTokenKeyCommitmentGetter {
43 public:
44 FixedKeyCommitmentGetter() = default;
FixedKeyCommitmentGetter(const url::Origin & issuer,mojom::TrustTokenKeyCommitmentResultPtr result)45 explicit FixedKeyCommitmentGetter(
46 const url::Origin& issuer,
47 mojom::TrustTokenKeyCommitmentResultPtr result)
48 : issuer_(issuer), result_(std::move(result)) {}
Get(const url::Origin & origin,base::OnceCallback<void (mojom::TrustTokenKeyCommitmentResultPtr)> on_done) const49 void Get(const url::Origin& origin,
50 base::OnceCallback<void(mojom::TrustTokenKeyCommitmentResultPtr)>
51 on_done) const override {
52 EXPECT_EQ(origin, issuer_);
53 std::move(on_done).Run(result_.Clone());
54 }
55
56 private:
57 url::Origin issuer_;
58 mojom::TrustTokenKeyCommitmentResultPtr result_;
59 };
60
61 base::NoDestructor<FixedKeyCommitmentGetter> g_fixed_key_commitment_getter{};
62
63 // MockCryptographer mocks out the cryptographic operations
64 // underlying Trust Tokens redemption.
65 class MockCryptographer
66 : public TrustTokenRequestRedemptionHelper::Cryptographer {
67 public:
68 MOCK_METHOD2(Initialize,
69 bool(mojom::TrustTokenProtocolVersion issuer_configured_version,
70 int issuer_configured_batch_size));
71
72 MOCK_METHOD3(
73 BeginRedemption,
74 base::Optional<std::string>(TrustToken token,
75 base::StringPiece verification_key,
76 const url::Origin& top_level_origin));
77
78 MOCK_METHOD1(ConfirmRedemption,
79 base::Optional<std::string>(base::StringPiece response_header));
80 };
81
82 class FakeKeyPairGenerator
83 : public TrustTokenRequestRedemptionHelper::KeyPairGenerator {
84 public:
Generate(std::string *,std::string *)85 bool Generate(std::string*, std::string*) override { return true; }
86 };
87
88 class FailingKeyPairGenerator
89 : public TrustTokenRequestRedemptionHelper::KeyPairGenerator {
90 public:
Generate(std::string *,std::string *)91 bool Generate(std::string*, std::string*) override { return false; }
92 };
93
94 class MockKeyPairGenerator
95 : public TrustTokenRequestRedemptionHelper::KeyPairGenerator {
96 public:
MockKeyPairGenerator(const std::string & signing,const std::string & verification)97 MockKeyPairGenerator(const std::string& signing,
98 const std::string& verification)
99 : signing_(signing), verification_(verification) {}
Generate(std::string * s,std::string * v)100 bool Generate(std::string* s, std::string* v) override {
101 s->swap(signing_);
102 v->swap(verification_);
103 return true;
104 }
105
106 private:
107 std::string signing_;
108 std::string verification_;
109 };
110
111 } // namespace
112
113 // Check that redemption fails if it would result in too many issuers being
114 // configured for the redemption top-level origin.
TEST_F(TrustTokenRequestRedemptionHelperTest,RejectsIfTooManyIssuers)115 TEST_F(TrustTokenRequestRedemptionHelperTest, RejectsIfTooManyIssuers) {
116 std::unique_ptr<TrustTokenStore> store = TrustTokenStore::CreateForTesting();
117
118 auto issuer = *SuitableTrustTokenOrigin::Create(GURL("https://issuer.com/"));
119 auto toplevel =
120 *SuitableTrustTokenOrigin::Create(GURL("https://toplevel.com/"));
121
122 // Associate the toplevel with the cap's worth of issuers different from
123 // |issuer|. (The cap is guaranteed to be quite small because of privacy
124 // requirements of the Trust Tokens protocol.)
125 for (int i = 0; i < kTrustTokenPerToplevelMaxNumberOfAssociatedIssuers; ++i) {
126 ASSERT_TRUE(store->SetAssociation(
127 *SuitableTrustTokenOrigin::Create(
128 GURL(base::StringPrintf("https://issuer%d.com/", i))),
129 toplevel));
130 }
131
132 TrustTokenRequestRedemptionHelper helper(
133 *SuitableTrustTokenOrigin::Create(GURL("https://toplevel.com/")),
134 mojom::TrustTokenRefreshPolicy::kUseCached, store.get(),
135 &*g_fixed_key_commitment_getter, std::make_unique<FakeKeyPairGenerator>(),
136 std::make_unique<MockCryptographer>());
137
138 auto request = MakeURLRequest("https://issuer.com/");
139 request->set_initiator(
140 *SuitableTrustTokenOrigin::Create(GURL("https://issuer.com/")));
141
142 mojom::TrustTokenOperationStatus result =
143 ExecuteBeginOperationAndWaitForResult(&helper, request.get());
144
145 EXPECT_EQ(result, mojom::TrustTokenOperationStatus::kResourceExhausted);
146 }
147
148 // Check that redemption fails if its key commitment request fails.
TEST_F(TrustTokenRequestRedemptionHelperTest,RejectsIfKeyCommitmentFails)149 TEST_F(TrustTokenRequestRedemptionHelperTest, RejectsIfKeyCommitmentFails) {
150 std::unique_ptr<TrustTokenStore> store = TrustTokenStore::CreateForTesting();
151
152 // Have the key commitment getter return nullptr, denoting that the key
153 // commitment fetch failed.
154 FixedKeyCommitmentGetter getter(
155 *SuitableTrustTokenOrigin::Create(GURL("https://issuer.com/")), nullptr);
156 TrustTokenRequestRedemptionHelper helper(
157 *SuitableTrustTokenOrigin::Create(GURL("https://toplevel.com/")),
158 mojom::TrustTokenRefreshPolicy::kUseCached, store.get(), &getter,
159 std::make_unique<FakeKeyPairGenerator>(),
160 std::make_unique<MockCryptographer>());
161
162 auto request = MakeURLRequest("https://issuer.com/");
163 request->set_initiator(
164 *SuitableTrustTokenOrigin::Create(GURL("https://issuer.com/")));
165
166 mojom::TrustTokenOperationStatus result =
167 ExecuteBeginOperationAndWaitForResult(&helper, request.get());
168
169 EXPECT_EQ(result, mojom::TrustTokenOperationStatus::kFailedPrecondition);
170 }
171
172 // Check that redemption fails with kResourceExhausted if there are no trust
173 // tokens stored for the (issuer, top-level origin) pair.
TEST_F(TrustTokenRequestRedemptionHelperTest,RejectsIfNoTokensToRedeem)174 TEST_F(TrustTokenRequestRedemptionHelperTest, RejectsIfNoTokensToRedeem) {
175 // Establish the following state:
176 // * Initialize an _empty_ trust token store.
177 // * Successfully return from the key commitment query.
178 std::unique_ptr<TrustTokenStore> store = TrustTokenStore::CreateForTesting();
179
180 auto getter = std::make_unique<FixedKeyCommitmentGetter>(
181 *SuitableTrustTokenOrigin::Create(GURL("https://issuer.com")),
182 mojom::TrustTokenKeyCommitmentResult::New());
183
184 TrustTokenRequestRedemptionHelper helper(
185 *SuitableTrustTokenOrigin::Create(GURL("https://toplevel.com/")),
186 mojom::TrustTokenRefreshPolicy::kUseCached, store.get(), &*getter,
187 std::make_unique<FakeKeyPairGenerator>(),
188 std::make_unique<MockCryptographer>());
189
190 auto request = MakeURLRequest("https://issuer.com/");
191 request->set_initiator(
192 *SuitableTrustTokenOrigin::Create(GURL("https://issuer.com/")));
193
194 mojom::TrustTokenOperationStatus result =
195 ExecuteBeginOperationAndWaitForResult(&helper, request.get());
196
197 EXPECT_EQ(result, mojom::TrustTokenOperationStatus::kResourceExhausted);
198 }
199
200 // Check that redemption fails with kInternalError if there's an error during
201 // initializing the cryptography delegate.
TEST_F(TrustTokenRequestRedemptionHelperTest,RejectsIfInitializingCryptographerFails)202 TEST_F(TrustTokenRequestRedemptionHelperTest,
203 RejectsIfInitializingCryptographerFails) {
204 // Establish the following state:
205 // * Initialize an _empty_ trust token store.
206 // * One key commitment returned from the key commitment registry, with one
207 // key, with body "".
208 // * One token stored corresponding to the key "" (this will be the token
209 // that the redemption request redeems; its key needs to match the key
210 // commitment's key so that it does not get evicted from storage after the key
211 // commitment is updated to reflect the key commitment result).
212 std::unique_ptr<TrustTokenStore> store = TrustTokenStore::CreateForTesting();
213 store->AddTokens(
214 *SuitableTrustTokenOrigin::Create(GURL("https://issuer.com/")),
215 std::vector<std::string>{"a token"},
216 /*key=*/"");
217
218 auto key_commitment_result = mojom::TrustTokenKeyCommitmentResult::New();
219 key_commitment_result->keys.push_back(
220 mojom::TrustTokenVerificationKey::New());
221 key_commitment_result->protocol_version =
222 mojom::TrustTokenProtocolVersion::kTrustTokenV2Pmb;
223 key_commitment_result->id = 1;
224 key_commitment_result->batch_size =
225 static_cast<int>(kMaximumTrustTokenIssuanceBatchSize);
226 auto getter = std::make_unique<FixedKeyCommitmentGetter>(
227 *SuitableTrustTokenOrigin::Create(GURL("https://issuer.com")),
228 std::move(key_commitment_result));
229
230 // Configure the cryptographer to fail to encode the redemption request.
231 auto cryptographer = std::make_unique<MockCryptographer>();
232 EXPECT_CALL(*cryptographer, Initialize(_, _)).WillOnce(Return(false));
233
234 TrustTokenRequestRedemptionHelper helper(
235 *SuitableTrustTokenOrigin::Create(GURL("https://toplevel.com/")),
236 mojom::TrustTokenRefreshPolicy::kUseCached, store.get(), &*getter,
237 std::make_unique<FakeKeyPairGenerator>(), std::move(cryptographer));
238
239 auto request = MakeURLRequest("https://issuer.com/");
240 request->set_initiator(
241 *SuitableTrustTokenOrigin::Create(GURL("https://issuer.com/")));
242
243 mojom::TrustTokenOperationStatus result =
244 ExecuteBeginOperationAndWaitForResult(&helper, request.get());
245
246 EXPECT_EQ(result, mojom::TrustTokenOperationStatus::kInternalError);
247 }
248
249 // Check that redemption fails with kInternalError if there's an error during
250 // encoding of the request header.
TEST_F(TrustTokenRequestRedemptionHelperTest,RejectsIfAddingRequestHeaderFails)251 TEST_F(TrustTokenRequestRedemptionHelperTest,
252 RejectsIfAddingRequestHeaderFails) {
253 // Establish the following state:
254 // * One key commitment returned from the key commitment registry, with one
255 // key, with body "".
256 // * One token stored corresponding to the key "" (this will be the token
257 // that the redemption request redeems; its key needs to match the key
258 // commitment's key so that it does not get evicted from storage after the key
259 // commitment is updated to reflect the key commitment result).
260 std::unique_ptr<TrustTokenStore> store = TrustTokenStore::CreateForTesting();
261 store->AddTokens(
262 *SuitableTrustTokenOrigin::Create(GURL("https://issuer.com/")),
263 std::vector<std::string>{"a token"},
264 /*key=*/"");
265
266 auto key_commitment_result = mojom::TrustTokenKeyCommitmentResult::New();
267 key_commitment_result->keys.push_back(
268 mojom::TrustTokenVerificationKey::New());
269 key_commitment_result->protocol_version =
270 mojom::TrustTokenProtocolVersion::kTrustTokenV2Pmb;
271 key_commitment_result->id = 1;
272 key_commitment_result->batch_size =
273 static_cast<int>(kMaximumTrustTokenIssuanceBatchSize);
274 auto getter = std::make_unique<FixedKeyCommitmentGetter>(
275 *SuitableTrustTokenOrigin::Create(GURL("https://issuer.com")),
276 std::move(key_commitment_result));
277
278 // Configure the cryptographer to fail to encode the redemption request.
279 auto cryptographer = std::make_unique<MockCryptographer>();
280 EXPECT_CALL(*cryptographer, Initialize(_, _)).WillOnce(Return(true));
281 EXPECT_CALL(*cryptographer, BeginRedemption(_, _, _))
282 .WillOnce(Return(base::nullopt));
283
284 TrustTokenRequestRedemptionHelper helper(
285 *SuitableTrustTokenOrigin::Create(GURL("https://toplevel.com/")),
286 mojom::TrustTokenRefreshPolicy::kUseCached, store.get(), &*getter,
287 std::make_unique<FakeKeyPairGenerator>(), std::move(cryptographer));
288
289 auto request = MakeURLRequest("https://issuer.com/");
290 request->set_initiator(
291 *SuitableTrustTokenOrigin::Create(GURL("https://issuer.com/")));
292
293 mojom::TrustTokenOperationStatus result =
294 ExecuteBeginOperationAndWaitForResult(&helper, request.get());
295
296 EXPECT_EQ(result, mojom::TrustTokenOperationStatus::kInternalError);
297 }
298
299 // Check that redemption fails with kInternalError if there's an error during
300 // generating the signing/validation key pair.
TEST_F(TrustTokenRequestRedemptionHelperTest,RejectsIfKeyPairGenerationFails)301 TEST_F(TrustTokenRequestRedemptionHelperTest, RejectsIfKeyPairGenerationFails) {
302 // Establish the following state:
303 // * One key commitment returned from the key commitment registry, with one
304 // key, with body "".
305 // * One token stored corresponding to the key "" (this will be the token
306 // that the redemption request redeems; its key needs to match the key
307 // commitment's key so that it does not get evicted from storage after the key
308 // commitment is updated to reflect the key commitment result).
309 std::unique_ptr<TrustTokenStore> store = TrustTokenStore::CreateForTesting();
310 store->AddTokens(
311 *SuitableTrustTokenOrigin::Create(GURL("https://issuer.com/")),
312 std::vector<std::string>{"a token"},
313 /*key=*/"");
314
315 auto key_commitment_result = mojom::TrustTokenKeyCommitmentResult::New();
316 key_commitment_result->keys.push_back(
317 mojom::TrustTokenVerificationKey::New());
318 key_commitment_result->protocol_version =
319 mojom::TrustTokenProtocolVersion::kTrustTokenV2Pmb;
320 key_commitment_result->id = 1;
321 key_commitment_result->batch_size =
322 static_cast<int>(kMaximumTrustTokenIssuanceBatchSize);
323 auto getter = std::make_unique<FixedKeyCommitmentGetter>(
324 *SuitableTrustTokenOrigin::Create(GURL("https://issuer.com")),
325 std::move(key_commitment_result));
326
327 // Provide |helper| a FailingKeyPairGenerator to ensure that key pair
328 // generation does not succeed.
329 TrustTokenRequestRedemptionHelper helper(
330 *SuitableTrustTokenOrigin::Create(GURL("https://toplevel.com/")),
331 mojom::TrustTokenRefreshPolicy::kUseCached, store.get(), &*getter,
332 std::make_unique<FailingKeyPairGenerator>(),
333 std::make_unique<MockCryptographer>());
334
335 auto request = MakeURLRequest("https://issuer.com/");
336 request->set_initiator(
337 *SuitableTrustTokenOrigin::Create(GURL("https://issuer.com/")));
338
339 mojom::TrustTokenOperationStatus result =
340 ExecuteBeginOperationAndWaitForResult(&helper, request.get());
341
342 // Since key pair generation failed, |Begin| should have failed and reported
343 // an internal error.
344 EXPECT_EQ(result, mojom::TrustTokenOperationStatus::kInternalError);
345 }
346
347 namespace {
348
349 class TrustTokenBeginRedemptionPostconditionsTest
350 : public TrustTokenRequestRedemptionHelperTest {
351 public:
SetUp()352 void SetUp() override {
353 // Establish the following state:
354 // * One key commitment returned from the key commitment registry, with one
355 // key, with body "".
356 // * One token stored corresponding to the key "" (this will be the token
357 // that the redemption request redeems; its key needs to match the key
358 // commitment's key so that it does not get evicted from storage after the
359 // key commitment is updated to reflect the key commitment result).
360 std::unique_ptr<TrustTokenStore> store =
361 TrustTokenStore::CreateForTesting();
362 store->AddTokens(
363 *SuitableTrustTokenOrigin::Create(GURL("https://issuer.com/")),
364 std::vector<std::string>{"a token"},
365 /*key=*/"");
366
367 auto key_commitment_result = mojom::TrustTokenKeyCommitmentResult::New();
368 key_commitment_result->keys.push_back(
369 mojom::TrustTokenVerificationKey::New());
370 key_commitment_result->protocol_version =
371 mojom::TrustTokenProtocolVersion::kTrustTokenV2Pmb;
372 key_commitment_result->id = 1;
373 key_commitment_result->batch_size =
374 static_cast<int>(kMaximumTrustTokenIssuanceBatchSize);
375 auto getter = std::make_unique<FixedKeyCommitmentGetter>(
376 *SuitableTrustTokenOrigin::Create(GURL("https://issuer.com")),
377 std::move(key_commitment_result));
378
379 // The value obtained from the cryptographer should be the exact
380 // Sec-Trust-Token header attached to the request.
381 auto cryptographer = std::make_unique<MockCryptographer>();
382 EXPECT_CALL(*cryptographer, Initialize(_, _)).WillOnce(Return(true));
383 EXPECT_CALL(*cryptographer, BeginRedemption(_, _, _))
384 .WillOnce(
385 Return(std::string("this string contains a redemption request")));
386
387 TrustTokenRequestRedemptionHelper helper(
388 *SuitableTrustTokenOrigin::Create(GURL("https://toplevel.com/")),
389 mojom::TrustTokenRefreshPolicy::kUseCached, store.get(), &*getter,
390 std::make_unique<FakeKeyPairGenerator>(), std::move(cryptographer));
391
392 request_ = MakeURLRequest("https://issuer.com/");
393 request_->set_initiator(
394 *SuitableTrustTokenOrigin::Create(GURL("https://issuer.com/")));
395
396 mojom::TrustTokenOperationStatus result =
397 ExecuteBeginOperationAndWaitForResult(&helper, request_.get());
398
399 EXPECT_EQ(result, mojom::TrustTokenOperationStatus::kOk);
400 }
401
402 protected:
403 std::unique_ptr<net::URLRequest> request_;
404 };
405
406 } // namespace
407
408 // Check that the redemption helper sets the Sec-Trust-Token and
409 // Sec-Trust-Token-Version headers on the outgoing request.
TEST_F(TrustTokenBeginRedemptionPostconditionsTest,SetsHeaders)410 TEST_F(TrustTokenBeginRedemptionPostconditionsTest, SetsHeaders) {
411 std::string attached_header;
412 EXPECT_TRUE(request_->extra_request_headers().GetHeader(
413 kTrustTokensSecTrustTokenHeader, &attached_header));
414 EXPECT_TRUE(request_->extra_request_headers().GetHeader(
415 kTrustTokensSecTrustTokenVersionHeader, &attached_header));
416 }
417
418 // Check that the redemption helper sets the LOAD_BYPASS_CACHE flag on the
419 // outgoing request.
TEST_F(TrustTokenBeginRedemptionPostconditionsTest,SetsLoadFlag)420 TEST_F(TrustTokenBeginRedemptionPostconditionsTest, SetsLoadFlag) {
421 EXPECT_TRUE(request_->load_flags() & net::LOAD_BYPASS_CACHE);
422 }
423
424 // Check that the redemption helper rejects responses lacking the
425 // Sec-Trust-Token response header.
TEST_F(TrustTokenRequestRedemptionHelperTest,RejectsIfResponseOmitsHeader)426 TEST_F(TrustTokenRequestRedemptionHelperTest, RejectsIfResponseOmitsHeader) {
427 // Establish the following state:
428 // * One key commitment returned from the key commitment registry, with one
429 // key, with body "".
430 // * One token stored corresponding to the key "" (this will be the token
431 // that the redemption request redeems; its key needs to match the key
432 // commitment's key so that it does not get evicted from storage after the key
433 // commitment is updated to reflect the key commitment result).
434 std::unique_ptr<TrustTokenStore> store = TrustTokenStore::CreateForTesting();
435 store->AddTokens(
436 *SuitableTrustTokenOrigin::Create(GURL("https://issuer.com/")),
437 std::vector<std::string>{"a token"},
438 /*key=*/"");
439
440 auto key_commitment_result = mojom::TrustTokenKeyCommitmentResult::New();
441 key_commitment_result->keys.push_back(
442 mojom::TrustTokenVerificationKey::New());
443 key_commitment_result->protocol_version =
444 mojom::TrustTokenProtocolVersion::kTrustTokenV2Pmb;
445 key_commitment_result->id = 1;
446 key_commitment_result->batch_size =
447 static_cast<int>(kMaximumTrustTokenIssuanceBatchSize);
448 auto getter = std::make_unique<FixedKeyCommitmentGetter>(
449 *SuitableTrustTokenOrigin::Create(GURL("https://issuer.com")),
450 std::move(key_commitment_result));
451
452 auto cryptographer = std::make_unique<MockCryptographer>();
453 EXPECT_CALL(*cryptographer, Initialize(_, _)).WillOnce(Return(true));
454 EXPECT_CALL(*cryptographer, BeginRedemption(_, _, _))
455 .WillOnce(
456 Return(std::string("this string contains a redemption request")));
457
458 TrustTokenRequestRedemptionHelper helper(
459 *SuitableTrustTokenOrigin::Create(GURL("https://toplevel.com/")),
460 mojom::TrustTokenRefreshPolicy::kUseCached, store.get(), &*getter,
461 std::make_unique<FakeKeyPairGenerator>(), std::move(cryptographer));
462
463 auto request = MakeURLRequest("https://issuer.com/");
464 request->set_initiator(
465 *SuitableTrustTokenOrigin::Create(GURL("https://issuer.com/")));
466
467 mojom::TrustTokenOperationStatus result =
468 ExecuteBeginOperationAndWaitForResult(&helper, request.get());
469
470 // Verify that the first half of the redemption operation succeeded, as a
471 // precursor to testing that the second half works, too.
472 ASSERT_EQ(result, mojom::TrustTokenOperationStatus::kOk);
473
474 // Add an empty list of response headers. In particular, this is missing the
475 // Sec-Trust-Token redemption response header.
476 auto response_head = mojom::URLResponseHead::New();
477 response_head->headers =
478 net::HttpResponseHeaders::TryToCreate("HTTP/1.1 200 OK\r\n");
479
480 // As a consequence, |Finalize| should fail.
481 EXPECT_EQ(ExecuteFinalizeAndWaitForResult(&helper, response_head.get()),
482 mojom::TrustTokenOperationStatus::kBadResponse);
483 }
484
485 // Check that the redemption helper handles a redemption response rejected by
486 // the underlying cryptographic library.
TEST_F(TrustTokenRequestRedemptionHelperTest,RejectsIfResponseIsUnusable)487 TEST_F(TrustTokenRequestRedemptionHelperTest, RejectsIfResponseIsUnusable) {
488 // Establish the following state:
489 // * One key commitment returned from the key commitment registry, with one
490 // key, with body "".
491 // * One token stored corresponding to the key "" (this will be the token
492 // that the redemption request redeems; its key needs to match the key
493 // commitment's key so that it does not get evicted from storage after the key
494 // commitment is updated to reflect the key commitment result).
495 std::unique_ptr<TrustTokenStore> store = TrustTokenStore::CreateForTesting();
496 store->AddTokens(
497 *SuitableTrustTokenOrigin::Create(GURL("https://issuer.com/")),
498 std::vector<std::string>{"a token"},
499 /*key=*/"");
500
501 auto key_commitment_result = mojom::TrustTokenKeyCommitmentResult::New();
502 key_commitment_result->keys.push_back(
503 mojom::TrustTokenVerificationKey::New());
504 key_commitment_result->protocol_version =
505 mojom::TrustTokenProtocolVersion::kTrustTokenV2Pmb;
506 key_commitment_result->id = 1;
507 key_commitment_result->batch_size =
508 static_cast<int>(kMaximumTrustTokenIssuanceBatchSize);
509 auto getter = std::make_unique<FixedKeyCommitmentGetter>(
510 *SuitableTrustTokenOrigin::Create(GURL("https://issuer.com")),
511 std::move(key_commitment_result));
512
513 // Configure the cryptographer to reject the response header by returning
514 // nullopt on ConfirmRedemption.
515 auto cryptographer = std::make_unique<MockCryptographer>();
516 EXPECT_CALL(*cryptographer, Initialize(_, _)).WillOnce(Return(true));
517 EXPECT_CALL(*cryptographer, BeginRedemption(_, _, _))
518 .WillOnce(
519 Return(std::string("this string contains a redemption request")));
520 EXPECT_CALL(*cryptographer, ConfirmRedemption(_))
521 .WillOnce(Return(base::nullopt));
522
523 TrustTokenRequestRedemptionHelper helper(
524 *SuitableTrustTokenOrigin::Create(GURL("https://toplevel.com/")),
525 mojom::TrustTokenRefreshPolicy::kUseCached, store.get(), &*getter,
526 std::make_unique<FakeKeyPairGenerator>(), std::move(cryptographer));
527
528 auto request = MakeURLRequest("https://issuer.com/");
529 request->set_initiator(
530 *SuitableTrustTokenOrigin::Create(GURL("https://issuer.com/")));
531
532 mojom::TrustTokenOperationStatus result =
533 ExecuteBeginOperationAndWaitForResult(&helper, request.get());
534
535 // Verify that the first half of the redemption operation succeeded, as a
536 // precursor to testing that the second half works, too.
537 ASSERT_EQ(result, mojom::TrustTokenOperationStatus::kOk);
538
539 auto response_head = mojom::URLResponseHead::New();
540 response_head->headers =
541 net::HttpResponseHeaders::TryToCreate("HTTP/1.1 200 OK\r\n");
542 response_head->headers->SetHeader(kTrustTokensSecTrustTokenHeader, "");
543
544 // Since the cryptographer rejected the response header by returning nullopt
545 // on ConfirmRedemption, expect to fail with kBadResponse.
546 EXPECT_EQ(ExecuteFinalizeAndWaitForResult(&helper, response_head.get()),
547 mojom::TrustTokenOperationStatus::kBadResponse);
548
549 // Processing the response should have stripped the header.
550 EXPECT_FALSE(
551 response_head->headers->HasHeader(kTrustTokensSecTrustTokenHeader));
552 }
553
554 // Check that, when preconditions are met and the underlying cryptographic steps
555 // successfully complete, the begin/finalize methods succeed.
TEST_F(TrustTokenRequestRedemptionHelperTest,Success)556 TEST_F(TrustTokenRequestRedemptionHelperTest, Success) {
557 // Establish the following state:
558 // * One key commitment returned from the key commitment registry, with one
559 // key, with body "".
560 // * One token stored corresponding to the key "" (this will be the token
561 // that the redemption request redeems; its key needs to match the key
562 // commitment's key so that it does not get evicted from storage after the key
563 // commitment is updated to reflect the key commitment result).
564 std::unique_ptr<TrustTokenStore> store = TrustTokenStore::CreateForTesting();
565 store->AddTokens(
566 *SuitableTrustTokenOrigin::Create(GURL("https://issuer.com/")),
567 std::vector<std::string>{"a token"},
568 /*key=*/"");
569
570 auto key_commitment_result = mojom::TrustTokenKeyCommitmentResult::New();
571 key_commitment_result->keys.push_back(
572 mojom::TrustTokenVerificationKey::New());
573 key_commitment_result->protocol_version =
574 mojom::TrustTokenProtocolVersion::kTrustTokenV2Pmb;
575 key_commitment_result->id = 1;
576 key_commitment_result->batch_size =
577 static_cast<int>(kMaximumTrustTokenIssuanceBatchSize);
578 auto getter = std::make_unique<FixedKeyCommitmentGetter>(
579 *SuitableTrustTokenOrigin::Create(GURL("https://issuer.com")),
580 std::move(key_commitment_result));
581
582 // Configure the cryptographer to succeed on both the outbound and inbound
583 // halves of the operation.
584 auto cryptographer = std::make_unique<MockCryptographer>();
585 EXPECT_CALL(*cryptographer, Initialize(_, _)).WillOnce(Return(true));
586 EXPECT_CALL(*cryptographer, BeginRedemption(_, _, _))
587 .WillOnce(Return("well-formed redemption request"));
588 EXPECT_CALL(*cryptographer, ConfirmRedemption(_))
589 .WillOnce(Return("a successfully-extracted RR"));
590
591 TrustTokenRequestRedemptionHelper helper(
592 *SuitableTrustTokenOrigin::Create(GURL("https://toplevel.com/")),
593 mojom::TrustTokenRefreshPolicy::kUseCached, store.get(), &*getter,
594 std::make_unique<FakeKeyPairGenerator>(), std::move(cryptographer));
595
596 auto request = MakeURLRequest("https://issuer.com/");
597 request->set_initiator(
598 *SuitableTrustTokenOrigin::Create(GURL("https://issuer.com/")));
599
600 mojom::TrustTokenOperationStatus result =
601 ExecuteBeginOperationAndWaitForResult(&helper, request.get());
602
603 // Since this test is testing the behavior on handling the response after
604 // successfully constructing a redemption request, sanity check that the setup
605 // has correctly caused constructing the request so succeed.
606 ASSERT_EQ(result, mojom::TrustTokenOperationStatus::kOk);
607
608 auto response_head = mojom::URLResponseHead::New();
609 response_head->headers =
610 net::HttpResponseHeaders::TryToCreate("HTTP/1.1 200 OK\r\n");
611 response_head->headers->SetHeader(kTrustTokensSecTrustTokenHeader, "");
612
613 // After a successfully constructed request, when the response is well-formed
614 // and the delegate accepts the response, Finalize should succeed.
615 EXPECT_EQ(ExecuteFinalizeAndWaitForResult(&helper, response_head.get()),
616 mojom::TrustTokenOperationStatus::kOk);
617
618 // Processing the response should have stripped the header.
619 EXPECT_FALSE(
620 response_head->headers->HasHeader(kTrustTokensSecTrustTokenHeader));
621 }
622
623 // Check that a successful Begin call associates the issuer with the redemption
624 // toplevel origin.
TEST_F(TrustTokenRequestRedemptionHelperTest,AssociatesIssuerWithToplevel)625 TEST_F(TrustTokenRequestRedemptionHelperTest, AssociatesIssuerWithToplevel) {
626 // Establish the following state:
627 // * One key commitment returned from the key commitment registry, with one
628 // key, with body "".
629 // * One token stored corresponding to the key "" (this will be the token
630 // that the redemption request redeems; its key needs to match the key
631 // commitment's key so that it does not get evicted from storage after the key
632 // commitment is updated to reflect the key commitment result).
633 std::unique_ptr<TrustTokenStore> store = TrustTokenStore::CreateForTesting();
634 store->AddTokens(
635 *SuitableTrustTokenOrigin::Create(GURL("https://issuer.com/")),
636 std::vector<std::string>{"a token"},
637 /*key=*/"");
638
639 auto key_commitment_result = mojom::TrustTokenKeyCommitmentResult::New();
640 key_commitment_result->keys.push_back(
641 mojom::TrustTokenVerificationKey::New());
642 key_commitment_result->protocol_version =
643 mojom::TrustTokenProtocolVersion::kTrustTokenV2Pmb;
644 key_commitment_result->id = 1;
645 key_commitment_result->batch_size =
646 static_cast<int>(kMaximumTrustTokenIssuanceBatchSize);
647 auto getter = std::make_unique<FixedKeyCommitmentGetter>(
648 *SuitableTrustTokenOrigin::Create(GURL("https://issuer.com")),
649 std::move(key_commitment_result));
650
651 // Configure the cryptographer to succeed on both the outbound and inbound
652 // halves of the operation.
653 auto cryptographer = std::make_unique<MockCryptographer>();
654 EXPECT_CALL(*cryptographer, Initialize(_, _)).WillOnce(Return(true));
655 EXPECT_CALL(*cryptographer, BeginRedemption(_, _, _))
656 .WillOnce(Return("well-formed redemption request"));
657
658 TrustTokenRequestRedemptionHelper helper(
659 *SuitableTrustTokenOrigin::Create(GURL("https://toplevel.com/")),
660 mojom::TrustTokenRefreshPolicy::kUseCached, store.get(), &*getter,
661 std::make_unique<FakeKeyPairGenerator>(), std::move(cryptographer));
662
663 auto request = MakeURLRequest("https://issuer.com/");
664 request->set_initiator(
665 *SuitableTrustTokenOrigin::Create(GURL("https://issuer.com/")));
666
667 mojom::TrustTokenOperationStatus result =
668 ExecuteBeginOperationAndWaitForResult(&helper, request.get());
669
670 // Since this test is testing the behavior on handling the response after
671 // successfully constructing a redemption request, sanity check that the setup
672 // has correctly caused constructing the request so succeed.
673 ASSERT_EQ(result, mojom::TrustTokenOperationStatus::kOk);
674
675 // After the operation has successfully begun, the issuer and the toplevel
676 // should be associated.
677 EXPECT_TRUE(store->IsAssociated(
678 *SuitableTrustTokenOrigin::Create(GURL("https://issuer.com/")),
679 *SuitableTrustTokenOrigin::Create(GURL("https://toplevel.com/"))));
680 }
681
682 // Check that a successful end-to-end Begin/Finalize flow stores the obtained
683 // redemption record (and associated key pair) in the trust token store.
TEST_F(TrustTokenRequestRedemptionHelperTest,StoresObtainedRedemptionRecord)684 TEST_F(TrustTokenRequestRedemptionHelperTest, StoresObtainedRedemptionRecord) {
685 // Establish the following state:
686 // * One key commitment returned from the key commitment registry, with one
687 // key, with body "".
688 // * One token stored corresponding to the key "" (this will be the token
689 // that the redemption request redeems; its key needs to match the key
690 // commitment's key so that it does not get evicted from storage after the key
691 // commitment is updated to reflect the key commitment result).
692 std::unique_ptr<TrustTokenStore> store = TrustTokenStore::CreateForTesting();
693 store->AddTokens(
694 *SuitableTrustTokenOrigin::Create(GURL("https://issuer.com/")),
695 std::vector<std::string>{"a token"},
696 /*key=*/"token verification key");
697
698 auto key_commitment_result = mojom::TrustTokenKeyCommitmentResult::New();
699 key_commitment_result->keys.push_back(mojom::TrustTokenVerificationKey::New(
700 "token verification key", /*expiry=*/base::Time::Max()));
701 key_commitment_result->protocol_version =
702 mojom::TrustTokenProtocolVersion::kTrustTokenV2Pmb;
703 key_commitment_result->id = 1;
704 key_commitment_result->batch_size =
705 static_cast<int>(kMaximumTrustTokenIssuanceBatchSize);
706 auto getter = std::make_unique<FixedKeyCommitmentGetter>(
707 *SuitableTrustTokenOrigin::Create(GURL("https://issuer.com")),
708 std::move(key_commitment_result));
709
710 auto cryptographer = std::make_unique<MockCryptographer>();
711 EXPECT_CALL(*cryptographer, Initialize(_, _)).WillOnce(Return(true));
712 EXPECT_CALL(*cryptographer, BeginRedemption(_, _, _))
713 .WillOnce(Return("well-formed redemption request"));
714 EXPECT_CALL(*cryptographer, ConfirmRedemption(_))
715 .WillOnce(Return("a successfully-extracted RR"));
716
717 TrustTokenRequestRedemptionHelper helper(
718 *SuitableTrustTokenOrigin::Create(GURL("https://toplevel.com/")),
719 mojom::TrustTokenRefreshPolicy::kUseCached, store.get(), &*getter,
720 std::make_unique<MockKeyPairGenerator>("signing key", "verification key"),
721 std::move(cryptographer));
722
723 auto request = MakeURLRequest("https://issuer.com/");
724 request->set_initiator(
725 *SuitableTrustTokenOrigin::Create(GURL("https://issuer.com/")));
726 mojom::TrustTokenOperationStatus result =
727 ExecuteBeginOperationAndWaitForResult(&helper, request.get());
728 EXPECT_EQ(result, mojom::TrustTokenOperationStatus::kOk);
729
730 auto response_head = mojom::URLResponseHead::New();
731 response_head->headers =
732 net::HttpResponseHeaders::TryToCreate("HTTP/1.1 200 OK\r\n");
733 response_head->headers->SetHeader(kTrustTokensSecTrustTokenHeader, "");
734 EXPECT_EQ(ExecuteFinalizeAndWaitForResult(&helper, response_head.get()),
735 mojom::TrustTokenOperationStatus::kOk);
736
737 // After the operation has successfully finished, the RR parsed from the
738 // server response should be in the store.
739 EXPECT_THAT(
740 store->RetrieveNonstaleRedemptionRecord(
741 *SuitableTrustTokenOrigin::Create(GURL("https://issuer.com/")),
742 *SuitableTrustTokenOrigin::Create(GURL("https://toplevel.com/"))),
743 Optional(AllOf(
744 Property(&TrustTokenRedemptionRecord::body,
745 "a successfully-extracted RR"),
746 Property(&TrustTokenRedemptionRecord::public_key, "verification key"),
747 Property(&TrustTokenRedemptionRecord::token_verification_key,
748 "token verification key"),
749 Property(&TrustTokenRedemptionRecord::signing_key, "signing key"))));
750 }
751
752 // Check that a "refresh" refresh mode is rejected unless the request's
753 // initiating origin is the issuer origin.
TEST_F(TrustTokenRequestRedemptionHelperTest,RejectsRefreshFromNonissuerOrigin)754 TEST_F(TrustTokenRequestRedemptionHelperTest,
755 RejectsRefreshFromNonissuerOrigin) {
756 std::unique_ptr<TrustTokenStore> store = TrustTokenStore::CreateForTesting();
757
758 TrustTokenRequestRedemptionHelper helper(
759 *SuitableTrustTokenOrigin::Create(GURL("https://toplevel.com/")),
760 mojom::TrustTokenRefreshPolicy::kRefresh, store.get(),
761 &*g_fixed_key_commitment_getter, std::make_unique<FakeKeyPairGenerator>(),
762 std::make_unique<MockCryptographer>());
763
764 // kRefresh should mean that redemption fails on requests with
765 // non-issuer initiators.
766 auto request = MakeURLRequest("https://issuer.com/");
767 request->set_initiator(
768 *SuitableTrustTokenOrigin::Create(GURL("https://not-issuer.com/")));
769
770 mojom::TrustTokenOperationStatus result =
771 ExecuteBeginOperationAndWaitForResult(&helper, request.get());
772
773 EXPECT_EQ(result, mojom::TrustTokenOperationStatus::kFailedPrecondition);
774 }
775
776 // On a redemption operation parameterized by kUseCachedRr, if there's an RR
777 // present in the store for the given issuer-toplevel pair, the request should
778 // return early with kAlreadyExists.
TEST_F(TrustTokenRequestRedemptionHelperTest,RedemptionRecordCacheHit)779 TEST_F(TrustTokenRequestRedemptionHelperTest, RedemptionRecordCacheHit) {
780 std::unique_ptr<TrustTokenStore> store = TrustTokenStore::CreateForTesting();
781 store->SetRedemptionRecord(
782 *SuitableTrustTokenOrigin::Create(GURL("https://issuer.com")),
783 *SuitableTrustTokenOrigin::Create(GURL("https://toplevel.com")),
784 TrustTokenRedemptionRecord());
785
786 TrustTokenRequestRedemptionHelper helper(
787 *SuitableTrustTokenOrigin::Create(GURL("https://toplevel.com/")),
788 mojom::TrustTokenRefreshPolicy::kUseCached, store.get(),
789 &*g_fixed_key_commitment_getter, std::make_unique<FakeKeyPairGenerator>(),
790 std::make_unique<MockCryptographer>());
791
792 auto request = MakeURLRequest("https://issuer.com/");
793 request->set_initiator(
794 *SuitableTrustTokenOrigin::Create(GURL("https://issuer.com/")));
795
796 mojom::TrustTokenOperationStatus result =
797 ExecuteBeginOperationAndWaitForResult(&helper, request.get());
798
799 EXPECT_EQ(result, mojom::TrustTokenOperationStatus::kAlreadyExists);
800 }
801
802 // Check that a successful end-to-end Begin/Finalize flow with kRefresh
803 // overwrites the previously stored redemption record (and associated key pair)
804 // in the trust token store.
TEST_F(TrustTokenRequestRedemptionHelperTest,SuccessUsingRefreshRrOverwritesStoredRr)805 TEST_F(TrustTokenRequestRedemptionHelperTest,
806 SuccessUsingRefreshRrOverwritesStoredRr) {
807 // Establish the following state:
808 // * A redemption record is already stored for the issuer, toplevel pair at
809 // hand.
810 // * One key commitment returned from the key commitment registry, with one
811 // key, with body "".
812 // * One token stored corresponding to the key "" (this will be the token
813 // that the redemption request redeems; its key needs to match the key
814 // commitment's key so that it does not get evicted from storage after the key
815 // commitment is updated to reflect the key commitment result).
816 std::unique_ptr<TrustTokenStore> store = TrustTokenStore::CreateForTesting();
817 store->SetRedemptionRecord(
818 *SuitableTrustTokenOrigin::Create(GURL("https://issuer.com")),
819 *SuitableTrustTokenOrigin::Create(GURL("https://toplevel.com")),
820 TrustTokenRedemptionRecord());
821 store->AddTokens(
822 *SuitableTrustTokenOrigin::Create(GURL("https://issuer.com/")),
823 std::vector<std::string>{"a token"},
824 /*key=*/"");
825
826 auto key_commitment_result = mojom::TrustTokenKeyCommitmentResult::New();
827 key_commitment_result->keys.push_back(
828 mojom::TrustTokenVerificationKey::New());
829 key_commitment_result->protocol_version =
830 mojom::TrustTokenProtocolVersion::kTrustTokenV2Pmb;
831 key_commitment_result->id = 1;
832 key_commitment_result->batch_size =
833 static_cast<int>(kMaximumTrustTokenIssuanceBatchSize);
834 auto getter = std::make_unique<FixedKeyCommitmentGetter>(
835 *SuitableTrustTokenOrigin::Create(GURL("https://issuer.com")),
836 std::move(key_commitment_result));
837
838 auto cryptographer = std::make_unique<MockCryptographer>();
839 EXPECT_CALL(*cryptographer, Initialize(_, _)).WillOnce(Return(true));
840 EXPECT_CALL(*cryptographer, BeginRedemption(_, _, _))
841 .WillOnce(Return("well-formed redemption request"));
842 EXPECT_CALL(*cryptographer, ConfirmRedemption(_))
843 .WillOnce(Return("a successfully-extracted RR"));
844
845 TrustTokenRequestRedemptionHelper helper(
846 *SuitableTrustTokenOrigin::Create(GURL("https://toplevel.com/")),
847 mojom::TrustTokenRefreshPolicy::kRefresh, store.get(), &*getter,
848 std::make_unique<MockKeyPairGenerator>("signing key", "verification key"),
849 std::move(cryptographer));
850
851 auto request = MakeURLRequest("https://issuer.com/");
852 request->set_initiator(
853 *SuitableTrustTokenOrigin::Create(GURL("https://issuer.com/")));
854
855 // Set the initiator in order to be able to use refresh mode
856 // kRefresh.
857 request->set_initiator(
858 *SuitableTrustTokenOrigin::Create(GURL("https://issuer.com/")));
859
860 mojom::TrustTokenOperationStatus result =
861 ExecuteBeginOperationAndWaitForResult(&helper, request.get());
862 EXPECT_EQ(result, mojom::TrustTokenOperationStatus::kOk);
863
864 auto response_head = mojom::URLResponseHead::New();
865 response_head->headers =
866 net::HttpResponseHeaders::TryToCreate("HTTP/1.1 200 OK\r\n");
867 response_head->headers->SetHeader(kTrustTokensSecTrustTokenHeader, "");
868 EXPECT_EQ(ExecuteFinalizeAndWaitForResult(&helper, response_head.get()),
869 mojom::TrustTokenOperationStatus::kOk);
870
871 // After the operation has successfully finished, the RR parsed from the
872 // server response should be in the store.
873 EXPECT_THAT(
874 store->RetrieveNonstaleRedemptionRecord(
875 *SuitableTrustTokenOrigin::Create(GURL("https://issuer.com/")),
876 *SuitableTrustTokenOrigin::Create(GURL("https://toplevel.com/"))),
877 Optional(AllOf(
878 Property(&TrustTokenRedemptionRecord::body,
879 "a successfully-extracted RR"),
880 Property(&TrustTokenRedemptionRecord::public_key, "verification key"),
881 Property(&TrustTokenRedemptionRecord::signing_key, "signing key"))));
882 }
883
TEST_F(TrustTokenRequestRedemptionHelperTest,RejectsUnsuitableInsecureIssuer)884 TEST_F(TrustTokenRequestRedemptionHelperTest, RejectsUnsuitableInsecureIssuer) {
885 auto store = TrustTokenStore::CreateForTesting();
886 TrustTokenRequestRedemptionHelper helper(
887 *SuitableTrustTokenOrigin::Create(GURL("https://toplevel.com/")),
888 mojom::TrustTokenRefreshPolicy::kUseCached, store.get(),
889 &*g_fixed_key_commitment_getter, std::make_unique<FakeKeyPairGenerator>(),
890 std::make_unique<MockCryptographer>());
891
892 auto request = MakeURLRequest("http://insecure-issuer.com/");
893
894 EXPECT_EQ(ExecuteBeginOperationAndWaitForResult(&helper, request.get()),
895 mojom::TrustTokenOperationStatus::kInvalidArgument);
896 }
897
TEST_F(TrustTokenRequestRedemptionHelperTest,RejectsUnsuitableNonHttpNonHttpsIssuer)898 TEST_F(TrustTokenRequestRedemptionHelperTest,
899 RejectsUnsuitableNonHttpNonHttpsIssuer) {
900 auto store = TrustTokenStore::CreateForTesting();
901 TrustTokenRequestRedemptionHelper helper(
902 *SuitableTrustTokenOrigin::Create(GURL("https://toplevel.com/")),
903 mojom::TrustTokenRefreshPolicy::kUseCached, store.get(),
904 &*g_fixed_key_commitment_getter, std::make_unique<FakeKeyPairGenerator>(),
905 std::make_unique<MockCryptographer>());
906
907 auto request = MakeURLRequest("file:///non-https-issuer.txt");
908
909 EXPECT_EQ(ExecuteBeginOperationAndWaitForResult(&helper, request.get()),
910 mojom::TrustTokenOperationStatus::kInvalidArgument);
911 }
912
TEST_F(TrustTokenRequestRedemptionHelperTest,RequiresInitiatorForRrRefresh)913 TEST_F(TrustTokenRequestRedemptionHelperTest, RequiresInitiatorForRrRefresh) {
914 // Refresh mode "refresh" requires that the request's initiator to
915 // be same-origin with the request's issuer. Test that, in this case, the
916 // redemption helper requires that the request have an initiator.
917 auto store = TrustTokenStore::CreateForTesting();
918 TrustTokenRequestRedemptionHelper helper(
919 *SuitableTrustTokenOrigin::Create(GURL("https://toplevel.com/")),
920 mojom::TrustTokenRefreshPolicy::kRefresh, store.get(),
921 &*g_fixed_key_commitment_getter, std::make_unique<FakeKeyPairGenerator>(),
922 std::make_unique<MockCryptographer>());
923
924 auto request = MakeURLRequest("https://issuer.example");
925 request->set_initiator(base::nullopt);
926
927 EXPECT_EQ(ExecuteBeginOperationAndWaitForResult(&helper, request.get()),
928 mojom::TrustTokenOperationStatus::kFailedPrecondition);
929 }
930
931 } // namespace network
932