diff options
author | ben@chromium.org <ben@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-01-16 03:29:03 +0000 |
---|---|---|
committer | ben@chromium.org <ben@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-01-16 03:29:03 +0000 |
commit | 3b073b258b19937b1dd9b6b2a609dee207865eba (patch) | |
tree | fb0779236d58e8a49513c1036d891c2e10c9573c /chrome/browser/ssl | |
parent | 7fb087e0800d0faaa31e4a029ad6813f251b4848 (diff) | |
download | chromium_src-3b073b258b19937b1dd9b6b2a609dee207865eba.zip chromium_src-3b073b258b19937b1dd9b6b2a609dee207865eba.tar.gz chromium_src-3b073b258b19937b1dd9b6b2a609dee207865eba.tar.bz2 |
Move all the SSL stuff into its own subdir
Review URL: http://codereview.chromium.org/18137
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@8165 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/ssl')
-rw-r--r-- | chrome/browser/ssl/ssl_blocking_page.cc | 140 | ||||
-rw-r--r-- | chrome/browser/ssl/ssl_blocking_page.h | 71 | ||||
-rw-r--r-- | chrome/browser/ssl/ssl_error_info.cc | 250 | ||||
-rw-r--r-- | chrome/browser/ssl/ssl_error_info.h | 83 | ||||
-rw-r--r-- | chrome/browser/ssl/ssl_manager.cc | 725 | ||||
-rw-r--r-- | chrome/browser/ssl/ssl_manager.h | 465 | ||||
-rw-r--r-- | chrome/browser/ssl/ssl_policy.cc | 483 | ||||
-rw-r--r-- | chrome/browser/ssl/ssl_policy.h | 69 | ||||
-rw-r--r-- | chrome/browser/ssl/ssl_uitest.cc | 932 |
9 files changed, 3218 insertions, 0 deletions
diff --git a/chrome/browser/ssl/ssl_blocking_page.cc b/chrome/browser/ssl/ssl_blocking_page.cc new file mode 100644 index 0000000..96d0003 --- /dev/null +++ b/chrome/browser/ssl/ssl_blocking_page.cc @@ -0,0 +1,140 @@ +// Copyright (c) 2006-2008 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/ssl/ssl_blocking_page.h" + +#include "base/string_piece.h" +#include "chrome/browser/browser.h" +#include "chrome/browser/browser_resources.h" +#include "chrome/browser/cert_store.h" +#include "chrome/browser/dom_operation_notification_details.h" +#include "chrome/browser/ssl/ssl_error_info.h" +#include "chrome/browser/tab_contents/navigation_controller.h" +#include "chrome/browser/tab_contents/navigation_entry.h" +#include "chrome/browser/tab_contents/web_contents.h" +#include "chrome/common/jstemplate_builder.h" +#include "chrome/common/l10n_util.h" +#include "chrome/common/pref_names.h" +#include "chrome/common/pref_service.h" +#include "chrome/common/resource_bundle.h" + +#include "generated_resources.h" + +// Note that we always create a navigation entry with SSL errors. +// No error happening loading a sub-resource triggers an interstitial so far. +SSLBlockingPage::SSLBlockingPage(SSLManager::CertError* error, + Delegate* delegate) + : InterstitialPage(error->GetWebContents(), true, error->request_url()), + error_(error), + delegate_(delegate), + delegate_has_been_notified_(false) { +} + +SSLBlockingPage::~SSLBlockingPage() { + if (!delegate_has_been_notified_) { + // The page is closed without the user having chosen what to do, default to + // deny. + NotifyDenyCertificate(); + } +} + +std::string SSLBlockingPage::GetHTMLContents() { + // Let's build the html error page. + DictionaryValue strings; + SSLErrorInfo error_info = delegate_->GetSSLErrorInfo(error_); + strings.SetString(L"title", + l10n_util::GetString(IDS_SSL_BLOCKING_PAGE_TITLE)); + strings.SetString(L"headLine", error_info.title()); + strings.SetString(L"description", error_info.details()); + + strings.SetString(L"moreInfoTitle", + l10n_util::GetString(IDS_CERT_ERROR_EXTRA_INFO_TITLE)); + SetExtraInfo(&strings, error_info.extra_information()); + + strings.SetString(L"proceed", + l10n_util::GetString(IDS_SSL_BLOCKING_PAGE_PROCEED)); + strings.SetString(L"exit", + l10n_util::GetString(IDS_SSL_BLOCKING_PAGE_EXIT)); + + strings.SetString(L"textdirection", + (l10n_util::GetTextDirection() == l10n_util::RIGHT_TO_LEFT) ? + L"rtl" : L"ltr"); + + static const StringPiece html( + ResourceBundle::GetSharedInstance().GetRawDataResource( + IDR_SSL_ROAD_BLOCK_HTML)); + + return jstemplate_builder::GetTemplateHtml(html, &strings, "template_root"); +} + +void SSLBlockingPage::UpdateEntry(NavigationEntry* entry) { + DCHECK(tab()->type() == TAB_CONTENTS_WEB); + WebContents* web = tab()->AsWebContents(); + const net::SSLInfo& ssl_info = error_->ssl_info(); + int cert_id = CertStore::GetSharedInstance()->StoreCert( + ssl_info.cert, web->render_view_host()->process()->host_id()); + + entry->ssl().set_security_style(SECURITY_STYLE_AUTHENTICATION_BROKEN); + entry->ssl().set_cert_id(cert_id); + entry->ssl().set_cert_status(ssl_info.cert_status); + entry->ssl().set_security_bits(ssl_info.security_bits); + NotificationService::current()->Notify( + NOTIFY_SSL_STATE_CHANGED, + Source<NavigationController>(web->controller()), + NotificationService::NoDetails()); +} + +void SSLBlockingPage::CommandReceived(const std::string& command) { + if (command == "1") { + Proceed(); + } else { + DontProceed(); + } +} + +void SSLBlockingPage::Proceed() { + // Accepting the certificate resumes the loading of the page. + NotifyAllowCertificate(); + + // This call hides and deletes the interstitial. + InterstitialPage::Proceed(); +} + +void SSLBlockingPage::DontProceed() { + NotifyDenyCertificate(); + InterstitialPage::DontProceed(); +} + + +void SSLBlockingPage::NotifyDenyCertificate() { + DCHECK(!delegate_has_been_notified_); + + delegate_->OnDenyCertificate(error_); + delegate_has_been_notified_ = true; +} + +void SSLBlockingPage::NotifyAllowCertificate() { + DCHECK(!delegate_has_been_notified_); + + delegate_->OnAllowCertificate(error_); + delegate_has_been_notified_ = true; +} + +// static +void SSLBlockingPage::SetExtraInfo( + DictionaryValue* strings, + const std::vector<std::wstring>& extra_info) { + DCHECK(extra_info.size() < 5); // We allow 5 paragraphs max. + const std::wstring keys[5] = { + L"moreInfo1", L"moreInfo2", L"moreInfo3", L"moreInfo4", L"moreInfo5" + }; + int i; + for (i = 0; i < static_cast<int>(extra_info.size()); i++) { + strings->SetString(keys[i], extra_info[i]); + } + for (;i < 5; i++) { + strings->SetString(keys[i], L""); + } +} + diff --git a/chrome/browser/ssl/ssl_blocking_page.h b/chrome/browser/ssl/ssl_blocking_page.h new file mode 100644 index 0000000..c3c2289 --- /dev/null +++ b/chrome/browser/ssl/ssl_blocking_page.h @@ -0,0 +1,71 @@ +// Copyright (c) 2006-2008 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. + +#ifndef CHROME_BROWSER_SSL_BLOCKING_PAGE_H_ +#define CHROME_BROWSER_SSL_BLOCKING_PAGE_H_ + +#include <string> + +#include "chrome/browser/tab_contents/interstitial_page.h" +#include "chrome/browser/ssl/ssl_manager.h" +#include "chrome/views/decision.h" + +// This class is responsible for showing/hiding the interstitial page that is +// shown when a certificate error happens. +// It deletes itself when the interstitial page is closed. +class SSLBlockingPage : public InterstitialPage { + public: + // An interface that classes that want to interact with the SSLBlockingPage + // should implement. + class Delegate { + public: + // Should return the information about the error that causes this blocking + // page. + virtual SSLErrorInfo GetSSLErrorInfo(SSLManager::CertError* error) = 0; + + // Notification that the user chose to reject the certificate. + virtual void OnDenyCertificate(SSLManager::CertError* error) = 0; + + // Notification that the user chose to accept the certificate. + virtual void OnAllowCertificate(SSLManager::CertError* error) = 0; + }; + + SSLBlockingPage(SSLManager::CertError* error, Delegate* delegate); + virtual ~SSLBlockingPage(); + + // A method that sets strings in the specified dictionary from the passed + // vector so that they can be used to resource the ssl_roadblock.html/ + // ssl_error.html files. + // Note: there can be up to 5 strings in |extra_info|. + static void SetExtraInfo(DictionaryValue* strings, + const std::vector<std::wstring>& extra_info); + + protected: + // InterstitialPage implementation. + virtual std::string GetHTMLContents(); + virtual void CommandReceived(const std::string& command); + virtual void UpdateEntry(NavigationEntry* entry); + virtual void Proceed(); + virtual void DontProceed(); + + private: + void NotifyDenyCertificate(); + void NotifyAllowCertificate(); + + // The error we represent. We will either call CancelRequest() or + // ContinueRequest() on this object. + scoped_refptr<SSLManager::CertError> error_; + + // Our delegate. It provides useful information, like the title and details + // about this error. + Delegate* delegate_; + + // A flag to indicate if we've notified |delegate_| of the user's decision. + bool delegate_has_been_notified_; + + + DISALLOW_COPY_AND_ASSIGN(SSLBlockingPage); +}; + +#endif // #ifndef CHROME_BROWSER_SSL_BLOCKING_PAGE_H_ diff --git a/chrome/browser/ssl/ssl_error_info.cc b/chrome/browser/ssl/ssl_error_info.cc new file mode 100644 index 0000000..ee12ae1 --- /dev/null +++ b/chrome/browser/ssl/ssl_error_info.cc @@ -0,0 +1,250 @@ +// Copyright (c) 2006-2008 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/ssl/ssl_error_info.h" + +#include "base/string_util.h" +#include "chrome/browser/cert_store.h" +#include "chrome/common/l10n_util.h" +#include "chrome/common/time_format.h" +#include "net/base/cert_status_flags.h" +#include "net/base/net_errors.h" +#include "net/base/ssl_info.h" +#include "googleurl/src/gurl.h" + +#include "chromium_strings.h" +#include "generated_resources.h" + +SSLErrorInfo::SSLErrorInfo(const std::wstring& title, + const std::wstring& details, + const std::wstring& short_description, + const std::vector<std::wstring>& extra_info) + : title_(title), + details_(details), + short_description_(short_description), + extra_information_(extra_info) { +} + +// static +SSLErrorInfo SSLErrorInfo::CreateError(ErrorType error_type, + net::X509Certificate* cert, + const GURL& request_url) { + std::wstring title, details, short_description; + std::vector<std::wstring> extra_info; + switch (error_type) { + case CERT_COMMON_NAME_INVALID: { + title = l10n_util::GetString(IDS_CERT_ERROR_COMMON_NAME_INVALID_TITLE); + // If the certificate contains multiple DNS names, we choose the most + // representative one -- either the DNS name that's also in the subject + // field, or the first one. If this heuristic turns out to be + // inadequate, we can consider choosing the DNS name that is the + // "closest match" to the host name in the request URL, or listing all + // the DNS names with an HTML <ul>. + std::vector<std::string> dns_names; + cert->GetDNSNames(&dns_names); + DCHECK(!dns_names.empty()); + size_t i = 0; + for (; i < dns_names.size(); ++i) { + if (dns_names[i] == cert->subject().common_name) + break; + } + if (i == dns_names.size()) + i = 0; + details = + l10n_util::GetStringF(IDS_CERT_ERROR_COMMON_NAME_INVALID_DETAILS, + UTF8ToWide(request_url.host()), + UTF8ToWide(dns_names[i]), + UTF8ToWide(request_url.host())); + short_description = + l10n_util::GetString(IDS_CERT_ERROR_COMMON_NAME_INVALID_DESCRIPTION); + extra_info.push_back( + l10n_util::GetString(IDS_CERT_ERROR_EXTRA_INFO_1)); + extra_info.push_back( + l10n_util::GetStringF( + IDS_CERT_ERROR_COMMON_NAME_INVALID_EXTRA_INFO_2, + UTF8ToWide(cert->subject().common_name), + UTF8ToWide(request_url.host()))); + break; + } + case CERT_DATE_INVALID: + extra_info.push_back( + l10n_util::GetString(IDS_CERT_ERROR_EXTRA_INFO_1)); + if (cert->HasExpired()) { + title = l10n_util::GetString(IDS_CERT_ERROR_EXPIRED_TITLE); + details = l10n_util::GetStringF(IDS_CERT_ERROR_EXPIRED_DETAILS, + UTF8ToWide(request_url.host()), + UTF8ToWide(request_url.host())); + short_description = + l10n_util::GetString(IDS_CERT_ERROR_EXPIRED_DESCRIPTION); + extra_info.push_back( + l10n_util::GetString(IDS_CERT_ERROR_EXPIRED_DETAILS_EXTRA_INFO_2)); + } else { + // Then it must be not yet valid. We don't check that it is not yet + // valid as there is still a very unlikely chance that the cert might + // have become valid since the error occurred. + title = l10n_util::GetString(IDS_CERT_ERROR_NOT_YET_VALID_TITLE); + details = l10n_util::GetStringF(IDS_CERT_ERROR_NOT_YET_VALID_DETAILS, + UTF8ToWide(request_url.host()), + UTF8ToWide(request_url.host())); + short_description = + l10n_util::GetString(IDS_CERT_ERROR_NOT_YET_VALID_DESCRIPTION); + extra_info.push_back( + l10n_util::GetString( + IDS_CERT_ERROR_NOT_YET_VALID_DETAILS_EXTRA_INFO_2)); + } + break; + case CERT_AUTHORITY_INVALID: + title = l10n_util::GetString(IDS_CERT_ERROR_AUTHORITY_INVALID_TITLE); + details = l10n_util::GetStringF(IDS_CERT_ERROR_AUTHORITY_INVALID_DETAILS, + UTF8ToWide(request_url.host())); + short_description = + l10n_util::GetString(IDS_CERT_ERROR_AUTHORITY_INVALID_DESCRIPTION); + extra_info.push_back( + l10n_util::GetString(IDS_CERT_ERROR_EXTRA_INFO_1)); + extra_info.push_back( + l10n_util::GetStringF(IDS_CERT_ERROR_AUTHORITY_INVALID_EXTRA_INFO_2, + UTF8ToWide(request_url.host()), + UTF8ToWide(request_url.host()))); + extra_info.push_back( + l10n_util::GetString(IDS_CERT_ERROR_AUTHORITY_INVALID_EXTRA_INFO_3)); + break; + case CERT_CONTAINS_ERRORS: + title = l10n_util::GetString(IDS_CERT_ERROR_CONTAINS_ERRORS_TITLE); + details = l10n_util::GetStringF(IDS_CERT_ERROR_CONTAINS_ERRORS_DETAILS, + UTF8ToWide(request_url.host())); + short_description = + l10n_util::GetString(IDS_CERT_ERROR_CONTAINS_ERRORS_DESCRIPTION); + extra_info.push_back( + l10n_util::GetStringF(IDS_CERT_ERROR_EXTRA_INFO_1, + UTF8ToWide(request_url.host()))); + extra_info.push_back( + l10n_util::GetString(IDS_CERT_ERROR_CONTAINS_ERRORS_EXTRA_INFO_2)); + break; + case CERT_NO_REVOCATION_MECHANISM: + title = + l10n_util::GetString(IDS_CERT_ERROR_NO_REVOCATION_MECHANISM_TITLE); + details = + l10n_util::GetString(IDS_CERT_ERROR_NO_REVOCATION_MECHANISM_DETAILS); + short_description = l10n_util::GetString( + IDS_CERT_ERROR_NO_REVOCATION_MECHANISM_DESCRIPTION); + break; + case CERT_UNABLE_TO_CHECK_REVOCATION: + title = + l10n_util::GetString(IDS_CERT_ERROR_UNABLE_TO_CHECK_REVOCATION_TITLE); + details = l10n_util::GetString( + IDS_CERT_ERROR_UNABLE_TO_CHECK_REVOCATION_DETAILS); + short_description = l10n_util::GetString( + IDS_CERT_ERROR_UNABLE_TO_CHECK_REVOCATION_DESCRIPTION); + break; + case CERT_REVOKED: + title = l10n_util::GetString(IDS_CERT_ERROR_REVOKED_CERT_TITLE); + details = l10n_util::GetStringF(IDS_CERT_ERROR_REVOKED_CERT_DETAILS, + UTF8ToWide(request_url.host())); + short_description = + l10n_util::GetString(IDS_CERT_ERROR_REVOKED_CERT_DESCRIPTION); + extra_info.push_back( + l10n_util::GetString(IDS_CERT_ERROR_EXTRA_INFO_1)); + extra_info.push_back( + l10n_util::GetString(IDS_CERT_ERROR_REVOKED_CERT_EXTRA_INFO_2)); + break; + case CERT_INVALID: + title = l10n_util::GetString(IDS_CERT_ERROR_INVALID_CERT_TITLE); + details = l10n_util::GetString(IDS_CERT_ERROR_INVALID_CERT_DETAILS); + short_description = + l10n_util::GetString(IDS_CERT_ERROR_INVALID_CERT_DESCRIPTION); + break; + case MIXED_CONTENTS: + title = l10n_util::GetString(IDS_SSL_MIXED_CONTENT_TITLE); + details = l10n_util::GetString(IDS_SSL_MIXED_CONTENT_DETAILS); + short_description = + l10n_util::GetString(IDS_SSL_MIXED_CONTENT_DESCRIPTION); + break; + case UNSAFE_CONTENTS: + title = l10n_util::GetString(IDS_SSL_UNSAFE_CONTENT_TITLE); + details = l10n_util::GetString(IDS_SSL_UNSAFE_CONTENT_DETAILS); + short_description = + l10n_util::GetString(IDS_SSL_UNSAFE_CONTENT_DESCRIPTION); + break; + case UNKNOWN: + title = l10n_util::GetString(IDS_CERT_ERROR_UNKNOWN_ERROR_TITLE); + details = l10n_util::GetString(IDS_CERT_ERROR_UNKNOWN_ERROR_DETAILS); + short_description = + l10n_util::GetString(IDS_CERT_ERROR_UNKNOWN_ERROR_DESCRIPTION); + break; + default: + NOTREACHED(); + } + return SSLErrorInfo(title, details, short_description, extra_info); +} + +SSLErrorInfo::~SSLErrorInfo() { +} + +// static +SSLErrorInfo::ErrorType SSLErrorInfo::NetErrorToErrorType(int net_error) { + switch (net_error) { + case net::ERR_CERT_COMMON_NAME_INVALID: + return CERT_COMMON_NAME_INVALID; + case net::ERR_CERT_DATE_INVALID: + return CERT_DATE_INVALID; + case net::ERR_CERT_AUTHORITY_INVALID: + return CERT_AUTHORITY_INVALID; + case net::ERR_CERT_CONTAINS_ERRORS: + return CERT_CONTAINS_ERRORS; + case net::ERR_CERT_NO_REVOCATION_MECHANISM: + return CERT_NO_REVOCATION_MECHANISM; + case net::ERR_CERT_UNABLE_TO_CHECK_REVOCATION: + return CERT_UNABLE_TO_CHECK_REVOCATION; + case net::ERR_CERT_REVOKED: + return CERT_REVOKED; + case net::ERR_CERT_INVALID: + return CERT_INVALID; + default: + NOTREACHED(); + return UNKNOWN; + } +} + +// static +int SSLErrorInfo::GetErrorsForCertStatus(int cert_id, + int cert_status, + const GURL& url, + std::vector<SSLErrorInfo>* errors) { + const int kErrorFlags[] = { + net::CERT_STATUS_COMMON_NAME_INVALID, + net::CERT_STATUS_DATE_INVALID, + net::CERT_STATUS_AUTHORITY_INVALID, + net::CERT_STATUS_NO_REVOCATION_MECHANISM, + net::CERT_STATUS_UNABLE_TO_CHECK_REVOCATION, + net::CERT_STATUS_REVOKED, + net::CERT_STATUS_INVALID + }; + + const ErrorType kErrorTypes[] = { + CERT_COMMON_NAME_INVALID, + CERT_DATE_INVALID, + CERT_AUTHORITY_INVALID, + CERT_NO_REVOCATION_MECHANISM, + CERT_UNABLE_TO_CHECK_REVOCATION, + CERT_REVOKED, + CERT_INVALID + }; + DCHECK(arraysize(kErrorFlags) == arraysize(kErrorTypes)); + + scoped_refptr<net::X509Certificate> cert = NULL; + int count = 0; + for (size_t i = 0; i < arraysize(kErrorFlags); ++i) { + if (cert_status & kErrorFlags[i]) { + count++; + if (!cert.get()) { + bool r = CertStore::GetSharedInstance()->RetrieveCert(cert_id, &cert); + DCHECK(r); + } + if (errors) + errors->push_back(SSLErrorInfo::CreateError(kErrorTypes[i], cert, url)); + } + } + return count; +} + diff --git a/chrome/browser/ssl/ssl_error_info.h b/chrome/browser/ssl/ssl_error_info.h new file mode 100644 index 0000000..c7d6698 --- /dev/null +++ b/chrome/browser/ssl/ssl_error_info.h @@ -0,0 +1,83 @@ +// Copyright (c) 2006-2008 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. + +#ifndef CHROME_BROWSER_SSL_ERROR_INFO_H__ +#define CHROME_BROWSER_SSL_ERROR_INFO_H__ + +#include <string> +#include <vector> + +#include "base/logging.h" +#include "net/base/x509_certificate.h" + +class GURL; + +// This class describes an error that happened while showing a page over SSL. +// An SSLErrorInfo object only exists on the UI thread and only contains +// information about an error (type of error and text details). +// Note no DISALLOW_EVIL_CONSTRUCTORS as we want the copy constructor. +class SSLErrorInfo { + public: + enum ErrorType { + CERT_COMMON_NAME_INVALID = 0, + CERT_DATE_INVALID, + CERT_AUTHORITY_INVALID, + CERT_CONTAINS_ERRORS, + CERT_NO_REVOCATION_MECHANISM, + CERT_UNABLE_TO_CHECK_REVOCATION, + CERT_REVOKED, + CERT_INVALID, + MIXED_CONTENTS, + UNSAFE_CONTENTS, + UNKNOWN + }; + + virtual ~SSLErrorInfo(); + + // Converts a network error code to an ErrorType. + static ErrorType NetErrorToErrorType(int net_error); + + static SSLErrorInfo CreateError(ErrorType error_type, + net::X509Certificate* cert, + const GURL& request_url); + + // Populates the specified |errors| vector with the errors contained in + // |cert_status|. Returns the number of errors found. + // Callers only interested in the error count can pass NULL for |errors|. + static int GetErrorsForCertStatus(int cert_status, + int cert_id, + const GURL& request_url, + std::vector<SSLErrorInfo>* errors); + + // A title describing the error, usually to be used with the details below. + const std::wstring& title() const { return title_; } + + // A description of the error. + const std::wstring& details() const { return details_; } + + // A short message describing the error (1 line). + const std::wstring& short_description() const { return short_description_; } + + // A lengthy explanation of what the error is. Each entry in the returned + // vector is a paragraph. + const std::vector<std::wstring>& extra_information() const { + return extra_information_; + } + +private: + SSLErrorInfo(const std::wstring& title, + const std::wstring& details, + const std::wstring& short_description, + const std::vector<std::wstring>& extra_info); + + std::wstring title_; + std::wstring details_; + std::wstring short_description_; + // Extra-informations contains paragraphs of text explaining in details what + // the error is and what the risks are. + std::vector<std::wstring> extra_information_; +}; + +#endif // CHROME_BROWSER_SSL_ERROR_INFO_H__ + diff --git a/chrome/browser/ssl/ssl_manager.cc b/chrome/browser/ssl/ssl_manager.cc new file mode 100644 index 0000000..b2fd246 --- /dev/null +++ b/chrome/browser/ssl/ssl_manager.cc @@ -0,0 +1,725 @@ +// Copyright (c) 2006-2008 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/ssl/ssl_manager.h" + +#include "base/message_loop.h" +#include "base/string_util.h" +#include "chrome/app/theme/theme_resources.h" +#include "chrome/browser/browser_process.h" +#include "chrome/browser/load_notification_details.h" +#include "chrome/browser/load_from_memory_cache_details.h" +#include "chrome/browser/render_view_host.h" +#include "chrome/browser/resource_request_details.h" +#include "chrome/browser/ssl/ssl_error_info.h" +#include "chrome/browser/ssl/ssl_policy.h" +#include "chrome/browser/tab_contents/infobar_delegate.h" +#include "chrome/browser/tab_contents/navigation_controller.h" +#include "chrome/browser/tab_contents/navigation_entry.h" +#include "chrome/browser/tab_contents/provisional_load_details.h" +#include "chrome/browser/tab_contents/tab_contents.h" +#include "chrome/browser/tab_contents/tab_util.h" +#include "chrome/browser/tab_contents/web_contents.h" +#include "chrome/common/l10n_util.h" +#include "chrome/common/notification_service.h" +#include "chrome/common/pref_names.h" +#include "chrome/common/pref_service.h" +#include "chrome/common/resource_bundle.h" +#include "chrome/views/decision.h" +#include "chrome/views/link.h" +#include "net/base/cert_status_flags.h" +#include "net/base/net_errors.h" +#include "net/url_request/url_request.h" +#include "webkit/glue/resource_type.h" +#include "generated_resources.h" + +class SSLInfoBarDelegate : public ConfirmInfoBarDelegate { + public: + SSLInfoBarDelegate(TabContents* contents, + const std::wstring message, + const std::wstring& button_label, + Task* task) + : ConfirmInfoBarDelegate(contents), + message_(message), + button_label_(button_label), + task_(task) { + } + virtual ~SSLInfoBarDelegate() {} + + // Overridden from ConfirmInfoBarDelegate: + virtual void InfoBarClosed() { + delete this; + } + virtual std::wstring GetMessageText() const { + return message_; + } + virtual SkBitmap* GetIcon() const { + return ResourceBundle::GetSharedInstance().GetBitmapNamed( + IDR_INFOBAR_SSL_WARNING); + } + virtual int GetButtons() const { + return !button_label_.empty() ? BUTTON_OK : BUTTON_NONE; + } + virtual std::wstring GetButtonLabel(InfoBarButton button) const { + return button_label_; + } + virtual bool Accept() { + if (task_.get()) { + task_->Run(); + task_.reset(); // Ensures we won't run the task again. + } + return true; + } + + private: + // Labels for the InfoBar's message and button. + std::wstring message_; + std::wstring button_label_; + + // A task to run when the InfoBar is accepted. + scoped_ptr<Task> task_; + + DISALLOW_COPY_AND_ASSIGN(SSLInfoBarDelegate); +}; + +//////////////////////////////////////////////////////////////////////////////// +// SSLManager + +// static +void SSLManager::RegisterUserPrefs(PrefService* prefs) { + prefs->RegisterIntegerPref(prefs::kMixedContentFiltering, + FilterPolicy::DONT_FILTER); +} + +SSLManager::SSLManager(NavigationController* controller, Delegate* delegate) + : controller_(controller), + delegate_(delegate) { + DCHECK(controller_); + + // If do delegate is supplied, use the default policy. + if (!delegate_) + delegate_ = SSLPolicy::GetDefaultPolicy(); + + // Subscribe to various notifications. + registrar_.Add(this, NOTIFY_NAV_ENTRY_COMMITTED, + Source<NavigationController>(controller_)); + registrar_.Add(this, NOTIFY_FAIL_PROVISIONAL_LOAD_WITH_ERROR, + Source<NavigationController>(controller_)); + registrar_.Add(this, NOTIFY_RESOURCE_RESPONSE_STARTED, + Source<NavigationController>(controller_)); + registrar_.Add(this, NOTIFY_RESOURCE_RECEIVED_REDIRECT, + Source<NavigationController>(controller_)); + registrar_.Add(this, NOTIFY_LOAD_FROM_MEMORY_CACHE, + Source<NavigationController>(controller_)); +} + +SSLManager::~SSLManager() { +} + +// Delegate API method. +void SSLManager::ShowMessage(const std::wstring& msg) { + ShowMessageWithLink(msg, std::wstring(), NULL); +} + +void SSLManager::ShowMessageWithLink(const std::wstring& msg, + const std::wstring& link_text, + Task* task) { + if (controller_->GetPendingEntry()) { + // The main frame is currently loading, wait until the load is committed so + // to show the error on the right page (once the location bar shows the + // correct url). + if (std::find(pending_messages_.begin(), pending_messages_.end(), msg) == + pending_messages_.end()) + pending_messages_.push_back(SSLMessageInfo(msg, link_text, task)); + + return; + } + + NavigationEntry* entry = controller_->GetActiveEntry(); + if (!entry) + return; + + // Don't show the message if the user doesn't expect an authenticated session. + if (entry->ssl().security_style() <= SECURITY_STYLE_UNAUTHENTICATED) + return; + + if (controller_->active_contents()) { + controller_->active_contents()->AddInfoBar( + new SSLInfoBarDelegate(controller_->active_contents(), msg, link_text, + task)); + } +} + +// Delegate API method. +bool SSLManager::SetMaxSecurityStyle(SecurityStyle style) { + NavigationEntry* entry = controller_->GetActiveEntry(); + if (!entry) { + NOTREACHED(); + return false; + } + + if (entry->ssl().security_style() > style) { + entry->ssl().set_security_style(style); + return true; + } + return false; +} + +// Delegate API method. +void SSLManager::AddMessageToConsole(const std::wstring& msg, + ConsoleMessageLevel level) { + TabContents* tab_contents = controller_->GetTabContents(TAB_CONTENTS_WEB); + if (!tab_contents) + return; + WebContents* web_contents = tab_contents->AsWebContents(); + if (!web_contents) + return; + + web_contents->render_view_host()->AddMessageToConsole( + std::wstring(), msg, level); +} + +// Delegate API method. +void SSLManager::DenyCertForHost(net::X509Certificate* cert, + const std::string& host) { + // Remember that we don't like this cert for this host. + // TODO(abarth): Do we want to persist this information in the user's profile? + cert_policy_for_host_[host].Deny(cert); +} + +// Delegate API method. +void SSLManager::AllowCertForHost(net::X509Certificate* cert, + const std::string& host) { + // Remember that we do like this cert for this host. + // TODO(abarth): Do we want to persist this information in the user's profile? + cert_policy_for_host_[host].Allow(cert); +} + +// Delegate API method. +net::X509Certificate::Policy::Judgment SSLManager::QueryPolicy( + net::X509Certificate* cert, const std::string& host) { + // TODO(abarth): Do we want to read this information from the user's profile? + return cert_policy_for_host_[host].Check(cert); +} + +bool SSLManager::CanShowInsecureContent(const GURL& url) { + // TODO(jcampan): Do we want to read this information from the user's profile? + return (can_show_insecure_content_for_host_.find(url.host()) != + can_show_insecure_content_for_host_.end()); +} + +void SSLManager::AllowShowInsecureContentForURL(const GURL& url) { + can_show_insecure_content_for_host_.insert(url.host()); +} + +bool SSLManager::ProcessedSSLErrorFromRequest() const { + NavigationEntry* entry = controller_->GetActiveEntry(); + if (!entry) { + NOTREACHED(); + return false; + } + + return net::IsCertStatusError(entry->ssl().cert_status()); +} + +//////////////////////////////////////////////////////////////////////////////// +// ErrorHandler + +SSLManager::ErrorHandler::ErrorHandler(ResourceDispatcherHost* rdh, + URLRequest* request, + MessageLoop* ui_loop) + : ui_loop_(ui_loop), + io_loop_(MessageLoop::current()), + manager_(NULL), + resource_dispatcher_host_(rdh), + request_has_been_notified_(false), + request_id_(0, 0), + request_url_(request->url()) { + DCHECK(MessageLoop::current() != ui_loop); + + ResourceDispatcherHost::ExtraRequestInfo* info = + ResourceDispatcherHost::ExtraInfoForRequest(request); + request_id_.render_process_host_id = info->render_process_host_id; + request_id_.request_id = info->request_id; + + if (!tab_util::GetTabContentsID(request, + &render_process_host_id_, + &tab_contents_id_)) + NOTREACHED(); + + // This makes sure we don't disappear on the IO thread until we've given an + // answer to the URLRequest. + // + // Release in CompleteCancelRequest, CompleteContinueRequest, + // CompleteStartRequest or CompleteTakeNoAction. + AddRef(); +} + +void SSLManager::ErrorHandler::Dispatch() { + DCHECK(MessageLoop::current() == ui_loop_); + + TabContents* web_contents = + tab_util::GetWebContentsByID(render_process_host_id_, tab_contents_id_); + + if (!web_contents) { + // We arrived on the UI thread, but the tab we're looking for is no longer + // here. + OnDispatchFailed(); + return; + } + + // Hand ourselves off to the SSLManager. + manager_ = web_contents->controller()->ssl_manager(); + OnDispatched(); +} + +WebContents* SSLManager::ErrorHandler::GetWebContents() { + return tab_util::GetWebContentsByID(render_process_host_id_, + tab_contents_id_); +} + +void SSLManager::ErrorHandler::CancelRequest() { + DCHECK(MessageLoop::current() == ui_loop_); + + // We need to complete this task on the IO thread. + io_loop_->PostTask(FROM_HERE, NewRunnableMethod( + this, &SSLManager::ErrorHandler::CompleteCancelRequest, + net::ERR_ABORTED)); +} + +void SSLManager::ErrorHandler::DenyRequest() { + DCHECK(MessageLoop::current() == ui_loop_); + + // We need to complete this task on the IO thread. + io_loop_->PostTask(FROM_HERE, NewRunnableMethod( + this, &SSLManager::ErrorHandler::CompleteCancelRequest, + net::ERR_INSECURE_RESPONSE)); +} + +void SSLManager::ErrorHandler::ContinueRequest() { + DCHECK(MessageLoop::current() == ui_loop_); + + // We need to complete this task on the IO thread. + io_loop_->PostTask(FROM_HERE, NewRunnableMethod( + this, &SSLManager::ErrorHandler::CompleteContinueRequest)); +} + +void SSLManager::ErrorHandler::StartRequest(FilterPolicy::Type filter_policy) { + DCHECK(MessageLoop::current() == ui_loop_); + + // We need to complete this task on the IO thread. + io_loop_->PostTask(FROM_HERE, NewRunnableMethod( + this, &SSLManager::ErrorHandler::CompleteStartRequest, filter_policy)); +} + +void SSLManager::ErrorHandler::TakeNoAction() { + DCHECK(MessageLoop::current() == ui_loop_); + + // We need to complete this task on the IO thread. + io_loop_->PostTask(FROM_HERE, NewRunnableMethod( + this, &SSLManager::ErrorHandler::CompleteTakeNoAction)); +} + +void SSLManager::ErrorHandler::CompleteCancelRequest(int error) { + DCHECK(MessageLoop::current() == io_loop_); + + // It is important that we notify the URLRequest only once. If we try to + // notify the request twice, it may no longer exist and |this| might have + // already have been deleted. + DCHECK(!request_has_been_notified_); + + if (!request_has_been_notified_) { + URLRequest* request = resource_dispatcher_host_->GetURLRequest(request_id_); + if (request) { + // The request can be NULL if it was cancelled by the renderer (as the + // result of the user navigating to a new page from the location bar). + DLOG(INFO) << "CompleteCancelRequest() url: " << request->url().spec(); + request->CancelWithError(error); + } + request_has_been_notified_ = true; + + // We're done with this object on the IO thread. + Release(); + } +} + +void SSLManager::ErrorHandler::CompleteContinueRequest() { + DCHECK(MessageLoop::current() == io_loop_); + + // It is important that we notify the URLRequest only once. If we try to + // notify the request twice, it may no longer exist and |this| might have + // already have been deleted. + DCHECK(!request_has_been_notified_); + + if (!request_has_been_notified_) { + URLRequest* request = resource_dispatcher_host_->GetURLRequest(request_id_); + if (request) { + // The request can be NULL if it was cancelled by the renderer (as the + // result of the user navigating to a new page from the location bar). + DLOG(INFO) << "CompleteContinueRequest() url: " << request->url().spec(); + request->ContinueDespiteLastError(); + } + request_has_been_notified_ = true; + + // We're done with this object on the IO thread. + Release(); + } +} + +void SSLManager::ErrorHandler::CompleteStartRequest( + FilterPolicy::Type filter_policy) { + DCHECK(MessageLoop::current() == io_loop_); + + // It is important that we notify the URLRequest only once. If we try to + // notify the request twice, it may no longer exist and |this| might have + // already have been deleted. + DCHECK(!request_has_been_notified_); + + if (request_has_been_notified_) + return; + + URLRequest* request = resource_dispatcher_host_->GetURLRequest(request_id_); + if (request) { + // The request can be NULL if it was cancelled by the renderer (as the + // result of the user navigating to a new page from the location bar). + DLOG(INFO) << "CompleteStartRequest() url: " << request->url().spec(); + // The request should not have been started (SUCCESS is the initial state). + DCHECK(request->status().status() == URLRequestStatus::SUCCESS); + ResourceDispatcherHost::ExtraRequestInfo* info = + ResourceDispatcherHost::ExtraInfoForRequest(request); + info->filter_policy = filter_policy; + request->Start(); + } + request_has_been_notified_ = true; + + // We're done with this object on the IO thread. + Release(); +} + +void SSLManager::ErrorHandler::CompleteTakeNoAction() { + DCHECK(MessageLoop::current() == io_loop_); + + // It is important that we notify the URLRequest only once. If we try to + // notify the request twice, it may no longer exist and |this| might have + // already have been deleted. + DCHECK(!request_has_been_notified_); + + if (!request_has_been_notified_) { + request_has_been_notified_ = true; + + // We're done with this object on the IO thread. + Release(); + } +} + + +//////////////////////////////////////////////////////////////////////////////// +// CertError + +SSLManager::CertError::CertError( + ResourceDispatcherHost* rdh, + URLRequest* request, + ResourceType::Type resource_type, + int cert_error, + net::X509Certificate* cert, + MessageLoop* ui_loop) + : ErrorHandler(rdh, request, ui_loop), + cert_error_(cert_error), + resource_type_(resource_type) { + DCHECK(request == resource_dispatcher_host_->GetURLRequest(request_id_)); + + // We cannot use the request->ssl_info(), it's not been initialized yet, so + // we have to set the fields manually. + ssl_info_.cert = cert; + ssl_info_.SetCertError(cert_error); +} + +// static +void SSLManager::OnSSLCertificateError(ResourceDispatcherHost* rdh, + URLRequest* request, + int cert_error, + net::X509Certificate* cert, + MessageLoop* ui_loop) { + DLOG(INFO) << "OnSSLCertificateError() cert_error: " << cert_error << + " url: " << request->url().spec(); + + ResourceDispatcherHost::ExtraRequestInfo* info = + ResourceDispatcherHost::ExtraInfoForRequest(request); + DCHECK(info); + + // A certificate error occurred. Construct a CertError object and hand it + // over to the UI thread for processing. + ui_loop->PostTask(FROM_HERE, + NewRunnableMethod(new CertError(rdh, request, info->resource_type, + cert_error, cert, ui_loop), + &CertError::Dispatch)); +} + +// static +void SSLManager::OnMixedContentRequest(ResourceDispatcherHost* rdh, + URLRequest* request, + MessageLoop* ui_loop) { + ui_loop->PostTask(FROM_HERE, + NewRunnableMethod(new MixedContentHandler(rdh, request, ui_loop), + &MixedContentHandler::Dispatch)); +} + +void SSLManager::OnCertError(CertError* error) { + // Ask our delegate to deal with the error. + NavigationEntry* entry = controller_->GetActiveEntry(); + // We might not have a navigation entry in some cases (e.g. when a + // HTTPS page opens a popup with no URL and then populate it with + // document.write()). See bug http://crbug.com/3845. + if (!entry) + return; + + delegate()->OnCertError(entry->url(), error); +} + +void SSLManager::OnMixedContent(MixedContentHandler* mixed_content) { + // Ask our delegate to deal with the mixed content. + NavigationEntry* entry = controller_->GetActiveEntry(); + // We might not have a navigation entry in some cases (e.g. when a + // HTTPS page opens a popup with no URL and then populate it with + // document.write()). See bug http://crbug.com/3845. + if (!entry) + return; + + delegate()->OnMixedContent(controller_, entry->url(), mixed_content); +} + +void SSLManager::Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details) { + // We should only be getting notifications from our controller. + DCHECK(source == Source<NavigationController>(controller_)); + + // Dispatch by type. + switch (type) { + case NOTIFY_NAV_ENTRY_COMMITTED: + DidCommitProvisionalLoad(details); + break; + case NOTIFY_FAIL_PROVISIONAL_LOAD_WITH_ERROR: + DidFailProvisionalLoadWithError( + Details<ProvisionalLoadDetails>(details).ptr()); + break; + case NOTIFY_RESOURCE_RESPONSE_STARTED: + DidStartResourceResponse(Details<ResourceRequestDetails>(details).ptr()); + break; + case NOTIFY_RESOURCE_RECEIVED_REDIRECT: + DidReceiveResourceRedirect( + Details<ResourceRedirectDetails>(details).ptr()); + break; + case NOTIFY_LOAD_FROM_MEMORY_CACHE: + DidLoadFromMemoryCache( + Details<LoadFromMemoryCacheDetails>(details).ptr()); + break; + default: + NOTREACHED() << "The SSLManager received an unexpected notification."; + } +} + +void SSLManager::InitializeEntryIfNeeded(NavigationEntry* entry) { + DCHECK(entry); + + // If the security style of the entry is SECURITY_STYLE_UNKNOWN, then it is a + // fresh entry and should get the default style. + if (entry->ssl().security_style() == SECURITY_STYLE_UNKNOWN) { + entry->ssl().set_security_style( + delegate()->GetDefaultStyle(entry->url())); + } +} + +void SSLManager::NavigationStateChanged() { + NavigationEntry* active_entry = controller_->GetActiveEntry(); + if (!active_entry) + return; // Nothing showing yet. + + // This might be a new entry we've never seen before. + InitializeEntryIfNeeded(active_entry); +} + +void SSLManager::DidLoadFromMemoryCache(LoadFromMemoryCacheDetails* details) { + DCHECK(details); + + // Simulate loading this resource through the usual path. + // Note that we specify SUB_RESOURCE as the resource type as WebCore only + // caches sub-resources. + delegate()->OnRequestStarted(this, details->url(), + ResourceType::SUB_RESOURCE, + details->ssl_cert_id(), + details->ssl_cert_status()); +} + +void SSLManager::DidCommitProvisionalLoad( + const NotificationDetails& in_details) { + NavigationController::LoadCommittedDetails* details = + Details<NavigationController::LoadCommittedDetails>(in_details).ptr(); + + // Ignore in-page navigations, they should not change the security style or + // the info-bars. + if (details->is_in_page) + return; + + // Decode the security details. + int ssl_cert_id, ssl_cert_status, ssl_security_bits; + DeserializeSecurityInfo(details->serialized_security_info, + &ssl_cert_id, &ssl_cert_status, &ssl_security_bits); + + bool changed = false; + if (details->is_main_frame) { + // Update the SSL states of the pending entry. + NavigationEntry* entry = controller_->GetActiveEntry(); + if (entry) { + // We may not have an entry if this is a navigation to an initial blank + // page. Reset the SSL information and add the new data we have. + entry->ssl() = NavigationEntry::SSLStatus(); + InitializeEntryIfNeeded(entry); // For security_style. + entry->ssl().set_cert_id(ssl_cert_id); + entry->ssl().set_cert_status(ssl_cert_status); + entry->ssl().set_security_bits(ssl_security_bits); + changed = true; + } + + ShowPendingMessages(); + } + + // An HTTPS response may not have a certificate for some reason. When that + // happens, use the unauthenticated (HTTP) rather than the authentication + // broken security style so that we can detect this error condition. + if (net::IsCertStatusError(ssl_cert_status)) { + changed |= SetMaxSecurityStyle(SECURITY_STYLE_AUTHENTICATION_BROKEN); + if (!details->is_main_frame && + !details->entry->ssl().has_unsafe_content()) { + details->entry->ssl().set_has_unsafe_content(); + changed = true; + } + } else if (details->entry->url().SchemeIsSecure() && !ssl_cert_id) { + if (details->is_main_frame) { + changed |= SetMaxSecurityStyle(SECURITY_STYLE_UNAUTHENTICATED); + } else { + // If the frame has been blocked we keep our security style as + // authenticated in that case as nothing insecure is actually showing or + // loaded. + if (!details->is_content_filtered && + !details->entry->ssl().has_mixed_content()) { + details->entry->ssl().set_has_mixed_content(); + changed = true; + } + } + } + + if (changed) { + // Only send the notification when something actually changed. + NotificationService::current()->Notify( + NOTIFY_SSL_STATE_CHANGED, + Source<NavigationController>(controller_), + NotificationService::NoDetails()); + } +} + +void SSLManager::DidFailProvisionalLoadWithError( + ProvisionalLoadDetails* details) { + DCHECK(details); + + // Ignore in-page navigations. + if (details->in_page_navigation()) + return; + + if (details->main_frame()) + ClearPendingMessages(); +} + +void SSLManager::DidStartResourceResponse(ResourceRequestDetails* details) { + DCHECK(details); + + // Notify our delegate that we started a resource request. Ideally, the + // delegate should have the ability to cancel the request, but we can't do + // that yet. + delegate()->OnRequestStarted(this, details->url(), + details->resource_type(), + details->ssl_cert_id() , + details->ssl_cert_status()); +} + +void SSLManager::DidReceiveResourceRedirect(ResourceRedirectDetails* details) { + // TODO(jcampan): when we receive a redirect for a sub-resource, we may want + // to clear any mixed/unsafe content error that it may have triggered. +} + +void SSLManager::ShowPendingMessages() { + std::vector<SSLMessageInfo>::const_iterator iter; + for (iter = pending_messages_.begin(); + iter != pending_messages_.end(); ++iter) { + ShowMessageWithLink(iter->message, iter->link_text, iter->action); + } + ClearPendingMessages(); +} + +void SSLManager::ClearPendingMessages() { + pending_messages_.clear(); +} + +// static +std::string SSLManager::SerializeSecurityInfo(int cert_id, + int cert_status, + int security_bits) { + Pickle pickle; + pickle.WriteInt(cert_id); + pickle.WriteInt(cert_status); + pickle.WriteInt(security_bits); + return std::string(static_cast<const char*>(pickle.data()), pickle.size()); +} + +// static +bool SSLManager::DeserializeSecurityInfo(const std::string& state, + int* cert_id, + int* cert_status, + int* security_bits) { + DCHECK(cert_id && cert_status && security_bits); + if (state.empty()) { + // No SSL used. + *cert_id = 0; + *cert_status = 0; + *security_bits = -1; + return false; + } + + Pickle pickle(state.data(), static_cast<int>(state.size())); + void * iter = NULL; + pickle.ReadInt(&iter, cert_id); + pickle.ReadInt(&iter, cert_status); + pickle.ReadInt(&iter, security_bits); + return true; +} + +// static +bool SSLManager::GetEVCertNames(const net::X509Certificate& cert, + std::wstring* short_name, + std::wstring* ca_name) { + DCHECK(short_name || ca_name); + + // EV are required to have an organization name and country. + if (cert.subject().organization_names.empty() || + cert.subject().country_name.empty()) { + NOTREACHED(); + return false; + } + + if (short_name) { + *short_name = l10n_util::GetStringF( + IDS_SECURE_CONNECTION_EV, + UTF8ToWide(cert.subject().organization_names[0]), + UTF8ToWide(cert.subject().country_name)); + } + + if (ca_name) { + // TODO(wtc): should we show the root CA's name instead? + *ca_name = l10n_util::GetStringF( + IDS_SECURE_CONNECTION_EV_CA, + UTF8ToWide(cert.issuer().organization_names[0])); + } + return true; +} + diff --git a/chrome/browser/ssl/ssl_manager.h b/chrome/browser/ssl/ssl_manager.h new file mode 100644 index 0000000..e650b35 --- /dev/null +++ b/chrome/browser/ssl/ssl_manager.h @@ -0,0 +1,465 @@ +// Copyright (c) 2006-2008 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. + +#ifndef CHROME_BROWSER_SSL_MANAGER_H_ +#define CHROME_BROWSER_SSL_MANAGER_H_ + +#include <string> +#include <map> + +#include "base/basictypes.h" +#include "base/observer_list.h" +#include "base/ref_counted.h" +#include "chrome/browser/renderer_host/resource_dispatcher_host.h" +#include "chrome/browser/security_style.h" +#include "chrome/browser/tab_contents/provisional_load_details.h" +#include "chrome/common/notification_registrar.h" +#include "chrome/common/notification_service.h" +#include "chrome/common/render_messages.h" +#include "googleurl/src/gurl.h" +#include "net/base/net_errors.h" +#include "net/base/ssl_info.h" +#include "net/base/x509_certificate.h" +#include "webkit/glue/console_message_level.h" +#include "webkit/glue/resource_type.h" + +class AutomationProvider; +class NavigationEntry; +class LoadFromMemoryCacheDetails; +class LoadNotificationDetails; +class NavigationController; +class PrefService; +class ResourceRedirectDetails; +class ResourceRequestDetails; +class SSLErrorInfo; +class Task; +class URLRequest; +class WebContents; + +// The SSLManager SSLManager controls the SSL UI elements in a TabContents. It +// listens for various events that influence when these elements should or +// should not be displayed and adjusts them accordingly. +// +// There is one SSLManager per tab. +// The security state (secure/insecure) is stored in the navigation entry. +// Along with it are stored any SSL error code and the associated cert. +// + +class SSLManager : public NotificationObserver { + public: + // An ErrorHandler carries information from the IO thread to the UI thread + // and is dispatched to the appropriate SSLManager when it arrives on the + // UI thread. Subclasses should override the OnDispatched/OnDispatchFailed + // methods to implement the actions that should be taken on the UI thread. + // These methods can call the different convenience methods ContinueRequest/ + // CancelRequest/StartRequest to perform any required action on the URLRequest + // the ErrorHandler was created with. + // IMPORTANT NOTE: if you are not doing anything in + // OnDispatched/OnDispatchFailed, make sure you call TakeNoAction(). This is + // necessary for ensuring the instance is not leaked. + class ErrorHandler : public base::RefCountedThreadSafe<ErrorHandler> { + public: + virtual ~ErrorHandler() { } + + // Find the appropriate SSLManager for the URLRequest and begin handling + // this error. + // + // Call on UI thread. + void Dispatch(); + + // Available on either thread. + const GURL& request_url() const { return request_url_; } + + // Call on the UI thread. + SSLManager* manager() const { return manager_; }; + + // Returns the WebContents this object is associated with. Should be + // called from the UI thread. + WebContents* GetWebContents(); + + // Cancels the associated URLRequest. + // This method can be called from OnDispatchFailed and OnDispatched. + void CancelRequest(); + + // Continue the URLRequest ignoring any previous errors. Note that some + // errors cannot be ignored, in which case this will result in the request + // being canceled. + // This method can be called from OnDispatchFailed and OnDispatched. + void ContinueRequest(); + + // Cancels the associated URLRequest and mark it as denied. The renderer + // processes such request in a special manner, optionally replacing them + // with alternate content (typically frames content is replaced with a + // warning message). + // This method can be called from OnDispatchFailed and OnDispatched. + void DenyRequest(); + + // Starts the associated URLRequest. |filter_policy| specifies whether the + // ResourceDispatcher should attempt to filter the loaded content in order + // to make it secure (ex: images are made slightly transparent and are + // stamped). + // Should only be called when the URLRequest has not already been started. + // This method can be called from OnDispatchFailed and OnDispatched. + void StartRequest(FilterPolicy::Type filter_policy); + + // Does nothing on the URLRequest but ensures the current instance ref + // count is decremented appropriately. Subclasses that do not want to + // take any specific actions in their OnDispatched/OnDispatchFailed should + // call this. + void TakeNoAction(); + + protected: + // Construct on the IO thread. + ErrorHandler(ResourceDispatcherHost* resource_dispatcher_host, + URLRequest* request, + MessageLoop* ui_loop); + + // The following 2 methods are the methods subclasses should implement. + virtual void OnDispatchFailed() { TakeNoAction(); } + + // Can use the manager_ member. + virtual void OnDispatched() { TakeNoAction(); } + + // We cache the message loops to be able to proxy events across the thread + // boundaries. + MessageLoop* ui_loop_; + MessageLoop* io_loop_; + + // Should only be accessed on the UI thread. + SSLManager* manager_; // Our manager. + + // The id of the URLRequest associated with this object. + // Should only be accessed from the IO thread. + ResourceDispatcherHost::GlobalRequestID request_id_; + + // The ResourceDispatcherHost we are associated with. + ResourceDispatcherHost* resource_dispatcher_host_; + + private: + // Completes the CancelRequest operation on the IO thread. + // Call on the IO thread. + void CompleteCancelRequest(int error); + + // Completes the ContinueRequest operation on the IO thread. + // + // Call on the IO thread. + void CompleteContinueRequest(); + + // Completes the StartRequest operation on the IO thread. + // Call on the IO thread. + void CompleteStartRequest(FilterPolicy::Type filter_policy); + + // Derefs this instance. + // Call on the IO thread. + void CompleteTakeNoAction(); + + // We use these members to find the correct SSLManager when we arrive on + // the UI thread. + int render_process_host_id_; + int tab_contents_id_; + + // This read-only member can be accessed on any thread. + const GURL request_url_; // The URL that we requested. + + // Should only be accessed on the IO thread + bool request_has_been_notified_; // A flag to make sure we notify the + // URLRequest exactly once. + + DISALLOW_EVIL_CONSTRUCTORS(ErrorHandler); + }; + + // A CertError represents an error that occurred with the certificate in an + // SSL session. A CertError object exists both on the IO thread and on the UI + // thread and allows us to cancel/continue a request it is associated with. + class CertError : public ErrorHandler { + public: + // These accessors are available on either thread + const net::SSLInfo& ssl_info() const { return ssl_info_; } + int cert_error() const { return cert_error_; } + + ResourceType::Type resource_type() const { return resource_type_; } + private: + // SSLManager is responsible for creating CertError objects. + friend class SSLManager; + + // Construct on the IO thread. + // We mark this method as private because it is tricky to correctly + // construct a CertError object. + CertError(ResourceDispatcherHost* resource_dispatcher_host, + URLRequest* request, + ResourceType::Type resource_type, + int cert_error, + net::X509Certificate* cert, + MessageLoop* ui_loop); + + // ErrorHandler methods + virtual void OnDispatchFailed() { CancelRequest(); } + virtual void OnDispatched() { manager_->OnCertError(this); } + + // These read-only members can be accessed on any thread. + net::SSLInfo ssl_info_; + const int cert_error_; // The error we represent. + + // What kind of resource is associated with the requested that generated + // that error. + ResourceType::Type resource_type_; + + DISALLOW_EVIL_CONSTRUCTORS(CertError); + }; + + // The MixedContentHandler class is used to query what to do with + // mixed content, from the IO thread to the UI thread. + class MixedContentHandler : public ErrorHandler { + public: + // Created on the IO thread. + MixedContentHandler(ResourceDispatcherHost* rdh, + URLRequest* request, + MessageLoop* ui_loop) + : ErrorHandler(rdh, request, ui_loop) { } + + protected: + virtual void OnDispatchFailed() { TakeNoAction(); } + virtual void OnDispatched() { manager()->OnMixedContent(this); } + + private: + DISALLOW_EVIL_CONSTRUCTORS(MixedContentHandler); + }; + + // The SSLManager will ask its delegate to decide how to handle events + // relevant to SSL. Delegates are expected to be stateless and intended to be + // easily implementable. + // + // Delegates should interact with the rest of the browser only through their + // parameters and through the delegate API of the SSLManager. + // + // If a delegate needs to do something tricky, consider having the SSLManager + // do it instead. + class Delegate { + public: + // An error occurred with the certificate in an SSL connection. + virtual void OnCertError(const GURL& main_frame_url, CertError* error) = 0; + + // A request for a mixed-content resource was made. Note that the resource + // request was not started yet and the delegate is responsible for starting + // it. + virtual void OnMixedContent( + NavigationController* navigation_controller, + const GURL& main_frame_url, + MixedContentHandler* mixed_content_handler) = 0; + + // We have started a resource request for the given URL. + virtual void OnRequestStarted(SSLManager* manager, + const GURL& url, + ResourceType::Type resource_type, + int ssl_cert_id, + int ssl_cert_status) = 0; + + // Returns the default security style for a given URL. + virtual SecurityStyle GetDefaultStyle(const GURL& url) = 0; + }; + + static void RegisterUserPrefs(PrefService* prefs); + + // Construct an SSLManager for the specified tab. + // If |delegate| is NULL, SSLPolicy::GetDefaultPolicy() is used. + SSLManager(NavigationController* controller, Delegate* delegate); + + ~SSLManager(); + + ////////////////////////////////////////////////////////////////////////////// + // Delegate API + // + // The SSL manager expects these methods to be called by its delegate. They + // exist to make Delegates easy to implement. + + // Ensure that the specified message is displayed to the user. This will + // display an InfoBar at the top of the associated tab. + void ShowMessage(const std::wstring& msg); + + // Same as ShowMessage but also contains a link that when clicked run the + // specified task. The SSL Manager becomes the owner of the task. + void ShowMessageWithLink(const std::wstring& msg, + const std::wstring& link_text, + Task* task); + + // Sets the maximum security style for the page. If the current security + // style is lower than |style|, this will not have an effect on the security + // indicators. + // + // It will return true if the navigation entry was updated or false if + // nothing changed. The caller is responsible for broadcasting + // NOTIFY_SSY_STATE_CHANGED if it returns true. + bool SetMaxSecurityStyle(SecurityStyle style); + + // Logs a message to the console of the page. + void AddMessageToConsole(const std::wstring& msg, + ConsoleMessageLevel level); + + // Records that |cert| is permitted to be used for |host| in the future. + void DenyCertForHost(net::X509Certificate* cert, const std::string& host); + + // Records that |cert| is not permitted to be used for |host| in the future. + void AllowCertForHost(net::X509Certificate* cert, const std::string& host); + + // Queries whether |cert| is allowed or denied for |host|. + net::X509Certificate::Policy::Judgment QueryPolicy( + net::X509Certificate* cert, const std::string& host); + + // Allow mixed/unsafe content to be visible (non filtered) for the specified + // URL. + // Note that the current implementation allows on a host name basis. + void AllowShowInsecureContentForURL(const GURL& url); + + // Returns whether the specified URL is allowed to show insecure (mixed or + // unsafe) content. + bool CanShowInsecureContent(const GURL& url); + + // + ////////////////////////////////////////////////////////////////////////////// + + // The delegate of the SSLManager. This value may be changed at any time, + // but it is not permissible for it to be NULL. + Delegate* delegate() const { return delegate_; } + void set_delegate(Delegate* delegate) { delegate_ = delegate; } + + // Entry point for SSLCertificateErrors. This function begins the process + // of resolving a certificate error during an SSL connection. SSLManager + // will adjust the security UI and either call |Cancel| or + // |ContinueDespiteLastError| on the URLRequest. + // + // Called on the IO thread. + static void OnSSLCertificateError(ResourceDispatcherHost* resource_dispatcher, + URLRequest* request, + int cert_error, + net::X509Certificate* cert, + MessageLoop* ui_loop); + + // Called when a mixed-content sub-resource request has been detected. The + // request is not started yet. The SSLManager will make a decision on whether + // to filter that request's content (with the filter_policy flag). + // TODO (jcampan): Implement a way to just cancel the request. This is not + // straight-forward as canceling a request that has not been started will + // not remove from the pending_requests_ of the ResourceDispatcherHost. + // Called on the IO thread. + static void OnMixedContentRequest(ResourceDispatcherHost* resource_dispatcher, + URLRequest* request, + MessageLoop* ui_loop); + + // Called by CertError::Dispatch to kick off processing of the cert error by + // the SSL manager. The error originated from the ResourceDispatcherHost. + // + // Called on the UI thread. + void OnCertError(CertError* error); + + // Called by MixedContentHandler::Dispatch to kick off processing of the + // mixed-content resource request. The info originated from the + // ResourceDispatcherHost. + // + // Called on the UI thread. + void OnMixedContent(MixedContentHandler* mixed_content); + + // Entry point for navigation. This function begins the process of updating + // the security UI when the main frame navigates to a new URL. + // + // Called on the UI thread. + virtual void Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details); + + // Entry point for navigation. This function begins the process of updating + // the security UI when the main frame navigates. + // + // Called on the UI thread. + void NavigationStateChanged(); + + // Called to determine if there were any processed SSL errors from request. + bool ProcessedSSLErrorFromRequest() const; + + NavigationController* controller() { return controller_; } + + // Convenience methods for serializing/deserializing the security info. + static std::string SerializeSecurityInfo(int cert_id, + int cert_status, + int security_bits); + static bool DeserializeSecurityInfo(const std::string& state, + int* cert_id, + int* cert_status, + int* security_bits); + + // Sets |short_name| to <organization_name> [<country>] and |ca_name| + // to something like: + // "Verified by <issuer_organization_name>" + static bool GetEVCertNames(const net::X509Certificate& cert, + std::wstring* short_name, + std::wstring* ca_name); + + private: + // SSLMessageInfo contains the information necessary for displaying a message + // in an info-bar. + struct SSLMessageInfo { + public: + explicit SSLMessageInfo(const std::wstring& text) + : message(text), + action(NULL) { } + SSLMessageInfo(const std::wstring& message, + const std::wstring& link_text, + Task* action) + : message(message), link_text(link_text), action(action) { } + + // Overridden so that std::find works. + bool operator==(const std::wstring& other_message) const { + // We are uniquing SSLMessageInfo by their message only. + return message == other_message; + } + + std::wstring message; + std::wstring link_text; + Task* action; + }; + + // Entry points for notifications to which we subscribe. Note that + // DidCommitProvisionalLoad uses the abstract NotificationDetails type since + // the type we need is in NavigationController which would create a circular + // header file dependency. + void DidLoadFromMemoryCache(LoadFromMemoryCacheDetails* details); + void DidCommitProvisionalLoad(const NotificationDetails& details); + void DidFailProvisionalLoadWithError(ProvisionalLoadDetails* details); + void DidStartResourceResponse(ResourceRequestDetails* details); + void DidReceiveResourceRedirect(ResourceRedirectDetails* details); + + // Convenience method for initializing navigation entries. + void InitializeEntryIfNeeded(NavigationEntry* entry); + + // Shows the pending messages (in info-bars) if any. + void ShowPendingMessages(); + + // Clears any pending messages. + void ClearPendingMessages(); + + // Our delegate. The delegate is responsible for making policy decisions. + // Must not be NULL. + Delegate* delegate_; + + // The NavigationController that owns this SSLManager. We are responsible + // for the security UI of this tab. + NavigationController* controller_; + + // Handles registering notifications with the NotificationService. + NotificationRegistrar registrar_; + + // Certificate policies for each host. + std::map<std::string, net::X509Certificate::Policy> cert_policy_for_host_; + + // Domains for which it is OK to show insecure content. + std::set<std::string> can_show_insecure_content_for_host_; + + // The list of messages that should be displayed (in info bars) when the page + // currently loading had loaded. + std::vector<SSLMessageInfo> pending_messages_; + + DISALLOW_COPY_AND_ASSIGN(SSLManager); +}; + +#endif // CHROME_BROWSER_SSL_MANAGER_H_ + diff --git a/chrome/browser/ssl/ssl_policy.cc b/chrome/browser/ssl/ssl_policy.cc new file mode 100644 index 0000000..e857244 --- /dev/null +++ b/chrome/browser/ssl/ssl_policy.cc @@ -0,0 +1,483 @@ +// Copyright (c) 2006-2008 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/ssl/ssl_policy.h" + +#include "base/singleton.h" +#include "base/string_piece.h" +#include "base/string_util.h" +#include "chrome/browser/browser_resources.h" +#include "chrome/browser/cert_store.h" +#include "chrome/browser/render_view_host.h" +#include "chrome/browser/ssl/ssl_error_info.h" +#include "chrome/browser/tab_contents/navigation_entry.h" +#include "chrome/browser/tab_contents/tab_contents.h" +#include "chrome/browser/tab_contents/web_contents.h" +#include "chrome/common/jstemplate_builder.h" +#include "chrome/common/l10n_util.h" +#include "chrome/common/notification_service.h" +#include "chrome/common/pref_names.h" +#include "chrome/common/pref_service.h" +#include "chrome/common/resource_bundle.h" +#include "chrome/common/chrome_switches.h" +#include "chrome/common/time_format.h" +#include "generated_resources.h" +#include "net/base/cert_status_flags.h" +#include "net/base/ssl_info.h" +#include "webkit/glue/console_message_level.h" +#include "webkit/glue/resource_type.h" + +// Wrap all these helper classes in an anonymous namespace. +namespace { + +static const char kDot = '.'; + +class ShowUnsafeContentTask : public Task { + public: + ShowUnsafeContentTask(const GURL& main_frame_url, + SSLManager::ErrorHandler* error_handler); + virtual ~ShowUnsafeContentTask(); + + virtual void Run(); + + private: + scoped_refptr<SSLManager::ErrorHandler> error_handler_; + GURL main_frame_url_; + + DISALLOW_EVIL_CONSTRUCTORS(ShowUnsafeContentTask); +}; + +ShowUnsafeContentTask::ShowUnsafeContentTask( + const GURL& main_frame_url, + SSLManager::ErrorHandler* error_handler) + : main_frame_url_(main_frame_url), + error_handler_(error_handler) { +} + +ShowUnsafeContentTask::~ShowUnsafeContentTask() { +} + +void ShowUnsafeContentTask::Run() { + error_handler_->manager()->AllowShowInsecureContentForURL(main_frame_url_); + // Reload the page. + error_handler_->GetWebContents()->controller()->Reload(true); +} + +static void ShowErrorPage(SSLPolicy* policy, SSLManager::CertError* error) { + SSLErrorInfo error_info = policy->GetSSLErrorInfo(error); + + // Let's build the html error page. + DictionaryValue strings; + strings.SetString(L"title", l10n_util::GetString(IDS_SSL_ERROR_PAGE_TITLE)); + strings.SetString(L"headLine", error_info.title()); + strings.SetString(L"description", error_info.details()); + strings.SetString(L"moreInfoTitle", + l10n_util::GetString(IDS_CERT_ERROR_EXTRA_INFO_TITLE)); + SSLBlockingPage::SetExtraInfo(&strings, error_info.extra_information()); + + strings.SetString(L"back", l10n_util::GetString(IDS_SSL_ERROR_PAGE_BACK)); + + strings.SetString(L"textdirection", + (l10n_util::GetTextDirection() == l10n_util::RIGHT_TO_LEFT) ? + L"rtl" : L"ltr"); + + static const StringPiece html( + ResourceBundle::GetSharedInstance().GetRawDataResource( + IDR_SSL_ERROR_HTML)); + + std::string html_text(jstemplate_builder::GetTemplateHtml(html, &strings, + "template_root")); + + WebContents* tab = error->GetWebContents(); + int cert_id = CertStore::GetSharedInstance()->StoreCert( + error->ssl_info().cert, tab->render_view_host()->process()->host_id()); + std::string security_info = + SSLManager::SerializeSecurityInfo(cert_id, + error->ssl_info().cert_status, + error->ssl_info().security_bits); + tab->render_view_host()->LoadAlternateHTMLString(html_text, + true, + error->request_url(), + security_info); + tab->controller()->GetActiveEntry()->set_page_type( + NavigationEntry::ERROR_PAGE); +} + +static void ShowBlockingPage(SSLPolicy* policy, SSLManager::CertError* error) { + SSLBlockingPage* blocking_page = new SSLBlockingPage(error, policy); + blocking_page->Show(); +} + +static bool IsIntranetHost(const std::string& host) { + const size_t dot = host.find(kDot); + return dot == std::basic_string<CHAR>::npos || + dot == host.length() - 1; +} + +class CommonNameInvalidPolicy : public SSLPolicy { + public: + static SSLPolicy* GetInstance() { + return Singleton<CommonNameInvalidPolicy>::get(); + } + + void OnCertError(const GURL& main_frame_url, + SSLManager::CertError* error) { + OnOverridableCertError(main_frame_url, error); + } +}; + +class DateInvalidPolicy : public SSLPolicy { + public: + static SSLPolicy* GetInstance() { + return Singleton<DateInvalidPolicy>::get(); + } + + void OnCertError(const GURL& main_frame_url, + SSLManager::CertError* error) { + OnOverridableCertError(main_frame_url, error); + } +}; + +class AuthorityInvalidPolicy : public SSLPolicy { + public: + static SSLPolicy* GetInstance() { + return Singleton<AuthorityInvalidPolicy>::get(); + } + + void OnCertError(const GURL& main_frame_url, + SSLManager::CertError* error) { + OnOverridableCertError(main_frame_url, error); + } +}; + +class ContainsErrorsPolicy : public SSLPolicy { + public: + static SSLPolicy* GetInstance() { + return Singleton<ContainsErrorsPolicy>::get(); + } + + void OnCertError(const GURL& main_frame_url, + SSLManager::CertError* error) { + OnFatalCertError(main_frame_url, error); + } +}; + +class NoRevocationMechanismPolicy : public SSLPolicy { + public: + static SSLPolicy* GetInstance() { + return Singleton<NoRevocationMechanismPolicy>::get(); + } + + void OnCertError(const GURL& main_frame_url, + SSLManager::CertError* error) { + // Silently ignore this error. + error->ContinueRequest(); + } +}; + +class UnableToCheckRevocationPolicy : public SSLPolicy { + public: + static SSLPolicy* GetInstance() { + return Singleton<UnableToCheckRevocationPolicy>::get(); + } + + void OnCertError(const GURL& main_frame_url, + SSLManager::CertError* error) { + // We ignore this error and display an info-bar. + error->ContinueRequest(); + error->manager()->ShowMessage(l10n_util::GetString( + IDS_CERT_ERROR_UNABLE_TO_CHECK_REVOCATION_INFO_BAR)); + } +}; + +class RevokedPolicy : public SSLPolicy { + public: + static SSLPolicy* GetInstance() { + return Singleton<RevokedPolicy>::get(); + } + + void OnCertError(const GURL& main_frame_url, + SSLManager::CertError* error) { + OnFatalCertError(main_frame_url, error); + } +}; + +class InvalidPolicy : public SSLPolicy { + public: + static SSLPolicy* GetInstance() { + return Singleton<InvalidPolicy>::get(); + } + + void OnCertError(const GURL& main_frame_url, + SSLManager::CertError* error) { + OnFatalCertError(main_frame_url, error); + } +}; + +class DefaultPolicy : public SSLPolicy { + public: + DefaultPolicy() { + // Load our helper classes to handle various cert errors. + DCHECK(SubPolicyIndex(net::ERR_CERT_COMMON_NAME_INVALID) == 0); + sub_policies_[0] = CommonNameInvalidPolicy::GetInstance(); + DCHECK(SubPolicyIndex(net::ERR_CERT_DATE_INVALID) == 1); + sub_policies_[1] = DateInvalidPolicy::GetInstance(); + DCHECK(SubPolicyIndex(net::ERR_CERT_AUTHORITY_INVALID) == 2); + sub_policies_[2] = AuthorityInvalidPolicy::GetInstance(); + DCHECK(SubPolicyIndex(net::ERR_CERT_CONTAINS_ERRORS) == 3); + sub_policies_[3] = ContainsErrorsPolicy::GetInstance(); + DCHECK(SubPolicyIndex(net::ERR_CERT_NO_REVOCATION_MECHANISM) == 4); + sub_policies_[4] = NoRevocationMechanismPolicy::GetInstance(); + DCHECK(SubPolicyIndex(net::ERR_CERT_UNABLE_TO_CHECK_REVOCATION) == 5); + sub_policies_[5] = UnableToCheckRevocationPolicy::GetInstance(); + DCHECK(SubPolicyIndex(net::ERR_CERT_REVOKED) == 6); + sub_policies_[6] = RevokedPolicy::GetInstance(); + DCHECK(SubPolicyIndex(net::ERR_CERT_INVALID) == 7); + sub_policies_[7] = InvalidPolicy::GetInstance(); + DCHECK(SubPolicyIndex(net::ERR_CERT_END) == 8); + } + + void OnCertError(const GURL& main_frame_url, + SSLManager::CertError* error) { + int index = SubPolicyIndex(error->cert_error()); + if (index < 0 || index >= arraysize(sub_policies_)) { + NOTREACHED(); + error->CancelRequest(); + return; + } + + // First we check if we know the policy for this error. + net::X509Certificate::Policy::Judgment judgment = + error->manager()->QueryPolicy(error->ssl_info().cert, + error->request_url().host()); + + switch (judgment) { + case net::X509Certificate::Policy::ALLOWED: + // We've been told to allow this certificate. + if (error->manager()->SetMaxSecurityStyle( + SECURITY_STYLE_AUTHENTICATION_BROKEN)) { + NotificationService::current()->Notify( + NOTIFY_SSL_STATE_CHANGED, + Source<NavigationController>(error->manager()->controller()), + Details<NavigationEntry>( + error->manager()->controller()->GetActiveEntry())); + } + error->ContinueRequest(); + break; + case net::X509Certificate::Policy::DENIED: + // For now we handle the DENIED as the UNKNOWN, which means a blocking + // page is shown to the user every time he comes back to the page. + case net::X509Certificate::Policy::UNKNOWN: + // We don't know how to handle this error. Ask our sub-policies. + sub_policies_[index]->OnCertError(main_frame_url, error); + break; + default: + NOTREACHED(); + } + } + + void OnMixedContent(NavigationController* navigation_controller, + const GURL& main_frame_url, + SSLManager::MixedContentHandler* mixed_content_handler) { + PrefService* prefs = navigation_controller->profile()->GetPrefs(); + FilterPolicy::Type filter_policy = FilterPolicy::DONT_FILTER; + if (!mixed_content_handler->manager()-> + CanShowInsecureContent(main_frame_url)) { + filter_policy = FilterPolicy::FromInt( + prefs->GetInteger(prefs::kMixedContentFiltering)); + } + if (filter_policy != FilterPolicy::DONT_FILTER) { + mixed_content_handler->manager()->ShowMessageWithLink( + l10n_util::GetString(IDS_SSL_INFO_BAR_FILTERED_CONTENT), + l10n_util::GetString(IDS_SSL_INFO_BAR_SHOW_CONTENT), + new ShowUnsafeContentTask(main_frame_url, mixed_content_handler)); + } + mixed_content_handler->StartRequest(filter_policy); + + NavigationEntry* entry = navigation_controller->GetLastCommittedEntry(); + DCHECK(entry); + // Even though we are loading the mixed-content resource, it will not be + // included in the page when we set the policy to FILTER_ALL or + // FILTER_ALL_EXCEPT_IMAGES (only images and they are stamped with warning + // icons), so we don't set the mixed-content mode in these cases. + if (filter_policy == FilterPolicy::DONT_FILTER) + entry->ssl().set_has_mixed_content(); + + // Print a message indicating the mixed-contents resource in the console. + const std::wstring& msg = l10n_util::GetStringF( + IDS_MIXED_CONTENT_LOG_MESSAGE, + UTF8ToWide(entry->url().spec()), + UTF8ToWide(mixed_content_handler->request_url().spec())); + mixed_content_handler->manager()-> + AddMessageToConsole(msg, MESSAGE_LEVEL_WARNING); + + NotificationService::current()->Notify( + NOTIFY_SSL_STATE_CHANGED, + Source<NavigationController>(navigation_controller), + Details<NavigationEntry>(entry)); + } + + void OnDenyCertificate(SSLManager::CertError* error) { + int index = SubPolicyIndex(error->cert_error()); + if (index < 0 || index >= arraysize(sub_policies_)) { + NOTREACHED(); + return; + } + sub_policies_[index]->OnDenyCertificate(error); + } + + void OnAllowCertificate(SSLManager::CertError* error) { + int index = SubPolicyIndex(error->cert_error()); + if (index < 0 || index >= arraysize(sub_policies_)) { + NOTREACHED(); + return; + } + sub_policies_[index]->OnAllowCertificate(error); + } + + private: + // Returns the index of the sub-policy for |cert_error| in the + // sub_policies_ array. + int SubPolicyIndex(int cert_error) { + // Certificate errors are negative integers from net::ERR_CERT_BEGIN + // (inclusive) to net::ERR_CERT_END (exclusive) in *decreasing* order. + return net::ERR_CERT_BEGIN - cert_error; + } + SSLPolicy* sub_policies_[net::ERR_CERT_BEGIN - net::ERR_CERT_END]; +}; + +} // namespace + +SSLPolicy* SSLPolicy::GetDefaultPolicy() { + // Lazily initialize our default policy instance. + static SSLPolicy* default_policy = new DefaultPolicy(); + return default_policy; +} + +SSLPolicy::SSLPolicy() { +} + +void SSLPolicy::OnCertError(const GURL& main_frame_url, + SSLManager::CertError* error) { + // Default to secure behavior. + error->CancelRequest(); +} + +void SSLPolicy::OnRequestStarted(SSLManager* manager, const GURL& url, + ResourceType::Type resource_type, + int ssl_cert_id, int ssl_cert_status) { + // These schemes never leave the browser and don't require a warning. + if (url.SchemeIs("data") || url.SchemeIs("javascript") || + url.SchemeIs("about")) + return; + + NavigationEntry* entry = manager->controller()->GetActiveEntry(); + if (!entry) { + // We may not have an entry for cases such as the inspector. + return; + } + + NavigationEntry::SSLStatus& ssl = entry->ssl(); + bool changed = false; + if (!entry->url().SchemeIsSecure() || // Current page is not secure. + resource_type == ResourceType::MAIN_FRAME || // Main frame load. + net::IsCertStatusError(ssl.cert_status())) { // There is already + // an error for the main page, don't report sub-resources as unsafe + // content. + // No mixed/unsafe content check necessary. + return; + } + + if (url.SchemeIsSecure()) { + // Check for insecure content (anything served over intranet is considered + // insecure). + + // TODO(jcampan): bug #1178228 Disabling the broken style for intranet + // hosts for beta as it is missing error strings (and cert status). + // if (IsIntranetHost(url.host()) || + // net::IsCertStatusError(ssl_cert_status)) { + if (net::IsCertStatusError(ssl_cert_status)) { + // The resource is unsafe. + if (!ssl.has_unsafe_content()) { + changed = true; + ssl.set_has_unsafe_content(); + manager->SetMaxSecurityStyle(SECURITY_STYLE_AUTHENTICATION_BROKEN); + } + } + } + + if (changed) { + // Only send the notification when something actually changed. + NotificationService::current()->Notify( + NOTIFY_SSL_STATE_CHANGED, + Source<NavigationController>(manager->controller()), + NotificationService::NoDetails()); + } +} + +SecurityStyle SSLPolicy::GetDefaultStyle(const GURL& url) { + // Show the secure style for HTTPS. + if (url.SchemeIsSecure()) { + // TODO(jcampan): bug #1178228 Disabling the broken style for intranet + // hosts for beta as it is missing error strings (and cert status). + // CAs issue certs for intranet hosts to anyone. + // if (IsIntranetHost(url.host())) + // return SECURITY_STYLE_AUTHENTICATION_BROKEN; + + return SECURITY_STYLE_AUTHENTICATED; + } + + // Otherwise, show the unauthenticated style. + return SECURITY_STYLE_UNAUTHENTICATED; +} + +SSLErrorInfo SSLPolicy::GetSSLErrorInfo(SSLManager::CertError* error) { + return SSLErrorInfo::CreateError( + SSLErrorInfo::NetErrorToErrorType(error->cert_error()), + error->ssl_info().cert, error->request_url()); +} + +void SSLPolicy::OnDenyCertificate(SSLManager::CertError* error) { + // Default behavior for rejecting a certificate. + error->CancelRequest(); + error->manager()->DenyCertForHost(error->ssl_info().cert, + error->request_url().host()); +} + +void SSLPolicy::OnAllowCertificate(SSLManager::CertError* error) { + // Default behavior for accepting a certificate. + // Note that we should not call SetMaxSecurityStyle here, because the active + // NavigationEntry has just been deleted (in HideInterstitialPage) and the + // new NavigationEntry will not be set until DidNavigate. This is ok, + // because the new NavigationEntry will have its max security style set + // within DidNavigate. + error->ContinueRequest(); + error->manager()->AllowCertForHost(error->ssl_info().cert, + error->request_url().host()); +} + +void SSLPolicy::OnOverridableCertError(const GURL& main_frame_url, + SSLManager::CertError* error) { + if (error->resource_type() != ResourceType::MAIN_FRAME) { + // A sub-resource has a certificate error. The user doesn't really + // have a context for making the right decision, so block the + // request hard, without an info bar to allow showing the insecure + // content. + error->DenyRequest(); + return; + } + // We need to ask the user to approve this certificate. + ShowBlockingPage(this, error); +} + +void SSLPolicy::OnFatalCertError(const GURL& main_frame_url, + SSLManager::CertError* error) { + if (error->resource_type() != ResourceType::MAIN_FRAME) { + error->DenyRequest(); + return; + } + error->CancelRequest(); + ShowErrorPage(this, error); + // No need to degrade our security indicators because we didn't continue. +} diff --git a/chrome/browser/ssl/ssl_policy.h b/chrome/browser/ssl/ssl_policy.h new file mode 100644 index 0000000..d317b63 --- /dev/null +++ b/chrome/browser/ssl/ssl_policy.h @@ -0,0 +1,69 @@ +// Copyright (c) 2006-2008 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. + +#ifndef CHROME_BROWSER_SSL_POLICY_H__ +#define CHROME_BROWSER_SSL_POLICY_H__ + +#include "chrome/browser/ssl/ssl_blocking_page.h" +#include "chrome/browser/ssl/ssl_manager.h" + +// The basic SSLPolicy. This class contains default implementations of all +// the SSLPolicy entry points. It is expected that subclasses will override +// most of these methods to implement policy specific to certain errors or +// situations. +class SSLPolicy : public SSLManager::Delegate, + public SSLBlockingPage::Delegate { + public: + // Factory method to get the default policy. + // + // SSLPolicy is not meant to be instantiated itself. Only subclasses should + // be instantiated. The default policy has more complex behavior than a + // direct instance of SSLPolicy. + static SSLPolicy* GetDefaultPolicy(); + + // SSLManager::Delegate methods. + virtual void OnCertError(const GURL& main_frame_url, + SSLManager::CertError* error); + virtual void OnMixedContent( + NavigationController* navigation_controller, + const GURL& main_frame_url, + SSLManager::MixedContentHandler* mixed_content_handler) { + // So far only the default policy is expected to receive mixed-content + // calls. + NOTREACHED(); + } + + virtual void OnRequestStarted(SSLManager* manager, + const GURL& url, + ResourceType::Type resource_type, + int ssl_cert_id, + int ssl_cert_status); + virtual SecurityStyle GetDefaultStyle(const GURL& url); + + // SSLBlockingPage::Delegate methods. + virtual SSLErrorInfo GetSSLErrorInfo(SSLManager::CertError* error); + virtual void OnDenyCertificate(SSLManager::CertError* error); + virtual void OnAllowCertificate(SSLManager::CertError* error); + + protected: + // Allow our subclasses to construct us. + SSLPolicy(); + + // Helper method for derived classes handling certificate errors that can be + // overridden by the user. + // Show a blocking page and let the user continue or cancel the request. + void OnOverridableCertError(const GURL& main_frame_url, + SSLManager::CertError* error); + + // Helper method for derived classes handling fatal certificate errors. + // Cancel the request and show an error page. + void OnFatalCertError(const GURL& main_frame_url, + SSLManager::CertError* error); + + private: + DISALLOW_EVIL_CONSTRUCTORS(SSLPolicy); +}; + +#endif // CHROME_BROWSER_SSL_POLICY_H__ + diff --git a/chrome/browser/ssl/ssl_uitest.cc b/chrome/browser/ssl/ssl_uitest.cc new file mode 100644 index 0000000..2a84744 --- /dev/null +++ b/chrome/browser/ssl/ssl_uitest.cc @@ -0,0 +1,932 @@ +// Copyright (c) 2006-2008 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 <Windows.h> +#include <Wincrypt.h> + +#include <string> + +#include "chrome/common/pref_names.h" +#include "chrome/test/automation/browser_proxy.h" +#include "chrome/test/automation/tab_proxy.h" +#include "chrome/test/ui/ui_test.h" +#include "net/base/ssl_test_util.h" +#include "net/url_request/url_request_unittest.h" + +namespace { + +const wchar_t kDocRoot[] = L"chrome/test/data"; + +class SSLUITest : public UITest { + protected: + SSLUITest() { + dom_automation_enabled_ = true; + EXPECT_TRUE(util_.CheckCATrusted()); + } + + TabProxy* GetActiveTabProxy() { + scoped_ptr<BrowserProxy> browser_proxy(automation()->GetBrowserWindow(0)); + EXPECT_TRUE(browser_proxy.get()); + return browser_proxy->GetActiveTab(); + } + + void NavigateTab(TabProxy* tab_proxy, const GURL& url) { + ASSERT_TRUE(tab_proxy->NavigateToURL(url)); + } + + void AppendTab(const GURL& url) { + scoped_ptr<BrowserProxy> browser_proxy(automation()->GetBrowserWindow(0)); + EXPECT_TRUE(browser_proxy.get()); + EXPECT_TRUE(browser_proxy->AppendTab(url)); + } + + HTTPTestServer* PlainServer() { + return HTTPTestServer::CreateServer(kDocRoot); + } + + HTTPSTestServer* GoodCertServer() { + return HTTPSTestServer::CreateServer(util_.kHostName, util_.kOKHTTPSPort, + kDocRoot, util_.GetOKCertPath().ToWStringHack()); + } + + HTTPSTestServer* BadCertServer() { + return HTTPSTestServer::CreateServer(util_.kHostName, util_.kBadHTTPSPort, + kDocRoot, util_.GetExpiredCertPath().ToWStringHack()); + } + + protected: + SSLTestUtil util_; + + DISALLOW_COPY_AND_ASSIGN(SSLUITest); +}; + +} // namespace + +// Visits a regular page over http. +TEST_F(SSLUITest, TestHTTP) { + scoped_refptr<HTTPTestServer> server = PlainServer(); + + scoped_ptr<TabProxy> tab(GetActiveTabProxy()); + NavigateTab(tab.get(), server->TestServerPageW(L"files/ssl/google.html")); + + NavigationEntry::PageType page_type; + EXPECT_TRUE(tab->GetPageType(&page_type)); + EXPECT_EQ(NavigationEntry::NORMAL_PAGE, page_type); + + SecurityStyle security_style; + int cert_status; + int mixed_content_state; + EXPECT_TRUE(tab->GetSecurityState(&security_style, &cert_status, + &mixed_content_state)); + EXPECT_EQ(SECURITY_STYLE_UNAUTHENTICATED, security_style); + EXPECT_EQ(0, cert_status); + EXPECT_EQ(NavigationEntry::SSLStatus::NORMAL_CONTENT, mixed_content_state); +} + +// Visits a page over http which includes broken https resources (status should +// be OK). +TEST_F(SSLUITest, TestHTTPWithBrokenHTTPSResource) { + scoped_refptr<HTTPTestServer> http_server = PlainServer(); + scoped_refptr<HTTPSTestServer> bad_https_server = BadCertServer(); + scoped_ptr<TabProxy> tab(GetActiveTabProxy()); + + NavigateTab(tab.get(), + http_server->TestServerPageW(L"files/ssl/page_with_unsafe_contents.html")); + + SecurityStyle security_style; + int cert_status; + int mixed_content_state; + EXPECT_TRUE(tab->GetSecurityState(&security_style, &cert_status, + &mixed_content_state)); + EXPECT_EQ(SECURITY_STYLE_UNAUTHENTICATED, security_style); + EXPECT_EQ(0, cert_status); + EXPECT_EQ(NavigationEntry::SSLStatus::NORMAL_CONTENT, mixed_content_state); +} + +// Visits a page over OK https: +TEST_F(SSLUITest, TestOKHTTPS) { + scoped_refptr<HTTPSTestServer> https_server = GoodCertServer(); + scoped_ptr<TabProxy> tab(GetActiveTabProxy()); + NavigateTab(tab.get(), + https_server->TestServerPageW(L"files/ssl/google.html")); + + NavigationEntry::PageType page_type; + EXPECT_TRUE(tab->GetPageType(&page_type)); + EXPECT_EQ(NavigationEntry::NORMAL_PAGE, page_type); + + SecurityStyle security_style; + int cert_status; + int mixed_content_state; + EXPECT_TRUE(tab->GetSecurityState(&security_style, &cert_status, + &mixed_content_state)); + EXPECT_EQ(SECURITY_STYLE_AUTHENTICATED, security_style); + EXPECT_EQ(0, cert_status & net::CERT_STATUS_ALL_ERRORS); + EXPECT_EQ(NavigationEntry::SSLStatus::NORMAL_CONTENT, mixed_content_state); +} + +// Visits a page with https error and proceed: +TEST_F(SSLUITest, TestHTTPSExpiredCertAndProceed) { + scoped_refptr<HTTPSTestServer> bad_https_server = BadCertServer(); + scoped_ptr<TabProxy> tab(GetActiveTabProxy()); + NavigateTab(tab.get(), + bad_https_server->TestServerPageW(L"files/ssl/google.html")); + + NavigationEntry::PageType page_type; + EXPECT_TRUE(tab->GetPageType(&page_type)); + EXPECT_EQ(NavigationEntry::INTERSTITIAL_PAGE, page_type); + + SecurityStyle security_style; + int cert_status; + int mixed_content_state; + EXPECT_TRUE(tab->GetSecurityState(&security_style, &cert_status, + &mixed_content_state)); + EXPECT_EQ(SECURITY_STYLE_AUTHENTICATION_BROKEN, security_style); + EXPECT_EQ(net::CERT_STATUS_DATE_INVALID, + cert_status & net::CERT_STATUS_ALL_ERRORS); + EXPECT_EQ(NavigationEntry::SSLStatus::NORMAL_CONTENT, mixed_content_state); + + EXPECT_TRUE(tab->TakeActionOnSSLBlockingPage(true)); + EXPECT_TRUE(tab->GetPageType(&page_type)); + EXPECT_EQ(NavigationEntry::NORMAL_PAGE, page_type); + EXPECT_TRUE(tab->GetSecurityState(&security_style, &cert_status, + &mixed_content_state)); + EXPECT_EQ(SECURITY_STYLE_AUTHENTICATION_BROKEN, security_style); + EXPECT_EQ(net::CERT_STATUS_DATE_INVALID, + cert_status & net::CERT_STATUS_ALL_ERRORS); + EXPECT_EQ(NavigationEntry::SSLStatus::NORMAL_CONTENT, mixed_content_state); +} + +// Visits a page with https error and don't proceed (and ensure we can still +// navigate at that point): +TEST_F(SSLUITest, TestHTTPSExpiredCertAndDontProceed) { + scoped_refptr<HTTPTestServer> http_server = PlainServer(); + scoped_refptr<HTTPSTestServer> good_https_server = GoodCertServer(); + scoped_refptr<HTTPSTestServer> bad_https_server = BadCertServer(); + scoped_ptr<TabProxy> tab(GetActiveTabProxy()); + + // First navigate to an OK page. + NavigateTab(tab.get(), + good_https_server->TestServerPageW(L"files/ssl/google.html")); + + GURL cross_site_url = + bad_https_server->TestServerPageW(L"files/ssl/google.html"); + // Change the host name from 127.0.0.1 to localhost so it triggers a + // cross-site navigation so we can test http://crbug.com/5800 is gone. + ASSERT_EQ("127.0.0.1", cross_site_url.host()); + GURL::Replacements replacements; + std::string new_host("localhost"); + replacements.SetHostStr(new_host); + cross_site_url = cross_site_url.ReplaceComponents(replacements); + + // Now go to a bad HTTPS page. + NavigateTab(tab.get(), cross_site_url); + + // An interstitial should be showing. + NavigationEntry::PageType page_type; + EXPECT_TRUE(tab->GetPageType(&page_type)); + EXPECT_EQ(NavigationEntry::INTERSTITIAL_PAGE, page_type); + + SecurityStyle security_style; + int cert_status; + int mixed_content_state; + EXPECT_TRUE(tab->GetSecurityState(&security_style, &cert_status, + &mixed_content_state)); + EXPECT_EQ(SECURITY_STYLE_AUTHENTICATION_BROKEN, security_style); + EXPECT_EQ(NavigationEntry::SSLStatus::NORMAL_CONTENT, mixed_content_state); + + // Simulate user clicking "Take me back". + EXPECT_TRUE(tab->TakeActionOnSSLBlockingPage(false)); + + // We should be back to the original good page. + EXPECT_TRUE(tab->GetPageType(&page_type)); + EXPECT_EQ(NavigationEntry::NORMAL_PAGE, page_type); + EXPECT_TRUE(tab->GetSecurityState(&security_style, &cert_status, + &mixed_content_state)); + EXPECT_EQ(SECURITY_STYLE_AUTHENTICATED, security_style); + EXPECT_EQ(0, cert_status & net::CERT_STATUS_ALL_ERRORS); + EXPECT_EQ(NavigationEntry::SSLStatus::NORMAL_CONTENT, mixed_content_state); + + // Try to navigate to a new page. (to make sure bug 5800 is fixed). + NavigateTab(tab.get(), + http_server->TestServerPageW(L"files/ssl/google.html")); + EXPECT_TRUE(tab->GetSecurityState(&security_style, &cert_status, + &mixed_content_state)); + EXPECT_EQ(SECURITY_STYLE_UNAUTHENTICATED, security_style); +} + +// +// Mixed contents +// + +// Visits a page with mixed content. +TEST_F(SSLUITest, TestMixedContents) { + scoped_refptr<HTTPSTestServer> https_server = GoodCertServer(); + scoped_refptr<HTTPTestServer> http_server = PlainServer(); + + // Load a page with mixed-content, the default behavior is to show the mixed + // content. + scoped_ptr<TabProxy> tab(GetActiveTabProxy()); + NavigateTab(tab.get(), + https_server->TestServerPageW(L"files/ssl/page_with_mixed_contents.html")); + NavigationEntry::PageType page_type; + EXPECT_TRUE(tab->GetPageType(&page_type)); + EXPECT_EQ(NavigationEntry::NORMAL_PAGE, page_type); + + SecurityStyle security_style; + int cert_status; + int mixed_content_state; + EXPECT_TRUE(tab->GetSecurityState(&security_style, &cert_status, + &mixed_content_state)); + EXPECT_EQ(SECURITY_STYLE_AUTHENTICATED, security_style); + EXPECT_EQ(0, + cert_status & net::CERT_STATUS_ALL_ERRORS); // No errors expected. + EXPECT_EQ(NavigationEntry::SSLStatus::MIXED_CONTENT, mixed_content_state); + + // Now select the block mixed-content pref and reload the page. + scoped_ptr<BrowserProxy> browser_proxy(automation()->GetBrowserWindow(0)); + EXPECT_TRUE(browser_proxy.get()); + EXPECT_TRUE(browser_proxy->SetIntPreference(prefs::kMixedContentFiltering, + FilterPolicy::FILTER_ALL)); + EXPECT_TRUE(tab->Reload()); + + // The image should be filtered. + int img_width; + EXPECT_TRUE(tab->ExecuteAndExtractInt(L"", + L"window.domAutomationController.send(ImageWidth());", + &img_width)); + // In order to check that the image was not loaded, we check its width. + // The actual image (Google logo) is 114 pixels wide, we assume the broken + // image is less than 100. + EXPECT_GT(100, img_width); + + // The state should be OK since we are not showing the resource. + EXPECT_TRUE(tab->GetSecurityState(&security_style, &cert_status, + &mixed_content_state)); + EXPECT_EQ(SECURITY_STYLE_AUTHENTICATED, security_style); + EXPECT_EQ(0, cert_status & net::CERT_STATUS_ALL_ERRORS); + EXPECT_EQ(NavigationEntry::SSLStatus::NORMAL_CONTENT, mixed_content_state); + + // There should be one info-bar to show the mixed-content. + int info_bar_count = 0; + EXPECT_TRUE(tab->GetSSLInfoBarCount(&info_bar_count)); + EXPECT_EQ(1, info_bar_count); + + // Activate the link on the info-bar to show the mixed-content. + EXPECT_TRUE(tab->ClickSSLInfoBarLink(0, true)); + + // The image should show now. + EXPECT_TRUE(tab->ExecuteAndExtractInt(L"", + L"window.domAutomationController.send(ImageWidth());", + &img_width)); + EXPECT_LT(100, img_width); + + // And our status should be mixed-content. + EXPECT_TRUE(tab->GetSecurityState(&security_style, &cert_status, + &mixed_content_state)); + EXPECT_EQ(SECURITY_STYLE_AUTHENTICATED, security_style); + EXPECT_EQ(0, cert_status & net::CERT_STATUS_ALL_ERRORS); + EXPECT_EQ(NavigationEntry::SSLStatus::MIXED_CONTENT, mixed_content_state); +} + +// Visits a page with unsafe content and make sure that: +// - frames content is replaced with warning +// - images and scripts are filtered out entirely +TEST_F(SSLUITest, TestUnsafeContents) { + scoped_refptr<HTTPSTestServer> good_https_server = GoodCertServer(); + scoped_refptr<HTTPSTestServer> bad_https_server = BadCertServer(); + + scoped_ptr<TabProxy> tab(GetActiveTabProxy()); + NavigateTab(tab.get(), + good_https_server->TestServerPageW( + L"files/ssl/page_with_unsafe_contents.html")); + NavigationEntry::PageType page_type; + EXPECT_TRUE(tab->GetPageType(&page_type)); + EXPECT_EQ(NavigationEntry::NORMAL_PAGE, page_type); + + SecurityStyle security_style; + int cert_status; + int mixed_content_state; + // When the bad content is filtered, the state is expected to be + // authenticated. + EXPECT_TRUE(tab->GetSecurityState(&security_style, &cert_status, + &mixed_content_state)); + EXPECT_EQ(SECURITY_STYLE_AUTHENTICATED, security_style); + EXPECT_EQ(0, + cert_status & net::CERT_STATUS_ALL_ERRORS); // No errors expected. + EXPECT_EQ(NavigationEntry::SSLStatus::NORMAL_CONTENT, mixed_content_state); + + // Because of cross-frame scripting restrictions, we cannot access the iframe + // content. So to know if the frame was loaded, we just check if a popup was + // opened (the iframe content opens one). + // Note: because of bug 1115868, no constrained window is opened right now. + // Once the bug is fixed, this will do the real check. + int constrained_window_count = 0; + EXPECT_TRUE(tab->GetConstrainedWindowCount(&constrained_window_count)); + EXPECT_EQ(0, constrained_window_count); + + int img_width; + EXPECT_TRUE(tab->ExecuteAndExtractInt(L"", + L"window.domAutomationController.send(ImageWidth());", + &img_width)); + // In order to check that the image was not loaded, we check its width. + // The actual image (Google logo) is 114 pixels wide, we assume the broken + // image is less than 100. + EXPECT_GT(100, img_width); + + bool js_result = false; + EXPECT_TRUE(tab->ExecuteAndExtractBool(L"", + L"window.domAutomationController.send(IsFooSet());", + &js_result)); + EXPECT_FALSE(js_result); +} + +// Visits a page with mixed content loaded by JS (after the initial page load). +TEST_F(SSLUITest, TestMixedContentsLoadedFromJS) { + scoped_refptr<HTTPSTestServer> https_server = GoodCertServer(); + scoped_refptr<HTTPTestServer> http_server = PlainServer(); + + scoped_ptr<TabProxy> tab(GetActiveTabProxy()); + NavigateTab(tab.get(), https_server->TestServerPageW( + L"files/ssl/page_with_dynamic_mixed_contents.html")); + NavigationEntry::PageType page_type; + EXPECT_TRUE(tab->GetPageType(&page_type)); + EXPECT_EQ(NavigationEntry::NORMAL_PAGE, page_type); + SecurityStyle security_style; + int cert_status; + int mixed_content_state; + EXPECT_TRUE(tab->GetSecurityState(&security_style, &cert_status, + &mixed_content_state)); + EXPECT_EQ(SECURITY_STYLE_AUTHENTICATED, security_style); + EXPECT_EQ(0, + cert_status & net::CERT_STATUS_ALL_ERRORS); // No errors expected. + EXPECT_EQ(NavigationEntry::SSLStatus::NORMAL_CONTENT, mixed_content_state); + + // Load the insecure image. + bool js_result = false; + EXPECT_TRUE(tab->ExecuteAndExtractBool(L"", + L"loadBadImage();", + &js_result)); + EXPECT_TRUE(js_result); + + // We should now have mixed-contents. + EXPECT_TRUE(tab->GetSecurityState(&security_style, &cert_status, + &mixed_content_state)); + EXPECT_EQ(SECURITY_STYLE_AUTHENTICATED, security_style); + EXPECT_EQ(0, + cert_status & net::CERT_STATUS_ALL_ERRORS); // No errors expected. + EXPECT_EQ(NavigationEntry::SSLStatus::MIXED_CONTENT, mixed_content_state); +} + +// Visits a page with an image over http. Visits another page over https +// referencing that same image over http (hoping it is coming from the webcore +// memory cache). +TEST_F(SSLUITest, TestCachedMixedContents) { + scoped_refptr<HTTPSTestServer> https_server = GoodCertServer(); + scoped_refptr<HTTPTestServer> http_server = PlainServer(); + + scoped_ptr<TabProxy> tab(GetActiveTabProxy()); + NavigateTab(tab.get(), http_server->TestServerPageW( + L"files/ssl/page_with_mixed_contents.html")); + + NavigationEntry::PageType page_type; + EXPECT_TRUE(tab->GetPageType(&page_type)); + EXPECT_EQ(NavigationEntry::NORMAL_PAGE, page_type); + SecurityStyle security_style; + int cert_status; + int mixed_content_state; + EXPECT_TRUE(tab->GetSecurityState(&security_style, &cert_status, + &mixed_content_state)); + EXPECT_EQ(SECURITY_STYLE_UNAUTHENTICATED, security_style); + EXPECT_EQ(0, + cert_status & net::CERT_STATUS_ALL_ERRORS); // No errors expected. + EXPECT_EQ(NavigationEntry::SSLStatus::NORMAL_CONTENT, mixed_content_state); + + // Load again but over SSL. It should have mixed-contents (even though the + // image comes from the WebCore memory cache). + NavigateTab(tab.get(), https_server->TestServerPageW( + L"files/ssl/page_with_mixed_contents.html")); + + EXPECT_TRUE(tab->GetSecurityState(&security_style, &cert_status, + &mixed_content_state)); + EXPECT_TRUE(tab->GetPageType(&page_type)); + EXPECT_EQ(NavigationEntry::NORMAL_PAGE, page_type); + EXPECT_EQ(SECURITY_STYLE_AUTHENTICATED, security_style); + EXPECT_EQ(0, + cert_status & net::CERT_STATUS_ALL_ERRORS); // No errors expected. + EXPECT_EQ(NavigationEntry::SSLStatus::MIXED_CONTENT, mixed_content_state); +} + +// This test ensures the CN invalid status does not 'stick' to a certificate +// (see bug #1044942) and that it depends on the host-name. +// TODO(jcampan): this test is flacky and fails sometimes (bug #1065095) +TEST_F(SSLUITest, DISABLED_TestCNInvalidStickiness) { + const std::string kLocalHost = "localhost"; + scoped_refptr<HTTPSTestServer> https_server = + HTTPSTestServer::CreateServer(kLocalHost, util_.kOKHTTPSPort, + kDocRoot, util_.GetOKCertPath().ToWStringHack()); + ASSERT_TRUE(NULL != https_server.get()); + + // First we hit the server with hostname, this generates an invalid policy + // error. + scoped_ptr<TabProxy> tab(GetActiveTabProxy()); + NavigateTab(tab.get(), https_server->TestServerPageW( + L"files/ssl/google.html")); + + // We get an interstitial page as a result. + NavigationEntry::PageType page_type; + EXPECT_TRUE(tab->GetPageType(&page_type)); + EXPECT_EQ(NavigationEntry::INTERSTITIAL_PAGE, page_type); + + SecurityStyle security_style; + int cert_status; + int mixed_content_state; + EXPECT_TRUE(tab->GetSecurityState(&security_style, &cert_status, + &mixed_content_state)); + EXPECT_EQ(SECURITY_STYLE_AUTHENTICATION_BROKEN, security_style); + EXPECT_EQ(net::CERT_STATUS_COMMON_NAME_INVALID, + cert_status & net::CERT_STATUS_ALL_ERRORS); + EXPECT_EQ(NavigationEntry::SSLStatus::NORMAL_CONTENT, mixed_content_state); + + // We proceed through the interstitial page. + EXPECT_TRUE(tab->TakeActionOnSSLBlockingPage(true)); + EXPECT_TRUE(tab->GetPageType(&page_type)); + EXPECT_EQ(NavigationEntry::NORMAL_PAGE, page_type); + + // Now we try again with the right host name this time. + + // Let's change the host-name in the url. + GURL url = https_server->TestServerPageW(L"files/ssl/google.html"); + std::string::size_type hostname_index = url.spec().find(kLocalHost); + ASSERT_TRUE(hostname_index != std::string::npos); // Test sanity check. + std::string new_url; + new_url.append(url.spec().substr(0, hostname_index)); + new_url.append(util_.kHostName); + new_url.append(url.spec().substr(hostname_index + kLocalHost.size())); + + NavigateTab(tab.get(), GURL(new_url)); + + // Security state should be OK. + EXPECT_TRUE(tab->GetSecurityState(&security_style, &cert_status, + &mixed_content_state)); + EXPECT_EQ(SECURITY_STYLE_AUTHENTICATED, security_style); + EXPECT_EQ(0, + cert_status & net::CERT_STATUS_ALL_ERRORS); // No errors expected. + EXPECT_EQ(NavigationEntry::SSLStatus::NORMAL_CONTENT, mixed_content_state); + + // Now try again the broken one to make sure it is still broken. + NavigateTab(tab.get(), https_server->TestServerPageW( + L"files/ssl/google.html")); + + EXPECT_TRUE(tab->GetPageType(&page_type)); + // Since we OKed the interstitial last time, we get right to the page. + EXPECT_EQ(NavigationEntry::NORMAL_PAGE, page_type); + EXPECT_TRUE(tab->GetSecurityState(&security_style, &cert_status, + &mixed_content_state)); + EXPECT_EQ(SECURITY_STYLE_AUTHENTICATION_BROKEN, security_style); + EXPECT_EQ(net::CERT_STATUS_COMMON_NAME_INVALID, + cert_status & net::CERT_STATUS_ALL_ERRORS); + EXPECT_EQ(NavigationEntry::SSLStatus::NORMAL_CONTENT, mixed_content_state); +} + +// Test that navigating to a #ref does not change a bad security state. +TEST_F(SSLUITest, TestRefNavigation) { + scoped_refptr<HTTPSTestServer> bad_https_server = BadCertServer(); + scoped_ptr<TabProxy> tab(GetActiveTabProxy()); + NavigateTab(tab.get(), + bad_https_server->TestServerPageW(L"files/ssl/page_with_refs.html")); + + NavigationEntry::PageType page_type; + EXPECT_TRUE(tab->GetPageType(&page_type)); + EXPECT_EQ(page_type, NavigationEntry::INTERSTITIAL_PAGE); + + SecurityStyle security_style; + int cert_status; + int mixed_content_state; + EXPECT_TRUE(tab->GetSecurityState(&security_style, &cert_status, + &mixed_content_state)); + EXPECT_EQ(SECURITY_STYLE_AUTHENTICATION_BROKEN, security_style); + EXPECT_EQ(net::CERT_STATUS_DATE_INVALID, cert_status); + EXPECT_EQ(NavigationEntry::SSLStatus::NORMAL_CONTENT, mixed_content_state); + + EXPECT_TRUE(tab->TakeActionOnSSLBlockingPage(true)); + EXPECT_TRUE(tab->GetPageType(&page_type)); + EXPECT_EQ(NavigationEntry::NORMAL_PAGE, page_type); + EXPECT_TRUE(tab->GetSecurityState(&security_style, &cert_status, + &mixed_content_state)); + EXPECT_EQ(SECURITY_STYLE_AUTHENTICATION_BROKEN, security_style); + EXPECT_EQ(net::CERT_STATUS_DATE_INVALID, + cert_status & net::CERT_STATUS_ALL_ERRORS); + EXPECT_EQ(NavigationEntry::SSLStatus::NORMAL_CONTENT, mixed_content_state); + + // Now navigate to a ref in the page. + NavigateTab(tab.get(), + bad_https_server->TestServerPageW(L"files/ssl/page_with_refs.html#jp")); + EXPECT_TRUE(tab->GetSecurityState(&security_style, &cert_status, + &mixed_content_state)); + EXPECT_EQ(SECURITY_STYLE_AUTHENTICATION_BROKEN, security_style); + EXPECT_EQ(net::CERT_STATUS_DATE_INVALID, + cert_status & net::CERT_STATUS_ALL_ERRORS); + EXPECT_EQ(NavigationEntry::SSLStatus::NORMAL_CONTENT, mixed_content_state); +} + +// Tests that closing a page that has a unsafe pop-up does not crash the browser +// (bug #1966). +// Disabled because flaky (bug #2136). +TEST_F(SSLUITest, DISABLED_TestCloseTabWithUnsafePopup) { + scoped_refptr<HTTPTestServer> http_server = PlainServer(); + scoped_refptr<HTTPSTestServer> bad_https_server = BadCertServer(); + + scoped_ptr<TabProxy> tab(GetActiveTabProxy()); + NavigateTab(tab.get(), + http_server->TestServerPageW( + L"files/ssl/page_with_unsafe_popup.html")); + + int popup_count = 0; + EXPECT_TRUE(tab->GetConstrainedWindowCount(&popup_count)); + EXPECT_EQ(1, popup_count); + + // Let's add another tab to make sure the browser does not exit when we close + // the first tab. + scoped_ptr<BrowserProxy> browser_proxy(automation()->GetBrowserWindow(0)); + EXPECT_TRUE(browser_proxy.get()); + browser_proxy->AppendTab( + http_server->TestServerPageW(L"files/ssl/google.html")); + + // Close the first tab. + tab->Close(); +} + +// Visit a page over bad https that is a redirect to a page with good https. +TEST_F(SSLUITest, TestRedirectBadToGoodHTTPS) { + scoped_refptr<HTTPSTestServer> good_https_server = GoodCertServer(); + scoped_refptr<HTTPSTestServer> bad_https_server = BadCertServer(); + + scoped_ptr<TabProxy> tab(GetActiveTabProxy()); + GURL url1 = bad_https_server->TestServerPageW(L"server-redirect?"); + GURL url2 = good_https_server->TestServerPageW(L"files/ssl/google.html"); + NavigateTab(tab.get(), GURL(url1.spec() + url2.spec())); + + NavigationEntry::PageType page_type; + EXPECT_TRUE(tab->GetPageType(&page_type)); + EXPECT_EQ(page_type, NavigationEntry::INTERSTITIAL_PAGE); + + SecurityStyle security_style; + int cert_status; + int mixed_content_state; + EXPECT_TRUE(tab->GetSecurityState(&security_style, &cert_status, + &mixed_content_state)); + EXPECT_EQ(SECURITY_STYLE_AUTHENTICATION_BROKEN, security_style); + EXPECT_EQ(net::CERT_STATUS_DATE_INVALID, cert_status); + EXPECT_EQ(NavigationEntry::SSLStatus::NORMAL_CONTENT, mixed_content_state); + + EXPECT_TRUE(tab->TakeActionOnSSLBlockingPage(true)); + // We have been redirected to the good page. + EXPECT_TRUE(tab->GetSecurityState(&security_style, &cert_status, + &mixed_content_state)); + EXPECT_EQ(SECURITY_STYLE_AUTHENTICATED, security_style); + EXPECT_EQ(0, + cert_status & net::CERT_STATUS_ALL_ERRORS); // No errors expected. + EXPECT_EQ(NavigationEntry::SSLStatus::NORMAL_CONTENT, mixed_content_state); +} + +// Visit a page over good https that is a redirect to a page with bad https. +TEST_F(SSLUITest, TestRedirectGoodToBadHTTPS) { + scoped_refptr<HTTPSTestServer> good_https_server = GoodCertServer(); + scoped_refptr<HTTPSTestServer> bad_https_server = BadCertServer(); + + scoped_ptr<TabProxy> tab(GetActiveTabProxy()); + GURL url1 = good_https_server->TestServerPageW(L"server-redirect?"); + GURL url2 = bad_https_server->TestServerPageW(L"files/ssl/google.html"); + NavigateTab(tab.get(), GURL(url1.spec() + url2.spec())); + + NavigationEntry::PageType page_type; + EXPECT_TRUE(tab->GetPageType(&page_type)); + EXPECT_EQ(page_type, NavigationEntry::INTERSTITIAL_PAGE); + + EXPECT_TRUE(tab->TakeActionOnSSLBlockingPage(true)); + + SecurityStyle security_style; + int cert_status; + int mixed_content_state; + EXPECT_TRUE(tab->GetSecurityState(&security_style, &cert_status, + &mixed_content_state)); + EXPECT_EQ(SECURITY_STYLE_AUTHENTICATION_BROKEN, security_style); + EXPECT_EQ(net::CERT_STATUS_DATE_INVALID, + cert_status & net::CERT_STATUS_ALL_ERRORS); + EXPECT_EQ(NavigationEntry::SSLStatus::NORMAL_CONTENT, mixed_content_state); +} + +// Visit a page over http that is a redirect to a page with https (good and +// bad). +TEST_F(SSLUITest, TestRedirectHTTPToHTTPS) { + scoped_refptr<HTTPTestServer> http_server = PlainServer(); + scoped_refptr<HTTPSTestServer> good_https_server = GoodCertServer(); + scoped_refptr<HTTPSTestServer> bad_https_server = BadCertServer(); + + // HTTP redirects to good HTTPS. + scoped_ptr<TabProxy> tab(GetActiveTabProxy()); + GURL http_url = http_server->TestServerPageW(L"server-redirect?"); + GURL good_https_url = + good_https_server->TestServerPageW(L"files/ssl/google.html"); + NavigateTab(tab.get(), GURL(http_url.spec() + good_https_url.spec())); + + SecurityStyle security_style; + int cert_status; + int mixed_content_state; + EXPECT_TRUE(tab->GetSecurityState(&security_style, &cert_status, + &mixed_content_state)); + EXPECT_EQ(SECURITY_STYLE_AUTHENTICATED, security_style); + EXPECT_EQ(0, cert_status & net::CERT_STATUS_ALL_ERRORS); + EXPECT_EQ(NavigationEntry::SSLStatus::NORMAL_CONTENT, mixed_content_state); + + // HTTP redirects to bad HTTPS. + GURL bad_https_url = + bad_https_server->TestServerPageW(L"files/ssl/google.html"); + NavigateTab(tab.get(), GURL(http_url.spec() + bad_https_url.spec())); + + NavigationEntry::PageType page_type; + EXPECT_TRUE(tab->GetPageType(&page_type)); + EXPECT_EQ(page_type, NavigationEntry::INTERSTITIAL_PAGE); + + // Continue on the interstitial. + EXPECT_TRUE(tab->TakeActionOnSSLBlockingPage(true)); + + EXPECT_TRUE(tab->GetSecurityState(&security_style, &cert_status, + &mixed_content_state)); + EXPECT_EQ(SECURITY_STYLE_AUTHENTICATION_BROKEN, security_style); + EXPECT_EQ(net::CERT_STATUS_DATE_INVALID, + cert_status & net::CERT_STATUS_ALL_ERRORS); + EXPECT_EQ(NavigationEntry::SSLStatus::NORMAL_CONTENT, mixed_content_state); +} + +// Visit a page over https that is a redirect to a page with http (to make sure +// we don't keep the secure state). +TEST_F(SSLUITest, TestRedirectHTTPSToHTTP) { + scoped_refptr<HTTPTestServer> http_server = PlainServer(); + scoped_refptr<HTTPSTestServer> https_server = GoodCertServer(); + + scoped_ptr<TabProxy> tab(GetActiveTabProxy()); + GURL https_url = https_server->TestServerPageW(L"server-redirect?"); + GURL http_url = http_server->TestServerPageW(L"files/ssl/google.html"); + NavigateTab(tab.get(), GURL(https_url.spec() + http_url.spec())); + + SecurityStyle security_style; + int cert_status; + int mixed_content_state; + EXPECT_TRUE(tab->GetSecurityState(&security_style, &cert_status, + &mixed_content_state)); + EXPECT_EQ(SECURITY_STYLE_UNAUTHENTICATED, security_style); + EXPECT_EQ(0, cert_status & net::CERT_STATUS_ALL_ERRORS); + EXPECT_EQ(NavigationEntry::SSLStatus::NORMAL_CONTENT, mixed_content_state); +} + +// Visits a page to which we could not connect (bad port) over http and https +// and make sure the security style is correct. +TEST_F(SSLUITest, TestConnectToBadPort) { + scoped_ptr<TabProxy> tab(GetActiveTabProxy()); + + GURL http_url("http://localhost:17"); + NavigateTab(tab.get(), http_url); + + SecurityStyle security_style; + int cert_status; + int mixed_content_state; + EXPECT_TRUE(tab->GetSecurityState(&security_style, &cert_status, + &mixed_content_state)); + EXPECT_EQ(SECURITY_STYLE_UNAUTHENTICATED, security_style); + EXPECT_EQ(0, cert_status & net::CERT_STATUS_ALL_ERRORS); + EXPECT_EQ(NavigationEntry::SSLStatus::NORMAL_CONTENT, mixed_content_state); + + // Same thing over HTTPS. + GURL https_url("https://localhost:17"); + NavigateTab(tab.get(), https_url); + + EXPECT_TRUE(tab->GetSecurityState(&security_style, &cert_status, + &mixed_content_state)); + EXPECT_EQ(SECURITY_STYLE_UNAUTHENTICATED, security_style); + EXPECT_EQ(0, cert_status & net::CERT_STATUS_ALL_ERRORS); + EXPECT_EQ(NavigationEntry::SSLStatus::NORMAL_CONTENT, mixed_content_state); +} + +// +// Frame navigation +// + +// From a good HTTPS top frame: +// - navigate to an OK HTTPS frame +// - navigate to a bad HTTPS (expect unsafe content and filtered frame), then +// back +// - navigate to HTTP (expect mixed content), then back +TEST_F(SSLUITest, TestGoodFrameNavigation) { + scoped_refptr<HTTPTestServer> http_server = PlainServer(); + scoped_refptr<HTTPSTestServer> good_https_server = GoodCertServer(); + scoped_refptr<HTTPSTestServer> bad_https_server = BadCertServer(); + + scoped_ptr<TabProxy> tab(GetActiveTabProxy()); + NavigateTab(tab.get(), + good_https_server->TestServerPageW(L"files/ssl/top_frame.html")); + + SecurityStyle security_style; + int cert_status; + int mixed_content_state; + EXPECT_TRUE(tab->GetSecurityState(&security_style, &cert_status, + &mixed_content_state)); + EXPECT_EQ(SECURITY_STYLE_AUTHENTICATED, security_style); + EXPECT_EQ(0, cert_status & net::CERT_STATUS_ALL_ERRORS); + EXPECT_EQ(NavigationEntry::SSLStatus::NORMAL_CONTENT, mixed_content_state); + + bool success = false; + // Now navigate inside the frame. + int64 last_nav_time = 0; + EXPECT_TRUE(tab->GetLastNavigationTime(&last_nav_time)); + EXPECT_TRUE(tab->ExecuteAndExtractBool(L"", + L"window.domAutomationController.send(clickLink('goodHTTPSLink'));", + &success)); + EXPECT_TRUE(success); + EXPECT_TRUE(tab->WaitForNavigation(last_nav_time)); + + // We should still be fine. + EXPECT_TRUE(tab->GetSecurityState(&security_style, &cert_status, + &mixed_content_state)); + EXPECT_EQ(SECURITY_STYLE_AUTHENTICATED, security_style); + EXPECT_EQ(0, cert_status & net::CERT_STATUS_ALL_ERRORS); + EXPECT_EQ(NavigationEntry::SSLStatus::NORMAL_CONTENT, mixed_content_state); + + // Now let's hit a bad page. + EXPECT_TRUE(tab->GetLastNavigationTime(&last_nav_time)); + EXPECT_TRUE(tab->ExecuteAndExtractBool(L"", + L"window.domAutomationController.send(clickLink('badHTTPSLink'));", + &success)); + EXPECT_TRUE(success); + EXPECT_TRUE(tab->WaitForNavigation(last_nav_time)); + + // The security style should still be secure. + EXPECT_TRUE(tab->GetSecurityState(&security_style, &cert_status, + &mixed_content_state)); + EXPECT_EQ(SECURITY_STYLE_AUTHENTICATED, security_style); + EXPECT_EQ(0, cert_status & net::CERT_STATUS_ALL_ERRORS); + EXPECT_EQ(NavigationEntry::SSLStatus::NORMAL_CONTENT, mixed_content_state); + + // And the frame should be blocked. + bool is_content_evil = true; + std::wstring content_frame_xpath(L"html/frameset/frame[2]"); + std::wstring is_frame_evil_js( + L"window.domAutomationController" + L".send(document.getElementById('evilDiv') != null);"); + EXPECT_TRUE(tab->ExecuteAndExtractBool(content_frame_xpath, + is_frame_evil_js, + &is_content_evil)); + EXPECT_FALSE(is_content_evil); + + // Now go back, our state should return to OK. + EXPECT_TRUE(tab->GoBack()); + EXPECT_TRUE(tab->GetSecurityState(&security_style, &cert_status, + &mixed_content_state)); + EXPECT_EQ(SECURITY_STYLE_AUTHENTICATED, security_style); + EXPECT_EQ(0, cert_status & net::CERT_STATUS_ALL_ERRORS); + EXPECT_EQ(NavigationEntry::SSLStatus::NORMAL_CONTENT, mixed_content_state); + + // Navigate to a page served over HTTP. + EXPECT_TRUE(tab->GetLastNavigationTime(&last_nav_time)); + EXPECT_TRUE(tab->ExecuteAndExtractBool(L"", + L"window.domAutomationController.send(clickLink('HTTPLink'));", + &success)); + EXPECT_TRUE(success); + EXPECT_TRUE(tab->WaitForNavigation(last_nav_time)); + + // Our state should be mixed-content. + // Status should be "contains bad contents". + EXPECT_TRUE(tab->GetSecurityState(&security_style, &cert_status, + &mixed_content_state)); + EXPECT_EQ(SECURITY_STYLE_AUTHENTICATED, security_style); + EXPECT_EQ(0, cert_status & net::CERT_STATUS_ALL_ERRORS); + EXPECT_EQ(NavigationEntry::SSLStatus::MIXED_CONTENT, mixed_content_state); + + // Go back, our state should be back to OK. + EXPECT_TRUE(tab->GoBack()); + EXPECT_TRUE(tab->GetSecurityState(&security_style, &cert_status, + &mixed_content_state)); + EXPECT_EQ(SECURITY_STYLE_AUTHENTICATED, security_style); + EXPECT_EQ(0, cert_status & net::CERT_STATUS_ALL_ERRORS); + EXPECT_EQ(NavigationEntry::SSLStatus::NORMAL_CONTENT, mixed_content_state); +} + +// From a bad HTTPS top frame: +// - navigate to an OK HTTPS frame (expected to be still authentication broken). +TEST_F(SSLUITest, TestBadFrameNavigation) { + scoped_refptr<HTTPSTestServer> good_https_server = GoodCertServer(); + scoped_refptr<HTTPSTestServer> bad_https_server = BadCertServer(); + + scoped_ptr<TabProxy> tab(GetActiveTabProxy()); + NavigateTab(tab.get(), + bad_https_server->TestServerPageW(L"files/ssl/top_frame.html")); + + SecurityStyle security_style; + int cert_status; + int mixed_content_state; + EXPECT_TRUE(tab->GetSecurityState(&security_style, &cert_status, + &mixed_content_state)); + EXPECT_EQ(SECURITY_STYLE_AUTHENTICATION_BROKEN, security_style); + EXPECT_EQ(net::CERT_STATUS_DATE_INVALID, + cert_status & net::CERT_STATUS_ALL_ERRORS); + EXPECT_EQ(NavigationEntry::SSLStatus::NORMAL_CONTENT, mixed_content_state); + + // Continue on the interstitial. + EXPECT_TRUE(tab->TakeActionOnSSLBlockingPage(true)); + + // Navigate to a good frame. + bool success = false; + int64 last_nav_time = 0; + EXPECT_TRUE(tab->GetLastNavigationTime(&last_nav_time)); + EXPECT_TRUE(tab->ExecuteAndExtractBool(L"", + L"window.domAutomationController.send(clickLink('goodHTTPSLink'));", + &success)); + EXPECT_TRUE(success); + EXPECT_TRUE(tab->WaitForNavigation(last_nav_time)); + + // We should still be authentication broken. + EXPECT_TRUE(tab->GetSecurityState(&security_style, &cert_status, + &mixed_content_state)); + EXPECT_EQ(SECURITY_STYLE_AUTHENTICATION_BROKEN, security_style); + EXPECT_EQ(net::CERT_STATUS_DATE_INVALID, + cert_status & net::CERT_STATUS_ALL_ERRORS); + EXPECT_EQ(NavigationEntry::SSLStatus::NORMAL_CONTENT, mixed_content_state); +} + +// From an HTTP top frame, navigate to good and bad HTTPS (security state should +// stay unauthenticated). +TEST_F(SSLUITest, TestUnauthenticatedFrameNavigation) { + scoped_refptr<HTTPTestServer> http_server = PlainServer(); + scoped_refptr<HTTPSTestServer> good_https_server = GoodCertServer(); + scoped_refptr<HTTPSTestServer> bad_https_server = BadCertServer(); + + scoped_ptr<TabProxy> tab(GetActiveTabProxy()); + NavigateTab(tab.get(), + http_server->TestServerPageW(L"files/ssl/top_frame.html")); + + SecurityStyle security_style; + int cert_status; + int mixed_content_state; + EXPECT_TRUE(tab->GetSecurityState(&security_style, &cert_status, + &mixed_content_state)); + EXPECT_EQ(SECURITY_STYLE_UNAUTHENTICATED, security_style); + EXPECT_EQ(0, cert_status & net::CERT_STATUS_ALL_ERRORS); + EXPECT_EQ(NavigationEntry::SSLStatus::NORMAL_CONTENT, mixed_content_state); + + // Now navigate inside the frame to a secure HTTPS frame. + bool success = false; + int64 last_nav_time = 0; + EXPECT_TRUE(tab->GetLastNavigationTime(&last_nav_time)); + EXPECT_TRUE(tab->ExecuteAndExtractBool(L"", + L"window.domAutomationController.send(clickLink('goodHTTPSLink'));", + &success)); + EXPECT_TRUE(success); + EXPECT_TRUE(tab->WaitForNavigation(last_nav_time)); + + // We should still be unauthenticated. + EXPECT_TRUE(tab->GetSecurityState(&security_style, &cert_status, + &mixed_content_state)); + EXPECT_EQ(SECURITY_STYLE_UNAUTHENTICATED, security_style); + EXPECT_EQ(0, cert_status & net::CERT_STATUS_ALL_ERRORS); + EXPECT_EQ(NavigationEntry::SSLStatus::NORMAL_CONTENT, mixed_content_state); + + // Now navigate to a bad HTTPS frame. + EXPECT_TRUE(tab->GetLastNavigationTime(&last_nav_time)); + EXPECT_TRUE(tab->ExecuteAndExtractBool(L"", + L"window.domAutomationController.send(clickLink('badHTTPSLink'));", + &success)); + EXPECT_TRUE(success); + EXPECT_TRUE(tab->WaitForNavigation(last_nav_time)); + + // State should not have changed. + EXPECT_TRUE(tab->GetSecurityState(&security_style, &cert_status, + &mixed_content_state)); + EXPECT_EQ(SECURITY_STYLE_UNAUTHENTICATED, security_style); + EXPECT_EQ(0, cert_status & net::CERT_STATUS_ALL_ERRORS); + EXPECT_EQ(NavigationEntry::SSLStatus::NORMAL_CONTENT, mixed_content_state); + + // And the frame should have been blocked (see bug #2316). + bool is_content_evil = true; + std::wstring content_frame_xpath(L"html/frameset/frame[2]"); + std::wstring is_frame_evil_js( + L"window.domAutomationController" + L".send(document.getElementById('evilDiv') != null);"); + EXPECT_TRUE(tab->ExecuteAndExtractBool(content_frame_xpath, + is_frame_evil_js, + &is_content_evil)); + EXPECT_FALSE(is_content_evil); +} + + +// TODO(jcampan): more tests to do below. + +// Visit a page over https that contains a frame with a redirect. + +// XMLHttpRequest mixed in synchronous mode. + +// XMLHttpRequest mixed in asynchronous mode. + +// XMLHttpRequest over bad ssl in synchronous mode. + +// XMLHttpRequest over OK ssl in synchronous mode. |