[go: nahoru, domu]

blob: 401ebfac7eacad16b5c8a890801d969486078151 [file] [log] [blame]
// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "services/network/trust_tokens/trust_token_request_issuance_helper.h"
#include <memory>
#include "base/callback.h"
#include "base/no_destructor.h"
#include "base/strings/stringprintf.h"
#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.h"
#include "build/build_config.h"
#include "net/base/load_flags.h"
#include "net/base/request_priority.h"
#include "net/http/http_response_headers.h"
#include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
#include "net/url_request/url_request.h"
#include "net/url_request/url_request_test_util.h"
#include "services/network/public/cpp/features.h"
#include "services/network/public/cpp/trust_token_http_headers.h"
#include "services/network/public/cpp/trust_token_parameterization.h"
#include "services/network/public/mojom/trust_tokens.mojom.h"
#include "services/network/public/mojom/url_response_head.mojom.h"
#include "services/network/test/trust_token_test_util.h"
#include "services/network/trust_tokens/operating_system_matching.h"
#include "services/network/trust_tokens/proto/public.pb.h"
#include "services/network/trust_tokens/trust_token_key_commitment_getter.h"
#include "services/network/trust_tokens/trust_token_parameterization.h"
#include "services/network/trust_tokens/trust_token_store.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/origin.h"
namespace network {
namespace {
using ::testing::_;
using ::testing::ByMove;
using ::testing::ElementsAre;
using ::testing::Invoke;
using ::testing::IsEmpty;
using ::testing::Property;
using ::testing::Return;
using ::testing::ReturnNull;
using ::testing::StrEq;
using ::testing::StrictMock;
using ::testing::WithArgs;
using TrustTokenRequestIssuanceHelperTest = TrustTokenRequestHelperTest;
using UnblindedTokens =
TrustTokenRequestIssuanceHelper::Cryptographer::UnblindedTokens;
// FixedKeyCommitmentGetter returns the provided commitment result when
// |Get| is called by the tested code.
class FixedKeyCommitmentGetter : public TrustTokenKeyCommitmentGetter {
public:
FixedKeyCommitmentGetter() = default;
explicit FixedKeyCommitmentGetter(
const url::Origin& issuer,
mojom::TrustTokenKeyCommitmentResultPtr result)
: issuer_(issuer), result_(std::move(result)) {}
void Get(const url::Origin& origin,
base::OnceCallback<void(mojom::TrustTokenKeyCommitmentResultPtr)>
on_done) const override {
EXPECT_EQ(origin, issuer_);
std::move(on_done).Run(result_.Clone());
}
private:
url::Origin issuer_;
mojom::TrustTokenKeyCommitmentResultPtr result_;
};
base::NoDestructor<FixedKeyCommitmentGetter> g_fixed_key_commitment_getter{};
// MockCryptographer mocks out the cryptographic operations underlying Trust
// Tokens issuance.
class MockCryptographer
: public TrustTokenRequestIssuanceHelper::Cryptographer {
public:
MOCK_METHOD2(Initialize,
bool(mojom::TrustTokenProtocolVersion issuer_configured_version,
int issuer_configured_batch_size));
MOCK_METHOD1(AddKey, bool(base::StringPiece key));
MOCK_METHOD1(BeginIssuance, absl::optional<std::string>(size_t num_tokens));
MOCK_METHOD1(
ConfirmIssuance,
std::unique_ptr<UnblindedTokens>(base::StringPiece response_header));
};
// MockLocalOperationDelegate mocks out executing a Trust Tokens operation
// mediated by the OS.
class MockLocalOperationDelegate : public LocalTrustTokenOperationDelegate {
public:
MOCK_METHOD2(
FulfillIssuance,
void(mojom::FulfillTrustTokenIssuanceRequestPtr request,
base::OnceCallback<void(mojom::FulfillTrustTokenIssuanceAnswerPtr)>
done));
};
class MockMetricsDelegate
: public TrustTokenRequestIssuanceHelper::MetricsDelegate {
public:
MOCK_METHOD0(WillExecutePlatformProvidedOperation, void());
};
class FakeMetricsDelegate
: public TrustTokenRequestIssuanceHelper::MetricsDelegate {
public:
void WillExecutePlatformProvidedOperation() override {}
};
base::NoDestructor<FakeMetricsDelegate> g_metrics_delegate{};
// Returns a key commitment result with reasonable values for all parameters.
mojom::TrustTokenKeyCommitmentResultPtr ReasonableKeyCommitmentResult() {
auto key_commitment_result = mojom::TrustTokenKeyCommitmentResult::New();
key_commitment_result->keys.push_back(mojom::TrustTokenVerificationKey::New(
"key", /*expiry=*/base::Time::Max()));
key_commitment_result->batch_size =
static_cast<int>(kMaximumTrustTokenIssuanceBatchSize);
key_commitment_result->protocol_version =
mojom::TrustTokenProtocolVersion::kTrustTokenV3Pmb;
key_commitment_result->id = 1;
return key_commitment_result;
}
FixedKeyCommitmentGetter* ReasonableKeyCommitmentGetter() {
static base::NoDestructor<FixedKeyCommitmentGetter>
reasonable_key_commitment_getter{
*SuitableTrustTokenOrigin::Create(GURL("https://issuer.com/")),
ReasonableKeyCommitmentResult()};
return reasonable_key_commitment_getter.get();
}
} // namespace
// Check that issuance fails if it would result in too many issuers being
// configured for the issuance top-level origin.
TEST_F(TrustTokenRequestIssuanceHelperTest, RejectsIfTooManyIssuers) {
std::unique_ptr<TrustTokenStore> store = TrustTokenStore::CreateForTesting();
auto issuer = *SuitableTrustTokenOrigin::Create(GURL("https://issuer.com/"));
auto toplevel =
*SuitableTrustTokenOrigin::Create(GURL("https://toplevel.com/"));
// Associate the toplevel with the cap's worth of issuers different from
// |issuer|. (The cap is guaranteed to be quite small because of privacy
// requirements of the Trust Tokens protocol.)
for (int i = 0; i < kTrustTokenPerToplevelMaxNumberOfAssociatedIssuers; ++i) {
ASSERT_TRUE(store->SetAssociation(
*SuitableTrustTokenOrigin::Create(
GURL(base::StringPrintf("https://issuer%d.com/", i))),
toplevel));
}
TrustTokenRequestIssuanceHelper helper(
toplevel, store.get(), g_fixed_key_commitment_getter.get(), absl::nullopt,
absl::nullopt, std::make_unique<MockCryptographer>(),
std::make_unique<MockLocalOperationDelegate>(),
base::BindRepeating(&IsCurrentOperatingSystem), g_metrics_delegate.get());
auto request = MakeURLRequest("https://issuer.com/");
request->set_initiator(issuer);
EXPECT_EQ(ExecuteBeginOperationAndWaitForResult(&helper, request.get()),
mojom::TrustTokenOperationStatus::kResourceExhausted);
}
// Check that issuance fails if the number of tokens stored for the issuer is
// already at capacity.
TEST_F(TrustTokenRequestIssuanceHelperTest, RejectsIfAtCapacity) {
std::unique_ptr<TrustTokenStore> store = TrustTokenStore::CreateForTesting();
auto issuer = *SuitableTrustTokenOrigin::Create(GURL("https://issuer.com/"));
// Fill up the store with tokens; issuance should fail the tokens for |issuer|
// are at capacity.
store->AddTokens(issuer,
std::vector<std::string>(kTrustTokenPerIssuerTokenCapacity),
/*issuing_key=*/"");
TrustTokenRequestIssuanceHelper helper(
*SuitableTrustTokenOrigin::Create(GURL("https://toplevel.com/")),
store.get(), g_fixed_key_commitment_getter.get(), absl::nullopt,
absl::nullopt, std::make_unique<MockCryptographer>(),
std::make_unique<MockLocalOperationDelegate>(),
base::BindRepeating(&IsCurrentOperatingSystem), g_metrics_delegate.get());
auto request = MakeURLRequest("https://issuer.com/");
request->set_initiator(issuer);
EXPECT_EQ(ExecuteBeginOperationAndWaitForResult(&helper, request.get()),
mojom::TrustTokenOperationStatus::kResourceExhausted);
}
// Check that issuance fails if its key commitment request fails.
TEST_F(TrustTokenRequestIssuanceHelperTest, RejectsIfKeyCommitmentFails) {
std::unique_ptr<TrustTokenStore> store = TrustTokenStore::CreateForTesting();
SuitableTrustTokenOrigin issuer =
*SuitableTrustTokenOrigin::Create(GURL("https://issuer.com/"));
// Have the key commitment getter return nullptr, denoting that the key
// commitment fetch failed.
auto getter = std::make_unique<FixedKeyCommitmentGetter>(issuer, nullptr);
TrustTokenRequestIssuanceHelper helper(
*SuitableTrustTokenOrigin::Create(GURL("https://toplevel.com/")),
store.get(), getter.get(), absl::nullopt, absl::nullopt,
std::make_unique<MockCryptographer>(),
std::make_unique<MockLocalOperationDelegate>(),
base::BindRepeating(&IsCurrentOperatingSystem), g_metrics_delegate.get());
auto request = MakeURLRequest("https://issuer.com/");
request->set_initiator(
*SuitableTrustTokenOrigin::Create(GURL("https://issuer.com/")));
EXPECT_EQ(ExecuteBeginOperationAndWaitForResult(&helper, request.get()),
mojom::TrustTokenOperationStatus::kFailedPrecondition);
}
// Reject if initializing the cryptography delegate fails.
TEST_F(TrustTokenRequestIssuanceHelperTest,
RejectsIfInitializingCryptographerFails) {
std::unique_ptr<TrustTokenStore> store = TrustTokenStore::CreateForTesting();
SuitableTrustTokenOrigin issuer =
*SuitableTrustTokenOrigin::Create(GURL("https://issuer.com/"));
auto cryptographer = std::make_unique<MockCryptographer>();
EXPECT_CALL(*cryptographer, Initialize(_, _)).WillOnce(Return(false));
TrustTokenRequestIssuanceHelper helper(
*SuitableTrustTokenOrigin::Create(GURL("https://toplevel.com/")),
store.get(), ReasonableKeyCommitmentGetter(), absl::nullopt,
absl::nullopt, std::move(cryptographer),
std::make_unique<MockLocalOperationDelegate>(),
base::BindRepeating(&IsCurrentOperatingSystem), g_metrics_delegate.get());
auto request = MakeURLRequest("https://issuer.com/");
request->set_initiator(issuer);
EXPECT_EQ(ExecuteBeginOperationAndWaitForResult(&helper, request.get()),
mojom::TrustTokenOperationStatus::kInternalError);
}
// Reject if one of the keys in the commitment is malformed.
TEST_F(TrustTokenRequestIssuanceHelperTest, RejectsIfAddingKeyFails) {
std::unique_ptr<TrustTokenStore> store = TrustTokenStore::CreateForTesting();
SuitableTrustTokenOrigin issuer =
*SuitableTrustTokenOrigin::Create(GURL("https://issuer.com/"));
auto cryptographer = std::make_unique<MockCryptographer>();
EXPECT_CALL(*cryptographer, Initialize(_, _)).WillOnce(Return(true));
EXPECT_CALL(*cryptographer, AddKey(_)).WillOnce(Return(false));
TrustTokenRequestIssuanceHelper helper(
*SuitableTrustTokenOrigin::Create(GURL("https://toplevel.com/")),
store.get(), ReasonableKeyCommitmentGetter(), absl::nullopt,
absl::nullopt, std::move(cryptographer),
std::make_unique<MockLocalOperationDelegate>(),
base::BindRepeating(&IsCurrentOperatingSystem), g_metrics_delegate.get());
auto request = MakeURLRequest("https://issuer.com/");
request->set_initiator(issuer);
EXPECT_EQ(ExecuteBeginOperationAndWaitForResult(&helper, request.get()),
mojom::TrustTokenOperationStatus::kFailedPrecondition);
}
// Reject if there's an error getting blinded, unsigned tokens from BoringSSL.
TEST_F(TrustTokenRequestIssuanceHelperTest,
RejectsIfGettingBlindedTokensFails) {
std::unique_ptr<TrustTokenStore> store = TrustTokenStore::CreateForTesting();
SuitableTrustTokenOrigin issuer =
*SuitableTrustTokenOrigin::Create(GURL("https://issuer.com/"));
auto cryptographer = std::make_unique<MockCryptographer>();
EXPECT_CALL(*cryptographer, Initialize(_, _)).WillOnce(Return(true));
EXPECT_CALL(*cryptographer, AddKey(_)).WillOnce(Return(true));
// Return nullopt, denoting an error, when the issuance helper requests
// blinded, unsigned tokens.
EXPECT_CALL(*cryptographer, BeginIssuance(_)).WillOnce(Return(absl::nullopt));
TrustTokenRequestIssuanceHelper helper(
*SuitableTrustTokenOrigin::Create(GURL("https://toplevel.com/")),
store.get(), ReasonableKeyCommitmentGetter(), absl::nullopt,
absl::nullopt, std::move(cryptographer),
std::make_unique<MockLocalOperationDelegate>(),
base::BindRepeating(&IsCurrentOperatingSystem), g_metrics_delegate.get());
auto request = MakeURLRequest("https://issuer.com/");
request->set_initiator(issuer);
// This is an internal error because creating blinded tokens is a
// cryptographic operation not dependent on the inputs provided by the client
// or the protocol state.
EXPECT_EQ(ExecuteBeginOperationAndWaitForResult(&helper, request.get()),
mojom::TrustTokenOperationStatus::kInternalError);
}
// Check that the issuance helper sets the Sec-Trust-Token and
// Sec-Trust-Token-Version headers on the outgoing request.
TEST_F(TrustTokenRequestIssuanceHelperTest, SetsRequestHeaders) {
std::unique_ptr<TrustTokenStore> store = TrustTokenStore::CreateForTesting();
SuitableTrustTokenOrigin issuer =
*SuitableTrustTokenOrigin::Create(GURL("https://issuer.com/"));
// The result of providing blinded, unsigned tokens should be the exact value
// of the Sec-Trust-Token header attached to the request.
auto cryptographer = std::make_unique<MockCryptographer>();
EXPECT_CALL(*cryptographer, Initialize(_, _)).WillOnce(Return(true));
EXPECT_CALL(*cryptographer, AddKey(_)).WillOnce(Return(true));
EXPECT_CALL(*cryptographer, BeginIssuance(_))
.WillOnce(
Return(std::string("this string contains some blinded tokens")));
TrustTokenRequestIssuanceHelper helper(
*SuitableTrustTokenOrigin::Create(GURL("https://toplevel.com/")),
store.get(), ReasonableKeyCommitmentGetter(), absl::nullopt,
absl::nullopt, std::move(cryptographer),
std::make_unique<MockLocalOperationDelegate>(),
base::BindRepeating(&IsCurrentOperatingSystem), g_metrics_delegate.get());
auto request = MakeURLRequest("https://issuer.com/");
request->set_initiator(issuer);
ASSERT_EQ(ExecuteBeginOperationAndWaitForResult(&helper, request.get()),
mojom::TrustTokenOperationStatus::kOk);
std::string attached_header;
EXPECT_TRUE(request->extra_request_headers().GetHeader(
kTrustTokensSecTrustTokenHeader, &attached_header));
EXPECT_EQ(attached_header, "this string contains some blinded tokens");
std::string attached_version_header;
EXPECT_TRUE(request->extra_request_headers().GetHeader(
kTrustTokensSecTrustTokenVersionHeader, &attached_version_header));
EXPECT_EQ(attached_version_header, "TrustTokenV3PMB");
}
// Check that the issuance helper sets the LOAD_BYPASS_CACHE flag on the
// outgoing request.
TEST_F(TrustTokenRequestIssuanceHelperTest, SetsLoadFlag) {
std::unique_ptr<TrustTokenStore> store = TrustTokenStore::CreateForTesting();
SuitableTrustTokenOrigin issuer =
*SuitableTrustTokenOrigin::Create(GURL("https://issuer.com/"));
// The result of providing blinded, unsigned tokens should be the exact value
// of the Sec-Trust-Token header attached to the request.
auto cryptographer = std::make_unique<MockCryptographer>();
EXPECT_CALL(*cryptographer, Initialize(_, _)).WillOnce(Return(true));
EXPECT_CALL(*cryptographer, AddKey(_)).WillOnce(Return(true));
EXPECT_CALL(*cryptographer, BeginIssuance(_))
.WillOnce(
Return(std::string("this string contains some blinded tokens")));
TrustTokenRequestIssuanceHelper helper(
*SuitableTrustTokenOrigin::Create(GURL("https://toplevel.com/")),
store.get(), ReasonableKeyCommitmentGetter(), absl::nullopt,
absl::nullopt, std::move(cryptographer),
std::make_unique<MockLocalOperationDelegate>(),
base::BindRepeating(&IsCurrentOperatingSystem), g_metrics_delegate.get());
auto request = MakeURLRequest("https://issuer.com/");
request->set_initiator(issuer);
ASSERT_EQ(ExecuteBeginOperationAndWaitForResult(&helper, request.get()),
mojom::TrustTokenOperationStatus::kOk);
EXPECT_TRUE(request->load_flags() & net::LOAD_BYPASS_CACHE);
}
// Check that the issuance helper rejects responses lacking the Sec-Trust-Token
// response header.
TEST_F(TrustTokenRequestIssuanceHelperTest, RejectsIfResponseOmitsHeader) {
std::unique_ptr<TrustTokenStore> store = TrustTokenStore::CreateForTesting();
SuitableTrustTokenOrigin issuer =
*SuitableTrustTokenOrigin::Create(GURL("https://issuer.com/"));
auto cryptographer = std::make_unique<MockCryptographer>();
EXPECT_CALL(*cryptographer, Initialize(_, _)).WillOnce(Return(true));
EXPECT_CALL(*cryptographer, AddKey(_)).WillOnce(Return(true));
EXPECT_CALL(*cryptographer, BeginIssuance(_))
.WillOnce(
Return(std::string("this string contains some blinded tokens")));
TrustTokenRequestIssuanceHelper helper(
*SuitableTrustTokenOrigin::Create(GURL("https://toplevel.com/")),
store.get(), ReasonableKeyCommitmentGetter(), absl::nullopt,
absl::nullopt, std::move(cryptographer),
std::make_unique<MockLocalOperationDelegate>(),
base::BindRepeating(&IsCurrentOperatingSystem), g_metrics_delegate.get());
auto request = MakeURLRequest("https://issuer.com/");
request->set_initiator(issuer);
ASSERT_EQ(ExecuteBeginOperationAndWaitForResult(&helper, request.get()),
mojom::TrustTokenOperationStatus::kOk);
auto response_head = mojom::URLResponseHead::New();
response_head->headers =
net::HttpResponseHeaders::TryToCreate("HTTP/1.1 200 OK\r\n");
EXPECT_EQ(ExecuteFinalizeAndWaitForResult(&helper, response_head.get()),
mojom::TrustTokenOperationStatus::kBadResponse);
}
// Check that the issuance helper correctly handles responses bearing empty
// Sec-Trust-Token headers, which represent "success but no tokens issued".
TEST_F(TrustTokenRequestIssuanceHelperTest, TreatsEmptyHeaderAsSuccess) {
std::unique_ptr<TrustTokenStore> store = TrustTokenStore::CreateForTesting();
SuitableTrustTokenOrigin issuer =
*SuitableTrustTokenOrigin::Create(GURL("https://issuer.com/"));
auto cryptographer = std::make_unique<MockCryptographer>();
EXPECT_CALL(*cryptographer, Initialize(_, _)).WillOnce(Return(true));
EXPECT_CALL(*cryptographer, AddKey(_)).WillOnce(Return(true));
EXPECT_CALL(*cryptographer, BeginIssuance(_))
.WillOnce(
Return(std::string("this string contains some blinded tokens")));
TrustTokenRequestIssuanceHelper helper(
*SuitableTrustTokenOrigin::Create(GURL("https://toplevel.com/")),
store.get(), ReasonableKeyCommitmentGetter(), absl::nullopt,
absl::nullopt, std::move(cryptographer),
std::make_unique<MockLocalOperationDelegate>(),
base::BindRepeating(&IsCurrentOperatingSystem), g_metrics_delegate.get());
auto request = MakeURLRequest("https://issuer.com/");
request->set_initiator(issuer);
ASSERT_EQ(ExecuteBeginOperationAndWaitForResult(&helper, request.get()),
mojom::TrustTokenOperationStatus::kOk);
auto response_head = mojom::URLResponseHead::New();
response_head->headers =
net::HttpResponseHeaders::TryToCreate("HTTP/1.1 200 OK\r\n");
response_head->headers->SetHeader(kTrustTokensSecTrustTokenHeader, "");
EXPECT_EQ(ExecuteFinalizeAndWaitForResult(&helper, response_head.get()),
mojom::TrustTokenOperationStatus::kOk);
// After the operation has successfully finished, the store should still
// contain no tokens for the issuer.
auto match_all_keys =
base::BindRepeating([](const std::string&) { return true; });
EXPECT_THAT(store->RetrieveMatchingTokens(issuer, std::move(match_all_keys)),
IsEmpty());
}
// Check that the issuance helper handles an issuance response rejected by the
// underlying cryptographic library.
TEST_F(TrustTokenRequestIssuanceHelperTest, RejectsIfResponseIsUnusable) {
std::unique_ptr<TrustTokenStore> store = TrustTokenStore::CreateForTesting();
SuitableTrustTokenOrigin issuer =
*SuitableTrustTokenOrigin::Create(GURL("https://issuer.com/"));
auto cryptographer = std::make_unique<MockCryptographer>();
EXPECT_CALL(*cryptographer, Initialize(_, _)).WillOnce(Return(true));
EXPECT_CALL(*cryptographer, AddKey(_)).WillOnce(Return(true));
EXPECT_CALL(*cryptographer, BeginIssuance(_))
.WillOnce(
Return(std::string("this string contains some blinded tokens")));
// Fail the "confirm issuance" step of validating the server's response
// within the underlying cryptographic library.
EXPECT_CALL(*cryptographer, ConfirmIssuance(_)).WillOnce(ReturnNull());
TrustTokenRequestIssuanceHelper helper(
*SuitableTrustTokenOrigin::Create(GURL("https://toplevel.com/")),
store.get(), ReasonableKeyCommitmentGetter(), absl::nullopt,
absl::nullopt, std::move(cryptographer),
std::make_unique<MockLocalOperationDelegate>(),
base::BindRepeating(&IsCurrentOperatingSystem), g_metrics_delegate.get());
auto request = MakeURLRequest("https://issuer.com/");
request->set_initiator(issuer);
ASSERT_EQ(ExecuteBeginOperationAndWaitForResult(&helper, request.get()),
mojom::TrustTokenOperationStatus::kOk);
auto response_head = mojom::URLResponseHead::New();
response_head->headers =
net::HttpResponseHeaders::TryToCreate("HTTP/1.1 200 OK\r\n");
response_head->headers->SetHeader(
kTrustTokensSecTrustTokenHeader,
"response from issuer (this value will be ignored, since "
"Cryptographer::ConfirmResponse is mocked out)");
EXPECT_EQ(ExecuteFinalizeAndWaitForResult(&helper, response_head.get()),
mojom::TrustTokenOperationStatus::kBadResponse);
// Verify that Finalize correctly stripped the response header.
EXPECT_FALSE(
response_head->headers->HasHeader(kTrustTokensSecTrustTokenHeader));
}
// Check that, when preconditions are met and the underlying cryptographic steps
// successfully complete, the begin/finalize methods succeed.
TEST_F(TrustTokenRequestIssuanceHelperTest, Success) {
std::unique_ptr<TrustTokenStore> store = TrustTokenStore::CreateForTesting();
SuitableTrustTokenOrigin issuer =
*SuitableTrustTokenOrigin::Create(GURL("https://issuer.com/"));
auto cryptographer = std::make_unique<MockCryptographer>();
EXPECT_CALL(*cryptographer, Initialize(_, _)).WillOnce(Return(true));
EXPECT_CALL(*cryptographer, AddKey(_)).WillOnce(Return(true));
EXPECT_CALL(*cryptographer, BeginIssuance(_))
.WillOnce(
Return(std::string("this string contains some blinded tokens")));
EXPECT_CALL(*cryptographer, ConfirmIssuance(_))
.WillOnce(Return(ByMove(std::make_unique<UnblindedTokens>())));
TrustTokenRequestIssuanceHelper helper(
*SuitableTrustTokenOrigin::Create(GURL("https://toplevel.com/")),
store.get(), ReasonableKeyCommitmentGetter(), absl::nullopt,
absl::nullopt, std::move(cryptographer),
std::make_unique<MockLocalOperationDelegate>(),
base::BindRepeating(&IsCurrentOperatingSystem), g_metrics_delegate.get());
auto request = MakeURLRequest("https://issuer.com/");
request->set_initiator(issuer);
ASSERT_EQ(ExecuteBeginOperationAndWaitForResult(&helper, request.get()),
mojom::TrustTokenOperationStatus::kOk);
auto response_head = mojom::URLResponseHead::New();
response_head->headers =
net::HttpResponseHeaders::TryToCreate("HTTP/1.1 200 OK\r\n");
response_head->headers->SetHeader(
kTrustTokensSecTrustTokenHeader,
"response from issuer (this value will be ignored, since "
"Cryptographer::ConfirmResponse is mocked out)");
EXPECT_EQ(ExecuteFinalizeAndWaitForResult(&helper, response_head.get()),
mojom::TrustTokenOperationStatus::kOk);
// Verify that Finalize correctly stripped the response header.
EXPECT_FALSE(
response_head->headers->HasHeader(kTrustTokensSecTrustTokenHeader));
}
// Check that a successful Begin call associates the issuer with the issuance
// toplevel origin.
TEST_F(TrustTokenRequestIssuanceHelperTest, AssociatesIssuerWithToplevel) {
std::unique_ptr<TrustTokenStore> store = TrustTokenStore::CreateForTesting();
SuitableTrustTokenOrigin issuer =
*SuitableTrustTokenOrigin::Create(GURL("https://issuer.com/"));
auto cryptographer = std::make_unique<MockCryptographer>();
EXPECT_CALL(*cryptographer, Initialize(_, _)).WillOnce(Return(true));
EXPECT_CALL(*cryptographer, AddKey(_)).WillOnce(Return(true));
EXPECT_CALL(*cryptographer, BeginIssuance(_))
.WillOnce(
Return(std::string("this string contains some blinded tokens")));
TrustTokenRequestIssuanceHelper helper(
*SuitableTrustTokenOrigin::Create(GURL("https://toplevel.com/")),
store.get(), ReasonableKeyCommitmentGetter(), absl::nullopt,
absl::nullopt, std::move(cryptographer),
std::make_unique<MockLocalOperationDelegate>(),
base::BindRepeating(&IsCurrentOperatingSystem), g_metrics_delegate.get());
auto request = MakeURLRequest("https://issuer.com/");
request->set_initiator(issuer);
ASSERT_EQ(ExecuteBeginOperationAndWaitForResult(&helper, request.get()),
mojom::TrustTokenOperationStatus::kOk);
// After the operation has successfully begun, the issuer and the toplevel
// should be associated.
EXPECT_TRUE(store->IsAssociated(issuer, *SuitableTrustTokenOrigin::Create(
GURL("https://toplevel.com/"))));
}
// Check that a successful end-to-end Begin/Finalize flow stores the obtained
// trust tokens in the trust token store.
TEST_F(TrustTokenRequestIssuanceHelperTest, StoresObtainedTokens) {
std::unique_ptr<TrustTokenStore> store = TrustTokenStore::CreateForTesting();
SuitableTrustTokenOrigin issuer =
*SuitableTrustTokenOrigin::Create(GURL("https://issuer.com/"));
// Have the Trust Tokens issuance conclude by the underlying cryptographic
// library returning one signed, unblinded token associated with the same
// returned from the key commitment.
auto unblinded_tokens = std::make_unique<UnblindedTokens>();
unblinded_tokens->body_of_verifying_key =
ReasonableKeyCommitmentResult()->keys.front()->body;
unblinded_tokens->tokens.push_back("a signed, unblinded token");
auto cryptographer = std::make_unique<MockCryptographer>();
EXPECT_CALL(*cryptographer, Initialize(_, _)).WillOnce(Return(true));
EXPECT_CALL(*cryptographer, AddKey(_)).WillOnce(Return(true));
EXPECT_CALL(*cryptographer, BeginIssuance(_))
.WillOnce(
Return(std::string("this string contains some blinded tokens")));
EXPECT_CALL(*cryptographer, ConfirmIssuance(_))
.WillOnce(Return(ByMove(std::move((unblinded_tokens)))));
TrustTokenRequestIssuanceHelper helper(
*SuitableTrustTokenOrigin::Create(GURL("https://toplevel.com/")),
store.get(), ReasonableKeyCommitmentGetter(), absl::nullopt,
absl::nullopt, std::move(cryptographer),
std::make_unique<MockLocalOperationDelegate>(),
base::BindRepeating(&IsCurrentOperatingSystem), g_metrics_delegate.get());
auto request = MakeURLRequest("https://issuer.com/");
request->set_initiator(issuer);
ASSERT_EQ(ExecuteBeginOperationAndWaitForResult(&helper, request.get()),
mojom::TrustTokenOperationStatus::kOk);
auto response_head = mojom::URLResponseHead::New();
response_head->headers =
net::HttpResponseHeaders::TryToCreate("HTTP/1.1 200 OK\r\n");
response_head->headers->SetHeader(
kTrustTokensSecTrustTokenHeader,
"response from issuer (this value will be ignored, since "
"Cryptographer::ConfirmResponse is mocked out)");
EXPECT_EQ(ExecuteFinalizeAndWaitForResult(&helper, response_head.get()),
mojom::TrustTokenOperationStatus::kOk);
// After the operation has successfully finished, the trust tokens parsed from
// the server response should be in the store.
auto match_all_keys =
base::BindRepeating([](const std::string&) { return true; });
EXPECT_THAT(
store->RetrieveMatchingTokens(issuer, std::move(match_all_keys)),
ElementsAre(Property(&TrustToken::body, "a signed, unblinded token")));
}
// Check that the issuance helper correctly handles responses bearing
// Sec-Trust-Token-Clear-Data header with value all.
TEST_F(TrustTokenRequestIssuanceHelperTest, DiscardDataResponseSuccess) {
std::unique_ptr<TrustTokenStore> store = TrustTokenStore::CreateForTesting();
// isser1 must be "https://issuer.com/", this is hard coded to
// ReasonableKeyCommitmentGetter
SuitableTrustTokenOrigin issuer1 =
*SuitableTrustTokenOrigin::Create(GURL("https://issuer.com/"));
// Add tokens that will be discarded with Sec-Trust-Token-Clear-Data response.
store->AddTokens(issuer1, std::vector<std::string>{"token1", "token2"},
"key");
SuitableTrustTokenOrigin issuer2 =
*SuitableTrustTokenOrigin::Create(GURL("https://issuer2.com/"));
store->AddTokens(issuer2, std::vector<std::string>{"token3", "token4"},
"key2");
auto unblinded_tokens = std::make_unique<UnblindedTokens>();
unblinded_tokens->body_of_verifying_key =
ReasonableKeyCommitmentResult()->keys.front()->body;
unblinded_tokens->tokens.push_back("a signed, unblinded token");
auto cryptographer = std::make_unique<MockCryptographer>();
EXPECT_CALL(*cryptographer, Initialize(_, _)).WillOnce(Return(true));
EXPECT_CALL(*cryptographer, AddKey(_)).WillOnce(Return(true));
EXPECT_CALL(*cryptographer, BeginIssuance(_))
.WillOnce(
Return(std::string("this string contains some blinded tokens")));
EXPECT_CALL(*cryptographer, ConfirmIssuance(_))
.WillOnce(Return(ByMove(std::move((unblinded_tokens)))));
// ReasonableKeyCommitmentGetter is for issuer1
TrustTokenRequestIssuanceHelper helper(
*SuitableTrustTokenOrigin::Create(GURL("https://toplevel.com/")),
store.get(), ReasonableKeyCommitmentGetter(), absl::nullopt,
absl::nullopt, std::move(cryptographer),
std::make_unique<MockLocalOperationDelegate>(),
base::BindRepeating(&IsCurrentOperatingSystem), g_metrics_delegate.get());
// request is from issuer1
auto request = MakeURLRequest("https://issuer.com/");
request->set_initiator(issuer1);
ASSERT_EQ(ExecuteBeginOperationAndWaitForResult(&helper, request.get()),
mojom::TrustTokenOperationStatus::kOk);
auto response_head = mojom::URLResponseHead::New();
response_head->headers =
net::HttpResponseHeaders::TryToCreate("HTTP/1.1 200 OK\r\n");
response_head->headers->SetHeader(
kTrustTokensSecTrustTokenHeader,
"response from issuer (this value will be ignored, since "
"Cryptographer::ConfirmResponse is mocked out)");
// Add clear data response header.
response_head->headers->SetHeader(
kTrustTokensResponseHeaderSecTrustTokenClearData, "all");
EXPECT_EQ(ExecuteFinalizeAndWaitForResult(&helper, response_head.get()),
mojom::TrustTokenOperationStatus::kOk);
// Trust token parsed from the server response should be in the store.
auto match_all_keys =
base::BindRepeating([](const std::string&) { return true; });
EXPECT_THAT(
store->RetrieveMatchingTokens(issuer1, std::move(match_all_keys)),
ElementsAre(Property(&TrustToken::body, "a signed, unblinded token")));
// The store should have only the recently issued token, and previous tokens
// should be discarded.
EXPECT_EQ(store->CountTokens(issuer1), 1);
// All issuer2 tokens must be in store
EXPECT_EQ(store->CountTokens(issuer2), 2);
// Verify that Finalize correctly stripped the response header.
EXPECT_FALSE(response_head->headers->HasHeader(
kTrustTokensResponseHeaderSecTrustTokenClearData));
}
// Check that the issuance helper correctly handles responses bearing
// Sec-Trust-Token-Clear-Data header when there are no tokens for the issuer
TEST_F(TrustTokenRequestIssuanceHelperTest,
DiscardDataResponseNoTokensInStore) {
std::unique_ptr<TrustTokenStore> store = TrustTokenStore::CreateForTesting();
SuitableTrustTokenOrigin issuer =
*SuitableTrustTokenOrigin::Create(GURL("https://issuer.com/"));
auto unblinded_tokens = std::make_unique<UnblindedTokens>();
unblinded_tokens->body_of_verifying_key =
ReasonableKeyCommitmentResult()->keys.front()->body;
unblinded_tokens->tokens.push_back("a signed, unblinded token");
auto cryptographer = std::make_unique<MockCryptographer>();
EXPECT_CALL(*cryptographer, Initialize(_, _)).WillOnce(Return(true));
EXPECT_CALL(*cryptographer, AddKey(_)).WillOnce(Return(true));
EXPECT_CALL(*cryptographer, BeginIssuance(_))
.WillOnce(
Return(std::string("this string contains some blinded tokens")));
EXPECT_CALL(*cryptographer, ConfirmIssuance(_))
.WillOnce(Return(ByMove(std::move((unblinded_tokens)))));
TrustTokenRequestIssuanceHelper helper(
*SuitableTrustTokenOrigin::Create(GURL("https://toplevel.com/")),
store.get(), ReasonableKeyCommitmentGetter(), absl::nullopt,
absl::nullopt, std::move(cryptographer),
std::make_unique<MockLocalOperationDelegate>(),
base::BindRepeating(&IsCurrentOperatingSystem), g_metrics_delegate.get());
auto request = MakeURLRequest("https://issuer.com/");
request->set_initiator(issuer);
ASSERT_EQ(ExecuteBeginOperationAndWaitForResult(&helper, request.get()),
mojom::TrustTokenOperationStatus::kOk);
auto response_head = mojom::URLResponseHead::New();
response_head->headers =
net::HttpResponseHeaders::TryToCreate("HTTP/1.1 200 OK\r\n");
response_head->headers->SetHeader(
kTrustTokensSecTrustTokenHeader,
"response from issuer (this value will be ignored, since "
"Cryptographer::ConfirmResponse is mocked out)");
// Add clear data response header.
response_head->headers->SetHeader(
kTrustTokensResponseHeaderSecTrustTokenClearData, "all");
EXPECT_EQ(ExecuteFinalizeAndWaitForResult(&helper, response_head.get()),
mojom::TrustTokenOperationStatus::kOk);
// Trust token parsed from the server response should be in the store.
auto match_all_keys =
base::BindRepeating([](const std::string&) { return true; });
EXPECT_THAT(
store->RetrieveMatchingTokens(issuer, std::move(match_all_keys)),
ElementsAre(Property(&TrustToken::body, "a signed, unblinded token")));
// The store should have only the recently issued token
EXPECT_EQ(store->CountTokens(issuer), 1);
// Verify that Finalize correctly stripped the response header.
EXPECT_FALSE(response_head->headers->HasHeader(
kTrustTokensResponseHeaderSecTrustTokenClearData));
}
// Check that the issuance helper correctly handles responses bearing
// Sec-Trust-Token-Clear-Data header with values other than "all"
TEST_F(TrustTokenRequestIssuanceHelperTest,
DiscardDataResponseWrongHeaderValue) {
std::unique_ptr<TrustTokenStore> store = TrustTokenStore::CreateForTesting();
SuitableTrustTokenOrigin issuer =
*SuitableTrustTokenOrigin::Create(GURL("https://issuer.com/"));
// Add tokens that will be discarded with Sec-Trust-Token-Clear-Data response.
store->AddTokens(issuer, std::vector<std::string>{"token1", "token2"}, "key");
auto unblinded_tokens = std::make_unique<UnblindedTokens>();
unblinded_tokens->body_of_verifying_key =
ReasonableKeyCommitmentResult()->keys.front()->body;
unblinded_tokens->tokens.push_back("a signed, unblinded token");
auto cryptographer = std::make_unique<MockCryptographer>();
EXPECT_CALL(*cryptographer, Initialize(_, _)).WillOnce(Return(true));
EXPECT_CALL(*cryptographer, AddKey(_)).WillOnce(Return(true));
EXPECT_CALL(*cryptographer, BeginIssuance(_))
.WillOnce(
Return(std::string("this string contains some blinded tokens")));
EXPECT_CALL(*cryptographer, ConfirmIssuance(_))
.WillOnce(Return(ByMove(std::move((unblinded_tokens)))));
TrustTokenRequestIssuanceHelper helper(
*SuitableTrustTokenOrigin::Create(GURL("https://toplevel.com/")),
store.get(), ReasonableKeyCommitmentGetter(), absl::nullopt,
absl::nullopt, std::move(cryptographer),
std::make_unique<MockLocalOperationDelegate>(),
base::BindRepeating(&IsCurrentOperatingSystem), g_metrics_delegate.get());
auto request = MakeURLRequest("https://issuer.com/");
request->set_initiator(issuer);
ASSERT_EQ(ExecuteBeginOperationAndWaitForResult(&helper, request.get()),
mojom::TrustTokenOperationStatus::kOk);
auto response_head = mojom::URLResponseHead::New();
response_head->headers =
net::HttpResponseHeaders::TryToCreate("HTTP/1.1 200 OK\r\n");
response_head->headers->SetHeader(
kTrustTokensSecTrustTokenHeader,
"response from issuer (this value will be ignored, since "
"Cryptographer::ConfirmResponse is mocked out)");
// Add clear data response header with a wrong value
response_head->headers->SetHeader(
kTrustTokensResponseHeaderSecTrustTokenClearData, "wrong_value");
EXPECT_EQ(ExecuteFinalizeAndWaitForResult(&helper, response_head.get()),
mojom::TrustTokenOperationStatus::kOk);
// The store should have previously issued tokens as well
EXPECT_EQ(store->CountTokens(issuer), 3);
// Verify that Finalize correctly stripped the response header.
EXPECT_FALSE(response_head->headers->HasHeader(
kTrustTokensResponseHeaderSecTrustTokenClearData));
}
// Check that the issuance helper correctly handles responses bearing
// Sec-Trust-Token-Clear-Data header where Sec-Trust-Token header is missing.
TEST_F(TrustTokenRequestIssuanceHelperTest,
DiscardDataResponseOmitsTrustTokenHeader) {
std::unique_ptr<TrustTokenStore> store = TrustTokenStore::CreateForTesting();
SuitableTrustTokenOrigin issuer =
*SuitableTrustTokenOrigin::Create(GURL("https://issuer.com/"));
// Add tokens that will be discarded with Sec-Trust-Token-Clear-Data response.
store->AddTokens(issuer, std::vector<std::string>{"token1", "token2"}, "key");
auto unblinded_tokens = std::make_unique<UnblindedTokens>();
unblinded_tokens->body_of_verifying_key =
ReasonableKeyCommitmentResult()->keys.front()->body;
unblinded_tokens->tokens.push_back("a signed, unblinded token");
auto cryptographer = std::make_unique<MockCryptographer>();
EXPECT_CALL(*cryptographer, Initialize(_, _)).WillOnce(Return(true));
EXPECT_CALL(*cryptographer, AddKey(_)).WillOnce(Return(true));
EXPECT_CALL(*cryptographer, BeginIssuance(_))
.WillOnce(
Return(std::string("this string contains some blinded tokens")));
TrustTokenRequestIssuanceHelper helper(
*SuitableTrustTokenOrigin::Create(GURL("https://toplevel.com/")),
store.get(), ReasonableKeyCommitmentGetter(), absl::nullopt,
absl::nullopt, std::move(cryptographer),
std::make_unique<MockLocalOperationDelegate>(),
base::BindRepeating(&IsCurrentOperatingSystem), g_metrics_delegate.get());
auto request = MakeURLRequest("https://issuer.com/");
request->set_initiator(issuer);
ASSERT_EQ(ExecuteBeginOperationAndWaitForResult(&helper, request.get()),
mojom::TrustTokenOperationStatus::kOk);
auto response_head = mojom::URLResponseHead::New();
response_head->headers =
net::HttpResponseHeaders::TryToCreate("HTTP/1.1 200 OK\r\n");
// Add clear data response header with wrong value
response_head->headers->SetHeader(
kTrustTokensResponseHeaderSecTrustTokenClearData, "");
EXPECT_EQ(ExecuteFinalizeAndWaitForResult(&helper, response_head.get()),
mojom::TrustTokenOperationStatus::kBadResponse);
// The store should have previously issued tokens
EXPECT_EQ(store->CountTokens(issuer), 2);
// Verify that Finalize correctly stripped the response header.
EXPECT_FALSE(response_head->headers->HasHeader(
kTrustTokensResponseHeaderSecTrustTokenClearData));
}
TEST_F(TrustTokenRequestIssuanceHelperTest, RejectsUnsuitableInsecureIssuer) {
auto store = TrustTokenStore::CreateForTesting();
TrustTokenRequestIssuanceHelper helper(
*SuitableTrustTokenOrigin::Create(GURL("https://toplevel.com/")),
store.get(), g_fixed_key_commitment_getter.get(), absl::nullopt,
absl::nullopt, std::make_unique<MockCryptographer>(),
std::make_unique<MockLocalOperationDelegate>(),
base::BindRepeating(&IsCurrentOperatingSystem), g_metrics_delegate.get());
auto request = MakeURLRequest("http://insecure-issuer.com/");
EXPECT_EQ(ExecuteBeginOperationAndWaitForResult(&helper, request.get()),
mojom::TrustTokenOperationStatus::kInvalidArgument);
}
TEST_F(TrustTokenRequestIssuanceHelperTest,
RejectsUnsuitableNonHttpNonHttpsIssuer) {
auto store = TrustTokenStore::CreateForTesting();
TrustTokenRequestIssuanceHelper helper(
*SuitableTrustTokenOrigin::Create(GURL("https://toplevel.com/")),
store.get(), g_fixed_key_commitment_getter.get(), absl::nullopt,
absl::nullopt, std::make_unique<MockCryptographer>(),
std::make_unique<MockLocalOperationDelegate>(),
base::BindRepeating(&IsCurrentOperatingSystem), g_metrics_delegate.get());
auto request = MakeURLRequest("file:///non-https-issuer.txt");
EXPECT_EQ(ExecuteBeginOperationAndWaitForResult(&helper, request.get()),
mojom::TrustTokenOperationStatus::kInvalidArgument);
}
TEST_F(TrustTokenRequestIssuanceHelperTest, RespectsMaximumBatchsize) {
std::unique_ptr<TrustTokenStore> store = TrustTokenStore::CreateForTesting();
SuitableTrustTokenOrigin issuer =
*SuitableTrustTokenOrigin::Create(GURL("https://issuer.com/"));
auto cryptographer = std::make_unique<MockCryptographer>();
EXPECT_CALL(*cryptographer, Initialize(_, _)).WillOnce(Return(true));
EXPECT_CALL(*cryptographer, AddKey(_)).WillOnce(Return(true));
// The batch size should be clamped to the configured maximum.
EXPECT_CALL(*cryptographer,
BeginIssuance(kMaximumTrustTokenIssuanceBatchSize))
.WillOnce(
Return(std::string("this string contains some blinded tokens")));
TrustTokenRequestIssuanceHelper helper(
*SuitableTrustTokenOrigin::Create(GURL("https://toplevel.com/")),
store.get(), ReasonableKeyCommitmentGetter(), absl::nullopt,
absl::nullopt, std::move(cryptographer),
std::make_unique<MockLocalOperationDelegate>(),
base::BindRepeating(&IsCurrentOperatingSystem), g_metrics_delegate.get());
auto request = MakeURLRequest("https://issuer.com/");
request->set_initiator(issuer);
ASSERT_EQ(ExecuteBeginOperationAndWaitForResult(&helper, request.get()),
mojom::TrustTokenOperationStatus::kOk);
}
// Check that attempting to issue with custom key commitments fails if custom
// key commitments are invalid.
TEST_F(TrustTokenRequestIssuanceHelperTest, BadCustomKeys) {
auto store = TrustTokenStore::CreateForTesting();
TrustTokenRequestIssuanceHelper helper(
*SuitableTrustTokenOrigin::Create(GURL("https://issuer.com/")),
store.get(), g_fixed_key_commitment_getter.get(), "junk keys",
absl::nullopt, std::make_unique<MockCryptographer>(),
std::make_unique<MockLocalOperationDelegate>(),
base::BindRepeating(&IsCurrentOperatingSystem), g_metrics_delegate.get());
auto request = MakeURLRequest("https://issuer.com/");
EXPECT_EQ(ExecuteBeginOperationAndWaitForResult(&helper, request.get()),
mojom::TrustTokenOperationStatus::kInvalidArgument);
}
// Check that a successful end-to-end Begin/Finalize flow with custom key
// commitments stores the obtained trust tokens in the trust token store.
TEST_F(TrustTokenRequestIssuanceHelperTest, CustomKeysStoresObtainedTokens) {
std::unique_ptr<TrustTokenStore> store = TrustTokenStore::CreateForTesting();
SuitableTrustTokenOrigin issuer =
*SuitableTrustTokenOrigin::Create(GURL("https://issuer.com/"));
// Have the Trust Tokens issuance conclude by the underlying cryptographic
// library returning one signed, unblinded token associated with the same
// returned from the key commitment.
auto unblinded_tokens = std::make_unique<UnblindedTokens>();
unblinded_tokens->body_of_verifying_key =
ReasonableKeyCommitmentResult()->keys.front()->body;
unblinded_tokens->tokens.push_back("a signed, unblinded token");
auto cryptographer = std::make_unique<MockCryptographer>();
EXPECT_CALL(*cryptographer, Initialize(_, _)).WillOnce(Return(true));
EXPECT_CALL(*cryptographer, AddKey(_)).WillOnce(Return(true));
EXPECT_CALL(*cryptographer, BeginIssuance(_))
.WillOnce(
Return(std::string("this string contains some blinded tokens")));
EXPECT_CALL(*cryptographer, ConfirmIssuance(_))
.WillOnce(Return(ByMove(std::move((unblinded_tokens)))));
base::Time one_minute_from_now = base::Time::Now() + base::Minutes(1);
int64_t one_minute_from_now_in_micros =
(one_minute_from_now - base::Time::UnixEpoch()).InMicroseconds();
const std::string basic_key = base::StringPrintf(
R"({ "TrustTokenV3PMB": {
"protocol_version": "TrustTokenV3PMB", "id": 1, "batchsize": 5,
"keys": {"1": { "Y": "akey", "expiry": "%s" }}
}})",
base::NumberToString(one_minute_from_now_in_micros).c_str());
TrustTokenRequestIssuanceHelper helper(
*SuitableTrustTokenOrigin::Create(GURL("https://toplevel.com/")),
store.get(), ReasonableKeyCommitmentGetter(), basic_key, absl::nullopt,
std::move(cryptographer), std::make_unique<MockLocalOperationDelegate>(),
base::BindRepeating(&IsCurrentOperatingSystem), g_metrics_delegate.get());
auto request = MakeURLRequest("https://issuer.com/");
request->set_initiator(issuer);
ASSERT_EQ(ExecuteBeginOperationAndWaitForResult(&helper, request.get()),
mojom::TrustTokenOperationStatus::kOk);
auto response_head = mojom::URLResponseHead::New();
response_head->headers =
net::HttpResponseHeaders::TryToCreate("HTTP/1.1 200 OK\r\n");
response_head->headers->SetHeader(
kTrustTokensSecTrustTokenHeader,
"response from issuer (this value will be ignored, since "
"Cryptographer::ConfirmResponse is mocked out)");
EXPECT_EQ(ExecuteFinalizeAndWaitForResult(&helper, response_head.get()),
mojom::TrustTokenOperationStatus::kOk);
// After the operation has successfully finished, the trust tokens parsed from
// the server response should be in the store.
auto match_all_keys =
base::BindRepeating([](const std::string&) { return true; });
EXPECT_THAT(
store->RetrieveMatchingTokens(issuer, std::move(match_all_keys)),
ElementsAre(Property(&TrustToken::body, "a signed, unblinded token")));
}
// Check that attempting to issue with custom key commitments fails if custom
// key commitments are invalid.
TEST_F(TrustTokenRequestIssuanceHelperTest, BadCustomIssuer) {
auto store = TrustTokenStore::CreateForTesting();
TrustTokenRequestIssuanceHelper helper(
*SuitableTrustTokenOrigin::Create(GURL("https://issuer.com/")),
store.get(), g_fixed_key_commitment_getter.get(), "junk keys",
url::Origin::Create(GURL("http://bad-issuer.com")),
std::make_unique<MockCryptographer>(),
std::make_unique<MockLocalOperationDelegate>(),
base::BindRepeating(&IsCurrentOperatingSystem), g_metrics_delegate.get());
auto request = MakeURLRequest("https://issuer.com/");
EXPECT_EQ(ExecuteBeginOperationAndWaitForResult(&helper, request.get()),
mojom::TrustTokenOperationStatus::kInvalidArgument);
}
// Check that a successful end-to-end Begin/Finalize flow with custom key
// commitments stores the obtained trust tokens in the trust token store.
TEST_F(TrustTokenRequestIssuanceHelperTest, CustomIssuerStoresObtainedTokens) {
std::unique_ptr<TrustTokenStore> store = TrustTokenStore::CreateForTesting();
SuitableTrustTokenOrigin fakeissuer =
*SuitableTrustTokenOrigin::Create(GURL("https://fakeissuer.com/"));
SuitableTrustTokenOrigin goodissuer =
*SuitableTrustTokenOrigin::Create(GURL("https://issuer.com/"));
// Have the Trust Tokens issuance conclude by the underlying cryptographic
// library returning one signed, unblinded token associated with the same
// returned from the key commitment.
auto unblinded_tokens = std::make_unique<UnblindedTokens>();
unblinded_tokens->body_of_verifying_key =
ReasonableKeyCommitmentResult()->keys.front()->body;
unblinded_tokens->tokens.push_back("a signed, unblinded token");
auto cryptographer = std::make_unique<MockCryptographer>();
EXPECT_CALL(*cryptographer, Initialize(_, _)).WillOnce(Return(true));
EXPECT_CALL(*cryptographer, AddKey(_)).WillOnce(Return(true));
EXPECT_CALL(*cryptographer, BeginIssuance(_))
.WillOnce(
Return(std::string("this string contains some blinded tokens")));
EXPECT_CALL(*cryptographer, ConfirmIssuance(_))
.WillOnce(Return(ByMove(std::move((unblinded_tokens)))));
base::Time one_minute_from_now = base::Time::Now() + base::Minutes(1);
int64_t one_minute_from_now_in_micros =
(one_minute_from_now - base::Time::UnixEpoch()).InMicroseconds();
const std::string basic_key = base::StringPrintf(
R"({ "TrustTokenV3PMB": {
"protocol_version": "TrustTokenV3PMB", "id": 1, "batchsize": 5,
"keys": {"1": { "Y": "akey", "expiry": "%s" }}
}})",
base::NumberToString(one_minute_from_now_in_micros).c_str());
TrustTokenRequestIssuanceHelper helper(
*SuitableTrustTokenOrigin::Create(GURL("https://toplevel.com/")),
store.get(), ReasonableKeyCommitmentGetter(), basic_key,
url::Origin::Create(GURL("https://issuer.com")), std::move(cryptographer),
std::make_unique<MockLocalOperationDelegate>(),
base::BindRepeating(&IsCurrentOperatingSystem), g_metrics_delegate.get());
auto request = MakeURLRequest("https://fakeissuer.com/");
request->set_initiator(fakeissuer);
ASSERT_EQ(ExecuteBeginOperationAndWaitForResult(&helper, request.get()),
mojom::TrustTokenOperationStatus::kOk);
auto response_head = mojom::URLResponseHead::New();
response_head->headers =
net::HttpResponseHeaders::TryToCreate("HTTP/1.1 200 OK\r\n");
response_head->headers->SetHeader(
kTrustTokensSecTrustTokenHeader,
"response from issuer (this value will be ignored, since "
"Cryptographer::ConfirmResponse is mocked out)");
EXPECT_EQ(ExecuteFinalizeAndWaitForResult(&helper, response_head.get()),
mojom::TrustTokenOperationStatus::kOk);
// After the operation has successfully finished, the trust tokens parsed from
// the server response should be in the store.
auto match_all_keys =
base::BindRepeating([](const std::string&) { return true; });
EXPECT_THAT(
store->RetrieveMatchingTokens(goodissuer, std::move(match_all_keys)),
ElementsAre(Property(&TrustToken::body, "a signed, unblinded token")));
}
class TrustTokenRequestIssuanceHelperTestWithPlatformIssuance
: public TrustTokenRequestIssuanceHelperTest {
public:
TrustTokenRequestIssuanceHelperTestWithPlatformIssuance() {
// This assertion helps safeguard against the brittleness of deserializing
// "true".
static_assert(
std::is_same<decltype(features::kPlatformProvidedTrustTokenIssuance
.default_value),
const bool>::value,
"Need to update this initialization logic if the type of the param "
"changes.");
features_.InitAndEnableFeatureWithParameters(
features::kTrustTokens,
{{features::kPlatformProvidedTrustTokenIssuance.name, "true"}});
}
private:
base::test::ScopedFeatureList features_;
};
TEST_F(TrustTokenRequestIssuanceHelperTestWithPlatformIssuance,
DiversionToOsSuccess) {
base::HistogramTester histograms;
// When an operation's diverted to the operating system and succeeds, we
// should report kOperationSuccessfullyFulfilledLocally.
std::unique_ptr<TrustTokenStore> store = TrustTokenStore::CreateForTesting();
SuitableTrustTokenOrigin issuer =
*SuitableTrustTokenOrigin::Create(GURL("https://issuer.com/"));
auto key_commitment_result = ReasonableKeyCommitmentResult();
key_commitment_result->request_issuance_locally_on.push_back(
mojom::TrustTokenKeyCommitmentResult::Os::kAndroid);
key_commitment_result->unavailable_local_operation_fallback =
mojom::TrustTokenKeyCommitmentResult::UnavailableLocalOperationFallback::
kReturnWithError;
auto getter = std::make_unique<FixedKeyCommitmentGetter>(
issuer, std::move(key_commitment_result));
// Have the Trust Tokens issuance conclude by the underlying cryptographic
// library returning one signed, unblinded token associated with the same
// returned from the key commitment.
auto unblinded_tokens = std::make_unique<UnblindedTokens>();
unblinded_tokens->body_of_verifying_key =
ReasonableKeyCommitmentResult()->keys.front()->body;
unblinded_tokens->tokens.push_back("a signed, unblinded token");
auto cryptographer = std::make_unique<MockCryptographer>();
EXPECT_CALL(*cryptographer, Initialize(_, _)).WillOnce(Return(true));
EXPECT_CALL(*cryptographer, AddKey(_)).WillOnce(Return(true));
EXPECT_CALL(*cryptographer, BeginIssuance(_))
.WillOnce(
Return(std::string("this string contains some blinded tokens")));
EXPECT_CALL(*cryptographer, ConfirmIssuance(StrEq("response from issuer")))
.WillOnce(Return(ByMove(std::move((unblinded_tokens)))));
auto local_operation_delegate =
std::make_unique<MockLocalOperationDelegate>();
auto answer = mojom::FulfillTrustTokenIssuanceAnswer::New();
answer->status = mojom::FulfillTrustTokenIssuanceAnswer::Status::kOk;
answer->response = "response from issuer";
auto receive_answer =
[&answer](
base::OnceCallback<void(mojom::FulfillTrustTokenIssuanceAnswerPtr)>
callback) { std::move(callback).Run(std::move(answer)); };
EXPECT_CALL(*local_operation_delegate, FulfillIssuance(_, _))
.WillOnce(WithArgs<1>(Invoke(receive_answer)));
TrustTokenRequestIssuanceHelper helper(
*SuitableTrustTokenOrigin::Create(GURL("https://toplevel.com/")),
store.get(), getter.get(), absl::nullopt, absl::nullopt,
std::move(cryptographer), std::move(local_operation_delegate),
base::BindRepeating([](mojom::TrustTokenKeyCommitmentResult::Os os) {
return os == mojom::TrustTokenKeyCommitmentResult::Os::kAndroid;
}),
g_metrics_delegate.get());
auto request = MakeURLRequest("https://issuer.com/");
request->set_initiator(issuer);
ASSERT_EQ(
ExecuteBeginOperationAndWaitForResult(&helper, request.get()),
mojom::TrustTokenOperationStatus::kOperationSuccessfullyFulfilledLocally);
// After the operation has successfully finished, the trust tokens parsed from
// the server response should be in the store.
auto match_all_keys =
base::BindRepeating([](const std::string&) { return true; });
EXPECT_THAT(
store->RetrieveMatchingTokens(issuer, std::move(match_all_keys)),
ElementsAre(Property(&TrustToken::body, "a signed, unblinded token")));
histograms.ExpectUniqueSample(
"Net.TrustTokens.IssuanceHelperLocalFulfillResult",
mojom::FulfillTrustTokenIssuanceAnswer::Status::kOk,
/*expected_count=*/1);
}
TEST_F(TrustTokenRequestIssuanceHelperTestWithPlatformIssuance,
ErrorDivertingToOs) {
// When an operation's diverted to the operating system and fails because the
// operation can't be executed in the current environment, but *not* because
// we're on the wrong OS, we should report kUnavailable (no matter the
// issuer's configured fallback).
std::unique_ptr<TrustTokenStore> store = TrustTokenStore::CreateForTesting();
SuitableTrustTokenOrigin issuer =
*SuitableTrustTokenOrigin::Create(GURL("https://issuer.com/"));
auto key_commitment_result = ReasonableKeyCommitmentResult();
key_commitment_result->request_issuance_locally_on.push_back(
mojom::TrustTokenKeyCommitmentResult::Os::kAndroid);
key_commitment_result->unavailable_local_operation_fallback =
mojom::TrustTokenKeyCommitmentResult::UnavailableLocalOperationFallback::
kWebIssuance;
auto getter = std::make_unique<FixedKeyCommitmentGetter>(
issuer, std::move(key_commitment_result));
auto cryptographer = std::make_unique<MockCryptographer>();
EXPECT_CALL(*cryptographer, Initialize(_, _)).WillOnce(Return(true));
EXPECT_CALL(*cryptographer, AddKey(_)).WillOnce(Return(true));
EXPECT_CALL(*cryptographer, BeginIssuance(_))
.WillOnce(
Return(std::string("this string contains some blinded tokens")));
// Respond to the local issuance request with kNotFound, indicating that there
// was no way to execute the operation locally:
auto local_operation_delegate =
std::make_unique<MockLocalOperationDelegate>();
auto answer = mojom::FulfillTrustTokenIssuanceAnswer::New();
answer->status = mojom::FulfillTrustTokenIssuanceAnswer::Status::kNotFound;
auto receive_answer =
[&answer](
base::OnceCallback<void(mojom::FulfillTrustTokenIssuanceAnswerPtr)>
callback) { std::move(callback).Run(std::move(answer)); };
EXPECT_CALL(*local_operation_delegate, FulfillIssuance(_, _))
.WillOnce(WithArgs<1>(Invoke(receive_answer)));
TrustTokenRequestIssuanceHelper helper(
*SuitableTrustTokenOrigin::Create(GURL("https://toplevel.com/")),
store.get(), getter.get(), absl::nullopt, absl::nullopt,
std::move(cryptographer), std::move(local_operation_delegate),
base::BindRepeating([](mojom::TrustTokenKeyCommitmentResult::Os os) {
return os == mojom::TrustTokenKeyCommitmentResult::Os::kAndroid;
}),
g_metrics_delegate.get());
auto request = MakeURLRequest("https://issuer.com/");
request->set_initiator(issuer);
ASSERT_EQ(ExecuteBeginOperationAndWaitForResult(&helper, request.get()),
mojom::TrustTokenOperationStatus::kUnavailable);
}
TEST_F(TrustTokenRequestIssuanceHelperTestWithPlatformIssuance,
DiversionToOsWrongOsWithFallbackConfiguredToReturnWithError) {
// When an operation can't be diverted to the operating system because the
// current platform wasn't among those specified by the issuer for local
// execution, and the issuer specified a fallback behavior of
// "return_with_error" in its key commitment, we should return kUnavailable.
std::unique_ptr<TrustTokenStore> store = TrustTokenStore::CreateForTesting();
SuitableTrustTokenOrigin issuer =
*SuitableTrustTokenOrigin::Create(GURL("https://issuer.com/"));
auto key_commitment_result = ReasonableKeyCommitmentResult();
key_commitment_result->request_issuance_locally_on.push_back(
mojom::TrustTokenKeyCommitmentResult::Os::kAndroid);
key_commitment_result->unavailable_local_operation_fallback =
mojom::TrustTokenKeyCommitmentResult::UnavailableLocalOperationFallback::
kReturnWithError;
auto getter = std::make_unique<FixedKeyCommitmentGetter>(
issuer, std::move(key_commitment_result));
TrustTokenRequestIssuanceHelper helper(
*SuitableTrustTokenOrigin::Create(GURL("https://toplevel.com/")),
store.get(), getter.get(), absl::nullopt, absl::nullopt,
std::make_unique<MockCryptographer>(),
std::make_unique<MockLocalOperationDelegate>(),
// Fail to match to the current OS...
base::BindLambdaForTesting(
[](mojom::TrustTokenKeyCommitmentResult::Os) { return false; }),
g_metrics_delegate.get());
auto request = MakeURLRequest("https://issuer.com/");
request->set_initiator(issuer);
// ... and the operation should fail.
ASSERT_EQ(ExecuteBeginOperationAndWaitForResult(&helper, request.get()),
mojom::TrustTokenOperationStatus::kUnavailable);
}
TEST_F(TrustTokenRequestIssuanceHelperTestWithPlatformIssuance,
DiversionToOsWrongOsWithFallbackConfiguredToRequestWebIssuance) {
// When an operation can't be diverted to the operating system because the
// current platform wasn't among those specified by the issuer for local
// execution, and the issuer specified a fallback behavior of
// "web_issuance" in its key commitment, we should attempt a web issuance.
std::unique_ptr<TrustTokenStore> store = TrustTokenStore::CreateForTesting();
SuitableTrustTokenOrigin issuer =
*SuitableTrustTokenOrigin::Create(GURL("https://issuer.com/"));
auto key_commitment_result = ReasonableKeyCommitmentResult();
key_commitment_result->request_issuance_locally_on.push_back(
mojom::TrustTokenKeyCommitmentResult::Os::kAndroid);
key_commitment_result->unavailable_local_operation_fallback =
mojom::TrustTokenKeyCommitmentResult::UnavailableLocalOperationFallback::
kWebIssuance;
auto getter = std::make_unique<FixedKeyCommitmentGetter>(
issuer, std::move(key_commitment_result));
auto cryptographer = std::make_unique<MockCryptographer>();
EXPECT_CALL(*cryptographer, Initialize(_, _)).WillOnce(Return(true));
EXPECT_CALL(*cryptographer, AddKey(_)).WillOnce(Return(true));
EXPECT_CALL(*cryptographer, BeginIssuance(_))
.WillOnce(
Return(std::string("this string contains some blinded tokens")));
TrustTokenRequestIssuanceHelper helper(
*SuitableTrustTokenOrigin::Create(GURL("https://toplevel.com/")),
store.get(), getter.get(), absl::nullopt, absl::nullopt,
std::move(cryptographer), std::make_unique<MockLocalOperationDelegate>(),
// Fail to match to the current OS...
base::BindLambdaForTesting(
[](mojom::TrustTokenKeyCommitmentResult::Os) { return false; }),
g_metrics_delegate.get());
auto request = MakeURLRequest("https://issuer.com/");
request->set_initiator(issuer);
// ... and the helper should finish beginning a standard Web issuance, because
// the issuer configured a fallback of |kWebIssuance|.
ASSERT_EQ(ExecuteBeginOperationAndWaitForResult(&helper, request.get()),
mojom::TrustTokenOperationStatus::kOk);
}
TEST_F(TrustTokenRequestIssuanceHelperTestWithPlatformIssuance,
DivertsToAndroidOnAndroid) {
base::HistogramTester histograms;
// Test that, when an issuer specifies that we should attempt issuance locally
// on Android, we do so.
std::unique_ptr<TrustTokenStore> store = TrustTokenStore::CreateForTesting();
SuitableTrustTokenOrigin issuer =
*SuitableTrustTokenOrigin::Create(GURL("https://issuer.com/"));
auto key_commitment_result = ReasonableKeyCommitmentResult();
// Specify that we should request issuance locally on Android...
key_commitment_result->request_issuance_locally_on.push_back(
mojom::TrustTokenKeyCommitmentResult::Os::kAndroid);
key_commitment_result->unavailable_local_operation_fallback =
mojom::TrustTokenKeyCommitmentResult::UnavailableLocalOperationFallback::
kReturnWithError;
auto getter = std::make_unique<FixedKeyCommitmentGetter>(
issuer, std::move(key_commitment_result));
auto cryptographer = std::make_unique<MockCryptographer>();
// Use strict mocks to make the test fail if the delegates get called on
// non-Android platforms:
auto local_operation_delegate =
std::make_unique<StrictMock<MockLocalOperationDelegate>>();
auto metrics_delegate = std::make_unique<StrictMock<MockMetricsDelegate>>();
#if BUILDFLAG(IS_ANDROID)
EXPECT_CALL(*cryptographer, Initialize(_, _)).WillOnce(Return(true));
EXPECT_CALL(*cryptographer, AddKey(_)).WillOnce(Return(true));
EXPECT_CALL(*cryptographer, BeginIssuance(_))
.WillOnce(
Return(std::string("this string contains some blinded tokens")));
// If we're on Android, respond to the local issuance request with an error.
auto answer = mojom::FulfillTrustTokenIssuanceAnswer::New();
answer->status =
mojom::FulfillTrustTokenIssuanceAnswer::Status::kUnknownError;
auto receive_answer =
[&answer](
base::OnceCallback<void(mojom::FulfillTrustTokenIssuanceAnswerPtr)>
callback) { std::move(callback).Run(std::move(answer)); };
EXPECT_CALL(*local_operation_delegate, FulfillIssuance(_, _))
.WillOnce(WithArgs<1>(Invoke(receive_answer)));
EXPECT_CALL(*metrics_delegate, WillExecutePlatformProvidedOperation());
#endif // BUILDFLAG(IS_ANDROID)
TrustTokenRequestIssuanceHelper helper(
*SuitableTrustTokenOrigin::Create(GURL("https://toplevel.com/")),
store.get(), getter.get(), absl::nullopt, absl::nullopt,
std::move(cryptographer), std::move(local_operation_delegate),
base::BindRepeating(&IsCurrentOperatingSystem), metrics_delegate.get());
auto request = MakeURLRequest("https://issuer.com/");
request->set_initiator(issuer);
// If we're on Android, we should observe the error returned by the delegate;
// if we're not on Android, we should observe error saying we couldn't execute
// issuance locally.
ASSERT_EQ(ExecuteBeginOperationAndWaitForResult(&helper, request.get()),
#if BUILDFLAG(IS_ANDROID)
mojom::TrustTokenOperationStatus::kUnknownError
#else
mojom::TrustTokenOperationStatus::kUnavailable
#endif // BUILDFLAG(IS_ANDROID)
);
#if BUILDFLAG(IS_ANDROID)
histograms.ExpectUniqueSample(
"Net.TrustTokens.IssuanceHelperLocalFulfillResult",
mojom::FulfillTrustTokenIssuanceAnswer::Status::kUnknownError,
/*expected_count=*/1);
#endif // BUILDFLAG(IS_ANDROID)
}
} // namespace network