blob: c33fd7bdd22d14defc2936cde07f3e01dc600c3c [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"
Peter Kasting1f016552019-09-09 22:20:5518#include "base/numerics/ranges.h"
[email protected]c6e584c2011-05-18 11:58:4419#include "base/rand_util.h"
tripta.gc49d42a2017-06-29 11:45:0520#include "base/stl_util.h"
[email protected]3ea1b182013-02-08 22:38:4121#include "base/strings/string_number_conversions.h"
[email protected]1988e1c2013-02-28 20:27:4222#include "base/strings/string_split.h"
[email protected]9c7ddc92013-06-11 01:40:5723#include "base/strings/string_util.h"
[email protected]c38831a12011-10-28 12:44:4924#include "base/synchronization/lock.h"
[email protected]c6e584c2011-05-18 11:58:4425#include "base/threading/thread_checker.h"
[email protected]0e498482013-06-28 01:53:4326#include "base/time/time.h"
[email protected]c6e584c2011-05-18 11:58:4427#include "base/values.h"
[email protected]c6e584c2011-05-18 11:58:4428#include "crypto/hmac.h"
29
30namespace {
31
32typedef std::map<std::string, std::string> VarValueMap;
33
34// Size of a tick in microseconds. This determines upper bound for average
35// number of passports generated per time unit. This bound equals to
36// (kMicrosecondsPerSecond / TickUs) calls per second.
avidd4e614352015-12-09 00:44:4937const int64_t kTickUs = 10000;
[email protected]c6e584c2011-05-18 11:58:4438
39// Verification window size in ticks; that means any passport expires in
40// (kVerificationWindowTicks * TickUs / kMicrosecondsPerSecond) seconds.
41const int kVerificationWindowTicks = 2000;
42
43// Generation window determines how well we are able to cope with bursts of
44// GeneratePassport calls those exceed upper bound on average speed.
45const int kGenerationWindowTicks = 20;
46
47// Makes no sense to compare other way round.
mostynb3a46e0bf2014-12-23 09:02:4348static_assert(kGenerationWindowTicks <= kVerificationWindowTicks,
49 "generation window should not be larger than the verification window");
[email protected]c6e584c2011-05-18 11:58:4450// We are not optimized for high value of kGenerationWindowTicks.
mostynb3a46e0bf2014-12-23 09:02:4351static_assert(kGenerationWindowTicks < 30,
52 "generation window should not be too large");
[email protected]c6e584c2011-05-18 11:58:4453
54// Regenerate key after this number of ticks.
55const int kKeyRegenerationSoftTicks = 500000;
56// Reject passports if key has not been regenerated in that number of ticks.
57const int kKeyRegenerationHardTicks = kKeyRegenerationSoftTicks * 2;
58
59// Limit for number of accepted var=value pairs. Feel free to bump this limit
60// higher once needed.
61const size_t kVarsLimit = 16;
62
63// Limit for length of caller-supplied strings. Feel free to bump this limit
64// higher once needed.
65const size_t kStringLengthLimit = 512;
66
67// Character used as a separator for construction of message to take HMAC of.
68// It is critical to validate all caller-supplied data (used to construct
69// message) to be clear of this separator because it could allow attacks.
70const char kItemSeparator = '\n';
71
72// Character used for var=value separation.
73const char kVarValueSeparator = '=';
74
75const size_t kKeySizeInBytes = 128 / 8;
[email protected]673266c42012-12-04 00:50:3576const size_t kHMACSizeInBytes = 256 / 8;
[email protected]c6e584c2011-05-18 11:58:4477
78// Length of base64 string required to encode given number of raw octets.
79#define BASE64_PER_RAW(X) (X > 0 ? ((X - 1) / 3 + 1) * 4 : 0)
80
81// Size of decimal string representing 64-bit tick.
82const size_t kTickStringLength = 20;
83
84// A passport consists of 2 parts: HMAC and tick.
85const size_t kPassportSize =
86 BASE64_PER_RAW(kHMACSizeInBytes) + kTickStringLength;
87
avidd4e614352015-12-09 00:44:4988int64_t GetCurrentTick() {
89 int64_t tick = base::Time::Now().ToInternalValue() / kTickUs;
90 if (tick < kVerificationWindowTicks || tick < kKeyRegenerationHardTicks ||
91 tick > std::numeric_limits<int64_t>::max() - kKeyRegenerationHardTicks) {
[email protected]c6e584c2011-05-18 11:58:4492 return 0;
93 }
94 return tick;
95}
96
97bool IsDomainSane(const std::string& domain) {
98 return !domain.empty() &&
99 domain.size() <= kStringLengthLimit &&
[email protected]527965412014-05-07 14:38:26100 base::IsStringUTF8(domain) &&
[email protected]c6e584c2011-05-18 11:58:44101 domain.find_first_of(kItemSeparator) == std::string::npos;
102}
103
104bool IsVarSane(const std::string& var) {
105 static const char kAllowedChars[] =
106 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
107 "abcdefghijklmnopqrstuvwxyz"
108 "0123456789"
109 "_";
mostynb3a46e0bf2014-12-23 09:02:43110 static_assert(
111 sizeof(kAllowedChars) == 26 + 26 + 10 + 1 + 1, "some mess with chars");
[email protected]c6e584c2011-05-18 11:58:44112 // We must not allow kItemSeparator in anything used as an input to construct
113 // message to sign.
Jan Wilken Dörrie0aaef982019-06-06 18:36:22114 DCHECK(!base::Contains(kAllowedChars, kItemSeparator));
115 DCHECK(!base::Contains(kAllowedChars, kVarValueSeparator));
[email protected]c6e584c2011-05-18 11:58:44116 return !var.empty() &&
117 var.size() <= kStringLengthLimit &&
[email protected]527965412014-05-07 14:38:26118 base::IsStringASCII(var) &&
[email protected]c6e584c2011-05-18 11:58:44119 var.find_first_not_of(kAllowedChars) == std::string::npos &&
brettwb3413062015-06-24 00:39:02120 !base::IsAsciiDigit(var[0]);
[email protected]c6e584c2011-05-18 11:58:44121}
122
123bool IsValueSane(const std::string& value) {
124 return value.size() <= kStringLengthLimit &&
[email protected]527965412014-05-07 14:38:26125 base::IsStringUTF8(value) &&
[email protected]c6e584c2011-05-18 11:58:44126 value.find_first_of(kItemSeparator) == std::string::npos;
127}
128
129bool IsVarValueMapSane(const VarValueMap& map) {
130 if (map.size() > kVarsLimit)
131 return false;
jdoerrie2f1af512018-10-03 00:59:37132 for (auto it = map.begin(); it != map.end(); ++it) {
[email protected]c6e584c2011-05-18 11:58:44133 const std::string& var = it->first;
134 const std::string& value = it->second;
135 if (!IsVarSane(var) || !IsValueSane(value))
136 return false;
137 }
138 return true;
139}
140
141void ConvertVarValueMapToBlob(const VarValueMap& map, std::string* out) {
142 out->clear();
143 DCHECK(IsVarValueMapSane(map));
jdoerrie2f1af512018-10-03 00:59:37144 for (auto it = map.begin(); it != map.end(); ++it)
[email protected]c6e584c2011-05-18 11:58:44145 *out += it->first + kVarValueSeparator + it->second + kItemSeparator;
146}
147
[email protected]08b14a52012-07-02 23:30:36148void CreatePassport(const std::string& domain,
149 const VarValueMap& map,
avidd4e614352015-12-09 00:44:49150 int64_t tick,
[email protected]08b14a52012-07-02 23:30:36151 const crypto::HMAC* engine,
152 std::string* out) {
[email protected]c6e584c2011-05-18 11:58:44153 DCHECK(engine);
154 DCHECK(out);
155 DCHECK(IsDomainSane(domain));
156 DCHECK(IsVarValueMapSane(map));
157
158 out->clear();
159 std::string result(kPassportSize, '0');
160
161 std::string blob;
162 blob = domain + kItemSeparator;
163 std::string tmp;
164 ConvertVarValueMapToBlob(map, &tmp);
Raul Tambrefff51b752019-02-04 13:09:47165 blob += tmp + kItemSeparator + base::NumberToString(tick);
[email protected]c6e584c2011-05-18 11:58:44166
167 std::string hmac;
168 unsigned char* hmac_data = reinterpret_cast<unsigned char*>(
Brett Wilsone3c4d1a2015-07-07 23:38:09169 base::WriteInto(&hmac, kHMACSizeInBytes + 1));
[email protected]c6e584c2011-05-18 11:58:44170 if (!engine->Sign(blob, hmac_data, kHMACSizeInBytes)) {
171 NOTREACHED();
172 return;
173 }
174 std::string hmac_base64;
[email protected]33fca122013-12-11 01:48:50175 base::Base64Encode(hmac, &hmac_base64);
[email protected]c6e584c2011-05-18 11:58:44176 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
Raul Tambrefff51b752019-02-04 13:09:47183 std::string tick_decimal = base::NumberToString(tick);
[email protected]c6e584c2011-05-18 11:58:44184 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
[email protected]c6e584c2011-05-18 11:58:44195class InternalAuthVerificationService {
196 public:
197 InternalAuthVerificationService()
198 : key_change_tick_(0),
199 dark_tick_(0) {
200 }
201
202 bool VerifyPassport(
203 const std::string& passport,
204 const std::string& domain,
205 const VarValueMap& map) {
avidd4e614352015-12-09 00:44:49206 int64_t current_tick = GetCurrentTick();
207 int64_t tick = PreVerifyPassport(passport, domain, current_tick);
[email protected]c6e584c2011-05-18 11:58:44208 if (tick == 0)
209 return false;
210 if (!IsVarValueMapSane(map))
211 return false;
212 std::string reference_passport;
213 CreatePassport(domain, map, tick, engine_.get(), &reference_passport);
214 if (passport != reference_passport) {
215 // Consider old key.
216 if (key_change_tick_ + get_verification_window_ticks() < tick) {
217 return false;
218 }
219 if (old_key_.empty() || old_engine_ == NULL)
220 return false;
221 CreatePassport(domain, map, tick, old_engine_.get(), &reference_passport);
222 if (passport != reference_passport)
223 return false;
224 }
225
226 // Record used tick to prevent reuse.
Brett Wilson275a1372017-09-01 20:27:54227 base::circular_deque<int64_t>::iterator it =
avidd4e614352015-12-09 00:44:49228 std::lower_bound(used_ticks_.begin(), used_ticks_.end(), tick);
[email protected]c6e584c2011-05-18 11:58:44229 DCHECK(it == used_ticks_.end() || *it != tick);
230 used_ticks_.insert(it, tick);
231
232 // Consider pruning |used_ticks_|.
233 if (used_ticks_.size() > 50) {
234 dark_tick_ = std::max(dark_tick_,
235 current_tick - get_verification_window_ticks());
236 used_ticks_.erase(
237 used_ticks_.begin(),
238 std::lower_bound(used_ticks_.begin(), used_ticks_.end(),
239 dark_tick_ + 1));
240 }
241 return true;
242 }
243
244 void ChangeKey(const std::string& key) {
245 old_key_.swap(key_);
246 key_.clear();
247 old_engine_.swap(engine_);
248 engine_.reset(NULL);
249
250 if (key.size() != kKeySizeInBytes)
251 return;
dcheng4af48582016-04-19 00:29:35252 std::unique_ptr<crypto::HMAC> new_engine(
[email protected]6df5b9e2011-07-30 05:18:01253 new crypto::HMAC(crypto::HMAC::SHA256));
254 if (!new_engine->Init(key))
255 return;
256 engine_.swap(new_engine);
[email protected]c6e584c2011-05-18 11:58:44257 key_ = key;
258 key_change_tick_ = GetCurrentTick();
259 }
260
261 private:
262 static int get_verification_window_ticks() {
263 return InternalAuthVerification::get_verification_window_ticks();
264 }
265
266 // Returns tick bound to given passport on success or zero on failure.
avidd4e614352015-12-09 00:44:49267 int64_t PreVerifyPassport(const std::string& passport,
268 const std::string& domain,
269 int64_t current_tick) {
[email protected]c6e584c2011-05-18 11:58:44270 if (passport.size() != kPassportSize ||
[email protected]527965412014-05-07 14:38:26271 !base::IsStringASCII(passport) ||
[email protected]c6e584c2011-05-18 11:58:44272 !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);
avidd4e614352015-12-09 00:44:49284 int64_t tick = 0;
[email protected]c6e584c2011-05-18 11:58:44285 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.
dcheng4af48582016-04-19 00:29:35303 std::unique_ptr<crypto::HMAC> engine_;
304 std::unique_ptr<crypto::HMAC> old_engine_;
[email protected]c6e584c2011-05-18 11:58:44305
306 // Tick at a time of recent key regeneration.
avidd4e614352015-12-09 00:44:49307 int64_t key_change_tick_;
[email protected]c6e584c2011-05-18 11:58:44308
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.
Brett Wilson275a1372017-09-01 20:27:54312 base::circular_deque<int64_t> used_ticks_;
[email protected]c6e584c2011-05-18 11:58:44313
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.
avidd4e614352015-12-09 00:44:49316 int64_t dark_tick_;
[email protected]c6e584c2011-05-18 11:58:44317
318 DISALLOW_COPY_AND_ASSIGN(InternalAuthVerificationService);
319};
320
[email protected]c6e584c2011-05-18 11:58:44321namespace {
322
cm.sanchi72edd192017-10-30 09:37:22323static base::LazyInstance<InternalAuthVerificationService>::DestructorAtExit
324 g_verification_service = LAZY_INSTANCE_INITIALIZER;
[email protected]67f92bc32012-01-26 01:56:19325static base::LazyInstance<base::Lock>::Leaky
[email protected]6de0fd1d2011-11-15 13:31:49326 g_verification_service_lock = LAZY_INSTANCE_INITIALIZER;
[email protected]c6e584c2011-05-18 11:58:44327
328} // namespace
329
[email protected]c6e584c2011-05-18 11:58:44330class InternalAuthGenerationService : public base::ThreadChecker {
331 public:
332 InternalAuthGenerationService() : key_regeneration_tick_(0) {
333 GenerateNewKey();
334 }
335
336 void GenerateNewKey() {
337 DCHECK(CalledOnValidThread());
dcheng4af48582016-04-19 00:29:35338 std::unique_ptr<crypto::HMAC> new_engine(
339 new crypto::HMAC(crypto::HMAC::SHA256));
[email protected]c6e584c2011-05-18 11:58:44340 std::string key = base::RandBytesAsString(kKeySizeInBytes);
[email protected]6df5b9e2011-07-30 05:18:01341 if (!new_engine->Init(key))
342 return;
343 engine_.swap(new_engine);
[email protected]c6e584c2011-05-18 11:58:44344 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.
avidd4e614352015-12-09 00:44:49350 int64_t GetUnusedTick(const std::string& domain) {
[email protected]c6e584c2011-05-18 11:58:44351 DCHECK(CalledOnValidThread());
352 if (engine_ == NULL) {
353 NOTREACHED();
354 return 0;
355 }
356 if (!IsDomainSane(domain))
357 return 0;
358
avidd4e614352015-12-09 00:44:49359 int64_t current_tick = GetCurrentTick();
[email protected]c6e584c2011-05-18 11:58:44360 if (!used_ticks_.empty() && used_ticks_.back() > current_tick)
361 current_tick = used_ticks_.back();
[email protected]e234b112011-09-13 10:10:29362 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 }
[email protected]c6e584c2011-05-18 11:58:44369
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 }
avidd4e614352015-12-09 00:44:49380 for (int64_t tick = current_tick;
381 tick > current_tick - kGenerationWindowTicks; --tick) {
[email protected]c6e584c2011-05-18 11:58:44382 int idx = static_cast<int>(used_ticks_.size()) -
383 static_cast<int>(current_tick - tick + 1);
384 if (idx < 0 || used_ticks_[idx] != tick) {
Jan Wilken Dörrie0aaef982019-06-06 18:36:22385 DCHECK(!base::Contains(used_ticks_, tick));
[email protected]c6e584c2011-05-18 11:58:44386 return tick;
387 }
388 }
389 NOTREACHED();
390 return 0;
391 }
392
avidd4e614352015-12-09 00:44:49393 std::string GeneratePassport(const std::string& domain,
394 const VarValueMap& map,
395 int64_t tick) {
[email protected]c6e584c2011-05-18 11:58:44396 DCHECK(CalledOnValidThread());
397 if (tick == 0) {
398 tick = GetUnusedTick(domain);
399 if (tick == 0)
400 return std::string();
401 }
402 if (!IsVarValueMapSane(map))
403 return std::string();
404
405 std::string result;
406 CreatePassport(domain, map, tick, engine_.get(), &result);
407 used_ticks_.insert(
408 std::lower_bound(used_ticks_.begin(), used_ticks_.end(), tick), tick);
409 return result;
410 }
411
412 private:
413 static int get_verification_window_ticks() {
414 return InternalAuthVerification::get_verification_window_ticks();
415 }
416
dcheng4af48582016-04-19 00:29:35417 std::unique_ptr<crypto::HMAC> engine_;
avidd4e614352015-12-09 00:44:49418 int64_t key_regeneration_tick_;
Brett Wilson275a1372017-09-01 20:27:54419 base::circular_deque<int64_t> used_ticks_;
[email protected]c6e584c2011-05-18 11:58:44420
421 DISALLOW_COPY_AND_ASSIGN(InternalAuthGenerationService);
422};
423
[email protected]c6e584c2011-05-18 11:58:44424namespace {
425
cm.sanchi72edd192017-10-30 09:37:22426static base::LazyInstance<InternalAuthGenerationService>::DestructorAtExit
427 g_generation_service = LAZY_INSTANCE_INITIALIZER;
[email protected]c6e584c2011-05-18 11:58:44428
429} // namespace
430
[email protected]c6e584c2011-05-18 11:58:44431// static
432bool InternalAuthVerification::VerifyPassport(
433 const std::string& passport,
434 const std::string& domain,
435 const VarValueMap& var_value_map) {
436 base::AutoLock alk(g_verification_service_lock.Get());
437 return g_verification_service.Get().VerifyPassport(
438 passport, domain, var_value_map);
439}
440
441// static
442void InternalAuthVerification::ChangeKey(const std::string& key) {
443 base::AutoLock alk(g_verification_service_lock.Get());
444 g_verification_service.Get().ChangeKey(key);
brettwb3413062015-06-24 00:39:02445}
[email protected]c6e584c2011-05-18 11:58:44446
447// static
448int InternalAuthVerification::get_verification_window_ticks() {
449 int candidate = kVerificationWindowTicks;
450 if (verification_window_seconds_ > 0)
451 candidate = verification_window_seconds_ *
452 base::Time::kMicrosecondsPerSecond / kTickUs;
Peter Kasting1f016552019-09-09 22:20:55453 return base::ClampToRange(candidate, 1, kVerificationWindowTicks);
[email protected]c6e584c2011-05-18 11:58:44454}
455
456int InternalAuthVerification::verification_window_seconds_ = 0;
457
458// static
459std::string InternalAuthGeneration::GeneratePassport(
460 const std::string& domain, const VarValueMap& var_value_map) {
461 return g_generation_service.Get().GeneratePassport(domain, var_value_map, 0);
462}
463
464// static
465void InternalAuthGeneration::GenerateNewKey() {
466 g_generation_service.Get().GenerateNewKey();
467}
468