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