OLD | NEW |
---|---|
1 // Copyright 2014 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "chrome/browser/metrics/variations/variations_seed_store.h" | 5 #include "chrome/browser/metrics/variations/variations_seed_store.h" |
6 | 6 |
7 #include "base/base64.h" | 7 #include "base/base64.h" |
8 #include "base/metrics/histogram.h" | 8 #include "base/metrics/histogram.h" |
9 #include "base/prefs/pref_registry_simple.h" | 9 #include "base/prefs/pref_registry_simple.h" |
10 #include "base/prefs/pref_service.h" | 10 #include "base/prefs/pref_service.h" |
11 #include "base/sha1.h" | 11 #include "base/sha1.h" |
12 #include "base/strings/string_number_conversions.h" | 12 #include "base/strings/string_number_conversions.h" |
13 #include "chrome/common/pref_names.h" | 13 #include "chrome/common/pref_names.h" |
14 #include "components/variations/proto/variations_seed.pb.h" | 14 #include "components/variations/proto/variations_seed.pb.h" |
15 #include "crypto/signature_verifier.h" | 15 #include "crypto/signature_verifier.h" |
16 | 16 |
17 namespace chrome_variations { | 17 namespace chrome_variations { |
18 | 18 |
19 namespace { | 19 namespace { |
20 | 20 |
21 // Computes a hash of the serialized variations seed data. | |
22 // TODO(asvitkine): Remove this once the seed signature is ubiquitous. | |
23 std::string HashSeed(const std::string& seed_data) { | |
24 const std::string sha1 = base::SHA1HashString(seed_data); | |
25 return base::HexEncode(sha1.data(), sha1.size()); | |
26 } | |
27 | |
28 // Signature verification is disabled on mobile platforms for now, since it | 21 // Signature verification is disabled on mobile platforms for now, since it |
29 // adds about ~15ms to the startup time on mobile (vs. a couple ms on desktop). | 22 // adds about ~15ms to the startup time on mobile (vs. a couple ms on desktop). |
30 bool SignatureVerificationEnabled() { | 23 bool SignatureVerificationEnabled() { |
31 #if defined(OS_IOS) || defined(OS_ANDROID) | 24 #if defined(OS_IOS) || defined(OS_ANDROID) |
32 return false; | 25 return false; |
33 #else | 26 #else |
34 return true; | 27 return true; |
35 #endif | 28 #endif |
36 } | 29 } |
37 | 30 |
(...skipping 21 matching lines...) Expand all Loading... | |
59 0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, | 52 0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, |
60 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, 0x03, 0x42, 0x00, | 53 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, 0x03, 0x42, 0x00, |
61 0x04, 0x51, 0x7c, 0x31, 0x4b, 0x50, 0x42, 0xdd, 0x59, 0xda, 0x0b, 0xfa, 0x43, | 54 0x04, 0x51, 0x7c, 0x31, 0x4b, 0x50, 0x42, 0xdd, 0x59, 0xda, 0x0b, 0xfa, 0x43, |
62 0x44, 0x33, 0x7c, 0x5f, 0xa1, 0x0b, 0xd5, 0x82, 0xf6, 0xac, 0x04, 0x19, 0x72, | 55 0x44, 0x33, 0x7c, 0x5f, 0xa1, 0x0b, 0xd5, 0x82, 0xf6, 0xac, 0x04, 0x19, 0x72, |
63 0x6c, 0x40, 0xd4, 0x3e, 0x56, 0xe2, 0xa0, 0x80, 0xa0, 0x41, 0xb3, 0x23, 0x7b, | 56 0x6c, 0x40, 0xd4, 0x3e, 0x56, 0xe2, 0xa0, 0x80, 0xa0, 0x41, 0xb3, 0x23, 0x7b, |
64 0x71, 0xc9, 0x80, 0x87, 0xde, 0x35, 0x0d, 0x25, 0x71, 0x09, 0x7f, 0xb4, 0x15, | 57 0x71, 0xc9, 0x80, 0x87, 0xde, 0x35, 0x0d, 0x25, 0x71, 0x09, 0x7f, 0xb4, 0x15, |
65 0x2b, 0xff, 0x82, 0x4d, 0xd3, 0xfe, 0xc5, 0xef, 0x20, 0xc6, 0xa3, 0x10, 0xbf, | 58 0x2b, 0xff, 0x82, 0x4d, 0xd3, 0xfe, 0xc5, 0xef, 0x20, 0xc6, 0xa3, 0x10, 0xbf, |
66 }; | 59 }; |
67 | 60 |
68 // Note: UMA histogram enum - don't re-order or remove entries. | 61 // Note: UMA histogram enum - don't re-order or remove entries. |
69 enum VariationSeedSignatureState { | |
70 VARIATIONS_SEED_SIGNATURE_MISSING, | |
71 VARIATIONS_SEED_SIGNATURE_DECODE_FAILED, | |
72 VARIATIONS_SEED_SIGNATURE_INVALID_SIGNATURE, | |
73 VARIATIONS_SEED_SIGNATURE_INVALID_SEED, | |
74 VARIATIONS_SEED_SIGNATURE_VALID, | |
75 VARIATIONS_SEED_SIGNATURE_ENUM_SIZE, | |
76 }; | |
77 | |
78 // Verifies a variations seed (the serialized proto bytes) with the specified | |
79 // base-64 encoded signate that was received from the server and returns the | |
80 // result. The signature is assumed to be an "ECDSA with SHA-256" signature | |
81 // (see kECDSAWithSHA256AlgorithmID above). | |
82 VariationSeedSignatureState VerifySeedSignature( | |
83 const std::string& seed_bytes, | |
84 const std::string& base64_seed_signature) { | |
85 if (base64_seed_signature.empty()) | |
86 return VARIATIONS_SEED_SIGNATURE_MISSING; | |
87 | |
88 std::string signature; | |
89 if (!base::Base64Decode(base64_seed_signature, &signature)) | |
90 return VARIATIONS_SEED_SIGNATURE_DECODE_FAILED; | |
91 | |
92 crypto::SignatureVerifier verifier; | |
93 if (!verifier.VerifyInit( | |
94 kECDSAWithSHA256AlgorithmID, sizeof(kECDSAWithSHA256AlgorithmID), | |
95 reinterpret_cast<const uint8*>(signature.data()), signature.size(), | |
96 kPublicKey, arraysize(kPublicKey))) { | |
97 return VARIATIONS_SEED_SIGNATURE_INVALID_SIGNATURE; | |
98 } | |
99 | |
100 verifier.VerifyUpdate(reinterpret_cast<const uint8*>(seed_bytes.data()), | |
101 seed_bytes.size()); | |
102 if (verifier.VerifyFinal()) | |
103 return VARIATIONS_SEED_SIGNATURE_VALID; | |
104 return VARIATIONS_SEED_SIGNATURE_INVALID_SEED; | |
105 } | |
106 | |
107 // Note: UMA histogram enum - don't re-order or remove entries. | |
108 enum VariationSeedEmptyState { | 62 enum VariationSeedEmptyState { |
109 VARIATIONS_SEED_NOT_EMPTY, | 63 VARIATIONS_SEED_NOT_EMPTY, |
110 VARIATIONS_SEED_EMPTY, | 64 VARIATIONS_SEED_EMPTY, |
111 VARIATIONS_SEED_CORRUPT, | 65 VARIATIONS_SEED_CORRUPT, |
66 VARIATIONS_SEED_INVALID_SIGNATURE, | |
112 VARIATIONS_SEED_EMPTY_ENUM_SIZE, | 67 VARIATIONS_SEED_EMPTY_ENUM_SIZE, |
113 }; | 68 }; |
114 | 69 |
115 void RecordVariationSeedEmptyHistogram(VariationSeedEmptyState state) { | 70 void RecordVariationSeedEmptyHistogram(VariationSeedEmptyState state) { |
116 UMA_HISTOGRAM_ENUMERATION("Variations.SeedEmpty", state, | 71 UMA_HISTOGRAM_ENUMERATION("Variations.SeedEmpty", state, |
117 VARIATIONS_SEED_EMPTY_ENUM_SIZE); | 72 VARIATIONS_SEED_EMPTY_ENUM_SIZE); |
118 } | 73 } |
119 | 74 |
120 } // namespace | 75 } // namespace |
121 | 76 |
122 VariationsSeedStore::VariationsSeedStore(PrefService* local_state) | 77 VariationsSeedStore::VariationsSeedStore(PrefService* local_state) |
123 : local_state_(local_state) { | 78 : local_state_(local_state) { |
124 } | 79 } |
125 | 80 |
126 VariationsSeedStore::~VariationsSeedStore() { | 81 VariationsSeedStore::~VariationsSeedStore() { |
127 } | 82 } |
128 | 83 |
129 bool VariationsSeedStore::LoadSeed(VariationsSeed* seed) { | 84 bool VariationsSeedStore::LoadSeed(VariationsSeed* seed) { |
130 const std::string base64_seed_data = | 85 const std::string base64_seed_data = |
131 local_state_->GetString(prefs::kVariationsSeed); | 86 local_state_->GetString(prefs::kVariationsSeed); |
132 if (base64_seed_data.empty()) { | 87 if (base64_seed_data.empty()) { |
133 RecordVariationSeedEmptyHistogram(VARIATIONS_SEED_EMPTY); | 88 RecordVariationSeedEmptyHistogram(VARIATIONS_SEED_EMPTY); |
134 return false; | 89 return false; |
135 } | 90 } |
136 | 91 |
137 const std::string hash_from_pref = | |
138 local_state_->GetString(prefs::kVariationsSeedHash); | |
139 // If the decode process fails, assume the pref value is corrupt and clear it. | 92 // If the decode process fails, assume the pref value is corrupt and clear it. |
140 std::string seed_data; | 93 std::string seed_data; |
141 if (!base::Base64Decode(base64_seed_data, &seed_data) || | 94 if (!base::Base64Decode(base64_seed_data, &seed_data) || |
142 (!hash_from_pref.empty() && HashSeed(seed_data) != hash_from_pref) || | |
143 !seed->ParseFromString(seed_data)) { | 95 !seed->ParseFromString(seed_data)) { |
144 VLOG(1) << "Variations seed data in local pref is corrupt, clearing the " | 96 VLOG(1) << "Variations seed data in local pref is corrupt, clearing the " |
145 << "pref."; | 97 << "pref."; |
146 ClearPrefs(); | 98 ClearPrefs(); |
147 RecordVariationSeedEmptyHistogram(VARIATIONS_SEED_CORRUPT); | 99 RecordVariationSeedEmptyHistogram(VARIATIONS_SEED_CORRUPT); |
148 return false; | 100 return false; |
149 } | 101 } |
150 | 102 |
151 if (SignatureVerificationEnabled()) { | 103 const std::string base64_seed_signature = |
152 const std::string base64_seed_signature = | 104 local_state_->GetString(prefs::kVariationsSeedSignature); |
153 local_state_->GetString(prefs::kVariationsSeedSignature); | 105 const VerifySignatureResult result = |
154 const VariationSeedSignatureState signature_state = | 106 VerifySeedSignature(seed_data, base64_seed_signature); |
155 VerifySeedSignature(seed_data, base64_seed_signature); | 107 if (result != VARIATIONS_SEED_SIGNATURE_ENUM_SIZE) { |
156 UMA_HISTOGRAM_ENUMERATION("Variations.LoadSeedSignature", signature_state, | 108 UMA_HISTOGRAM_ENUMERATION("Variations.LoadSeedSignature", result, |
157 VARIATIONS_SEED_SIGNATURE_ENUM_SIZE); | 109 VARIATIONS_SEED_SIGNATURE_ENUM_SIZE); |
110 if (result != VARIATIONS_SEED_SIGNATURE_VALID) { | |
111 VLOG(1) << "Variations seed signature in local pref missing or invalid " | |
jwd
2014/03/05 17:56:27
Do these need to be VLOGs?
Alexei Svitkine (slow)
2014/03/05 18:21:43
I've followed the convention that's used in this f
jwd
2014/03/05 18:36:00
Ah, didn't notice the convention. I was thinking D
| |
112 << "with result: " << result << ". Clearing the pref."; | |
113 ClearPrefs(); | |
114 RecordVariationSeedEmptyHistogram(VARIATIONS_SEED_INVALID_SIGNATURE); | |
115 return false; | |
116 } | |
158 } | 117 } |
159 | 118 |
160 variations_serial_number_ = seed->serial_number(); | 119 variations_serial_number_ = seed->serial_number(); |
161 RecordVariationSeedEmptyHistogram(VARIATIONS_SEED_NOT_EMPTY); | 120 RecordVariationSeedEmptyHistogram(VARIATIONS_SEED_NOT_EMPTY); |
162 return true; | 121 return true; |
163 } | 122 } |
164 | 123 |
165 bool VariationsSeedStore::StoreSeedData( | 124 bool VariationsSeedStore::StoreSeedData( |
166 const std::string& seed_data, | 125 const std::string& seed_data, |
167 const std::string& base64_seed_signature, | 126 const std::string& base64_seed_signature, |
168 const base::Time& date_fetched) { | 127 const base::Time& date_fetched) { |
169 if (seed_data.empty()) { | 128 if (seed_data.empty()) { |
170 VLOG(1) << "Variations seed data is empty, rejecting the seed."; | 129 VLOG(1) << "Variations seed data is empty, rejecting the seed."; |
171 return false; | 130 return false; |
172 } | 131 } |
173 | 132 |
174 // Only store the seed data if it parses correctly. | 133 // Only store the seed data if it parses correctly. |
175 VariationsSeed seed; | 134 VariationsSeed seed; |
176 if (!seed.ParseFromString(seed_data)) { | 135 if (!seed.ParseFromString(seed_data)) { |
177 VLOG(1) << "Variations seed data is not in valid proto format, " | 136 VLOG(1) << "Variations seed data is not in valid proto format, " |
178 << "rejecting the seed."; | 137 << "rejecting the seed."; |
179 return false; | 138 return false; |
180 } | 139 } |
181 | 140 |
182 if (SignatureVerificationEnabled()) { | 141 const VerifySignatureResult result = |
183 const VariationSeedSignatureState signature_state = | 142 VerifySeedSignature(seed_data, base64_seed_signature); |
184 VerifySeedSignature(seed_data, base64_seed_signature); | 143 if (result != VARIATIONS_SEED_SIGNATURE_ENUM_SIZE) { |
185 UMA_HISTOGRAM_ENUMERATION("Variations.StoreSeedSignature", signature_state, | 144 UMA_HISTOGRAM_ENUMERATION("Variations.StoreSeedSignature", result, |
186 VARIATIONS_SEED_SIGNATURE_ENUM_SIZE); | 145 VARIATIONS_SEED_SIGNATURE_ENUM_SIZE); |
146 if (result != VARIATIONS_SEED_SIGNATURE_VALID) { | |
147 VLOG(1) << "Variations seed signature missing or invalid with result: " | |
148 << result << ". Rejecting the seed."; | |
149 return false; | |
150 } | |
187 } | 151 } |
188 | 152 |
189 std::string base64_seed_data; | 153 std::string base64_seed_data; |
190 base::Base64Encode(seed_data, &base64_seed_data); | 154 base::Base64Encode(seed_data, &base64_seed_data); |
191 | 155 |
156 // TODO(asvitkine): This pref is no longer being used. Remove it completely | |
157 // in a couple of releases. | |
158 local_state_->ClearPref(prefs::kVariationsSeedHash); | |
159 | |
192 local_state_->SetString(prefs::kVariationsSeed, base64_seed_data); | 160 local_state_->SetString(prefs::kVariationsSeed, base64_seed_data); |
193 local_state_->SetString(prefs::kVariationsSeedHash, HashSeed(seed_data)); | |
194 local_state_->SetInt64(prefs::kVariationsSeedDate, | 161 local_state_->SetInt64(prefs::kVariationsSeedDate, |
195 date_fetched.ToInternalValue()); | 162 date_fetched.ToInternalValue()); |
196 local_state_->SetString(prefs::kVariationsSeedSignature, | 163 local_state_->SetString(prefs::kVariationsSeedSignature, |
197 base64_seed_signature); | 164 base64_seed_signature); |
198 variations_serial_number_ = seed.serial_number(); | 165 variations_serial_number_ = seed.serial_number(); |
199 | 166 |
200 return true; | 167 return true; |
201 } | 168 } |
202 | 169 |
203 // static | 170 // static |
204 void VariationsSeedStore::RegisterPrefs(PrefRegistrySimple* registry) { | 171 void VariationsSeedStore::RegisterPrefs(PrefRegistrySimple* registry) { |
205 registry->RegisterStringPref(prefs::kVariationsSeed, std::string()); | 172 registry->RegisterStringPref(prefs::kVariationsSeed, std::string()); |
206 registry->RegisterStringPref(prefs::kVariationsSeedHash, std::string()); | 173 registry->RegisterStringPref(prefs::kVariationsSeedHash, std::string()); |
207 registry->RegisterInt64Pref(prefs::kVariationsSeedDate, | 174 registry->RegisterInt64Pref(prefs::kVariationsSeedDate, |
208 base::Time().ToInternalValue()); | 175 base::Time().ToInternalValue()); |
209 registry->RegisterStringPref(prefs::kVariationsSeedSignature, std::string()); | 176 registry->RegisterStringPref(prefs::kVariationsSeedSignature, std::string()); |
210 } | 177 } |
211 | 178 |
212 void VariationsSeedStore::ClearPrefs() { | 179 void VariationsSeedStore::ClearPrefs() { |
213 local_state_->ClearPref(prefs::kVariationsSeed); | 180 local_state_->ClearPref(prefs::kVariationsSeed); |
214 local_state_->ClearPref(prefs::kVariationsSeedDate); | 181 local_state_->ClearPref(prefs::kVariationsSeedDate); |
215 local_state_->ClearPref(prefs::kVariationsSeedHash); | 182 local_state_->ClearPref(prefs::kVariationsSeedHash); |
216 local_state_->ClearPref(prefs::kVariationsSeedSignature); | 183 local_state_->ClearPref(prefs::kVariationsSeedSignature); |
217 } | 184 } |
218 | 185 |
186 VariationsSeedStore::VerifySignatureResult | |
187 VariationsSeedStore::VerifySeedSignature( | |
jwd
2014/03/05 17:56:27
Can this be tested? What about having a testing ke
Alexei Svitkine (slow)
2014/03/05 18:21:43
A testing key pair would require passing the publi
jwd
2014/03/05 18:36:00
Base-64 blob seems fine then.
Alexei Svitkine (slow)
2014/03/06 18:16:07
Done. Added checking for all the possible return v
| |
188 const std::string& seed_bytes, | |
189 const std::string& base64_seed_signature) { | |
190 if (!SignatureVerificationEnabled()) | |
191 return VARIATIONS_SEED_SIGNATURE_ENUM_SIZE; | |
192 | |
193 if (base64_seed_signature.empty()) | |
194 return VARIATIONS_SEED_SIGNATURE_MISSING; | |
195 | |
196 std::string signature; | |
197 if (!base::Base64Decode(base64_seed_signature, &signature)) | |
198 return VARIATIONS_SEED_SIGNATURE_DECODE_FAILED; | |
199 | |
200 crypto::SignatureVerifier verifier; | |
201 if (!verifier.VerifyInit( | |
202 kECDSAWithSHA256AlgorithmID, sizeof(kECDSAWithSHA256AlgorithmID), | |
203 reinterpret_cast<const uint8*>(signature.data()), signature.size(), | |
204 kPublicKey, arraysize(kPublicKey))) { | |
205 return VARIATIONS_SEED_SIGNATURE_INVALID_SIGNATURE; | |
206 } | |
207 | |
208 verifier.VerifyUpdate(reinterpret_cast<const uint8*>(seed_bytes.data()), | |
209 seed_bytes.size()); | |
210 if (verifier.VerifyFinal()) | |
211 return VARIATIONS_SEED_SIGNATURE_VALID; | |
212 return VARIATIONS_SEED_SIGNATURE_INVALID_SEED; | |
213 } | |
214 | |
219 } // namespace chrome_variations | 215 } // namespace chrome_variations |
OLD | NEW |