diff options
author | iclelland <iclelland@chromium.org> | 2016-02-02 07:00:36 -0800 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2016-02-02 15:01:56 +0000 |
commit | 5b27fa016f2736e2186984e5f737e805c2836d9c (patch) | |
tree | f2c48ffafec4d3d376a4df3cb013c3059251ab83 /content | |
parent | e72c97cfbd4829b5fbec934e67460886be345882 (diff) | |
download | chromium_src-5b27fa016f2736e2186984e5f737e805c2836d9c.zip chromium_src-5b27fa016f2736e2186984e5f737e805c2836d9c.tar.gz chromium_src-5b27fa016f2736e2186984e5f737e805c2836d9c.tar.bz2 |
Rename Experimental Framework classes and concepts
"Experiments" is far too generic a term to use for the trials enabled by the experimental framework, so this patch renames everything to (hopefully) more precise terms.
These changes are most prominent:
core::ExperimentalFeatures class => OriginTrials
core::Experiments class => OriginTrialContext
API Key => Trial Token (or "Token" where unambiguous)
API => Feature
/experiments/ directories => /origin_trials/
api_name (RuntimeEnabledFeatures) => origin_trial_feature_name
BUG=575808
Review URL: https://codereview.chromium.org/1635593004
Cr-Commit-Position: refs/heads/master@{#372943}
Diffstat (limited to 'content')
-rw-r--r-- | content/child/blink_platform_impl.cc | 4 | ||||
-rw-r--r-- | content/child/blink_platform_impl.h | 6 | ||||
-rw-r--r-- | content/child/experiments/api_key_validator.cc | 27 | ||||
-rw-r--r-- | content/child/experiments/api_key_validator.h | 33 | ||||
-rw-r--r-- | content/child/origin_trials/trial_token_validator.cc | 24 | ||||
-rw-r--r-- | content/child/origin_trials/trial_token_validator.h | 33 | ||||
-rw-r--r-- | content/common/experiments/api_key.h | 95 | ||||
-rw-r--r-- | content/common/experiments/api_key_unittest.cc | 191 | ||||
-rw-r--r-- | content/common/origin_trials/trial_token.cc (renamed from content/common/experiments/api_key.cc) | 67 | ||||
-rw-r--r-- | content/common/origin_trials/trial_token.h | 96 | ||||
-rw-r--r-- | content/common/origin_trials/trial_token_unittest.cc | 192 | ||||
-rw-r--r-- | content/content_child.gypi | 4 | ||||
-rw-r--r-- | content/content_common.gypi | 4 | ||||
-rw-r--r-- | content/content_tests.gypi | 2 |
14 files changed, 388 insertions, 390 deletions
diff --git a/content/child/blink_platform_impl.cc b/content/child/blink_platform_impl.cc index d798e5c..7671d05 100644 --- a/content/child/blink_platform_impl.cc +++ b/content/child/blink_platform_impl.cc @@ -1062,8 +1062,8 @@ blink::WebSyncProvider* BlinkPlatformImpl::backgroundSyncProvider() { main_thread_task_runner_.get()); } -blink::WebApiKeyValidator* BlinkPlatformImpl::apiKeyValidator() { - return &api_key_validator_; +blink::WebTrialTokenValidator* BlinkPlatformImpl::trialTokenValidator() { + return &trial_token_validator_; } WebThemeEngine* BlinkPlatformImpl::themeEngine() { diff --git a/content/child/blink_platform_impl.h b/content/child/blink_platform_impl.h index 47787d5..1418689 100644 --- a/content/child/blink_platform_impl.h +++ b/content/child/blink_platform_impl.h @@ -15,7 +15,7 @@ #include "base/trace_event/trace_event.h" #include "build/build_config.h" #include "components/webcrypto/webcrypto_impl.h" -#include "content/child/experiments/api_key_validator.h" +#include "content/child/origin_trials/trial_token_validator.h" #include "content/child/webfallbackthemeengine_impl.h" #include "content/common/content_export.h" #include "third_party/WebKit/public/platform/Platform.h" @@ -159,7 +159,7 @@ class CONTENT_EXPORT BlinkPlatformImpl blink::WebServicePortProviderClient*) override; blink::WebPermissionClient* permissionClient() override; blink::WebSyncProvider* backgroundSyncProvider() override; - blink::WebApiKeyValidator* apiKeyValidator() override; + blink::WebTrialTokenValidator* trialTokenValidator() override; blink::WebString domCodeStringFromEnum(int dom_code) override; int domEnumFromCodeString(const blink::WebString& codeString) override; @@ -197,7 +197,7 @@ class CONTENT_EXPORT BlinkPlatformImpl scoped_ptr<PermissionDispatcher> permission_client_; scoped_ptr<BackgroundSyncProvider> main_thread_sync_provider_; - ApiKeyValidator api_key_validator_; + TrialTokenValidator trial_token_validator_; scheduler::WebThreadBase* compositor_thread_; }; diff --git a/content/child/experiments/api_key_validator.cc b/content/child/experiments/api_key_validator.cc deleted file mode 100644 index 3d87ecfb..0000000 --- a/content/child/experiments/api_key_validator.cc +++ /dev/null @@ -1,27 +0,0 @@ -// 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 "content/child/experiments/api_key_validator.h" - -#include "base/time/time.h" -#include "content/common/experiments/api_key.h" - -namespace content { - -ApiKeyValidator::ApiKeyValidator() {} -ApiKeyValidator::~ApiKeyValidator() {} - -bool ApiKeyValidator::validateApiKey(const blink::WebString& apiKey, - const blink::WebString& origin, - const blink::WebString& apiName) { - bool result = false; - scoped_ptr<ApiKey> key = ApiKey::Parse(apiKey.utf8()); - if (key) { - result = (key->IsAppropriate(origin.utf8(), apiName.utf8()) && - key->IsValid(base::Time::Now())); - } - return result; -} - -} // namespace content diff --git a/content/child/experiments/api_key_validator.h b/content/child/experiments/api_key_validator.h deleted file mode 100644 index 1be8434..0000000 --- a/content/child/experiments/api_key_validator.h +++ /dev/null @@ -1,33 +0,0 @@ -// 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. - -#ifndef CONTENT_CHILD_EXPERIMENTS_API_KEY_VALIDATOR_H_ -#define CONTENT_CHILD_EXPERIMENTS_API_KEY_VALIDATOR_H_ - -#include <string> -#include "third_party/WebKit/public/platform/WebApiKeyValidator.h" - -namespace content { - -// The ApiKeyValidator is called by the Experimental Framework code in Blink to -// validate tokens to enable experimental APIs. -// -// This class is thread-safe. -class ApiKeyValidator : public blink::WebApiKeyValidator { - public: - ApiKeyValidator(); - ~ApiKeyValidator() override; - - // blink::WebApiKeyValidator implementation - bool validateApiKey(const blink::WebString& apiKey, - const blink::WebString& origin, - const blink::WebString& apiName) override; - - private: - DISALLOW_COPY_AND_ASSIGN(ApiKeyValidator); -}; - -} // namespace content - -#endif // CONTENT_CHILD_EXPERIMENTS_API_KEY_VALIDATOR_H_ diff --git a/content/child/origin_trials/trial_token_validator.cc b/content/child/origin_trials/trial_token_validator.cc new file mode 100644 index 0000000..b1015c5 --- /dev/null +++ b/content/child/origin_trials/trial_token_validator.cc @@ -0,0 +1,24 @@ +// 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 "content/child/origin_trials/trial_token_validator.h" + +#include "base/time/time.h" +#include "content/common/origin_trials/trial_token.h" + +namespace content { + +TrialTokenValidator::TrialTokenValidator() {} +TrialTokenValidator::~TrialTokenValidator() {} + +bool TrialTokenValidator::validateToken(const blink::WebString& token, + const blink::WebString& origin, + const blink::WebString& featureName) { + scoped_ptr<TrialToken> trialToken = TrialToken::Parse(token.utf8()); + return trialToken && + trialToken->IsAppropriate(origin.utf8(), featureName.utf8()) && + trialToken->IsValid(base::Time::Now()); +} + +} // namespace content diff --git a/content/child/origin_trials/trial_token_validator.h b/content/child/origin_trials/trial_token_validator.h new file mode 100644 index 0000000..2013bc1 --- /dev/null +++ b/content/child/origin_trials/trial_token_validator.h @@ -0,0 +1,33 @@ +// 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. + +#ifndef CONTENT_CHILD_ORIGIN_TRIALS_TRIAL_TOKEN_VALIDATOR_H_ +#define CONTENT_CHILD_ORIGIN_TRIALS_TRIAL_TOKEN_VALIDATOR_H_ + +#include <string> +#include "third_party/WebKit/public/platform/WebTrialTokenValidator.h" + +namespace content { + +// The TrialTokenValidator is called by the Experimental Framework code in Blink +// to validate tokens to enable experimental features. +// +// This class is thread-safe. +class TrialTokenValidator : public blink::WebTrialTokenValidator { + public: + TrialTokenValidator(); + ~TrialTokenValidator() override; + + // blink::WebTrialTokenValidator implementation + bool validateToken(const blink::WebString& token, + const blink::WebString& origin, + const blink::WebString& featureName) override; + + private: + DISALLOW_COPY_AND_ASSIGN(TrialTokenValidator); +}; + +} // namespace content + +#endif // CONTENT_CHILD_ORIGIN_TRIALS_TRIAL_TOKEN_VALIDATOR_H_ diff --git a/content/common/experiments/api_key.h b/content/common/experiments/api_key.h deleted file mode 100644 index 1ad575a1..0000000 --- a/content/common/experiments/api_key.h +++ /dev/null @@ -1,95 +0,0 @@ -// Copyright 2015 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 CONTENT_COMMON_EXPERIMENTS_API_KEY_H_ -#define CONTENT_COMMON_EXPERIMENTS_API_KEY_H_ - -#include <string> - -#include "base/memory/scoped_ptr.h" -#include "base/strings/string_piece.h" -#include "base/time/time.h" -#include "content/common/content_export.h" -#include "url/gurl.h" - -namespace content { - -// The Experimental Framework (EF) provides limited access to experimental APIs, -// on a per-origin basis. This class defines the API key data structure, used -// to securely provide access to an experimental API. -// -// Experimental APIs are defined by string names, provided by the implementers. -// The EF code does not maintain an enum or constant list for experiment names. -// Instead, the EF validates the name provided by the API implementation against -// any provided API keys. -// -// More documentation on the key format can be found at -// https://docs.google.com/document/d/1v5fi0EUV_QHckVHVF2K4P72iNywnrJtNhNZ6i2NPt0M - -class CONTENT_EXPORT ApiKey { - public: - ~ApiKey(); - - // Returns a key object if the string represents a well-formed key, or - // nullptr otherwise. (This does not mean that the key is valid, just that it - // can be parsed.) - static scoped_ptr<ApiKey> Parse(const std::string& key_text); - - // Returns true if this API is appropriate for use by the given origin, for - // the given API name. This does not check whether the signature is valid, or - // whether the key itself has expired. - bool IsAppropriate(const std::string& origin, - const std::string& apiName) const; - - // Returns true if this API key has a valid signature, and has not expired. - bool IsValid(const base::Time& now) const; - - std::string signature() { return signature_; } - std::string data() { return data_; } - GURL origin() { return origin_; } - std::string api_name() { return api_name_; } - uint64_t expiry_timestamp() { return expiry_timestamp_; } - - protected: - friend class ApiKeyTest; - - bool ValidateOrigin(const std::string& origin) const; - bool ValidateApiName(const std::string& api_name) const; - bool ValidateDate(const base::Time& now) const; - bool ValidateSignature(const base::StringPiece& public_key) const; - - static bool ValidateSignature(const std::string& signature_text, - const std::string& data, - const base::StringPiece& public_key); - - private: - ApiKey(const std::string& signature, - const std::string& data, - const GURL& origin, - const std::string& api_name, - uint64_t expiry_timestamp); - - // The base64-encoded-signature portion of the key. For the key to be valid, - // this must be a valid signature for the data portion of the key, as verified - // by the public key in api_key.cc. - std::string signature_; - - // The portion of the key string which is signed, and whose signature is - // contained in the signature_ member. - std::string data_; - - // The origin for which this key is valid. Must be a secure origin. - GURL origin_; - - // The name of the API experiment which this key enables. - std::string api_name_; - - // The time until which this key should be considered valid, in UTC, as - // seconds since the Unix epoch. - uint64_t expiry_timestamp_; -}; - -} // namespace content - -#endif // CONTENT_COMMON_EXPERIMENTS_API_KEY_H_ diff --git a/content/common/experiments/api_key_unittest.cc b/content/common/experiments/api_key_unittest.cc deleted file mode 100644 index a70ffcc..0000000 --- a/content/common/experiments/api_key_unittest.cc +++ /dev/null @@ -1,191 +0,0 @@ -// Copyright 2015 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 "content/common/experiments/api_key.h" - -#include "base/macros.h" -#include "base/memory/scoped_ptr.h" -#include "base/strings/string_util.h" -#include "base/test/simple_test_clock.h" -#include "base/time/time.h" -#include "testing/gtest/include/gtest/gtest.h" - -namespace content { - -namespace { - -// 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, -}; - -// This is a good key, signed with the above test private key. -const char* kSampleAPIKey = - "UsEO0cNxoUtBnHDJdGPWTlXuLENjXcEIPL7Bs7sbvicPCcvAtyqhQuTJ9h/u1R3VZpWigtI+S" - "dUwk7Dyk/qbDw==|https://valid.example.com|Frobulate|1458766277"; -const char* kExpectedAPIKeySignature = - "UsEO0cNxoUtBnHDJdGPWTlXuLENjXcEIPL7Bs7sbvicPCcvAtyqhQuTJ9h/u1R3VZpWigtI+S" - "dUwk7Dyk/qbDw=="; -const char* kExpectedAPIKeyData = - "https://valid.example.com|Frobulate|1458766277"; -const char* kExpectedAPIName = "Frobulate"; -const char* kExpectedOrigin = "https://valid.example.com"; -const uint64_t kExpectedExpiry = 1458766277; - -// The key should not be valid for this origin, or for this API. -const char* kInvalidOrigin = "https://invalid.example.com"; -const char* kInsecureOrigin = "http://valid.example.com"; -const char* kInvalidAPIName = "Grokalyze"; - -// The key should be valid if the current time is kValidTimestamp or earlier. -double kValidTimestamp = 1458766276.0; - -// The key should be invalid if the current time is kInvalidTimestamp or later. -double kInvalidTimestamp = 1458766278.0; - -// Well-formed API key with an invalid signature. -const char* kInvalidSignatureAPIKey = - "CO8hDne98QeFeOJ0DbRZCBN3uE0nyaPgaLlkYhSWnbRoDfEAg+TXELaYfQPfEvKYFauBg/hnx" - "mba765hz0mXMc==|https://valid.example.com|Frobulate|1458766277"; - -// Various ill-formed API keys. These should all fail to parse. -const char* kInvalidAPIKeys[] = { - // Invalid - only one part - "abcde", - // Not enough parts - "https://valid.example.com|APIName|1458766277", - // Delimiter in API Name - "Signature|https://valid.example.com|API|Name|1458766277", - // Extra string field - "Signature|https://valid.example.com|APIName|1458766277|SomethingElse", - // Extra numeric field - "Signature|https://valid.example.com|APIName|1458766277|1458766277", - // Non-numeric expiry timestamp - "Signature|https://valid.example.com|APIName|abcdefghij", - "Signature|https://valid.example.com|APIName|1458766277x", - // Negative expiry timestamp - "Signature|https://valid.example.com|APIName|-1458766277", - // Origin not a proper origin URL - "Signature|abcdef|APIName|1458766277", - "Signature|data:text/plain,abcdef|APIName|1458766277", - "Signature|javascript:alert(1)|APIName|1458766277"}; -const size_t kNumInvalidAPIKeys = arraysize(kInvalidAPIKeys); - -} // namespace - -class ApiKeyTest : public testing::Test { - public: - ApiKeyTest() - : public_key_( - base::StringPiece(reinterpret_cast<const char*>(kTestPublicKey), - arraysize(kTestPublicKey))) {} - - protected: - bool ValidateOrigin(ApiKey* api_key, const char* origin) { - return api_key->ValidateOrigin(origin); - } - - bool ValidateApiName(ApiKey* api_key, const char* api_name) { - return api_key->ValidateApiName(api_name); - } - - bool ValidateDate(ApiKey* api_key, const base::Time& now) { - return api_key->ValidateDate(now); - } - - bool ValidateSignature(ApiKey* api_key, const base::StringPiece& public_key) { - return api_key->ValidateSignature(public_key); - } - - const base::StringPiece& public_key() { return public_key_; }; - - private: - base::StringPiece public_key_; -}; - -TEST_F(ApiKeyTest, ParseEmptyString) { - scoped_ptr<ApiKey> empty_key = ApiKey::Parse(""); - EXPECT_FALSE(empty_key); -} - -TEST_F(ApiKeyTest, ParseInvalidStrings) { - for (size_t i = 0; i < kNumInvalidAPIKeys; ++i) { - scoped_ptr<ApiKey> empty_key = ApiKey::Parse(kInvalidAPIKeys[i]); - EXPECT_FALSE(empty_key) << "Invalid API Key should not parse: " - << kInvalidAPIKeys[i]; - } -} - -TEST_F(ApiKeyTest, ParseValidKeyString) { - scoped_ptr<ApiKey> key = ApiKey::Parse(kSampleAPIKey); - ASSERT_TRUE(key); - EXPECT_EQ(kExpectedAPIName, key->api_name()); - EXPECT_EQ(kExpectedAPIKeySignature, key->signature()); - EXPECT_EQ(kExpectedAPIKeyData, key->data()); - EXPECT_EQ(GURL(kExpectedOrigin), key->origin()); - EXPECT_EQ(kExpectedExpiry, key->expiry_timestamp()); -} - -TEST_F(ApiKeyTest, ValidateValidKey) { - scoped_ptr<ApiKey> key = ApiKey::Parse(kSampleAPIKey); - ASSERT_TRUE(key); - EXPECT_TRUE(ValidateOrigin(key.get(), kExpectedOrigin)); - EXPECT_FALSE(ValidateOrigin(key.get(), kInvalidOrigin)); - EXPECT_FALSE(ValidateOrigin(key.get(), kInsecureOrigin)); - EXPECT_TRUE(ValidateApiName(key.get(), kExpectedAPIName)); - EXPECT_FALSE(ValidateApiName(key.get(), kInvalidAPIName)); - EXPECT_TRUE( - ValidateDate(key.get(), base::Time::FromDoubleT(kValidTimestamp))); - EXPECT_FALSE( - ValidateDate(key.get(), base::Time::FromDoubleT(kInvalidTimestamp))); -} - -TEST_F(ApiKeyTest, KeyIsAppropriateForOriginAndAPI) { - scoped_ptr<ApiKey> key = ApiKey::Parse(kSampleAPIKey); - ASSERT_TRUE(key); - EXPECT_TRUE(key->IsAppropriate(kExpectedOrigin, kExpectedAPIName)); - EXPECT_TRUE(key->IsAppropriate(kExpectedOrigin, - base::ToUpperASCII(kExpectedAPIName))); - EXPECT_TRUE(key->IsAppropriate(kExpectedOrigin, - base::ToLowerASCII(kExpectedAPIName))); - EXPECT_FALSE(key->IsAppropriate(kInvalidOrigin, kExpectedAPIName)); - EXPECT_FALSE(key->IsAppropriate(kInsecureOrigin, kExpectedAPIName)); - EXPECT_FALSE(key->IsAppropriate(kExpectedOrigin, kInvalidAPIName)); -} - -TEST_F(ApiKeyTest, ValidateValidSignature) { - scoped_ptr<ApiKey> key = ApiKey::Parse(kSampleAPIKey); - ASSERT_TRUE(key); - EXPECT_TRUE(ValidateSignature(key.get(), public_key())); -} - -TEST_F(ApiKeyTest, ValidateInvalidSignature) { - scoped_ptr<ApiKey> key = ApiKey::Parse(kInvalidSignatureAPIKey); - ASSERT_TRUE(key); - EXPECT_FALSE(ValidateSignature(key.get(), public_key())); -} - -TEST_F(ApiKeyTest, ValidateSignatureOnWrongKey) { - scoped_ptr<ApiKey> key = ApiKey::Parse(kSampleAPIKey); - ASSERT_TRUE(key); - // Signature will be invalid if tested against the real public key - EXPECT_FALSE(key->IsValid(base::Time::FromDoubleT(kValidTimestamp))); -} - -TEST_F(ApiKeyTest, ValidateWhenNotExpired) { - scoped_ptr<ApiKey> key = ApiKey::Parse(kSampleAPIKey); - ASSERT_TRUE(key); -} - -} // namespace content diff --git a/content/common/experiments/api_key.cc b/content/common/origin_trials/trial_token.cc index 9199d5d..5c64bcb 100644 --- a/content/common/experiments/api_key.cc +++ b/content/common/origin_trials/trial_token.cc @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "content/common/experiments/api_key.h" +#include "content/common/origin_trials/trial_token.h" #include <openssl/curve25519.h> @@ -30,31 +30,30 @@ static const uint8_t kPublicKey[] = { 0x51, 0x14, 0x66, 0xaa, 0x02, 0x53, 0x4e, 0x33, 0xa1, 0x15, }; -const char* kApiKeyFieldSeparator = "|"; +const char* kFieldSeparator = "|"; } // namespace -ApiKey::~ApiKey() {} +TrialToken::~TrialToken() {} -scoped_ptr<ApiKey> ApiKey::Parse(const std::string& key_text) { - if (key_text.empty()) { +scoped_ptr<TrialToken> TrialToken::Parse(const std::string& token_text) { + if (token_text.empty()) { return nullptr; } - // API Key should resemble: - // signature|origin|api_name|expiry_timestamp - // TODO(iclelland): Add version code to API key format to identify key algo + // A valid token should resemble: + // signature|origin|feature_name|expiry_timestamp + // TODO(iclelland): Add version code to token format to identify key algo // https://crbug.com/570684 - std::vector<std::string> parts = - SplitString(key_text, kApiKeyFieldSeparator, base::KEEP_WHITESPACE, - base::SPLIT_WANT_ALL); + std::vector<std::string> parts = SplitString( + token_text, kFieldSeparator, base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL); if (parts.size() != 4) { return nullptr; } const std::string& signature = parts[0]; const std::string& origin_string = parts[1]; - const std::string& api_name = parts[2]; + const std::string& feature_name = parts[2]; const std::string& expiry_string = parts[3]; uint64_t expiry_timestamp; @@ -68,30 +67,30 @@ scoped_ptr<ApiKey> ApiKey::Parse(const std::string& key_text) { return nullptr; } - // signed data is (origin + "|" + api_name + "|" + expiry). - std::string data = key_text.substr(signature.length() + 1); + // signed data is (origin + "|" + feature_name + "|" + expiry). + std::string data = token_text.substr(signature.length() + 1); - return make_scoped_ptr( - new ApiKey(signature, data, origin_url, api_name, expiry_timestamp)); + return make_scoped_ptr(new TrialToken(signature, data, origin_url, + feature_name, expiry_timestamp)); } -ApiKey::ApiKey(const std::string& signature, - const std::string& data, - const GURL& origin, - const std::string& api_name, - uint64_t expiry_timestamp) +TrialToken::TrialToken(const std::string& signature, + const std::string& data, + const GURL& origin, + const std::string& feature_name, + uint64_t expiry_timestamp) : signature_(signature), data_(data), origin_(origin), - api_name_(api_name), + feature_name_(feature_name), expiry_timestamp_(expiry_timestamp) {} -bool ApiKey::IsAppropriate(const std::string& origin, - const std::string& api_name) const { - return ValidateOrigin(origin) && ValidateApiName(api_name); +bool TrialToken::IsAppropriate(const std::string& origin, + const std::string& feature_name) const { + return ValidateOrigin(origin) && ValidateFeatureName(feature_name); } -bool ApiKey::IsValid(const base::Time& now) const { +bool TrialToken::IsValid(const base::Time& now) const { // TODO(iclelland): Allow for multiple signing keys, and iterate over all // active keys here. https://crbug.com/543220 return ValidateDate(now) && @@ -99,27 +98,27 @@ bool ApiKey::IsValid(const base::Time& now) const { reinterpret_cast<const char*>(kPublicKey), arraysize(kPublicKey))); } -bool ApiKey::ValidateOrigin(const std::string& origin) const { +bool TrialToken::ValidateOrigin(const std::string& origin) const { return GURL(origin) == origin_; } -bool ApiKey::ValidateApiName(const std::string& api_name) const { - return base::EqualsCaseInsensitiveASCII(api_name, api_name_); +bool TrialToken::ValidateFeatureName(const std::string& feature_name) const { + return base::EqualsCaseInsensitiveASCII(feature_name, feature_name_); } -bool ApiKey::ValidateDate(const base::Time& now) const { +bool TrialToken::ValidateDate(const base::Time& now) const { base::Time expiry_time = base::Time::FromDoubleT((double)expiry_timestamp_); return expiry_time > now; } -bool ApiKey::ValidateSignature(const base::StringPiece& public_key) const { +bool TrialToken::ValidateSignature(const base::StringPiece& public_key) const { return ValidateSignature(signature_, data_, public_key); } // static -bool ApiKey::ValidateSignature(const std::string& signature_text, - const std::string& data, - const base::StringPiece& public_key) { +bool TrialToken::ValidateSignature(const std::string& signature_text, + const std::string& data, + const base::StringPiece& public_key) { // Public key must be 32 bytes long for Ed25519. CHECK_EQ(public_key.length(), 32UL); diff --git a/content/common/origin_trials/trial_token.h b/content/common/origin_trials/trial_token.h new file mode 100644 index 0000000..bdb964b --- /dev/null +++ b/content/common/origin_trials/trial_token.h @@ -0,0 +1,96 @@ +// Copyright 2015 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 CONTENT_COMMON_ORIGIN_TRIALS_TRIAL_TOKEN_H_ +#define CONTENT_COMMON_ORIGIN_TRIALS_TRIAL_TOKEN_H_ + +#include <string> + +#include "base/memory/scoped_ptr.h" +#include "base/strings/string_piece.h" +#include "base/time/time.h" +#include "content/common/content_export.h" +#include "url/gurl.h" + +namespace content { + +// The Experimental Framework (EF) provides limited access to experimental +// features, on a per-origin basis (origin trials). This class defines the trial +// token data structure, used to securely provide access to an experimental +// feature. +// +// Features are defined by string names, provided by the implementers. The EF +// code does not maintain an enum or constant list for feature names. Instead, +// the EF validates the name provided by the feature implementation against any +// provided tokens. +// +// More documentation on the token format can be found at +// https://docs.google.com/document/d/1v5fi0EUV_QHckVHVF2K4P72iNywnrJtNhNZ6i2NPt0M + +class CONTENT_EXPORT TrialToken { + public: + ~TrialToken(); + + // Returns a token object if the string represents a well-formed token, or + // nullptr otherwise. (This does not mean that the token is valid, just that + // it can be parsed.) + static scoped_ptr<TrialToken> Parse(const std::string& token_text); + + // Returns true if this feature is appropriate for use by the given origin, + // for the given feature name. This does not check whether the signature is + // valid, or whether the token itself has expired. + bool IsAppropriate(const std::string& origin, + const std::string& featureName) const; + + // Returns true if this token has a valid signature, and has not expired. + bool IsValid(const base::Time& now) const; + + std::string signature() { return signature_; } + std::string data() { return data_; } + GURL origin() { return origin_; } + std::string feature_name() { return feature_name_; } + uint64_t expiry_timestamp() { return expiry_timestamp_; } + + protected: + friend class TrialTokenTest; + + bool ValidateOrigin(const std::string& origin) const; + bool ValidateFeatureName(const std::string& feature_name) const; + bool ValidateDate(const base::Time& now) const; + bool ValidateSignature(const base::StringPiece& public_key) const; + + static bool ValidateSignature(const std::string& signature_text, + const std::string& data, + const base::StringPiece& public_key); + + private: + TrialToken(const std::string& signature, + const std::string& data, + const GURL& origin, + const std::string& feature_name, + uint64_t expiry_timestamp); + + // The base64-encoded-signature portion of the token. For the token to be + // valid, this must be a valid signature for the data portion of the token, as + // verified by the public key in trial_token.cc. + std::string signature_; + + // The portion of the token string which is signed, and whose signature is + // contained in the signature_ member. + std::string data_; + + // The origin for which this token is valid. Must be a secure origin. + GURL origin_; + + // The name of the experimental feature which this token enables. + std::string feature_name_; + + // The time until which this token should be considered valid, in UTC, as + // seconds since the Unix epoch. + uint64_t expiry_timestamp_; +}; + +} // namespace content + +#endif // CONTENT_COMMON_ORIGIN_TRIALS_TRIAL_TOKEN_H_ diff --git a/content/common/origin_trials/trial_token_unittest.cc b/content/common/origin_trials/trial_token_unittest.cc new file mode 100644 index 0000000..c4426fd --- /dev/null +++ b/content/common/origin_trials/trial_token_unittest.cc @@ -0,0 +1,192 @@ +// Copyright 2015 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 "content/common/origin_trials/trial_token.h" + +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" +#include "base/strings/string_util.h" +#include "base/test/simple_test_clock.h" +#include "base/time/time.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace content { + +namespace { + +// 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, +}; + +// This is a good trial token, signed with the above test private key. +const char* kSampleToken = + "UsEO0cNxoUtBnHDJdGPWTlXuLENjXcEIPL7Bs7sbvicPCcvAtyqhQuTJ9h/u1R3VZpWigtI+S" + "dUwk7Dyk/qbDw==|https://valid.example.com|Frobulate|1458766277"; +const char* kExpectedSignature = + "UsEO0cNxoUtBnHDJdGPWTlXuLENjXcEIPL7Bs7sbvicPCcvAtyqhQuTJ9h/u1R3VZpWigtI+S" + "dUwk7Dyk/qbDw=="; +const char* kExpectedData = "https://valid.example.com|Frobulate|1458766277"; +const char* kExpectedFeatureName = "Frobulate"; +const char* kExpectedOrigin = "https://valid.example.com"; +const uint64_t kExpectedExpiry = 1458766277; + +// The token should not be valid for this origin, or for this feature. +const char* kInvalidOrigin = "https://invalid.example.com"; +const char* kInsecureOrigin = "http://valid.example.com"; +const char* kInvalidFeatureName = "Grokalyze"; + +// The token should be valid if the current time is kValidTimestamp or earlier. +double kValidTimestamp = 1458766276.0; + +// The token should be invalid if the current time is kInvalidTimestamp or +// later. +double kInvalidTimestamp = 1458766278.0; + +// Well-formed trial token with an invalid signature. +const char* kInvalidSignatureToken = + "CO8hDne98QeFeOJ0DbRZCBN3uE0nyaPgaLlkYhSWnbRoDfEAg+TXELaYfQPfEvKYFauBg/hnx" + "mba765hz0mXMc==|https://valid.example.com|Frobulate|1458766277"; + +// Various ill-formed trial tokens. These should all fail to parse. +const char* kInvalidTokens[] = { + // Invalid - only one part + "abcde", + // Not enough parts + "https://valid.example.com|FeatuerName|1458766277", + // Delimiter in feature name + "Signature|https://valid.example.com|Feature|Name|1458766277", + // Extra string field + "Signature|https://valid.example.com|FeatureName|1458766277|SomethingElse", + // Extra numeric field + "Signature|https://valid.example.com|FeatureName|1458766277|1458766277", + // Non-numeric expiry timestamp + "Signature|https://valid.example.com|FeatureName|abcdefghij", + "Signature|https://valid.example.com|FeatureName|1458766277x", + // Negative expiry timestamp + "Signature|https://valid.example.com|FeatureName|-1458766277", + // Origin not a proper origin URL + "Signature|abcdef|FeatureName|1458766277", + "Signature|data:text/plain,abcdef|FeatureName|1458766277", + "Signature|javascript:alert(1)|FeatureName|1458766277"}; +const size_t kNumInvalidTokens = arraysize(kInvalidTokens); + +} // namespace + +class TrialTokenTest : public testing::Test { + public: + TrialTokenTest() + : public_key_( + base::StringPiece(reinterpret_cast<const char*>(kTestPublicKey), + arraysize(kTestPublicKey))) {} + + protected: + bool ValidateOrigin(TrialToken* token, const char* origin) { + return token->ValidateOrigin(origin); + } + + bool ValidateFeatureName(TrialToken* token, const char* feature_name) { + return token->ValidateFeatureName(feature_name); + } + + bool ValidateDate(TrialToken* token, const base::Time& now) { + return token->ValidateDate(now); + } + + bool ValidateSignature(TrialToken* token, + const base::StringPiece& public_key) { + return token->ValidateSignature(public_key); + } + + const base::StringPiece& public_key() { return public_key_; }; + + private: + base::StringPiece public_key_; +}; + +TEST_F(TrialTokenTest, ParseEmptyString) { + scoped_ptr<TrialToken> empty_token = TrialToken::Parse(""); + EXPECT_FALSE(empty_token); +} + +TEST_F(TrialTokenTest, ParseInvalidStrings) { + for (size_t i = 0; i < kNumInvalidTokens; ++i) { + scoped_ptr<TrialToken> empty_token = TrialToken::Parse(kInvalidTokens[i]); + EXPECT_FALSE(empty_token) << "Invalid trial token should not parse: " + << kInvalidTokens[i]; + } +} + +TEST_F(TrialTokenTest, ParseValidToken) { + scoped_ptr<TrialToken> token = TrialToken::Parse(kSampleToken); + ASSERT_TRUE(token); + EXPECT_EQ(kExpectedFeatureName, token->feature_name()); + EXPECT_EQ(kExpectedSignature, token->signature()); + EXPECT_EQ(kExpectedData, token->data()); + EXPECT_EQ(GURL(kExpectedOrigin), token->origin()); + EXPECT_EQ(kExpectedExpiry, token->expiry_timestamp()); +} + +TEST_F(TrialTokenTest, ValidateValidToken) { + scoped_ptr<TrialToken> token = TrialToken::Parse(kSampleToken); + ASSERT_TRUE(token); + EXPECT_TRUE(ValidateOrigin(token.get(), kExpectedOrigin)); + EXPECT_FALSE(ValidateOrigin(token.get(), kInvalidOrigin)); + EXPECT_FALSE(ValidateOrigin(token.get(), kInsecureOrigin)); + EXPECT_TRUE(ValidateFeatureName(token.get(), kExpectedFeatureName)); + EXPECT_FALSE(ValidateFeatureName(token.get(), kInvalidFeatureName)); + EXPECT_TRUE( + ValidateDate(token.get(), base::Time::FromDoubleT(kValidTimestamp))); + EXPECT_FALSE( + ValidateDate(token.get(), base::Time::FromDoubleT(kInvalidTimestamp))); +} + +TEST_F(TrialTokenTest, TokenIsAppropriateForOriginAndFeature) { + scoped_ptr<TrialToken> token = TrialToken::Parse(kSampleToken); + ASSERT_TRUE(token); + EXPECT_TRUE(token->IsAppropriate(kExpectedOrigin, kExpectedFeatureName)); + EXPECT_TRUE(token->IsAppropriate(kExpectedOrigin, + base::ToUpperASCII(kExpectedFeatureName))); + EXPECT_TRUE(token->IsAppropriate(kExpectedOrigin, + base::ToLowerASCII(kExpectedFeatureName))); + EXPECT_FALSE(token->IsAppropriate(kInvalidOrigin, kExpectedFeatureName)); + EXPECT_FALSE(token->IsAppropriate(kInsecureOrigin, kExpectedFeatureName)); + EXPECT_FALSE(token->IsAppropriate(kExpectedOrigin, kInvalidFeatureName)); +} + +TEST_F(TrialTokenTest, ValidateValidSignature) { + scoped_ptr<TrialToken> token = TrialToken::Parse(kSampleToken); + ASSERT_TRUE(token); + EXPECT_TRUE(ValidateSignature(token.get(), public_key())); +} + +TEST_F(TrialTokenTest, ValidateInvalidSignature) { + scoped_ptr<TrialToken> token = TrialToken::Parse(kInvalidSignatureToken); + ASSERT_TRUE(token); + EXPECT_FALSE(ValidateSignature(token.get(), public_key())); +} + +TEST_F(TrialTokenTest, ValidateSignatureOnWrongKey) { + scoped_ptr<TrialToken> token = TrialToken::Parse(kSampleToken); + ASSERT_TRUE(token); + // Signature will be invalid if tested against the real public key + EXPECT_FALSE(token->IsValid(base::Time::FromDoubleT(kValidTimestamp))); +} + +TEST_F(TrialTokenTest, ValidateWhenNotExpired) { + scoped_ptr<TrialToken> token = TrialToken::Parse(kSampleToken); + ASSERT_TRUE(token); +} + +} // namespace content diff --git a/content/content_child.gypi b/content/content_child.gypi index 62d84ec..4718a75 100644 --- a/content/content_child.gypi +++ b/content/content_child.gypi @@ -86,8 +86,6 @@ 'child/dwrite_font_proxy/dwrite_font_proxy_win.h', 'child/dwrite_font_proxy/dwrite_localized_strings_win.cc', 'child/dwrite_font_proxy/dwrite_localized_strings_win.h', - 'child/experiments/api_key_validator.cc', - 'child/experiments/api_key_validator.h', 'child/file_info_util.cc', 'child/file_info_util.h', 'child/fileapi/file_system_dispatcher.cc', @@ -175,6 +173,8 @@ 'child/npapi/webplugin_ime_win.cc', 'child/npapi/webplugin_ime_win.h', 'child/npapi/webplugin_resource_client.h', + 'child/origin_trials/trial_token_validator.cc', + 'child/origin_trials/trial_token_validator.h', 'child/permissions/permission_dispatcher.cc', 'child/permissions/permission_dispatcher.h', 'child/permissions/permission_dispatcher_thread_proxy.cc', diff --git a/content/content_common.gypi b/content/content_common.gypi index 2330466..0c04506 100644 --- a/content/content_common.gypi +++ b/content/content_common.gypi @@ -257,8 +257,6 @@ 'common/dwrite_font_platform_win.cc', 'common/dwrite_font_proxy_messages.h', 'common/edit_command.h', - 'common/experiments/api_key.cc', - 'common/experiments/api_key.h', 'common/file_utilities_messages.h', 'common/fileapi/file_system_messages.h', 'common/fileapi/webblob_messages.h', @@ -469,6 +467,8 @@ 'common/notification_constants.h', 'common/one_writer_seqlock.cc', 'common/one_writer_seqlock.h', + 'common/origin_trials/trial_token.cc', + 'common/origin_trials/trial_token.h', 'common/origin_util.cc', 'common/p2p_messages.h', 'common/page_state_serialization.cc', diff --git a/content/content_tests.gypi b/content/content_tests.gypi index 848231a..8cf840a 100644 --- a/content/content_tests.gypi +++ b/content/content_tests.gypi @@ -658,7 +658,6 @@ 'common/discardable_shared_memory_heap_unittest.cc', 'common/dom_storage/dom_storage_map_unittest.cc', 'common/dwrite_font_platform_win_unittest.cc', - 'common/experiments/api_key_unittest.cc', 'common/fileapi/file_system_util_unittest.cc', 'common/gpu/client/gpu_memory_buffer_impl_shared_memory_unittest.cc', 'common/gpu/gpu_channel_manager_unittest.cc', @@ -678,6 +677,7 @@ 'common/mac/attributed_string_coder_unittest.mm', 'common/mac/font_descriptor_unittest.mm', 'common/one_writer_seqlock_unittest.cc', + 'common/origin_trials/trial_token_unittest.cc', 'common/origin_util_unittest.cc', 'common/page_state_serialization_unittest.cc', 'common/page_zoom_unittest.cc', |