ssl_blocking_page.cc revision 868fa2fe829687343ffae624259930155e16dbd8
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
3// found in the LICENSE file.
4
5#include "chrome/browser/ssl/ssl_blocking_page.h"
6
7#include "base/i18n/rtl.h"
8#include "base/metrics/field_trial.h"
9#include "base/metrics/histogram.h"
10#include "base/strings/string_piece.h"
11#include "base/strings/utf_string_conversions.h"
12#include "base/values.h"
13#include "chrome/browser/profiles/profile.h"
14#include "chrome/browser/renderer_preferences_util.h"
15#include "chrome/browser/ssl/ssl_error_info.h"
16#include "chrome/browser/ui/browser.h"
17#include "chrome/browser/ui/browser_finder.h"
18#include "content/public/browser/cert_store.h"
19#include "content/public/browser/interstitial_page.h"
20#include "content/public/browser/navigation_controller.h"
21#include "content/public/browser/navigation_entry.h"
22#include "content/public/browser/notification_service.h"
23#include "content/public/browser/notification_types.h"
24#include "content/public/browser/render_process_host.h"
25#include "content/public/browser/render_view_host.h"
26#include "content/public/browser/web_contents.h"
27#include "content/public/common/ssl_status.h"
28#include "grit/app_locale_settings.h"
29#include "grit/browser_resources.h"
30#include "grit/generated_resources.h"
31#include "net/base/net_errors.h"
32#include "ui/base/l10n/l10n_util.h"
33#include "ui/base/resource/resource_bundle.h"
34#include "ui/webui/jstemplate_builder.h"
35
36#if defined(OS_WIN)
37#include "base/win/windows_version.h"
38#endif
39
40using base::TimeDelta;
41using base::TimeTicks;
42using content::InterstitialPage;
43using content::NavigationController;
44using content::NavigationEntry;
45
46#define HISTOGRAM_INTERSTITIAL_SMALL_TIME(name, sample) \
47    UMA_HISTOGRAM_CUSTOM_TIMES( \
48        name, \
49        sample, \
50        base::TimeDelta::FromMilliseconds(400), \
51        base::TimeDelta::FromMinutes(15), 75);
52
53#define HISTOGRAM_INTERSTITIAL_LARGE_TIME(name, sample) \
54    UMA_HISTOGRAM_CUSTOM_TIMES( \
55        name, \
56        sample, \
57        base::TimeDelta::FromMilliseconds(400), \
58        base::TimeDelta::FromMinutes(20), 50);
59
60namespace {
61
62// These represent the commands sent by ssl_roadblock.html.
63enum SSLBlockingPageCommands {
64  CMD_DONT_PROCEED,
65  CMD_PROCEED,
66  CMD_FOCUS,
67  CMD_MORE,
68  CMD_SHOW_UNDERSTAND,  // Used by the Finch trial.
69};
70
71// Events for UMA.
72enum SSLBlockingPageEvent {
73  SHOW_ALL,
74  SHOW_OVERRIDABLE,
75  PROCEED_OVERRIDABLE,
76  PROCEED_NAME,
77  PROCEED_DATE,
78  PROCEED_AUTHORITY,
79  DONT_PROCEED_OVERRIDABLE,
80  DONT_PROCEED_NAME,
81  DONT_PROCEED_DATE,
82  DONT_PROCEED_AUTHORITY,
83  MORE,
84  SHOW_UNDERSTAND,
85  UNUSED_BLOCKING_PAGE_EVENT,
86};
87
88void RecordSSLBlockingPageEventStats(SSLBlockingPageEvent event) {
89  UMA_HISTOGRAM_ENUMERATION("interstitial.ssl",
90                            event,
91                            UNUSED_BLOCKING_PAGE_EVENT);
92}
93
94void RecordSSLBlockingPageTimeStats(
95    bool proceed,
96    int cert_error,
97    bool overridable,
98    const base::TimeTicks& start_time,
99    const base::TimeTicks& end_time) {
100  UMA_HISTOGRAM_ENUMERATION("interstitial.ssl_error_type",
101     SSLErrorInfo::NetErrorToErrorType(cert_error), SSLErrorInfo::END_OF_ENUM);
102  if (start_time.is_null() || !overridable) {
103    // A null start time will occur if the page never came into focus and the
104    // user quit without seeing it. If so, we don't record the time.
105    // The user might not have an option except to turn back; that happens
106    // if overridable is true.  If so, the time/outcome isn't meaningful.
107    return;
108  }
109  base::TimeDelta delta = end_time - start_time;
110  if (proceed) {
111    RecordSSLBlockingPageEventStats(PROCEED_OVERRIDABLE);
112    HISTOGRAM_INTERSTITIAL_LARGE_TIME("interstitial.ssl_accept_time", delta);
113  } else if (!proceed) {
114    RecordSSLBlockingPageEventStats(DONT_PROCEED_OVERRIDABLE);
115    HISTOGRAM_INTERSTITIAL_LARGE_TIME("interstitial.ssl_reject_time", delta);
116  }
117  SSLErrorInfo::ErrorType type = SSLErrorInfo::NetErrorToErrorType(cert_error);
118  switch (type) {
119    case SSLErrorInfo::CERT_COMMON_NAME_INVALID: {
120      HISTOGRAM_INTERSTITIAL_SMALL_TIME(
121          "interstitial.common_name_invalid_time",
122          delta);
123      if (proceed)
124        RecordSSLBlockingPageEventStats(PROCEED_NAME);
125      else
126        RecordSSLBlockingPageEventStats(DONT_PROCEED_NAME);
127      break;
128    }
129    case SSLErrorInfo::CERT_DATE_INVALID: {
130      HISTOGRAM_INTERSTITIAL_SMALL_TIME(
131          "interstitial.date_invalid_time",
132          delta);
133      if (proceed)
134        RecordSSLBlockingPageEventStats(PROCEED_DATE);
135      else
136        RecordSSLBlockingPageEventStats(DONT_PROCEED_DATE);
137      break;
138    }
139    case SSLErrorInfo::CERT_AUTHORITY_INVALID: {
140      HISTOGRAM_INTERSTITIAL_SMALL_TIME(
141          "interstitial.authority_invalid_time",
142          delta);
143      if (proceed)
144        RecordSSLBlockingPageEventStats(PROCEED_AUTHORITY);
145      else
146        RecordSSLBlockingPageEventStats(DONT_PROCEED_AUTHORITY);
147      break;
148    }
149    default: {
150      break;
151    }
152  }
153}
154
155// These are the constants for the Finch experiment.
156const char kStudyName[] = "InterstitialSSL517";
157const char kCondition15Control[] = "Condition15SSLControl";
158const char kCondition16Firefox[] = "Condition16SSLFirefox";
159const char kCondition17FancyFirefox[] = "Condition17SSLFancyFirefox";
160const char kCondition18NoImages[] = "Condition18SSLNoImages";
161
162}  // namespace
163
164// Note that we always create a navigation entry with SSL errors.
165// No error happening loading a sub-resource triggers an interstitial so far.
166SSLBlockingPage::SSLBlockingPage(
167    content::WebContents* web_contents,
168    int cert_error,
169    const net::SSLInfo& ssl_info,
170    const GURL& request_url,
171    bool overridable,
172    bool strict_enforcement,
173    const base::Callback<void(bool)>& callback)
174    : callback_(callback),
175      web_contents_(web_contents),
176      cert_error_(cert_error),
177      ssl_info_(ssl_info),
178      request_url_(request_url),
179      overridable_(overridable),
180      strict_enforcement_(strict_enforcement) {
181  trialCondition_ = base::FieldTrialList::FindFullName(kStudyName);
182
183  RecordSSLBlockingPageEventStats(SHOW_ALL);
184  if (overridable_ && !strict_enforcement_)
185    RecordSSLBlockingPageEventStats(SHOW_OVERRIDABLE);
186
187  interstitial_page_ = InterstitialPage::Create(
188      web_contents_, true, request_url, this);
189  display_start_time_ = TimeTicks();
190  interstitial_page_->Show();
191}
192
193SSLBlockingPage::~SSLBlockingPage() {
194  if (!callback_.is_null()) {
195    RecordSSLBlockingPageTimeStats(
196      false, cert_error_,
197      overridable_ && !strict_enforcement_, display_start_time_,
198      base::TimeTicks::Now());
199    // The page is closed without the user having chosen what to do, default to
200    // deny.
201    NotifyDenyCertificate();
202  }
203}
204
205std::string SSLBlockingPage::GetHTMLContents() {
206  // Let's build the html error page.
207  DictionaryValue strings;
208  SSLErrorInfo error_info =
209      SSLErrorInfo::CreateError(SSLErrorInfo::NetErrorToErrorType(cert_error_),
210                                ssl_info_.cert.get(),
211                                request_url_);
212
213  int resource_id = IDR_SSL_ROAD_BLOCK_HTML;
214  strings.SetString("headLine", error_info.title());
215  strings.SetString("description", error_info.details());
216  strings.SetString("moreInfoTitle",
217      l10n_util::GetStringUTF16(IDS_CERT_ERROR_EXTRA_INFO_TITLE));
218  SetExtraInfo(&strings, error_info.extra_information());
219
220  strings.SetString("exit",
221                    l10n_util::GetStringUTF16(IDS_SSL_BLOCKING_PAGE_EXIT));
222
223  if (overridable_ && !strict_enforcement_) {
224    strings.SetString("title",
225                      l10n_util::GetStringUTF16(IDS_SSL_BLOCKING_PAGE_TITLE));
226    strings.SetString("proceed",
227                      l10n_util::GetStringUTF16(IDS_SSL_BLOCKING_PAGE_PROCEED));
228    strings.SetString("reasonForNotProceeding",
229                      l10n_util::GetStringUTF16(
230                          IDS_SSL_BLOCKING_PAGE_SHOULD_NOT_PROCEED));
231    strings.SetString("errorType", "overridable");
232  } else {
233    strings.SetString("title",
234                      l10n_util::GetStringUTF16(IDS_SSL_ERROR_PAGE_TITLE));
235    if (strict_enforcement_) {
236      strings.SetString("reasonForNotProceeding",
237                        l10n_util::GetStringUTF16(
238                            IDS_SSL_ERROR_PAGE_CANNOT_PROCEED));
239    } else {
240      strings.SetString("reasonForNotProceeding", std::string());
241    }
242    strings.SetString("errorType", "notoverridable");
243  }
244
245  strings.SetString("textdirection", base::i18n::IsRTL() ? "rtl" : "ltr");
246
247  // Set up the Finch trial layouts.
248  strings.SetString("trialType", trialCondition_);
249  if (trialCondition_ == kCondition16Firefox ||
250      trialCondition_ == kCondition17FancyFirefox ||
251      trialCondition_ == kCondition18NoImages) {
252    strings.SetString("domain", request_url_.host());
253    std::string font_family = l10n_util::GetStringUTF8(IDS_WEB_FONT_FAMILY);
254#if defined(OS_WIN)
255    if (base::win::GetVersion() < base::win::VERSION_VISTA) {
256      font_family = l10n_util::GetStringUTF8(IDS_WEB_FONT_FAMILY_XP);
257    }
258#endif
259#if defined(TOOLKIT_GTK)
260    font_family = ui::ResourceBundle::GetSharedInstance().GetFont(
261        ui::ResourceBundle::BaseFont).GetFontName() + ", " + font_family;
262#endif
263    strings.SetString("fontfamily", font_family);
264    if (trialCondition_ == kCondition16Firefox ||
265        trialCondition_ == kCondition18NoImages) {
266      resource_id = IDR_SSL_FIREFOX_HTML;
267    } else if (trialCondition_ == kCondition17FancyFirefox) {
268      resource_id = IDR_SSL_FANCY_FIREFOX_HTML;
269    }
270  }
271
272  base::StringPiece html(
273      ResourceBundle::GetSharedInstance().GetRawDataResource(
274          resource_id));
275
276  return webui::GetI18nTemplateHtml(html, &strings);
277}
278
279void SSLBlockingPage::OverrideEntry(NavigationEntry* entry) {
280  int cert_id = content::CertStore::GetInstance()->StoreCert(
281      ssl_info_.cert.get(), web_contents_->GetRenderProcessHost()->GetID());
282
283  entry->GetSSL().security_style =
284      content::SECURITY_STYLE_AUTHENTICATION_BROKEN;
285  entry->GetSSL().cert_id = cert_id;
286  entry->GetSSL().cert_status = ssl_info_.cert_status;
287  entry->GetSSL().security_bits = ssl_info_.security_bits;
288#if !defined(OS_ANDROID)
289  Browser* browser = chrome::FindBrowserWithWebContents(web_contents_);
290  if (browser)
291    browser->VisibleSSLStateChanged(web_contents_);
292#endif  // !defined(OS_ANDROID)
293}
294
295// Matches events defined in ssl_error.html and ssl_roadblock.html.
296void SSLBlockingPage::CommandReceived(const std::string& command) {
297  int cmd = atoi(command.c_str());
298  if (cmd == CMD_DONT_PROCEED) {
299    interstitial_page_->DontProceed();
300  } else if (cmd == CMD_PROCEED) {
301    interstitial_page_->Proceed();
302  } else if (cmd == CMD_FOCUS) {
303    // Start recording the time when the page is first in focus
304    display_start_time_ = base::TimeTicks::Now();
305  } else if (cmd == CMD_MORE) {
306    RecordSSLBlockingPageEventStats(MORE);
307  } else if (cmd == CMD_SHOW_UNDERSTAND) {
308    // Used in the Finch experiment.
309    RecordSSLBlockingPageEventStats(SHOW_UNDERSTAND);
310  }
311}
312
313void SSLBlockingPage::OverrideRendererPrefs(
314      content::RendererPreferences* prefs) {
315  Profile* profile = Profile::FromBrowserContext(
316      web_contents_->GetBrowserContext());
317  renderer_preferences_util::UpdateFromSystemSettings(prefs, profile);
318}
319
320void SSLBlockingPage::OnProceed() {
321  RecordSSLBlockingPageTimeStats(true, cert_error_,
322      overridable_ && !strict_enforcement_, display_start_time_,
323      base::TimeTicks::Now());
324
325  // Accepting the certificate resumes the loading of the page.
326  NotifyAllowCertificate();
327}
328
329void SSLBlockingPage::OnDontProceed() {
330  RecordSSLBlockingPageTimeStats(false, cert_error_,
331    overridable_ && !strict_enforcement_, display_start_time_,
332    base::TimeTicks::Now());
333
334  NotifyDenyCertificate();
335}
336
337void SSLBlockingPage::NotifyDenyCertificate() {
338  // It's possible that callback_ may not exist if the user clicks "Proceed"
339  // followed by pressing the back button before the interstitial is hidden.
340  // In that case the certificate will still be treated as allowed.
341  if (callback_.is_null())
342    return;
343
344  callback_.Run(false);
345  callback_.Reset();
346}
347
348void SSLBlockingPage::NotifyAllowCertificate() {
349  DCHECK(!callback_.is_null());
350
351  callback_.Run(true);
352  callback_.Reset();
353}
354
355// static
356void SSLBlockingPage::SetExtraInfo(
357    DictionaryValue* strings,
358    const std::vector<string16>& extra_info) {
359  DCHECK_LT(extra_info.size(), 5U);  // We allow 5 paragraphs max.
360  const char* keys[5] = {
361      "moreInfo1", "moreInfo2", "moreInfo3", "moreInfo4", "moreInfo5"
362  };
363  int i;
364  for (i = 0; i < static_cast<int>(extra_info.size()); i++) {
365    strings->SetString(keys[i], extra_info[i]);
366  }
367  for (; i < 5; i++) {
368    strings->SetString(keys[i], std::string());
369  }
370}
371