Skip to content

Commit

Permalink
[Origin Trials] Validator checks for disabled tokens
Browse files Browse the repository at this point in the history
Change the TrialTokenValidator to check for disabled tokens using the
origin trial policy.

Disabled tokens will be identified by signature, so extract and store
the signature on each token.

BUG=582042

Review-Url: https://codereview.chromium.org/2725033002
Cr-Commit-Position: refs/heads/master@{#454900}
  • Loading branch information
jpchase authored and Commit bot committed Mar 6, 2017
1 parent 2317b2a commit f12fb02
Show file tree
Hide file tree
Showing 6 changed files with 139 additions and 18 deletions.
18 changes: 13 additions & 5 deletions content/common/origin_trials/trial_token.cc
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,19 @@ std::unique_ptr<TrialToken> TrialToken::From(
blink::WebOriginTrialTokenStatus* out_status) {
DCHECK(out_status);
std::string token_payload;
*out_status = Extract(token_text, public_key, &token_payload);
std::string token_signature;
*out_status =
Extract(token_text, public_key, &token_payload, &token_signature);
if (*out_status != blink::WebOriginTrialTokenStatus::Success) {
return nullptr;
}
std::unique_ptr<TrialToken> token = Parse(token_payload);
*out_status = token ? blink::WebOriginTrialTokenStatus::Success
: blink::WebOriginTrialTokenStatus::Malformed;
if (token) {
token->signature_ = token_signature;
*out_status = blink::WebOriginTrialTokenStatus::Success;
} else {
*out_status = blink::WebOriginTrialTokenStatus::Malformed;
}
return token;
}

Expand All @@ -78,7 +84,8 @@ blink::WebOriginTrialTokenStatus TrialToken::IsValid(
blink::WebOriginTrialTokenStatus TrialToken::Extract(
const std::string& token_text,
base::StringPiece public_key,
std::string* out_token_payload) {
std::string* out_token_payload,
std::string* out_token_signature) {
if (token_text.empty()) {
return blink::WebOriginTrialTokenStatus::Malformed;
}
Expand Down Expand Up @@ -129,8 +136,9 @@ blink::WebOriginTrialTokenStatus TrialToken::Extract(
return blink::WebOriginTrialTokenStatus::InvalidSignature;
}

// Return just the payload, as a new string.
// Return the payload and signature, as new strings.
*out_token_payload = token_contents.substr(kPayloadOffset, payload_length);
*out_token_signature = signature.as_string();
return blink::WebOriginTrialTokenStatus::Success;
}

Expand Down
16 changes: 11 additions & 5 deletions content/common/origin_trials/trial_token.h
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ class CONTENT_EXPORT TrialToken {
bool match_subdomains() const { return match_subdomains_; }
std::string feature_name() { return feature_name_; }
base::Time expiry_time() { return expiry_time_; }
std::string signature() { return signature_; }

protected:
// Tests can access the Parse method directly to validate it, and so are
Expand All @@ -69,14 +70,16 @@ class CONTENT_EXPORT TrialToken {
friend class TrialTokenTest;
friend int ::LLVMFuzzerTestOneInput(const uint8_t*, size_t);

// If the string represents a properly signed and well-formed token, the token
// payload is returned in the |out_token_payload| parameter and success is
// returned. Otherwise,the return code indicates what was wrong with the
// string, and |out_token_payload| is unchanged.
// If the string represents a properly signed and well-formed token, success
// is returned, with the token payload and signature returned in the
// |out_token_payload| and |out_token_signature| parameters, respectively.
// Otherwise,the return code indicates what was wrong with the string, and
// |out_token_payload| and |out_token_signature| are unchanged.
static blink::WebOriginTrialTokenStatus Extract(
const std::string& token_text,
base::StringPiece public_key,
std::string* out_token_payload);
std::string* out_token_payload,
std::string* out_token_signature);

// Returns a token object if the string represents a well-formed JSON token
// payload, or nullptr otherwise.
Expand Down Expand Up @@ -107,6 +110,9 @@ class CONTENT_EXPORT TrialToken {

// The time until which this token should be considered valid.
base::Time expiry_time_;

// The signature identifying the fully signed contents of the token.
std::string signature_;
};

} // namespace content
Expand Down
63 changes: 55 additions & 8 deletions content/common/origin_trials/trial_token_unittest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,13 @@ const char* kSampleToken =
"7FO5L22sNvkZZnacLvmfNwsAAABZeyJvcmlnaW4iOiAiaHR0cHM6Ly92YWxpZC5l"
"eGFtcGxlLmNvbTo0NDMiLCAiZmVhdHVyZSI6ICJGcm9idWxhdGUiLCAiZXhwaXJ5"
"IjogMTQ1ODc2NjI3N30=";
const uint8_t kSampleTokenSignature[] = {
0x9f, 0x90, 0xfd, 0x09, 0xb4, 0x10, 0xb6, 0x9d, 0x66, 0xa9, 0x7e,
0x76, 0x51, 0x06, 0x4b, 0x09, 0xc0, 0x56, 0xc1, 0x59, 0x2a, 0x00,
0x84, 0xb5, 0x46, 0x60, 0xf2, 0x27, 0x50, 0x0b, 0x7b, 0x9e, 0x92,
0x42, 0x1e, 0x49, 0x92, 0x18, 0xd6, 0xd7, 0xed, 0xa1, 0x87, 0x6b,
0xc2, 0x1a, 0xa3, 0xec, 0x53, 0xb9, 0x2f, 0x6d, 0xac, 0x36, 0xf9,
0x19, 0x66, 0x76, 0x9c, 0x2e, 0xf9, 0x9f, 0x37, 0x0b};

// This is a good subdomain trial token, signed with the above test private key.
// Generate this token with the command (in tools/origin_trials):
Expand All @@ -74,6 +81,13 @@ const char* kSampleSubdomainToken =
"FjpbmQG+VCPk1NrldVXZng4AAABoeyJvcmlnaW4iOiAiaHR0cHM6Ly9leGFtcGxl"
"LmNvbTo0NDMiLCAiaXNTdWJkb21haW4iOiB0cnVlLCAiZmVhdHVyZSI6ICJGcm9i"
"dWxhdGUiLCAiZXhwaXJ5IjogMTQ1ODc2NjI3N30=";
const uint8_t kSampleSubdomainTokenSignature[] = {
0xeb, 0xbe, 0x8f, 0xd9, 0xd7, 0x01, 0x0a, 0x32, 0xe7, 0xeb, 0x74,
0xd0, 0xc8, 0x96, 0x6a, 0x46, 0x70, 0x14, 0x4c, 0x5c, 0x74, 0xd0,
0xbc, 0x10, 0xd9, 0x11, 0x74, 0xad, 0x60, 0x2f, 0x83, 0x8c, 0x14,
0x74, 0xb4, 0x01, 0xb6, 0x42, 0xb1, 0xcb, 0x25, 0x0d, 0x37, 0x0f,
0xd5, 0xf8, 0xcd, 0x16, 0x3a, 0x5b, 0x99, 0x01, 0xbe, 0x54, 0x23,
0xe4, 0xd4, 0xda, 0xe5, 0x75, 0x55, 0xd9, 0x9e, 0x0e};

// This is a good trial token, explicitly not a subdomain, signed with the above
// test private key. Generate this token with the command:
Expand All @@ -84,6 +98,13 @@ const char* kSampleNonSubdomainToken =
"lvH52Winvy39tHbsU2gJJQYAAABveyJvcmlnaW4iOiAiaHR0cHM6Ly92YWxpZC5l"
"eGFtcGxlLmNvbTo0NDMiLCAiaXNTdWJkb21haW4iOiBmYWxzZSwgImZlYXR1cmUi"
"OiAiRnJvYnVsYXRlIiwgImV4cGlyeSI6IDE0NTg3NjYyNzd9";
const uint8_t kSampleNonSubdomainTokenSignature[] = {
0xb7, 0x83, 0xf7, 0xbf, 0x43, 0xee, 0xd3, 0xb4, 0x96, 0xe4, 0x99,
0x4e, 0xbd, 0x7e, 0xff, 0xe2, 0x7a, 0x13, 0x44, 0x92, 0x8f, 0xf1,
0x84, 0x53, 0x22, 0xca, 0xe3, 0x5a, 0x35, 0x85, 0x71, 0x73, 0x5f,
0x0d, 0x51, 0xed, 0x9d, 0x61, 0x08, 0x31, 0xec, 0xd2, 0x05, 0xd6,
0x55, 0x2b, 0xb5, 0x96, 0xf1, 0xf9, 0xd9, 0x68, 0xa7, 0xbf, 0x2d,
0xfd, 0xb4, 0x76, 0xec, 0x53, 0x68, 0x09, 0x25, 0x06};

const char* kExpectedFeatureName = "Frobulate";
const char* kExpectedOrigin = "https://valid.example.com";
Expand Down Expand Up @@ -198,6 +219,15 @@ class TrialTokenTest : public testing::TestWithParam<const char*> {
expected_expiry_(base::Time::FromDoubleT(kExpectedExpiry)),
valid_timestamp_(base::Time::FromDoubleT(kValidTimestamp)),
invalid_timestamp_(base::Time::FromDoubleT(kInvalidTimestamp)),
expected_signature_(
std::string(reinterpret_cast<const char*>(kSampleTokenSignature),
arraysize(kSampleTokenSignature))),
expected_subdomain_signature_(std::string(
reinterpret_cast<const char*>(kSampleSubdomainTokenSignature),
arraysize(kSampleSubdomainTokenSignature))),
expected_nonsubdomain_signature_(std::string(
reinterpret_cast<const char*>(kSampleNonSubdomainTokenSignature),
arraysize(kSampleNonSubdomainTokenSignature))),
correct_public_key_(
base::StringPiece(reinterpret_cast<const char*>(kTestPublicKey),
arraysize(kTestPublicKey))),
Expand All @@ -208,15 +238,18 @@ class TrialTokenTest : public testing::TestWithParam<const char*> {
protected:
blink::WebOriginTrialTokenStatus Extract(const std::string& token_text,
base::StringPiece public_key,
std::string* token_payload) {
return TrialToken::Extract(token_text, public_key, token_payload);
std::string* token_payload,
std::string* token_signature) {
return TrialToken::Extract(token_text, public_key, token_payload,
token_signature);
}

blink::WebOriginTrialTokenStatus ExtractIgnorePayload(
const std::string& token_text,
base::StringPiece public_key) {
std::string token_payload;
return Extract(token_text, public_key, &token_payload);
std::string token_signature;
return Extract(token_text, public_key, &token_payload, &token_signature);
}

std::unique_ptr<TrialToken> Parse(const std::string& token_payload) {
Expand Down Expand Up @@ -251,6 +284,10 @@ class TrialTokenTest : public testing::TestWithParam<const char*> {
const base::Time valid_timestamp_;
const base::Time invalid_timestamp_;

std::string expected_signature_;
std::string expected_subdomain_signature_;
std::string expected_nonsubdomain_signature_;

private:
base::StringPiece correct_public_key_;
base::StringPiece incorrect_public_key_;
Expand All @@ -264,26 +301,34 @@ class TrialTokenTest : public testing::TestWithParam<const char*> {
// token.
TEST_F(TrialTokenTest, ValidateValidSignature) {
std::string token_payload;
blink::WebOriginTrialTokenStatus status =
Extract(kSampleToken, correct_public_key(), &token_payload);
std::string token_signature;
blink::WebOriginTrialTokenStatus status = Extract(
kSampleToken, correct_public_key(), &token_payload, &token_signature);
ASSERT_EQ(blink::WebOriginTrialTokenStatus::Success, status);
EXPECT_STREQ(kSampleTokenJSON, token_payload.c_str());
EXPECT_EQ(expected_signature_, token_signature);
}

TEST_F(TrialTokenTest, ValidateSubdomainValidSignature) {
std::string token_payload;
std::string token_signature;
blink::WebOriginTrialTokenStatus status =
Extract(kSampleSubdomainToken, correct_public_key(), &token_payload);
Extract(kSampleSubdomainToken, correct_public_key(), &token_payload,
&token_signature);
ASSERT_EQ(blink::WebOriginTrialTokenStatus::Success, status);
EXPECT_STREQ(kSampleSubdomainTokenJSON, token_payload.c_str());
EXPECT_EQ(expected_subdomain_signature_, token_signature);
}

TEST_F(TrialTokenTest, ValidateNonSubdomainValidSignature) {
std::string token_payload;
std::string token_signature;
blink::WebOriginTrialTokenStatus status =
Extract(kSampleNonSubdomainToken, correct_public_key(), &token_payload);
Extract(kSampleNonSubdomainToken, correct_public_key(), &token_payload,
&token_signature);
ASSERT_EQ(blink::WebOriginTrialTokenStatus::Success, status);
EXPECT_STREQ(kSampleNonSubdomainTokenJSON, token_payload.c_str());
EXPECT_EQ(expected_nonsubdomain_signature_, token_signature);
}

TEST_F(TrialTokenTest, ValidateInvalidSignature) {
Expand Down Expand Up @@ -430,13 +475,15 @@ TEST_F(TrialTokenTest, SubdomainTokenIsValid) {
token->IsValid(expected_origin_, invalid_timestamp_));
}

// Test overall extraction, to ensure output status matches returned token
// Test overall extraction, to ensure output status matches returned token, and
// signature is provided.
TEST_F(TrialTokenTest, ExtractValidToken) {
blink::WebOriginTrialTokenStatus status;
std::unique_ptr<TrialToken> token =
TrialToken::From(kSampleToken, correct_public_key(), &status);
EXPECT_TRUE(token);
EXPECT_EQ(blink::WebOriginTrialTokenStatus::Success, status);
EXPECT_EQ(expected_signature_, token->signature());
}

TEST_F(TrialTokenTest, ExtractInvalidSignature) {
Expand Down
4 changes: 4 additions & 0 deletions content/common/origin_trials/trial_token_validator.cc
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ blink::WebOriginTrialTokenStatus TrialTokenValidator::ValidateToken(
if (origin_trial_policy->IsFeatureDisabled(trial_token->feature_name()))
return blink::WebOriginTrialTokenStatus::FeatureDisabled;

// TODO(chasej): Add a new status for disabled tokens
if (origin_trial_policy->IsTokenDisabled(trial_token->signature()))
return blink::WebOriginTrialTokenStatus::FeatureDisabled;

*feature_name = trial_token->feature_name();
return blink::WebOriginTrialTokenStatus::Success;
}
Expand Down
55 changes: 55 additions & 0 deletions content/common/origin_trials/trial_token_validator_unittest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,13 @@ const char kSampleToken[] =
"O4fM3Sa+MEd+5JcIgSZafw8AAABZeyJvcmlnaW4iOiAiaHR0cHM6Ly92YWxpZC5l"
"eGFtcGxlLmNvbTo0NDMiLCAiZmVhdHVyZSI6ICJGcm9idWxhdGUiLCAiZXhwaXJ5"
"IjogMjAwMDAwMDAwMH0=";
const uint8_t kSampleTokenSignature[] = {
0xe4, 0x7f, 0xd6, 0x68, 0x3e, 0xff, 0x0e, 0x51, 0x38, 0xb3, 0x79,
0xe0, 0xe9, 0x36, 0xd2, 0xb0, 0x29, 0x2b, 0x7a, 0x29, 0x81, 0x1e,
0xd3, 0xab, 0xd6, 0x5f, 0xce, 0x10, 0x13, 0x42, 0x69, 0xc2, 0x6b,
0xe0, 0x6d, 0x3c, 0x0d, 0x51, 0x47, 0x0e, 0x0d, 0x8a, 0x07, 0xf7,
0xdf, 0xaa, 0xfe, 0x3b, 0x87, 0xcc, 0xdd, 0x26, 0xbe, 0x30, 0x47,
0x7e, 0xe4, 0x97, 0x08, 0x81, 0x26, 0x5a, 0x7f, 0x0f};

// The token should be valid for this origin and for this feature.
const char kAppropriateOrigin[] = "https://valid.example.com";
Expand All @@ -85,6 +92,13 @@ const char kExpiredToken[] =
"RrOtlAwa0gPqqn+A8GTD3AQAAABZeyJvcmlnaW4iOiAiaHR0cHM6Ly92YWxpZC5l"
"eGFtcGxlLmNvbTo0NDMiLCAiZmVhdHVyZSI6ICJGcm9idWxhdGUiLCAiZXhwaXJ5"
"IjogMTAwMDAwMDAwMH0=";
const uint8_t kExpiredTokenSignature[] = {
0x61, 0xcf, 0x50, 0x85, 0xcc, 0x69, 0x77, 0xbd, 0x8d, 0x65, 0xbc,
0x90, 0x97, 0x83, 0x15, 0x7a, 0x25, 0x56, 0x34, 0xfd, 0xde, 0x9e,
0x17, 0x32, 0x72, 0xb8, 0xfa, 0x33, 0x18, 0x77, 0x6a, 0x63, 0xaa,
0xd1, 0x5c, 0x60, 0x1d, 0x5b, 0x52, 0x67, 0x43, 0xf0, 0xfb, 0xa7,
0x40, 0xa3, 0x3e, 0x46, 0xb3, 0xad, 0x94, 0x0c, 0x1a, 0xd2, 0x03,
0xea, 0xaa, 0x7f, 0x80, 0xf0, 0x64, 0xc3, 0xdc, 0x04};

const char kUnparsableToken[] = "abcde";

Expand Down Expand Up @@ -119,10 +133,19 @@ class TestOriginTrialPolicy : public OriginTrialPolicy {
void DisableFeature(const std::string& feature) {
disabled_features_.insert(feature);
}
void DisableToken(const std::string& token) {
disabled_tokens_.insert(token);
}

protected:
bool IsTokenDisabled(base::StringPiece token_signature) const override {
return disabled_tokens_.count(token_signature.as_string()) > 0;
}

private:
const uint8_t* key_ = nullptr;
std::set<std::string> disabled_features_;
std::set<std::string> disabled_tokens_;
};

class TestContentClient : public ContentClient {
Expand All @@ -138,6 +161,9 @@ class TestContentClient : public ContentClient {
void DisableFeature(const std::string& feature) {
origin_trial_policy_.DisableFeature(feature);
}
void DisableToken(const std::string& token_signature) {
origin_trial_policy_.DisableToken(token_signature);
}

private:
TestOriginTrialPolicy origin_trial_policy_;
Expand All @@ -151,6 +177,12 @@ class TrialTokenValidatorTest : public testing::Test {
: appropriate_origin_(GURL(kAppropriateOrigin)),
inappropriate_origin_(GURL(kInappropriateOrigin)),
insecure_origin_(GURL(kInsecureOrigin)),
valid_token_signature_(
std::string(reinterpret_cast<const char*>(kSampleTokenSignature),
arraysize(kSampleTokenSignature))),
expired_token_signature_(
std::string(reinterpret_cast<const char*>(kExpiredTokenSignature),
arraysize(kExpiredTokenSignature))),
response_headers_(new net::HttpResponseHeaders("")) {
SetPublicKey(kTestPublicKey);
SetContentClient(&test_content_client_);
Expand All @@ -175,10 +207,17 @@ class TrialTokenValidatorTest : public testing::Test {
test_content_client_.DisableFeature(feature);
}

void DisableToken(const std::string& token_signature) {
test_content_client_.DisableToken(token_signature);
}

const url::Origin appropriate_origin_;
const url::Origin inappropriate_origin_;
const url::Origin insecure_origin_;

std::string valid_token_signature_;
std::string expired_token_signature_;

scoped_refptr<net::HttpResponseHeaders> response_headers_;

private:
Expand Down Expand Up @@ -259,6 +298,22 @@ TEST_F(TrialTokenValidatorTest, MAYBE_ValidatorRespectsDisabledFeatures) {
appropriate_origin_, &feature));
}

TEST_F(TrialTokenValidatorTest, ValidatorRespectsDisabledTokens) {
std::string feature;
// Disable an irrelevant token; token should still validate
DisableToken(expired_token_signature_);
EXPECT_EQ(blink::WebOriginTrialTokenStatus::Success,
TrialTokenValidator::ValidateToken(kSampleToken,
appropriate_origin_, &feature));
EXPECT_EQ(kAppropriateFeatureName, feature);
// Disable the token; it should no longer be valid
DisableToken(valid_token_signature_);
// TODO(chasej): Add a new status for disabled tokens
EXPECT_EQ(blink::WebOriginTrialTokenStatus::FeatureDisabled,
TrialTokenValidator::ValidateToken(kSampleToken,
appropriate_origin_, &feature));
}

TEST_F(TrialTokenValidatorTest, ValidateRequestInsecure) {
response_headers_->AddHeader(std::string("Origin-Trial: ") +
kInsecureOriginToken);
Expand Down
1 change: 1 addition & 0 deletions tools/origin_trials/generate_token.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ def main():
print " Is Subdomain: %s" % args.is_subdomain
print " Feature: %s" % args.trial_name
print " Expiry: %d (%s UTC)" % (expiry, datetime.utcfromtimestamp(expiry))
print " Signature: %s" % ", ".join('0x%02x' % ord(x) for x in signature)
print

# Output the properly-formatted token.
Expand Down

0 comments on commit f12fb02

Please sign in to comment.