page_info_model.cc revision 513209b27ff55e2841eac0e4120199c23acce758
1// Copyright (c) 2010 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/page_info_model.h"
6
7#include <string>
8
9#include "app/l10n_util.h"
10#include "app/resource_bundle.h"
11#include "base/command_line.h"
12#include "base/i18n/time_formatting.h"
13#include "base/string_number_conversions.h"
14#include "base/utf_string_conversions.h"
15#include "chrome/browser/cert_store.h"
16#include "chrome/browser/prefs/pref_service.h"
17#include "chrome/browser/profile.h"
18#include "chrome/browser/ssl/ssl_manager.h"
19#include "chrome/common/chrome_switches.h"
20#include "chrome/common/pref_names.h"
21#include "grit/generated_resources.h"
22#include "grit/theme_resources.h"
23#include "net/base/cert_status_flags.h"
24#include "net/base/ssl_connection_status_flags.h"
25#include "net/base/ssl_cipher_suite_names.h"
26#include "net/base/x509_certificate.h"
27
28#if defined(OS_MACOSX)
29#include "base/mac_util.h"
30#endif
31
32PageInfoModel::PageInfoModel(Profile* profile,
33                             const GURL& url,
34                             const NavigationEntry::SSLStatus& ssl,
35                             bool show_history,
36                             PageInfoModelObserver* observer)
37    : observer_(observer) {
38  Init();
39
40  SectionStateIcon icon_id = ICON_STATE_OK;
41  string16 headline;
42  string16 description;
43  scoped_refptr<net::X509Certificate> cert;
44
45  // Identity section.
46  string16 subject_name(UTF8ToUTF16(url.host()));
47  bool empty_subject_name = false;
48  if (subject_name.empty()) {
49    subject_name.assign(
50        l10n_util::GetStringUTF16(IDS_PAGE_INFO_SECURITY_TAB_UNKNOWN_PARTY));
51    empty_subject_name = true;
52  }
53
54  // Some of what IsCertStatusError classifies as errors we want to show as
55  // warnings instead.
56  static const int cert_warnings =
57      net::CERT_STATUS_UNABLE_TO_CHECK_REVOCATION |
58      net::CERT_STATUS_NO_REVOCATION_MECHANISM;
59  int status_with_warnings_removed = ssl.cert_status() & ~cert_warnings;
60
61  if (ssl.cert_id() &&
62      CertStore::GetSharedInstance()->RetrieveCert(ssl.cert_id(), &cert) &&
63      !net::IsCertStatusError(status_with_warnings_removed)) {
64    // No error found so far, check cert_status warnings.
65    int cert_status = ssl.cert_status();
66    if (cert_status & cert_warnings) {
67      string16 issuer_name(UTF8ToUTF16(cert->issuer().GetDisplayName()));
68      if (issuer_name.empty()) {
69        issuer_name.assign(l10n_util::GetStringUTF16(
70            IDS_PAGE_INFO_SECURITY_TAB_UNKNOWN_PARTY));
71      }
72      description.assign(l10n_util::GetStringFUTF16(
73          IDS_PAGE_INFO_SECURITY_TAB_SECURE_IDENTITY, issuer_name));
74
75      description += ASCIIToUTF16("\n\n");
76      if (cert_status & net::CERT_STATUS_UNABLE_TO_CHECK_REVOCATION) {
77        description += l10n_util::GetStringUTF16(
78            IDS_PAGE_INFO_SECURITY_TAB_UNABLE_TO_CHECK_REVOCATION);
79      } else if (cert_status & net::CERT_STATUS_NO_REVOCATION_MECHANISM) {
80        description += l10n_util::GetStringUTF16(
81            IDS_PAGE_INFO_SECURITY_TAB_NO_REVOCATION_MECHANISM);
82      } else {
83        NOTREACHED() << "Need to specify string for this warning";
84      }
85      icon_id = ICON_STATE_WARNING_MINOR;
86    } else if ((ssl.cert_status() & net::CERT_STATUS_IS_EV) != 0) {
87      // EV HTTPS page.
88      DCHECK(!cert->subject().organization_names.empty());
89      headline =
90          l10n_util::GetStringFUTF16(IDS_PAGE_INFO_EV_IDENTITY_TITLE,
91              UTF8ToUTF16(cert->subject().organization_names[0]),
92              UTF8ToUTF16(url.host()));
93      // An EV Cert is required to have a city (localityName) and country but
94      // state is "if any".
95      DCHECK(!cert->subject().locality_name.empty());
96      DCHECK(!cert->subject().country_name.empty());
97      string16 locality;
98      if (!cert->subject().state_or_province_name.empty()) {
99        locality = l10n_util::GetStringFUTF16(
100            IDS_PAGEINFO_ADDRESS,
101            UTF8ToUTF16(cert->subject().locality_name),
102            UTF8ToUTF16(cert->subject().state_or_province_name),
103            UTF8ToUTF16(cert->subject().country_name));
104      } else {
105        locality = l10n_util::GetStringFUTF16(
106            IDS_PAGEINFO_PARTIAL_ADDRESS,
107            UTF8ToUTF16(cert->subject().locality_name),
108            UTF8ToUTF16(cert->subject().country_name));
109      }
110      DCHECK(!cert->subject().organization_names.empty());
111      description.assign(l10n_util::GetStringFUTF16(
112          IDS_PAGE_INFO_SECURITY_TAB_SECURE_IDENTITY_EV,
113          UTF8ToUTF16(cert->subject().organization_names[0]),
114          locality,
115          UTF8ToUTF16(cert->issuer().GetDisplayName())));
116    } else if ((ssl.cert_status() & net::CERT_STATUS_IS_DNSSEC) != 0) {
117      // DNSSEC authenticated page.
118      if (empty_subject_name)
119        headline.clear();  // Don't display any title.
120      else
121        headline.assign(subject_name);
122      description.assign(l10n_util::GetStringFUTF16(
123          IDS_PAGE_INFO_SECURITY_TAB_SECURE_IDENTITY, UTF8ToUTF16("DNSSEC")));
124    } else {
125      // Non-EV OK HTTPS page.
126      if (empty_subject_name)
127        headline.clear();  // Don't display any title.
128      else
129        headline.assign(subject_name);
130      string16 issuer_name(UTF8ToUTF16(cert->issuer().GetDisplayName()));
131      if (issuer_name.empty()) {
132        issuer_name.assign(l10n_util::GetStringUTF16(
133            IDS_PAGE_INFO_SECURITY_TAB_UNKNOWN_PARTY));
134      }
135      description.assign(l10n_util::GetStringFUTF16(
136          IDS_PAGE_INFO_SECURITY_TAB_SECURE_IDENTITY, issuer_name));
137    }
138  } else {
139    // HTTP or HTTPS with errors (not warnings).
140    description.assign(l10n_util::GetStringUTF16(
141        IDS_PAGE_INFO_SECURITY_TAB_INSECURE_IDENTITY));
142    icon_id = ssl.security_style() == SECURITY_STYLE_UNAUTHENTICATED ?
143        ICON_STATE_WARNING_MAJOR : ICON_STATE_ERROR;
144
145    if (ssl.cert_status() & net::CERT_STATUS_NON_UNIQUE_NAME) {
146      description += ASCIIToUTF16("\n\n");
147      description += l10n_util::GetStringUTF16(
148          IDS_PAGE_INFO_SECURITY_TAB_NON_UNIQUE_NAME);
149    }
150  }
151  sections_.push_back(SectionInfo(
152      icon_id,
153      headline,
154      description,
155      SECTION_INFO_IDENTITY));
156
157  // Connection section.
158  // We consider anything less than 80 bits encryption to be weak encryption.
159  // TODO(wtc): Bug 1198735: report mixed/unsafe content for unencrypted and
160  // weakly encrypted connections.
161  icon_id = ICON_STATE_OK;
162  headline.clear();
163  description.clear();
164  if (ssl.security_bits() < 0) {
165    // Security strength is unknown.  Say nothing.
166    icon_id = ICON_STATE_ERROR;
167  } else if (ssl.security_bits() == 0) {
168    icon_id = ssl.security_style() == SECURITY_STYLE_UNAUTHENTICATED ?
169        ICON_STATE_WARNING_MAJOR : ICON_STATE_ERROR;
170    description.assign(l10n_util::GetStringFUTF16(
171        IDS_PAGE_INFO_SECURITY_TAB_NOT_ENCRYPTED_CONNECTION_TEXT,
172        subject_name));
173  } else if (ssl.security_bits() < 80) {
174    icon_id = ICON_STATE_ERROR;
175    description.assign(l10n_util::GetStringFUTF16(
176        IDS_PAGE_INFO_SECURITY_TAB_WEAK_ENCRYPTION_CONNECTION_TEXT,
177        subject_name));
178  } else {
179    description.assign(l10n_util::GetStringFUTF16(
180        IDS_PAGE_INFO_SECURITY_TAB_ENCRYPTED_CONNECTION_TEXT,
181        subject_name,
182        base::IntToString16(ssl.security_bits())));
183    if (ssl.displayed_insecure_content() || ssl.ran_insecure_content()) {
184      icon_id = ssl.ran_insecure_content() ?
185          ICON_STATE_ERROR : ICON_STATE_WARNING_MINOR;
186      description.assign(l10n_util::GetStringFUTF16(
187          IDS_PAGE_INFO_SECURITY_TAB_ENCRYPTED_SENTENCE_LINK,
188          description,
189          l10n_util::GetStringUTF16(ssl.ran_insecure_content() ?
190              IDS_PAGE_INFO_SECURITY_TAB_ENCRYPTED_INSECURE_CONTENT_ERROR :
191              IDS_PAGE_INFO_SECURITY_TAB_ENCRYPTED_INSECURE_CONTENT_WARNING)));
192    }
193  }
194
195  uint16 cipher_suite =
196      net::SSLConnectionStatusToCipherSuite(ssl.connection_status());
197  if (ssl.security_bits() > 0 && cipher_suite) {
198    int ssl_version =
199        net::SSLConnectionStatusToVersion(ssl.connection_status());
200    const char* ssl_version_str;
201    net::SSLVersionToString(&ssl_version_str, ssl_version);
202    description += ASCIIToUTF16("\n\n");
203    description += l10n_util::GetStringFUTF16(
204        IDS_PAGE_INFO_SECURITY_TAB_SSL_VERSION,
205        ASCIIToUTF16(ssl_version_str));
206
207    bool did_fallback = (ssl.connection_status() &
208                         net::SSL_CONNECTION_SSL3_FALLBACK) != 0;
209    bool no_renegotiation =
210        (ssl.connection_status() &
211        net::SSL_CONNECTION_NO_RENEGOTIATION_EXTENSION) != 0;
212    const char *key_exchange, *cipher, *mac;
213    net::SSLCipherSuiteToStrings(&key_exchange, &cipher, &mac, cipher_suite);
214
215    description += ASCIIToUTF16("\n\n");
216    description += l10n_util::GetStringFUTF16(
217        IDS_PAGE_INFO_SECURITY_TAB_ENCRYPTION_DETAILS,
218        ASCIIToUTF16(cipher), ASCIIToUTF16(mac), ASCIIToUTF16(key_exchange));
219
220    description += ASCIIToUTF16("\n\n");
221    uint8 compression_id =
222        net::SSLConnectionStatusToCompression(ssl.connection_status());
223    if (compression_id) {
224      const char* compression;
225      net::SSLCompressionToString(&compression, compression_id);
226      description += l10n_util::GetStringFUTF16(
227          IDS_PAGE_INFO_SECURITY_TAB_COMPRESSION_DETAILS,
228          ASCIIToUTF16(compression));
229    } else {
230      description += l10n_util::GetStringUTF16(
231          IDS_PAGE_INFO_SECURITY_TAB_NO_COMPRESSION);
232    }
233
234    if (did_fallback) {
235      // For now, only SSLv3 fallback will trigger a warning icon.
236      icon_id = ICON_STATE_ERROR;
237      description += ASCIIToUTF16("\n\n");
238      description += l10n_util::GetStringUTF16(
239          IDS_PAGE_INFO_SECURITY_TAB_FALLBACK_MESSAGE);
240    }
241    if (no_renegotiation) {
242      description += ASCIIToUTF16("\n\n");
243      description += l10n_util::GetStringUTF16(
244          IDS_PAGE_INFO_SECURITY_TAB_RENEGOTIATION_MESSAGE);
245    }
246  }
247
248  if (!description.empty()) {
249    sections_.push_back(SectionInfo(
250        icon_id,
251        headline,
252        description,
253        SECTION_INFO_CONNECTION));
254  }
255
256  // Request the number of visits.
257  HistoryService* history = profile->GetHistoryService(
258      Profile::EXPLICIT_ACCESS);
259  if (show_history && history) {
260    history->GetVisitCountToHost(
261        url,
262        &request_consumer_,
263        NewCallback(this, &PageInfoModel::OnGotVisitCountToHost));
264  }
265}
266
267PageInfoModel::~PageInfoModel() {
268#if defined(OS_MACOSX)
269  // Release the NSImages.
270  for (std::vector<gfx::NativeImage>::iterator it = icons_.begin();
271       it != icons_.end(); ++it) {
272    mac_util::NSObjectRelease(*it);
273  }
274#endif
275}
276
277int PageInfoModel::GetSectionCount() {
278  return sections_.size();
279}
280
281PageInfoModel::SectionInfo PageInfoModel::GetSectionInfo(int index) {
282  DCHECK(index < static_cast<int>(sections_.size()));
283  return sections_[index];
284}
285
286gfx::NativeImage PageInfoModel::GetIconImage(SectionStateIcon icon_id) {
287  if (icon_id == ICON_NONE)
288    return NULL;
289  // The bubble uses new, various icons.
290  return icons_[icon_id];
291}
292
293void PageInfoModel::OnGotVisitCountToHost(HistoryService::Handle handle,
294                                          bool found_visits,
295                                          int count,
296                                          base::Time first_visit) {
297  if (!found_visits) {
298    // This indicates an error, such as the page wasn't http/https; do nothing.
299    return;
300  }
301
302  bool visited_before_today = false;
303  if (count) {
304    base::Time today = base::Time::Now().LocalMidnight();
305    base::Time first_visit_midnight = first_visit.LocalMidnight();
306    visited_before_today = (first_visit_midnight < today);
307  }
308
309  string16 headline = l10n_util::GetStringUTF16(IDS_PAGE_INFO_SITE_INFO_TITLE);
310
311  if (!visited_before_today) {
312    sections_.push_back(SectionInfo(
313        ICON_STATE_WARNING_MAJOR,
314        headline,
315        l10n_util::GetStringUTF16(
316            IDS_PAGE_INFO_SECURITY_TAB_FIRST_VISITED_TODAY),
317        SECTION_INFO_FIRST_VISIT));
318  } else {
319    sections_.push_back(SectionInfo(
320        ICON_STATE_INFO,
321        headline,
322        l10n_util::GetStringFUTF16(
323            IDS_PAGE_INFO_SECURITY_TAB_VISITED_BEFORE_TODAY,
324            WideToUTF16(base::TimeFormatShortDate(first_visit))),
325        SECTION_INFO_FIRST_VISIT));
326  }
327  observer_->ModelChanged();
328}
329
330PageInfoModel::PageInfoModel() : observer_(NULL) {
331  Init();
332}
333
334void PageInfoModel::Init() {
335  // Loads the icons into the vector. The order must match the SectionStateIcon
336  // enum.
337  icons_.push_back(GetBitmapNamed(IDR_PAGEINFO_GOOD));
338  icons_.push_back(GetBitmapNamed(IDR_PAGEINFO_WARNING_MINOR));
339  icons_.push_back(GetBitmapNamed(IDR_PAGEINFO_WARNING_MAJOR));
340  icons_.push_back(GetBitmapNamed(IDR_PAGEINFO_BAD));
341  icons_.push_back(GetBitmapNamed(IDR_PAGEINFO_INFO));
342}
343
344gfx::NativeImage PageInfoModel::GetBitmapNamed(int resource_id) {
345  ResourceBundle& rb = ResourceBundle::GetSharedInstance();
346  gfx::NativeImage image = rb.GetNativeImageNamed(resource_id);
347#if defined(OS_MACOSX)
348  // Unlike other platforms, the Mac ResourceBundle does not keep a shared image
349  // cache. These are released in the dtor.
350  mac_util::NSObjectRetain(image);
351#endif
352  return image;
353}
354