Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(43)

Side by Side Diff: content/common/origin_trials/trial_token.cc

Issue 1858763003: Change origin trial token format (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Update test tokens for version 2 Created 4 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright 2015 The Chromium Authors. All rights reserved. 1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "content/common/origin_trials/trial_token.h" 5 #include "content/common/origin_trials/trial_token.h"
6 6
7 #include <openssl/curve25519.h> 7 #include <openssl/curve25519.h>
8 8
9 #include <vector> 9 #include <vector>
10 10
11 #include "base/base64.h" 11 #include "base/base64.h"
12 #include "base/big_endian.h"
13 #include "base/json/json_reader.h"
12 #include "base/macros.h" 14 #include "base/macros.h"
13 #include "base/memory/ptr_util.h" 15 #include "base/memory/ptr_util.h"
14 #include "base/strings/string_number_conversions.h" 16 #include "base/strings/string_piece.h"
15 #include "base/strings/string_split.h"
16 #include "base/strings/string_util.h"
17 #include "base/strings/utf_string_conversions.h"
18 #include "base/time/time.h" 17 #include "base/time/time.h"
18 #include "base/values.h"
19 #include "url/gurl.h" 19 #include "url/gurl.h"
20 #include "url/origin.h" 20 #include "url/origin.h"
21 21
22 namespace content { 22 namespace content {
23 23
24 namespace { 24 namespace {
25 25
26 // Version 1 is the only token version currently supported 26 // Version is a 1-byte field at offset 0.
27 const uint8_t kVersion1 = 1; 27 const size_t kVersionOffset = 0;
28 const size_t kVersionSize = 1;
28 29
29 const char* kFieldSeparator = "|"; 30 // These constants define the Version 2 field sizes and offsets.
31 const size_t kSignatureOffset = kVersionOffset + kVersionSize;
32 const size_t kSignatureSize = 64;
33 const size_t kPayloadLengthOffset = kSignatureOffset + kSignatureSize;
34 const size_t kPayloadLengthSize = 4;
35 const size_t kPayloadOffset = kPayloadLengthOffset + kPayloadLengthSize;
36
37 // Version 2 is the only token version currently supported. Version 1 was
38 // introduced in Chrome M50, and removed in M51. There were no experiments
39 // enabled in the stable M50 release which would have used those tokens.
40 const uint8_t kVersion2 = 2;
30 41
31 } // namespace 42 } // namespace
32 43
33 TrialToken::~TrialToken() {} 44 TrialToken::~TrialToken() {}
34 45
35 std::unique_ptr<TrialToken> TrialToken::Parse(const std::string& token_text) { 46 // Static
36 if (token_text.empty()) { 47 std::unique_ptr<TrialToken> TrialToken::From(const std::string& token_text,
48 base::StringPiece public_key) {
49 std::unique_ptr<std::string> token_payload = Extract(token_text, public_key);
50 if (!token_payload) {
51 return nullptr;
52 }
53 return Parse(*token_payload);
54 }
55
56 bool TrialToken::IsValidForFeature(const url::Origin& origin,
57 base::StringPiece feature_name,
58 const base::Time& now) const {
59 return ValidateOrigin(origin) && ValidateFeatureName(feature_name) &&
60 ValidateDate(now);
61 }
62
63 std::unique_ptr<std::string> TrialToken::Extract(
64 const std::string& token_payload,
65 base::StringPiece public_key) {
66 if (token_payload.empty()) {
37 return nullptr; 67 return nullptr;
38 } 68 }
39 69
40 // Extract the version from the token. The version must be the first part of 70 // Token is base64-encoded; decode first.
41 // the token, separated from the remainder, as: 71 std::string token_contents;
42 // version|<version-specific contents> 72 if (!base::Base64Decode(token_payload, &token_contents)) {
43 size_t version_end = token_text.find(kFieldSeparator);
44 if (version_end == std::string::npos) {
45 return nullptr; 73 return nullptr;
46 } 74 }
47 75
48 std::string version_string = token_text.substr(0, version_end); 76 // Only version 2 currently supported.
49 unsigned int version = 0; 77 if (token_contents.length() < (kVersionOffset + kVersionSize)) {
50 if (!base::StringToUint(version_string, &version) || version > UINT8_MAX) { 78 return nullptr;
79 }
80 uint8_t version = token_contents[kVersionOffset];
81 if (version != kVersion2) {
51 return nullptr; 82 return nullptr;
52 } 83 }
53 84
54 // Only version 1 currently supported 85 // Token must be large enough to contain a version, signature, and payload
55 if (version != kVersion1) { 86 // length.
87 if (token_contents.length() < (kPayloadLengthOffset + kPayloadLengthSize)) {
56 return nullptr; 88 return nullptr;
57 } 89 }
58 90
59 // Extract the version-specific contents of the token 91 // Extract the length of the signed data (Big-endian).
60 std::string token_contents = token_text.substr(version_end + 1); 92 uint32_t payload_length;
93 base::ReadBigEndian(&(token_contents[kPayloadLengthOffset]), &payload_length);
61 94
62 // The contents of a valid version 1 token should resemble: 95 // Validate that the stated length matches the actual payload length.
63 // signature|origin|feature_name|expiry_timestamp 96 if (payload_length != token_contents.length() - kPayloadOffset) {
64 std::vector<std::string> parts =
65 SplitString(token_contents, kFieldSeparator, base::KEEP_WHITESPACE,
66 base::SPLIT_WANT_ALL);
67 if (parts.size() != 4) {
68 return nullptr; 97 return nullptr;
69 } 98 }
70 99
71 const std::string& signature = parts[0]; 100 // Extract the version-specific contents of the token.
72 const std::string& origin_string = parts[1]; 101 const char* token_bytes = token_contents.data();
73 const std::string& feature_name = parts[2]; 102 base::StringPiece version_piece(token_bytes + kVersionOffset, kVersionSize);
74 const std::string& expiry_string = parts[3]; 103 base::StringPiece signature(token_bytes + kSignatureOffset, kSignatureSize);
104 base::StringPiece payload_piece(token_bytes + kPayloadLengthOffset,
105 kPayloadLengthSize + payload_length);
75 106
76 uint64_t expiry_timestamp; 107 // The data which is covered by the signature is (version + length + payload).
77 if (!base::StringToUint64(expiry_string, &expiry_timestamp)) { 108 std::string signed_data =
109 version_piece.as_string() + payload_piece.as_string();
110
111 // Validate the signature on the data before proceeding.
112 if (!TrialToken::ValidateSignature(signature, signed_data, public_key)) {
78 return nullptr; 113 return nullptr;
79 } 114 }
80 115
81 // Ensure that the origin is a valid (non-unique) origin URL 116 // Return just the payload, as a new string.
117 return base::WrapUnique(
118 new std::string(token_contents, kPayloadOffset, payload_length));
119 }
120
121 std::unique_ptr<TrialToken> TrialToken::Parse(const std::string& token_json) {
palmer 2016/04/11 20:25:57 What you call |token_json| here is called |token_p
iclelland 2016/04/12 04:09:32 Done.
122 // signed_data is JSON-encoded.
123 std::unique_ptr<base::DictionaryValue> datadict =
124 base::DictionaryValue::From(base::JSONReader::Read(token_json));
125 if (!datadict) {
126 return nullptr;
127 }
128
129 std::string origin_string;
130 std::string feature_name;
131 int expiry_timestamp = 0;
132 datadict->GetString("origin", &origin_string);
133 datadict->GetString("feature", &feature_name);
134 datadict->GetInteger("expiry", &expiry_timestamp);
135
136 // Ensure that the origin is a valid (non-unique) origin URL.
82 url::Origin origin = url::Origin(GURL(origin_string)); 137 url::Origin origin = url::Origin(GURL(origin_string));
83 if (origin.unique()) { 138 if (origin.unique()) {
84 return nullptr; 139 return nullptr;
85 } 140 }
86 141
87 // Signed data is (origin + "|" + feature_name + "|" + expiry). 142 // Ensure that the feature name is a valid string.
88 std::string data = token_contents.substr(signature.length() + 1); 143 if (feature_name.empty()) {
144 return nullptr;
145 }
89 146
90 return base::WrapUnique(new TrialToken(version, signature, data, origin, 147 // Ensure that the expiry timestamp is a valid (positive) integer.
91 feature_name, expiry_timestamp)); 148 if (expiry_timestamp <= 0) {
92 } 149 return nullptr;
150 }
93 151
94 bool TrialToken::IsAppropriate(const url::Origin& origin, 152 return base::WrapUnique(
95 base::StringPiece feature_name) const { 153 new TrialToken(origin, feature_name, expiry_timestamp));
96 return ValidateOrigin(origin) && ValidateFeatureName(feature_name);
97 }
98
99 bool TrialToken::IsValid(const base::Time& now,
100 base::StringPiece public_key) const {
101 // TODO(iclelland): Allow for multiple signing keys, and iterate over all
102 // active keys here. https://crbug.com/543220
103 return ValidateDate(now) && ValidateSignature(public_key);
104 } 154 }
105 155
106 bool TrialToken::ValidateOrigin(const url::Origin& origin) const { 156 bool TrialToken::ValidateOrigin(const url::Origin& origin) const {
107 return origin == origin_; 157 return origin == origin_;
108 } 158 }
109 159
110 bool TrialToken::ValidateFeatureName(base::StringPiece feature_name) const { 160 bool TrialToken::ValidateFeatureName(base::StringPiece feature_name) const {
111 return feature_name == feature_name_; 161 return feature_name == feature_name_;
112 } 162 }
113 163
114 bool TrialToken::ValidateDate(const base::Time& now) const { 164 bool TrialToken::ValidateDate(const base::Time& now) const {
115 return expiry_time_ > now; 165 return expiry_time_ > now;
116 } 166 }
117 167
118 bool TrialToken::ValidateSignature(base::StringPiece public_key) const {
119 return ValidateSignature(signature_, data_, public_key);
120 }
121
122 // static 168 // static
123 bool TrialToken::ValidateSignature(const std::string& signature_text, 169 bool TrialToken::ValidateSignature(base::StringPiece signature,
124 const std::string& data, 170 const std::string& data,
125 base::StringPiece public_key) { 171 base::StringPiece public_key) {
126 // Public key must be 32 bytes long for Ed25519. 172 // Public key must be 32 bytes long for Ed25519.
127 CHECK_EQ(public_key.length(), 32UL); 173 CHECK_EQ(public_key.length(), 32UL);
128 174
129 std::string signature; 175 // Signature must be 64 bytes long.
130 // signature_text is base64-encoded; decode first.
131 if (!base::Base64Decode(signature_text, &signature)) {
132 return false;
133 }
134
135 // Signature must be 64 bytes long
136 if (signature.length() != 64) { 176 if (signature.length() != 64) {
137 return false; 177 return false;
138 } 178 }
139 179
140 int result = ED25519_verify( 180 int result = ED25519_verify(
141 reinterpret_cast<const uint8_t*>(data.data()), data.length(), 181 reinterpret_cast<const uint8_t*>(data.data()), data.length(),
142 reinterpret_cast<const uint8_t*>(signature.data()), 182 reinterpret_cast<const uint8_t*>(signature.data()),
143 reinterpret_cast<const uint8_t*>(public_key.data())); 183 reinterpret_cast<const uint8_t*>(public_key.data()));
palmer 2016/04/11 20:25:57 These casts indicate to me that the StringPiece an
iclelland 2016/04/12 04:09:32 That's a really good point... I recall that this s
144 return (result != 0); 184 return (result != 0);
145 } 185 }
146 186
147 TrialToken::TrialToken(uint8_t version, 187 TrialToken::TrialToken(const url::Origin& origin,
148 const std::string& signature,
149 const std::string& data,
150 const url::Origin& origin,
151 const std::string& feature_name, 188 const std::string& feature_name,
152 uint64_t expiry_timestamp) 189 uint64_t expiry_timestamp)
153 : version_(version), 190 : origin_(origin),
154 signature_(signature),
155 data_(data),
156 origin_(origin),
157 feature_name_(feature_name), 191 feature_name_(feature_name),
158 expiry_time_(base::Time::FromDoubleT(expiry_timestamp)) {} 192 expiry_time_(base::Time::FromDoubleT(expiry_timestamp)) {}
159 193
160 } // namespace content 194 } // namespace content
OLDNEW
« no previous file with comments | « content/common/origin_trials/trial_token.h ('k') | content/common/origin_trials/trial_token_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698