• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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