Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(223)

Side by Side Diff: chrome/browser/metrics/variations/variations_seed_store.cc

Issue 1271123003: Componentize VariationsSeedStore and its unittest (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@componentize_variations_prefs
Patch Set: Created 5 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
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.
4
5 #include "chrome/browser/metrics/variations/variations_seed_store.h"
6
7 #include "base/base64.h"
8 #include "base/metrics/histogram_macros.h"
9 #include "base/prefs/pref_registry_simple.h"
10 #include "base/prefs/pref_service.h"
11 #include "base/sha1.h"
12 #include "base/strings/string_number_conversions.h"
13 #include "components/metrics/compression_utils.h"
14 #include "components/variations/pref_names.h"
15 #include "components/variations/proto/variations_seed.pb.h"
16 #include "crypto/signature_verifier.h"
17
18 namespace chrome_variations {
19
20 namespace {
21
22 // Signature verification is disabled on mobile platforms for now, since it
23 // adds about ~15ms to the startup time on mobile (vs. a couple ms on desktop).
24 bool SignatureVerificationEnabled() {
25 #if defined(OS_IOS) || defined(OS_ANDROID)
26 return false;
27 #else
28 return true;
29 #endif
30 }
31
32 // This is the algorithm ID for ECDSA with SHA-256. Parameters are ABSENT.
33 // RFC 5758:
34 // ecdsa-with-SHA256 OBJECT IDENTIFIER ::= { iso(1) member-body(2)
35 // us(840) ansi-X9-62(10045) signatures(4) ecdsa-with-SHA2(3) 2 }
36 // ...
37 // When the ecdsa-with-SHA224, ecdsa-with-SHA256, ecdsa-with-SHA384, or
38 // ecdsa-with-SHA512 algorithm identifier appears in the algorithm field
39 // as an AlgorithmIdentifier, the encoding MUST omit the parameters
40 // field. That is, the AlgorithmIdentifier SHALL be a SEQUENCE of one
41 // component, the OID ecdsa-with-SHA224, ecdsa-with-SHA256, ecdsa-with-
42 // SHA384, or ecdsa-with-SHA512.
43 // See also RFC 5480, Appendix A.
44 const uint8 kECDSAWithSHA256AlgorithmID[] = {
45 0x30, 0x0a,
46 0x06, 0x08,
47 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02,
48 };
49
50 // The ECDSA public key of the variations server for verifying variations seed
51 // signatures.
52 const uint8_t kPublicKey[] = {
53 0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01,
54 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, 0x03, 0x42, 0x00,
55 0x04, 0x51, 0x7c, 0x31, 0x4b, 0x50, 0x42, 0xdd, 0x59, 0xda, 0x0b, 0xfa, 0x43,
56 0x44, 0x33, 0x7c, 0x5f, 0xa1, 0x0b, 0xd5, 0x82, 0xf6, 0xac, 0x04, 0x19, 0x72,
57 0x6c, 0x40, 0xd4, 0x3e, 0x56, 0xe2, 0xa0, 0x80, 0xa0, 0x41, 0xb3, 0x23, 0x7b,
58 0x71, 0xc9, 0x80, 0x87, 0xde, 0x35, 0x0d, 0x25, 0x71, 0x09, 0x7f, 0xb4, 0x15,
59 0x2b, 0xff, 0x82, 0x4d, 0xd3, 0xfe, 0xc5, 0xef, 0x20, 0xc6, 0xa3, 0x10, 0xbf,
60 };
61
62 // Note: UMA histogram enum - don't re-order or remove entries.
63 enum VariationSeedEmptyState {
64 VARIATIONS_SEED_NOT_EMPTY,
65 VARIATIONS_SEED_EMPTY,
66 VARIATIONS_SEED_CORRUPT,
67 VARIATIONS_SEED_INVALID_SIGNATURE,
68 VARIATIONS_SEED_CORRUPT_BASE64,
69 VARIATIONS_SEED_CORRUPT_PROTOBUF,
70 VARIATIONS_SEED_CORRUPT_GZIP,
71 VARIATIONS_SEED_EMPTY_ENUM_SIZE,
72 };
73
74 void RecordVariationSeedEmptyHistogram(VariationSeedEmptyState state) {
75 UMA_HISTOGRAM_ENUMERATION("Variations.SeedEmpty", state,
76 VARIATIONS_SEED_EMPTY_ENUM_SIZE);
77 }
78
79 enum VariationsSeedStoreResult {
80 VARIATIONS_SEED_STORE_SUCCESS,
81 VARIATIONS_SEED_STORE_FAILED_EMPTY,
82 VARIATIONS_SEED_STORE_FAILED_PARSE,
83 VARIATIONS_SEED_STORE_FAILED_SIGNATURE,
84 VARIATIONS_SEED_STORE_FAILED_GZIP,
85 VARIATIONS_SEED_STORE_RESULT_ENUM_SIZE,
86 };
87
88 void RecordVariationsSeedStoreHistogram(VariationsSeedStoreResult result) {
89 UMA_HISTOGRAM_ENUMERATION("Variations.SeedStoreResult", result,
90 VARIATIONS_SEED_STORE_RESULT_ENUM_SIZE);
91 }
92
93 // Note: UMA histogram enum - don't re-order or remove entries.
94 enum VariationsSeedDateChangeState {
95 SEED_DATE_NO_OLD_DATE,
96 SEED_DATE_NEW_DATE_OLDER,
97 SEED_DATE_SAME_DAY,
98 SEED_DATE_NEW_DAY,
99 SEED_DATE_ENUM_SIZE,
100 };
101
102 // Truncates a time to the start of the day in UTC. If given a time representing
103 // 2014-03-11 10:18:03.1 UTC, it will return a time representing
104 // 2014-03-11 00:00:00.0 UTC.
105 base::Time TruncateToUTCDay(const base::Time& time) {
106 base::Time::Exploded exploded;
107 time.UTCExplode(&exploded);
108 exploded.hour = 0;
109 exploded.minute = 0;
110 exploded.second = 0;
111 exploded.millisecond = 0;
112
113 return base::Time::FromUTCExploded(exploded);
114 }
115
116 VariationsSeedDateChangeState GetSeedDateChangeState(
117 const base::Time& server_seed_date,
118 const base::Time& stored_seed_date) {
119 if (server_seed_date < stored_seed_date)
120 return SEED_DATE_NEW_DATE_OLDER;
121
122 if (TruncateToUTCDay(server_seed_date) !=
123 TruncateToUTCDay(stored_seed_date)) {
124 // The server date is earlier than the stored date, and they are from
125 // different UTC days, so |server_seed_date| is a valid new day.
126 return SEED_DATE_NEW_DAY;
127 }
128 return SEED_DATE_SAME_DAY;
129 }
130
131 } // namespace
132
133 VariationsSeedStore::VariationsSeedStore(PrefService* local_state)
134 : local_state_(local_state) {
135 }
136
137 VariationsSeedStore::~VariationsSeedStore() {
138 }
139
140 bool VariationsSeedStore::LoadSeed(variations::VariationsSeed* seed) {
141 invalid_base64_signature_.clear();
142
143 std::string seed_data;
144 if (!ReadSeedData(&seed_data))
145 return false;
146
147 const std::string base64_seed_signature =
148 local_state_->GetString(metrics::prefs::kVariationsSeedSignature);
149 const VerifySignatureResult result =
150 VerifySeedSignature(seed_data, base64_seed_signature);
151 if (result != VARIATIONS_SEED_SIGNATURE_ENUM_SIZE) {
152 UMA_HISTOGRAM_ENUMERATION("Variations.LoadSeedSignature", result,
153 VARIATIONS_SEED_SIGNATURE_ENUM_SIZE);
154 if (result != VARIATIONS_SEED_SIGNATURE_VALID) {
155 ClearPrefs();
156 RecordVariationSeedEmptyHistogram(VARIATIONS_SEED_INVALID_SIGNATURE);
157 // Record the invalid signature.
158 invalid_base64_signature_ = base64_seed_signature;
159 return false;
160 }
161 }
162
163 if (!seed->ParseFromString(seed_data)) {
164 ClearPrefs();
165 RecordVariationSeedEmptyHistogram(VARIATIONS_SEED_CORRUPT_PROTOBUF);
166 return false;
167 }
168
169 // Migrate any existing country code from the seed to the pref, if the pref is
170 // empty. TODO(asvitkine): Clean up the code in M50+ when sufficient number
171 // of clients have migrated.
172 if (seed->has_country_code() &&
173 local_state_->GetString(metrics::prefs::kVariationsCountry).empty()) {
174 local_state_->SetString(metrics::prefs::kVariationsCountry,
175 seed->country_code());
176 }
177 variations_serial_number_ = seed->serial_number();
178 RecordVariationSeedEmptyHistogram(VARIATIONS_SEED_NOT_EMPTY);
179 return true;
180 }
181
182 bool VariationsSeedStore::StoreSeedData(
183 const std::string& seed_data,
184 const std::string& base64_seed_signature,
185 const base::Time& date_fetched,
186 variations::VariationsSeed* parsed_seed) {
187 if (seed_data.empty()) {
188 RecordVariationsSeedStoreHistogram(VARIATIONS_SEED_STORE_FAILED_EMPTY);
189 return false;
190 }
191
192 // Only store the seed data if it parses correctly.
193 variations::VariationsSeed seed;
194 if (!seed.ParseFromString(seed_data)) {
195 RecordVariationsSeedStoreHistogram(VARIATIONS_SEED_STORE_FAILED_PARSE);
196 return false;
197 }
198
199 const VerifySignatureResult result =
200 VerifySeedSignature(seed_data, base64_seed_signature);
201 if (result != VARIATIONS_SEED_SIGNATURE_ENUM_SIZE) {
202 UMA_HISTOGRAM_ENUMERATION("Variations.StoreSeedSignature", result,
203 VARIATIONS_SEED_SIGNATURE_ENUM_SIZE);
204 if (result != VARIATIONS_SEED_SIGNATURE_VALID) {
205 RecordVariationsSeedStoreHistogram(
206 VARIATIONS_SEED_STORE_FAILED_SIGNATURE);
207 return false;
208 }
209 }
210
211 // Compress the seed before base64-encoding and storing.
212 std::string compressed_seed_data;
213 if (!metrics::GzipCompress(seed_data, &compressed_seed_data)) {
214 RecordVariationsSeedStoreHistogram(VARIATIONS_SEED_STORE_FAILED_GZIP);
215 return false;
216 }
217
218 std::string base64_seed_data;
219 base::Base64Encode(compressed_seed_data, &base64_seed_data);
220
221 // TODO(asvitkine): This pref is no longer being used. Remove it completely
222 // in M45+.
223 local_state_->ClearPref(metrics::prefs::kVariationsSeed);
224
225 // Update the saved country code only if one was returned from the server.
226 if (seed.has_country_code())
227 local_state_->SetString(metrics::prefs::kVariationsCountry,
228 seed.country_code());
229
230 local_state_->SetString(metrics::prefs::kVariationsCompressedSeed,
231 base64_seed_data);
232 UpdateSeedDateAndLogDayChange(date_fetched);
233 local_state_->SetString(metrics::prefs::kVariationsSeedSignature,
234 base64_seed_signature);
235 variations_serial_number_ = seed.serial_number();
236 if (parsed_seed)
237 seed.Swap(parsed_seed);
238
239 RecordVariationsSeedStoreHistogram(VARIATIONS_SEED_STORE_SUCCESS);
240 return true;
241 }
242
243 void VariationsSeedStore::UpdateSeedDateAndLogDayChange(
244 const base::Time& server_date_fetched) {
245 VariationsSeedDateChangeState date_change = SEED_DATE_NO_OLD_DATE;
246
247 if (local_state_->HasPrefPath(metrics::prefs::kVariationsSeedDate)) {
248 const int64 stored_date_value =
249 local_state_->GetInt64(metrics::prefs::kVariationsSeedDate);
250 const base::Time stored_date =
251 base::Time::FromInternalValue(stored_date_value);
252
253 date_change = GetSeedDateChangeState(server_date_fetched, stored_date);
254 }
255
256 UMA_HISTOGRAM_ENUMERATION("Variations.SeedDateChange", date_change,
257 SEED_DATE_ENUM_SIZE);
258
259 local_state_->SetInt64(metrics::prefs::kVariationsSeedDate,
260 server_date_fetched.ToInternalValue());
261 }
262
263 // static
264 void VariationsSeedStore::RegisterPrefs(PrefRegistrySimple* registry) {
265 registry->RegisterStringPref(metrics::prefs::kVariationsCompressedSeed,
266 std::string());
267 registry->RegisterStringPref(metrics::prefs::kVariationsSeed, std::string());
268 registry->RegisterInt64Pref(metrics::prefs::kVariationsSeedDate,
269 base::Time().ToInternalValue());
270 registry->RegisterStringPref(metrics::prefs::kVariationsSeedSignature,
271 std::string());
272 registry->RegisterStringPref(metrics::prefs::kVariationsCountry,
273 std::string());
274 }
275
276 void VariationsSeedStore::ClearPrefs() {
277 local_state_->ClearPref(metrics::prefs::kVariationsCompressedSeed);
278 local_state_->ClearPref(metrics::prefs::kVariationsSeed);
279 local_state_->ClearPref(metrics::prefs::kVariationsSeedDate);
280 local_state_->ClearPref(metrics::prefs::kVariationsSeedSignature);
281 }
282
283 bool VariationsSeedStore::ReadSeedData(std::string* seed_data) {
284 std::string base64_seed_data =
285 local_state_->GetString(metrics::prefs::kVariationsCompressedSeed);
286 const bool is_compressed = !base64_seed_data.empty();
287 // If there's no compressed seed, fall back to the uncompressed one.
288 if (!is_compressed)
289 base64_seed_data = local_state_->GetString(metrics::prefs::kVariationsSeed);
290
291 if (base64_seed_data.empty()) {
292 RecordVariationSeedEmptyHistogram(VARIATIONS_SEED_EMPTY);
293 return false;
294 }
295
296 // If the decode process fails, assume the pref value is corrupt and clear it.
297 std::string decoded_data;
298 if (!base::Base64Decode(base64_seed_data, &decoded_data)) {
299 ClearPrefs();
300 RecordVariationSeedEmptyHistogram(VARIATIONS_SEED_CORRUPT_BASE64);
301 return false;
302 }
303
304 if (!is_compressed) {
305 seed_data->swap(decoded_data);
306 } else if (!metrics::GzipUncompress(decoded_data, seed_data)) {
307 ClearPrefs();
308 RecordVariationSeedEmptyHistogram(VARIATIONS_SEED_CORRUPT_GZIP);
309 return false;
310 }
311
312 return true;
313 }
314
315 VariationsSeedStore::VerifySignatureResult
316 VariationsSeedStore::VerifySeedSignature(
317 const std::string& seed_bytes,
318 const std::string& base64_seed_signature) {
319 if (!SignatureVerificationEnabled())
320 return VARIATIONS_SEED_SIGNATURE_ENUM_SIZE;
321
322 if (base64_seed_signature.empty())
323 return VARIATIONS_SEED_SIGNATURE_MISSING;
324
325 std::string signature;
326 if (!base::Base64Decode(base64_seed_signature, &signature))
327 return VARIATIONS_SEED_SIGNATURE_DECODE_FAILED;
328
329 crypto::SignatureVerifier verifier;
330 if (!verifier.VerifyInit(
331 kECDSAWithSHA256AlgorithmID, sizeof(kECDSAWithSHA256AlgorithmID),
332 reinterpret_cast<const uint8*>(signature.data()), signature.size(),
333 kPublicKey, arraysize(kPublicKey))) {
334 return VARIATIONS_SEED_SIGNATURE_INVALID_SIGNATURE;
335 }
336
337 verifier.VerifyUpdate(reinterpret_cast<const uint8*>(seed_bytes.data()),
338 seed_bytes.size());
339 if (verifier.VerifyFinal())
340 return VARIATIONS_SEED_SIGNATURE_VALID;
341 return VARIATIONS_SEED_SIGNATURE_INVALID_SEED;
342 }
343
344 std::string VariationsSeedStore::GetInvalidSignature() const {
345 return invalid_base64_signature_;
346 }
347
348 } // namespace chrome_variations
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698