Index: chrome/browser/metrics/variations/variations_seed_store.cc |
diff --git a/chrome/browser/metrics/variations/variations_seed_store.cc b/chrome/browser/metrics/variations/variations_seed_store.cc |
deleted file mode 100644 |
index 209cd8877df42f2cca87817b7348b6e940f754a6..0000000000000000000000000000000000000000 |
--- a/chrome/browser/metrics/variations/variations_seed_store.cc |
+++ /dev/null |
@@ -1,453 +0,0 @@ |
-// 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 "chrome/browser/metrics/variations/variations_seed_store.h" |
- |
-#include "base/base64.h" |
-#include "base/metrics/histogram_macros.h" |
-#include "base/numerics/safe_math.h" |
-#include "base/prefs/pref_registry_simple.h" |
-#include "base/prefs/pref_service.h" |
-#include "base/sha1.h" |
-#include "base/strings/string_number_conversions.h" |
-#include "components/compression/compression_utils.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" |
- |
-namespace chrome_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 |
-} |
- |
-// This is the algorithm ID for ECDSA with SHA-256. Parameters are ABSENT. |
-// RFC 5758: |
-// ecdsa-with-SHA256 OBJECT IDENTIFIER ::= { iso(1) member-body(2) |
-// us(840) ansi-X9-62(10045) signatures(4) ecdsa-with-SHA2(3) 2 } |
-// ... |
-// When the ecdsa-with-SHA224, ecdsa-with-SHA256, ecdsa-with-SHA384, or |
-// ecdsa-with-SHA512 algorithm identifier appears in the algorithm field |
-// as an AlgorithmIdentifier, the encoding MUST omit the parameters |
-// field. That is, the AlgorithmIdentifier SHALL be a SEQUENCE of one |
-// component, the OID ecdsa-with-SHA224, ecdsa-with-SHA256, ecdsa-with- |
-// SHA384, or ecdsa-with-SHA512. |
-// See also RFC 5480, Appendix A. |
-const uint8 kECDSAWithSHA256AlgorithmID[] = { |
- 0x30, 0x0a, |
- 0x06, 0x08, |
- 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02, |
-}; |
- |
-// 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_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; |
- |
- return base::Time::FromUTCExploded(exploded); |
-} |
- |
-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; |
-} |
- |
-} // 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(); |
- |
- 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, |
- variations::VariationsSeed* parsed_seed) { |
- if (!is_delta_compressed) { |
- const bool result = |
- StoreSeedDataNoDelta(data, base64_seed_signature, country_code, |
- date_fetched, parsed_seed); |
- if (result) { |
- UMA_HISTOGRAM_COUNTS_1000("Variations.StoreSeed.Size", |
- 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, 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() - data.length(); |
- UMA_HISTOGRAM_PERCENTAGE("Variations.StoreSeed.DeltaSize.ReductionPercent", |
- 100 * size_reduction / updated_seed_data.length()); |
- UMA_HISTOGRAM_COUNTS_1000("Variations.StoreSeed.DeltaSize", |
- 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 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( |
- kECDSAWithSHA256AlgorithmID, sizeof(kECDSAWithSHA256AlgorithmID), |
- reinterpret_cast<const uint8*>(signature.data()), signature.size(), |
- kPublicKey, arraysize(kPublicKey))) { |
- return VARIATIONS_SEED_SIGNATURE_INVALID_SIGNATURE; |
- } |
- |
- verifier.VerifyUpdate(reinterpret_cast<const uint8*>(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); |
-} |
- |
-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); |
- 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); |
- |
- // 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*>(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 existing_data_size = static_cast<uint32>(existing_data.size()); |
- while (in.CurrentPosition() != static_cast<int>(patch.length())) { |
- uint32 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 offset; |
- uint32 length; |
- if (!in.ReadVarint32(&offset) || !in.ReadVarint32(&length)) |
- return false; |
- |
- // Check for |offset + length| being out of range and for overflow. |
- base::CheckedNumeric<uint32> 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; |
-} |
- |
-} // namespace chrome_variations |