blob: c9017607a3a24b2c9ae72321a24707a3c37406e9 [file] [log] [blame]
// Copyright 2016 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 "third_party/blink/public/common/origin_trials/trial_token_validator.h"
#include <memory>
#include <set>
#include <string>
#include "base/bind.h"
#include "base/memory/ptr_util.h"
#include "base/stl_util.h"
#include "base/strings/string_util.h"
#include "base/test/simple_test_clock.h"
#include "base/time/time.h"
#include "net/http/http_response_headers.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/origin_trials/origin_trial_policy.h"
#include "third_party/blink/public/common/origin_trials/trial_token.h"
#include "url/gurl.h"
namespace blink {
namespace trial_token_validator_unittest {
// This is a sample public key for testing the API. The corresponding private
// key (use this to generate new samples for this test file) is:
//
// 0x83, 0x67, 0xf4, 0xcd, 0x2a, 0x1f, 0x0e, 0x04, 0x0d, 0x43, 0x13,
// 0x4c, 0x67, 0xc4, 0xf4, 0x28, 0xc9, 0x90, 0x15, 0x02, 0xe2, 0xba,
// 0xfd, 0xbb, 0xfa, 0xbc, 0x92, 0x76, 0x8a, 0x2c, 0x4b, 0xc7, 0x75,
// 0x10, 0xac, 0xf9, 0x3a, 0x1c, 0xb8, 0xa9, 0x28, 0x70, 0xd2, 0x9a,
// 0xd0, 0x0b, 0x59, 0xe1, 0xac, 0x2b, 0xb7, 0xd5, 0xca, 0x1f, 0x64,
// 0x90, 0x08, 0x8e, 0xa8, 0xe0, 0x56, 0x3a, 0x04, 0xd0
const uint8_t kTestPublicKey[] = {
0x75, 0x10, 0xac, 0xf9, 0x3a, 0x1c, 0xb8, 0xa9, 0x28, 0x70, 0xd2,
0x9a, 0xd0, 0x0b, 0x59, 0xe1, 0xac, 0x2b, 0xb7, 0xd5, 0xca, 0x1f,
0x64, 0x90, 0x08, 0x8e, 0xa8, 0xe0, 0x56, 0x3a, 0x04, 0xd0,
};
// 0x21, 0xee, 0xfa, 0x81, 0x6a, 0xff, 0xdf, 0xb8, 0xc1, 0xdd, 0x75,
// 0x05, 0x04, 0x29, 0x68, 0x67, 0x60, 0x85, 0x91, 0xd0, 0x50, 0x16,
// 0x0a, 0xcf, 0xa2, 0x37, 0xa3, 0x2e, 0x11, 0x7a, 0x17, 0x96, 0x50,
// 0x07, 0x4d, 0x76, 0x55, 0x56, 0x42, 0x17, 0x2d, 0x8a, 0x9c, 0x47,
// 0x96, 0x25, 0xda, 0x70, 0xaa, 0xb9, 0xfd, 0x53, 0x5d, 0x51, 0x3e,
// 0x16, 0xab, 0xb4, 0x86, 0xea, 0xf3, 0x35, 0xc6, 0xca
const uint8_t kTestPublicKey2[] = {
0x50, 0x07, 0x4d, 0x76, 0x55, 0x56, 0x42, 0x17, 0x2d, 0x8a, 0x9c,
0x47, 0x96, 0x25, 0xda, 0x70, 0xaa, 0xb9, 0xfd, 0x53, 0x5d, 0x51,
0x3e, 0x16, 0xab, 0xb4, 0x86, 0xea, 0xf3, 0x35, 0xc6, 0xca,
};
// This is a good trial token, signed with the above test private key.
// TODO(iclelland): This token expires in 2033. Update it or find a way
// to autogenerate it before then.
// Generate this token with the command (in tools/origin_trials):
// generate_token.py valid.example.com Frobulate --expire-timestamp=2000000000
const char kSampleToken[] =
"AuR/1mg+/w5ROLN54Ok20rApK3opgR7Tq9ZfzhATQmnCa+BtPA1RRw4Nigf336r+"
"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";
const char kAppropriateFeatureName[] = "Frobulate";
const char kInappropriateFeatureName[] = "Grokalyze";
const char kInappropriateOrigin[] = "https://invalid.example.com";
const char kInsecureOrigin[] = "http://valid.example.com";
// Well-formed trial token with an invalid signature.
// This token is a corruption of the above valid token.
const char kInvalidSignatureToken[] =
"AuR/1mg+/w5ROLN54Ok20rApK3opgR7Tq9ZfzhATQmnCa+BtPA1RRw4Nigf336r+"
"RrOtlAwa0gPqqn+A8GTD3AQAAABZeyJvcmlnaW4iOiAiaHR0cHM6Ly92YWxpZC5l"
"eGFtcGxlLmNvbTo0NDMiLCAiZmVhdHVyZSI6ICJGcm9idWxhdGUiLCAiZXhwaXJ5"
"IjogMjAwMDAwMDAwMH0=";
// Well-formed, but expired, trial token. (Expired in 2001)
// Generate this token with the command (in tools/origin_trials):
// generate_token.py valid.example.com Frobulate --expire-timestamp=1000000000
const char kExpiredToken[] =
"AmHPUIXMaXe9jWW8kJeDFXolVjT93p4XMnK4+jMYd2pjqtFcYB1bUmdD8PunQKM+"
"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";
// Well-formed token, for an insecure origin.
// Generate this token with the command (in tools/origin_trials):
// generate_token.py http://valid.example.com Frobulate
// --expire-timestamp=2000000000
const char kInsecureOriginToken[] =
"AjfC47H1q8/Ho5ALFkjkwf9CBK6oUUeRTlFc50Dj+eZEyGGKFIY2WTxMBfy8cLc3"
"E0nmFroDA3OmABmO5jMCFgkAAABXeyJvcmlnaW4iOiAiaHR0cDovL3ZhbGlkLmV4"
"YW1wbGUuY29tOjgwIiwgImZlYXR1cmUiOiAiRnJvYnVsYXRlIiwgImV4cGlyeSI6"
"IDIwMDAwMDAwMDB9";
// This timestamp is set to a time after the expiry timestamp of kExpiredToken,
// but before the expiry timestamp of kValidToken.
double kNowTimestamp = 1500000000;
class TestOriginTrialPolicy : public OriginTrialPolicy {
public:
bool IsOriginTrialsSupported() const override { return true; }
bool IsOriginSecure(const GURL& url) const override {
return url.SchemeIs("https");
}
base::StringPiece GetPublicKey() const override {
return base::StringPiece(reinterpret_cast<const char*>(key_),
base::size(kTestPublicKey));
}
bool IsFeatureDisabled(base::StringPiece feature) const override {
return disabled_features_.count(feature.as_string()) > 0;
}
// Test setup methods
void SetPublicKey(const uint8_t* key) { key_ = key; }
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 TrialTokenValidatorTest : public testing::Test {
public:
TrialTokenValidatorTest()
: appropriate_origin_(url::Origin::Create(GURL(kAppropriateOrigin))),
inappropriate_origin_(url::Origin::Create(GURL(kInappropriateOrigin))),
insecure_origin_(url::Origin::Create(GURL(kInsecureOrigin))),
valid_token_signature_(
std::string(reinterpret_cast<const char*>(kSampleTokenSignature),
base::size(kSampleTokenSignature))),
expired_token_signature_(
std::string(reinterpret_cast<const char*>(kExpiredTokenSignature),
base::size(kExpiredTokenSignature))),
response_headers_(new net::HttpResponseHeaders("")) {
TrialTokenValidator::SetOriginTrialPolicyGetter(
base::BindRepeating([](OriginTrialPolicy* policy) { return policy; },
base::Unretained(&policy_)));
SetPublicKey(kTestPublicKey);
}
~TrialTokenValidatorTest() override {
TrialTokenValidator::ResetOriginTrialPolicyGetter();
}
void SetPublicKey(const uint8_t* key) { policy_.SetPublicKey(key); }
void DisableFeature(const std::string& feature) {
policy_.DisableFeature(feature);
}
void DisableToken(const std::string& token_signature) {
policy_.DisableToken(token_signature);
}
base::Time Now() { return base::Time::FromDoubleT(kNowTimestamp); }
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_;
TestOriginTrialPolicy policy_;
const TrialTokenValidator validator_;
};
TEST_F(TrialTokenValidatorTest, ValidateValidToken) {
std::string feature;
EXPECT_EQ(blink::OriginTrialTokenStatus::kSuccess,
validator_.ValidateToken(kSampleToken, appropriate_origin_,
&feature, Now()));
EXPECT_EQ(kAppropriateFeatureName, feature);
}
TEST_F(TrialTokenValidatorTest, ValidateInappropriateOrigin) {
std::string feature;
EXPECT_EQ(blink::OriginTrialTokenStatus::kWrongOrigin,
validator_.ValidateToken(kSampleToken, inappropriate_origin_,
&feature, Now()));
EXPECT_EQ(blink::OriginTrialTokenStatus::kWrongOrigin,
validator_.ValidateToken(kSampleToken, insecure_origin_, &feature,
Now()));
}
TEST_F(TrialTokenValidatorTest, ValidateInvalidSignature) {
std::string feature;
EXPECT_EQ(blink::OriginTrialTokenStatus::kInvalidSignature,
validator_.ValidateToken(kInvalidSignatureToken,
appropriate_origin_, &feature, Now()));
}
TEST_F(TrialTokenValidatorTest, ValidateUnparsableToken) {
std::string feature;
EXPECT_EQ(blink::OriginTrialTokenStatus::kMalformed,
validator_.ValidateToken(kUnparsableToken, appropriate_origin_,
&feature, Now()));
}
TEST_F(TrialTokenValidatorTest, ValidateExpiredToken) {
std::string feature;
EXPECT_EQ(blink::OriginTrialTokenStatus::kExpired,
validator_.ValidateToken(kExpiredToken, appropriate_origin_,
&feature, Now()));
}
TEST_F(TrialTokenValidatorTest, ValidateValidTokenWithIncorrectKey) {
std::string feature;
SetPublicKey(kTestPublicKey2);
EXPECT_EQ(blink::OriginTrialTokenStatus::kInvalidSignature,
validator_.ValidateToken(kSampleToken, appropriate_origin_,
&feature, Now()));
}
TEST_F(TrialTokenValidatorTest, ValidatorRespectsDisabledFeatures) {
std::string feature;
// Disable an irrelevant feature; token should still validate
DisableFeature(kInappropriateFeatureName);
EXPECT_EQ(blink::OriginTrialTokenStatus::kSuccess,
validator_.ValidateToken(kSampleToken, appropriate_origin_,
&feature, Now()));
EXPECT_EQ(kAppropriateFeatureName, feature);
// Disable the token's feature; it should no longer be valid
DisableFeature(kAppropriateFeatureName);
EXPECT_EQ(blink::OriginTrialTokenStatus::kFeatureDisabled,
validator_.ValidateToken(kSampleToken, appropriate_origin_,
&feature, Now()));
}
TEST_F(TrialTokenValidatorTest, ValidatorRespectsDisabledTokens) {
std::string feature;
// Disable an irrelevant token; token should still validate
DisableToken(expired_token_signature_);
EXPECT_EQ(blink::OriginTrialTokenStatus::kSuccess,
validator_.ValidateToken(kSampleToken, appropriate_origin_,
&feature, Now()));
EXPECT_EQ(kAppropriateFeatureName, feature);
// Disable the token; it should no longer be valid
DisableToken(valid_token_signature_);
EXPECT_EQ(blink::OriginTrialTokenStatus::kTokenDisabled,
validator_.ValidateToken(kSampleToken, appropriate_origin_,
&feature, Now()));
}
TEST_F(TrialTokenValidatorTest, ValidateRequestInsecure) {
response_headers_->AddHeader(std::string("Origin-Trial: ") +
kInsecureOriginToken);
EXPECT_FALSE(validator_.RequestEnablesFeature(
GURL(kInsecureOrigin), response_headers_.get(), kAppropriateFeatureName,
Now()));
}
TEST_F(TrialTokenValidatorTest, ValidateRequestValidToken) {
response_headers_->AddHeader(std::string("Origin-Trial: ") + kSampleToken);
EXPECT_TRUE(validator_.RequestEnablesFeature(GURL(kAppropriateOrigin),
response_headers_.get(),
kAppropriateFeatureName, Now()));
}
TEST_F(TrialTokenValidatorTest, ValidateRequestNoTokens) {
EXPECT_FALSE(validator_.RequestEnablesFeature(
GURL(kAppropriateOrigin), response_headers_.get(),
kAppropriateFeatureName, Now()));
}
TEST_F(TrialTokenValidatorTest, ValidateRequestMultipleHeaders) {
response_headers_->AddHeader(std::string("Origin-Trial: ") + kSampleToken);
response_headers_->AddHeader(std::string("Origin-Trial: ") + kExpiredToken);
EXPECT_TRUE(validator_.RequestEnablesFeature(GURL(kAppropriateOrigin),
response_headers_.get(),
kAppropriateFeatureName, Now()));
EXPECT_FALSE(validator_.RequestEnablesFeature(
GURL(kAppropriateOrigin), response_headers_.get(),
kInappropriateFeatureName, Now()));
EXPECT_FALSE(validator_.RequestEnablesFeature(
GURL(kInappropriateOrigin), response_headers_.get(),
kAppropriateFeatureName, Now()));
}
TEST_F(TrialTokenValidatorTest, ValidateRequestMultipleHeaderValues) {
response_headers_->AddHeader(std::string("Origin-Trial: ") + kExpiredToken +
", " + kSampleToken);
EXPECT_TRUE(validator_.RequestEnablesFeature(GURL(kAppropriateOrigin),
response_headers_.get(),
kAppropriateFeatureName, Now()));
EXPECT_FALSE(validator_.RequestEnablesFeature(
GURL(kAppropriateOrigin), response_headers_.get(),
kInappropriateFeatureName, Now()));
EXPECT_FALSE(validator_.RequestEnablesFeature(
GURL(kInappropriateOrigin), response_headers_.get(),
kAppropriateFeatureName, Now()));
}
} // namespace trial_token_validator_unittest
} // namespace blink