blob: cdbff42f7ce5279f712f0c50edc94664f970906e [file] [log] [blame]
[email protected]67f92bc32012-01-26 01:56:191// Copyright (c) 2012 The Chromium Authors. All rights reserved.
[email protected]c6e584c2011-05-18 11:58:442// 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
avi6846aef2015-12-26 01:09:387#include <stddef.h>
avidd4e614352015-12-09 00:44:498#include <stdint.h>
9
[email protected]c6e584c2011-05-18 11:58:4410#include <algorithm>
avidd4e614352015-12-09 00:44:4911#include <limits>
dcheng4af48582016-04-19 00:29:3512#include <memory>
[email protected]c6e584c2011-05-18 11:58:4413
14#include "base/base64.h"
Brett Wilson275a1372017-09-01 20:27:5415#include "base/containers/circular_deque.h"
[email protected]c6e584c2011-05-18 11:58:4416#include "base/lazy_instance.h"
avi6846aef2015-12-26 01:09:3817#include "base/macros.h"
[email protected]c6e584c2011-05-18 11:58:4418#include "base/rand_util.h"
tripta.gc49d42a2017-06-29 11:45:0519#include "base/stl_util.h"
[email protected]3ea1b182013-02-08 22:38:4120#include "base/strings/string_number_conversions.h"
[email protected]1988e1c2013-02-28 20:27:4221#include "base/strings/string_split.h"
[email protected]9c7ddc92013-06-11 01:40:5722#include "base/strings/string_util.h"
[email protected]c38831a12011-10-28 12:44:4923#include "base/synchronization/lock.h"
[email protected]c6e584c2011-05-18 11:58:4424#include "base/threading/thread_checker.h"
[email protected]0e498482013-06-28 01:53:4325#include "base/time/time.h"
[email protected]c6e584c2011-05-18 11:58:4426#include "base/values.h"
[email protected]c6e584c2011-05-18 11:58:4427#include "crypto/hmac.h"
28
29namespace {
30
31typedef std::map<std::string, std::string> VarValueMap;
32
33// Size of a tick in microseconds. This determines upper bound for average
34// number of passports generated per time unit. This bound equals to
35// (kMicrosecondsPerSecond / TickUs) calls per second.
avidd4e614352015-12-09 00:44:4936const int64_t kTickUs = 10000;
[email protected]c6e584c2011-05-18 11:58:4437
38// Verification window size in ticks; that means any passport expires in
39// (kVerificationWindowTicks * TickUs / kMicrosecondsPerSecond) seconds.
40const int kVerificationWindowTicks = 2000;
41
42// Generation window determines how well we are able to cope with bursts of
43// GeneratePassport calls those exceed upper bound on average speed.
44const int kGenerationWindowTicks = 20;
45
46// Makes no sense to compare other way round.
mostynb3a46e0bf2014-12-23 09:02:4347static_assert(kGenerationWindowTicks <= kVerificationWindowTicks,
48 "generation window should not be larger than the verification window");
[email protected]c6e584c2011-05-18 11:58:4449// We are not optimized for high value of kGenerationWindowTicks.
mostynb3a46e0bf2014-12-23 09:02:4350static_assert(kGenerationWindowTicks < 30,
51 "generation window should not be too large");
[email protected]c6e584c2011-05-18 11:58:4452
53// Regenerate key after this number of ticks.
54const int kKeyRegenerationSoftTicks = 500000;
55// Reject passports if key has not been regenerated in that number of ticks.
56const int kKeyRegenerationHardTicks = kKeyRegenerationSoftTicks * 2;
57
58// Limit for number of accepted var=value pairs. Feel free to bump this limit
59// higher once needed.
60const size_t kVarsLimit = 16;
61
62// Limit for length of caller-supplied strings. Feel free to bump this limit
63// higher once needed.
64const size_t kStringLengthLimit = 512;
65
66// Character used as a separator for construction of message to take HMAC of.
67// It is critical to validate all caller-supplied data (used to construct
68// message) to be clear of this separator because it could allow attacks.
69const char kItemSeparator = '\n';
70
71// Character used for var=value separation.
72const char kVarValueSeparator = '=';
73
74const size_t kKeySizeInBytes = 128 / 8;
[email protected]673266c42012-12-04 00:50:3575const size_t kHMACSizeInBytes = 256 / 8;
[email protected]c6e584c2011-05-18 11:58:4476
77// Length of base64 string required to encode given number of raw octets.
78#define BASE64_PER_RAW(X) (X > 0 ? ((X - 1) / 3 + 1) * 4 : 0)
79
80// Size of decimal string representing 64-bit tick.
81const size_t kTickStringLength = 20;
82
83// A passport consists of 2 parts: HMAC and tick.
84const size_t kPassportSize =
85 BASE64_PER_RAW(kHMACSizeInBytes) + kTickStringLength;
86
avidd4e614352015-12-09 00:44:4987int64_t GetCurrentTick() {
88 int64_t tick = base::Time::Now().ToInternalValue() / kTickUs;
89 if (tick < kVerificationWindowTicks || tick < kKeyRegenerationHardTicks ||
90 tick > std::numeric_limits<int64_t>::max() - kKeyRegenerationHardTicks) {
[email protected]c6e584c2011-05-18 11:58:4491 return 0;
92 }
93 return tick;
94}
95
96bool IsDomainSane(const std::string& domain) {
97 return !domain.empty() &&
98 domain.size() <= kStringLengthLimit &&
[email protected]527965412014-05-07 14:38:2699 base::IsStringUTF8(domain) &&
[email protected]c6e584c2011-05-18 11:58:44100 domain.find_first_of(kItemSeparator) == std::string::npos;
101}
102
103bool IsVarSane(const std::string& var) {
104 static const char kAllowedChars[] =
105 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
106 "abcdefghijklmnopqrstuvwxyz"
107 "0123456789"
108 "_";
mostynb3a46e0bf2014-12-23 09:02:43109 static_assert(
110 sizeof(kAllowedChars) == 26 + 26 + 10 + 1 + 1, "some mess with chars");
[email protected]c6e584c2011-05-18 11:58:44111 // We must not allow kItemSeparator in anything used as an input to construct
112 // message to sign.
Haeun Kim3f6123502018-08-26 18:03:04113 DCHECK(!base::ContainsValue(kAllowedChars, kItemSeparator));
114 DCHECK(!base::ContainsValue(kAllowedChars, kVarValueSeparator));
[email protected]c6e584c2011-05-18 11:58:44115 return !var.empty() &&
116 var.size() <= kStringLengthLimit &&
[email protected]527965412014-05-07 14:38:26117 base::IsStringASCII(var) &&
[email protected]c6e584c2011-05-18 11:58:44118 var.find_first_not_of(kAllowedChars) == std::string::npos &&
brettwb3413062015-06-24 00:39:02119 !base::IsAsciiDigit(var[0]);
[email protected]c6e584c2011-05-18 11:58:44120}
121
122bool IsValueSane(const std::string& value) {
123 return value.size() <= kStringLengthLimit &&
[email protected]527965412014-05-07 14:38:26124 base::IsStringUTF8(value) &&
[email protected]c6e584c2011-05-18 11:58:44125 value.find_first_of(kItemSeparator) == std::string::npos;
126}
127
128bool IsVarValueMapSane(const VarValueMap& map) {
129 if (map.size() > kVarsLimit)
130 return false;
jdoerrie2f1af512018-10-03 00:59:37131 for (auto it = map.begin(); it != map.end(); ++it) {
[email protected]c6e584c2011-05-18 11:58:44132 const std::string& var = it->first;
133 const std::string& value = it->second;
134 if (!IsVarSane(var) || !IsValueSane(value))
135 return false;
136 }
137 return true;
138}
139
140void ConvertVarValueMapToBlob(const VarValueMap& map, std::string* out) {
141 out->clear();
142 DCHECK(IsVarValueMapSane(map));
jdoerrie2f1af512018-10-03 00:59:37143 for (auto it = map.begin(); it != map.end(); ++it)
[email protected]c6e584c2011-05-18 11:58:44144 *out += it->first + kVarValueSeparator + it->second + kItemSeparator;
145}
146
[email protected]08b14a52012-07-02 23:30:36147void CreatePassport(const std::string& domain,
148 const VarValueMap& map,
avidd4e614352015-12-09 00:44:49149 int64_t tick,
[email protected]08b14a52012-07-02 23:30:36150 const crypto::HMAC* engine,
151 std::string* out) {
[email protected]c6e584c2011-05-18 11:58:44152 DCHECK(engine);
153 DCHECK(out);
154 DCHECK(IsDomainSane(domain));
155 DCHECK(IsVarValueMapSane(map));
156
157 out->clear();
158 std::string result(kPassportSize, '0');
159
160 std::string blob;
161 blob = domain + kItemSeparator;
162 std::string tmp;
163 ConvertVarValueMapToBlob(map, &tmp);
ricea95212c12015-09-19 04:10:07164 blob += tmp + kItemSeparator + base::Int64ToString(tick);
[email protected]c6e584c2011-05-18 11:58:44165
166 std::string hmac;
167 unsigned char* hmac_data = reinterpret_cast<unsigned char*>(
Brett Wilsone3c4d1a2015-07-07 23:38:09168 base::WriteInto(&hmac, kHMACSizeInBytes + 1));
[email protected]c6e584c2011-05-18 11:58:44169 if (!engine->Sign(blob, hmac_data, kHMACSizeInBytes)) {
170 NOTREACHED();
171 return;
172 }
173 std::string hmac_base64;
[email protected]33fca122013-12-11 01:48:50174 base::Base64Encode(hmac, &hmac_base64);
[email protected]c6e584c2011-05-18 11:58:44175 if (hmac_base64.size() != BASE64_PER_RAW(kHMACSizeInBytes)) {
176 NOTREACHED();
177 return;
178 }
179 DCHECK(hmac_base64.size() < result.size());
180 std::copy(hmac_base64.begin(), hmac_base64.end(), result.begin());
181
ricea95212c12015-09-19 04:10:07182 std::string tick_decimal = base::Int64ToString(tick);
[email protected]c6e584c2011-05-18 11:58:44183 DCHECK(tick_decimal.size() <= kTickStringLength);
184 std::copy(
185 tick_decimal.begin(),
186 tick_decimal.end(),
187 result.begin() + kPassportSize - tick_decimal.size());
188
189 out->swap(result);
190}
191
192} // namespace
193
[email protected]c6e584c2011-05-18 11:58:44194class InternalAuthVerificationService {
195 public:
196 InternalAuthVerificationService()
197 : key_change_tick_(0),
198 dark_tick_(0) {
199 }
200
201 bool VerifyPassport(
202 const std::string& passport,
203 const std::string& domain,
204 const VarValueMap& map) {
avidd4e614352015-12-09 00:44:49205 int64_t current_tick = GetCurrentTick();
206 int64_t tick = PreVerifyPassport(passport, domain, current_tick);
[email protected]c6e584c2011-05-18 11:58:44207 if (tick == 0)
208 return false;
209 if (!IsVarValueMapSane(map))
210 return false;
211 std::string reference_passport;
212 CreatePassport(domain, map, tick, engine_.get(), &reference_passport);
213 if (passport != reference_passport) {
214 // Consider old key.
215 if (key_change_tick_ + get_verification_window_ticks() < tick) {
216 return false;
217 }
218 if (old_key_.empty() || old_engine_ == NULL)
219 return false;
220 CreatePassport(domain, map, tick, old_engine_.get(), &reference_passport);
221 if (passport != reference_passport)
222 return false;
223 }
224
225 // Record used tick to prevent reuse.
Brett Wilson275a1372017-09-01 20:27:54226 base::circular_deque<int64_t>::iterator it =
avidd4e614352015-12-09 00:44:49227 std::lower_bound(used_ticks_.begin(), used_ticks_.end(), tick);
[email protected]c6e584c2011-05-18 11:58:44228 DCHECK(it == used_ticks_.end() || *it != tick);
229 used_ticks_.insert(it, tick);
230
231 // Consider pruning |used_ticks_|.
232 if (used_ticks_.size() > 50) {
233 dark_tick_ = std::max(dark_tick_,
234 current_tick - get_verification_window_ticks());
235 used_ticks_.erase(
236 used_ticks_.begin(),
237 std::lower_bound(used_ticks_.begin(), used_ticks_.end(),
238 dark_tick_ + 1));
239 }
240 return true;
241 }
242
243 void ChangeKey(const std::string& key) {
244 old_key_.swap(key_);
245 key_.clear();
246 old_engine_.swap(engine_);
247 engine_.reset(NULL);
248
249 if (key.size() != kKeySizeInBytes)
250 return;
dcheng4af48582016-04-19 00:29:35251 std::unique_ptr<crypto::HMAC> new_engine(
[email protected]6df5b9e2011-07-30 05:18:01252 new crypto::HMAC(crypto::HMAC::SHA256));
253 if (!new_engine->Init(key))
254 return;
255 engine_.swap(new_engine);
[email protected]c6e584c2011-05-18 11:58:44256 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.
avidd4e614352015-12-09 00:44:49266 int64_t PreVerifyPassport(const std::string& passport,
267 const std::string& domain,
268 int64_t current_tick) {
[email protected]c6e584c2011-05-18 11:58:44269 if (passport.size() != kPassportSize ||
[email protected]527965412014-05-07 14:38:26270 !base::IsStringASCII(passport) ||
[email protected]c6e584c2011-05-18 11:58:44271 !IsDomainSane(domain) ||
272 current_tick <= dark_tick_ ||
273 current_tick > key_change_tick_ + kKeyRegenerationHardTicks ||
274 key_.empty() ||
275 engine_ == NULL) {
276 return 0;
277 }
278
279 // Passport consists of 2 parts: first hmac and then tick.
280 std::string tick_decimal =
281 passport.substr(BASE64_PER_RAW(kHMACSizeInBytes));
282 DCHECK(tick_decimal.size() == kTickStringLength);
avidd4e614352015-12-09 00:44:49283 int64_t tick = 0;
[email protected]c6e584c2011-05-18 11:58:44284 if (!base::StringToInt64(tick_decimal, &tick) ||
285 tick <= dark_tick_ ||
286 tick > key_change_tick_ + kKeyRegenerationHardTicks ||
287 tick < current_tick - get_verification_window_ticks() ||
288 std::binary_search(used_ticks_.begin(), used_ticks_.end(), tick)) {
289 return 0;
290 }
291 return tick;
292 }
293
294 // Current key.
295 std::string key_;
296
297 // We keep previous key in order to be able to verify passports during
298 // regeneration time. Keys are regenerated on a regular basis.
299 std::string old_key_;
300
301 // Corresponding HMAC engines.
dcheng4af48582016-04-19 00:29:35302 std::unique_ptr<crypto::HMAC> engine_;
303 std::unique_ptr<crypto::HMAC> old_engine_;
[email protected]c6e584c2011-05-18 11:58:44304
305 // Tick at a time of recent key regeneration.
avidd4e614352015-12-09 00:44:49306 int64_t key_change_tick_;
[email protected]c6e584c2011-05-18 11:58:44307
308 // Keeps track of ticks of successfully verified passports to prevent their
309 // reuse. Size of this container is kept reasonably low by purging outdated
310 // ticks.
Brett Wilson275a1372017-09-01 20:27:54311 base::circular_deque<int64_t> used_ticks_;
[email protected]c6e584c2011-05-18 11:58:44312
313 // Some ticks before |dark_tick_| were purged from |used_ticks_| container.
314 // That means that we must not trust any tick less than or equal to dark tick.
avidd4e614352015-12-09 00:44:49315 int64_t dark_tick_;
[email protected]c6e584c2011-05-18 11:58:44316
317 DISALLOW_COPY_AND_ASSIGN(InternalAuthVerificationService);
318};
319
[email protected]c6e584c2011-05-18 11:58:44320namespace {
321
cm.sanchi72edd192017-10-30 09:37:22322static base::LazyInstance<InternalAuthVerificationService>::DestructorAtExit
323 g_verification_service = LAZY_INSTANCE_INITIALIZER;
[email protected]67f92bc32012-01-26 01:56:19324static base::LazyInstance<base::Lock>::Leaky
[email protected]6de0fd1d2011-11-15 13:31:49325 g_verification_service_lock = LAZY_INSTANCE_INITIALIZER;
[email protected]c6e584c2011-05-18 11:58:44326
327} // namespace
328
[email protected]c6e584c2011-05-18 11:58:44329class InternalAuthGenerationService : public base::ThreadChecker {
330 public:
331 InternalAuthGenerationService() : key_regeneration_tick_(0) {
332 GenerateNewKey();
333 }
334
335 void GenerateNewKey() {
336 DCHECK(CalledOnValidThread());
dcheng4af48582016-04-19 00:29:35337 std::unique_ptr<crypto::HMAC> new_engine(
338 new crypto::HMAC(crypto::HMAC::SHA256));
[email protected]c6e584c2011-05-18 11:58:44339 std::string key = base::RandBytesAsString(kKeySizeInBytes);
[email protected]6df5b9e2011-07-30 05:18:01340 if (!new_engine->Init(key))
341 return;
342 engine_.swap(new_engine);
[email protected]c6e584c2011-05-18 11:58:44343 key_regeneration_tick_ = GetCurrentTick();
344 g_verification_service.Get().ChangeKey(key);
345 std::fill(key.begin(), key.end(), 0);
346 }
347
348 // Returns zero on failure.
avidd4e614352015-12-09 00:44:49349 int64_t GetUnusedTick(const std::string& domain) {
[email protected]c6e584c2011-05-18 11:58:44350 DCHECK(CalledOnValidThread());
351 if (engine_ == NULL) {
352 NOTREACHED();
353 return 0;
354 }
355 if (!IsDomainSane(domain))
356 return 0;
357
avidd4e614352015-12-09 00:44:49358 int64_t current_tick = GetCurrentTick();
[email protected]c6e584c2011-05-18 11:58:44359 if (!used_ticks_.empty() && used_ticks_.back() > current_tick)
360 current_tick = used_ticks_.back();
[email protected]e234b112011-09-13 10:10:29361 for (bool first_iteration = true;; first_iteration = false) {
362 if (current_tick < key_regeneration_tick_ + kKeyRegenerationHardTicks)
363 break;
364 if (!first_iteration)
365 return 0;
366 GenerateNewKey();
367 }
[email protected]c6e584c2011-05-18 11:58:44368
369 // Forget outdated ticks if any.
370 used_ticks_.erase(
371 used_ticks_.begin(),
372 std::lower_bound(used_ticks_.begin(), used_ticks_.end(),
373 current_tick - kGenerationWindowTicks + 1));
374 DCHECK(used_ticks_.size() <= kGenerationWindowTicks + 0u);
375 if (used_ticks_.size() >= kGenerationWindowTicks + 0u) {
376 // Average speed of GeneratePassport calls exceeds limit.
377 return 0;
378 }
avidd4e614352015-12-09 00:44:49379 for (int64_t tick = current_tick;
380 tick > current_tick - kGenerationWindowTicks; --tick) {
[email protected]c6e584c2011-05-18 11:58:44381 int idx = static_cast<int>(used_ticks_.size()) -
382 static_cast<int>(current_tick - tick + 1);
383 if (idx < 0 || used_ticks_[idx] != tick) {
tripta.gc49d42a2017-06-29 11:45:05384 DCHECK(!base::ContainsValue(used_ticks_, tick));
[email protected]c6e584c2011-05-18 11:58:44385 return tick;
386 }
387 }
388 NOTREACHED();
389 return 0;
390 }
391
avidd4e614352015-12-09 00:44:49392 std::string GeneratePassport(const std::string& domain,
393 const VarValueMap& map,
394 int64_t tick) {
[email protected]c6e584c2011-05-18 11:58:44395 DCHECK(CalledOnValidThread());
396 if (tick == 0) {
397 tick = GetUnusedTick(domain);
398 if (tick == 0)
399 return std::string();
400 }
401 if (!IsVarValueMapSane(map))
402 return std::string();
403
404 std::string result;
405 CreatePassport(domain, map, tick, engine_.get(), &result);
406 used_ticks_.insert(
407 std::lower_bound(used_ticks_.begin(), used_ticks_.end(), tick), tick);
408 return result;
409 }
410
411 private:
412 static int get_verification_window_ticks() {
413 return InternalAuthVerification::get_verification_window_ticks();
414 }
415
dcheng4af48582016-04-19 00:29:35416 std::unique_ptr<crypto::HMAC> engine_;
avidd4e614352015-12-09 00:44:49417 int64_t key_regeneration_tick_;
Brett Wilson275a1372017-09-01 20:27:54418 base::circular_deque<int64_t> used_ticks_;
[email protected]c6e584c2011-05-18 11:58:44419
420 DISALLOW_COPY_AND_ASSIGN(InternalAuthGenerationService);
421};
422
[email protected]c6e584c2011-05-18 11:58:44423namespace {
424
cm.sanchi72edd192017-10-30 09:37:22425static base::LazyInstance<InternalAuthGenerationService>::DestructorAtExit
426 g_generation_service = LAZY_INSTANCE_INITIALIZER;
[email protected]c6e584c2011-05-18 11:58:44427
428} // namespace
429
[email protected]c6e584c2011-05-18 11:58:44430// static
431bool InternalAuthVerification::VerifyPassport(
432 const std::string& passport,
433 const std::string& domain,
434 const VarValueMap& var_value_map) {
435 base::AutoLock alk(g_verification_service_lock.Get());
436 return g_verification_service.Get().VerifyPassport(
437 passport, domain, var_value_map);
438}
439
440// static
441void InternalAuthVerification::ChangeKey(const std::string& key) {
442 base::AutoLock alk(g_verification_service_lock.Get());
443 g_verification_service.Get().ChangeKey(key);
brettwb3413062015-06-24 00:39:02444}
[email protected]c6e584c2011-05-18 11:58:44445
446// static
447int InternalAuthVerification::get_verification_window_ticks() {
448 int candidate = kVerificationWindowTicks;
449 if (verification_window_seconds_ > 0)
450 candidate = verification_window_seconds_ *
451 base::Time::kMicrosecondsPerSecond / kTickUs;
452 return std::max(1, std::min(candidate, kVerificationWindowTicks));
453}
454
455int InternalAuthVerification::verification_window_seconds_ = 0;
456
457// static
458std::string InternalAuthGeneration::GeneratePassport(
459 const std::string& domain, const VarValueMap& var_value_map) {
460 return g_generation_service.Get().GeneratePassport(domain, var_value_map, 0);
461}
462
463// static
464void InternalAuthGeneration::GenerateNewKey() {
465 g_generation_service.Get().GenerateNewKey();
466}
467