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

Side by Side Diff: chrome/browser/internal_auth.cc

Issue 6683060: Private API for extensions like ssh-client that need access to TCP. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: m Created 9 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 | Annotate | Revision Log
OLDNEW
(Empty)
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "chrome/browser/internal_auth.h"
6
7 #include <algorithm>
8 #include <deque>
9
10 #include "base/crypto/symmetric_key.h"
11 #include "base/base64.h"
12 #include "base/hmac.h"
13 #include "base/lazy_instance.h"
14 #include "base/synchronization/lock.h"
15 #include "base/string_number_conversions.h"
16 #include "base/string_split.h"
17 #include "base/string_util.h"
18 #include "base/threading/thread_checker.h"
19 #include "base/time.h"
20 #include "base/timer.h"
21 #include "base/values.h"
22 #include "content/browser/browser_thread.h"
23
24 namespace {
25
26 typedef std::map<std::string, std::string> VarValueMap;
27
28 // Size of a tick in microseconds. This determines upper bound for average
29 // number of tokens generated per time unit. This bound equals to
30 // (kMicrosecondsPerSecond / TickUs) calls per second.
31 const int64 kTickUs = 10000;
32
33 // Verification window size in ticks; that means any token expires in
34 // (kVerificationWindowTicks * TickUs / kMicrosecondsPerSecond) seconds.
35 const int kVerificationWindowTicks = 2000;
36
37 // Generation window determines how well we are able to cope with bursts of
38 // GenerateToken calls those exceed upper bound on average speed.
39 const int kGenerationWindowTicks = 20;
40
41 // Makes no sense to compare other way round.
42 COMPILE_ASSERT(kGenerationWindowTicks <= kVerificationWindowTicks,
43 makes_no_sense_to_have_generation_window_larger_than_verification_one);
44 // We are not optimized for high value of kGenerationWindowTicks.
45 COMPILE_ASSERT(kGenerationWindowTicks < 30, too_large_generation_window);
46
47 // Regenerate key after this number of ticks.
48 const int kKeyRegenerationSoftTicks = 500000;
49 // Reject tokens if key has not been regenerated in that number of ticks.
50 const int kKeyRegenerationHardTicks = kKeyRegenerationSoftTicks * 2;
51
52 // Limit for number of accepted var=value pairs. Feel free to bump this limit
53 // higher once needed.
54 const size_t kVarsLimit = 16;
55
56 // Limit for length of caller-supplied strings. Feel free to bump this limit
57 // higher once needed.
58 const size_t kStringLengthLimit = 512;
59
60 // Character used as a separator for construction of message to take HMAC of.
61 // It is critical to validate all caller-supplied data (used to construct
62 // message) to be clear of this separator because it could allow attacks.
63 const char kItemSeparator = '\n';
64
65 // Character used for var=value separation.
66 const char kVarValueSeparator = '=';
67
68 const size_t kKeySizeInBytes = 128 / 8;
69 const int kHMACSizeInBytes = 256 / 8;
70
71 // Length of base64 string required to encode given number of raw octets.
72 #define BASE64_PER_RAW(X) (X > 0 ? ((X - 1) / 3 + 1) * 4 : 0)
73
74 // Size of decimal string representing 64-bit tick.
75 const size_t kTickStringLength = 20;
76
77 // A token consists of 2 parts: HMAC and tick.
78 const size_t kTokenSize = BASE64_PER_RAW(kHMACSizeInBytes) + kTickStringLength;
79
80 int64 GetCurrentTick() {
81 int64 tick = base::Time::Now().ToInternalValue() / kTickUs;
82 if (tick < kVerificationWindowTicks ||
83 tick < kKeyRegenerationHardTicks ||
84 tick > kint64max - kKeyRegenerationHardTicks) {
85 return 0;
86 }
87 return tick;
88 }
89
90 bool IsDomainSane(const std::string& domain) {
91 return !domain.empty() &&
92 domain.size() <= kStringLengthLimit &&
93 IsStringUTF8(domain) &&
94 domain.find_first_of(kItemSeparator) == std::string::npos;
95 }
96
97 bool IsVarSane(const std::string& var) {
98 static const char kAllowedChars[] =
99 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
100 "abcdefghijklmnopqrstuvwxyz"
101 "0123456789"
102 "_";
103 COMPILE_ASSERT(
104 sizeof(kAllowedChars) == 26 + 26 + 10 + 1 + 1, some_mess_with_chars);
105 // We must not allow kItemSeparator in anything used as an input to construct
106 // message to sign.
107 DCHECK(std::find(kAllowedChars, kAllowedChars + arraysize(kAllowedChars),
108 kItemSeparator) == kAllowedChars + arraysize(kAllowedChars));
109 DCHECK(std::find(kAllowedChars, kAllowedChars + arraysize(kAllowedChars),
110 kVarValueSeparator) == kAllowedChars + arraysize(kAllowedChars));
111 return !var.empty() &&
112 var.size() <= kStringLengthLimit &&
113 IsStringASCII(var) &&
114 var.find_first_not_of(kAllowedChars) == std::string::npos &&
115 !IsAsciiDigit(var[0]);
116 }
117
118 bool IsValueSane(const std::string& value) {
119 return value.size() <= kStringLengthLimit &&
120 IsStringUTF8(value) &&
121 value.find_first_of(kItemSeparator) == std::string::npos;
122 }
123
124 bool IsVarValueMapSane(const VarValueMap& map) {
125 if (map.size() > kVarsLimit)
126 return false;
127 for (VarValueMap::const_iterator it = map.begin(); it != map.end(); ++it) {
128 const std::string& var = it->first;
129 const std::string& value = it->second;
130 if (!IsVarSane(var) || !IsValueSane(value))
131 return false;
132 }
133 return true;
134 }
135
136 class VarValuePair {
137 public:
138 VarValuePair(const std::string* var, const std::string* value)
139 : var_(var),
140 value_(value) {
141 DCHECK(IsVarSane(*var));
142 DCHECK(IsValueSane(*value));
143 }
144
145 bool operator<(const VarValuePair& rhs) const {
146 DCHECK(this == &rhs || *var_ != *rhs.var_);
147 // Compare strings in canonical way disregarding any locale setting.
148 return std::lexicographical_compare(
149 var_->c_str(), var_->c_str() + var_->size(),
150 rhs.var_->c_str(), rhs.var_->c_str() + rhs.var_->size());
151 }
152
153 bool operator==(const VarValuePair& rhs) const {
154 DCHECK(this == &rhs || *var_ != *rhs.var_);
155 return *var_ == *rhs.var_;
156 }
157
158 const std::string& var() const { return *var_; }
159 const std::string& value() const { return *value_; }
160
161 private:
162 const std::string* var_;
163 const std::string* value_;
164 };
165
166 void ConvertVarValueMapToBlob(const VarValueMap& map, std::string* out) {
167 out->clear();
168 DCHECK(IsVarValueMapSane(map));
169 std::vector<VarValuePair> var_values;
170 for (VarValueMap::const_iterator it = map.begin(); it != map.end(); ++it)
171 var_values.push_back(VarValuePair(&it->first, &it->second));
172 std::sort(var_values.begin(), var_values.end());
173 for (size_t i = 0; i < var_values.size(); ++i) {
174 *out += var_values[i].var() + kVarValueSeparator;
175 *out += var_values[i].value() + kItemSeparator;
176 }
177 }
178
179 void CreateToken(
180 const std::string& domain,
181 const VarValueMap& map,
182 int64 tick,
183 const base::HMAC* engine,
184 std::string* out) {
185 DCHECK(engine);
186 DCHECK(out);
187 DCHECK(IsDomainSane(domain));
188 DCHECK(IsVarValueMapSane(map));
189
190 out->clear();
191 std::string result(kTokenSize, '0');
192
193 std::string blob;
194 blob = domain + kItemSeparator;
195 std::string tmp;
196 ConvertVarValueMapToBlob(map, &tmp);
197 blob += tmp + kItemSeparator + base::Uint64ToString(tick);
198
199 std::string hmac;
200 unsigned char* hmac_data = reinterpret_cast<unsigned char*>(
201 WriteInto(&hmac, kHMACSizeInBytes + 1));
202 if (!engine->Sign(blob, hmac_data, kHMACSizeInBytes)) {
203 NOTREACHED();
204 return;
205 }
206 std::string hmac_base64;
207 if (!base::Base64Encode(hmac, &hmac_base64)) {
208 NOTREACHED();
209 return;
210 }
211 if (hmac_base64.size() != BASE64_PER_RAW(kHMACSizeInBytes)) {
212 NOTREACHED();
213 return;
214 }
215 DCHECK(hmac_base64.size() < result.size());
216 std::copy(hmac_base64.begin(), hmac_base64.end(), result.begin());
217
218 std::string tick_decimal = base::Uint64ToString(tick);
219 DCHECK(tick_decimal.size() <= kTickStringLength);
220 std::copy(
221 tick_decimal.begin(),
222 tick_decimal.end(),
223 result.begin() + kTokenSize - tick_decimal.size());
224
225 out->swap(result);
226 }
227
228 } // namespace
229
230 namespace browser {
231
232 class InternalAuthVerificationService {
233 public:
234 InternalAuthVerificationService()
235 : key_change_tick_(0),
236 dark_tick_(0) {
237 }
238
239 bool VerifyToken(
240 const std::string& token,
241 const std::string& domain,
242 const VarValueMap& map) {
243 int64 current_tick = GetCurrentTick();
244 int64 tick = PreVerifyToken(token, domain, current_tick);
245 if (tick == 0)
246 return false;
247 if (!IsVarValueMapSane(map))
248 return false;
249 std::string reference_token;
250 CreateToken(domain, map, tick, engine_.get(), &reference_token);
251 if (token != reference_token) {
252 // Consider old key.
253 if (key_change_tick_ + get_verification_window_ticks() < tick) {
254 return false;
255 }
256 if (old_key_.empty() || old_engine_ == NULL)
257 return false;
258 CreateToken(domain, map, tick, old_engine_.get(), &reference_token);
259 if (token != reference_token)
260 return false;
261 }
262
263 // Record used tick to prevent reuse.
264 std::deque<int64>::iterator it = std::lower_bound(
265 used_ticks_.begin(), used_ticks_.end(), tick);
266 DCHECK(it == used_ticks_.end() || *it != tick);
267 used_ticks_.insert(it, tick);
268
269 // Consider pruning |used_ticks_|.
270 if (used_ticks_.size() > 50) {
271 dark_tick_ = std::max(dark_tick_,
272 current_tick - get_verification_window_ticks());
273 used_ticks_.erase(
274 used_ticks_.begin(),
275 std::lower_bound(used_ticks_.begin(), used_ticks_.end(),
276 dark_tick_ + 1));
277 }
278 return true;
279 }
280
281 void ChangeKey(const std::string& key) {
282 old_key_.swap(key_);
283 key_.clear();
284 old_engine_.swap(engine_);
285 engine_.reset(NULL);
286
287 if (key.size() != kKeySizeInBytes)
288 return;
289 engine_.reset(new base::HMAC(base::HMAC::SHA256));
290 engine_->Init(key);
291 key_ = key;
292 key_change_tick_ = GetCurrentTick();
293 }
294
295 private:
296 static int get_verification_window_ticks() {
297 return InternalAuthVerification::get_verification_window_ticks();
298 }
299
300 // Returns tick bound to given token on success or zero on failure.
301 int64 PreVerifyToken(
302 const std::string& token,
303 const std::string& domain,
304 int64 current_tick) {
305 if (token.size() != kTokenSize ||
306 !IsStringASCII(token) ||
307 !IsDomainSane(domain) ||
308 current_tick <= dark_tick_ ||
309 current_tick > key_change_tick_ + kKeyRegenerationHardTicks ||
310 key_.empty() ||
311 engine_ == NULL) {
312 return 0;
313 }
314
315 // Token consists of 2 parts: first hmac and then tick.
316 std::string tick_decimal = token.substr(BASE64_PER_RAW(kHMACSizeInBytes));
317 DCHECK(tick_decimal.size() == kTickStringLength);
318 int64 tick = 0;
319 if (!base::StringToInt64(tick_decimal, &tick) ||
320 tick <= dark_tick_ ||
321 tick > key_change_tick_ + kKeyRegenerationHardTicks ||
322 tick < current_tick - get_verification_window_ticks() ||
323 std::binary_search(used_ticks_.begin(), used_ticks_.end(), tick)) {
324 return 0;
325 }
326 return tick;
327 }
328
329 // Current key.
330 std::string key_;
331
332 // We keep previous key in order to be able to verify tokens during
333 // regeneration time.. Keys are regenerated on a regular basis.
334 std::string old_key_;
335
336 // Corresponding HMAC engines.
337 scoped_ptr<base::HMAC> engine_;
338 scoped_ptr<base::HMAC> old_engine_;
339
340 // Tick at a time of recent key regeneration.
341 int64 key_change_tick_;
342
343 // Keeps track of ticks of successfully verified tokens to prevent their
344 // reuse. Size of this container is kept reasonably low by purging outdated
345 // ticks.
346 std::deque<int64> used_ticks_;
347
348 // Some ticks before |dark_tick_| were purged from |used_ticks_| container.
349 // That means that we must not trust any tick less than or equal to dark tick.
350 int64 dark_tick_;
351
352 DISALLOW_COPY_AND_ASSIGN(InternalAuthVerificationService);
353 };
354
355 } // namespace browser
356
357 namespace {
358
359 static base::LazyInstance<browser::InternalAuthVerificationService>
360 g_verification_service(base::LINKER_INITIALIZED);
361 static base::LazyInstance<base::Lock> g_verification_service_lock(
362 base::LINKER_INITIALIZED);
363
364 } // namespace
365
366 namespace browser {
367
368 class InternalAuthGenerationService : public base::ThreadChecker {
369 public:
370 InternalAuthGenerationService() : key_regeneration_tick_(0) {
371 GenerateNewKey();
372 }
373
374 void GenerateNewKey() {
375 DCHECK(CalledOnValidThread());
376 if (!timer_.IsRunning()) {
377 timer_.Start(
378 base::TimeDelta::FromMicroseconds(
379 kKeyRegenerationSoftTicks * kTickUs),
380 this,
381 &InternalAuthGenerationService::GenerateNewKey);
382 }
383
384 key_.clear();
385 engine_.reset(NULL);
386
387 if (!base::SymmetricKey::GenerateRandomBytes(
388 kKeySizeInBytes,
389 reinterpret_cast<uint8*>(WriteInto(&key_, kKeySizeInBytes + 1)))) {
390 NOTREACHED();
391 key_ = std::string();
392 return;
393 }
394 engine_.reset(new base::HMAC(base::HMAC::SHA256));
395 engine_->Init(key_);
396 key_regeneration_tick_ = GetCurrentTick();
397 g_verification_service.Get().ChangeKey(key_);
398 }
399
400 // Returns zero on failure.
401 int64 GetUnusedTick(const std::string& domain) {
402 DCHECK(CalledOnValidThread());
403 if (key_.empty() || engine_ == NULL) {
404 NOTREACHED();
405 return 0;
406 }
407 if (!IsDomainSane(domain))
408 return 0;
409
410 int64 current_tick = GetCurrentTick();
411 if (!used_ticks_.empty() && used_ticks_.back() > current_tick)
412 current_tick = used_ticks_.back();
413 if (current_tick > key_regeneration_tick_ + kKeyRegenerationHardTicks)
414 return 0;
415
416 // Forget outdated ticks if any.
417 used_ticks_.erase(
418 used_ticks_.begin(),
419 std::lower_bound(used_ticks_.begin(), used_ticks_.end(),
420 current_tick - kGenerationWindowTicks + 1));
421 DCHECK(used_ticks_.size() <= kGenerationWindowTicks + 0u);
422 if (used_ticks_.size() >= kGenerationWindowTicks + 0u) {
423 // Average speed of GenerateToken calls exceeds limit.
424 return 0;
425 }
426 for (int64 tick = current_tick;
427 tick > current_tick - kGenerationWindowTicks;
428 --tick) {
429 int idx = static_cast<int>(used_ticks_.size()) -
430 static_cast<int>(current_tick - tick + 1);
431 if (idx < 0 || used_ticks_[idx] != tick) {
432 DCHECK(used_ticks_.end() ==
433 std::find(used_ticks_.begin(), used_ticks_.end(), tick));
434 return tick;
435 }
436 }
437 NOTREACHED();
438 return 0;
439 }
440
441 std::string GenerateToken(
442 const std::string& domain,
443 const VarValueMap& map,
444 int64 tick) {
445 DCHECK(CalledOnValidThread());
446 if (tick == 0) {
447 tick = GetUnusedTick(domain);
448 if (tick == 0)
449 return std::string();
450 }
451 if (!IsVarValueMapSane(map))
452 return std::string();
453
454 std::string result;
455 CreateToken(domain, map, tick, engine_.get(), &result);
456 used_ticks_.insert(
457 std::lower_bound(used_ticks_.begin(), used_ticks_.end(), tick), tick);
458 return result;
459 }
460
461 private:
462 static int get_verification_window_ticks() {
463 return InternalAuthVerification::get_verification_window_ticks();
464 }
465
466 std::string key_;
467 scoped_ptr<base::HMAC> engine_;
468 base::RepeatingTimer<InternalAuthGenerationService> timer_;
469 int64 key_regeneration_tick_;
470 std::deque<int64> used_ticks_;
471
472 DISALLOW_COPY_AND_ASSIGN(InternalAuthGenerationService);
473 };
474
475 } // namespace browser
476
477 namespace {
478
479 static base::LazyInstance<browser::InternalAuthGenerationService>
480 g_generation_service(base::LINKER_INITIALIZED);
481
482 } // namespace
483
484 namespace browser {
485
486 // static
487 bool InternalAuthVerification::VerifyToken(
488 const std::string& token,
489 const std::string& domain,
490 const VarValueMap& var_value_map) {
491 base::AutoLock alk(g_verification_service_lock.Get());
492 return g_verification_service.Get().VerifyToken(token, domain, var_value_map);
493 }
494
495 // static
496 void InternalAuthVerification::ChangeKey(const std::string& key) {
497 base::AutoLock alk(g_verification_service_lock.Get());
498 g_verification_service.Get().ChangeKey(key);
499 };
500
501 // static
502 int InternalAuthVerification::get_verification_window_ticks() {
503 int candidate = kVerificationWindowTicks;
504 if (verification_window_seconds_ > 0)
505 candidate = verification_window_seconds_ *
506 base::Time::kMicrosecondsPerSecond / kTickUs;
507 return std::max(1, std::min(candidate, kVerificationWindowTicks));
508 }
509
510 int InternalAuthVerification::verification_window_seconds_ = 0;
511
512 // static
513 std::string InternalAuthGeneration::GenerateToken(
514 const std::string& domain,
515 const VarValueMap& var_value_map) {
516 return g_generation_service.Get().GenerateToken(domain, var_value_map, 0);
517 }
518
519 // static
520 void InternalAuthGeneration::GenerateNewKey() {
521 g_generation_service.Get().GenerateNewKey();
522 }
523
524 } // namespace browser
525
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698