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

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: Fixes from felt plus new incognito browser tests 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 enum CreateDictionaryEntriesDisposition {
22 CreateDictionaryEntries,
23 DoNotCreateDictionaryEntries
24 };
Ryan Sleevi 2014/07/08 23:53:28 Duplicate definition as the .cc. What's up with th
jww 2014/07/11 00:08:40 This was actually fixed in the latest patch set th
25
26 // This is a helper function that returns the length of time before a
27 // certificate decision expires based on the command line flags. Returns a
28 // non-negative value in seconds, where a value of 0 specifically indicates that
29 // decisions should not be remembered after the current session has ended (but
30 // should be remembered indefinitely as long as the session does not end), which
31 // is the "old" style of certificate decision memory. Returns an int64 rather
32 // than uint64 (even though guaranteed to be non-negative) because that's what
33 // callers expect.
34 int64 GetExpirationDelta() {
35 if (!CommandLine::ForCurrentProcess()->HasSwitch(
36 switches::kRememberCertErrorDecisions))
37 return 0;
Ryan Sleevi 2014/07/08 23:53:28 Braces since you wrap?
jww 2014/07/11 00:08:41 Done.
38
39 std::string switch_value =
40 CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
41 switches::kRememberCertErrorDecisions);
42 // If the expiration is set to 0, that means cert proceed decisions should
43 // never expire, until a restart of the profile.
44 int64 expiration_delta;
45 if (!base::StringToInt64(switch_value, &expiration_delta)) {
46 LOG(ERROR) << "Failed to parse the certificate error decision "
47 << "memory length: " << switch_value;
Ryan Sleevi 2014/07/08 23:53:28 Seems to be spammy if the user didn't pass the swi
jww 2014/07/11 00:08:41 It shouldn't reach this if the user didn't include
48 } else if (expiration_delta < 0) {
49 expiration_delta = 0;
50 LOG(ERROR) << "Invalid negative certificate error decision memory length: "
51 << switch_value;
52 }
53
54 return expiration_delta;
55 }
56
57 const char kSSLCertDecisionCertErrorMapKey[] = "cert_exceptions_map";
58 const char kSSLCertDecisionExpirationTimeKey[] = "decision_expiration_time";
59 const char kSSLCertDecisionVersionKey[] = "version";
60 const int kDefaultSSLCertDecisionVersion = 1;
Ryan Sleevi 2014/07/08 23:53:28 anal retentive nit: Prefer constants declared firs
jww 2014/07/11 00:08:40 Done.
61
62 std::string GetKey(net::X509Certificate* cert, net::CertStatus error) {
63 net::SHA1HashValue fingerprint = cert->fingerprint();
64 std::string base64_fingerprint;
65 base::Base64Encode(
66 base::StringPiece(reinterpret_cast<const char*>(fingerprint.data),
67 sizeof(fingerprint.data)),
68 &base64_fingerprint);
69 return base::UintToString(error) + base64_fingerprint;
70 }
71
72 } // namespace
73
74 // This helper function gets the dictionary of certificate fingerprints to
75 // errors of certificates that have been accepted by the user from the content
76 // dictionary that has been passed in. The returned pointer is owned by the the
77 // argument dict that is passed in.
78 //
79 // If create_entries is set to |DoNotCreateDictionaryEntries|,
80 // GetValidCertDecisionsDict will return NULL if there is anything invalid about
81 // the setting, such as an invalid version or invalid value types (in addition
82 // to there not be any values in the dictionary). If create_entries is set to
83 // |CreateDictionaryEntries|, if no dictionary is found or the decisions are
84 // expired, a new dictionary will be created
85 base::DictionaryValue* ChromeSSLHostStateDecisions::GetValidCertDecisionsDict(
86 base::DictionaryValue* dict,
87 CreateDictionaryEntriesDisposition create_entries) {
88 // Extract the version of the certificate decision structure from the content
89 // setting.
90 int version;
91 bool success = dict->GetInteger(kSSLCertDecisionVersionKey, &version);
92 if (!success) {
93 if (create_entries == DoNotCreateDictionaryEntries)
94 return NULL;
95
96 dict->SetInteger(kSSLCertDecisionVersionKey,
97 kDefaultSSLCertDecisionVersion);
98 version = kDefaultSSLCertDecisionVersion;
99 }
100
101 // If the version is somehow a newer version than Chrome can handle, there's
102 // really nothing to do other than fail silently and pretend it doesn't exist
103 // (or is malformed).
104 if (version > kDefaultSSLCertDecisionVersion)
105 return NULL;
106
107 // Extract the certificate decision's expiration time from the content
108 // setting. If there is no expiration time, that means it should never expire
109 // and it should resest only at session restart, so skip all of the expiration
110 // checks.
111 bool expired = false;
112 double decision_expiration_dbl;
113 base::Time now = clock_->Now();
114 base::Time decision_expiration;
115 if (dict->HasKey(kSSLCertDecisionExpirationTimeKey)) {
116 success = dict->GetDouble(kSSLCertDecisionExpirationTimeKey,
117 &decision_expiration_dbl);
118 decision_expiration = base::Time::FromDoubleT(decision_expiration_dbl);
119 }
120
121 // Check if the user certificate decision has expired. If it has expired, and
122 // create_entries is |DoNotCreateDictionaryEntries|, treat the entry as if it
123 // doesn't exist and return |NULL|. If it has expired, and create_entries is
124 // |DoNotCreateDictionaryEntries|, set the entry's expiration date to the
125 // current time plus the expiration time delta. Also, set expired to |true|
126 // which will force the create of a new dictionary in the dictionary
127 // extraction pass. None of this should be done if expiration happens on
128 // session end (i.e. the expiration delta is 0).
Ryan Sleevi 2014/07/08 23:53:28 This feels like it's violating the cardinal rule o
jww 2014/07/11 00:08:41 Done.
129 if (defaultSSLCertDecisionExpirationDelta_.InSeconds() > 0 &&
130 decision_expiration < now) {
131 if (create_entries == DoNotCreateDictionaryEntries)
132 return NULL;
133
134 expired = true;
135
136 // If defaultSSLCertDecisionExpirationDelta_ is 0, that means that decisions
Ryan Sleevi 2014/07/08 23:53:28 bad variable name - default_ssl_cert_decision_expi
jww 2014/07/11 00:08:41 Done.
137 // should never expire. Thus, in that case, no key should be set.
138 if (defaultSSLCertDecisionExpirationDelta_.InSeconds() != 0) {
139 base::Time expiration_time = now + defaultSSLCertDecisionExpirationDelta_;
140 dict->SetDouble(kSSLCertDecisionExpirationTimeKey,
141 expiration_time.ToDoubleT());
142 }
143 }
144
145 // Extract the map of certificate fingerprints to errors from the setting.
146 base::DictionaryValue* cert_error_dict; // Owned by dict
147 if (expired ||
148 !dict->GetDictionary(kSSLCertDecisionCertErrorMapKey, &cert_error_dict)) {
149 if (create_entries == DoNotCreateDictionaryEntries)
150 return NULL;
151
152 cert_error_dict = new base::DictionaryValue();
153 // dict takes ownership of cert_error_dict
154 dict->Set(kSSLCertDecisionCertErrorMapKey, cert_error_dict);
155 }
156
157 return cert_error_dict;
158 }
159
160 // If defaultSSLCertDecisionExpirationDelta_ is 0, that means that all invalid
161 // certificate proceed decisions should be forgotten when the session ends. At
162 // attempt is made in the destructor to remove the entries, but in the case that
163 // things didn't shut down cleanly, on start, Clear is called to guarantee a
164 // clean state.
165 ChromeSSLHostStateDecisions::ChromeSSLHostStateDecisions(Profile* profile)
166 : clock_(new base::DefaultClock()),
167 defaultSSLCertDecisionExpirationDelta_(
168 base::TimeDelta::FromSeconds(GetExpirationDelta())),
169 profile_(profile) {
170 if (defaultSSLCertDecisionExpirationDelta_.InSeconds() == 0)
171 Clear();
172 }
173
174 ChromeSSLHostStateDecisions::~ChromeSSLHostStateDecisions() {
175 if (defaultSSLCertDecisionExpirationDelta_.InSeconds() == 0)
176 Clear();
177 }
178
179 void ChromeSSLHostStateDecisions::DenyCert(const GURL& url,
180 net::X509Certificate* cert,
181 net::CertStatus error) {
182 ChangeCertPolicy(url, cert, error, net::CertPolicy::DENIED);
183 }
184
185 void ChromeSSLHostStateDecisions::AllowCert(const GURL& url,
186 net::X509Certificate* cert,
187 net::CertStatus error) {
188 ChangeCertPolicy(url, cert, error, net::CertPolicy::ALLOWED);
189 }
190
191 void ChromeSSLHostStateDecisions::Clear() {
192 profile_->GetHostContentSettingsMap()->ClearSettingsForOneType(
193 CONTENT_SETTINGS_TYPE_SSL_CERT_DECISIONS);
194 }
195
196 net::CertPolicy::Judgment ChromeSSLHostStateDecisions::QueryPolicy(
197 const GURL& url,
198 net::X509Certificate* cert,
199 net::CertStatus error) {
200 HostContentSettingsMap* map = profile_->GetHostContentSettingsMap();
201 scoped_ptr<base::Value> value(map->GetWebsiteSetting(
202 url, url, CONTENT_SETTINGS_TYPE_SSL_CERT_DECISIONS, "", NULL));
Ryan Sleevi 2014/07/08 23:53:28 std::string(), not ""
jww 2014/07/11 00:08:40 Done.
203
204 DCHECK(value.get());
205
206 if (!value->IsType(base::Value::TYPE_DICTIONARY))
207 return net::CertPolicy::UNKNOWN;
208
209 base::DictionaryValue* dict; // Owned by value
210 int policy_decision; // Owned by dict
211 bool success = value->GetAsDictionary(&dict);
212 DCHECK(success);
213
214 // First, check that the version of the dictionary of decisions is a version
215 // that is known. Otherwise, fail.
216 base::DictionaryValue* cert_error_dict; // Owned by value
217 cert_error_dict =
218 GetValidCertDecisionsDict(dict, DoNotCreateDictionaryEntries);
219 if (!cert_error_dict)
220 return net::CertPolicy::UNKNOWN;
221
222 success = cert_error_dict->GetIntegerWithoutPathExpansion(GetKey(cert, error),
223 &policy_decision);
224
225 if (!success)
226 return net::CertPolicy::Judgment::UNKNOWN;
227
228 return static_cast<net::CertPolicy::Judgment>(policy_decision);
229 }
230
231 void ChromeSSLHostStateDecisions::RevokeAllowAndDenyPreferences(
232 const GURL& url) {
233 const ContentSettingsPattern pattern =
234 ContentSettingsPattern::FromURLNoWildcard(url);
235 HostContentSettingsMap* map = profile_->GetHostContentSettingsMap();
236
237 map->SetWebsiteSetting(
238 pattern, pattern, CONTENT_SETTINGS_TYPE_SSL_CERT_DECISIONS, "", NULL);
Ryan Sleevi 2014/07/08 23:53:28 std::string() - and throughout rest of file.
jww 2014/07/11 00:08:41 Done.
239 }
240
241 bool ChromeSSLHostStateDecisions::HasAllowedOrDeniedCert(const GURL& url) {
242 const ContentSettingsPattern pattern =
243 ContentSettingsPattern::FromURLNoWildcard(url);
244 HostContentSettingsMap* map = profile_->GetHostContentSettingsMap();
245
246 scoped_ptr<base::Value> value(map->GetWebsiteSetting(
247 url, url, CONTENT_SETTINGS_TYPE_SSL_CERT_DECISIONS, "", NULL));
Ryan Sleevi 2014/07/08 23:53:28 Why don't you DCHECK here, but you do on line 204?
jww 2014/07/11 00:08:40 Good catch. The DCHECK on 204 was from testing ear
248
249 if (!value->IsType(base::Value::TYPE_DICTIONARY))
250 return false;
251
252 base::DictionaryValue* dict; // Owned by value
253 bool success = value->GetAsDictionary(&dict);
254 DCHECK(success);
255
256 for (base::DictionaryValue::Iterator it(*dict); !it.IsAtEnd(); it.Advance()) {
257 int policy_decision; // Owned by dict
258 success = it.value().GetAsInteger(&policy_decision);
259 if (success && (static_cast<net::CertPolicy::Judgment>(policy_decision) !=
260 net::CertPolicy::UNKNOWN))
261 return true;
262 }
263
264 return false;
265 }
266
267 void ChromeSSLHostStateDecisions::ChangeCertPolicy(
268 const GURL& url,
269 net::X509Certificate* cert,
270 net::CertStatus error,
271 net::CertPolicy::Judgment judgment) {
272 const ContentSettingsPattern pattern =
273 ContentSettingsPattern::FromURLNoWildcard(url);
274 HostContentSettingsMap* map = profile_->GetHostContentSettingsMap();
275 scoped_ptr<base::Value> value(map->GetWebsiteSetting(
276 url, url, CONTENT_SETTINGS_TYPE_SSL_CERT_DECISIONS, "", NULL));
Ryan Sleevi 2014/07/08 23:53:28 ditto DCHECK
jww 2014/07/11 00:08:41 See above.
277
278 if (!value->IsType(base::Value::TYPE_DICTIONARY))
279 value.reset(new base::DictionaryValue());
280
281 base::DictionaryValue* dict;
282 bool success = value->GetAsDictionary(&dict);
283 DCHECK(success);
284
285 base::DictionaryValue* cert_dict =
286 GetValidCertDecisionsDict(dict, CreateDictionaryEntries);
287 // If a a valid certificate dictionary cannot be extracted from the content
288 // setting, that means it's in an unknown format. Unfortunately, there's
289 // nothing to be done in that case, so a silent fail is the only option.
290 if (!cert_dict)
291 return;
292
293 dict->SetIntegerWithoutPathExpansion(kSSLCertDecisionVersionKey,
294 kDefaultSSLCertDecisionVersion);
295 cert_dict->SetIntegerWithoutPathExpansion(GetKey(cert, error), judgment);
296
297 // The map takes ownership of the value, so it is released in the call to
298 // SetWebsiteSetting.
299 map->SetWebsiteSetting(pattern,
300 pattern,
301 CONTENT_SETTINGS_TYPE_SSL_CERT_DECISIONS,
302 "",
303 value.release());
304 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698