Update To 11.40.268.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / ssl / chrome_ssl_host_state_delegate.cc
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
3 // found in the LICENSE file.
4
5 #include "chrome/browser/ssl/chrome_ssl_host_state_delegate.h"
6
7 #include <set>
8
9 #include "base/base64.h"
10 #include "base/bind.h"
11 #include "base/command_line.h"
12 #include "base/guid.h"
13 #include "base/logging.h"
14 #include "base/metrics/field_trial.h"
15 #include "base/strings/string_number_conversions.h"
16 #include "base/time/clock.h"
17 #include "base/time/default_clock.h"
18 #include "base/time/time.h"
19 #include "base/values.h"
20 #include "chrome/browser/profiles/profile.h"
21 #include "chrome/common/chrome_switches.h"
22 #include "components/content_settings/core/browser/host_content_settings_map.h"
23 #include "components/content_settings/core/common/content_settings_types.h"
24 #include "components/variations/variations_associated_data.h"
25 #include "net/base/hash_value.h"
26 #include "net/cert/x509_certificate.h"
27 #include "net/http/http_transaction_factory.h"
28 #include "net/url_request/url_request_context.h"
29 #include "net/url_request/url_request_context_getter.h"
30 #include "url/gurl.h"
31
32 namespace {
33
34 // Switch value that specifies that certificate decisions should be forgotten at
35 // the end of the current session.
36 const int64 kForgetAtSessionEndSwitchValue = -1;
37
38 // Experiment information
39 const char kRememberCertificateErrorDecisionsFieldTrialName[] =
40     "RememberCertificateErrorDecisions";
41 const char kRememberCertificateErrorDecisionsFieldTrialDefaultGroup[] =
42     "Default";
43 const char kRememberCertificateErrorDecisionsFieldTrialLengthParam[] = "length";
44
45 // Keys for the per-site error + certificate finger to judgment content
46 // settings map.
47 const char kSSLCertDecisionCertErrorMapKey[] = "cert_exceptions_map";
48 const char kSSLCertDecisionExpirationTimeKey[] = "decision_expiration_time";
49 const char kSSLCertDecisionVersionKey[] = "version";
50 const char kSSLCertDecisionGUIDKey[] = "guid";
51
52 const int kDefaultSSLCertDecisionVersion = 1;
53
54 void CloseIdleConnections(
55     scoped_refptr<net::URLRequestContextGetter> url_request_context_getter) {
56   url_request_context_getter->
57       GetURLRequestContext()->
58       http_transaction_factory()->
59       GetSession()->
60       CloseIdleConnections();
61 }
62
63 // All SSL decisions are per host (and are shared arcoss schemes), so this
64 // canonicalizes all hosts into a secure scheme GURL to use with content
65 // settings. The returned GURL will be the passed in host with an empty path and
66 // https:// as the scheme.
67 GURL GetSecureGURLForHost(const std::string& host) {
68   std::string url = "https://" + host;
69   return GURL(url);
70 }
71
72 // This is a helper function that returns the length of time before a
73 // certificate decision expires based on the command line flags. Returns a
74 // non-negative value in seconds or a value of -1 indicating that decisions
75 // should not be remembered after the current session has ended (but should be
76 // remembered indefinitely as long as the session does not end), which is the
77 // "old" style of certificate decision memory. Uses the experimental group
78 // unless overridden by a command line flag.
79 int64 GetExpirationDelta() {
80   // Check command line flags first to give them priority, then check
81   // experimental groups.
82   if (CommandLine::ForCurrentProcess()->HasSwitch(
83           switches::kRememberCertErrorDecisions)) {
84     std::string switch_value =
85         CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
86             switches::kRememberCertErrorDecisions);
87     int64 expiration_delta;
88     if (!base::StringToInt64(base::StringPiece(switch_value),
89                              &expiration_delta) ||
90         expiration_delta < kForgetAtSessionEndSwitchValue) {
91       LOG(ERROR) << "Failed to parse the certificate error decision "
92                  << "memory length: " << switch_value;
93       return kForgetAtSessionEndSwitchValue;
94     }
95
96     return expiration_delta;
97   }
98
99   // If the user is in the field trial, set the expiration to the length
100   // associated with that experimental group.  The default group cannot have
101   // parameters associated with it, so it needs to be handled explictly.
102   std::string group_name = base::FieldTrialList::FindFullName(
103       kRememberCertificateErrorDecisionsFieldTrialName);
104   if (!group_name.empty() &&
105       group_name.compare(
106           kRememberCertificateErrorDecisionsFieldTrialDefaultGroup) != 0) {
107     int64 field_trial_param_length;
108     std::string param = variations::GetVariationParamValue(
109         kRememberCertificateErrorDecisionsFieldTrialName,
110         kRememberCertificateErrorDecisionsFieldTrialLengthParam);
111     if (!param.empty() && base::StringToInt64(base::StringPiece(param),
112                                               &field_trial_param_length)) {
113       return field_trial_param_length;
114     }
115   }
116
117   return kForgetAtSessionEndSwitchValue;
118 }
119
120 std::string GetKey(const net::X509Certificate& cert, net::CertStatus error) {
121   // Since a security decision will be made based on the fingerprint, Chrome
122   // should use the SHA-256 fingerprint for the certificate.
123   net::SHA256HashValue fingerprint =
124       net::X509Certificate::CalculateChainFingerprint256(
125           cert.os_cert_handle(), cert.GetIntermediateCertificates());
126   std::string base64_fingerprint;
127   base::Base64Encode(
128       base::StringPiece(reinterpret_cast<const char*>(fingerprint.data),
129                         sizeof(fingerprint.data)),
130       &base64_fingerprint);
131   return base::UintToString(error) + base64_fingerprint;
132 }
133
134 }  // namespace
135
136 // This helper function gets the dictionary of certificate fingerprints to
137 // errors of certificates that have been accepted by the user from the content
138 // dictionary that has been passed in. The returned pointer is owned by the the
139 // argument dict that is passed in.
140 //
141 // If create_entries is set to |DO_NOT_CREATE_DICTIONARY_ENTRIES|,
142 // GetValidCertDecisionsDict will return NULL if there is anything invalid about
143 // the setting, such as an invalid version or invalid value types (in addition
144 // to there not being any values in the dictionary). If create_entries is set to
145 // |CREATE_DICTIONARY_ENTRIES|, if no dictionary is found or the decisions are
146 // expired, a new dictionary will be created.
147 base::DictionaryValue* ChromeSSLHostStateDelegate::GetValidCertDecisionsDict(
148     base::DictionaryValue* dict,
149     CreateDictionaryEntriesDisposition create_entries,
150     bool* expired_previous_decision) {
151   // This needs to be done first in case the method is short circuited by an
152   // early failure.
153   *expired_previous_decision = false;
154
155   // Extract the version of the certificate decision structure from the content
156   // setting.
157   int version;
158   bool success = dict->GetInteger(kSSLCertDecisionVersionKey, &version);
159   if (!success) {
160     if (create_entries == DO_NOT_CREATE_DICTIONARY_ENTRIES)
161       return NULL;
162
163     dict->SetInteger(kSSLCertDecisionVersionKey,
164                      kDefaultSSLCertDecisionVersion);
165     version = kDefaultSSLCertDecisionVersion;
166   }
167
168   // If the version is somehow a newer version than Chrome can handle, there's
169   // really nothing to do other than fail silently and pretend it doesn't exist
170   // (or is malformed).
171   if (version > kDefaultSSLCertDecisionVersion) {
172     LOG(ERROR) << "Failed to parse a certificate error exception that is in a "
173                << "newer version format (" << version << ") than is supported ("
174                << kDefaultSSLCertDecisionVersion << ")";
175     return NULL;
176   }
177
178   // Extract the certificate decision's expiration time from the content
179   // setting. If there is no expiration time, that means it should never expire
180   // and it should reset only at session restart, so skip all of the expiration
181   // checks.
182   bool expired = false;
183   base::Time now = clock_->Now();
184   base::Time decision_expiration;
185   if (dict->HasKey(kSSLCertDecisionExpirationTimeKey)) {
186     std::string decision_expiration_string;
187     int64 decision_expiration_int64;
188     success = dict->GetString(kSSLCertDecisionExpirationTimeKey,
189                               &decision_expiration_string);
190     if (!base::StringToInt64(base::StringPiece(decision_expiration_string),
191                              &decision_expiration_int64)) {
192       LOG(ERROR) << "Failed to parse a certificate error exception that has a "
193                  << "bad value for an expiration time: "
194                  << decision_expiration_string;
195       return NULL;
196     }
197     decision_expiration =
198         base::Time::FromInternalValue(decision_expiration_int64);
199   }
200
201   // Check to see if the user's certificate decision has expired.
202   // - Expired and |create_entries| is DO_NOT_CREATE_DICTIONARY_ENTRIES, return
203   // NULL.
204   // - Expired and |create_entries| is CREATE_DICTIONARY_ENTRIES, update the
205   // expiration time.
206   if (should_remember_ssl_decisions_ !=
207           FORGET_SSL_EXCEPTION_DECISIONS_AT_SESSION_END &&
208       decision_expiration.ToInternalValue() <= now.ToInternalValue()) {
209     *expired_previous_decision = true;
210
211     if (create_entries == DO_NOT_CREATE_DICTIONARY_ENTRIES)
212       return NULL;
213
214     expired = true;
215     base::Time expiration_time =
216         now + default_ssl_cert_decision_expiration_delta_;
217     // Unfortunately, JSON (and thus content settings) doesn't support int64
218     // values, only doubles. Since this mildly depends on precision, it is
219     // better to store the value as a string.
220     dict->SetString(kSSLCertDecisionExpirationTimeKey,
221                     base::Int64ToString(expiration_time.ToInternalValue()));
222   } else if (should_remember_ssl_decisions_ ==
223              FORGET_SSL_EXCEPTION_DECISIONS_AT_SESSION_END) {
224     if (dict->HasKey(kSSLCertDecisionGUIDKey)) {
225       std::string old_expiration_guid;
226       success = dict->GetString(kSSLCertDecisionGUIDKey, &old_expiration_guid);
227       if (old_expiration_guid.compare(current_expiration_guid_) != 0) {
228         *expired_previous_decision = true;
229         expired = true;
230       }
231     }
232   }
233
234   dict->SetString(kSSLCertDecisionGUIDKey, current_expiration_guid_);
235
236   // Extract the map of certificate fingerprints to errors from the setting.
237   base::DictionaryValue* cert_error_dict = NULL;  // Will be owned by dict
238   if (expired ||
239       !dict->GetDictionary(kSSLCertDecisionCertErrorMapKey, &cert_error_dict)) {
240     if (create_entries == DO_NOT_CREATE_DICTIONARY_ENTRIES)
241       return NULL;
242
243     cert_error_dict = new base::DictionaryValue();
244     // dict takes ownership of cert_error_dict
245     dict->Set(kSSLCertDecisionCertErrorMapKey, cert_error_dict);
246   }
247
248   return cert_error_dict;
249 }
250
251 // If |should_remember_ssl_decisions_| is
252 // FORGET_SSL_EXCEPTION_DECISIONS_AT_SESSION_END, that means that all invalid
253 // certificate proceed decisions should be forgotten when the session ends. To
254 // simulate that, Chrome keeps track of a guid to represent the current browser
255 // session and stores it in decision entries. See the comment for
256 // |current_expiration_guid_| for more information.
257 ChromeSSLHostStateDelegate::ChromeSSLHostStateDelegate(Profile* profile)
258     : clock_(new base::DefaultClock()),
259       profile_(profile),
260       current_expiration_guid_(base::GenerateGUID()) {
261   int64 expiration_delta = GetExpirationDelta();
262   if (expiration_delta == kForgetAtSessionEndSwitchValue) {
263     should_remember_ssl_decisions_ =
264         FORGET_SSL_EXCEPTION_DECISIONS_AT_SESSION_END;
265     expiration_delta = 0;
266   } else {
267     should_remember_ssl_decisions_ = REMEMBER_SSL_EXCEPTION_DECISIONS_FOR_DELTA;
268   }
269   default_ssl_cert_decision_expiration_delta_ =
270       base::TimeDelta::FromSeconds(expiration_delta);
271 }
272
273 ChromeSSLHostStateDelegate::~ChromeSSLHostStateDelegate() {
274 }
275
276 void ChromeSSLHostStateDelegate::AllowCert(const std::string& host,
277                                            const net::X509Certificate& cert,
278                                            net::CertStatus error) {
279   GURL url = GetSecureGURLForHost(host);
280   const ContentSettingsPattern pattern =
281       ContentSettingsPattern::FromURLNoWildcard(url);
282   HostContentSettingsMap* map = profile_->GetHostContentSettingsMap();
283   scoped_ptr<base::Value> value(map->GetWebsiteSetting(
284       url, url, CONTENT_SETTINGS_TYPE_SSL_CERT_DECISIONS, std::string(), NULL));
285
286   if (!value.get() || !value->IsType(base::Value::TYPE_DICTIONARY))
287     value.reset(new base::DictionaryValue());
288
289   base::DictionaryValue* dict;
290   bool success = value->GetAsDictionary(&dict);
291   DCHECK(success);
292
293   bool expired_previous_decision;  // unused value in this function
294   base::DictionaryValue* cert_dict = GetValidCertDecisionsDict(
295       dict, CREATE_DICTIONARY_ENTRIES, &expired_previous_decision);
296   // If a a valid certificate dictionary cannot be extracted from the content
297   // setting, that means it's in an unknown format. Unfortunately, there's
298   // nothing to be done in that case, so a silent fail is the only option.
299   if (!cert_dict)
300     return;
301
302   dict->SetIntegerWithoutPathExpansion(kSSLCertDecisionVersionKey,
303                                        kDefaultSSLCertDecisionVersion);
304   cert_dict->SetIntegerWithoutPathExpansion(GetKey(cert, error), ALLOWED);
305
306   // The map takes ownership of the value, so it is released in the call to
307   // SetWebsiteSetting.
308   map->SetWebsiteSetting(pattern,
309                          pattern,
310                          CONTENT_SETTINGS_TYPE_SSL_CERT_DECISIONS,
311                          std::string(),
312                          value.release());
313 }
314
315 void ChromeSSLHostStateDelegate::Clear() {
316   profile_->GetHostContentSettingsMap()->ClearSettingsForOneType(
317       CONTENT_SETTINGS_TYPE_SSL_CERT_DECISIONS);
318 }
319
320 content::SSLHostStateDelegate::CertJudgment
321 ChromeSSLHostStateDelegate::QueryPolicy(const std::string& host,
322                                         const net::X509Certificate& cert,
323                                         net::CertStatus error,
324                                         bool* expired_previous_decision) {
325   HostContentSettingsMap* map = profile_->GetHostContentSettingsMap();
326   GURL url = GetSecureGURLForHost(host);
327   scoped_ptr<base::Value> value(map->GetWebsiteSetting(
328       url, url, CONTENT_SETTINGS_TYPE_SSL_CERT_DECISIONS, std::string(), NULL));
329
330   // Set a default value in case this method is short circuited and doesn't do a
331   // full query.
332   *expired_previous_decision = false;
333   if (!value.get() || !value->IsType(base::Value::TYPE_DICTIONARY))
334     return DENIED;
335
336   base::DictionaryValue* dict;  // Owned by value
337   int policy_decision;
338   bool success = value->GetAsDictionary(&dict);
339   DCHECK(success);
340
341   base::DictionaryValue* cert_error_dict;  // Owned by value
342   cert_error_dict = GetValidCertDecisionsDict(
343       dict, DO_NOT_CREATE_DICTIONARY_ENTRIES, expired_previous_decision);
344   if (!cert_error_dict) {
345     // This revoke is necessary to clear any old expired setting that may be
346     // lingering in the case that an old decision expried.
347     RevokeUserAllowExceptions(host);
348     return DENIED;
349   }
350
351   success = cert_error_dict->GetIntegerWithoutPathExpansion(GetKey(cert, error),
352                                                             &policy_decision);
353
354   // If a policy decision was successfully retrieved and it's a valid value of
355   // ALLOWED, return the valid value. Otherwise, return DENIED.
356   if (success && policy_decision == ALLOWED)
357     return ALLOWED;
358
359   return DENIED;
360 }
361
362 void ChromeSSLHostStateDelegate::RevokeUserAllowExceptions(
363     const std::string& host) {
364   GURL url = GetSecureGURLForHost(host);
365   const ContentSettingsPattern pattern =
366       ContentSettingsPattern::FromURLNoWildcard(url);
367   HostContentSettingsMap* map = profile_->GetHostContentSettingsMap();
368
369   map->SetWebsiteSetting(pattern,
370                          pattern,
371                          CONTENT_SETTINGS_TYPE_SSL_CERT_DECISIONS,
372                          std::string(),
373                          NULL);
374 }
375
376 // TODO(jww): This will revoke all of the decisions in the browser context.
377 // However, the networking stack actually keeps track of its own list of
378 // exceptions per-HttpNetworkTransaction in the SSLConfig structure (see the
379 // allowed_bad_certs Vector in net/ssl/ssl_config.h). This dual-tracking of
380 // exceptions introduces a problem where the browser context can revoke a
381 // certificate, but if a transaction reuses a cached version of the SSLConfig
382 // (probably from a pooled socket), it may bypass the intestitial layer.
383 //
384 // Over time, the cached versions should expire and it should converge on
385 // showing the interstitial. We probably need to introduce into the networking
386 // stack a way revoke SSLConfig's allowed_bad_certs lists per socket.
387 //
388 // For now, RevokeUserAllowExceptionsHard is our solution for the rare case
389 // where it is necessary to revoke the preferences immediately. It does so by
390 // flushing idle sockets, thus it is a big hammer and should be wielded with
391 // extreme caution as it can have a big, negative impact on network performance.
392 void ChromeSSLHostStateDelegate::RevokeUserAllowExceptionsHard(
393     const std::string& host) {
394   RevokeUserAllowExceptions(host);
395   scoped_refptr<net::URLRequestContextGetter> getter(
396       profile_->GetRequestContext());
397   getter->GetNetworkTaskRunner()->PostTask(
398       FROM_HERE, base::Bind(&CloseIdleConnections, getter));
399 }
400
401 bool ChromeSSLHostStateDelegate::HasAllowException(
402     const std::string& host) const {
403   GURL url = GetSecureGURLForHost(host);
404   const ContentSettingsPattern pattern =
405       ContentSettingsPattern::FromURLNoWildcard(url);
406   HostContentSettingsMap* map = profile_->GetHostContentSettingsMap();
407
408   scoped_ptr<base::Value> value(map->GetWebsiteSetting(
409       url, url, CONTENT_SETTINGS_TYPE_SSL_CERT_DECISIONS, std::string(), NULL));
410
411   if (!value.get() || !value->IsType(base::Value::TYPE_DICTIONARY))
412     return false;
413
414   base::DictionaryValue* dict;  // Owned by value
415   bool success = value->GetAsDictionary(&dict);
416   DCHECK(success);
417
418   for (base::DictionaryValue::Iterator it(*dict); !it.IsAtEnd(); it.Advance()) {
419     int policy_decision;  // Owned by dict
420     success = it.value().GetAsInteger(&policy_decision);
421     if (success && (static_cast<CertJudgment>(policy_decision) == ALLOWED))
422       return true;
423   }
424
425   return false;
426 }
427
428 void ChromeSSLHostStateDelegate::HostRanInsecureContent(const std::string& host,
429                                                         int pid) {
430   ran_insecure_content_hosts_.insert(BrokenHostEntry(host, pid));
431 }
432
433 bool ChromeSSLHostStateDelegate::DidHostRunInsecureContent(
434     const std::string& host,
435     int pid) const {
436   return !!ran_insecure_content_hosts_.count(BrokenHostEntry(host, pid));
437 }
438 void ChromeSSLHostStateDelegate::SetClock(scoped_ptr<base::Clock> clock) {
439   clock_.reset(clock.release());
440 }