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

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: removed changes to rand_util_unittest Created 9 years, 7 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
« no previous file with comments | « chrome/browser/internal_auth.h ('k') | chrome/browser/internal_auth_unittest.cc » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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/base64.h"
11 #include "base/lazy_instance.h"
12 #include "base/rand_util.h"
13 #include "base/synchronization/lock.h"
14 #include "base/string_number_conversions.h"
15 #include "base/string_split.h"
16 #include "base/string_util.h"
17 #include "base/threading/thread_checker.h"
18 #include "base/time.h"
19 #include "base/timer.h"
20 #include "base/values.h"
21 #include "content/browser/browser_thread.h"
22 #include "crypto/hmac.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 passports 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 passport 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 // GeneratePassport 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 passports 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 passport consists of 2 parts: HMAC and tick.
78 const size_t kPassportSize =
79 BASE64_PER_RAW(kHMACSizeInBytes) + kTickStringLength;
80
81 int64 GetCurrentTick() {
82 int64 tick = base::Time::Now().ToInternalValue() / kTickUs;
83 if (tick < kVerificationWindowTicks ||
84 tick < kKeyRegenerationHardTicks ||
85 tick > kint64max - kKeyRegenerationHardTicks) {
86 return 0;
87 }
88 return tick;
89 }
90
91 bool IsDomainSane(const std::string& domain) {
92 return !domain.empty() &&
93 domain.size() <= kStringLengthLimit &&
94 IsStringUTF8(domain) &&
95 domain.find_first_of(kItemSeparator) == std::string::npos;
96 }
97
98 bool IsVarSane(const std::string& var) {
99 static const char kAllowedChars[] =
100 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
101 "abcdefghijklmnopqrstuvwxyz"
102 "0123456789"
103 "_";
104 COMPILE_ASSERT(
105 sizeof(kAllowedChars) == 26 + 26 + 10 + 1 + 1, some_mess_with_chars);
106 // We must not allow kItemSeparator in anything used as an input to construct
107 // message to sign.
108 DCHECK(std::find(kAllowedChars, kAllowedChars + arraysize(kAllowedChars),
109 kItemSeparator) == kAllowedChars + arraysize(kAllowedChars));
110 DCHECK(std::find(kAllowedChars, kAllowedChars + arraysize(kAllowedChars),
111 kVarValueSeparator) == kAllowedChars + arraysize(kAllowedChars));
112 return !var.empty() &&
113 var.size() <= kStringLengthLimit &&
114 IsStringASCII(var) &&
115 var.find_first_not_of(kAllowedChars) == std::string::npos &&
116 !IsAsciiDigit(var[0]);
117 }
118
119 bool IsValueSane(const std::string& value) {
120 return value.size() <= kStringLengthLimit &&
121 IsStringUTF8(value) &&
122 value.find_first_of(kItemSeparator) == std::string::npos;
123 }
124
125 bool IsVarValueMapSane(const VarValueMap& map) {
126 if (map.size() > kVarsLimit)
127 return false;
128 for (VarValueMap::const_iterator it = map.begin(); it != map.end(); ++it) {
129 const std::string& var = it->first;
130 const std::string& value = it->second;
131 if (!IsVarSane(var) || !IsValueSane(value))
132 return false;
133 }
134 return true;
135 }
136
137 void ConvertVarValueMapToBlob(const VarValueMap& map, std::string* out) {
138 out->clear();
139 DCHECK(IsVarValueMapSane(map));
140 for (VarValueMap::const_iterator it = map.begin(); it != map.end(); ++it)
141 *out += it->first + kVarValueSeparator + it->second + kItemSeparator;
142 }
143
144 void CreatePassport(
145 const std::string& domain,
146 const VarValueMap& map,
147 int64 tick,
148 const crypto::HMAC* engine,
149 std::string* out) {
150 DCHECK(engine);
151 DCHECK(out);
152 DCHECK(IsDomainSane(domain));
153 DCHECK(IsVarValueMapSane(map));
154
155 out->clear();
156 std::string result(kPassportSize, '0');
157
158 std::string blob;
159 blob = domain + kItemSeparator;
160 std::string tmp;
161 ConvertVarValueMapToBlob(map, &tmp);
162 blob += tmp + kItemSeparator + base::Uint64ToString(tick);
163
164 std::string hmac;
165 unsigned char* hmac_data = reinterpret_cast<unsigned char*>(
166 WriteInto(&hmac, kHMACSizeInBytes + 1));
167 if (!engine->Sign(blob, hmac_data, kHMACSizeInBytes)) {
168 NOTREACHED();
169 return;
170 }
171 std::string hmac_base64;
172 if (!base::Base64Encode(hmac, &hmac_base64)) {
173 NOTREACHED();
174 return;
175 }
176 if (hmac_base64.size() != BASE64_PER_RAW(kHMACSizeInBytes)) {
177 NOTREACHED();
178 return;
179 }
180 DCHECK(hmac_base64.size() < result.size());
181 std::copy(hmac_base64.begin(), hmac_base64.end(), result.begin());
182
183 std::string tick_decimal = base::Uint64ToString(tick);
184 DCHECK(tick_decimal.size() <= kTickStringLength);
185 std::copy(
186 tick_decimal.begin(),
187 tick_decimal.end(),
188 result.begin() + kPassportSize - tick_decimal.size());
189
190 out->swap(result);
191 }
192
193 } // namespace
194
195 namespace browser {
196
197 class InternalAuthVerificationService {
198 public:
199 InternalAuthVerificationService()
200 : key_change_tick_(0),
201 dark_tick_(0) {
202 }
203
204 bool VerifyPassport(
205 const std::string& passport,
206 const std::string& domain,
207 const VarValueMap& map) {
208 int64 current_tick = GetCurrentTick();
209 int64 tick = PreVerifyPassport(passport, domain, current_tick);
210 if (tick == 0)
211 return false;
212 if (!IsVarValueMapSane(map))
213 return false;
214 std::string reference_passport;
215 CreatePassport(domain, map, tick, engine_.get(), &reference_passport);
216 if (passport != reference_passport) {
217 // Consider old key.
218 if (key_change_tick_ + get_verification_window_ticks() < tick) {
219 return false;
220 }
221 if (old_key_.empty() || old_engine_ == NULL)
222 return false;
223 CreatePassport(domain, map, tick, old_engine_.get(), &reference_passport);
224 if (passport != reference_passport)
225 return false;
226 }
227
228 // Record used tick to prevent reuse.
229 std::deque<int64>::iterator it = std::lower_bound(
230 used_ticks_.begin(), used_ticks_.end(), tick);
231 DCHECK(it == used_ticks_.end() || *it != tick);
232 used_ticks_.insert(it, tick);
233
234 // Consider pruning |used_ticks_|.
235 if (used_ticks_.size() > 50) {
236 dark_tick_ = std::max(dark_tick_,
237 current_tick - get_verification_window_ticks());
238 used_ticks_.erase(
239 used_ticks_.begin(),
240 std::lower_bound(used_ticks_.begin(), used_ticks_.end(),
241 dark_tick_ + 1));
242 }
243 return true;
244 }
245
246 void ChangeKey(const std::string& key) {
247 old_key_.swap(key_);
248 key_.clear();
249 old_engine_.swap(engine_);
250 engine_.reset(NULL);
251
252 if (key.size() != kKeySizeInBytes)
253 return;
254 engine_.reset(new crypto::HMAC(crypto::HMAC::SHA256));
255 engine_->Init(key);
256 key_ = key;
257 key_change_tick_ = GetCurrentTick();
258 }
259
260 private:
261 static int get_verification_window_ticks() {
262 return InternalAuthVerification::get_verification_window_ticks();
263 }
264
265 // Returns tick bound to given passport on success or zero on failure.
266 int64 PreVerifyPassport(
267 const std::string& passport,
268 const std::string& domain,
269 int64 current_tick) {
270 if (passport.size() != kPassportSize ||
271 !IsStringASCII(passport) ||
272 !IsDomainSane(domain) ||
273 current_tick <= dark_tick_ ||
274 current_tick > key_change_tick_ + kKeyRegenerationHardTicks ||
275 key_.empty() ||
276 engine_ == NULL) {
277 return 0;
278 }
279
280 // Passport consists of 2 parts: first hmac and then tick.
281 std::string tick_decimal =
282 passport.substr(BASE64_PER_RAW(kHMACSizeInBytes));
283 DCHECK(tick_decimal.size() == kTickStringLength);
284 int64 tick = 0;
285 if (!base::StringToInt64(tick_decimal, &tick) ||
286 tick <= dark_tick_ ||
287 tick > key_change_tick_ + kKeyRegenerationHardTicks ||
288 tick < current_tick - get_verification_window_ticks() ||
289 std::binary_search(used_ticks_.begin(), used_ticks_.end(), tick)) {
290 return 0;
291 }
292 return tick;
293 }
294
295 // Current key.
296 std::string key_;
297
298 // We keep previous key in order to be able to verify passports during
299 // regeneration time. Keys are regenerated on a regular basis.
300 std::string old_key_;
301
302 // Corresponding HMAC engines.
303 scoped_ptr<crypto::HMAC> engine_;
304 scoped_ptr<crypto::HMAC> old_engine_;
305
306 // Tick at a time of recent key regeneration.
307 int64 key_change_tick_;
308
309 // Keeps track of ticks of successfully verified passports to prevent their
310 // reuse. Size of this container is kept reasonably low by purging outdated
311 // ticks.
312 std::deque<int64> used_ticks_;
313
314 // Some ticks before |dark_tick_| were purged from |used_ticks_| container.
315 // That means that we must not trust any tick less than or equal to dark tick.
316 int64 dark_tick_;
317
318 DISALLOW_COPY_AND_ASSIGN(InternalAuthVerificationService);
319 };
320
321 } // namespace browser
322
323 namespace {
324
325 static base::LazyInstance<browser::InternalAuthVerificationService>
326 g_verification_service(base::LINKER_INITIALIZED);
327 static base::LazyInstance<base::Lock> g_verification_service_lock(
328 base::LINKER_INITIALIZED);
329
330 } // namespace
331
332 namespace browser {
333
334 class InternalAuthGenerationService : public base::ThreadChecker {
335 public:
336 InternalAuthGenerationService() : key_regeneration_tick_(0) {
337 GenerateNewKey();
338 }
339
340 void GenerateNewKey() {
341 DCHECK(CalledOnValidThread());
342 if (!timer_.IsRunning()) {
343 timer_.Start(
344 base::TimeDelta::FromMicroseconds(
345 kKeyRegenerationSoftTicks * kTickUs),
346 this,
347 &InternalAuthGenerationService::GenerateNewKey);
348 }
349
350 engine_.reset(new crypto::HMAC(crypto::HMAC::SHA256));
351 std::string key = base::RandBytesAsString(kKeySizeInBytes);
352 engine_->Init(key);
353 key_regeneration_tick_ = GetCurrentTick();
354 g_verification_service.Get().ChangeKey(key);
355 std::fill(key.begin(), key.end(), 0);
356 }
357
358 // Returns zero on failure.
359 int64 GetUnusedTick(const std::string& domain) {
360 DCHECK(CalledOnValidThread());
361 if (engine_ == NULL) {
362 NOTREACHED();
363 return 0;
364 }
365 if (!IsDomainSane(domain))
366 return 0;
367
368 int64 current_tick = GetCurrentTick();
369 if (!used_ticks_.empty() && used_ticks_.back() > current_tick)
370 current_tick = used_ticks_.back();
371 if (current_tick > key_regeneration_tick_ + kKeyRegenerationHardTicks)
372 return 0;
373
374 // Forget outdated ticks if any.
375 used_ticks_.erase(
376 used_ticks_.begin(),
377 std::lower_bound(used_ticks_.begin(), used_ticks_.end(),
378 current_tick - kGenerationWindowTicks + 1));
379 DCHECK(used_ticks_.size() <= kGenerationWindowTicks + 0u);
380 if (used_ticks_.size() >= kGenerationWindowTicks + 0u) {
381 // Average speed of GeneratePassport calls exceeds limit.
382 return 0;
383 }
384 for (int64 tick = current_tick;
385 tick > current_tick - kGenerationWindowTicks;
386 --tick) {
387 int idx = static_cast<int>(used_ticks_.size()) -
388 static_cast<int>(current_tick - tick + 1);
389 if (idx < 0 || used_ticks_[idx] != tick) {
390 DCHECK(used_ticks_.end() ==
391 std::find(used_ticks_.begin(), used_ticks_.end(), tick));
392 return tick;
393 }
394 }
395 NOTREACHED();
396 return 0;
397 }
398
399 std::string GeneratePassport(
400 const std::string& domain, const VarValueMap& map, int64 tick) {
401 DCHECK(CalledOnValidThread());
402 if (tick == 0) {
403 tick = GetUnusedTick(domain);
404 if (tick == 0)
405 return std::string();
406 }
407 if (!IsVarValueMapSane(map))
408 return std::string();
409
410 std::string result;
411 CreatePassport(domain, map, tick, engine_.get(), &result);
412 used_ticks_.insert(
413 std::lower_bound(used_ticks_.begin(), used_ticks_.end(), tick), tick);
414 return result;
415 }
416
417 private:
418 static int get_verification_window_ticks() {
419 return InternalAuthVerification::get_verification_window_ticks();
420 }
421
422 scoped_ptr<crypto::HMAC> engine_;
423 base::RepeatingTimer<InternalAuthGenerationService> timer_;
424 int64 key_regeneration_tick_;
425 std::deque<int64> used_ticks_;
426
427 DISALLOW_COPY_AND_ASSIGN(InternalAuthGenerationService);
428 };
429
430 } // namespace browser
431
432 namespace {
433
434 static base::LazyInstance<browser::InternalAuthGenerationService>
435 g_generation_service(base::LINKER_INITIALIZED);
436
437 } // namespace
438
439 namespace browser {
440
441 // static
442 bool InternalAuthVerification::VerifyPassport(
443 const std::string& passport,
444 const std::string& domain,
445 const VarValueMap& var_value_map) {
446 base::AutoLock alk(g_verification_service_lock.Get());
447 return g_verification_service.Get().VerifyPassport(
448 passport, domain, var_value_map);
449 }
450
451 // static
452 void InternalAuthVerification::ChangeKey(const std::string& key) {
453 base::AutoLock alk(g_verification_service_lock.Get());
454 g_verification_service.Get().ChangeKey(key);
455 };
456
457 // static
458 int InternalAuthVerification::get_verification_window_ticks() {
459 int candidate = kVerificationWindowTicks;
460 if (verification_window_seconds_ > 0)
461 candidate = verification_window_seconds_ *
462 base::Time::kMicrosecondsPerSecond / kTickUs;
463 return std::max(1, std::min(candidate, kVerificationWindowTicks));
464 }
465
466 int InternalAuthVerification::verification_window_seconds_ = 0;
467
468 // static
469 std::string InternalAuthGeneration::GeneratePassport(
470 const std::string& domain, const VarValueMap& var_value_map) {
471 return g_generation_service.Get().GeneratePassport(domain, var_value_map, 0);
472 }
473
474 // static
475 void InternalAuthGeneration::GenerateNewKey() {
476 g_generation_service.Get().GenerateNewKey();
477 }
478
479 } // namespace browser
480
OLDNEW
« no previous file with comments | « chrome/browser/internal_auth.h ('k') | chrome/browser/internal_auth_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698