Home | History | Annotate | Download | only in browser
      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 
     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 
     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 
     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 
    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 
    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 
    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 
    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:
    193   InternalAuthVerificationService()
    194       : key_change_tick_(0),
    195         dark_tick_(0) {
    196   }
    197 
    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 
    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:
    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.
    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:
    333   InternalAuthGenerationService() : key_regeneration_tick_(0) {
    334     GenerateNewKey();
    335   }
    336 
    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.
    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 
    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:
    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
    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
    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
    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
    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
    470 void InternalAuthGeneration::GenerateNewKey() {
    471   g_generation_service.Get().GenerateNewKey();
    472 }
    473 
    474 }  // namespace chrome
    475