OLD | NEW |
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 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/net/transport_security_persister.h" | 5 #include "chrome/browser/net/transport_security_persister.h" |
6 | 6 |
7 #include "base/base64.h" | 7 #include "base/base64.h" |
8 #include "base/bind.h" | 8 #include "base/bind.h" |
9 #include "base/file_util.h" | 9 #include "base/file_util.h" |
10 #include "base/files/file_path.h" | 10 #include "base/files/file_path.h" |
(...skipping 141 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
152 DCHECK_EQ(transport_security_state_, state); | 152 DCHECK_EQ(transport_security_state_, state); |
153 | 153 |
154 if (!readonly_) | 154 if (!readonly_) |
155 writer_.ScheduleWrite(this); | 155 writer_.ScheduleWrite(this); |
156 } | 156 } |
157 | 157 |
158 bool TransportSecurityPersister::SerializeData(std::string* output) { | 158 bool TransportSecurityPersister::SerializeData(std::string* output) { |
159 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | 159 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
160 | 160 |
161 DictionaryValue toplevel; | 161 DictionaryValue toplevel; |
162 base::Time now = base::Time::Now(); | 162 std::map<std::string, TransportSecurityState::HSTSEntry>::const_iterator |
163 TransportSecurityState::Iterator state(*transport_security_state_); | 163 iter = transport_security_state_->GetHSTSEntries().begin(); |
164 for (; state.HasNext(); state.Advance()) { | 164 for (; iter != transport_security_state_->GetHSTSEntries().end(); ++iter) { |
165 const std::string& hostname = state.hostname(); | 165 const std::string& hashed_host = iter->first; |
166 const TransportSecurityState::DomainState& domain_state = | 166 const TransportSecurityState::HSTSEntry& entry = iter->second; |
167 state.domain_state(); | |
168 | 167 |
169 DictionaryValue* serialized = new DictionaryValue; | 168 DictionaryValue* serialized = new DictionaryValue; |
170 serialized->SetBoolean(kIncludeSubdomains, | 169 serialized->SetBoolean(kIncludeSubdomains, |
171 domain_state.include_subdomains); | 170 entry.include_subdomains_); |
172 serialized->SetDouble(kCreated, domain_state.created.ToDoubleT()); | 171 serialized->SetDouble(kCreated, entry.created_.ToDoubleT()); |
173 serialized->SetDouble(kExpiry, domain_state.upgrade_expiry.ToDoubleT()); | 172 serialized->SetDouble(kExpiry, entry.expiry_.ToDoubleT()); |
174 serialized->SetDouble(kDynamicSPKIHashesExpiry, | 173 serialized->SetString(kMode, kForceHTTPS); |
175 domain_state.dynamic_spki_hashes_expiry.ToDoubleT()); | 174 toplevel.Set(HashedDomainToExternalString(hashed_host), serialized); |
176 | |
177 switch (domain_state.upgrade_mode) { | |
178 case TransportSecurityState::DomainState::MODE_FORCE_HTTPS: | |
179 serialized->SetString(kMode, kForceHTTPS); | |
180 break; | |
181 case TransportSecurityState::DomainState::MODE_DEFAULT: | |
182 serialized->SetString(kMode, kDefault); | |
183 break; | |
184 default: | |
185 NOTREACHED() << "DomainState with unknown mode"; | |
186 delete serialized; | |
187 continue; | |
188 } | |
189 | |
190 serialized->Set(kStaticSPKIHashes, | |
191 SPKIHashesToListValue(domain_state.static_spki_hashes)); | |
192 | |
193 if (now < domain_state.dynamic_spki_hashes_expiry) { | |
194 serialized->Set(kDynamicSPKIHashes, | |
195 SPKIHashesToListValue(domain_state.dynamic_spki_hashes)); | |
196 } | |
197 | |
198 toplevel.Set(HashedDomainToExternalString(hostname), serialized); | |
199 } | 175 } |
200 | 176 |
201 base::JSONWriter::WriteWithOptions(&toplevel, | 177 base::JSONWriter::WriteWithOptions(&toplevel, |
202 base::JSONWriter::OPTIONS_PRETTY_PRINT, | 178 base::JSONWriter::OPTIONS_PRETTY_PRINT, |
203 output); | 179 output); |
204 return true; | 180 return true; |
205 } | 181 } |
206 | 182 |
207 bool TransportSecurityPersister::DeserializeFromCommandLine( | |
208 const std::string& serialized) { | |
209 // Purposefully ignore |dirty| because we do not want to persist entries | |
210 // deserialized in this way. | |
211 bool dirty; | |
212 return Deserialize(serialized, true, &dirty, transport_security_state_); | |
213 } | |
214 | |
215 bool TransportSecurityPersister::LoadEntries(const std::string& serialized, | 183 bool TransportSecurityPersister::LoadEntries(const std::string& serialized, |
216 bool* dirty) { | 184 bool* dirty) { |
217 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | 185 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
218 | 186 |
219 transport_security_state_->ClearDynamicData(); | 187 transport_security_state_->ClearDynamicData(); |
220 return Deserialize(serialized, false, dirty, transport_security_state_); | 188 return Deserialize(serialized, false, dirty, transport_security_state_); |
221 } | 189 } |
222 | 190 |
223 // static | 191 // static |
224 bool TransportSecurityPersister::Deserialize(const std::string& serialized, | 192 bool TransportSecurityPersister::Deserialize(const std::string& serialized, |
225 bool forced, | 193 bool forced, |
226 bool* dirty, | 194 bool* dirty, |
227 TransportSecurityState* state) { | 195 TransportSecurityState* state) { |
228 scoped_ptr<Value> value(base::JSONReader::Read(serialized)); | 196 scoped_ptr<Value> value(base::JSONReader::Read(serialized)); |
229 DictionaryValue* dict_value; | 197 DictionaryValue* dict_value; |
230 if (!value.get() || !value->GetAsDictionary(&dict_value)) | 198 if (!value.get() || !value->GetAsDictionary(&dict_value)) |
231 return false; | 199 return false; |
232 | 200 |
233 const base::Time current_time(base::Time::Now()); | 201 const base::Time current_time(base::Time::Now()); |
| 202 |
| 203 // dirtied is set to false only if every JSON entry is succesfully loaded |
| 204 // and has a creation date. Otherwise, dirtied is set to true, so that |
| 205 // the JSON will be re-serialized in canonical form. |
234 bool dirtied = false; | 206 bool dirtied = false; |
235 | 207 |
236 for (DictionaryValue::key_iterator i = dict_value->begin_keys(); | 208 for (DictionaryValue::key_iterator i = dict_value->begin_keys(); |
237 i != dict_value->end_keys(); ++i) { | 209 i != dict_value->end_keys(); ++i) { |
238 DictionaryValue* parsed; | 210 DictionaryValue* parsed; |
239 if (!dict_value->GetDictionaryWithoutPathExpansion(*i, &parsed)) { | 211 if (!dict_value->GetDictionaryWithoutPathExpansion(*i, &parsed)) { |
240 LOG(WARNING) << "Could not parse entry " << *i << "; skipping entry"; | 212 LOG(WARNING) << "Could not parse entry " << *i << "; skipping entry"; |
| 213 dirtied = true; |
241 continue; | 214 continue; |
242 } | 215 } |
243 | 216 |
| 217 bool include_subdomains; |
| 218 double created_double, expiry_double; |
| 219 base::Time created, expiry; |
244 std::string mode_string; | 220 std::string mode_string; |
245 double created; | |
246 double expiry; | |
247 double dynamic_spki_hashes_expiry = 0.0; | |
248 TransportSecurityState::DomainState domain_state; | |
249 | 221 |
250 if (!parsed->GetBoolean(kIncludeSubdomains, | 222 if (!parsed->GetBoolean(kIncludeSubdomains, |
251 &domain_state.include_subdomains) || | 223 &include_subdomains) || |
252 !parsed->GetString(kMode, &mode_string) || | 224 !parsed->GetString(kMode, &mode_string) || |
253 !parsed->GetDouble(kExpiry, &expiry)) { | 225 !parsed->GetDouble(kExpiry, &expiry_double)) { |
254 LOG(WARNING) << "Could not parse some elements of entry " << *i | 226 LOG(WARNING) << "Could not parse some elements of entry " << *i |
255 << "; skipping entry"; | 227 << "; skipping entry"; |
| 228 dirtied = true; |
256 continue; | 229 continue; |
257 } | 230 } |
258 | 231 |
259 // Don't fail if this key is not present. | 232 if (parsed->GetDouble(kCreated, &created_double)) { |
260 parsed->GetDouble(kDynamicSPKIHashesExpiry, | 233 created = base::Time::FromDoubleT(created_double); |
261 &dynamic_spki_hashes_expiry); | |
262 | |
263 ListValue* pins_list = NULL; | |
264 // preloaded_spki_hashes is a legacy synonym for static_spki_hashes. | |
265 if (parsed->GetList(kStaticSPKIHashes, &pins_list)) | |
266 SPKIHashesFromListValue(*pins_list, &domain_state.static_spki_hashes); | |
267 else if (parsed->GetList(kPreloadedSPKIHashes, &pins_list)) | |
268 SPKIHashesFromListValue(*pins_list, &domain_state.static_spki_hashes); | |
269 | |
270 if (parsed->GetList(kDynamicSPKIHashes, &pins_list)) | |
271 SPKIHashesFromListValue(*pins_list, &domain_state.dynamic_spki_hashes); | |
272 | |
273 if (mode_string == kForceHTTPS || mode_string == kStrict) { | |
274 domain_state.upgrade_mode = | |
275 TransportSecurityState::DomainState::MODE_FORCE_HTTPS; | |
276 } else if (mode_string == kDefault || mode_string == kPinningOnly) { | |
277 domain_state.upgrade_mode = | |
278 TransportSecurityState::DomainState::MODE_DEFAULT; | |
279 } else { | |
280 LOG(WARNING) << "Unknown TransportSecurityState mode string " | |
281 << mode_string << " found for entry " << *i | |
282 << "; skipping entry"; | |
283 continue; | |
284 } | |
285 | |
286 domain_state.upgrade_expiry = base::Time::FromDoubleT(expiry); | |
287 domain_state.dynamic_spki_hashes_expiry = | |
288 base::Time::FromDoubleT(dynamic_spki_hashes_expiry); | |
289 if (parsed->GetDouble(kCreated, &created)) { | |
290 domain_state.created = base::Time::FromDoubleT(created); | |
291 } else { | 234 } else { |
292 // We're migrating an old entry with no creation date. Make sure we | 235 // We're migrating an old entry with no creation date. Make sure we |
293 // write the new date back in a reasonable time frame. | 236 // write the new date back in a reasonable time frame. |
294 dirtied = true; | 237 dirtied = true; |
295 domain_state.created = base::Time::Now(); | 238 created = base::Time::Now(); |
296 } | 239 } |
297 | 240 |
298 if (domain_state.upgrade_expiry <= current_time && | 241 if (mode_string == kForceHTTPS || mode_string == kStrict) { |
299 domain_state.dynamic_spki_hashes_expiry <= current_time) { | 242 std::string hashed_host = ExternalStringToHashedDomain(*i); |
300 // Make sure we dirty the state if we drop an entry. | 243 base::Time expiry = base::Time::FromDoubleT(expiry_double); |
301 dirtied = true; | 244 if (expiry > current_time) { |
302 continue; | 245 state->AddHSTSHashedHost(hashed_host, created, expiry, |
| 246 include_subdomains); |
| 247 } else { |
| 248 dirtied = true; |
| 249 } |
303 } | 250 } |
304 | |
305 std::string hashed = ExternalStringToHashedDomain(*i); | |
306 if (hashed.empty()) { | |
307 dirtied = true; | |
308 continue; | |
309 } | |
310 | |
311 if (forced) | |
312 state->AddOrUpdateForcedHosts(hashed, domain_state); | |
313 else | |
314 state->AddOrUpdateEnabledHosts(hashed, domain_state); | |
315 } | 251 } |
316 | 252 |
317 *dirty = dirtied; | 253 *dirty = dirtied; |
318 return true; | 254 return true; |
319 } | 255 } |
320 | 256 |
321 void TransportSecurityPersister::CompleteLoad(const std::string& state) { | 257 void TransportSecurityPersister::CompleteLoad(const std::string& state) { |
322 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | 258 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
323 | 259 |
324 bool dirty = false; | 260 bool dirty = false; |
325 if (!LoadEntries(state, &dirty)) { | 261 if (!LoadEntries(state, &dirty)) { |
326 LOG(ERROR) << "Failed to deserialize state: " << state; | 262 LOG(ERROR) << "Failed to deserialize state: " << state; |
327 return; | 263 return; |
328 } | 264 } |
329 if (dirty) | 265 if (dirty) |
330 StateIsDirty(transport_security_state_); | 266 StateIsDirty(transport_security_state_); |
331 } | 267 } |
OLD | NEW |