| // Copyright (c) 2010 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/page_info_model.h" |
| |
| #include <string> |
| |
| #include "base/command_line.h" |
| #include "base/i18n/time_formatting.h" |
| #include "base/string_number_conversions.h" |
| #include "base/utf_string_conversions.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/ssl/ssl_error_info.h" |
| #include "chrome/browser/ssl/ssl_manager.h" |
| #include "content/browser/cert_store.h" |
| #include "grit/generated_resources.h" |
| #include "grit/theme_resources.h" |
| #include "net/base/cert_status_flags.h" |
| #include "net/base/ssl_connection_status_flags.h" |
| #include "net/base/ssl_cipher_suite_names.h" |
| #include "net/base/x509_certificate.h" |
| #include "ui/base/l10n/l10n_util.h" |
| #include "ui/base/resource/resource_bundle.h" |
| |
| PageInfoModel::PageInfoModel(Profile* profile, |
| const GURL& url, |
| const NavigationEntry::SSLStatus& ssl, |
| bool show_history, |
| PageInfoModelObserver* observer) |
| : observer_(observer) { |
| Init(); |
| |
| SectionStateIcon icon_id = ICON_STATE_OK; |
| string16 headline; |
| string16 description; |
| scoped_refptr<net::X509Certificate> cert; |
| |
| // Identity section. |
| string16 subject_name(UTF8ToUTF16(url.host())); |
| bool empty_subject_name = false; |
| if (subject_name.empty()) { |
| subject_name.assign( |
| l10n_util::GetStringUTF16(IDS_PAGE_INFO_SECURITY_TAB_UNKNOWN_PARTY)); |
| empty_subject_name = true; |
| } |
| |
| // Some of what IsCertStatusError classifies as errors we want to show as |
| // warnings instead. |
| static const int cert_warnings = |
| net::CERT_STATUS_UNABLE_TO_CHECK_REVOCATION | |
| net::CERT_STATUS_NO_REVOCATION_MECHANISM; |
| int status_with_warnings_removed = ssl.cert_status() & ~cert_warnings; |
| |
| if (ssl.cert_id() && |
| CertStore::GetInstance()->RetrieveCert(ssl.cert_id(), &cert) && |
| !net::IsCertStatusError(status_with_warnings_removed)) { |
| // No error found so far, check cert_status warnings. |
| int cert_status = ssl.cert_status(); |
| if (cert_status & cert_warnings) { |
| string16 issuer_name(UTF8ToUTF16(cert->issuer().GetDisplayName())); |
| if (issuer_name.empty()) { |
| issuer_name.assign(l10n_util::GetStringUTF16( |
| IDS_PAGE_INFO_SECURITY_TAB_UNKNOWN_PARTY)); |
| } |
| description.assign(l10n_util::GetStringFUTF16( |
| IDS_PAGE_INFO_SECURITY_TAB_SECURE_IDENTITY, issuer_name)); |
| |
| description += ASCIIToUTF16("\n\n"); |
| if (cert_status & net::CERT_STATUS_UNABLE_TO_CHECK_REVOCATION) { |
| description += l10n_util::GetStringUTF16( |
| IDS_PAGE_INFO_SECURITY_TAB_UNABLE_TO_CHECK_REVOCATION); |
| } else if (cert_status & net::CERT_STATUS_NO_REVOCATION_MECHANISM) { |
| description += l10n_util::GetStringUTF16( |
| IDS_PAGE_INFO_SECURITY_TAB_NO_REVOCATION_MECHANISM); |
| } else { |
| NOTREACHED() << "Need to specify string for this warning"; |
| } |
| icon_id = ICON_STATE_WARNING_MINOR; |
| } else if ((ssl.cert_status() & net::CERT_STATUS_IS_EV) != 0) { |
| // EV HTTPS page. |
| DCHECK(!cert->subject().organization_names.empty()); |
| headline = |
| l10n_util::GetStringFUTF16(IDS_PAGE_INFO_EV_IDENTITY_TITLE, |
| UTF8ToUTF16(cert->subject().organization_names[0]), |
| UTF8ToUTF16(url.host())); |
| // An EV Cert is required to have a city (localityName) and country but |
| // state is "if any". |
| DCHECK(!cert->subject().locality_name.empty()); |
| DCHECK(!cert->subject().country_name.empty()); |
| string16 locality; |
| if (!cert->subject().state_or_province_name.empty()) { |
| locality = l10n_util::GetStringFUTF16( |
| IDS_PAGEINFO_ADDRESS, |
| UTF8ToUTF16(cert->subject().locality_name), |
| UTF8ToUTF16(cert->subject().state_or_province_name), |
| UTF8ToUTF16(cert->subject().country_name)); |
| } else { |
| locality = l10n_util::GetStringFUTF16( |
| IDS_PAGEINFO_PARTIAL_ADDRESS, |
| UTF8ToUTF16(cert->subject().locality_name), |
| UTF8ToUTF16(cert->subject().country_name)); |
| } |
| DCHECK(!cert->subject().organization_names.empty()); |
| description.assign(l10n_util::GetStringFUTF16( |
| IDS_PAGE_INFO_SECURITY_TAB_SECURE_IDENTITY_EV, |
| UTF8ToUTF16(cert->subject().organization_names[0]), |
| locality, |
| UTF8ToUTF16(cert->issuer().GetDisplayName()))); |
| } else if ((ssl.cert_status() & net::CERT_STATUS_IS_DNSSEC) != 0) { |
| // DNSSEC authenticated page. |
| if (empty_subject_name) |
| headline.clear(); // Don't display any title. |
| else |
| headline.assign(subject_name); |
| description.assign(l10n_util::GetStringFUTF16( |
| IDS_PAGE_INFO_SECURITY_TAB_SECURE_IDENTITY, UTF8ToUTF16("DNSSEC"))); |
| } else { |
| // Non-EV OK HTTPS page. |
| if (empty_subject_name) |
| headline.clear(); // Don't display any title. |
| else |
| headline.assign(subject_name); |
| string16 issuer_name(UTF8ToUTF16(cert->issuer().GetDisplayName())); |
| if (issuer_name.empty()) { |
| issuer_name.assign(l10n_util::GetStringUTF16( |
| IDS_PAGE_INFO_SECURITY_TAB_UNKNOWN_PARTY)); |
| } |
| description.assign(l10n_util::GetStringFUTF16( |
| IDS_PAGE_INFO_SECURITY_TAB_SECURE_IDENTITY, issuer_name)); |
| } |
| } else { |
| // HTTP or HTTPS with errors (not warnings). |
| description.assign(l10n_util::GetStringUTF16( |
| IDS_PAGE_INFO_SECURITY_TAB_INSECURE_IDENTITY)); |
| icon_id = ssl.security_style() == SECURITY_STYLE_UNAUTHENTICATED ? |
| ICON_STATE_WARNING_MAJOR : ICON_STATE_ERROR; |
| |
| const string16 bullet = UTF8ToUTF16("\n • "); |
| std::vector<SSLErrorInfo> errors; |
| SSLErrorInfo::GetErrorsForCertStatus(ssl.cert_id(), ssl.cert_status(), |
| url, &errors); |
| for (size_t i = 0; i < errors.size(); ++i) { |
| description += bullet; |
| description += errors[i].short_description(); |
| } |
| |
| if (ssl.cert_status() & net::CERT_STATUS_NON_UNIQUE_NAME) { |
| description += ASCIIToUTF16("\n\n"); |
| description += l10n_util::GetStringUTF16( |
| IDS_PAGE_INFO_SECURITY_TAB_NON_UNIQUE_NAME); |
| } |
| } |
| sections_.push_back(SectionInfo( |
| icon_id, |
| headline, |
| description, |
| SECTION_INFO_IDENTITY)); |
| |
| // Connection section. |
| // We consider anything less than 80 bits encryption to be weak encryption. |
| // TODO(wtc): Bug 1198735: report mixed/unsafe content for unencrypted and |
| // weakly encrypted connections. |
| icon_id = ICON_STATE_OK; |
| headline.clear(); |
| description.clear(); |
| if (!ssl.cert_id()) { |
| // Not HTTPS. |
| DCHECK_EQ(ssl.security_style(), SECURITY_STYLE_UNAUTHENTICATED); |
| icon_id = ssl.security_style() == SECURITY_STYLE_UNAUTHENTICATED ? |
| ICON_STATE_WARNING_MAJOR : ICON_STATE_ERROR; |
| description.assign(l10n_util::GetStringFUTF16( |
| IDS_PAGE_INFO_SECURITY_TAB_NOT_ENCRYPTED_CONNECTION_TEXT, |
| subject_name)); |
| } else if (ssl.security_bits() < 0) { |
| // Security strength is unknown. Say nothing. |
| icon_id = ICON_STATE_ERROR; |
| } else if (ssl.security_bits() == 0) { |
| DCHECK_NE(ssl.security_style(), SECURITY_STYLE_UNAUTHENTICATED); |
| icon_id = ICON_STATE_ERROR; |
| description.assign(l10n_util::GetStringFUTF16( |
| IDS_PAGE_INFO_SECURITY_TAB_NOT_ENCRYPTED_CONNECTION_TEXT, |
| subject_name)); |
| } else if (ssl.security_bits() < 80) { |
| icon_id = ICON_STATE_ERROR; |
| description.assign(l10n_util::GetStringFUTF16( |
| IDS_PAGE_INFO_SECURITY_TAB_WEAK_ENCRYPTION_CONNECTION_TEXT, |
| subject_name)); |
| } else { |
| description.assign(l10n_util::GetStringFUTF16( |
| IDS_PAGE_INFO_SECURITY_TAB_ENCRYPTED_CONNECTION_TEXT, |
| subject_name, |
| base::IntToString16(ssl.security_bits()))); |
| if (ssl.displayed_insecure_content() || ssl.ran_insecure_content()) { |
| icon_id = ssl.ran_insecure_content() ? |
| ICON_STATE_ERROR : ICON_STATE_WARNING_MINOR; |
| description.assign(l10n_util::GetStringFUTF16( |
| IDS_PAGE_INFO_SECURITY_TAB_ENCRYPTED_SENTENCE_LINK, |
| description, |
| l10n_util::GetStringUTF16(ssl.ran_insecure_content() ? |
| IDS_PAGE_INFO_SECURITY_TAB_ENCRYPTED_INSECURE_CONTENT_ERROR : |
| IDS_PAGE_INFO_SECURITY_TAB_ENCRYPTED_INSECURE_CONTENT_WARNING))); |
| } |
| } |
| |
| uint16 cipher_suite = |
| net::SSLConnectionStatusToCipherSuite(ssl.connection_status()); |
| if (ssl.security_bits() > 0 && cipher_suite) { |
| int ssl_version = |
| net::SSLConnectionStatusToVersion(ssl.connection_status()); |
| const char* ssl_version_str; |
| net::SSLVersionToString(&ssl_version_str, ssl_version); |
| description += ASCIIToUTF16("\n\n"); |
| description += l10n_util::GetStringFUTF16( |
| IDS_PAGE_INFO_SECURITY_TAB_SSL_VERSION, |
| ASCIIToUTF16(ssl_version_str)); |
| |
| bool did_fallback = (ssl.connection_status() & |
| net::SSL_CONNECTION_SSL3_FALLBACK) != 0; |
| bool no_renegotiation = |
| (ssl.connection_status() & |
| net::SSL_CONNECTION_NO_RENEGOTIATION_EXTENSION) != 0; |
| const char *key_exchange, *cipher, *mac; |
| net::SSLCipherSuiteToStrings(&key_exchange, &cipher, &mac, cipher_suite); |
| |
| description += ASCIIToUTF16("\n\n"); |
| description += l10n_util::GetStringFUTF16( |
| IDS_PAGE_INFO_SECURITY_TAB_ENCRYPTION_DETAILS, |
| ASCIIToUTF16(cipher), ASCIIToUTF16(mac), ASCIIToUTF16(key_exchange)); |
| |
| description += ASCIIToUTF16("\n\n"); |
| uint8 compression_id = |
| net::SSLConnectionStatusToCompression(ssl.connection_status()); |
| if (compression_id) { |
| const char* compression; |
| net::SSLCompressionToString(&compression, compression_id); |
| description += l10n_util::GetStringFUTF16( |
| IDS_PAGE_INFO_SECURITY_TAB_COMPRESSION_DETAILS, |
| ASCIIToUTF16(compression)); |
| } else { |
| description += l10n_util::GetStringUTF16( |
| IDS_PAGE_INFO_SECURITY_TAB_NO_COMPRESSION); |
| } |
| |
| if (did_fallback) { |
| // For now, only SSLv3 fallback will trigger a warning icon. |
| if (icon_id < ICON_STATE_WARNING_MINOR) |
| icon_id = ICON_STATE_WARNING_MINOR; |
| description += ASCIIToUTF16("\n\n"); |
| description += l10n_util::GetStringUTF16( |
| IDS_PAGE_INFO_SECURITY_TAB_FALLBACK_MESSAGE); |
| } |
| if (no_renegotiation) { |
| description += ASCIIToUTF16("\n\n"); |
| description += l10n_util::GetStringUTF16( |
| IDS_PAGE_INFO_SECURITY_TAB_RENEGOTIATION_MESSAGE); |
| } |
| } |
| |
| if (!description.empty()) { |
| sections_.push_back(SectionInfo( |
| icon_id, |
| headline, |
| description, |
| SECTION_INFO_CONNECTION)); |
| } |
| |
| // Request the number of visits. |
| HistoryService* history = profile->GetHistoryService( |
| Profile::EXPLICIT_ACCESS); |
| if (show_history && history) { |
| history->GetVisitCountToHost( |
| url, |
| &request_consumer_, |
| NewCallback(this, &PageInfoModel::OnGotVisitCountToHost)); |
| } |
| } |
| |
| PageInfoModel::~PageInfoModel() {} |
| |
| int PageInfoModel::GetSectionCount() { |
| return sections_.size(); |
| } |
| |
| PageInfoModel::SectionInfo PageInfoModel::GetSectionInfo(int index) { |
| DCHECK(index < static_cast<int>(sections_.size())); |
| return sections_[index]; |
| } |
| |
| gfx::Image* PageInfoModel::GetIconImage(SectionStateIcon icon_id) { |
| if (icon_id == ICON_NONE) |
| return NULL; |
| // The bubble uses new, various icons. |
| return icons_[icon_id]; |
| } |
| |
| void PageInfoModel::OnGotVisitCountToHost(HistoryService::Handle handle, |
| bool found_visits, |
| int count, |
| base::Time first_visit) { |
| if (!found_visits) { |
| // This indicates an error, such as the page wasn't http/https; do nothing. |
| return; |
| } |
| |
| bool visited_before_today = false; |
| if (count) { |
| base::Time today = base::Time::Now().LocalMidnight(); |
| base::Time first_visit_midnight = first_visit.LocalMidnight(); |
| visited_before_today = (first_visit_midnight < today); |
| } |
| |
| string16 headline = l10n_util::GetStringUTF16(IDS_PAGE_INFO_SITE_INFO_TITLE); |
| |
| if (!visited_before_today) { |
| sections_.push_back(SectionInfo( |
| ICON_STATE_WARNING_MAJOR, |
| headline, |
| l10n_util::GetStringUTF16( |
| IDS_PAGE_INFO_SECURITY_TAB_FIRST_VISITED_TODAY), |
| SECTION_INFO_FIRST_VISIT)); |
| } else { |
| sections_.push_back(SectionInfo( |
| ICON_STATE_INFO, |
| headline, |
| l10n_util::GetStringFUTF16( |
| IDS_PAGE_INFO_SECURITY_TAB_VISITED_BEFORE_TODAY, |
| base::TimeFormatShortDate(first_visit)), |
| SECTION_INFO_FIRST_VISIT)); |
| } |
| observer_->ModelChanged(); |
| } |
| |
| PageInfoModel::PageInfoModel() : observer_(NULL) { |
| Init(); |
| } |
| |
| void PageInfoModel::Init() { |
| // Loads the icons into the vector. The order must match the SectionStateIcon |
| // enum. |
| ResourceBundle& rb = ResourceBundle::GetSharedInstance(); |
| icons_.push_back(&rb.GetNativeImageNamed(IDR_PAGEINFO_GOOD)); |
| icons_.push_back(&rb.GetNativeImageNamed(IDR_PAGEINFO_WARNING_MINOR)); |
| icons_.push_back(&rb.GetNativeImageNamed(IDR_PAGEINFO_WARNING_MAJOR)); |
| icons_.push_back(&rb.GetNativeImageNamed(IDR_PAGEINFO_BAD)); |
| icons_.push_back(&rb.GetNativeImageNamed(IDR_PAGEINFO_INFO)); |
| } |