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

Side by Side Diff: chrome/browser/ssl/chrome_ssl_host_state_decisions.cc

Issue 369703002: Remember user decisions on invalid certificates behind a flag (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: More fixes from sleevi plus a rebase on ToT Created 6 years, 5 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 | Annotate | Revision Log
OLDNEW
(Empty)
1 // Copyright (c) 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/ssl/chrome_ssl_host_state_decisions.h"
6
7 #include "base/base64.h"
8 #include "base/command_line.h"
9 #include "base/logging.h"
10 #include "base/strings/string_number_conversions.h"
11 #include "base/time/default_clock.h"
12 #include "base/time/time.h"
13 #include "chrome/browser/content_settings/host_content_settings_map.h"
14 #include "chrome/browser/profiles/profile.h"
15 #include "chrome/common/chrome_switches.h"
16 #include "chrome/common/content_settings_types.h"
17 #include "url/gurl.h"
18
19 namespace {
20
21 // Time expiration values in seconds for the command line switches for turning
22 // on certificate error decision time based memory.
23 const int64 kForgetAtSessionEndSwitchValue = -1;
24 const int64 kExpirationZeroInSeconds = 0;
25 const int64 kExpirationOneDayInSeconds = 86400;
26 const int64 kExpirationThreeDaysInSeconds = 259200;
27 const int64 kExpirationOneWeekInSeconds = 604800;
28 const int64 kExpirationOneMonthInSeconds = 2592000;
29
30 // Keys for the per-site error + certificate finger to judgement content
31 // settings map.
32 const char kSSLCertDecisionCertErrorMapKey[] = "cert_exceptions_map";
33 const char kSSLCertDecisionExpirationTimeKey[] = "decision_expiration_time";
34 const char kSSLCertDecisionVersionKey[] = "version";
35
36 const int kDefaultSSLCertDecisionVersion = 1;
37
38 // This is a helper function that returns the length of time before a
39 // certificate decision expires based on the command line flags. Returns a
40 // non-negative value in seconds or a value of -1 indicating that decisions
41 // should not be remembered after the current session has ended (but should be
42 // remembered indefinitely as long as the session does not end), which is the
43 // "old" style of certificate decision memory.
44 int64 GetExpirationDelta() {
45 if (CommandLine::ForCurrentProcess()->HasSwitch(
46 switches::kRememberCertErrorDecisionsDisable)) {
47 return kForgetAtSessionEndSwitchValue;
48 } else if (CommandLine::ForCurrentProcess()->HasSwitch(
49 switches::kRememberCertErrorDecisionsNone)) {
50 return kExpirationZeroInSeconds;
51 } else if (CommandLine::ForCurrentProcess()->HasSwitch(
52 switches::kRememberCertErrorDecisionsOneDay)) {
53 return kExpirationOneDayInSeconds;
54 } else if (CommandLine::ForCurrentProcess()->HasSwitch(
55 switches::kRememberCertErrorDecisionsThreeDays)) {
56 return kExpirationThreeDaysInSeconds;
57 } else if (CommandLine::ForCurrentProcess()->HasSwitch(
58 switches::kRememberCertErrorDecisionsOneWeek)) {
59 return kExpirationOneWeekInSeconds;
60 } else if (CommandLine::ForCurrentProcess()->HasSwitch(
61 switches::kRememberCertErrorDecisionsOneMonth)) {
Ryan Sleevi 2014/07/17 23:19:35 Can we cap at a week? I'm a little nervous for a m
jww 2014/07/21 23:39:32 There's actually a good chance we want to do longe
62 return kExpirationOneMonthInSeconds;
63 }
64
65 return kForgetAtSessionEndSwitchValue;
66 }
67
68 std::string GetKey(net::X509Certificate* cert, net::CertStatus error) {
69 net::SHA1HashValue fingerprint = cert->fingerprint();
70 std::string base64_fingerprint;
71 base::Base64Encode(
72 base::StringPiece(reinterpret_cast<const char*>(fingerprint.data),
73 sizeof(fingerprint.data)),
74 &base64_fingerprint);
75 return base::UintToString(error) + base64_fingerprint;
76 }
77
78 } // namespace
79
80 // This helper function gets the dictionary of certificate fingerprints to
81 // errors of certificates that have been accepted by the user from the content
82 // dictionary that has been passed in. The returned pointer is owned by the the
83 // argument dict that is passed in.
84 //
85 // If create_entries is set to |DoNotCreateDictionaryEntries|,
86 // GetValidCertDecisionsDict will return NULL if there is anything invalid about
87 // the setting, such as an invalid version or invalid value types (in addition
88 // to there not be any values in the dictionary). If create_entries is set to
89 // |CreateDictionaryEntries|, if no dictionary is found or the decisions are
90 // expired, a new dictionary will be created
91 base::DictionaryValue* ChromeSSLHostStateDecisions::GetValidCertDecisionsDict(
92 base::DictionaryValue* dict,
93 CreateDictionaryEntriesDisposition create_entries) {
94 // Extract the version of the certificate decision structure from the content
95 // setting.
96 int version;
97 bool success = dict->GetInteger(kSSLCertDecisionVersionKey, &version);
98 if (!success) {
99 if (create_entries == DoNotCreateDictionaryEntries)
100 return NULL;
101
102 dict->SetInteger(kSSLCertDecisionVersionKey,
103 kDefaultSSLCertDecisionVersion);
104 version = kDefaultSSLCertDecisionVersion;
105 }
106
107 // If the version is somehow a newer version than Chrome can handle, there's
108 // really nothing to do other than fail silently and pretend it doesn't exist
109 // (or is malformed).
110 if (version > kDefaultSSLCertDecisionVersion) {
111 LOG(ERROR) << "Failed to parse a certificate error exception that is in a "
112 << "newer version format (" << version << ") than is supported ("
113 << kDefaultSSLCertDecisionVersion << ")";
114 return NULL;
115 }
116
117 // Extract the certificate decision's expiration time from the content
118 // setting. If there is no expiration time, that means it should never expire
119 // and it should resest only at session restart, so skip all of the expiration
Ryan Sleevi 2014/07/17 23:19:35 typo, s/resest/reset
jww 2014/07/21 23:39:32 Done.
120 // checks.
121 bool expired = false;
122 base::Time now = clock_->Now();
123 base::Time decision_expiration;
124 if (dict->HasKey(kSSLCertDecisionExpirationTimeKey)) {
125 std::string decision_expiration_string;
126 int64 decision_expiration_int64;
127 success = dict->GetString(kSSLCertDecisionExpirationTimeKey,
128 &decision_expiration_string);
129 if (!base::StringToInt64(base::StringPiece(decision_expiration_string),
130 &decision_expiration_int64)) {
131 LOG(ERROR) << "Failed to parse a certificate error exception that has a "
132 << "bad value for an expiration time: "
133 << decision_expiration_string;
134 return NULL;
135 }
136 decision_expiration =
137 base::Time::FromInternalValue(decision_expiration_int64);
138 }
139
140 // Check to see if the user's certificate decision has expired.
141 // - Expired and |create_entries| is DoNotCreateDictionaryEntries, return
142 // NULL.
143 // - Expired and |create_entries| is CreateDictionaryEntries, update the
144 // expiration time.
145 if (should_remember_ssl_decisions_ !=
146 ForgetSSLExceptionDecisionsAtSessionEnd &&
147 decision_expiration.ToInternalValue() <= now.ToInternalValue()) {
148 if (create_entries == DoNotCreateDictionaryEntries)
149 return NULL;
150
151 expired = true;
152
153 base::Time expiration_time =
154 now + default_ssl_cert_decision_expiration_delta_;
155 // Unfortunately, JSON (and thus content settings) doesn't support int64
156 // values, only doubles. Since this mildly depends on precision, it is
157 // better to store the value as a string.
158 dict->SetString(kSSLCertDecisionExpirationTimeKey,
159 base::Int64ToString(expiration_time.ToInternalValue()));
160 }
Ryan Sleevi 2014/07/17 23:19:35 What about if the user sets their clock a week ahe
jww 2014/07/21 23:39:32 They will get a longer expiration time. That is, i
161
162 // Extract the map of certificate fingerprints to errors from the setting.
163 base::DictionaryValue* cert_error_dict; // Will be owned by dict
164 if (expired ||
165 !dict->GetDictionary(kSSLCertDecisionCertErrorMapKey, &cert_error_dict)) {
166 if (create_entries == DoNotCreateDictionaryEntries)
167 return NULL;
168
169 cert_error_dict = new base::DictionaryValue();
170 // dict takes ownership of cert_error_dict
171 dict->Set(kSSLCertDecisionCertErrorMapKey, cert_error_dict);
172 }
173
174 return cert_error_dict;
175 }
176
177 // If should_remember_ssl_decisions_ is ForgetSSLExceptionDecisionsAtSessionEnd,
Ryan Sleevi 2014/07/17 23:19:35 add ||
jww 2014/07/21 23:39:32 Done.
178 // that means that all invalid certificate proceed decisions should be forgotten
179 // when the session ends. At attempt is made in the destructor to remove the
180 // entries, but in the case that things didn't shut down cleanly, on start,
181 // Clear is called to guarantee a clean state.
182 ChromeSSLHostStateDecisions::ChromeSSLHostStateDecisions(Profile* profile)
183 : clock_(new base::DefaultClock()), profile_(profile) {
184 int64 expiration_delta = GetExpirationDelta();
185 if (expiration_delta == kForgetAtSessionEndSwitchValue) {
186 should_remember_ssl_decisions_ = ForgetSSLExceptionDecisionsAtSessionEnd;
187 expiration_delta = 0;
188 Clear();
189 } else {
190 should_remember_ssl_decisions_ = RememberSSLExceptionDecisionsForDelta;
191 }
192 default_ssl_cert_decision_expiration_delta_ =
193 base::TimeDelta::FromSeconds(expiration_delta);
194 }
195
196 ChromeSSLHostStateDecisions::~ChromeSSLHostStateDecisions() {
197 if (should_remember_ssl_decisions_ == ForgetSSLExceptionDecisionsAtSessionEnd)
198 Clear();
199 }
200
201 void ChromeSSLHostStateDecisions::DenyCert(const GURL& url,
202 net::X509Certificate* cert,
203 net::CertStatus error) {
204 ChangeCertPolicy(url, cert, error, net::CertPolicy::DENIED);
205 }
206
207 void ChromeSSLHostStateDecisions::AllowCert(const GURL& url,
208 net::X509Certificate* cert,
209 net::CertStatus error) {
210 ChangeCertPolicy(url, cert, error, net::CertPolicy::ALLOWED);
211 }
212
213 void ChromeSSLHostStateDecisions::Clear() {
214 profile_->GetHostContentSettingsMap()->ClearSettingsForOneType(
215 CONTENT_SETTINGS_TYPE_SSL_CERT_DECISIONS);
216 }
217
218 net::CertPolicy::Judgment ChromeSSLHostStateDecisions::QueryPolicy(
219 const GURL& url,
220 net::X509Certificate* cert,
221 net::CertStatus error) {
222 HostContentSettingsMap* map = profile_->GetHostContentSettingsMap();
223 scoped_ptr<base::Value> value(map->GetWebsiteSetting(
224 url, url, CONTENT_SETTINGS_TYPE_SSL_CERT_DECISIONS, std::string(), NULL));
225
226 if (!value.get() || !value->IsType(base::Value::TYPE_DICTIONARY))
227 return net::CertPolicy::UNKNOWN;
228
229 base::DictionaryValue* dict; // Owned by value
230 int policy_decision; // Owned by dict
Ryan Sleevi 2014/07/17 23:19:35 This comment doesn't make sense.
jww 2014/07/21 23:39:32 Ha. Nope, it certainly does not. Removing.
231 bool success = value->GetAsDictionary(&dict);
232 DCHECK(success);
233
234 base::DictionaryValue* cert_error_dict; // Owned by value
235 cert_error_dict =
236 GetValidCertDecisionsDict(dict, DoNotCreateDictionaryEntries);
237 if (!cert_error_dict)
238 return net::CertPolicy::UNKNOWN;
239
240 success = cert_error_dict->GetIntegerWithoutPathExpansion(GetKey(cert, error),
241 &policy_decision);
242
243 if (!success)
244 return net::CertPolicy::Judgment::UNKNOWN;
245
246 return static_cast<net::CertPolicy::Judgment>(policy_decision);
247 }
248
249 void ChromeSSLHostStateDecisions::RevokeAllowAndDenyPreferences(
250 const GURL& url) {
251 const ContentSettingsPattern pattern =
252 ContentSettingsPattern::FromURLNoWildcard(url);
253 HostContentSettingsMap* map = profile_->GetHostContentSettingsMap();
254
255 map->SetWebsiteSetting(pattern,
256 pattern,
257 CONTENT_SETTINGS_TYPE_SSL_CERT_DECISIONS,
258 std::string(),
259 NULL);
260 }
261
262 bool ChromeSSLHostStateDecisions::HasAllowedOrDeniedCert(const GURL& url) {
263 const ContentSettingsPattern pattern =
264 ContentSettingsPattern::FromURLNoWildcard(url);
265 HostContentSettingsMap* map = profile_->GetHostContentSettingsMap();
266
267 scoped_ptr<base::Value> value(map->GetWebsiteSetting(
268 url, url, CONTENT_SETTINGS_TYPE_SSL_CERT_DECISIONS, std::string(), NULL));
269
270 if (!value.get() || !value->IsType(base::Value::TYPE_DICTIONARY))
271 return false;
272
273 base::DictionaryValue* dict; // Owned by value
274 bool success = value->GetAsDictionary(&dict);
275 DCHECK(success);
276
277 for (base::DictionaryValue::Iterator it(*dict); !it.IsAtEnd(); it.Advance()) {
278 int policy_decision; // Owned by dict
279 success = it.value().GetAsInteger(&policy_decision);
280 if (success && (static_cast<net::CertPolicy::Judgment>(policy_decision) !=
281 net::CertPolicy::UNKNOWN))
282 return true;
283 }
284
285 return false;
286 }
287
288 void ChromeSSLHostStateDecisions::ChangeCertPolicy(
289 const GURL& url,
290 net::X509Certificate* cert,
291 net::CertStatus error,
292 net::CertPolicy::Judgment judgment) {
293 const ContentSettingsPattern pattern =
294 ContentSettingsPattern::FromURLNoWildcard(url);
295 HostContentSettingsMap* map = profile_->GetHostContentSettingsMap();
296 scoped_ptr<base::Value> value(map->GetWebsiteSetting(
297 url, url, CONTENT_SETTINGS_TYPE_SSL_CERT_DECISIONS, std::string(), NULL));
298
299 if (!value.get() || !value->IsType(base::Value::TYPE_DICTIONARY))
300 value.reset(new base::DictionaryValue());
301
302 base::DictionaryValue* dict;
303 bool success = value->GetAsDictionary(&dict);
304 DCHECK(success);
305
306 base::DictionaryValue* cert_dict =
307 GetValidCertDecisionsDict(dict, CreateDictionaryEntries);
308 // If a a valid certificate dictionary cannot be extracted from the content
309 // setting, that means it's in an unknown format. Unfortunately, there's
310 // nothing to be done in that case, so a silent fail is the only option.
311 if (!cert_dict)
312 return;
313
314 dict->SetIntegerWithoutPathExpansion(kSSLCertDecisionVersionKey,
315 kDefaultSSLCertDecisionVersion);
316 cert_dict->SetIntegerWithoutPathExpansion(GetKey(cert, error), judgment);
317
318 // The map takes ownership of the value, so it is released in the call to
319 // SetWebsiteSetting.
320 map->SetWebsiteSetting(pattern,
321 pattern,
322 CONTENT_SETTINGS_TYPE_SSL_CERT_DECISIONS,
323 std::string(),
324 value.release());
325 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698