ssl_blocking_page.cc revision eb525c5499e34cc9c4b825d6d9e75bb07cc06ace
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";
161const char kCondition19Policeman[] = "Condition19SSLPoliceman";
162const char kCondition20Stoplight[] = "Condition20SSLStoplight";
163const char kCondition21Badguy[] = "Condition21SSLBadguy";
164
165}  // namespace
166
167// Note that we always create a navigation entry with SSL errors.
168// No error happening loading a sub-resource triggers an interstitial so far.
169SSLBlockingPage::SSLBlockingPage(
170    content::WebContents* web_contents,
171    int cert_error,
172    const net::SSLInfo& ssl_info,
173    const GURL& request_url,
174    bool overridable,
175    bool strict_enforcement,
176    const base::Callback<void(bool)>& callback)
177    : callback_(callback),
178      web_contents_(web_contents),
179      cert_error_(cert_error),
180      ssl_info_(ssl_info),
181      request_url_(request_url),
182      overridable_(overridable),
183      strict_enforcement_(strict_enforcement) {
184  trialCondition_ = base::FieldTrialList::FindFullName(kStudyName);
185
186  RecordSSLBlockingPageEventStats(SHOW_ALL);
187  if (overridable_ && !strict_enforcement_)
188    RecordSSLBlockingPageEventStats(SHOW_OVERRIDABLE);
189
190  interstitial_page_ = InterstitialPage::Create(
191      web_contents_, true, request_url, this);
192  display_start_time_ = TimeTicks();
193  interstitial_page_->Show();
194}
195
196SSLBlockingPage::~SSLBlockingPage() {
197  if (!callback_.is_null()) {
198    RecordSSLBlockingPageTimeStats(
199      false, cert_error_,
200      overridable_ && !strict_enforcement_, display_start_time_,
201      base::TimeTicks::Now());
202    // The page is closed without the user having chosen what to do, default to
203    // deny.
204    NotifyDenyCertificate();
205  }
206}
207
208std::string SSLBlockingPage::GetHTMLContents() {
209  // Let's build the html error page.
210  DictionaryValue strings;
211  SSLErrorInfo error_info =
212      SSLErrorInfo::CreateError(SSLErrorInfo::NetErrorToErrorType(cert_error_),
213                                ssl_info_.cert.get(),
214                                request_url_);
215
216  int resource_id = IDR_SSL_ROAD_BLOCK_HTML;
217  strings.SetString("headLine", error_info.title());
218  strings.SetString("description", error_info.details());
219  strings.SetString("moreInfoTitle",
220      l10n_util::GetStringUTF16(IDS_CERT_ERROR_EXTRA_INFO_TITLE));
221  SetExtraInfo(&strings, error_info.extra_information());
222
223  strings.SetString("exit",
224                    l10n_util::GetStringUTF16(IDS_SSL_BLOCKING_PAGE_EXIT));
225
226  if (overridable_ && !strict_enforcement_) {
227    strings.SetString("title",
228                      l10n_util::GetStringUTF16(IDS_SSL_BLOCKING_PAGE_TITLE));
229    strings.SetString("proceed",
230                      l10n_util::GetStringUTF16(IDS_SSL_BLOCKING_PAGE_PROCEED));
231    strings.SetString("reasonForNotProceeding",
232                      l10n_util::GetStringUTF16(
233                          IDS_SSL_BLOCKING_PAGE_SHOULD_NOT_PROCEED));
234    strings.SetString("errorType", "overridable");
235  } else {
236    strings.SetString("title",
237                      l10n_util::GetStringUTF16(IDS_SSL_ERROR_PAGE_TITLE));
238    if (strict_enforcement_) {
239      strings.SetString("reasonForNotProceeding",
240                        l10n_util::GetStringUTF16(
241                            IDS_SSL_ERROR_PAGE_CANNOT_PROCEED));
242    } else {
243      strings.SetString("reasonForNotProceeding", std::string());
244    }
245    strings.SetString("errorType", "notoverridable");
246  }
247
248  strings.SetString("textdirection", base::i18n::IsRTL() ? "rtl" : "ltr");
249
250  // Set up the Finch trial layouts.
251  strings.SetString("trialType", trialCondition_);
252  if (trialCondition_ == kCondition16Firefox ||
253      trialCondition_ == kCondition17FancyFirefox ||
254      trialCondition_ == kCondition18NoImages) {
255    strings.SetString("domain", request_url_.host());
256    std::string font_family = l10n_util::GetStringUTF8(IDS_WEB_FONT_FAMILY);
257#if defined(OS_WIN)
258    if (base::win::GetVersion() < base::win::VERSION_VISTA) {
259      font_family = l10n_util::GetStringUTF8(IDS_WEB_FONT_FAMILY_XP);
260    }
261#endif
262#if defined(TOOLKIT_GTK)
263    font_family = ui::ResourceBundle::GetSharedInstance().GetFont(
264        ui::ResourceBundle::BaseFont).GetFontName() + ", " + font_family;
265#endif
266    strings.SetString("fontfamily", font_family);
267    if (trialCondition_ == kCondition16Firefox ||
268        trialCondition_ == kCondition18NoImages) {
269      resource_id = IDR_SSL_FIREFOX_HTML;
270    } else if (trialCondition_ == kCondition17FancyFirefox) {
271      resource_id = IDR_SSL_FANCY_FIREFOX_HTML;
272    }
273  }
274
275  base::StringPiece html(
276      ResourceBundle::GetSharedInstance().GetRawDataResource(
277          resource_id));
278
279  return webui::GetI18nTemplateHtml(html, &strings);
280}
281
282void SSLBlockingPage::OverrideEntry(NavigationEntry* entry) {
283  int cert_id = content::CertStore::GetInstance()->StoreCert(
284      ssl_info_.cert.get(), web_contents_->GetRenderProcessHost()->GetID());
285
286  entry->GetSSL().security_style =
287      content::SECURITY_STYLE_AUTHENTICATION_BROKEN;
288  entry->GetSSL().cert_id = cert_id;
289  entry->GetSSL().cert_status = ssl_info_.cert_status;
290  entry->GetSSL().security_bits = ssl_info_.security_bits;
291#if !defined(OS_ANDROID)
292  Browser* browser = chrome::FindBrowserWithWebContents(web_contents_);
293  if (browser)
294    browser->VisibleSSLStateChanged(web_contents_);
295#endif  // !defined(OS_ANDROID)
296}
297
298// Matches events defined in ssl_error.html and ssl_roadblock.html.
299void SSLBlockingPage::CommandReceived(const std::string& command) {
300  int cmd = atoi(command.c_str());
301  if (cmd == CMD_DONT_PROCEED) {
302    interstitial_page_->DontProceed();
303  } else if (cmd == CMD_PROCEED) {
304    interstitial_page_->Proceed();
305  } else if (cmd == CMD_FOCUS) {
306    // Start recording the time when the page is first in focus
307    display_start_time_ = base::TimeTicks::Now();
308  } else if (cmd == CMD_MORE) {
309    RecordSSLBlockingPageEventStats(MORE);
310  } else if (cmd == CMD_SHOW_UNDERSTAND) {
311    // Used in the Finch experiment.
312    RecordSSLBlockingPageEventStats(SHOW_UNDERSTAND);
313  }
314}
315
316void SSLBlockingPage::OverrideRendererPrefs(
317      content::RendererPreferences* prefs) {
318  Profile* profile = Profile::FromBrowserContext(
319      web_contents_->GetBrowserContext());
320  renderer_preferences_util::UpdateFromSystemSettings(prefs, profile);
321}
322
323void SSLBlockingPage::OnProceed() {
324  RecordSSLBlockingPageTimeStats(true, cert_error_,
325      overridable_ && !strict_enforcement_, display_start_time_,
326      base::TimeTicks::Now());
327
328  // Accepting the certificate resumes the loading of the page.
329  NotifyAllowCertificate();
330}
331
332void SSLBlockingPage::OnDontProceed() {
333  RecordSSLBlockingPageTimeStats(false, cert_error_,
334    overridable_ && !strict_enforcement_, display_start_time_,
335    base::TimeTicks::Now());
336
337  NotifyDenyCertificate();
338}
339
340void SSLBlockingPage::NotifyDenyCertificate() {
341  // It's possible that callback_ may not exist if the user clicks "Proceed"
342  // followed by pressing the back button before the interstitial is hidden.
343  // In that case the certificate will still be treated as allowed.
344  if (callback_.is_null())
345    return;
346
347  callback_.Run(false);
348  callback_.Reset();
349}
350
351void SSLBlockingPage::NotifyAllowCertificate() {
352  DCHECK(!callback_.is_null());
353
354  callback_.Run(true);
355  callback_.Reset();
356}
357
358// static
359void SSLBlockingPage::SetExtraInfo(
360    DictionaryValue* strings,
361    const std::vector<string16>& extra_info) {
362  DCHECK_LT(extra_info.size(), 5U);  // We allow 5 paragraphs max.
363  const char* keys[5] = {
364      "moreInfo1", "moreInfo2", "moreInfo3", "moreInfo4", "moreInfo5"
365  };
366  int i;
367  for (i = 0; i < static_cast<int>(extra_info.size()); i++) {
368    strings->SetString(keys[i], extra_info[i]);
369  }
370  for (; i < 5; i++) {
371    strings->SetString(keys[i], std::string());
372  }
373}
374