| // Copyright 2016 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 "components/security_state/content/content_utils.h" |
| |
| #include <string> |
| #include <vector> |
| |
| #include "base/memory/ptr_util.h" |
| #include "base/strings/string16.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "components/security_state/core/security_state.h" |
| #include "components/strings/grit/components_chromium_strings.h" |
| #include "components/strings/grit/components_strings.h" |
| #include "content/public/browser/navigation_entry.h" |
| #include "content/public/browser/security_style_explanation.h" |
| #include "content/public/browser/security_style_explanations.h" |
| #include "content/public/browser/ssl_status.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/common/content_client.h" |
| #include "net/base/net_errors.h" |
| #include "net/cert/x509_certificate.h" |
| #include "net/ssl/ssl_cipher_suite_names.h" |
| #include "net/ssl/ssl_connection_status_flags.h" |
| #include "third_party/boringssl/src/include/openssl/ssl.h" |
| #include "ui/base/l10n/l10n_util.h" |
| |
| namespace security_state { |
| |
| namespace { |
| |
| // Note: This is a lossy operation. Not all of the policies that can be |
| // expressed by a SecurityLevel can be expressed by a blink::WebSecurityStyle. |
| blink::WebSecurityStyle SecurityLevelToSecurityStyle( |
| security_state::SecurityLevel security_level) { |
| switch (security_level) { |
| case security_state::NONE: |
| case security_state::HTTP_SHOW_WARNING: |
| return blink::WebSecurityStyleUnauthenticated; |
| case security_state::SECURITY_WARNING: |
| case security_state::SECURE_WITH_POLICY_INSTALLED_CERT: |
| return blink::WebSecurityStyleWarning; |
| case security_state::EV_SECURE: |
| case security_state::SECURE: |
| return blink::WebSecurityStyleAuthenticated; |
| case security_state::DANGEROUS: |
| return blink::WebSecurityStyleAuthenticationBroken; |
| } |
| |
| NOTREACHED(); |
| return blink::WebSecurityStyleUnknown; |
| } |
| |
| void AddConnectionExplanation( |
| const security_state::SecurityInfo& security_info, |
| content::SecurityStyleExplanations* security_style_explanations) { |
| // Avoid showing TLS details when we couldn't even establish a TLS connection |
| // (e.g. for net errors) or if there was no real connection (some tests). We |
| // check the |connection_status| to see if there was a connection. |
| if (security_info.connection_status == 0) { |
| return; |
| } |
| |
| int ssl_version = |
| net::SSLConnectionStatusToVersion(security_info.connection_status); |
| const char* protocol; |
| net::SSLVersionToString(&protocol, ssl_version); |
| const char* key_exchange; |
| const char* cipher; |
| const char* mac; |
| bool is_aead; |
| bool is_tls13; |
| uint16_t cipher_suite = |
| net::SSLConnectionStatusToCipherSuite(security_info.connection_status); |
| net::SSLCipherSuiteToStrings(&key_exchange, &cipher, &mac, &is_aead, |
| &is_tls13, cipher_suite); |
| base::string16 protocol_name = base::ASCIIToUTF16(protocol); |
| const base::string16 cipher_name = |
| (mac == NULL) ? base::ASCIIToUTF16(cipher) |
| : l10n_util::GetStringFUTF16(IDS_CIPHER_WITH_MAC, |
| base::ASCIIToUTF16(cipher), |
| base::ASCIIToUTF16(mac)); |
| |
| // Include the key exchange group (previously known as curve) if specified. |
| base::string16 key_exchange_name; |
| if (is_tls13) { |
| key_exchange_name = base::ASCIIToUTF16( |
| SSL_get_curve_name(security_info.key_exchange_group)); |
| } else if (security_info.key_exchange_group != 0) { |
| key_exchange_name = l10n_util::GetStringFUTF16( |
| IDS_SSL_KEY_EXCHANGE_WITH_GROUP, base::ASCIIToUTF16(key_exchange), |
| base::ASCIIToUTF16( |
| SSL_get_curve_name(security_info.key_exchange_group))); |
| } else { |
| key_exchange_name = base::ASCIIToUTF16(key_exchange); |
| } |
| |
| if (security_info.obsolete_ssl_status == net::OBSOLETE_SSL_NONE) { |
| security_style_explanations->secure_explanations.push_back( |
| content::SecurityStyleExplanation( |
| l10n_util::GetStringUTF8(IDS_STRONG_SSL_SUMMARY), |
| l10n_util::GetStringFUTF8(IDS_STRONG_SSL_DESCRIPTION, protocol_name, |
| key_exchange_name, cipher_name))); |
| return; |
| } |
| |
| std::vector<base::string16> description_replacements; |
| int status = security_info.obsolete_ssl_status; |
| int str_id; |
| |
| str_id = (status & net::OBSOLETE_SSL_MASK_PROTOCOL) |
| ? IDS_SSL_AN_OBSOLETE_PROTOCOL |
| : IDS_SSL_A_STRONG_PROTOCOL; |
| description_replacements.push_back(l10n_util::GetStringUTF16(str_id)); |
| description_replacements.push_back(protocol_name); |
| |
| str_id = (status & net::OBSOLETE_SSL_MASK_KEY_EXCHANGE) |
| ? IDS_SSL_AN_OBSOLETE_KEY_EXCHANGE |
| : IDS_SSL_A_STRONG_KEY_EXCHANGE; |
| description_replacements.push_back(l10n_util::GetStringUTF16(str_id)); |
| description_replacements.push_back(key_exchange_name); |
| |
| str_id = (status & net::OBSOLETE_SSL_MASK_CIPHER) ? IDS_SSL_AN_OBSOLETE_CIPHER |
| : IDS_SSL_A_STRONG_CIPHER; |
| description_replacements.push_back(l10n_util::GetStringUTF16(str_id)); |
| description_replacements.push_back(cipher_name); |
| |
| security_style_explanations->info_explanations.push_back( |
| content::SecurityStyleExplanation( |
| l10n_util::GetStringUTF8(IDS_OBSOLETE_SSL_SUMMARY), |
| base::UTF16ToUTF8( |
| l10n_util::GetStringFUTF16(IDS_OBSOLETE_SSL_DESCRIPTION, |
| description_replacements, nullptr)))); |
| } |
| |
| } // namespace |
| |
| std::unique_ptr<security_state::VisibleSecurityState> GetVisibleSecurityState( |
| content::WebContents* web_contents) { |
| auto state = base::MakeUnique<security_state::VisibleSecurityState>(); |
| |
| content::NavigationEntry* entry = |
| web_contents->GetController().GetVisibleEntry(); |
| if (!entry || !entry->GetSSL().initialized) |
| return state; |
| |
| state->connection_info_initialized = true; |
| state->url = entry->GetURL(); |
| const content::SSLStatus& ssl = entry->GetSSL(); |
| state->certificate = ssl.certificate; |
| state->cert_status = ssl.cert_status; |
| state->connection_status = ssl.connection_status; |
| state->key_exchange_group = ssl.key_exchange_group; |
| state->security_bits = ssl.security_bits; |
| state->pkp_bypassed = ssl.pkp_bypassed; |
| state->sct_verify_statuses.clear(); |
| state->sct_verify_statuses.insert(state->sct_verify_statuses.begin(), |
| ssl.sct_statuses.begin(), |
| ssl.sct_statuses.end()); |
| state->displayed_mixed_content = |
| !!(ssl.content_status & content::SSLStatus::DISPLAYED_INSECURE_CONTENT); |
| state->ran_mixed_content = |
| !!(ssl.content_status & content::SSLStatus::RAN_INSECURE_CONTENT); |
| state->displayed_content_with_cert_errors = |
| !!(ssl.content_status & |
| content::SSLStatus::DISPLAYED_CONTENT_WITH_CERT_ERRORS); |
| state->ran_content_with_cert_errors = |
| !!(ssl.content_status & content::SSLStatus::RAN_CONTENT_WITH_CERT_ERRORS); |
| state->displayed_password_field_on_http = |
| !!(ssl.content_status & |
| content::SSLStatus::DISPLAYED_PASSWORD_FIELD_ON_HTTP); |
| state->displayed_credit_card_field_on_http = |
| !!(ssl.content_status & |
| content::SSLStatus::DISPLAYED_CREDIT_CARD_FIELD_ON_HTTP); |
| |
| return state; |
| } |
| |
| blink::WebSecurityStyle GetSecurityStyle( |
| const security_state::SecurityInfo& security_info, |
| content::SecurityStyleExplanations* security_style_explanations) { |
| const blink::WebSecurityStyle security_style = |
| SecurityLevelToSecurityStyle(security_info.security_level); |
| |
| // The HTTP_SHOW_WARNING state may occur if the page is served as a data: URI |
| // or if it is served non-securely AND contains a sensitive form field. |
| if (security_info.security_level == security_state::HTTP_SHOW_WARNING && |
| (security_info.displayed_password_field_on_http || |
| security_info.displayed_credit_card_field_on_http)) { |
| security_style_explanations->unauthenticated_explanations.push_back( |
| content::SecurityStyleExplanation( |
| l10n_util::GetStringUTF8(IDS_PRIVATE_USER_DATA_INPUT), |
| l10n_util::GetStringUTF8(IDS_PRIVATE_USER_DATA_INPUT_DESCRIPTION))); |
| } |
| security_style_explanations->ran_insecure_content_style = |
| SecurityLevelToSecurityStyle(security_state::kRanInsecureContentLevel); |
| security_style_explanations->displayed_insecure_content_style = |
| SecurityLevelToSecurityStyle( |
| security_state::kDisplayedInsecureContentLevel); |
| |
| if (security_info.malicious_content_status != |
| security_state::MALICIOUS_CONTENT_STATUS_NONE) { |
| security_style_explanations->summary = |
| l10n_util::GetStringUTF8(IDS_SAFEBROWSING_WARNING); |
| } |
| |
| // Check if the page is HTTP; if so, no more explanations are needed. Note |
| // that SecurityStyleUnauthenticated does not necessarily mean that |
| // the page is loaded over HTTP, because the security style merely |
| // represents how the embedder wishes to display the security state of |
| // the page, and the embedder can choose to display HTTPS page as HTTP |
| // if it wants to (for example, displaying deprecated crypto |
| // algorithms with the same UI treatment as HTTP pages). |
| security_style_explanations->scheme_is_cryptographic = |
| security_info.scheme_is_cryptographic; |
| if (!security_info.scheme_is_cryptographic) { |
| return security_style; |
| } |
| |
| if (security_info.sha1_in_chain) { |
| security_style_explanations->unauthenticated_explanations.push_back( |
| content::SecurityStyleExplanation( |
| l10n_util::GetStringUTF8(IDS_SHA1), |
| l10n_util::GetStringUTF8(IDS_SHA1_DESCRIPTION), |
| !!security_info.certificate)); |
| } |
| |
| if (security_info.cert_missing_subject_alt_name) { |
| security_style_explanations->broken_explanations.push_back( |
| content::SecurityStyleExplanation( |
| l10n_util::GetStringUTF8(IDS_SUBJECT_ALT_NAME_MISSING), |
| l10n_util::GetStringUTF8(IDS_SUBJECT_ALT_NAME_MISSING_DESCRIPTION), |
| !!security_info.certificate)); |
| } |
| |
| // Record the presence of mixed content (HTTP subresources on an HTTPS |
| // page). |
| security_style_explanations->ran_mixed_content = |
| security_info.mixed_content_status == |
| security_state::CONTENT_STATUS_RAN || |
| security_info.mixed_content_status == |
| security_state::CONTENT_STATUS_DISPLAYED_AND_RAN; |
| security_style_explanations->displayed_mixed_content = |
| security_info.mixed_content_status == |
| security_state::CONTENT_STATUS_DISPLAYED || |
| security_info.mixed_content_status == |
| security_state::CONTENT_STATUS_DISPLAYED_AND_RAN; |
| |
| bool is_cert_status_error = net::IsCertStatusError(security_info.cert_status); |
| bool is_cert_status_minor_error = |
| net::IsCertStatusMinorError(security_info.cert_status); |
| |
| // If the main resource was loaded no certificate errors or only minor |
| // certificate errors, then record the presence of subresources with |
| // certificate errors. Subresource certificate errors aren't recorded |
| // when the main resource was loaded with major certificate errors |
| // because, in the common case, these subresource certificate errors |
| // would be duplicative with the main resource's error. |
| if (!is_cert_status_error || is_cert_status_minor_error) { |
| security_style_explanations->ran_content_with_cert_errors = |
| security_info.content_with_cert_errors_status == |
| security_state::CONTENT_STATUS_RAN || |
| security_info.content_with_cert_errors_status == |
| security_state::CONTENT_STATUS_DISPLAYED_AND_RAN; |
| security_style_explanations->displayed_content_with_cert_errors = |
| security_info.content_with_cert_errors_status == |
| security_state::CONTENT_STATUS_DISPLAYED || |
| security_info.content_with_cert_errors_status == |
| security_state::CONTENT_STATUS_DISPLAYED_AND_RAN; |
| } |
| |
| if (is_cert_status_error) { |
| base::string16 error_string = base::UTF8ToUTF16(net::ErrorToString( |
| net::MapCertStatusToNetError(security_info.cert_status))); |
| |
| content::SecurityStyleExplanation explanation( |
| l10n_util::GetStringUTF8(IDS_CERTIFICATE_CHAIN_ERROR), |
| l10n_util::GetStringFUTF8( |
| IDS_CERTIFICATE_CHAIN_ERROR_DESCRIPTION_FORMAT, error_string), |
| !!security_info.certificate); |
| |
| if (is_cert_status_minor_error) { |
| security_style_explanations->unauthenticated_explanations.push_back( |
| explanation); |
| } else { |
| security_style_explanations->broken_explanations.push_back(explanation); |
| } |
| } else { |
| // If the certificate does not have errors and is not using SHA1, then add |
| // an explanation that the certificate is valid. |
| if (!security_info.sha1_in_chain) { |
| security_style_explanations->secure_explanations.push_back( |
| content::SecurityStyleExplanation( |
| l10n_util::GetStringUTF8(IDS_VALID_SERVER_CERTIFICATE), |
| l10n_util::GetStringUTF8( |
| IDS_VALID_SERVER_CERTIFICATE_DESCRIPTION), |
| !!security_info.certificate)); |
| } |
| } |
| |
| AddConnectionExplanation(security_info, security_style_explanations); |
| |
| security_style_explanations->pkp_bypassed = security_info.pkp_bypassed; |
| if (security_info.pkp_bypassed) { |
| security_style_explanations->info_explanations.push_back( |
| content::SecurityStyleExplanation( |
| "Public-Key Pinning Bypassed", |
| "Public-key pinning was bypassed by a local root certificate.")); |
| } |
| |
| return security_style; |
| } |
| |
| } // namespace security_state |