blob: 843def7ed5ea56a8a80262364387fbbe5e75d664 [file] [log] [blame]
// Copyright 2020 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 "services/network/trust_tokens/expiry_inspecting_record_expiry_delegate.h"
#include <vector>
#include "base/test/task_environment.h"
#include "components/cbor/values.h"
#include "components/cbor/writer.h"
#include "services/network/public/mojom/trust_tokens.mojom.h"
#include "services/network/trust_tokens/proto/public.pb.h"
#include "services/network/trust_tokens/signed_redemption_record_serialization.h"
#include "services/network/trust_tokens/suitable_trust_token_origin.h"
#include "services/network/trust_tokens/trust_token_key_commitment_getter.h"
#include "services/network/trust_tokens/trust_token_parameterization.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace network {
namespace {
// Overwrites |record| with a serialized signed redemption record with
// just enough structure to have a well-formed expiry time of |expiry|.
//
// Reference: Trust Tokens design doc (currently the normative source for SRR
// structure),
// https://docs.google.com/document/d/1TNnya6B8pyomDK2F1R9CL3dY10OAmqWlnCxsWyOBDVQ/edit#heading=h.7mkzvhpqb8l5
const char kExpiryTimestampKey[] = "expiry-timestamp";
void SetRecordExpiry(SignedTrustTokenRedemptionRecord* record,
base::Time expiry) {
cbor::Value::MapValue map;
map[cbor::Value(kExpiryTimestampKey, cbor::Value::Type::STRING)] =
cbor::Value((expiry - base::Time::UnixEpoch()).InSeconds());
std::vector<uint8_t> empty_signature;
record->set_body(*ConstructSignedRedemptionRecord(
*cbor::Writer::Write(cbor::Value(std::move(map))), empty_signature));
}
class FixedKeyCommitmentGetter
: public SynchronousTrustTokenKeyCommitmentGetter {
public:
explicit FixedKeyCommitmentGetter(
mojom::TrustTokenKeyCommitmentResultPtr keys)
: keys_(std::move(keys)) {}
mojom::TrustTokenKeyCommitmentResultPtr GetSync(
const url::Origin& origin) const override {
return keys_.Clone();
}
private:
mojom::TrustTokenKeyCommitmentResultPtr keys_;
};
} // namespace
// If storage contains no key commitment result for the record's issuer
// whatsoever, then, in particular, it doesn't contain the token verification
// key associated with the record; this means that the record should be marked
// as expired.
TEST(ExpiryInspectingRecordExpiryDelegate, ExpiresRecordWhenNoKeys) {
base::test::TaskEnvironment env(
base::test::TaskEnvironment::TimeSource::MOCK_TIME);
FixedKeyCommitmentGetter getter(nullptr); // return no keys
ExpiryInspectingRecordExpiryDelegate delegate(&getter);
SignedTrustTokenRedemptionRecord record;
SetRecordExpiry(&record, base::Time::Now() + base::TimeDelta::FromMinutes(1));
EXPECT_TRUE(delegate.IsRecordExpired(
record,
*SuitableTrustTokenOrigin::Create(GURL("https://issuer.example"))));
}
// A record with its expiry time in the past... should be marked as expired.
TEST(ExpiryInspectingRecordExpiryDelegate, ExpiresRecordWithPastExpiry) {
base::test::TaskEnvironment env(
base::test::TaskEnvironment::TimeSource::MOCK_TIME);
auto commitment_result = mojom::TrustTokenKeyCommitmentResult::New();
commitment_result->keys.push_back(mojom::TrustTokenVerificationKey::New(
"key", /*expiry=*/base::Time::Max()));
FixedKeyCommitmentGetter getter(std::move(commitment_result));
ExpiryInspectingRecordExpiryDelegate delegate(&getter);
SignedTrustTokenRedemptionRecord record;
record.set_token_verification_key("key");
SetRecordExpiry(&record, base::Time::Now() - base::TimeDelta::FromMinutes(1));
EXPECT_TRUE(delegate.IsRecordExpired(
record,
*SuitableTrustTokenOrigin::Create(GURL("https://issuer.example"))));
}
// The delegate's interface defines a record as expired if its expiration time
// is not in the future.
TEST(ExpiryInspectingRecordExpiryDelegate, ExpiresRecordExpiringRightNow) {
base::test::TaskEnvironment env(
base::test::TaskEnvironment::TimeSource::MOCK_TIME);
auto commitment_result = mojom::TrustTokenKeyCommitmentResult::New();
commitment_result->keys.push_back(mojom::TrustTokenVerificationKey::New(
"key", /*expiry=*/base::Time::Max()));
FixedKeyCommitmentGetter getter(std::move(commitment_result));
ExpiryInspectingRecordExpiryDelegate delegate(&getter);
SignedTrustTokenRedemptionRecord record;
record.set_token_verification_key("key");
SetRecordExpiry(&record, base::Time::Now());
EXPECT_TRUE(delegate.IsRecordExpired(
record,
*SuitableTrustTokenOrigin::Create(GURL("https://issuer.example"))));
}
// When we have a key commitment result for the record's issuer, but the
// record's key is not in the commitment result, the record should be marked
// expired.
TEST(ExpiryInspectingRecordExpiryDelegate, ExpiresRecordWithNoMatchingKey) {
base::test::TaskEnvironment env(
base::test::TaskEnvironment::TimeSource::MOCK_TIME);
// This commitment result is valid, but it contains no keys.
auto commitment_result = mojom::TrustTokenKeyCommitmentResult::New();
FixedKeyCommitmentGetter getter(std::move(commitment_result));
ExpiryInspectingRecordExpiryDelegate delegate(&getter);
SignedTrustTokenRedemptionRecord record;
// "key" is not present in the commitment
record.set_token_verification_key("key");
SetRecordExpiry(&record, base::Time::Now() + base::TimeDelta::FromMinutes(1));
EXPECT_TRUE(delegate.IsRecordExpired(
record,
*SuitableTrustTokenOrigin::Create(GURL("https://issuer.example"))));
}
// When a record's issuer has the record's key in its current commitment, and
// the record's expiry timestamp hasn't passed, the record should not be marked
// as expired.
TEST(ExpiryInspectingRecordExpiryDelegate, DoesntExpireUnexpiredRecord) {
base::test::TaskEnvironment env(
base::test::TaskEnvironment::TimeSource::MOCK_TIME);
auto commitment_result = mojom::TrustTokenKeyCommitmentResult::New();
commitment_result->keys.push_back(mojom::TrustTokenVerificationKey::New(
"key", /*expiry=*/base::Time::Max()));
FixedKeyCommitmentGetter getter(std::move(commitment_result));
ExpiryInspectingRecordExpiryDelegate delegate(&getter);
SignedTrustTokenRedemptionRecord record;
record.set_token_verification_key("key");
SetRecordExpiry(&record, base::Time::Now() + base::TimeDelta::FromMinutes(1));
EXPECT_FALSE(delegate.IsRecordExpired(
record,
*SuitableTrustTokenOrigin::Create(GURL("https://issuer.example"))));
}
TEST(ExpiryInspectingRecordExpiryDelegate, ExpiresRecordWithMalformedBody) {
base::test::TaskEnvironment env(
base::test::TaskEnvironment::TimeSource::MOCK_TIME);
auto commitment_result = mojom::TrustTokenKeyCommitmentResult::New();
commitment_result->keys.push_back(mojom::TrustTokenVerificationKey::New(
"key", /*expiry=*/base::Time::Max()));
FixedKeyCommitmentGetter getter(std::move(commitment_result));
ExpiryInspectingRecordExpiryDelegate delegate(&getter);
// This SRR has an empty (and, consequently, invalid) body; it should be
// marked as expired.
SignedTrustTokenRedemptionRecord record;
record.set_token_verification_key("key");
EXPECT_TRUE(delegate.IsRecordExpired(
record,
*SuitableTrustTokenOrigin::Create(GURL("https://issuer.example"))));
}
TEST(ExpiryInspectingRecordExpiryDelegate, RespectsDefaultTimestamp) {
base::test::TaskEnvironment env(
base::test::TaskEnvironment::TimeSource::MOCK_TIME);
auto commitment_result = mojom::TrustTokenKeyCommitmentResult::New();
commitment_result->keys.push_back(mojom::TrustTokenVerificationKey::New(
"key", /*expiry=*/base::Time::Max()));
FixedKeyCommitmentGetter getter(std::move(commitment_result));
ExpiryInspectingRecordExpiryDelegate delegate(&getter);
SignedTrustTokenRedemptionRecord record;
record.set_token_verification_key("key");
// Construct an SRR body missing the (optional) expiry timestamp field.
std::vector<uint8_t> empty_signature;
cbor::Value::MapValue map;
record.set_body(*ConstructSignedRedemptionRecord(
*cbor::Writer::Write(cbor::Value(std::move(map))), empty_signature));
// The record's expiry should depend on whether the default expiry is in the
// future or the past.
EXPECT_EQ(
delegate.IsRecordExpired(record, *SuitableTrustTokenOrigin::Create(
GURL("https://issuer.example"))),
kTrustTokenDefaultRedemptionRecordExpiry <= base::Time::Now());
static_assert(kTrustTokenDefaultRedemptionRecordExpiry == base::Time::Max(),
"If kTrustTokenDefaultRedemptionRecordExpiry becomes less "
"than base::Time::Max(), add another test case moving the "
"clock past Time::Max() and confirming that a record with the"
" default timestamp is marked as expired.");
}
TEST(ExpiryInspectingRecordExpiryDelegate, ExpiresRecordWithTypeUnsafeExpiry) {
base::test::TaskEnvironment env(
base::test::TaskEnvironment::TimeSource::MOCK_TIME);
auto commitment_result = mojom::TrustTokenKeyCommitmentResult::New();
commitment_result->keys.push_back(mojom::TrustTokenVerificationKey::New(
"key", /*expiry=*/base::Time::Max()));
FixedKeyCommitmentGetter getter(std::move(commitment_result));
ExpiryInspectingRecordExpiryDelegate delegate(&getter);
SignedTrustTokenRedemptionRecord record;
record.set_token_verification_key("key");
std::vector<uint8_t> empty_signature;
cbor::Value::MapValue map;
map[cbor::Value(kExpiryTimestampKey, cbor::Value::Type::STRING)] =
cbor::Value("oops! not an int", cbor::Value::Type::STRING);
record.set_body(*ConstructSignedRedemptionRecord(
*cbor::Writer::Write(cbor::Value(std::move(map))), empty_signature));
// Since the expiry is of the wrong type, the record should be marked as
// expired.
EXPECT_TRUE(delegate.IsRecordExpired(
record,
*SuitableTrustTokenOrigin::Create(GURL("https://issuer.example"))));
}
} // namespace network