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