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

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

Powered by Google App Engine
This is Rietveld 408576698