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