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