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

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: Bump version number 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/json/json_reader.h"
12 #include "base/macros.h" 13 #include "base/macros.h"
13 #include "base/strings/string_number_conversions.h" 14 #include "base/strings/string_piece.h"
14 #include "base/strings/string_split.h"
15 #include "base/strings/string_util.h"
16 #include "base/strings/utf_string_conversions.h"
17 #include "base/time/time.h" 15 #include "base/time/time.h"
16 #include "base/values.h"
18 #include "url/gurl.h" 17 #include "url/gurl.h"
19 #include "url/origin.h" 18 #include "url/origin.h"
20 19
21 namespace content { 20 namespace content {
22 21
23 namespace { 22 namespace {
24 23
25 // Version 1 is the only token version currently supported 24 // Version is a 1-byte field at offset 0
Avi (use Gerrit) 2016/04/08 21:34:24 Make comments full sentences and end them with a f
iclelland 2016/04/11 16:59:37 Done.
26 const uint8_t kVersion1 = 1; 25 const size_t kVersionOffset = 0;
26 const size_t kVersionSize = 1;
27 27
28 const char* kFieldSeparator = "|"; 28 // Version 2 is the only token version currently supported. Version 1 was
Marijn Kruisselbrink 2016/04/08 21:19:30 nit: Would it make sense to keep the offsets and s
iclelland 2016/04/11 16:59:37 Done.
29 // introduced in Chrome M50, and removed in M51. There were no experiments
30 // enabled in the stable M50 release which would have used those tokens.
31 const uint8_t kVersion2 = 2;
32
33 // Version 2 field sizes and offsets
Avi (use Gerrit) 2016/04/08 21:34:24 .
iclelland 2016/04/11 16:59:37 Done. And turned into a full sentence.
34 const size_t kSignatureOffset = kVersionOffset + kVersionSize;
35 const size_t kSignatureSize = 64;
36 const size_t kPayloadLengthOffset = kSignatureOffset + kSignatureSize;
37 const size_t kPayloadLengthSize = 4;
38 const size_t kPayloadOffset = kPayloadLengthOffset + kPayloadLengthSize;
29 39
30 } // namespace 40 } // namespace
31 41
32 TrialToken::~TrialToken() {} 42 TrialToken::~TrialToken() {}
33 43
34 scoped_ptr<TrialToken> TrialToken::Parse(const std::string& token_text) { 44 // Static
35 if (token_text.empty()) { 45 scoped_ptr<TrialToken> TrialToken::From(const std::string& token_text,
46 base::StringPiece public_key) {
47 scoped_ptr<std::string> token_payload = Extract(token_text, public_key);
48 if (!token_payload) {
49 return nullptr;
50 }
51 return Parse(*token_payload);
52 }
53
54 bool TrialToken::IsValidForFeature(const url::Origin& origin,
55 base::StringPiece feature_name,
56 const base::Time& now) const {
57 return ValidateOrigin(origin) && ValidateFeatureName(feature_name) &&
58 ValidateDate(now);
59 }
60
61 scoped_ptr<std::string> TrialToken::Extract(const std::string& token_payload,
62 base::StringPiece public_key) {
63 if (token_payload.empty()) {
36 return nullptr; 64 return nullptr;
37 } 65 }
38 66
39 // Extract the version from the token. The version must be the first part of 67 // Token is base64-encoded; decode first.
40 // the token, separated from the remainder, as: 68 std::string token_contents;
41 // version|<version-specific contents> 69 if (!base::Base64Decode(token_payload, &token_contents)) {
42 size_t version_end = token_text.find(kFieldSeparator);
43 if (version_end == std::string::npos) {
44 return nullptr; 70 return nullptr;
45 } 71 }
46 72
47 std::string version_string = token_text.substr(0, version_end); 73 // Only version 2 currently supported.
48 unsigned int version = 0; 74 if (token_contents.length() < (kVersionOffset + kVersionSize)) {
49 if (!base::StringToUint(version_string, &version) || version > UINT8_MAX) { 75 return nullptr;
76 }
77 uint8_t version = token_contents[kVersionOffset];
78 if (version != kVersion2) {
50 return nullptr; 79 return nullptr;
51 } 80 }
52 81
53 // Only version 1 currently supported 82 // Token must be large enough to contain a version, signature, and payload
54 if (version != kVersion1) { 83 // length.
84 if (token_contents.length() < (kPayloadLengthOffset + kPayloadLengthSize)) {
85 return nullptr;
86 }
87
88 // Extract the length of the signed data (Big-endian).
Marijn Kruisselbrink 2016/04/08 21:19:30 Can you use something like base::BigEndianReader t
iclelland 2016/04/11 16:59:37 Yes! Thanks, I was looking around for just the fun
89 uint32_t payload_length =
90 ((uint8_t)token_contents[kPayloadLengthOffset] << 24) +
Marijn Kruisselbrink 2016/04/08 21:19:30 C-style casts are against the style guide. Either
iclelland 2016/04/11 16:59:37 Acknowledged. Thanks, switched to ReadBigEndian to
91 ((uint8_t)token_contents[kPayloadLengthOffset + 1] << 16) +
92 ((uint8_t)token_contents[kPayloadLengthOffset + 2] << 8) +
93 ((uint8_t)token_contents[kPayloadLengthOffset + 3]);
94
95 // Validate that the stated length matches the actual payload length.
96 if (payload_length != token_contents.length() - kPayloadOffset) {
55 return nullptr; 97 return nullptr;
56 } 98 }
57 99
58 // Extract the version-specific contents of the token 100 // Extract the version-specific contents of the token
Avi (use Gerrit) 2016/04/08 21:34:24 .
iclelland 2016/04/11 16:59:37 Done.
59 std::string token_contents = token_text.substr(version_end + 1); 101 const char* token_bytes = token_contents.data();
102 base::StringPiece version_piece(token_bytes + kVersionOffset, kVersionSize);
103 base::StringPiece signature(token_bytes + kSignatureOffset, kSignatureSize);
104 base::StringPiece payload_piece(token_bytes + kPayloadLengthOffset,
Marijn Kruisselbrink 2016/04/08 21:19:30 nit: I was a bit confused on first reading that "p
iclelland 2016/04/11 16:59:37 I was trying to avoid extra string allocations dur
105 kPayloadLengthSize + payload_length);
60 106
61 // The contents of a valid version 1 token should resemble: 107 // The data which is covered by the signature is (version + length + payload)
Avi (use Gerrit) 2016/04/08 21:34:24 .
iclelland 2016/04/11 16:59:37 Done.
62 // signature|origin|feature_name|expiry_timestamp 108 std::string signed_data =
63 std::vector<std::string> parts = 109 version_piece.as_string() + payload_piece.as_string();
64 SplitString(token_contents, kFieldSeparator, base::KEEP_WHITESPACE, 110
65 base::SPLIT_WANT_ALL); 111 // Validate the signature on the data before proceeding
Avi (use Gerrit) 2016/04/08 21:34:23 .
iclelland 2016/04/11 16:59:37 Done.
66 if (parts.size() != 4) { 112 if (!TrialToken::ValidateSignature(signature, signed_data, public_key)) {
67 return nullptr; 113 return nullptr;
68 } 114 }
69 115
70 const std::string& signature = parts[0]; 116 // Return just the payload, as a new string.
71 const std::string& origin_string = parts[1]; 117 return make_scoped_ptr(
72 const std::string& feature_name = parts[2]; 118 new std::string(token_contents, kPayloadOffset, payload_length));
73 const std::string& expiry_string = parts[3]; 119 }
74 120
75 uint64_t expiry_timestamp; 121 scoped_ptr<TrialToken> TrialToken::Parse(const std::string& token_json) {
76 if (!base::StringToUint64(expiry_string, &expiry_timestamp)) { 122 // signed_data is JSON-encoded
Avi (use Gerrit) 2016/04/08 21:34:24 . etc
iclelland 2016/04/11 16:59:37 Done.
123 scoped_ptr<base::DictionaryValue> datadict =
124 base::DictionaryValue::From(base::JSONReader::Read(token_json));
125 if (!datadict) {
77 return nullptr; 126 return nullptr;
78 } 127 }
79 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
80 // Ensure that the origin is a valid (non-unique) origin URL 136 // Ensure that the origin is a valid (non-unique) origin URL
81 url::Origin origin = url::Origin(GURL(origin_string)); 137 url::Origin origin = url::Origin(GURL(origin_string));
82 if (origin.unique()) { 138 if (origin.unique()) {
83 return nullptr; 139 return nullptr;
84 } 140 }
85 141
86 // Signed data is (origin + "|" + feature_name + "|" + expiry). 142 // Ensure that the feature name is a valid string
87 std::string data = token_contents.substr(signature.length() + 1); 143 if (feature_name.empty()) {
144 return nullptr;
145 }
88 146
89 return make_scoped_ptr(new TrialToken(version, signature, data, origin, 147 // Ensure that the expiry timestamp is a valid (positive) integer
90 feature_name, expiry_timestamp)); 148 if (expiry_timestamp <= 0) {
91 } 149 return nullptr;
150 }
92 151
93 bool TrialToken::IsAppropriate(const url::Origin& origin, 152 return make_scoped_ptr(
94 base::StringPiece feature_name) const { 153 new TrialToken(origin, feature_name, expiry_timestamp));
95 return ValidateOrigin(origin) && ValidateFeatureName(feature_name);
96 }
97
98 bool TrialToken::IsValid(const base::Time& now,
99 base::StringPiece public_key) const {
100 // TODO(iclelland): Allow for multiple signing keys, and iterate over all
101 // active keys here. https://crbug.com/543220
102 return ValidateDate(now) && ValidateSignature(public_key);
103 } 154 }
104 155
105 bool TrialToken::ValidateOrigin(const url::Origin& origin) const { 156 bool TrialToken::ValidateOrigin(const url::Origin& origin) const {
106 return origin == origin_; 157 return origin == origin_;
107 } 158 }
108 159
109 bool TrialToken::ValidateFeatureName(base::StringPiece feature_name) const { 160 bool TrialToken::ValidateFeatureName(base::StringPiece feature_name) const {
110 return feature_name == feature_name_; 161 return feature_name == feature_name_;
111 } 162 }
112 163
113 bool TrialToken::ValidateDate(const base::Time& now) const { 164 bool TrialToken::ValidateDate(const base::Time& now) const {
114 return expiry_time_ > now; 165 return expiry_time_ > now;
115 } 166 }
116 167
117 bool TrialToken::ValidateSignature(base::StringPiece public_key) const {
118 return ValidateSignature(signature_, data_, public_key);
119 }
120
121 // static 168 // static
122 bool TrialToken::ValidateSignature(const std::string& signature_text, 169 bool TrialToken::ValidateSignature(base::StringPiece signature,
123 const std::string& data, 170 const std::string& data,
124 base::StringPiece public_key) { 171 base::StringPiece public_key) {
125 // Public key must be 32 bytes long for Ed25519. 172 // Public key must be 32 bytes long for Ed25519.
126 CHECK_EQ(public_key.length(), 32UL); 173 CHECK_EQ(public_key.length(), 32UL);
127 174
128 std::string signature;
129 // signature_text is base64-encoded; decode first.
130 if (!base::Base64Decode(signature_text, &signature)) {
131 return false;
132 }
133
134 // Signature must be 64 bytes long 175 // Signature must be 64 bytes long
135 if (signature.length() != 64) { 176 if (signature.length() != 64) {
136 return false; 177 return false;
137 } 178 }
138 179
139 int result = ED25519_verify( 180 int result = ED25519_verify(
140 reinterpret_cast<const uint8_t*>(data.data()), data.length(), 181 reinterpret_cast<const uint8_t*>(data.data()), data.length(),
141 reinterpret_cast<const uint8_t*>(signature.data()), 182 reinterpret_cast<const uint8_t*>(signature.data()),
142 reinterpret_cast<const uint8_t*>(public_key.data())); 183 reinterpret_cast<const uint8_t*>(public_key.data()));
143 return (result != 0); 184 return (result != 0);
144 } 185 }
145 186
146 TrialToken::TrialToken(uint8_t version, 187 TrialToken::TrialToken(const url::Origin& origin,
147 const std::string& signature,
148 const std::string& data,
149 const url::Origin& origin,
150 const std::string& feature_name, 188 const std::string& feature_name,
151 uint64_t expiry_timestamp) 189 uint64_t expiry_timestamp)
152 : version_(version), 190 : origin_(origin),
153 signature_(signature),
154 data_(data),
155 origin_(origin),
156 feature_name_(feature_name), 191 feature_name_(feature_name),
157 expiry_time_(base::Time::FromDoubleT(expiry_timestamp)) {} 192 expiry_time_(base::Time::FromDoubleT(expiry_timestamp)) {}
158 193
159 } // namespace content 194 } // namespace content
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698