1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545
|
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/variations/variations_seed_store.h"
#include <stdint.h>
#include "base/base64.h"
#include "base/macros.h"
#include "base/metrics/histogram_macros.h"
#include "base/numerics/safe_math.h"
#include "base/sha1.h"
#include "base/strings/string_number_conversions.h"
#include "build/build_config.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/pref_service.h"
#include "components/variations/pref_names.h"
#include "components/variations/proto/variations_seed.pb.h"
#include "crypto/signature_verifier.h"
#include "third_party/protobuf/src/google/protobuf/io/coded_stream.h"
#include "third_party/zlib/google/compression_utils.h"
#if defined(OS_ANDROID)
#include "components/variations/android/variations_seed_bridge.h"
#endif // OS_ANDROID
namespace variations {
namespace {
// Signature verification is disabled on mobile platforms for now, since it
// adds about ~15ms to the startup time on mobile (vs. a couple ms on desktop).
bool SignatureVerificationEnabled() {
#if defined(OS_IOS) || defined(OS_ANDROID)
return false;
#else
return true;
#endif
}
// The ECDSA public key of the variations server for verifying variations seed
// signatures.
const uint8_t kPublicKey[] = {
0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01,
0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, 0x03, 0x42, 0x00,
0x04, 0x51, 0x7c, 0x31, 0x4b, 0x50, 0x42, 0xdd, 0x59, 0xda, 0x0b, 0xfa, 0x43,
0x44, 0x33, 0x7c, 0x5f, 0xa1, 0x0b, 0xd5, 0x82, 0xf6, 0xac, 0x04, 0x19, 0x72,
0x6c, 0x40, 0xd4, 0x3e, 0x56, 0xe2, 0xa0, 0x80, 0xa0, 0x41, 0xb3, 0x23, 0x7b,
0x71, 0xc9, 0x80, 0x87, 0xde, 0x35, 0x0d, 0x25, 0x71, 0x09, 0x7f, 0xb4, 0x15,
0x2b, 0xff, 0x82, 0x4d, 0xd3, 0xfe, 0xc5, 0xef, 0x20, 0xc6, 0xa3, 0x10, 0xbf,
};
// Note: UMA histogram enum - don't re-order or remove entries.
enum VariationSeedEmptyState {
VARIATIONS_SEED_NOT_EMPTY,
VARIATIONS_SEED_EMPTY,
VARIATIONS_SEED_CORRUPT,
VARIATIONS_SEED_INVALID_SIGNATURE,
VARIATIONS_SEED_CORRUPT_BASE64,
VARIATIONS_SEED_CORRUPT_PROTOBUF,
VARIATIONS_SEED_CORRUPT_GZIP,
VARIATIONS_SEED_EMPTY_ENUM_SIZE,
};
void RecordVariationSeedEmptyHistogram(VariationSeedEmptyState state) {
UMA_HISTOGRAM_ENUMERATION("Variations.SeedEmpty", state,
VARIATIONS_SEED_EMPTY_ENUM_SIZE);
}
enum VariationsSeedStoreResult {
VARIATIONS_SEED_STORE_SUCCESS,
VARIATIONS_SEED_STORE_FAILED_EMPTY,
VARIATIONS_SEED_STORE_FAILED_PARSE,
VARIATIONS_SEED_STORE_FAILED_SIGNATURE,
VARIATIONS_SEED_STORE_FAILED_GZIP,
// DELTA_COUNT is not so much a result of the seed store, but rather counting
// the number of delta-compressed seeds the SeedStore() function saw. Kept in
// the same histogram for convenience of comparing against the other values.
VARIATIONS_SEED_STORE_DELTA_COUNT,
VARIATIONS_SEED_STORE_FAILED_DELTA_READ_SEED,
VARIATIONS_SEED_STORE_FAILED_DELTA_APPLY,
VARIATIONS_SEED_STORE_FAILED_DELTA_STORE,
VARIATIONS_SEED_STORE_FAILED_UNGZIP,
VARIATIONS_SEED_STORE_FAILED_EMPTY_GZIP_CONTENTS,
VARIATIONS_SEED_STORE_FAILED_UNSUPPORTED_SEED_FORMAT,
VARIATIONS_SEED_STORE_RESULT_ENUM_SIZE,
};
void RecordSeedStoreHistogram(VariationsSeedStoreResult result) {
UMA_HISTOGRAM_ENUMERATION("Variations.SeedStoreResult", result,
VARIATIONS_SEED_STORE_RESULT_ENUM_SIZE);
}
// Note: UMA histogram enum - don't re-order or remove entries.
enum VariationsSeedDateChangeState {
SEED_DATE_NO_OLD_DATE,
SEED_DATE_NEW_DATE_OLDER,
SEED_DATE_SAME_DAY,
SEED_DATE_NEW_DAY,
SEED_DATE_ENUM_SIZE,
};
// Truncates a time to the start of the day in UTC. If given a time representing
// 2014-03-11 10:18:03.1 UTC, it will return a time representing
// 2014-03-11 00:00:00.0 UTC.
base::Time TruncateToUTCDay(const base::Time& time) {
base::Time::Exploded exploded;
time.UTCExplode(&exploded);
exploded.hour = 0;
exploded.minute = 0;
exploded.second = 0;
exploded.millisecond = 0;
base::Time out_time;
bool conversion_success = base::Time::FromUTCExploded(exploded, &out_time);
DCHECK(conversion_success);
return out_time;
}
VariationsSeedDateChangeState GetSeedDateChangeState(
const base::Time& server_seed_date,
const base::Time& stored_seed_date) {
if (server_seed_date < stored_seed_date)
return SEED_DATE_NEW_DATE_OLDER;
if (TruncateToUTCDay(server_seed_date) !=
TruncateToUTCDay(stored_seed_date)) {
// The server date is earlier than the stored date, and they are from
// different UTC days, so |server_seed_date| is a valid new day.
return SEED_DATE_NEW_DAY;
}
return SEED_DATE_SAME_DAY;
}
#if defined(OS_ANDROID)
enum FirstRunResult {
FIRST_RUN_SEED_IMPORT_SUCCESS,
FIRST_RUN_SEED_IMPORT_FAIL_NO_CALLBACK,
FIRST_RUN_SEED_IMPORT_FAIL_NO_FIRST_RUN_SEED,
FIRST_RUN_SEED_IMPORT_FAIL_STORE_FAILED,
FIRST_RUN_SEED_IMPORT_FAIL_INVALID_RESPONSE_DATE,
FIRST_RUN_RESULT_ENUM_SIZE,
};
void RecordFirstRunResult(FirstRunResult result) {
UMA_HISTOGRAM_ENUMERATION("Variations.FirstRunResult", result,
FIRST_RUN_RESULT_ENUM_SIZE);
}
#endif // OS_ANDROID
} // namespace
VariationsSeedStore::VariationsSeedStore(PrefService* local_state)
: local_state_(local_state), seed_has_country_code_(false) {
}
VariationsSeedStore::~VariationsSeedStore() {
}
bool VariationsSeedStore::LoadSeed(variations::VariationsSeed* seed) {
invalid_base64_signature_.clear();
#if defined(OS_ANDROID)
if (!local_state_->HasPrefPath(prefs::kVariationsSeedSignature))
ImportFirstRunJavaSeed();
#endif // OS_ANDROID
std::string seed_data;
if (!ReadSeedData(&seed_data))
return false;
const std::string base64_seed_signature =
local_state_->GetString(prefs::kVariationsSeedSignature);
const VerifySignatureResult result =
VerifySeedSignature(seed_data, base64_seed_signature);
if (result != VARIATIONS_SEED_SIGNATURE_ENUM_SIZE) {
UMA_HISTOGRAM_ENUMERATION("Variations.LoadSeedSignature", result,
VARIATIONS_SEED_SIGNATURE_ENUM_SIZE);
if (result != VARIATIONS_SEED_SIGNATURE_VALID) {
ClearPrefs();
RecordVariationSeedEmptyHistogram(VARIATIONS_SEED_INVALID_SIGNATURE);
// Record the invalid signature.
invalid_base64_signature_ = base64_seed_signature;
return false;
}
}
if (!seed->ParseFromString(seed_data)) {
ClearPrefs();
RecordVariationSeedEmptyHistogram(VARIATIONS_SEED_CORRUPT_PROTOBUF);
return false;
}
// Migrate any existing country code from the seed to the pref, if the pref is
// empty. TODO(asvitkine): Clean up the code in M50+ when sufficient number
// of clients have migrated.
if (seed->has_country_code() &&
local_state_->GetString(prefs::kVariationsCountry).empty()) {
local_state_->SetString(prefs::kVariationsCountry, seed->country_code());
}
variations_serial_number_ = seed->serial_number();
seed_has_country_code_ = seed->has_country_code();
RecordVariationSeedEmptyHistogram(VARIATIONS_SEED_NOT_EMPTY);
return true;
}
bool VariationsSeedStore::StoreSeedData(
const std::string& data,
const std::string& base64_seed_signature,
const std::string& country_code,
const base::Time& date_fetched,
bool is_delta_compressed,
bool is_gzip_compressed,
variations::VariationsSeed* parsed_seed) {
// If the data is gzip compressed, first uncompress it.
std::string ungzipped_data;
if (is_gzip_compressed) {
if (compression::GzipUncompress(data, &ungzipped_data)) {
if (ungzipped_data.empty()) {
RecordSeedStoreHistogram(
VARIATIONS_SEED_STORE_FAILED_EMPTY_GZIP_CONTENTS);
return false;
}
int size_reduction = ungzipped_data.length() - data.length();
UMA_HISTOGRAM_PERCENTAGE("Variations.StoreSeed.GzipSize.ReductionPercent",
100 * size_reduction / ungzipped_data.length());
UMA_HISTOGRAM_COUNTS_1000("Variations.StoreSeed.GzipSize",
data.length() / 1024);
} else {
RecordSeedStoreHistogram(VARIATIONS_SEED_STORE_FAILED_UNGZIP);
return false;
}
} else {
ungzipped_data = data;
}
if (!is_delta_compressed) {
const bool result =
StoreSeedDataNoDelta(ungzipped_data, base64_seed_signature,
country_code, date_fetched, parsed_seed);
if (result) {
UMA_HISTOGRAM_COUNTS_1000("Variations.StoreSeed.Size",
ungzipped_data.length() / 1024);
}
return result;
}
// If the data is delta compressed, first decode it.
DCHECK(invalid_base64_signature_.empty());
RecordSeedStoreHistogram(VARIATIONS_SEED_STORE_DELTA_COUNT);
std::string existing_seed_data;
std::string updated_seed_data;
if (!ReadSeedData(&existing_seed_data)) {
RecordSeedStoreHistogram(VARIATIONS_SEED_STORE_FAILED_DELTA_READ_SEED);
return false;
}
if (!ApplyDeltaPatch(existing_seed_data, ungzipped_data,
&updated_seed_data)) {
RecordSeedStoreHistogram(VARIATIONS_SEED_STORE_FAILED_DELTA_APPLY);
return false;
}
const bool result =
StoreSeedDataNoDelta(updated_seed_data, base64_seed_signature,
country_code, date_fetched, parsed_seed);
if (result) {
// Note: |updated_seed_data.length()| is guaranteed to be non-zero, else
// result would be false.
int size_reduction = updated_seed_data.length() - ungzipped_data.length();
UMA_HISTOGRAM_PERCENTAGE("Variations.StoreSeed.DeltaSize.ReductionPercent",
100 * size_reduction / updated_seed_data.length());
UMA_HISTOGRAM_COUNTS_1000("Variations.StoreSeed.DeltaSize",
ungzipped_data.length() / 1024);
} else {
RecordSeedStoreHistogram(VARIATIONS_SEED_STORE_FAILED_DELTA_STORE);
}
return result;
}
void VariationsSeedStore::UpdateSeedDateAndLogDayChange(
const base::Time& server_date_fetched) {
VariationsSeedDateChangeState date_change = SEED_DATE_NO_OLD_DATE;
if (local_state_->HasPrefPath(prefs::kVariationsSeedDate)) {
const int64_t stored_date_value =
local_state_->GetInt64(prefs::kVariationsSeedDate);
const base::Time stored_date =
base::Time::FromInternalValue(stored_date_value);
date_change = GetSeedDateChangeState(server_date_fetched, stored_date);
}
UMA_HISTOGRAM_ENUMERATION("Variations.SeedDateChange", date_change,
SEED_DATE_ENUM_SIZE);
local_state_->SetInt64(prefs::kVariationsSeedDate,
server_date_fetched.ToInternalValue());
}
std::string VariationsSeedStore::GetInvalidSignature() const {
return invalid_base64_signature_;
}
// static
void VariationsSeedStore::RegisterPrefs(PrefRegistrySimple* registry) {
registry->RegisterStringPref(prefs::kVariationsCompressedSeed, std::string());
registry->RegisterStringPref(prefs::kVariationsSeed, std::string());
registry->RegisterInt64Pref(prefs::kVariationsSeedDate,
base::Time().ToInternalValue());
registry->RegisterStringPref(prefs::kVariationsSeedSignature, std::string());
registry->RegisterStringPref(prefs::kVariationsCountry, std::string());
}
VariationsSeedStore::VerifySignatureResult
VariationsSeedStore::VerifySeedSignature(
const std::string& seed_bytes,
const std::string& base64_seed_signature) {
if (!SignatureVerificationEnabled())
return VARIATIONS_SEED_SIGNATURE_ENUM_SIZE;
if (base64_seed_signature.empty())
return VARIATIONS_SEED_SIGNATURE_MISSING;
std::string signature;
if (!base::Base64Decode(base64_seed_signature, &signature))
return VARIATIONS_SEED_SIGNATURE_DECODE_FAILED;
crypto::SignatureVerifier verifier;
if (!verifier.VerifyInit(crypto::SignatureVerifier::ECDSA_SHA256,
reinterpret_cast<const uint8_t*>(signature.data()),
signature.size(), kPublicKey,
arraysize(kPublicKey))) {
return VARIATIONS_SEED_SIGNATURE_INVALID_SIGNATURE;
}
verifier.VerifyUpdate(reinterpret_cast<const uint8_t*>(seed_bytes.data()),
seed_bytes.size());
if (verifier.VerifyFinal())
return VARIATIONS_SEED_SIGNATURE_VALID;
return VARIATIONS_SEED_SIGNATURE_INVALID_SEED;
}
void VariationsSeedStore::ClearPrefs() {
local_state_->ClearPref(prefs::kVariationsCompressedSeed);
local_state_->ClearPref(prefs::kVariationsSeed);
local_state_->ClearPref(prefs::kVariationsSeedDate);
local_state_->ClearPref(prefs::kVariationsSeedSignature);
}
#if defined(OS_ANDROID)
void VariationsSeedStore::ImportFirstRunJavaSeed() {
DVLOG(1) << "Importing first run seed from Java preferences.";
std::string seed_data;
std::string seed_signature;
std::string seed_country;
std::string response_date;
bool is_gzip_compressed;
android::GetVariationsFirstRunSeed(&seed_data, &seed_signature, &seed_country,
&response_date, &is_gzip_compressed);
if (seed_data.empty()) {
RecordFirstRunResult(FIRST_RUN_SEED_IMPORT_FAIL_NO_FIRST_RUN_SEED);
return;
}
base::Time current_date;
if (!base::Time::FromUTCString(response_date.c_str(), ¤t_date)) {
RecordFirstRunResult(FIRST_RUN_SEED_IMPORT_FAIL_INVALID_RESPONSE_DATE);
LOG(WARNING) << "Invalid response date: " << response_date;
return;
}
if (!StoreSeedData(seed_data, seed_signature, seed_country, current_date,
false, is_gzip_compressed, nullptr)) {
RecordFirstRunResult(FIRST_RUN_SEED_IMPORT_FAIL_STORE_FAILED);
LOG(WARNING) << "First run variations seed is invalid.";
return;
}
RecordFirstRunResult(FIRST_RUN_SEED_IMPORT_SUCCESS);
}
#endif // OS_ANDROID
bool VariationsSeedStore::ReadSeedData(std::string* seed_data) {
std::string base64_seed_data =
local_state_->GetString(prefs::kVariationsCompressedSeed);
const bool is_compressed = !base64_seed_data.empty();
// If there's no compressed seed, fall back to the uncompressed one.
if (!is_compressed)
base64_seed_data = local_state_->GetString(prefs::kVariationsSeed);
if (base64_seed_data.empty()) {
RecordVariationSeedEmptyHistogram(VARIATIONS_SEED_EMPTY);
return false;
}
// If the decode process fails, assume the pref value is corrupt and clear it.
std::string decoded_data;
if (!base::Base64Decode(base64_seed_data, &decoded_data)) {
ClearPrefs();
RecordVariationSeedEmptyHistogram(VARIATIONS_SEED_CORRUPT_BASE64);
return false;
}
if (!is_compressed) {
seed_data->swap(decoded_data);
} else if (!compression::GzipUncompress(decoded_data, seed_data)) {
ClearPrefs();
RecordVariationSeedEmptyHistogram(VARIATIONS_SEED_CORRUPT_GZIP);
return false;
}
return true;
}
bool VariationsSeedStore::StoreSeedDataNoDelta(
const std::string& seed_data,
const std::string& base64_seed_signature,
const std::string& country_code,
const base::Time& date_fetched,
variations::VariationsSeed* parsed_seed) {
if (seed_data.empty()) {
RecordSeedStoreHistogram(VARIATIONS_SEED_STORE_FAILED_EMPTY_GZIP_CONTENTS);
return false;
}
// Only store the seed data if it parses correctly.
variations::VariationsSeed seed;
if (!seed.ParseFromString(seed_data)) {
RecordSeedStoreHistogram(VARIATIONS_SEED_STORE_FAILED_PARSE);
return false;
}
const VerifySignatureResult result =
VerifySeedSignature(seed_data, base64_seed_signature);
if (result != VARIATIONS_SEED_SIGNATURE_ENUM_SIZE) {
UMA_HISTOGRAM_ENUMERATION("Variations.StoreSeedSignature", result,
VARIATIONS_SEED_SIGNATURE_ENUM_SIZE);
if (result != VARIATIONS_SEED_SIGNATURE_VALID) {
RecordSeedStoreHistogram(VARIATIONS_SEED_STORE_FAILED_SIGNATURE);
return false;
}
}
// Compress the seed before base64-encoding and storing.
std::string compressed_seed_data;
if (!compression::GzipCompress(seed_data, &compressed_seed_data)) {
RecordSeedStoreHistogram(VARIATIONS_SEED_STORE_FAILED_GZIP);
return false;
}
std::string base64_seed_data;
base::Base64Encode(compressed_seed_data, &base64_seed_data);
// TODO(asvitkine): This pref is no longer being used. Remove it completely
// in M45+.
local_state_->ClearPref(prefs::kVariationsSeed);
#if defined(OS_ANDROID)
// If currently we do not have any stored pref then we mark seed storing as
// successful on the Java side of Chrome for Android to avoid repeated seed
// fetches and clear preferences on the Java side.
if (local_state_->GetString(prefs::kVariationsCompressedSeed).empty()) {
android::MarkVariationsSeedAsStored();
android::ClearJavaFirstRunPrefs();
}
#endif
// Update the saved country code only if one was returned from the server.
// Prefer the country code that was transmitted in the header over the one in
// the seed (which is deprecated).
if (!country_code.empty())
local_state_->SetString(prefs::kVariationsCountry, country_code);
else if (seed.has_country_code())
local_state_->SetString(prefs::kVariationsCountry, seed.country_code());
local_state_->SetString(prefs::kVariationsCompressedSeed, base64_seed_data);
UpdateSeedDateAndLogDayChange(date_fetched);
local_state_->SetString(prefs::kVariationsSeedSignature,
base64_seed_signature);
variations_serial_number_ = seed.serial_number();
if (parsed_seed)
seed.Swap(parsed_seed);
RecordSeedStoreHistogram(VARIATIONS_SEED_STORE_SUCCESS);
return true;
}
// static
bool VariationsSeedStore::ApplyDeltaPatch(const std::string& existing_data,
const std::string& patch,
std::string* output) {
output->clear();
google::protobuf::io::CodedInputStream in(
reinterpret_cast<const uint8_t*>(patch.data()), patch.length());
// Temporary string declared outside the loop so it can be re-used between
// different iterations (rather than allocating new ones).
std::string temp;
const uint32_t existing_data_size =
static_cast<uint32_t>(existing_data.size());
while (in.CurrentPosition() != static_cast<int>(patch.length())) {
uint32_t value;
if (!in.ReadVarint32(&value))
return false;
if (value != 0) {
// A non-zero value indicates the number of bytes to copy from the patch
// stream to the output.
// No need to guard against bad data (i.e. very large |value|) because the
// call below will fail if |value| is greater than the size of the patch.
if (!in.ReadString(&temp, value))
return false;
output->append(temp);
} else {
// Otherwise, when it's zero, it indicates that it's followed by a pair of
// numbers - |offset| and |length| that specify a range of data to copy
// from |existing_data|.
uint32_t offset;
uint32_t length;
if (!in.ReadVarint32(&offset) || !in.ReadVarint32(&length))
return false;
// Check for |offset + length| being out of range and for overflow.
base::CheckedNumeric<uint32_t> end_offset(offset);
end_offset += length;
if (!end_offset.IsValid() || end_offset.ValueOrDie() > existing_data_size)
return false;
output->append(existing_data, offset, length);
}
}
return true;
}
void VariationsSeedStore::ReportUnsupportedSeedFormatError() {
RecordSeedStoreHistogram(
VARIATIONS_SEED_STORE_FAILED_UNSUPPORTED_SEED_FORMAT);
}
} // namespace variations
|