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

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

Powered by Google App Engine
This is Rietveld 408576698