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

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 parser unit tests, improve error detection 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_number_conversions.h"
14 #include "base/strings/string_split.h" 15 #include "base/strings/string_split.h"
15 #include "base/strings/string_util.h" 16 #include "base/strings/string_util.h"
16 #include "base/strings/utf_string_conversions.h" 17 #include "base/strings/utf_string_conversions.h"
17 #include "base/time/time.h" 18 #include "base/time/time.h"
19 #include "base/values.h"
18 #include "url/gurl.h" 20 #include "url/gurl.h"
19 #include "url/origin.h" 21 #include "url/origin.h"
20 22
21 namespace content { 23 namespace content {
22 24
23 namespace { 25 namespace {
24 26
25 // Version 1 is the only token version currently supported 27 // Version 1 is the only token version currently supported
26 const uint8_t kVersion1 = 1; 28 const uint8_t kVersion1 = 1;
27 29
28 const char* kFieldSeparator = "|"; 30 // Version is a 1-byte field
31 const size_t kVersionBytesLength = 1;
32
33 // Version 1 field sizes and offsets
34 const size_t kSignatureBytesLength = 512 >> 3; // get from curve25519 instead?
35
36 const size_t kVersionOffset = 0;
chasej 2016/04/07 15:52:30 Nit: The version offset is independent of version.
iclelland 2016/04/07 20:26:43 Done.
37 const size_t kSignatureOffset = kVersionOffset + kVersionBytesLength;
38 const size_t kPayloadLengthOffset = kSignatureOffset + kSignatureBytesLength;
39 const size_t kPayloadOffset = kPayloadLengthOffset + 4;
chasej 2016/04/07 15:52:30 Should/add use a constant for kPayloadLengthBytesL
iclelland 2016/04/07 20:26:43 Yeah, if we have a named constant for 0, then why
29 40
30 } // namespace 41 } // namespace
31 42
32 TrialToken::~TrialToken() {} 43 TrialToken::~TrialToken() {}
33 44
34 scoped_ptr<TrialToken> TrialToken::Parse(const std::string& token_text) { 45 // Static
46 scoped_ptr<TrialToken> TrialToken::From(const std::string& token_text,
47 base::StringPiece public_key) {
48 scoped_ptr<std::string> token_json = Extract(token_text, public_key);
49 if (!token_json) {
50 return nullptr;
51 }
52 return Parse(*token_json);
53 }
54
55 scoped_ptr<std::string> TrialToken::Extract(const std::string& token_text,
56 base::StringPiece public_key) {
35 if (token_text.empty()) { 57 if (token_text.empty()) {
36 return nullptr; 58 return nullptr;
37 } 59 }
38 60
39 // Extract the version from the token. The version must be the first part of 61 // Token is base64-encoded; decode first.
40 // the token, separated from the remainder, as: 62 std::string token_contents;
41 // version|<version-specific contents> 63 if (!base::Base64Decode(token_text, &token_contents)) {
42 size_t version_end = token_text.find(kFieldSeparator);
43 if (version_end == std::string::npos) {
44 return nullptr; 64 return nullptr;
45 } 65 }
46 66
47 std::string version_string = token_text.substr(0, version_end); 67 // Token must be large enough to contain a version, signature, and payload
48 unsigned int version = 0; 68 // length.
49 if (!base::StringToUint(version_string, &version) || version > UINT8_MAX) { 69 if (token_contents.length() < kPayloadOffset) {
chasej 2016/04/07 15:52:30 Nit, but this is really a version-specific check,
iclelland 2016/04/07 20:26:43 Good catch. Done.
50 return nullptr; 70 return nullptr;
51 } 71 }
52 72
53 // Only version 1 currently supported 73 // Only version 1 currently supported.
74 uint8_t version = token_contents[0];
chasej 2016/04/07 15:52:30 Should use the kVersionOffset constant here, not h
iclelland 2016/04/07 20:26:43 Done.
54 if (version != kVersion1) { 75 if (version != kVersion1) {
55 return nullptr; 76 return nullptr;
56 } 77 }
57 78
58 // Extract the version-specific contents of the token 79 // Extract the length of the signed data (Big-endian).
59 std::string token_contents = token_text.substr(version_end + 1); 80 uint32_t payload_length =
81 ((uint8_t)token_contents[kPayloadLengthOffset] << 24) +
82 ((uint8_t)token_contents[kPayloadLengthOffset + 1] << 16) +
83 ((uint8_t)token_contents[kPayloadLengthOffset + 2] << 8) +
84 ((uint8_t)token_contents[kPayloadLengthOffset + 3]);
60 85
61 // The contents of a valid version 1 token should resemble: 86 // Validate that the stated length matches the actual payload length.
62 // signature|origin|feature_name|expiry_timestamp 87 if (payload_length != token_contents.length() - kPayloadOffset) {
63 std::vector<std::string> parts =
64 SplitString(token_contents, kFieldSeparator, base::KEEP_WHITESPACE,
65 base::SPLIT_WANT_ALL);
66 if (parts.size() != 4) {
67 return nullptr; 88 return nullptr;
68 } 89 }
69 90
70 const std::string& signature = parts[0]; 91 // Extract the version-specific contents of the token
71 const std::string& origin_string = parts[1]; 92 std::string version_string =
72 const std::string& feature_name = parts[2]; 93 token_contents.substr(kVersionOffset, kVersionBytesLength);
chasej 2016/04/07 15:52:30 Nit: Again, this part is not version-specific.
iclelland 2016/04/07 20:26:43 It kind of is, in that we only *need* the version
73 const std::string& expiry_string = parts[3]; 94 std::string signature =
95 token_contents.substr(kSignatureOffset, kSignatureBytesLength);
96 std::string payload_length_string =
97 token_contents.substr(kPayloadLengthOffset, 4);
98 std::string payload = token_contents.substr(kPayloadOffset, payload_length);
74 99
75 uint64_t expiry_timestamp; 100 // The data which is covered by the signature is (version + length + payload)
76 if (!base::StringToUint64(expiry_string, &expiry_timestamp)) { 101 std::string signed_data = version_string + payload_length_string + payload;
chasej 2016/04/07 15:52:30 Could/should the substrings use StringPiece to avo
iclelland 2016/04/07 20:26:43 Done; we change them back to strings afterwards fo
102
103 // Validate the signature on the data before proceeding
104 if (!TrialToken::ValidateSignature(signature, signed_data, public_key)) {
77 return nullptr; 105 return nullptr;
78 } 106 }
79 107
108 // Return just the payload, as a new string.
109 return make_scoped_ptr(new std::string(payload));
110 }
111
112 scoped_ptr<TrialToken> TrialToken::Parse(const std::string& token_json) {
113 // signed_data is JSON-encoded
114 scoped_ptr<base::DictionaryValue> datadict =
115 base::DictionaryValue::From(base::JSONReader::Read(token_json));
116 if (!datadict) {
117 return nullptr;
118 }
119
120 std::string origin_string;
121 std::string feature_name;
122 int expiry_timestamp = 0;
123 datadict->GetString("origin", &origin_string);
124 datadict->GetString("feature", &feature_name);
125 datadict->GetInteger("expiry", &expiry_timestamp);
126
80 // Ensure that the origin is a valid (non-unique) origin URL 127 // Ensure that the origin is a valid (non-unique) origin URL
81 url::Origin origin = url::Origin(GURL(origin_string)); 128 url::Origin origin = url::Origin(GURL(origin_string));
82 if (origin.unique()) { 129 if (origin.unique()) {
83 return nullptr; 130 return nullptr;
84 } 131 }
85 132
86 // Signed data is (origin + "|" + feature_name + "|" + expiry). 133 // Ensure that the feature name is a valid string
87 std::string data = token_contents.substr(signature.length() + 1); 134 if (feature_name.empty()) {
135 return nullptr;
136 }
88 137
89 return make_scoped_ptr(new TrialToken(version, signature, data, origin, 138 // Ensure that the expiry timestamp is a valid (positive) integer
90 feature_name, expiry_timestamp)); 139 if (expiry_timestamp <= 0) {
140 return nullptr;
141 }
142
143 return make_scoped_ptr(
144 new TrialToken(origin, feature_name, expiry_timestamp));
91 } 145 }
92 146
93 bool TrialToken::IsAppropriate(const url::Origin& origin, 147 bool TrialToken::IsAppropriate(const url::Origin& origin,
94 base::StringPiece feature_name) const { 148 base::StringPiece feature_name) const {
95 return ValidateOrigin(origin) && ValidateFeatureName(feature_name); 149 return ValidateOrigin(origin) && ValidateFeatureName(feature_name);
96 } 150 }
97 151
98 bool TrialToken::IsValid(const base::Time& now, 152 bool TrialToken::IsValid(const base::Time& now) const {
99 base::StringPiece public_key) const { 153 return ValidateDate(now);
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(const std::string& 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