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

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: sigh-nedness Created 9 years, 9 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/time.h"
19 #include "base/timer.h"
20 #include "base/values.h"
21 #include "content/browser/browser_thread.h"
22
23 namespace {
24
25 typedef std::map<std::string, std::string> VarValueMap;
26
27 // Size of a tick in microseconds. This determines upper bound for average
28 // number of tokens generated per time unit. This bound equals to
29 // (base::Time::kMicrosecondsPerSecond / TickUs) calls per second.
30 const int64 kTickUs = 10000;
31
32 // Verification window size in ticks; that means any token expires in
33 // (kVerificationWindowSizeTicks * TickUs / kMicrosecondsPerSecond) seconds.
34 const int kVerificationWindowSizeTicks = 2000;
35
36 // Generation window determines how well we are able to cope with bursts of
37 // GenerateToken calls those exceed upper bound on average speed.
38 const int kGenerationWindowSizeTicks = 20;
39
40 // Makes no sense to compare other way round.
41 COMPILE_ASSERT(kGenerationWindowSizeTicks <= kVerificationWindowSizeTicks,
42 makes_no_sense_to_have_generation_window_larger_than_verification_one);
43 // We are not optimized for high value of kGenerationWindowSizeTicks.
44 COMPILE_ASSERT(kGenerationWindowSizeTicks < 30, too_large_generation_window);
45
46 // Regenerate key after this number of ticks.
47 const int kKeyRegenerationSoftTicks = 500000;
48 // Reject tokens if key has not been regenerated in that number of ticks.
49 const int kKeyRegenerationHardTicks = kKeyRegenerationSoftTicks * 2;
50
51 // Limit for number of accepted var=value pairs. Feel free to bump this limit
52 // higher once needed.
53 const size_t kVarsLimit = 16;
54
55 // Limit for length of caller-supplied strings. Feel free to bump this limit
56 // higher once needed.
57 const size_t kStringLengthLimit = 512;
58
59 // Character used as a separator for construction of message to take HMAC of.
60 const char kItemSeparator = '\n';
61
62 // Character used for var=value separation.
63 const char kVarValueSeparator = '=';
64
65 // Regular expression pattern imposed on variables.
66 const char kVarPattern[] = "^[a-zA-Z_]*$";
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 - 1) / 3 * 4)
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 // TODO(dilmah): move this into "base/string_split.h".
81 bool SplitStringIntoKeyValue(
82 const std::string& line,
83 char key_value_delimiter,
84 std::string* key, std::string* value) {
85 key->clear();
86 value->clear();
87
88 size_t end_key_pos = line.find_first_of(key_value_delimiter);
89 if (end_key_pos == std::string::npos)
90 return false; // no key
91 if (end_key_pos == 0)
92 return false; // empty key
93 key->assign(line, 0, end_key_pos);
94
95 // Find the value string. Empty value is OK.
96 value->assign(line, end_key_pos + 1, std::string::npos);
97 return true;
98 }
99
100 int64 GetCurrentTick() {
101 int64 tick = base::Time::Now().ToInternalValue() / kTickUs;
102 if (tick < kVerificationWindowSizeTicks ||
103 tick < kKeyRegenerationHardTicks ||
104 tick > kint64max - kKeyRegenerationHardTicks) {
105 return 0;
106 }
107 return tick;
108 }
109
110 bool IsVarValueMapSane(const VarValueMap& map) {
111 if (map.size() > kVarsLimit)
112 return false;
113 for (VarValueMap::const_iterator it = map.begin(); it != map.end(); ++it) {
114 const std::string& var = it->first;
115 const std::string& value = it->second;
116 if (var.size() > kStringLengthLimit ||
117 !IsStringASCII(var) ||
118 var.find_first_of(kItemSeparator) != std::string::npos ||
119 !MatchPattern(var, kVarPattern) ||
120 value.size() > kStringLengthLimit ||
121 !IsStringUTF8(value) ||
122 value.find_first_of(kItemSeparator) != std::string::npos) {
123 return false;
124 }
125 }
126 return true;
127 }
128
129 // Convert arguments list into var=value map. Returns true on success.
130 bool ConvertArgListToVarValueMap(
131 const std::vector<std::string>& arg_list, VarValueMap* out) {
132 out->clear();
133 VarValueMap result;
134
135 if (arg_list.size() > kVarsLimit)
136 return false;
137 for (size_t i = 0; i < arg_list.size(); ++i) {
138 if (arg_list[i].size() > kStringLengthLimit)
139 return false;
140 if (!IsStringUTF8(arg_list[i]))
141 return false;
142 if (arg_list[i].find_first_of(kItemSeparator) != std::string::npos)
143 return false;
144
145 std::string var;
146 std::string value;
147 if (!SplitStringIntoKeyValue(
148 arg_list[i], kVarValueSeparator, &var, &value) ||
149 var.empty() ||
150 !MatchPattern(var, "^[a-zA-Z_]*$")) {
151 return false;
152 }
153 if (result.find(var) != result.end())
154 return false;
155 result[var] = value;
156 }
157 DCHECK(IsVarValueMapSane(result));
158 out->swap(result);
159 return true;
160 }
161
162 class VarValuePair {
163 public:
164 VarValuePair(const std::string& var, const std::string& value)
165 : var_(&var),
166 value_(&value) {
167 DCHECK(!var.empty());
168 DCHECK(var.size() <= kStringLengthLimit);
169 DCHECK(value.size() <= kStringLengthLimit);
170 DCHECK(IsStringASCII(var));
171 DCHECK(IsStringUTF8(value));
172 DCHECK(MatchPattern(var, "^[a-zA-Z_]*$"));
173 DCHECK(var.find_first_of(kItemSeparator) == std::string::npos);
174 DCHECK(value.find_first_of(kItemSeparator) == std::string::npos);
175 }
176
177 bool operator<(const VarValuePair& rhs) const {
178 DCHECK(this == &rhs || *var_ != *rhs.var_);
179 // Compare strings in deterministic way disregarding any locale setting.
180 return std::lexicographical_compare(
181 var_->c_str(), var_->c_str() + var_->size(),
182 rhs.var_->c_str(), rhs.var_->c_str() + rhs.var_->size());
183 }
184
185 bool operator==(const VarValuePair& rhs) const {
186 DCHECK(this == &rhs || *var_ != *rhs.var_);
187 return *var_ == *rhs.var_;
188 }
189
190 std::string var() const { return *var_; }
191 std::string value() const { return *value_; }
192
193 private:
194 const std::string* var_;
195 const std::string* value_;
196 };
197
198 void ConvertVarValueMapToBlob(const VarValueMap& map, std::string* out) {
199 out->clear();
200 DCHECK(IsVarValueMapSane(map));
201 std::vector<VarValuePair> var_values;
202 for (VarValueMap::const_iterator it = map.begin(); it != map.end(); ++it)
203 var_values.push_back(VarValuePair(it->first, it->second));
204 std::sort(var_values.begin(), var_values.end());
205 for (size_t i = 0; i < var_values.size(); ++i) {
206 *out += var_values[i].var() + kVarValueSeparator;
207 *out += var_values[i].value() + kItemSeparator;
208 }
209 }
210
211 bool IsHolderIdSane(const std::string& holder_id) {
212 return !holder_id.empty() &&
213 holder_id.size() <= kStringLengthLimit &&
214 IsStringUTF8(holder_id) &&
215 holder_id.find_first_of(kItemSeparator) == std::string::npos;
216 }
217
218 void CreateToken(
219 const std::string& holder_id,
220 const VarValueMap& map,
221 int64 tick,
222 const base::HMAC* engine,
223 std::string* out) {
224 DCHECK(engine);
225 DCHECK(out);
226 DCHECK(IsHolderIdSane(holder_id));
227 DCHECK(IsVarValueMapSane(map));
228
229 out->clear();
230 std::string result(kTokenSize, '0');
231
232 std::string blob;
233 blob = holder_id + kItemSeparator;
234 std::string tmp;
235 ConvertVarValueMapToBlob(map, &tmp);
236 blob += tmp + kItemSeparator + base::Uint64ToString(tick);
237
238 std::string hmac;
239 unsigned char* hmac_data = reinterpret_cast<unsigned char*>(
240 WriteInto(&hmac, kHMACSizeInBytes + 1));
241 if (!engine->Sign(blob, hmac_data, kHMACSizeInBytes)) {
242 NOTREACHED();
243 return;
244 }
245 std::string hmac_base64;
246 if (!base::Base64Encode(hmac, &hmac_base64)) {
247 NOTREACHED();
248 return;
249 }
250 if (hmac_base64.size() != BASE64_PER_RAW(kHMACSizeInBytes)) {
251 NOTREACHED();
252 return;
253 }
254 std::copy(hmac_base64.begin(), hmac_base64.end(), result.begin());
255
256 std::string tick_decimal = base::Uint64ToString(tick);
257 DCHECK(tick_decimal.size() <= kTickStringLength);
258 std::copy(
259 tick_decimal.begin(),
260 tick_decimal.end(),
261 hmac_base64.begin() + kTokenSize - tick_decimal.size());
262
263 out->swap(result);
264 }
265
266 class InternalAuthVerificationService {
267 public:
268 InternalAuthVerificationService()
269 : key_change_tick_(0),
270 dark_tick_(0) {
271 }
272
273 bool VerifyToken(
274 const std::string& token,
275 const std::string& holder_id,
276 const std::vector<std::string>& arg_list) {
277 int64 current_tick = GetCurrentTick();
278 int64 tick = PreVerifyToken(token, holder_id, current_tick);
279 if (tick == 0)
280 return false;
281
282 VarValueMap map;
283 if (!ConvertArgListToVarValueMap(arg_list, &map))
284 return false;
285 DCHECK(IsVarValueMapSane(map));
286 return VerifyToken(token, holder_id, map, true);
287 }
288
289 bool VerifyToken(
290 const std::string& token,
291 const std::string& holder_id,
292 const VarValueMap& map,
293 bool map_known_sane) {
294 int64 current_tick = GetCurrentTick();
295 int64 tick = PreVerifyToken(token, holder_id, current_tick);
296 if (tick == 0)
297 return false;
298 if (map_known_sane) {
299 DCHECK(IsVarValueMapSane(map));
300 } else {
301 if (!IsVarValueMapSane(map))
302 return false;
303 }
304 std::string reference_token;
305 CreateToken(holder_id, map, tick, engine_.get(), &reference_token);
306 if (token != reference_token) {
307 // Consider old key.
308 if (key_change_tick_ + kVerificationWindowSizeTicks < tick)
309 return false;
310 if (old_key_.empty() || old_engine_ == NULL)
311 return false;
312 CreateToken(holder_id, map, tick, old_engine_.get(), &reference_token);
313 if (token != reference_token)
314 return false;
315 }
316
317 // Record used tick to prevent reuse.
318 std::deque<int64>::deque::iterator it = std::lower_bound(
319 used_ticks_.begin(), used_ticks_.end(), tick);
320 DCHECK(it == used_ticks_.end() || *it != tick);
321 used_ticks_.insert(it, tick);
322
323 // Consider pruning |used_ticks_|.
324 if (used_ticks_.size() > 50) {
325 dark_tick_ = std::max(
326 dark_tick_, current_tick - kVerificationWindowSizeTicks);
327 std::deque<int64>::deque::iterator it = std::lower_bound(
328 used_ticks_.begin(), used_ticks_.end(), dark_tick_ + 1);
329 if (it != used_ticks_.end())
330 used_ticks_.erase(used_ticks_.begin(), ++it);
331 }
332 return true;
333 }
334
335 void ChangeKey(const std::string& key) {
336 old_key_.swap(key_);
337 key_.clear();
338 old_engine_.swap(engine_);
339 engine_.reset(NULL);
340
341 if (key.size() != kKeySizeInBytes)
342 return;
343 engine_.reset(new base::HMAC(base::HMAC::SHA256));
344 engine_->Init(key);
345 key_ = key;
346 key_change_tick_ = GetCurrentTick();
347 }
348
349 private:
350 // Returns tick bound to given token on success or zero on failure.
351 int64 PreVerifyToken(
352 const std::string& token,
353 const std::string& holder_id,
354 int64 current_tick) {
355 if (token.size() != kTokenSize ||
356 !IsStringASCII(token) ||
357 !IsHolderIdSane(holder_id) ||
358 current_tick <= dark_tick_ ||
359 current_tick > key_change_tick_ + kKeyRegenerationHardTicks ||
360 key_.empty() ||
361 engine_ == NULL) {
362 return 0;
363 }
364
365 // Token consists of 2 parts: first hmac and then tick.
366 std::string tick_decimal = token.substr(BASE64_PER_RAW(kHMACSizeInBytes));
367 DCHECK(tick_decimal.size() == kTickStringLength);
368 int64 tick = 0;
369 if (!base::StringToInt64(tick_decimal, &tick) ||
370 tick <= dark_tick_ ||
371 tick > key_change_tick_ + kKeyRegenerationHardTicks ||
372 std::binary_search(used_ticks_.begin(), used_ticks_.end(), tick)) {
373 return 0;
374 }
375 return tick;
376 }
377
378 // Current key.
379 std::string key_;
380
381 // We keep previous key in order to be able to verify tokens during
382 // regeneration time.. Keys are regenerated on a regular basis.
383 std::string old_key_;
384
385 // Corresponding HMAC engines.
386 scoped_ptr<base::HMAC> engine_;
387 scoped_ptr<base::HMAC> old_engine_;
388
389 // Tick at a time of recent key regeneration.
390 int64 key_change_tick_;
391
392 // States that we forget information about some ticks used before this tick.
393 // Implies that we must not trust any tick less than or equal to dark tick.
394 int64 dark_tick_;
395
396 std::deque<int64> used_ticks_;
397
398 DISALLOW_COPY_AND_ASSIGN(InternalAuthVerificationService);
399 };
400
401 static base::LazyInstance<InternalAuthVerificationService>
402 g_verification_service(base::LINKER_INITIALIZED);
403 static base::LazyInstance<base::Lock> g_verification_service_lock(
404 base::LINKER_INITIALIZED);
405
406 class InternalAuthGenerationService {
407 public:
408 InternalAuthGenerationService() : key_regeneration_tick_(0) {
409 GenerateNewKey();
410 }
411
412 void GenerateNewKey() {
413 if (!timer_.IsRunning()) {
414 timer_.Start(
415 base::TimeDelta::FromMicroseconds(
416 kKeyRegenerationSoftTicks * kTickUs),
417 this,
418 &InternalAuthGenerationService::GenerateNewKey);
419 }
420
421 key_.clear();
422 engine_.reset(NULL);
423
424 unsigned char* key_data = reinterpret_cast<unsigned char*>(
425 WriteInto(&key_, kKeySizeInBytes + 1));
426 if (!base::SymmetricKey::GenerateRandomBytes(kKeySizeInBytes, key_data)) {
427 NOTREACHED();
428 key_ = std::string();
429 return;
430 }
431 engine_.reset(new base::HMAC(base::HMAC::SHA256));
432 engine_->Init(key_);
433 key_regeneration_tick_ = GetCurrentTick();
434 g_verification_service.Get().ChangeKey(key_);
435 }
436
437 // Returns zero on failure.
438 int64 GetUnusedTick(const std::string& holder_id) {
439 if (!timer_.IsRunning()) {
440 NOTREACHED();
441 return 0;
442 }
443 if (key_.empty() || engine_ == NULL) {
444 NOTREACHED();
445 return 0;
446 }
447 if (!IsHolderIdSane(holder_id))
448 return 0;
449
450 int64 current_tick = GetCurrentTick();
451 if (!used_ticks_.empty() && used_ticks_.back() > current_tick)
452 current_tick = used_ticks_.back();
453 if (current_tick > key_regeneration_tick_ + kKeyRegenerationHardTicks)
454 return 0;
455
456 std::deque<int64>::iterator it = std::lower_bound(
457 used_ticks_.begin(), used_ticks_.end(),
458 current_tick - kGenerationWindowSizeTicks + 1);
459 if (it != used_ticks_.end()) {
460 // Forget outdated ticks.
461 used_ticks_.erase(used_ticks_.begin(), ++it);
462 }
463 DCHECK(used_ticks_.size() <= kGenerationWindowSizeTicks + 0u);
464 if (used_ticks_.size() >= kGenerationWindowSizeTicks + 0u) {
465 // Average speed of GenerateToken calls exceeds limit.
466 return 0;
467 }
468 for (int64 tick = current_tick;
469 tick > current_tick - kGenerationWindowSizeTicks;
470 --tick) {
471 int idx = used_ticks_.size() - (current_tick - tick + 1);
472 if (idx < 0 || used_ticks_[idx] != tick) {
473 DCHECK(used_ticks_.end() ==
474 std::find(used_ticks_.begin(), used_ticks_.end(), tick));
475 return tick;
476 }
477 }
478 NOTREACHED();
479 return 0;
480 }
481
482 std::string GenerateToken(
483 const std::string holder_id,
484 const VarValueMap& map,
485 int64 tick,
486 bool map_known_sane) {
487 if (tick == 0) {
488 tick = GetUnusedTick(holder_id);
489 if (tick == 0)
490 return std::string();
491 }
492 if (map_known_sane) {
493 DCHECK(IsVarValueMapSane(map));
494 } else {
495 if (!IsVarValueMapSane(map))
496 return std::string();
497 }
498
499 std::string result;
500 CreateToken(holder_id, map, tick, engine_.get(), &result);
501 used_ticks_.insert(
502 std::lower_bound(used_ticks_.begin(), used_ticks_.end(), tick), tick);
503 return result;
504 }
505
506 std::string GenerateToken(
507 const std::string holder_id,
508 const std::vector<std::string>& arg_list) {
509 int64 tick = GetUnusedTick(holder_id);
510 if (tick == 0)
511 return std::string();
512
513 VarValueMap map;
514 if (!ConvertArgListToVarValueMap(arg_list, &map))
515 return false;
516 return GenerateToken(holder_id, map, tick, true);
517 }
518
519 private:
520 std::string key_;
521 scoped_ptr<base::HMAC> engine_;
522 base::RepeatingTimer<InternalAuthGenerationService> timer_;
523 int64 key_regeneration_tick_;
524 std::deque<int64> used_ticks_;
525
526 DISALLOW_COPY_AND_ASSIGN(InternalAuthGenerationService);
527 };
528
529 static base::LazyInstance<InternalAuthGenerationService> g_generation_service(
530 base::LINKER_INITIALIZED);
531
532 } // namespace
533
534 namespace browser {
535
536 // static
537 bool InternalAuthVerification::VerifyToken(
538 const std::string& token,
539 const std::string& holder_id,
540 const std::vector<std::string>& arg_list) {
541 base::AutoLock alk(g_verification_service_lock.Get());
542 return g_verification_service.Get().VerifyToken(token, holder_id, arg_list);
543 }
544
545 // static
546 bool InternalAuthVerification::VerifyToken(
547 const std::string& token,
548 const std::string& holder_id,
549 const VarValueMap& var_value_map) {
550 base::AutoLock alk(g_verification_service_lock.Get());
551 return g_verification_service.Get().VerifyToken(
552 token, holder_id, var_value_map, false);
553 }
554
555 // static
556 void InternalAuthVerification::ChangeKey(const std::string& key) {
557 base::AutoLock alk(g_verification_service_lock.Get());
558 g_verification_service.Get().ChangeKey(key);
559 };
560
561 // static
562 std::string InternalAuthGeneration::GenerateToken(
563 const std::string& holder_id,
564 const std::vector<std::string>& arg_list) {
565 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
566 return g_generation_service.Get().GenerateToken(holder_id, arg_list);
567 }
568
569 // static
570 std::string InternalAuthGeneration::GenerateToken(
571 const std::string& holder_id,
572 const VarValueMap& var_value_map) {
573 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
574 return g_generation_service.Get().GenerateToken(
575 holder_id, var_value_map, 0, false);
576 }
577
578 } // namespace browser
579
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698