Commit 2733f907 authored by David Van Cleve's avatar David Van Cleve Committed by Commit Bot

Update Trust Tokens store to accommodate issuance requirements.

This CL updates the Trust Tokens store to handle a few requirements for
token issuance:
1. Allow adding tokens to fail if there is no stored key commitment for
the tokens' issuer with value equal to that of the tokens' associated
key.  (Previously, this was DCHECKED as a precondition, but it's easier
to handle within the store.)
2. Add a capacity limiting the number of tokens stored per issuer, and
make TrustTokenStore::AddTokens respect this capacity.
3. Add a number-of-tokens-for-issuer getter so clients can check (for
instance) if tokens are at capacity without attempting to add a token.
4. Add a cap on the number of issuers allowed to be associated with any
given top level origin, and make TrustTokenStore::SetAssociated respect
this limit.

Bug: 1042962
Test: Expand unittests.
Change-Id: I544aa64eeeab1a1f6c777631f1579d2402fd179e
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2057523Reviewed-by: default avatarCharlie Harrison <csharrison@chromium.org>
Commit-Queue: David Van Cleve <davidvc@chromium.org>
Cr-Commit-Position: refs/heads/master@{#749328}
parent 7b7ca106
...@@ -41,6 +41,21 @@ constexpr char kTrustTokenKeyCommitmentWellKnownPath[] = ...@@ -41,6 +41,21 @@ constexpr char kTrustTokenKeyCommitmentWellKnownPath[] =
// A value of 4 MiB should be ample for initial experimentation and can be // A value of 4 MiB should be ample for initial experimentation and can be
// revisited if necessary. // revisited if necessary.
constexpr size_t kTrustTokenKeyCommitmentRegistryMaxSizeBytes = 1 << 22; constexpr size_t kTrustTokenKeyCommitmentRegistryMaxSizeBytes = 1 << 22;
// The maximum number of (signed, unblinded) trust tokens allowed to be stored
// concurrently, scoped per token issuer.
//
// 500 is chosen as a high-but-not-excessive value for initial experimentation.
constexpr int kTrustTokenPerIssuerTokenCapacity = 500;
// The maximum number of trust token issuers allowed to be associated with a
// given top-level origin.
//
// This value is quite low because registering additional issuers with an origin
// has a number of privacy risks (for instance, whether or not a user has any
// tokens issued by a given issuer reveals one bit of identifying information).
constexpr int kTrustTokenPerToplevelMaxNumberOfAssociatedIssuers = 2;
} // namespace network } // namespace network
#endif // SERVICES_NETWORK_TRUST_TOKENS_TRUST_TOKEN_PARAMETERIZATION_H_ #endif // SERVICES_NETWORK_TRUST_TOKENS_TRUST_TOKEN_PARAMETERIZATION_H_
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
#include "services/network/trust_tokens/in_memory_trust_token_persister.h" #include "services/network/trust_tokens/in_memory_trust_token_persister.h"
#include "services/network/trust_tokens/proto/public.pb.h" #include "services/network/trust_tokens/proto/public.pb.h"
#include "services/network/trust_tokens/proto/storage.pb.h" #include "services/network/trust_tokens/proto/storage.pb.h"
#include "services/network/trust_tokens/trust_token_parameterization.h"
#include "services/network/trust_tokens/types.h" #include "services/network/trust_tokens/types.h"
#include "third_party/protobuf/src/google/protobuf/repeated_field.h" #include "third_party/protobuf/src/google/protobuf/repeated_field.h"
...@@ -127,7 +128,7 @@ bool TrustTokenStore::IsAssociated(const url::Origin& issuer, ...@@ -127,7 +128,7 @@ bool TrustTokenStore::IsAssociated(const url::Origin& issuer,
return base::Contains(config->associated_issuers(), issuer.Serialize()); return base::Contains(config->associated_issuers(), issuer.Serialize());
} }
void TrustTokenStore::SetAssociation(const url::Origin& issuer, bool TrustTokenStore::SetAssociation(const url::Origin& issuer,
const url::Origin& top_level) { const url::Origin& top_level) {
DCHECK(!issuer.opaque()); DCHECK(!issuer.opaque());
DCHECK(!top_level.opaque()); DCHECK(!top_level.opaque());
...@@ -136,10 +137,19 @@ void TrustTokenStore::SetAssociation(const url::Origin& issuer, ...@@ -136,10 +137,19 @@ void TrustTokenStore::SetAssociation(const url::Origin& issuer,
if (!config) if (!config)
config = std::make_unique<TrustTokenToplevelConfig>(); config = std::make_unique<TrustTokenToplevelConfig>();
auto string_issuer = issuer.Serialize(); auto string_issuer = issuer.Serialize();
if (!base::Contains(config->associated_issuers(), string_issuer)) {
config->add_associated_issuers(std::move(string_issuer)); if (base::Contains(config->associated_issuers(), string_issuer))
persister_->SetToplevelConfig(top_level, std::move(config)); return true;
if (config->associated_issuers_size() >=
kTrustTokenPerToplevelMaxNumberOfAssociatedIssuers) {
return false;
} }
config->add_associated_issuers(std::move(string_issuer));
persister_->SetToplevelConfig(top_level, std::move(config));
return true;
} }
std::vector<TrustTokenKeyCommitment> TrustTokenStore::KeyCommitments( std::vector<TrustTokenKeyCommitment> TrustTokenStore::KeyCommitments(
...@@ -225,24 +235,45 @@ base::Optional<int> TrustTokenStore::BatchSize(const url::Origin& issuer) { ...@@ -225,24 +235,45 @@ base::Optional<int> TrustTokenStore::BatchSize(const url::Origin& issuer) {
return config->batch_size(); return config->batch_size();
} }
void TrustTokenStore::AddTokens(const url::Origin& issuer, bool TrustTokenStore::AddTokens(const url::Origin& issuer,
base::span<const std::string> token_bodies, base::span<const std::string> token_bodies,
base::StringPiece issuing_key) { base::StringPiece issuing_key) {
DCHECK(!issuer.opaque()); DCHECK(!issuer.opaque());
auto config = persister_->GetIssuerConfig(issuer); auto config = persister_->GetIssuerConfig(issuer);
DCHECK(config && if (!config)
std::any_of(config->keys().begin(), config->keys().end(), return false;
[issuing_key](const TrustTokenKeyCommitment& commitment) {
return commitment.key() == issuing_key;
}));
for (const auto& token_body : token_bodies) { // Only allow storing tokens with an issuing key that is currently present in
// this token store (i.e., the token's issuer provided this key in a
// previous key commitment, and the key has not yet expired).
bool key_is_present =
std::any_of(config->keys().begin(), config->keys().end(),
[issuing_key](const TrustTokenKeyCommitment& commitment) {
return commitment.key() == issuing_key;
});
if (!key_is_present)
return false;
for (auto it = token_bodies.begin();
it != token_bodies.end() &&
config->tokens_size() < kTrustTokenPerIssuerTokenCapacity;
++it) {
TrustToken* entry = config->add_tokens(); TrustToken* entry = config->add_tokens();
entry->set_body(token_body); entry->set_body(*it);
entry->set_signing_key(std::string(issuing_key)); entry->set_signing_key(std::string(issuing_key));
} }
persister_->SetIssuerConfig(issuer, std::move(config)); persister_->SetIssuerConfig(issuer, std::move(config));
return true;
}
int TrustTokenStore::CountTokens(const url::Origin& issuer) {
DCHECK(!issuer.opaque());
auto config = persister_->GetIssuerConfig(issuer);
if (!config)
return 0;
return config->tokens_size();
} }
std::vector<TrustToken> TrustTokenStore::RetrieveMatchingTokens( std::vector<TrustToken> TrustTokenStore::RetrieveMatchingTokens(
......
...@@ -120,12 +120,18 @@ class TrustTokenStore { ...@@ -120,12 +120,18 @@ class TrustTokenStore {
WARN_UNUSED_RESULT virtual bool IsAssociated(const url::Origin& issuer, WARN_UNUSED_RESULT virtual bool IsAssociated(const url::Origin& issuer,
const url::Origin& top_level); const url::Origin& top_level);
// Associates |issuer| with |top_level|. (It's the caller's responsibility to // If associating |issuer| with |top_level| would exceed the cap on the number
// enforce any cap on the number of top levels per issuer.) // of issuers allowed to be associated with a given top-level origin, returns
// false. Otherwise, associates |issuer| with |top_level| and returns true.
//
// TODO(crbug.com/1060716): As part of adding solid support for multiple
// issuers, it'd be good to make these associations expire after some
// reasonably long amount of time, so that top-level origins can change their
// minds about their associated issuers.
// //
// |issuer| and |top_level| must not be opaque. // |issuer| and |top_level| must not be opaque.
virtual void SetAssociation(const url::Origin& issuer, WARN_UNUSED_RESULT virtual bool SetAssociation(const url::Origin& issuer,
const url::Origin& top_level); const url::Origin& top_level);
//// Methods related to reading and writing issuer values configured via key //// Methods related to reading and writing issuer values configured via key
//// commitment queries, such as key commitments and batch sizes: //// commitment queries, such as key commitments and batch sizes:
...@@ -173,16 +179,24 @@ class TrustTokenStore { ...@@ -173,16 +179,24 @@ class TrustTokenStore {
//// Methods related to reading and writing signed tokens: //// Methods related to reading and writing signed tokens:
// Associates to the given issuer additional signed // If |issuer| does not have a stored key commitment corresponding to
// |issuing_key|, returns false.
//
// Otherwise, associates to the given issuer additional signed
// trust tokens with: // trust tokens with:
// - token bodies given by |token_bodies| // - token bodies given by |token_bodies|
// - signing keys given by |issuing_key|. // - signing keys given by |issuing_key|.
// //
// |issuer| must not be opaque and must have a stored // |issuer| must not be opaque.
// key commitment corresponding to |issuing_key|. WARN_UNUSED_RESULT virtual bool AddTokens(
virtual void AddTokens(const url::Origin& issuer, const url::Origin& issuer,
base::span<const std::string> token_bodies, base::span<const std::string> token_bodies,
base::StringPiece issuing_key); base::StringPiece issuing_key);
// Returns the number of tokens stored for |issuer|.
//
// |issuer| must not be opaque.
WARN_UNUSED_RESULT virtual int CountTokens(const url::Origin& issuer);
// Returns all signed tokens from |issuer| signed by keys matching // Returns all signed tokens from |issuer| signed by keys matching
// the given predicate. // the given predicate.
......
...@@ -13,6 +13,7 @@ ...@@ -13,6 +13,7 @@
#include "services/network/trust_tokens/in_memory_trust_token_persister.h" #include "services/network/trust_tokens/in_memory_trust_token_persister.h"
#include "services/network/trust_tokens/proto/public.pb.h" #include "services/network/trust_tokens/proto/public.pb.h"
#include "services/network/trust_tokens/proto/storage.pb.h" #include "services/network/trust_tokens/proto/storage.pb.h"
#include "services/network/trust_tokens/trust_token_parameterization.h"
#include "testing/gmock/include/gmock/gmock.h" #include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h" #include "url/gurl.h"
...@@ -188,10 +189,42 @@ TEST(TrustTokenStore, AssociatesToplevelsWithIssuers) { ...@@ -188,10 +189,42 @@ TEST(TrustTokenStore, AssociatesToplevelsWithIssuers) {
// the store should think that that issuer is associated // the store should think that that issuer is associated
// with that toplevel. // with that toplevel.
my_store.SetAssociation(issuer, toplevel); EXPECT_TRUE(my_store.SetAssociation(issuer, toplevel));
EXPECT_TRUE(my_store.IsAssociated(issuer, toplevel)); EXPECT_TRUE(my_store.IsAssociated(issuer, toplevel));
} }
// Test that issuer-toplevel association works correctly when a toplevel's
// number-of-issuance cap has been reached: reassocating an already-associated
// issuer should succeed, while associating any other issuer should fail.
TEST(TrustTokenStore, IssuerToplevelAssociationAtNumberOfAssociationsCap) {
auto persister = std::make_unique<InMemoryTrustTokenPersister>();
url::Origin toplevel = url::Origin::Create(GURL("https://toplevel.com"));
url::Origin issuer = url::Origin::Create(GURL("https://issuer.com"));
auto config = std::make_unique<TrustTokenToplevelConfig>();
for (int i = 0; i < kTrustTokenPerToplevelMaxNumberOfAssociatedIssuers - 1;
++i)
config->add_associated_issuers();
*config->add_associated_issuers() = issuer.Serialize();
persister->SetToplevelConfig(toplevel, std::move(config));
TrustTokenStore my_store(std::move(persister));
// Sanity check that the test set the config up correctly.
ASSERT_TRUE(my_store.IsAssociated(issuer, toplevel));
// Even though we're at the cap, SetAssociation for an already-associated
// toplevel should return true.
EXPECT_TRUE(my_store.SetAssociation(issuer, toplevel));
// Since we're at the cap, SetAssociation for an issuer not already associated
// with the top-level origin should fail.
EXPECT_FALSE(my_store.SetAssociation(
url::Origin::Create(GURL("https://someotherissuer.com")), toplevel));
}
TEST(TrustTokenStore, StoresKeyCommitments) { TEST(TrustTokenStore, StoresKeyCommitments) {
// A newly initialized store should not think // A newly initialized store should not think
// any issuers have committed keys. // any issuers have committed keys.
...@@ -267,6 +300,78 @@ TEST(TrustTokenStore, KeyUpdateRemovesNonupdatedKeys) { ...@@ -267,6 +300,78 @@ TEST(TrustTokenStore, KeyUpdateRemovesNonupdatedKeys) {
EXPECT_TRUE(my_store.KeyCommitments(issuer).empty()); EXPECT_TRUE(my_store.KeyCommitments(issuer).empty());
} }
TEST(TrustTokenStore, WontAddTokensWithoutMatchingIssuerState) {
TrustTokenStore my_store(std::make_unique<InMemoryTrustTokenPersister>());
url::Origin issuer = url::Origin::Create(GURL("https://issuer.com"));
// Since the store has absolutely no state stored for |issuer|---and, in
// particular, no commitment corresponding to the given key---the insert
// should fail.
EXPECT_FALSE(my_store.AddTokens(issuer,
std::vector<std::string>{"some token body"},
/*issuing_key=*/"key"));
}
TEST(TrustTokenStore, WontAddTokensWithoutMatchingKeyCommitment) {
TrustTokenStore my_store(std::make_unique<InMemoryTrustTokenPersister>());
url::Origin issuer = url::Origin::Create(GURL("https://issuer.com"));
my_store.SetBatchSize(issuer, 5);
// Since the store has no commitment corresponding to the given key, even
// though it has some other state stored for the issuer, the insert should
// fail.
EXPECT_FALSE(
my_store.AddTokens(issuer, std::vector<std::string>{"some token body"},
/*issuing_key=*/
"some key corresponding to the"
"token; since this isn't in a stored key commitment "
"for |issuer|, the insert should fail"));
}
TEST(TrustTokenStore, AddingTokensRespectsCapacity) {
TrustTokenStore my_store(std::make_unique<InMemoryTrustTokenPersister>());
url::Origin issuer = url::Origin::Create(GURL("https://issuer.com"));
// Add a key with an empty body.
my_store.SetKeyCommitmentsAndPruneStaleState(
issuer, std::vector<TrustTokenKeyCommitment>(1));
// Attempting to add many, many tokens corresponding to that key should be
// successful, but the operation should only add a quantity of tokens equal to
// the difference between the number of currently-stored tokens and the
// capacity.
ASSERT_TRUE(my_store.AddTokens(
issuer, std::vector<std::string>(kTrustTokenPerIssuerTokenCapacity * 2),
/*issuing_key=*/
""));
EXPECT_EQ(my_store.CountTokens(issuer), kTrustTokenPerIssuerTokenCapacity);
}
TEST(TrustTokenStore, CountsTokens) {
TrustTokenStore my_store(std::make_unique<InMemoryTrustTokenPersister>());
url::Origin issuer = url::Origin::Create(GURL("https://issuer.com"));
// A freshly initialized store should be storing zero tokens.
EXPECT_EQ(my_store.CountTokens(issuer), 0);
// Add a key with an empty body.
my_store.SetKeyCommitmentsAndPruneStaleState(
issuer, std::vector<TrustTokenKeyCommitment>{TrustTokenKeyCommitment()});
// Add a token; the count should increase.
ASSERT_TRUE(my_store.AddTokens(issuer, std::vector<std::string>(1),
/*issuing_key=*/""));
EXPECT_EQ(my_store.CountTokens(issuer), 1);
// Add two more tokens; the count should change accordingly.
ASSERT_TRUE(my_store.AddTokens(issuer, std::vector<std::string>(2),
/*issuing_key=*/""));
EXPECT_EQ(my_store.CountTokens(issuer), 3);
}
TEST(TrustTokenStore, PrunesDataAssociatedWithRemovedKeyCommitments) { TEST(TrustTokenStore, PrunesDataAssociatedWithRemovedKeyCommitments) {
// Removing a committed key should result in trust tokens // Removing a committed key should result in trust tokens
// associated with the removed key being pruned from the store. // associated with the removed key being pruned from the store.
...@@ -283,11 +388,13 @@ TEST(TrustTokenStore, PrunesDataAssociatedWithRemovedKeyCommitments) { ...@@ -283,11 +388,13 @@ TEST(TrustTokenStore, PrunesDataAssociatedWithRemovedKeyCommitments) {
issuer, issuer,
std::vector<TrustTokenKeyCommitment>{my_commitment, another_commitment}); std::vector<TrustTokenKeyCommitment>{my_commitment, another_commitment});
my_store.AddTokens(issuer, std::vector<std::string>{"some token body"}, EXPECT_TRUE(my_store.AddTokens(issuer,
my_commitment.key()); std::vector<std::string>{"some token body"},
my_commitment.key()));
my_store.AddTokens(issuer, std::vector<std::string>{"some other token body"}, EXPECT_TRUE(my_store.AddTokens(
another_commitment.key()); issuer, std::vector<std::string>{"some other token body"},
another_commitment.key()));
my_store.SetKeyCommitmentsAndPruneStaleState( my_store.SetKeyCommitmentsAndPruneStaleState(
issuer, std::vector<TrustTokenKeyCommitment>{another_commitment}); issuer, std::vector<TrustTokenKeyCommitment>{another_commitment});
...@@ -354,8 +461,8 @@ TEST(TrustTokenStore, AddsTrustTokens) { ...@@ -354,8 +461,8 @@ TEST(TrustTokenStore, AddsTrustTokens) {
TrustToken expected_token; TrustToken expected_token;
expected_token.set_body("some token"); expected_token.set_body("some token");
expected_token.set_signing_key(kMyKey); expected_token.set_signing_key(kMyKey);
my_store.AddTokens(issuer, std::vector<std::string>{expected_token.body()}, EXPECT_TRUE(my_store.AddTokens(
kMyKey); issuer, std::vector<std::string>{expected_token.body()}, kMyKey));
EXPECT_THAT(my_store.RetrieveMatchingTokens(issuer, match_all_keys), EXPECT_THAT(my_store.RetrieveMatchingTokens(issuer, match_all_keys),
ElementsAre(EqualsProto(expected_token))); ElementsAre(EqualsProto(expected_token)));
...@@ -384,12 +491,12 @@ TEST(TrustTokenStore, RetrievesTrustTokensRespectingNontrivialPredicate) { ...@@ -384,12 +491,12 @@ TEST(TrustTokenStore, RetrievesTrustTokensRespectingNontrivialPredicate) {
issuer, std::vector<TrustTokenKeyCommitment>{matching_commitment, issuer, std::vector<TrustTokenKeyCommitment>{matching_commitment,
nonmatching_commitment}); nonmatching_commitment});
my_store.AddTokens(issuer, std::vector<std::string>{expected_token.body()}, EXPECT_TRUE(my_store.AddTokens(
kMatchingKey); issuer, std::vector<std::string>{expected_token.body()}, kMatchingKey));
my_store.AddTokens( EXPECT_TRUE(my_store.AddTokens(
issuer, issuer,
std::vector<std::string>{"this one should get rejected by the predicate"}, std::vector<std::string>{"this one should get rejected by the predicate"},
kNonmatchingKey); kNonmatchingKey));
EXPECT_THAT(my_store.RetrieveMatchingTokens( EXPECT_THAT(my_store.RetrieveMatchingTokens(
issuer, base::BindRepeating( issuer, base::BindRepeating(
...@@ -425,9 +532,9 @@ TEST(TrustTokenStore, DeletesSingleToken) { ...@@ -425,9 +532,9 @@ TEST(TrustTokenStore, DeletesSingleToken) {
my_store.SetKeyCommitmentsAndPruneStaleState( my_store.SetKeyCommitmentsAndPruneStaleState(
issuer, std::vector<TrustTokenKeyCommitment>{my_commitment}); issuer, std::vector<TrustTokenKeyCommitment>{my_commitment});
my_store.AddTokens( EXPECT_TRUE(my_store.AddTokens(
issuer, std::vector<std::string>{first_token.body(), second_token.body()}, issuer, std::vector<std::string>{first_token.body(), second_token.body()},
my_commitment.key()); my_commitment.key()));
my_store.DeleteToken(issuer, first_token); my_store.DeleteToken(issuer, first_token);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment