blob: 83a1183898b1611e92d8bd65430137d9d2c4010c [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"
Hans Wennborg1790e6b2020-04-24 19:10:3315#include "base/check.h"
Brett Wilson275a1372017-09-01 20:27:5416#include "base/containers/circular_deque.h"
Jan Wilken Dörrieb5a41c32020-12-09 18:55:4717#include "base/containers/contains.h"
Avi Drissmand383d0b32021-07-24 07:02:0018#include "base/cxx17_backports.h"
[email protected]c6e584c2011-05-18 11:58:4419#include "base/lazy_instance.h"
Hans Wennborg1790e6b2020-04-24 19:10:3320#include "base/notreached.h"
[email protected]c6e584c2011-05-18 11:58:4421#include "base/rand_util.h"
[email protected]3ea1b182013-02-08 22:38:4122#include "base/strings/string_number_conversions.h"
[email protected]1988e1c2013-02-28 20:27:4223#include "base/strings/string_split.h"
[email protected]9c7ddc92013-06-11 01:40:5724#include "base/strings/string_util.h"
[email protected]c38831a12011-10-28 12:44:4925#include "base/synchronization/lock.h"
[email protected]c6e584c2011-05-18 11:58:4426#include "base/threading/thread_checker.h"
[email protected]0e498482013-06-28 01:53:4327#include "base/time/time.h"
[email protected]c6e584c2011-05-18 11:58:4428#include "base/values.h"
[email protected]c6e584c2011-05-18 11:58:4429#include "crypto/hmac.h"
30
31namespace {
32
33typedef std::map<std::string, std::string> VarValueMap;
34
35// Size of a tick in microseconds. This determines upper bound for average
36// number of passports generated per time unit. This bound equals to
37// (kMicrosecondsPerSecond / TickUs) calls per second.
avidd4e614352015-12-09 00:44:4938const int64_t kTickUs = 10000;
[email protected]c6e584c2011-05-18 11:58:4439
40// Verification window size in ticks; that means any passport expires in
41// (kVerificationWindowTicks * TickUs / kMicrosecondsPerSecond) seconds.
42const int kVerificationWindowTicks = 2000;
43
44// Generation window determines how well we are able to cope with bursts of
45// GeneratePassport calls those exceed upper bound on average speed.
46const int kGenerationWindowTicks = 20;
47
48// Makes no sense to compare other way round.
mostynb3a46e0bf2014-12-23 09:02:4349static_assert(kGenerationWindowTicks <= kVerificationWindowTicks,
50 "generation window should not be larger than the verification window");
[email protected]c6e584c2011-05-18 11:58:4451// We are not optimized for high value of kGenerationWindowTicks.
mostynb3a46e0bf2014-12-23 09:02:4352static_assert(kGenerationWindowTicks < 30,
53 "generation window should not be too large");
[email protected]c6e584c2011-05-18 11:58:4454
55// Regenerate key after this number of ticks.
56const int kKeyRegenerationSoftTicks = 500000;
57// Reject passports if key has not been regenerated in that number of ticks.
58const int kKeyRegenerationHardTicks = kKeyRegenerationSoftTicks * 2;
59
60// Limit for number of accepted var=value pairs. Feel free to bump this limit
61// higher once needed.
62const size_t kVarsLimit = 16;
63
64// Limit for length of caller-supplied strings. Feel free to bump this limit
65// higher once needed.
66const size_t kStringLengthLimit = 512;
67
68// Character used as a separator for construction of message to take HMAC of.
69// It is critical to validate all caller-supplied data (used to construct
70// message) to be clear of this separator because it could allow attacks.
71const char kItemSeparator = '\n';
72
73// Character used for var=value separation.
74const char kVarValueSeparator = '=';
75
76const size_t kKeySizeInBytes = 128 / 8;
[email protected]673266c42012-12-04 00:50:3577const size_t kHMACSizeInBytes = 256 / 8;
[email protected]c6e584c2011-05-18 11:58:4478
79// Length of base64 string required to encode given number of raw octets.
80#define BASE64_PER_RAW(X) (X > 0 ? ((X - 1) / 3 + 1) * 4 : 0)
81
82// Size of decimal string representing 64-bit tick.
83const size_t kTickStringLength = 20;
84
85// A passport consists of 2 parts: HMAC and tick.
86const size_t kPassportSize =
87 BASE64_PER_RAW(kHMACSizeInBytes) + kTickStringLength;
88
avidd4e614352015-12-09 00:44:4989int64_t GetCurrentTick() {
90 int64_t tick = base::Time::Now().ToInternalValue() / kTickUs;
91 if (tick < kVerificationWindowTicks || tick < kKeyRegenerationHardTicks ||
92 tick > std::numeric_limits<int64_t>::max() - kKeyRegenerationHardTicks) {
[email protected]c6e584c2011-05-18 11:58:4493 return 0;
94 }
95 return tick;
96}
97
98bool IsDomainSane(const std::string& domain) {
99 return !domain.empty() &&
100 domain.size() <= kStringLengthLimit &&
[email protected]527965412014-05-07 14:38:26101 base::IsStringUTF8(domain) &&
[email protected]c6e584c2011-05-18 11:58:44102 domain.find_first_of(kItemSeparator) == std::string::npos;
103}
104
105bool IsVarSane(const std::string& var) {
106 static const char kAllowedChars[] =
107 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
108 "abcdefghijklmnopqrstuvwxyz"
109 "0123456789"
110 "_";
mostynb3a46e0bf2014-12-23 09:02:43111 static_assert(
112 sizeof(kAllowedChars) == 26 + 26 + 10 + 1 + 1, "some mess with chars");
[email protected]c6e584c2011-05-18 11:58:44113 // We must not allow kItemSeparator in anything used as an input to construct
114 // message to sign.
Jan Wilken Dörrie0aaef982019-06-06 18:36:22115 DCHECK(!base::Contains(kAllowedChars, kItemSeparator));
116 DCHECK(!base::Contains(kAllowedChars, kVarValueSeparator));
[email protected]c6e584c2011-05-18 11:58:44117 return !var.empty() &&
118 var.size() <= kStringLengthLimit &&
[email protected]527965412014-05-07 14:38:26119 base::IsStringASCII(var) &&
[email protected]c6e584c2011-05-18 11:58:44120 var.find_first_not_of(kAllowedChars) == std::string::npos &&
brettwb3413062015-06-24 00:39:02121 !base::IsAsciiDigit(var[0]);
[email protected]c6e584c2011-05-18 11:58:44122}
123
124bool IsValueSane(const std::string& value) {
125 return value.size() <= kStringLengthLimit &&
[email protected]527965412014-05-07 14:38:26126 base::IsStringUTF8(value) &&
[email protected]c6e584c2011-05-18 11:58:44127 value.find_first_of(kItemSeparator) == std::string::npos;
128}
129
130bool IsVarValueMapSane(const VarValueMap& map) {
131 if (map.size() > kVarsLimit)
132 return false;
jdoerrie2f1af512018-10-03 00:59:37133 for (auto it = map.begin(); it != map.end(); ++it) {
[email protected]c6e584c2011-05-18 11:58:44134 const std::string& var = it->first;
135 const std::string& value = it->second;
136 if (!IsVarSane(var) || !IsValueSane(value))
137 return false;
138 }
139 return true;
140}
141
142void ConvertVarValueMapToBlob(const VarValueMap& map, std::string* out) {
143 out->clear();
144 DCHECK(IsVarValueMapSane(map));
jdoerrie2f1af512018-10-03 00:59:37145 for (auto it = map.begin(); it != map.end(); ++it)
[email protected]c6e584c2011-05-18 11:58:44146 *out += it->first + kVarValueSeparator + it->second + kItemSeparator;
147}
148
[email protected]08b14a52012-07-02 23:30:36149void CreatePassport(const std::string& domain,
150 const VarValueMap& map,
avidd4e614352015-12-09 00:44:49151 int64_t tick,
[email protected]08b14a52012-07-02 23:30:36152 const crypto::HMAC* engine,
153 std::string* out) {
[email protected]c6e584c2011-05-18 11:58:44154 DCHECK(engine);
155 DCHECK(out);
156 DCHECK(IsDomainSane(domain));
157 DCHECK(IsVarValueMapSane(map));
158
159 out->clear();
160 std::string result(kPassportSize, '0');
161
162 std::string blob;
163 blob = domain + kItemSeparator;
164 std::string tmp;
165 ConvertVarValueMapToBlob(map, &tmp);
Raul Tambrefff51b752019-02-04 13:09:47166 blob += tmp + kItemSeparator + base::NumberToString(tick);
[email protected]c6e584c2011-05-18 11:58:44167
168 std::string hmac;
169 unsigned char* hmac_data = reinterpret_cast<unsigned char*>(
Brett Wilsone3c4d1a2015-07-07 23:38:09170 base::WriteInto(&hmac, kHMACSizeInBytes + 1));
[email protected]c6e584c2011-05-18 11:58:44171 if (!engine->Sign(blob, hmac_data, kHMACSizeInBytes)) {
172 NOTREACHED();
173 return;
174 }
175 std::string hmac_base64;
[email protected]33fca122013-12-11 01:48:50176 base::Base64Encode(hmac, &hmac_base64);
[email protected]c6e584c2011-05-18 11:58:44177 if (hmac_base64.size() != BASE64_PER_RAW(kHMACSizeInBytes)) {
178 NOTREACHED();
179 return;
180 }
181 DCHECK(hmac_base64.size() < result.size());
182 std::copy(hmac_base64.begin(), hmac_base64.end(), result.begin());
183
Raul Tambrefff51b752019-02-04 13:09:47184 std::string tick_decimal = base::NumberToString(tick);
[email protected]c6e584c2011-05-18 11:58:44185 DCHECK(tick_decimal.size() <= kTickStringLength);
186 std::copy(
187 tick_decimal.begin(),
188 tick_decimal.end(),
189 result.begin() + kPassportSize - tick_decimal.size());
190
191 out->swap(result);
192}
193
194} // namespace
195
[email protected]c6e584c2011-05-18 11:58:44196class InternalAuthVerificationService {
197 public:
198 InternalAuthVerificationService()
199 : key_change_tick_(0),
200 dark_tick_(0) {
201 }
202
Peter Boströmfadb1752021-09-30 19:17:01203 InternalAuthVerificationService(const InternalAuthVerificationService&) =
204 delete;
205 InternalAuthVerificationService& operator=(
206 const InternalAuthVerificationService&) = delete;
207
[email protected]c6e584c2011-05-18 11:58:44208 bool VerifyPassport(
209 const std::string& passport,
210 const std::string& domain,
211 const VarValueMap& map) {
avidd4e614352015-12-09 00:44:49212 int64_t current_tick = GetCurrentTick();
213 int64_t tick = PreVerifyPassport(passport, domain, current_tick);
[email protected]c6e584c2011-05-18 11:58:44214 if (tick == 0)
215 return false;
216 if (!IsVarValueMapSane(map))
217 return false;
218 std::string reference_passport;
219 CreatePassport(domain, map, tick, engine_.get(), &reference_passport);
220 if (passport != reference_passport) {
221 // Consider old key.
222 if (key_change_tick_ + get_verification_window_ticks() < tick) {
223 return false;
224 }
225 if (old_key_.empty() || old_engine_ == NULL)
226 return false;
227 CreatePassport(domain, map, tick, old_engine_.get(), &reference_passport);
228 if (passport != reference_passport)
229 return false;
230 }
231
232 // Record used tick to prevent reuse.
Brett Wilson275a1372017-09-01 20:27:54233 base::circular_deque<int64_t>::iterator it =
avidd4e614352015-12-09 00:44:49234 std::lower_bound(used_ticks_.begin(), used_ticks_.end(), tick);
[email protected]c6e584c2011-05-18 11:58:44235 DCHECK(it == used_ticks_.end() || *it != tick);
236 used_ticks_.insert(it, tick);
237
238 // Consider pruning |used_ticks_|.
239 if (used_ticks_.size() > 50) {
240 dark_tick_ = std::max(dark_tick_,
241 current_tick - get_verification_window_ticks());
242 used_ticks_.erase(
243 used_ticks_.begin(),
244 std::lower_bound(used_ticks_.begin(), used_ticks_.end(),
245 dark_tick_ + 1));
246 }
247 return true;
248 }
249
250 void ChangeKey(const std::string& key) {
251 old_key_.swap(key_);
252 key_.clear();
253 old_engine_.swap(engine_);
254 engine_.reset(NULL);
255
256 if (key.size() != kKeySizeInBytes)
257 return;
dcheng4af48582016-04-19 00:29:35258 std::unique_ptr<crypto::HMAC> new_engine(
[email protected]6df5b9e2011-07-30 05:18:01259 new crypto::HMAC(crypto::HMAC::SHA256));
260 if (!new_engine->Init(key))
261 return;
262 engine_.swap(new_engine);
[email protected]c6e584c2011-05-18 11:58:44263 key_ = key;
264 key_change_tick_ = GetCurrentTick();
265 }
266
267 private:
268 static int get_verification_window_ticks() {
269 return InternalAuthVerification::get_verification_window_ticks();
270 }
271
272 // Returns tick bound to given passport on success or zero on failure.
avidd4e614352015-12-09 00:44:49273 int64_t PreVerifyPassport(const std::string& passport,
274 const std::string& domain,
275 int64_t current_tick) {
[email protected]c6e584c2011-05-18 11:58:44276 if (passport.size() != kPassportSize ||
[email protected]527965412014-05-07 14:38:26277 !base::IsStringASCII(passport) ||
[email protected]c6e584c2011-05-18 11:58:44278 !IsDomainSane(domain) ||
279 current_tick <= dark_tick_ ||
280 current_tick > key_change_tick_ + kKeyRegenerationHardTicks ||
281 key_.empty() ||
282 engine_ == NULL) {
283 return 0;
284 }
285
286 // Passport consists of 2 parts: first hmac and then tick.
287 std::string tick_decimal =
288 passport.substr(BASE64_PER_RAW(kHMACSizeInBytes));
289 DCHECK(tick_decimal.size() == kTickStringLength);
avidd4e614352015-12-09 00:44:49290 int64_t tick = 0;
[email protected]c6e584c2011-05-18 11:58:44291 if (!base::StringToInt64(tick_decimal, &tick) ||
292 tick <= dark_tick_ ||
293 tick > key_change_tick_ + kKeyRegenerationHardTicks ||
294 tick < current_tick - get_verification_window_ticks() ||
295 std::binary_search(used_ticks_.begin(), used_ticks_.end(), tick)) {
296 return 0;
297 }
298 return tick;
299 }
300
301 // Current key.
302 std::string key_;
303
304 // We keep previous key in order to be able to verify passports during
305 // regeneration time. Keys are regenerated on a regular basis.
306 std::string old_key_;
307
308 // Corresponding HMAC engines.
dcheng4af48582016-04-19 00:29:35309 std::unique_ptr<crypto::HMAC> engine_;
310 std::unique_ptr<crypto::HMAC> old_engine_;
[email protected]c6e584c2011-05-18 11:58:44311
312 // Tick at a time of recent key regeneration.
avidd4e614352015-12-09 00:44:49313 int64_t key_change_tick_;
[email protected]c6e584c2011-05-18 11:58:44314
315 // Keeps track of ticks of successfully verified passports to prevent their
316 // reuse. Size of this container is kept reasonably low by purging outdated
317 // ticks.
Brett Wilson275a1372017-09-01 20:27:54318 base::circular_deque<int64_t> used_ticks_;
[email protected]c6e584c2011-05-18 11:58:44319
320 // Some ticks before |dark_tick_| were purged from |used_ticks_| container.
321 // That means that we must not trust any tick less than or equal to dark tick.
avidd4e614352015-12-09 00:44:49322 int64_t dark_tick_;
[email protected]c6e584c2011-05-18 11:58:44323};
324
[email protected]c6e584c2011-05-18 11:58:44325namespace {
326
cm.sanchi72edd192017-10-30 09:37:22327static base::LazyInstance<InternalAuthVerificationService>::DestructorAtExit
328 g_verification_service = LAZY_INSTANCE_INITIALIZER;
[email protected]67f92bc32012-01-26 01:56:19329static base::LazyInstance<base::Lock>::Leaky
[email protected]6de0fd1d2011-11-15 13:31:49330 g_verification_service_lock = LAZY_INSTANCE_INITIALIZER;
[email protected]c6e584c2011-05-18 11:58:44331
332} // namespace
333
[email protected]c6e584c2011-05-18 11:58:44334class InternalAuthGenerationService : public base::ThreadChecker {
335 public:
336 InternalAuthGenerationService() : key_regeneration_tick_(0) {
337 GenerateNewKey();
338 }
339
Peter Boströmfadb1752021-09-30 19:17:01340 InternalAuthGenerationService(const InternalAuthGenerationService&) = delete;
341 InternalAuthGenerationService& operator=(
342 const InternalAuthGenerationService&) = delete;
343
[email protected]c6e584c2011-05-18 11:58:44344 void GenerateNewKey() {
345 DCHECK(CalledOnValidThread());
dcheng4af48582016-04-19 00:29:35346 std::unique_ptr<crypto::HMAC> new_engine(
347 new crypto::HMAC(crypto::HMAC::SHA256));
[email protected]c6e584c2011-05-18 11:58:44348 std::string key = base::RandBytesAsString(kKeySizeInBytes);
[email protected]6df5b9e2011-07-30 05:18:01349 if (!new_engine->Init(key))
350 return;
351 engine_.swap(new_engine);
[email protected]c6e584c2011-05-18 11:58:44352 key_regeneration_tick_ = GetCurrentTick();
353 g_verification_service.Get().ChangeKey(key);
354 std::fill(key.begin(), key.end(), 0);
355 }
356
357 // Returns zero on failure.
avidd4e614352015-12-09 00:44:49358 int64_t GetUnusedTick(const std::string& domain) {
[email protected]c6e584c2011-05-18 11:58:44359 DCHECK(CalledOnValidThread());
360 if (engine_ == NULL) {
361 NOTREACHED();
362 return 0;
363 }
364 if (!IsDomainSane(domain))
365 return 0;
366
avidd4e614352015-12-09 00:44:49367 int64_t current_tick = GetCurrentTick();
[email protected]c6e584c2011-05-18 11:58:44368 if (!used_ticks_.empty() && used_ticks_.back() > current_tick)
369 current_tick = used_ticks_.back();
[email protected]e234b112011-09-13 10:10:29370 for (bool first_iteration = true;; first_iteration = false) {
371 if (current_tick < key_regeneration_tick_ + kKeyRegenerationHardTicks)
372 break;
373 if (!first_iteration)
374 return 0;
375 GenerateNewKey();
376 }
[email protected]c6e584c2011-05-18 11:58:44377
378 // Forget outdated ticks if any.
379 used_ticks_.erase(
380 used_ticks_.begin(),
381 std::lower_bound(used_ticks_.begin(), used_ticks_.end(),
382 current_tick - kGenerationWindowTicks + 1));
383 DCHECK(used_ticks_.size() <= kGenerationWindowTicks + 0u);
384 if (used_ticks_.size() >= kGenerationWindowTicks + 0u) {
385 // Average speed of GeneratePassport calls exceeds limit.
386 return 0;
387 }
avidd4e614352015-12-09 00:44:49388 for (int64_t tick = current_tick;
389 tick > current_tick - kGenerationWindowTicks; --tick) {
[email protected]c6e584c2011-05-18 11:58:44390 int idx = static_cast<int>(used_ticks_.size()) -
391 static_cast<int>(current_tick - tick + 1);
392 if (idx < 0 || used_ticks_[idx] != tick) {
Jan Wilken Dörrie0aaef982019-06-06 18:36:22393 DCHECK(!base::Contains(used_ticks_, tick));
[email protected]c6e584c2011-05-18 11:58:44394 return tick;
395 }
396 }
397 NOTREACHED();
398 return 0;
399 }
400
avidd4e614352015-12-09 00:44:49401 std::string GeneratePassport(const std::string& domain,
402 const VarValueMap& map,
403 int64_t tick) {
[email protected]c6e584c2011-05-18 11:58:44404 DCHECK(CalledOnValidThread());
405 if (tick == 0) {
406 tick = GetUnusedTick(domain);
407 if (tick == 0)
408 return std::string();
409 }
410 if (!IsVarValueMapSane(map))
411 return std::string();
412
413 std::string result;
414 CreatePassport(domain, map, tick, engine_.get(), &result);
415 used_ticks_.insert(
416 std::lower_bound(used_ticks_.begin(), used_ticks_.end(), tick), tick);
417 return result;
418 }
419
420 private:
421 static int get_verification_window_ticks() {
422 return InternalAuthVerification::get_verification_window_ticks();
423 }
424
dcheng4af48582016-04-19 00:29:35425 std::unique_ptr<crypto::HMAC> engine_;
avidd4e614352015-12-09 00:44:49426 int64_t key_regeneration_tick_;
Brett Wilson275a1372017-09-01 20:27:54427 base::circular_deque<int64_t> used_ticks_;
[email protected]