Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / components / variations / variations_seed_store.cc
blobe20b0560069cc23ebc1c66e028fe3bc652734ad0
1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "components/variations/variations_seed_store.h"
7 #include "base/base64.h"
8 #include "base/metrics/histogram_macros.h"
9 #include "base/numerics/safe_math.h"
10 #include "base/prefs/pref_registry_simple.h"
11 #include "base/prefs/pref_service.h"
12 #include "base/sha1.h"
13 #include "base/strings/string_number_conversions.h"
14 #include "components/compression/compression_utils.h"
15 #include "components/variations/pref_names.h"
16 #include "components/variations/proto/variations_seed.pb.h"
17 #include "crypto/signature_verifier.h"
18 #include "third_party/protobuf/src/google/protobuf/io/coded_stream.h"
20 namespace variations {
22 namespace {
24 // Signature verification is disabled on mobile platforms for now, since it
25 // adds about ~15ms to the startup time on mobile (vs. a couple ms on desktop).
26 bool SignatureVerificationEnabled() {
27 #if defined(OS_IOS) || defined(OS_ANDROID)
28 return false;
29 #else
30 return true;
31 #endif
34 // This is the algorithm ID for ECDSA with SHA-256. Parameters are ABSENT.
35 // RFC 5758:
36 // ecdsa-with-SHA256 OBJECT IDENTIFIER ::= { iso(1) member-body(2)
37 // us(840) ansi-X9-62(10045) signatures(4) ecdsa-with-SHA2(3) 2 }
38 // ...
39 // When the ecdsa-with-SHA224, ecdsa-with-SHA256, ecdsa-with-SHA384, or
40 // ecdsa-with-SHA512 algorithm identifier appears in the algorithm field
41 // as an AlgorithmIdentifier, the encoding MUST omit the parameters
42 // field. That is, the AlgorithmIdentifier SHALL be a SEQUENCE of one
43 // component, the OID ecdsa-with-SHA224, ecdsa-with-SHA256, ecdsa-with-
44 // SHA384, or ecdsa-with-SHA512.
45 // See also RFC 5480, Appendix A.
46 const uint8 kECDSAWithSHA256AlgorithmID[] = {
47 0x30, 0x0a,
48 0x06, 0x08,
49 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02,
52 // The ECDSA public key of the variations server for verifying variations seed
53 // signatures.
54 const uint8_t kPublicKey[] = {
55 0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01,
56 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, 0x03, 0x42, 0x00,
57 0x04, 0x51, 0x7c, 0x31, 0x4b, 0x50, 0x42, 0xdd, 0x59, 0xda, 0x0b, 0xfa, 0x43,
58 0x44, 0x33, 0x7c, 0x5f, 0xa1, 0x0b, 0xd5, 0x82, 0xf6, 0xac, 0x04, 0x19, 0x72,
59 0x6c, 0x40, 0xd4, 0x3e, 0x56, 0xe2, 0xa0, 0x80, 0xa0, 0x41, 0xb3, 0x23, 0x7b,
60 0x71, 0xc9, 0x80, 0x87, 0xde, 0x35, 0x0d, 0x25, 0x71, 0x09, 0x7f, 0xb4, 0x15,
61 0x2b, 0xff, 0x82, 0x4d, 0xd3, 0xfe, 0xc5, 0xef, 0x20, 0xc6, 0xa3, 0x10, 0xbf,
64 // Note: UMA histogram enum - don't re-order or remove entries.
65 enum VariationSeedEmptyState {
66 VARIATIONS_SEED_NOT_EMPTY,
67 VARIATIONS_SEED_EMPTY,
68 VARIATIONS_SEED_CORRUPT,
69 VARIATIONS_SEED_INVALID_SIGNATURE,
70 VARIATIONS_SEED_CORRUPT_BASE64,
71 VARIATIONS_SEED_CORRUPT_PROTOBUF,
72 VARIATIONS_SEED_CORRUPT_GZIP,
73 VARIATIONS_SEED_EMPTY_ENUM_SIZE,
76 void RecordVariationSeedEmptyHistogram(VariationSeedEmptyState state) {
77 UMA_HISTOGRAM_ENUMERATION("Variations.SeedEmpty", state,
78 VARIATIONS_SEED_EMPTY_ENUM_SIZE);
81 enum VariationsSeedStoreResult {
82 VARIATIONS_SEED_STORE_SUCCESS,
83 VARIATIONS_SEED_STORE_FAILED_EMPTY,
84 VARIATIONS_SEED_STORE_FAILED_PARSE,
85 VARIATIONS_SEED_STORE_FAILED_SIGNATURE,
86 VARIATIONS_SEED_STORE_FAILED_GZIP,
87 // DELTA_COUNT is not so much a result of the seed store, but rather counting
88 // the number of delta-compressed seeds the SeedStore() function saw. Kept in
89 // the same histogram for convenience of comparing against the other values.
90 VARIATIONS_SEED_STORE_DELTA_COUNT,
91 VARIATIONS_SEED_STORE_FAILED_DELTA_READ_SEED,
92 VARIATIONS_SEED_STORE_FAILED_DELTA_APPLY,
93 VARIATIONS_SEED_STORE_FAILED_DELTA_STORE,
94 VARIATIONS_SEED_STORE_RESULT_ENUM_SIZE,
97 void RecordSeedStoreHistogram(VariationsSeedStoreResult result) {
98 UMA_HISTOGRAM_ENUMERATION("Variations.SeedStoreResult", result,
99 VARIATIONS_SEED_STORE_RESULT_ENUM_SIZE);
102 // Note: UMA histogram enum - don't re-order or remove entries.
103 enum VariationsSeedDateChangeState {
104 SEED_DATE_NO_OLD_DATE,
105 SEED_DATE_NEW_DATE_OLDER,
106 SEED_DATE_SAME_DAY,
107 SEED_DATE_NEW_DAY,
108 SEED_DATE_ENUM_SIZE,
111 // Truncates a time to the start of the day in UTC. If given a time representing
112 // 2014-03-11 10:18:03.1 UTC, it will return a time representing
113 // 2014-03-11 00:00:00.0 UTC.
114 base::Time TruncateToUTCDay(const base::Time& time) {
115 base::Time::Exploded exploded;
116 time.UTCExplode(&exploded);
117 exploded.hour = 0;
118 exploded.minute = 0;
119 exploded.second = 0;
120 exploded.millisecond = 0;
122 return base::Time::FromUTCExploded(exploded);
125 VariationsSeedDateChangeState GetSeedDateChangeState(
126 const base::Time& server_seed_date,
127 const base::Time& stored_seed_date) {
128 if (server_seed_date < stored_seed_date)
129 return SEED_DATE_NEW_DATE_OLDER;
131 if (TruncateToUTCDay(server_seed_date) !=
132 TruncateToUTCDay(stored_seed_date)) {
133 // The server date is earlier than the stored date, and they are from
134 // different UTC days, so |server_seed_date| is a valid new day.
135 return SEED_DATE_NEW_DAY;
137 return SEED_DATE_SAME_DAY;
140 } // namespace
142 VariationsSeedStore::VariationsSeedStore(PrefService* local_state)
143 : local_state_(local_state), seed_has_country_code_(false) {
146 VariationsSeedStore::~VariationsSeedStore() {
149 bool VariationsSeedStore::LoadSeed(variations::VariationsSeed* seed) {
150 invalid_base64_signature_.clear();
152 std::string seed_data;
153 if (!ReadSeedData(&seed_data))
154 return false;
156 const std::string base64_seed_signature =
157 local_state_->GetString(prefs::kVariationsSeedSignature);
158 const VerifySignatureResult result =
159 VerifySeedSignature(seed_data, base64_seed_signature);
160 if (result != VARIATIONS_SEED_SIGNATURE_ENUM_SIZE) {
161 UMA_HISTOGRAM_ENUMERATION("Variations.LoadSeedSignature", result,
162 VARIATIONS_SEED_SIGNATURE_ENUM_SIZE);
163 if (result != VARIATIONS_SEED_SIGNATURE_VALID) {
164 ClearPrefs();
165 RecordVariationSeedEmptyHistogram(VARIATIONS_SEED_INVALID_SIGNATURE);
166 // Record the invalid signature.
167 invalid_base64_signature_ = base64_seed_signature;
168 return false;
172 if (!seed->ParseFromString(seed_data)) {
173 ClearPrefs();
174 RecordVariationSeedEmptyHistogram(VARIATIONS_SEED_CORRUPT_PROTOBUF);
175 return false;
178 // Migrate any existing country code from the seed to the pref, if the pref is
179 // empty. TODO(asvitkine): Clean up the code in M50+ when sufficient number
180 // of clients have migrated.
181 if (seed->has_country_code() &&
182 local_state_->GetString(prefs::kVariationsCountry).empty()) {
183 local_state_->SetString(prefs::kVariationsCountry, seed->country_code());
185 variations_serial_number_ = seed->serial_number();
186 seed_has_country_code_ = seed->has_country_code();
187 RecordVariationSeedEmptyHistogram(VARIATIONS_SEED_NOT_EMPTY);
188 return true;
191 bool VariationsSeedStore::StoreSeedData(
192 const std::string& data,
193 const std::string& base64_seed_signature,
194 const std::string& country_code,
195 const base::Time& date_fetched,
196 bool is_delta_compressed,
197 variations::VariationsSeed* parsed_seed) {
198 if (!is_delta_compressed) {
199 const bool result =
200 StoreSeedDataNoDelta(data, base64_seed_signature, country_code,
201 date_fetched, parsed_seed);
202 if (result) {
203 UMA_HISTOGRAM_COUNTS_1000("Variations.StoreSeed.Size",
204 data.length() / 1024);
206 return result;
209 // If the data is delta compressed, first decode it.
210 DCHECK(invalid_base64_signature_.empty());
211 RecordSeedStoreHistogram(VARIATIONS_SEED_STORE_DELTA_COUNT);
213 std::string existing_seed_data;
214 std::string updated_seed_data;
215 if (!ReadSeedData(&existing_seed_data)) {
216 RecordSeedStoreHistogram(VARIATIONS_SEED_STORE_FAILED_DELTA_READ_SEED);
217 return false;
219 if (!ApplyDeltaPatch(existing_seed_data, data, &updated_seed_data)) {
220 RecordSeedStoreHistogram(VARIATIONS_SEED_STORE_FAILED_DELTA_APPLY);
221 return false;
224 const bool result =
225 StoreSeedDataNoDelta(updated_seed_data, base64_seed_signature,
226 country_code, date_fetched, parsed_seed);
227 if (result) {
228 // Note: |updated_seed_data.length()| is guaranteed to be non-zero, else
229 // result would be false.
230 int size_reduction = updated_seed_data.length() - data.length();
231 UMA_HISTOGRAM_PERCENTAGE("Variations.StoreSeed.DeltaSize.ReductionPercent",
232 100 * size_reduction / updated_seed_data.length());
233 UMA_HISTOGRAM_COUNTS_1000("Variations.StoreSeed.DeltaSize",
234 data.length() / 1024);
235 } else {
236 RecordSeedStoreHistogram(VARIATIONS_SEED_STORE_FAILED_DELTA_STORE);
238 return result;
241 void VariationsSeedStore::UpdateSeedDateAndLogDayChange(
242 const base::Time& server_date_fetched) {
243 VariationsSeedDateChangeState date_change = SEED_DATE_NO_OLD_DATE;
245 if (local_state_->HasPrefPath(prefs::kVariationsSeedDate)) {
246 const int64 stored_date_value =
247 local_state_->GetInt64(prefs::kVariationsSeedDate);
248 const base::Time stored_date =
249 base::Time::FromInternalValue(stored_date_value);
251 date_change = GetSeedDateChangeState(server_date_fetched, stored_date);
254 UMA_HISTOGRAM_ENUMERATION("Variations.SeedDateChange", date_change,
255 SEED_DATE_ENUM_SIZE);
257 local_state_->SetInt64(prefs::kVariationsSeedDate,
258 server_date_fetched.ToInternalValue());
261 std::string VariationsSeedStore::GetInvalidSignature() const {
262 return invalid_base64_signature_;
265 // static
266 void VariationsSeedStore::RegisterPrefs(PrefRegistrySimple* registry) {
267 registry->RegisterStringPref(prefs::kVariationsCompressedSeed, std::string());
268 registry->RegisterStringPref(prefs::kVariationsSeed, std::string());
269 registry->RegisterInt64Pref(prefs::kVariationsSeedDate,
270 base::Time().ToInternalValue());
271 registry->RegisterStringPref(prefs::kVariationsSeedSignature, std::string());
272 registry->RegisterStringPref(prefs::kVariationsCountry, std::string());
275 VariationsSeedStore::VerifySignatureResult
276 VariationsSeedStore::VerifySeedSignature(
277 const std::string& seed_bytes,
278 const std::string& base64_seed_signature) {
279 if (!SignatureVerificationEnabled())
280 return VARIATIONS_SEED_SIGNATURE_ENUM_SIZE;
282 if (base64_seed_signature.empty())
283 return VARIATIONS_SEED_SIGNATURE_MISSING;
285 std::string signature;
286 if (!base::Base64Decode(base64_seed_signature, &signature))
287 return VARIATIONS_SEED_SIGNATURE_DECODE_FAILED;
289 crypto::SignatureVerifier verifier;
290 if (!verifier.VerifyInit(
291 kECDSAWithSHA256AlgorithmID, sizeof(kECDSAWithSHA256AlgorithmID),
292 reinterpret_cast<const uint8*>(signature.data()), signature.size(),
293 kPublicKey, arraysize(kPublicKey))) {
294 return VARIATIONS_SEED_SIGNATURE_INVALID_SIGNATURE;
297 verifier.VerifyUpdate(reinterpret_cast<const uint8*>(seed_bytes.data()),
298 seed_bytes.size());
299 if (verifier.VerifyFinal())
300 return VARIATIONS_SEED_SIGNATURE_VALID;
301 return VARIATIONS_SEED_SIGNATURE_INVALID_SEED;
304 void VariationsSeedStore::ClearPrefs() {
305 local_state_->ClearPref(prefs::kVariationsCompressedSeed);
306 local_state_->ClearPref(prefs::kVariationsSeed);
307 local_state_->ClearPref(prefs::kVariationsSeedDate);
308 local_state_->ClearPref(prefs::kVariationsSeedSignature);
311 bool VariationsSeedStore::ReadSeedData(std::string* seed_data) {
312 std::string base64_seed_data =
313 local_state_->GetString(prefs::kVariationsCompressedSeed);
314 const bool is_compressed = !base64_seed_data.empty();
315 // If there's no compressed seed, fall back to the uncompressed one.
316 if (!is_compressed)
317 base64_seed_data = local_state_->GetString(prefs::kVariationsSeed);
319 if (base64_seed_data.empty()) {
320 RecordVariationSeedEmptyHistogram(VARIATIONS_SEED_EMPTY);
321 return false;
324 // If the decode process fails, assume the pref value is corrupt and clear it.
325 std::string decoded_data;
326 if (!base::Base64Decode(base64_seed_data, &decoded_data)) {
327 ClearPrefs();
328 RecordVariationSeedEmptyHistogram(VARIATIONS_SEED_CORRUPT_BASE64);
329 return false;
332 if (!is_compressed) {
333 seed_data->swap(decoded_data);
334 } else if (!compression::GzipUncompress(decoded_data, seed_data)) {
335 ClearPrefs();
336 RecordVariationSeedEmptyHistogram(VARIATIONS_SEED_CORRUPT_GZIP);
337 return false;
340 return true;
343 bool VariationsSeedStore::StoreSeedDataNoDelta(
344 const std::string& seed_data,
345 const std::string& base64_seed_signature,
346 const std::string& country_code,
347 const base::Time& date_fetched,
348 variations::VariationsSeed* parsed_seed) {
349 if (seed_data.empty()) {
350 RecordSeedStoreHistogram(VARIATIONS_SEED_STORE_FAILED_EMPTY);
351 return false;
354 // Only store the seed data if it parses correctly.
355 variations::VariationsSeed seed;
356 if (!seed.ParseFromString(seed_data)) {
357 RecordSeedStoreHistogram(VARIATIONS_SEED_STORE_FAILED_PARSE);
358 return false;
361 const VerifySignatureResult result =
362 VerifySeedSignature(seed_data, base64_seed_signature);
363 if (result != VARIATIONS_SEED_SIGNATURE_ENUM_SIZE) {
364 UMA_HISTOGRAM_ENUMERATION("Variations.StoreSeedSignature", result,
365 VARIATIONS_SEED_SIGNATURE_ENUM_SIZE);
366 if (result != VARIATIONS_SEED_SIGNATURE_VALID) {
367 RecordSeedStoreHistogram(VARIATIONS_SEED_STORE_FAILED_SIGNATURE);
368 return false;
372 // Compress the seed before base64-encoding and storing.
373 std::string compressed_seed_data;
374 if (!compression::GzipCompress(seed_data, &compressed_seed_data)) {
375 RecordSeedStoreHistogram(VARIATIONS_SEED_STORE_FAILED_GZIP);
376 return false;
379 std::string base64_seed_data;
380 base::Base64Encode(compressed_seed_data, &base64_seed_data);
382 // TODO(asvitkine): This pref is no longer being used. Remove it completely
383 // in M45+.
384 local_state_->ClearPref(prefs::kVariationsSeed);
386 // Update the saved country code only if one was returned from the server.
387 // Prefer the country code that was transmitted in the header over the one in
388 // the seed (which is deprecated).
389 if (!country_code.empty())
390 local_state_->SetString(prefs::kVariationsCountry, country_code);
391 else if (seed.has_country_code())
392 local_state_->SetString(prefs::kVariationsCountry, seed.country_code());
394 local_state_->SetString(prefs::kVariationsCompressedSeed, base64_seed_data);
395 UpdateSeedDateAndLogDayChange(date_fetched);
396 local_state_->SetString(prefs::kVariationsSeedSignature,
397 base64_seed_signature);
398 variations_serial_number_ = seed.serial_number();
399 if (parsed_seed)
400 seed.Swap(parsed_seed);
402 RecordSeedStoreHistogram(VARIATIONS_SEED_STORE_SUCCESS);
403 return true;
406 // static
407 bool VariationsSeedStore::ApplyDeltaPatch(const std::string& existing_data,
408 const std::string& patch,
409 std::string* output) {
410 output->clear();
412 google::protobuf::io::CodedInputStream in(
413 reinterpret_cast<const uint8*>(patch.data()), patch.length());
414 // Temporary string declared outside the loop so it can be re-used between
415 // different iterations (rather than allocating new ones).
416 std::string temp;
418 const uint32 existing_data_size = static_cast<uint32>(existing_data.size());
419 while (in.CurrentPosition() != static_cast<int>(patch.length())) {
420 uint32 value;
421 if (!in.ReadVarint32(&value))
422 return false;
424 if (value != 0) {
425 // A non-zero value indicates the number of bytes to copy from the patch
426 // stream to the output.
428 // No need to guard against bad data (i.e. very large |value|) because the
429 // call below will fail if |value| is greater than the size of the patch.
430 if (!in.ReadString(&temp, value))
431 return false;
432 output->append(temp);
433 } else {
434 // Otherwise, when it's zero, it indicates that it's followed by a pair of
435 // numbers - |offset| and |length| that specify a range of data to copy
436 // from |existing_data|.
437 uint32 offset;
438 uint32 length;
439 if (!in.ReadVarint32(&offset) || !in.ReadVarint32(&length))
440 return false;
442 // Check for |offset + length| being out of range and for overflow.
443 base::CheckedNumeric<uint32> end_offset(offset);
444 end_offset += length;
445 if (!end_offset.IsValid() || end_offset.ValueOrDie() > existing_data_size)
446 return false;
447 output->append(existing_data, offset, length);
450 return true;
453 } // namespace variations