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