Commit 8e9102fb authored by David Van Cleve's avatar David Van Cleve Committed by Commit Bot

[Trust tokens] Wrapper for protocol -> storage operations

This CL adds net::trust_tokens::Store, which translates between
Trust Tokens protocol-related operations and the underlying
key-value representation of the persistence layer
(net::trust_tokens::Persister).

Bug: 1036494
Change-Id: Ida2e2f85bb473ffb8057ba96583ddc76d689d558
Cq-Do-Not-Cancel-Tryjobs: true
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1986383Reviewed-by: default avatarAsanka Herath <asanka@chromium.org>
Commit-Queue: David Van Cleve <davidvc@chromium.org>
Cr-Commit-Position: refs/heads/master@{#733777}
parent 088f73dd
......@@ -6,13 +6,12 @@ import("//third_party/protobuf/proto_library.gni")
source_set("trust_tokens") {
visibility = [
"//net",
"//services/network",
":tests",
"//net/*",
"//services/network/",
]
# TODO(davidvc): Public API to be added in a subsequent CL.
public = []
public = [ "trust_token_store.h" ]
friend = [ ":tests" ]
......@@ -20,26 +19,35 @@ source_set("trust_tokens") {
"in_memory_trust_token_persister.cc",
"in_memory_trust_token_persister.h",
"trust_token_persister.h",
"trust_token_store.cc",
"types.cc",
"types.h",
]
deps = [
":public_proto",
":storage_proto",
"//base",
"//url",
]
public_deps = [ ":public_proto" ]
}
source_set("tests") {
testonly = true
sources = [ "trust_token_persister_unittest.cc" ]
sources = [
"trust_token_persister_unittest.cc",
"trust_token_store_unittest.cc",
"types_unittest.cc",
]
deps = [
":public_proto",
":storage_proto",
":trust_tokens",
"//base",
"//base/test:test_support",
"//testing/gmock",
"//testing/gtest",
"//url",
......
csharrison@chromium.org
svaldez@chromium.org
asanka@chromium.org
......@@ -8,13 +8,14 @@ package net;
option optimize_for = LITE_RUNTIME;
// A TrustTokenCommitmentKey message represents a single commitment key received
// from an issuer’s key commitments endpoint.
message TrustTokenCommitmentKey {
// The body of the keys. Used for comparison (when checking if
// A TrustTokenKeyCommitment message stores a single key received
// from an issuer’s key commitments endpoint, along with associated
// metadata.
message TrustTokenKeyCommitment {
// The key's body. Used for comparison (when checking if
// stored tokens’ keys are still current) and, by BoringSSL, for
// cryptographic operations.
optional bytes body = 1; // required
optional bytes key = 1; // required
// The expiry time of the key.
// (Here and elsewhere, string times are serialized base::Times
......@@ -36,7 +37,7 @@ message TrustToken {
// The key with which the Token was signed. Tokens
// are only provided to servers while their commitment keys
// remain active.
optional bytes commitment_key_body = 2; // required
optional bytes signing_key = 2; // required
}
// A SignedTrustTokenRedemptionRecord message stores state associated with a
......@@ -46,8 +47,9 @@ message TrustToken {
// with the SRR.
message SignedTrustTokenRedemptionRecord {
// The body of an SRR encodes information such as its top-level
// origin and its expiration time, but Chrome doesn’t control
// the encoding and uses a library to extract these values.
// origin and its expiration time. The encoding is opaque to //net; BoringSSL
// provides interfaces to read data from the body that is pertinent to
// protocol execution.
optional bytes body = 1;
// If one of public_key or signing_key is present, the other must also be
// present.
......
......@@ -10,7 +10,7 @@ option optimize_for = LITE_RUNTIME;
import "public.proto";
// An TrustTokenIssuerConfig message represents persistent state scoped
// A TrustTokenIssuerConfig message represents persistent state scoped
// to a single Trust Tokens issuer origin.
// An issuer’s config contains:
// - any signed-but-unspent tokens received from the issuer; and
......@@ -18,8 +18,8 @@ import "public.proto";
// per issuance request), if it has configured one via its key
// commitment endpoint.
message TrustTokenIssuerConfig {
// Keys the issuer has recently committed (we don’t need to store stale keys)
repeated TrustTokenCommitmentKey keys = 1;
// Keys the issuer has recently committed (no need to store stale keys)
repeated TrustTokenKeyCommitment keys = 1;
optional int32 batch_size = 2;
repeated TrustToken tokens = 3;
// The time of the most recent issuance for this pair. Used for
......
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "net/trust_tokens/trust_token_store.h"
#include <memory>
#include <utility>
#include "base/optional.h"
#include "net/trust_tokens/proto/public.pb.h"
#include "net/trust_tokens/proto/storage.pb.h"
#include "net/trust_tokens/types.h"
#include "third_party/protobuf/src/google/protobuf/repeated_field.h"
namespace net {
namespace {
// Until the underlying BoringSSL functionality is implemented to extract
// expiry timestamps from Signed Redemption Record bodies, default to
// never expiring stored SRRs.
class NeverExpiringExpiryDelegate
: public TrustTokenStore::RecordExpiryDelegate {
public:
bool IsRecordExpired(
const SignedTrustTokenRedemptionRecord& record) override {
return false;
}
};
} // namespace
TrustTokenStore::TrustTokenStore(std::unique_ptr<TrustTokenPersister> persister)
: TrustTokenStore(std::move(persister),
std::make_unique<NeverExpiringExpiryDelegate>()) {}
TrustTokenStore::TrustTokenStore(
std::unique_ptr<TrustTokenPersister> persister,
std::unique_ptr<RecordExpiryDelegate> expiry_delegate_for_testing)
: persister_(std::move(persister)),
record_expiry_delegate_(std::move(expiry_delegate_for_testing)) {
DCHECK(persister_);
}
TrustTokenStore::~TrustTokenStore() = default;
void TrustTokenStore::RecordIssuance(const url::Origin& issuer) {
DCHECK(!issuer.opaque());
url::Origin issuer_origin = issuer;
std::unique_ptr<TrustTokenIssuerConfig> config =
persister_->GetIssuerConfig(issuer);
if (!config)
config = std::make_unique<TrustTokenIssuerConfig>();
config->set_last_issuance(internal::TimeToString(base::Time::Now()));
persister_->SetIssuerConfig(issuer, std::move(config));
}
base::Optional<base::TimeDelta> TrustTokenStore::TimeSinceLastIssuance(
const url::Origin& issuer) {
DCHECK(!issuer.opaque());
std::unique_ptr<TrustTokenIssuerConfig> config =
persister_->GetIssuerConfig(issuer);
if (!config)
return base::nullopt;
if (!config->has_last_issuance())
return base::nullopt;
base::Optional<base::Time> maybe_last_issuance =
internal::StringToTime(config->last_issuance());
if (!maybe_last_issuance)
return base::nullopt;
base::TimeDelta ret = base::Time::Now() - *maybe_last_issuance;
if (ret < base::TimeDelta())
return base::nullopt;
return ret;
}
void TrustTokenStore::RecordRedemption(const url::Origin& issuer,
const url::Origin& top_level) {
DCHECK(!issuer.opaque());
DCHECK(!top_level.opaque());
std::unique_ptr<TrustTokenIssuerToplevelPairConfig> config =
persister_->GetIssuerToplevelPairConfig(issuer, top_level);
if (!config)
config = std::make_unique<TrustTokenIssuerToplevelPairConfig>();
config->set_last_redemption(internal::TimeToString(base::Time::Now()));
persister_->SetIssuerToplevelPairConfig(issuer, top_level, std::move(config));
}
base::Optional<base::TimeDelta> TrustTokenStore::TimeSinceLastRedemption(
const url::Origin& issuer,
const url::Origin& top_level) {
DCHECK(!issuer.opaque());
DCHECK(!top_level.opaque());
auto config = persister_->GetIssuerToplevelPairConfig(issuer, top_level);
if (!config)
return base::nullopt;
if (!config->has_last_redemption())
return base::nullopt;
base::Optional<base::Time> maybe_last_redemption =
internal::StringToTime(config->last_redemption());
// internal::StringToTime can fail in the case of data corruption (or writer
// error).
if (!maybe_last_redemption)
return base::nullopt;
base::TimeDelta ret = base::Time::Now() - *maybe_last_redemption;
if (ret < base::TimeDelta())
return base::nullopt;
return ret;
}
bool TrustTokenStore::IsAssociated(const url::Origin& issuer,
const url::Origin& top_level) {
DCHECK(!issuer.opaque());
DCHECK(!top_level.opaque());
std::unique_ptr<TrustTokenToplevelConfig> config =
persister_->GetToplevelConfig(top_level);
if (!config)
return false;
return base::Contains(config->associated_issuers(), issuer.Serialize());
}
void TrustTokenStore::SetAssociation(const url::Origin& issuer,
const url::Origin& top_level) {
DCHECK(!issuer.opaque());
DCHECK(!top_level.opaque());
std::unique_ptr<TrustTokenToplevelConfig> config =
persister_->GetToplevelConfig(top_level);
if (!config)
config = std::make_unique<TrustTokenToplevelConfig>();
auto string_issuer = issuer.Serialize();
if (!base::Contains(config->associated_issuers(), string_issuer)) {
config->add_associated_issuers(std::move(string_issuer));
persister_->SetToplevelConfig(top_level, std::move(config));
}
}
std::vector<TrustTokenKeyCommitment> TrustTokenStore::KeyCommitments(
const url::Origin& issuer) {
DCHECK(!issuer.opaque());
std::unique_ptr<TrustTokenIssuerConfig> config =
persister_->GetIssuerConfig(issuer);
if (!config)
return std::vector<TrustTokenKeyCommitment>();
return std::vector<TrustTokenKeyCommitment>(config->keys().begin(),
config->keys().end());
}
void TrustTokenStore::SetKeyCommitmentsAndPruneStaleState(
const url::Origin& issuer,
base::span<const TrustTokenKeyCommitment> keys) {
DCHECK(!issuer.opaque());
DCHECK([&keys]() {
std::set<base::StringPiece> unique_keys;
for (const auto& key : keys)
unique_keys.insert(base::StringPiece(key.key()));
return unique_keys.size() == keys.size();
}());
std::unique_ptr<TrustTokenIssuerConfig> config =
persister_->GetIssuerConfig(issuer);
if (!config)
config = std::make_unique<TrustTokenIssuerConfig>();
// Because of the characteristics of the protocol, this will be
// quite small (~3 elements).
google::protobuf::RepeatedPtrField<TrustTokenKeyCommitment> keys_to_add(
keys.begin(), keys.end());
for (TrustTokenKeyCommitment& new_key : keys_to_add) {
for (const TrustTokenKeyCommitment& existing_key : config->keys()) {
if (existing_key.key() == new_key.key()) {
// It's safe to break here because of the precondition that
// the commitments in |keys_to_add| have distinct keys.
*new_key.mutable_first_seen_at() = existing_key.first_seen_at();
break;
}
}
}
config->mutable_keys()->Swap(&keys_to_add);
google::protobuf::RepeatedPtrField<TrustToken> filtered_tokens;
for (auto& token : *config->mutable_tokens()) {
if (std::any_of(config->keys().begin(), config->keys().end(),
[&token](const TrustTokenKeyCommitment& key) {
return key.key() == token.signing_key();
}))
*filtered_tokens.Add() = std::move(token);
}
config->mutable_tokens()->Swap(&filtered_tokens);
persister_->SetIssuerConfig(issuer, std::move(config));
}
void TrustTokenStore::SetBatchSize(const url::Origin& issuer, int batch_size) {
DCHECK(!issuer.opaque());
DCHECK(batch_size > 0);
std::unique_ptr<TrustTokenIssuerConfig> config =
persister_->GetIssuerConfig(issuer);
if (!config)
config = std::make_unique<TrustTokenIssuerConfig>();
config->set_batch_size(batch_size);
persister_->SetIssuerConfig(issuer, std::move(config));
}
base::Optional<int> TrustTokenStore::BatchSize(const url::Origin& issuer) {
DCHECK(!issuer.opaque());
std::unique_ptr<TrustTokenIssuerConfig> config =
persister_->GetIssuerConfig(issuer);
if (!config)
return base::nullopt;
if (!config->has_batch_size() || config->batch_size() <= 0)
return base::nullopt;
return config->batch_size();
}
void TrustTokenStore::AddTokens(const url::Origin& issuer,
base::span<const std::string> token_bodies,
base::StringPiece issuing_key) {
DCHECK(!issuer.opaque());
auto config = persister_->GetIssuerConfig(issuer);
DCHECK(config &&
std::any_of(config->keys().begin(), config->keys().end(),
[issuing_key](const TrustTokenKeyCommitment& commitment) {
return commitment.key() == issuing_key;
}));
for (const auto& token_body : token_bodies) {
TrustToken* entry = config->add_tokens();
entry->set_body(token_body);
entry->set_signing_key(std::string(issuing_key));
}
persister_->SetIssuerConfig(issuer, std::move(config));
}
std::vector<TrustToken> TrustTokenStore::RetrieveMatchingTokens(
const url::Origin& issuer,
base::RepeatingCallback<bool(const std::string&)> key_matcher) {
DCHECK(!issuer.opaque());
auto config = persister_->GetIssuerConfig(issuer);
std::vector<TrustToken> matching_tokens;
if (!config)
return matching_tokens;
std::copy_if(config->tokens().begin(), config->tokens().end(),
std::back_inserter(matching_tokens),
[&key_matcher](const TrustToken& token) {
return token.has_signing_key() &&
key_matcher.Run(token.signing_key());
});
return matching_tokens;
}
void TrustTokenStore::DeleteToken(const url::Origin& issuer,
const TrustToken& to_delete) {
DCHECK(!issuer.opaque());
auto config = persister_->GetIssuerConfig(issuer);
if (!config)
return;
for (auto it = config->mutable_tokens()->begin();
it != config->mutable_tokens()->end(); ++it) {
if (it->body() == to_delete.body()) {
config->mutable_tokens()->erase(it);
break;
}
}
persister_->SetIssuerConfig(issuer, std::move(config));
}
void TrustTokenStore::SetRedemptionRecord(
const url::Origin& issuer,
const url::Origin& top_level,
const SignedTrustTokenRedemptionRecord& record) {
DCHECK(!issuer.opaque());
DCHECK(!top_level.opaque());
auto config = persister_->GetIssuerToplevelPairConfig(issuer, top_level);
if (!config)
config = std::make_unique<TrustTokenIssuerToplevelPairConfig>();
*config->mutable_signed_redemption_record() = record;
persister_->SetIssuerToplevelPairConfig(issuer, top_level, std::move(config));
}
base::Optional<SignedTrustTokenRedemptionRecord>
TrustTokenStore::RetrieveNonstaleRedemptionRecord(
const url::Origin& issuer,
const url::Origin& top_level) {
DCHECK(!issuer.opaque());
DCHECK(!top_level.opaque());
auto config = persister_->GetIssuerToplevelPairConfig(issuer, top_level);
if (!config)
return base::nullopt;
if (!config->has_signed_redemption_record())
return base::nullopt;
if (record_expiry_delegate_->IsRecordExpired(
config->signed_redemption_record()))
return base::nullopt;
return config->signed_redemption_record();
}
} // namespace net
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef NET_TRUST_TOKENS_TRUST_TOKEN_STORE_H_
#define NET_TRUST_TOKENS_TRUST_TOKEN_STORE_H_
#include <memory>
#include <string>
#include <vector>
#include "base/callback.h"
#include "base/compiler_specific.h"
#include "base/optional.h"
#include "base/time/time.h"
#include "net/trust_tokens/proto/public.pb.h"
#include "net/trust_tokens/trust_token_persister.h"
#include "net/trust_tokens/types.h"
#include "url/origin.h"
namespace net {
// A TrustTokenStore provides operations on persistent state necessary for
// the various steps of the Trust TrustTokens protocol.
//
// For more information about the protocol, see the explainer at
// https://github.com/WICG/trust-token-api.
//
// TrustTokenStore translates operations germane to different steps
// of token issuance, token redemption, and request signing into
// operations in the key-value representation used by the persistence
// layer.
//
// For example, it provides operations:
// - checking preconditions for the different protocol steps;
// - storing unblinded, signed tokens; and
// - managing Signed Redemption Records (SRRs) and corresponding key pairs.
//
// TrustTokenStore's methods do minimal precondition checking and, in
// particular, only selectively verify protocol-level invariants and
// input integrity.
class TrustTokenStore {
public:
class RecordExpiryDelegate {
public:
virtual ~RecordExpiryDelegate() = default;
// Returns whether the given Signed Redemption Record has expired.
// This is implemented with a delegate to abstract away reading
// the values of SRRs (they're opaque to this store).
virtual bool IsRecordExpired(
const SignedTrustTokenRedemptionRecord& record) = 0;
};
// Creates a new TrustTokenStore passing read and write operations through
// to the given persister.
//
// Until the underlying BoringSSL functionality is implemented to extract
// expiry timestamps from Signed Redemption Record bodies, defaults to
// never expiring stored SRRs.
//
// |persister| must not be null.
explicit TrustTokenStore(std::unique_ptr<TrustTokenPersister> persister);
// Creates a TrustTokenStore relying on the given delegate for judging whether
// signed redemption records have expired.
//
// |persister| must not be null.
TrustTokenStore(
std::unique_ptr<TrustTokenPersister> persister,
std::unique_ptr<RecordExpiryDelegate> expiry_delegate_for_testing);
virtual ~TrustTokenStore();
//// Methods related to ratelimits:
// Updates the given issuer's last issuance time to now.
//
// |issuer| must not be opaque.
virtual void RecordIssuance(const url::Origin& issuer);
// Returns the time since the last call to RecordIssuance for
// issuer |issuer|, or nullopt in the following two cases:
// 1. there is no currently-recorded prior issuance for the
// issuer, or
// 2. the time since the last issuance is negative (because
// of, for instance, corruption or clock skew).
//
// |issuer| must not be opaque.
WARN_UNUSED_RESULT virtual base::Optional<base::TimeDelta>
TimeSinceLastIssuance(const url::Origin& issuer);
// Updates the given (issuer, top-level) origin pair's last redemption time
// to now.
//
// |issuer| and |top_level| must not be opaque.
virtual void RecordRedemption(const url::Origin& issuer,
const url::Origin& top_level);
// Returns the time elapsed since the last redemption recorded by
// RecordRedemption for issuer |issuer| and top level |top_level|,
// or nullopt in the following two cases:
// 1. there was no prior redemption for the (issuer,
// top-level origin) pair.
// 2. the time since the last redepmption is negative (because
// of, for instance, corruption or clock skew).
//
// |issuer| and |top_level| must not be opaque.
WARN_UNUSED_RESULT virtual base::Optional<base::TimeDelta>
TimeSinceLastRedemption(const url::Origin& issuer,
const url::Origin& top_level);
// Returns whether |issuer| is associated with |top_level|.
//
// |issuer| and |top_level| must not be opaque.
WARN_UNUSED_RESULT virtual bool IsAssociated(const url::Origin& issuer,
const url::Origin& top_level);
// Associates |issuer| with |top_level|. (It's the caller's responsibility to
// enforce any cap on the number of top levels per issuer.)
//
// |issuer| and |top_level| must not be opaque.
virtual void SetAssociation(const url::Origin& issuer,
const url::Origin& top_level);
//// Methods related to reading and writing issuer values configured via key
//// commitment queries, such as key commitments and batch sizes:
// Returns all stored key commitments (including related metadata:
// see the definition of TrustTokenKeyCommitment) for the given issuer.
//
// |issuer| must not be opaque.
WARN_UNUSED_RESULT virtual std::vector<TrustTokenKeyCommitment>
KeyCommitments(const url::Origin& issuer);
// Sets the key commitments for |issuer| to exactly the keys in |keys|.
// If there is a key in |keys| with the same key() as a key already stored:
// - maintains the "first seen at" time for the key
// - updates the expiry date to the new expiry date, even if it is sooner
// than the previous expiry date
//
// Also prunes all state corresponding to keys *not* in |keys|:
// - removes all stored signed tokens for |issuer| that were signed with
// keys not in |keys|
// - removes all key commitments for |issuer| with keys not in |keys|
//
// It is the client's responsibility to validate the
// reasonableness of the given keys' expiry times. (For instance, one might
// wish to avoid providing keys with expiry times in the past.)
//
// |issuer| must not be opaque, and the commitments in |keys| must have
// distinct keys.
virtual void SetKeyCommitmentsAndPruneStaleState(
const url::Origin& issuer,
base::span<const TrustTokenKeyCommitment> keys);
// Returns the "batch size" (number of blinded tokens to provide per issuance
// request) for the given issuer, if present and greater than 0. Otherwise,
// returns nullopt.
//
// |issuer| must not be opaque.
WARN_UNUSED_RESULT virtual base::Optional<int> BatchSize(
const url::Origin& issuer);
// Sets the given issuer's batch size (see above).
//
// |issuer| must not be opaque; |batch_size| must be at least 1.
virtual void SetBatchSize(const url::Origin& issuer, int batch_size);
//// Methods related to reading and writing signed tokens:
// Associates to the given issuer additional signed
// trust tokens with:
// - token bodies given by |token_bodies|
// - signing keys given by |issuing_key|.
//
// |issuer| must not be opaque and must have a stored
// key commitment corresponding to |issuing_key|.
virtual void AddTokens(const url::Origin& issuer,
base::span<const std::string> token_bodies,
base::StringPiece issuing_key);
// Returns all signed tokens from |issuer| signed by keys matching
// the given predicate.
//
// |issuer| must not be opaque.
WARN_UNUSED_RESULT virtual std::vector<TrustToken> RetrieveMatchingTokens(
const url::Origin& issuer,
base::RepeatingCallback<bool(const std::string&)> key_matcher);
// If |to_delete| is a token issued by |issuer|, deletes the token.
//
// |issuer| must not be opaque.
void DeleteToken(const url::Origin& issuer, const TrustToken& to_delete);
//// Methods concerning Signed Redemption Records (SRRs)
// Sets the cached SRR corresponding to the pair (issuer, top_level)
// to |record|. Overwrites any existing record.
//
// |issuer| and |top_level| must not be opaque.
virtual void SetRedemptionRecord(
const url::Origin& issuer,
const url::Origin& top_level,
const SignedTrustTokenRedemptionRecord& record);
// Attempts to retrieve the stored SRR for the given pair of (issuer,
// top-level) origins.
// - If the pair has a current (i.e., non-expired) SRR, returns that SRR.
// - Otherwise, returns nullopt.
//
// |issuer| and |top_level| must not be opaque.
WARN_UNUSED_RESULT virtual base::Optional<SignedTrustTokenRedemptionRecord>
RetrieveNonstaleRedemptionRecord(const url::Origin& issuer,
const url::Origin& top_level);
private:
std::unique_ptr<TrustTokenPersister> persister_;
std::unique_ptr<RecordExpiryDelegate> record_expiry_delegate_;
};
} // namespace net
#endif // NET_TRUST_TOKENS_TRUST_TOKEN_STORE_H_
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "net/trust_tokens/trust_token_store.h"
#include <memory>
#include <utility>
#include "base/bind.h"
#include "base/test/task_environment.h"
#include "base/time/time.h"
#include "net/trust_tokens/in_memory_trust_token_persister.h"
#include "net/trust_tokens/proto/public.pb.h"
#include "net/trust_tokens/proto/storage.pb.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"
#include "url/origin.h"
using ::testing::ElementsAre;
using ::testing::Optional;
namespace net {
namespace trust_tokens {
namespace {
MATCHER_P(EqualsProto,
message,
"Match a proto Message equal to the matcher's argument.") {
std::string expected_serialized, actual_serialized;
message.SerializeToString(&expected_serialized);
arg.SerializeToString(&actual_serialized);
return expected_serialized == actual_serialized;
}
} // namespace
TEST(TrustTokenStoreTest, RecordsIssuances) {
// A newly initialized store should not think it's
// recorded any issuances.
TrustTokenStore my_store(std::make_unique<InMemoryTrustTokenPersister>());
url::Origin issuer = url::Origin::Create(GURL("https://issuer.com"));
base::test::TaskEnvironment env(
base::test::TaskEnvironment::TimeSource::MOCK_TIME);
EXPECT_EQ(my_store.TimeSinceLastIssuance(issuer), base::nullopt);
// Recording an issuance should result in the time
// since last issuance being correctly returned.
my_store.RecordIssuance(issuer);
auto delta = base::TimeDelta::FromSeconds(1);
env.AdvanceClock(delta);
EXPECT_THAT(my_store.TimeSinceLastIssuance(issuer), Optional(delta));
}
TEST(TrustTokenStoreTest, DoesntReportMissingOrMalformedIssuanceTimestamps) {
auto my_persister = std::make_unique<InMemoryTrustTokenPersister>();
auto* raw_persister = my_persister.get();
TrustTokenStore my_store(std::move(my_persister));
url::Origin issuer = url::Origin::Create(GURL("https://issuer.com"));
auto issuer_config_with_no_time = std::make_unique<TrustTokenIssuerConfig>();
raw_persister->SetIssuerConfig(issuer, std::move(issuer_config_with_no_time));
EXPECT_EQ(my_store.TimeSinceLastIssuance(issuer), base::nullopt);
auto issuer_config_with_malformed_time =
std::make_unique<TrustTokenIssuerConfig>();
issuer_config_with_malformed_time->set_last_issuance(
"not a valid serialization of a base::Time");
raw_persister->SetIssuerConfig(issuer,
std::move(issuer_config_with_malformed_time));
EXPECT_EQ(my_store.TimeSinceLastIssuance(issuer), base::nullopt);
}
TEST(TrustTokenStoreTest, DoesntReportNegativeTimeSinceLastIssuance) {
auto my_persister = std::make_unique<InMemoryTrustTokenPersister>();
auto* raw_persister = my_persister.get();
base::test::TaskEnvironment env(
base::test::TaskEnvironment::TimeSource::MOCK_TIME);
TrustTokenStore my_store(std::move(my_persister));
url::Origin issuer = url::Origin::Create(GURL("https://issuer.com"));
base::Time later_than_now =
base::Time::Now() + base::TimeDelta::FromSeconds(1);
auto issuer_config_with_future_time =
std::make_unique<TrustTokenIssuerConfig>();
issuer_config_with_future_time->set_last_issuance(
internal::TimeToString(later_than_now));
raw_persister->SetIssuerConfig(issuer,
std::move(issuer_config_with_future_time));
// TimeSinceLastIssuance shouldn't return negative values.
EXPECT_EQ(my_store.TimeSinceLastIssuance(issuer), base::nullopt);
}
TEST(TrustTokenStore, RecordsRedemptions) {
// A newly initialized store should not think it's
// recorded any redemptions.
TrustTokenStore my_store(std::make_unique<InMemoryTrustTokenPersister>());
url::Origin issuer = url::Origin::Create(GURL("https://issuer.com"));
url::Origin toplevel = url::Origin::Create(GURL("https://toplevel.com"));
base::test::TaskEnvironment env(
base::test::TaskEnvironment::TimeSource::MOCK_TIME);
EXPECT_EQ(my_store.TimeSinceLastRedemption(issuer, toplevel), base::nullopt);
// Recording a redemption should result in the time
// since last redemption being correctly returned.
my_store.RecordRedemption(issuer, toplevel);
auto delta = base::TimeDelta::FromSeconds(1);
env.AdvanceClock(delta);
EXPECT_THAT(my_store.TimeSinceLastRedemption(issuer, toplevel),
Optional(delta));
}
TEST(TrustTokenStoreTest, DoesntReportMissingOrMalformedRedemptionTimestamps) {
auto my_persister = std::make_unique<InMemoryTrustTokenPersister>();
auto* raw_persister = my_persister.get();
TrustTokenStore my_store(std::move(my_persister));
url::Origin issuer = url::Origin::Create(GURL("https://issuer.com"));
url::Origin toplevel = url::Origin::Create(GURL("https://toplevel.com"));
auto config_with_no_time =
std::make_unique<TrustTokenIssuerToplevelPairConfig>();
raw_persister->SetIssuerToplevelPairConfig(issuer, toplevel,
std::move(config_with_no_time));
EXPECT_EQ(my_store.TimeSinceLastRedemption(issuer, toplevel), base::nullopt);
auto config_with_malformed_time =
std::make_unique<TrustTokenIssuerToplevelPairConfig>();
config_with_malformed_time->set_last_redemption(
"not a valid serialization of a base::Time");
raw_persister->SetIssuerToplevelPairConfig(
issuer, toplevel, std::move(config_with_malformed_time));
EXPECT_EQ(my_store.TimeSinceLastRedemption(issuer, toplevel), base::nullopt);
}
TEST(TrustTokenStoreTest, DoesntReportNegativeTimeSinceLastRedemption) {
auto my_persister = std::make_unique<InMemoryTrustTokenPersister>();
auto* raw_persister = my_persister.get();
TrustTokenStore my_store(std::move(my_persister));
base::test::TaskEnvironment env(
base::test::TaskEnvironment::TimeSource::MOCK_TIME);
url::Origin issuer = url::Origin::Create(GURL("https://issuer.com"));
url::Origin toplevel = url::Origin::Create(GURL("https://toplevel.com"));
base::Time later_than_now =
base::Time::Now() + base::TimeDelta::FromSeconds(1);
auto config_with_future_time =
std::make_unique<TrustTokenIssuerToplevelPairConfig>();
config_with_future_time->set_last_redemption(
internal::TimeToString(later_than_now));
raw_persister->SetIssuerToplevelPairConfig(
issuer, toplevel, std::move(config_with_future_time));
// TimeSinceLastRedemption shouldn't return negative values.
EXPECT_EQ(my_store.TimeSinceLastRedemption(issuer, toplevel), base::nullopt);
}
TEST(TrustTokenStore, AssociatesToplevelsWithIssuers) {
// A newly initialized store should not think
// any toplevels are associated with any issuers.
TrustTokenStore my_store(std::make_unique<InMemoryTrustTokenPersister>());
url::Origin issuer = url::Origin::Create(GURL("https://issuer.com"));
url::Origin toplevel = url::Origin::Create(GURL("https://toplevel.com"));
EXPECT_FALSE(my_store.IsAssociated(issuer, toplevel));
// After associating an issuer with a toplevel,
// the store should think that that issuer is associated
// with that toplevel.
my_store.SetAssociation(issuer, toplevel);
EXPECT_TRUE(my_store.IsAssociated(issuer, toplevel));
}
TEST(TrustTokenStore, StoresKeyCommitments) {
// A newly initialized store should not think
// any issuers have committed keys.
TrustTokenStore my_store(std::make_unique<InMemoryTrustTokenPersister>());
url::Origin issuer = url::Origin::Create(GURL("https://issuer.com"));
EXPECT_TRUE(my_store.KeyCommitments(issuer).empty());
// A stored committed key should be returned
// by a subsequent query.
TrustTokenKeyCommitment my_commitment;
my_commitment.set_key("quite a secure key, this");
my_store.SetKeyCommitmentsAndPruneStaleState(
issuer, std::vector<TrustTokenKeyCommitment>{my_commitment});
EXPECT_THAT(my_store.KeyCommitments(issuer),
ElementsAre(EqualsProto(my_commitment)));
}
TEST(TrustTokenStore, OverwritesExistingKeyCommitments) {
// Overwriting an existing committed key should lead
// to the key's metadata being fused:
// - the key should still be present
// - the "first seen at" should not change
// - the expiry date should be updated
TrustTokenStore my_store(std::make_unique<InMemoryTrustTokenPersister>());
url::Origin issuer = url::Origin::Create(GURL("https://issuer.com"));
const std::string kMyKey = "quite a secure key, this";
TrustTokenKeyCommitment my_commitment;
my_commitment.set_key(kMyKey);
const std::string kMySerializedTime = "four o'clock";
const std::string kReplacementSerializedTime = "five o'clock";
my_commitment.set_expiry(kMySerializedTime);
my_commitment.set_first_seen_at(kMySerializedTime);
my_store.SetKeyCommitmentsAndPruneStaleState(
issuer, std::vector<TrustTokenKeyCommitment>{my_commitment});
TrustTokenKeyCommitment replacement_commitment;
replacement_commitment.set_key(kMyKey);
replacement_commitment.set_expiry(kReplacementSerializedTime);
replacement_commitment.set_first_seen_at(kReplacementSerializedTime);
my_store.SetKeyCommitmentsAndPruneStaleState(
issuer, std::vector<TrustTokenKeyCommitment>{replacement_commitment});
ASSERT_EQ(my_store.KeyCommitments(issuer).size(), 1u);
auto got = my_store.KeyCommitments(issuer).front();
EXPECT_TRUE(got.key() == kMyKey);
EXPECT_TRUE(got.first_seen_at() == kMySerializedTime);
EXPECT_TRUE(got.expiry() == kReplacementSerializedTime);
}
TEST(TrustTokenStore, KeyUpdateRemovesNonupdatedKeys) {
TrustTokenStore my_store(std::make_unique<InMemoryTrustTokenPersister>());
url::Origin issuer = url::Origin::Create(GURL("https://issuer.com"));
TrustTokenKeyCommitment my_commitment;
my_commitment.set_key("quite a secure key, this");
my_store.SetKeyCommitmentsAndPruneStaleState(
issuer, std::vector<TrustTokenKeyCommitment>{my_commitment});
// When committed keys are changed, the store should
// remove all keys not present in the provided set.
my_store.SetKeyCommitmentsAndPruneStaleState(
issuer, std::vector<TrustTokenKeyCommitment>());
EXPECT_TRUE(my_store.KeyCommitments(issuer).empty());
}
TEST(TrustTokenStore, PrunesDataAssociatedWithRemovedKeyCommitments) {
// Removing a committed key should result in trust tokens
// associated with the removed key being pruned from the store.
TrustTokenStore my_store(std::make_unique<InMemoryTrustTokenPersister>());
url::Origin issuer = url::Origin::Create(GURL("https://issuer.com"));
TrustTokenKeyCommitment my_commitment;
my_commitment.set_key("quite a secure key, this");
TrustTokenKeyCommitment another_commitment;
another_commitment.set_key("distinct from the first key");
my_store.SetKeyCommitmentsAndPruneStaleState(
issuer,
std::vector<TrustTokenKeyCommitment>{my_commitment, another_commitment});
my_store.AddTokens(issuer, std::vector<std::string>{"some token body"},
my_commitment.key());
my_store.AddTokens(issuer, std::vector<std::string>{"some other token body"},
another_commitment.key());
my_store.SetKeyCommitmentsAndPruneStaleState(
issuer, std::vector<TrustTokenKeyCommitment>{another_commitment});
TrustToken expected_token;
expected_token.set_body("some other token body");
expected_token.set_signing_key(another_commitment.key());
// Removing |my_commitment| should have
// - led to the removal of the token associated with the removed key and
// - *not* led to the removal of the token associated with the remaining key.
EXPECT_THAT(my_store.RetrieveMatchingTokens(
issuer, base::BindRepeating(
[](const std::string& t) { return true; })),
ElementsAre(EqualsProto(expected_token)));
}
TEST(TrustTokenStore, SetsBatchSize) {
// A newly initialized store should not think
// any issuers have associated batch sizes.
auto my_persister = std::make_unique<InMemoryTrustTokenPersister>();
auto* raw_persister = my_persister.get();
TrustTokenStore my_store(std::move(my_persister));
url::Origin issuer = url::Origin::Create(GURL("https://issuer.com"));
EXPECT_EQ(my_store.BatchSize(issuer), base::nullopt);
// Setting an issuer's batch size should mean that
// subsequent queries return that batch size.
my_store.SetBatchSize(issuer, 1);
EXPECT_THAT(my_store.BatchSize(issuer), Optional(1));
// If the issuer config is storing a bad batch size for some reason,
// the store's client should see nullopt.
auto bad_config = std::make_unique<TrustTokenIssuerConfig>();
bad_config->set_batch_size(-1);
raw_persister->SetIssuerConfig(issuer, std::move(bad_config));
EXPECT_EQ(my_store.BatchSize(issuer), base::nullopt);
}
TEST(TrustTokenStore, AddsTrustTokens) {
// A newly initialized store should not think
// any issuers have associated trust tokens.
TrustTokenStore my_store(std::make_unique<InMemoryTrustTokenPersister>());
url::Origin issuer = url::Origin::Create(GURL("https://issuer.com"));
auto match_all_keys =
base::BindRepeating([](const std::string& t) { return true; });
EXPECT_TRUE(my_store.RetrieveMatchingTokens(issuer, match_all_keys).empty());
// Adding a token should result in that token being
// returned by subsequent queries with predicates accepting
// that token.
const std::string kMyKey = "abcdef";
TrustTokenKeyCommitment my_commitment;
my_commitment.set_key(kMyKey);
my_store.SetKeyCommitmentsAndPruneStaleState(
issuer, std::vector<TrustTokenKeyCommitment>{my_commitment});
TrustToken expected_token;
expected_token.set_body("some token");
expected_token.set_signing_key(kMyKey);
my_store.AddTokens(issuer, std::vector<std::string>{expected_token.body()},
kMyKey);
EXPECT_THAT(my_store.RetrieveMatchingTokens(issuer, match_all_keys),
ElementsAre(EqualsProto(expected_token)));
}
TEST(TrustTokenStore, RetrievesTrustTokensRespectingNontrivialPredicate) {
// RetrieveMatchingTokens should not return tokens rejected by
// the provided predicate.
TrustTokenStore my_store(std::make_unique<InMemoryTrustTokenPersister>());
url::Origin issuer = url::Origin::Create(GURL("https://issuer.com"));
const std::string kMatchingKey = "bbbbbb";
const std::string kNonmatchingKey = "aaaaaa";
TrustTokenKeyCommitment matching_commitment;
matching_commitment.set_key(kMatchingKey);
TrustTokenKeyCommitment nonmatching_commitment;
nonmatching_commitment.set_key(kNonmatchingKey);
TrustToken expected_token;
expected_token.set_body("this one should get returned");
expected_token.set_signing_key(kMatchingKey);
my_store.SetKeyCommitmentsAndPruneStaleState(
issuer, std::vector<TrustTokenKeyCommitment>{matching_commitment,
nonmatching_commitment});
my_store.AddTokens(issuer, std::vector<std::string>{expected_token.body()},
kMatchingKey);
my_store.AddTokens(
issuer,
std::vector<std::string>{"this one should get rejected by the predicate"},
kNonmatchingKey);
EXPECT_THAT(my_store.RetrieveMatchingTokens(
issuer, base::BindRepeating(
[](const std::string& pattern,
const std::string& possible_match) {
return possible_match == pattern;
},
kMatchingKey)),
ElementsAre(EqualsProto(expected_token)));
}
TEST(TrustTokenStore, DeletesSingleToken) {
TrustTokenStore my_store(std::make_unique<InMemoryTrustTokenPersister>());
url::Origin issuer = url::Origin::Create(GURL("https://issuer.com"));
auto match_all_keys =
base::BindRepeating([](const std::string& t) { return true; });
// Deleting a single token should result in that token
// not being returned by subsequent RetrieveMatchingTokens calls.
// On the other hand, tokens *not* deleted should still be
// returned.
TrustTokenKeyCommitment my_commitment;
my_commitment.set_key("key");
TrustToken first_token;
first_token.set_body("delete me!");
first_token.set_signing_key(my_commitment.key());
TrustToken second_token;
second_token.set_body("don't delete me!");
second_token.set_signing_key(my_commitment.key());
my_store.SetKeyCommitmentsAndPruneStaleState(
issuer, std::vector<TrustTokenKeyCommitment>{my_commitment});
my_store.AddTokens(
issuer, std::vector<std::string>{first_token.body(), second_token.body()},
my_commitment.key());
my_store.DeleteToken(issuer, first_token);
EXPECT_THAT(my_store.RetrieveMatchingTokens(issuer, match_all_keys),
ElementsAre(EqualsProto(second_token)));
}
TEST(TrustTokenStore, DeleteTokenForMissingIssuer) {
TrustTokenStore my_store(std::make_unique<InMemoryTrustTokenPersister>());
url::Origin issuer = url::Origin::Create(GURL("https://issuer.com"));
// Deletes for issuers not present in the store should gracefully no-op.
my_store.DeleteToken(issuer, TrustToken());
}
TEST(TrustTokenStore, SetsAndRetrievesRedemptionRecord) {
// A newly initialized store should not think
// it has any signed redemption records.
TrustTokenStore my_store(std::make_unique<InMemoryTrustTokenPersister>());
url::Origin issuer = url::Origin::Create(GURL("https://issuer.com"));
url::Origin toplevel = url::Origin::Create(GURL("https://toplevel.com"));
base::test::TaskEnvironment env(
base::test::TaskEnvironment::TimeSource::MOCK_TIME);
EXPECT_EQ(my_store.RetrieveNonstaleRedemptionRecord(issuer, toplevel),
base::nullopt);
// Providing a redemption record should mean that subsequent
// queries (modulo the record's staleness) should return that
// record.
SignedTrustTokenRedemptionRecord my_record;
my_record.set_body("Look at me! I'm a signed redemption record!");
my_store.SetRedemptionRecord(issuer, toplevel, my_record);
EXPECT_THAT(my_store.RetrieveNonstaleRedemptionRecord(issuer, toplevel),
Optional(EqualsProto(my_record)));
}
TEST(TrustTokenStore, RetrieveRedemptionRecordHandlesConfigWithNoRecord) {
// A RetrieveRedemptionRecord call for an (issuer, toplevel) pair with
// no redemption record stored should gracefully return the default value.
auto my_persister = std::make_unique<InMemoryTrustTokenPersister>();
auto* raw_persister = my_persister.get();
TrustTokenStore my_store(std::move(my_persister));
url::Origin issuer = url::Origin::Create(GURL("https://issuer.com"));
url::Origin toplevel = url::Origin::Create(GURL("https://toplevel.com"));
raw_persister->SetIssuerToplevelPairConfig(
issuer, toplevel, std::make_unique<TrustTokenIssuerToplevelPairConfig>());
EXPECT_EQ(my_store.RetrieveNonstaleRedemptionRecord(issuer, toplevel),
base::nullopt);
}
TEST(TrustTokenStore, SetRedemptionRecordOverwritesExisting) {
// Subsequent redemption records should overwrite ones set earlier.
TrustTokenStore my_store(std::make_unique<InMemoryTrustTokenPersister>());
url::Origin issuer = url::Origin::Create(GURL("https://issuer.com"));
url::Origin toplevel = url::Origin::Create(GURL("https://toplevel.com"));
base::test::TaskEnvironment env(
base::test::TaskEnvironment::TimeSource::MOCK_TIME);
SignedTrustTokenRedemptionRecord my_record;
my_record.set_body("Look at me! I'm a signed redemption record!");
my_store.SetRedemptionRecord(issuer, toplevel, my_record);
SignedTrustTokenRedemptionRecord another_record;
another_record.set_body(
"If all goes well, this one should overwrite |my_record|.");
my_store.SetRedemptionRecord(issuer, toplevel, another_record);
EXPECT_THAT(my_store.RetrieveNonstaleRedemptionRecord(issuer, toplevel),
Optional(EqualsProto(another_record)));
}
namespace {
// Characterizes an SRR as expired if its body begins with an "a".
class LetterAExpiringExpiryDelegate
: public TrustTokenStore::RecordExpiryDelegate {
public:
bool IsRecordExpired(
const SignedTrustTokenRedemptionRecord& record) override {
return record.body().size() > 1 && record.body().front() == 'a';
}
};
} // namespace
TEST(TrustTokenStore, DoesNotReturnStaleRedemptionRecord) {
// Once a redemption record expires, it should no longer
// be returned by retrieval queries.
TrustTokenStore my_store(std::make_unique<InMemoryTrustTokenPersister>(),
std::make_unique<LetterAExpiringExpiryDelegate>());
url::Origin issuer = url::Origin::Create(GURL("https://issuer.com"));
url::Origin toplevel = url::Origin::Create(GURL("https://toplevel.com"));
SignedTrustTokenRedemptionRecord my_record;
my_record.set_body("aLook at me! I'm an expired signed redemption record!");
my_store.SetRedemptionRecord(issuer, toplevel, my_record);
EXPECT_EQ(my_store.RetrieveNonstaleRedemptionRecord(issuer, toplevel),
base::nullopt);
}
} // namespace trust_tokens
} // namespace net
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "net/trust_tokens/types.h"
#include "base/time/time.h"
#include "base/value_conversions.h"
#include "base/values.h"
#include "url/origin.h"
namespace net {
namespace internal {
base::Optional<base::Time> StringToTime(base::StringPiece my_string) {
base::Time ret;
if (!base::GetValueAsTime(base::Value(my_string), &ret))
return base::nullopt;
return ret;
}
std::string TimeToString(base::Time my_time) {
return base::CreateTimeValue(my_time).GetString();
}
} // namespace internal
} // namespace net
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef NET_TRUST_TOKENS_TYPES_H_
#define NET_TRUST_TOKENS_TYPES_H_
#include <string>
#include "base/strings/string_piece_forward.h"
#include "base/time/time.h"
#include "url/origin.h"
namespace net {
namespace internal {
// types.h provides utility functions for Trust TrustTokens type conversion.
// Deserializes a base::Time. Returns nullopt on failure (for instance,
// deserialization can fail if |my_string| is malformed due to data
// corruption) and the deserialized Time on success.
base::Optional<base::Time> StringToTime(base::StringPiece my_string);
// Serializes a base::Time.
std::string TimeToString(base::Time my_time);
} // namespace internal
} // namespace net
#endif // NET_TRUST_TOKENS_TYPES_H_
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "net/trust_tokens/types.h"
#include "base/time/time.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using ::testing::Optional;
namespace net {
namespace internal {
// trust_tokens/types.h's TimeToString/StringToTime implementations are
// thin wrappers around well-tested //base conversion methods, so these
// tests are just sanity checks to make sure that values are actually
// getting passed to the pertinent //base libraries.
TEST(TrustTokenTypes, TimeToStringRoundtrip) {
auto my_time = base::Time::UnixEpoch() + base::TimeDelta::FromMilliseconds(
373849174829374); // arbitrary
EXPECT_THAT(StringToTime(TimeToString(my_time)), Optional(my_time));
}
TEST(TrustTokenTypes, TimeFromBadStringFails) {
EXPECT_EQ(StringToTime("I bet this isn't a valid representation of a time."),
base::nullopt);
}
} // namespace internal
} // namespace net
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