diff options
author | agl@chromium.org <agl@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-12-11 21:04:42 +0000 |
---|---|---|
committer | agl@chromium.org <agl@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-12-11 21:04:42 +0000 |
commit | 326e67907033c1e8db115327d59482b1ae6db3ec (patch) | |
tree | 2940d88e6de6ce6ba528c4671b6574b4ff6bd1bd | |
parent | 5973945e4c3d2baf2b92d11be55c1692a09b12e3 (diff) | |
download | chromium_src-326e67907033c1e8db115327d59482b1ae6db3ec.zip chromium_src-326e67907033c1e8db115327d59482b1ae6db3ec.tar.gz chromium_src-326e67907033c1e8db115327d59482b1ae6db3ec.tar.bz2 |
SPDY: augment Strict Transport Security with the beginnings of SPDY upgrade.
This adds an opportunistic flag to the information that we store in
the Strict Transport Security State. Given this, STSS might be
misnamed now, but renaming it in this patch would add huge amounts of
noise.
We process the 'X-Bodge-Transport-Security' header which has the same
format as the STS header. When we see this on an HTTP connection,
we'll probe for a clean HTTPS path to the host and then remember it.
This header should be considered mutually exclusive with STS, although
this isn't enforced in the code.
The remembered flag is currently ignored by the rest of the code. This
will be addressed in a future patch.
The header should be called 'Opportunistic-Transport-Security' in the
future, but we have some issues to work out before we take that name.
http://codereview.chromium.org/456011
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@34380 0039d316-1c4b-4281-b951-d872f2087c98
19 files changed, 794 insertions, 454 deletions
diff --git a/chrome/browser/automation/automation_profile_impl.cc b/chrome/browser/automation/automation_profile_impl.cc index ce0949e..0acd37d 100644 --- a/chrome/browser/automation/automation_profile_impl.cc +++ b/chrome/browser/automation/automation_profile_impl.cc @@ -38,7 +38,7 @@ class AutomationURLRequestContext : public ChromeURLRequestContext { http_transaction_factory_ = NULL; ftp_transaction_factory_ = NULL; cookie_store_ = NULL; - strict_transport_security_state_ = NULL; + transport_security_state_ = NULL; } scoped_refptr<ChromeURLRequestContext> original_context_; diff --git a/chrome/browser/net/chrome_url_request_context.cc b/chrome/browser/net/chrome_url_request_context.cc index 51b5517..97fba69 100644 --- a/chrome/browser/net/chrome_url_request_context.cc +++ b/chrome/browser/net/chrome_url_request_context.cc @@ -750,7 +750,7 @@ ChromeURLRequestContext::ChromeURLRequestContext( ftp_transaction_factory_ = other->ftp_transaction_factory_; cookie_store_ = other->cookie_store_; cookie_policy_.set_type(other->cookie_policy_.type()); - strict_transport_security_state_ = other->strict_transport_security_state_; + transport_security_state_ = other->transport_security_state_; accept_language_ = other->accept_language_; accept_charset_ = other->accept_charset_; referrer_charset_ = other->referrer_charset_; @@ -835,7 +835,7 @@ ChromeURLRequestContextFactory::ChromeURLRequestContextFactory(Profile* profile) blacklist_manager_ = profile->GetBlacklistManager(); // TODO(eroman): this doesn't look safe; sharing between IO and UI threads! - strict_transport_security_state_ = profile->GetStrictTransportSecurityState(); + transport_security_state_ = profile->GetTransportSecurityState(); if (profile->GetExtensionsService()) { const ExtensionList* extensions = @@ -872,8 +872,8 @@ void ChromeURLRequestContextFactory::ApplyProfileParametersToContext( context->set_user_script_dir_path(user_script_dir_path_); context->set_host_zoom_map(host_zoom_map_); context->set_blacklist_manager(blacklist_manager_.get()); - context->set_strict_transport_security_state( - strict_transport_security_state_); + context->set_transport_security_state( + transport_security_state_); context->set_ssl_config_service(ssl_config_service_); } diff --git a/chrome/browser/net/chrome_url_request_context.h b/chrome/browser/net/chrome_url_request_context.h index 55c497a..912f484 100644 --- a/chrome/browser/net/chrome_url_request_context.h +++ b/chrome/browser/net/chrome_url_request_context.h @@ -212,9 +212,9 @@ class ChromeURLRequestContext : public URLRequestContext { void set_cookie_policy_type(net::CookiePolicy::Type type) { cookie_policy_.set_type(type); } - void set_strict_transport_security_state( - net::StrictTransportSecurityState* state) { - strict_transport_security_state_ = state; + void set_transport_security_state( + net::TransportSecurityState* state) { + transport_security_state_ = state; } void set_ssl_config_service(net::SSLConfigService* service) { ssl_config_service_ = service; @@ -317,7 +317,7 @@ class ChromeURLRequestContextFactory { FilePath user_script_dir_path_; scoped_refptr<HostZoomMap> host_zoom_map_; scoped_refptr<BlacklistManager> blacklist_manager_; - net::StrictTransportSecurityState* strict_transport_security_state_; + net::TransportSecurityState* transport_security_state_; scoped_refptr<net::SSLConfigService> ssl_config_service_; FilePath profile_dir_path_; diff --git a/chrome/browser/profile.cc b/chrome/browser/profile.cc index fa245e0..3f5deee 100644 --- a/chrome/browser/profile.cc +++ b/chrome/browser/profile.cc @@ -27,7 +27,7 @@ #include "chrome/browser/extensions/user_script_master.h" #include "chrome/browser/favicon_service.h" #include "chrome/browser/spellcheck_host.h" -#include "chrome/browser/strict_transport_security_persister.h" +#include "chrome/browser/transport_security_persister.h" #include "chrome/browser/history/history.h" #include "chrome/browser/host_zoom_map.h" #include "chrome/browser/in_process_webkit/webkit_context.h" @@ -59,7 +59,7 @@ #include "chrome/common/pref_names.h" #include "chrome/common/render_messages.h" #include "grit/locale_settings.h" -#include "net/base/strict_transport_security_state.h" +#include "net/base/transport_security_state.h" #include "webkit/database/database_tracker.h" #if defined(OS_LINUX) @@ -273,13 +273,13 @@ class OffTheRecordProfileImpl : public Profile, return ssl_host_state_.get(); } - virtual net::StrictTransportSecurityState* GetStrictTransportSecurityState() { - if (!strict_transport_security_state_.get()) { - strict_transport_security_state_ = - new net::StrictTransportSecurityState(); + virtual net::TransportSecurityState* GetTransportSecurityState() { + if (!transport_security_state_.get()) { + transport_security_state_ = + new net::TransportSecurityState(); } - return strict_transport_security_state_.get(); + return transport_security_state_.get(); } virtual HistoryService* GetHistoryService(ServiceAccessType sat) { @@ -551,9 +551,9 @@ class OffTheRecordProfileImpl : public Profile, // the user visited while OTR. scoped_ptr<SSLHostState> ssl_host_state_; - // The StrictTransportSecurityState that only stores enabled sites in memory. - scoped_refptr<net::StrictTransportSecurityState> - strict_transport_security_state_; + // The TransportSecurityState that only stores enabled sites in memory. + scoped_refptr<net::TransportSecurityState> + transport_security_state_; // Time we were started. Time start_time_; @@ -855,17 +855,17 @@ SSLHostState* ProfileImpl::GetSSLHostState() { return ssl_host_state_.get(); } -net::StrictTransportSecurityState* - ProfileImpl::GetStrictTransportSecurityState() { - if (!strict_transport_security_state_.get()) { - strict_transport_security_state_ = new net::StrictTransportSecurityState(); - strict_transport_security_persister_ = - new StrictTransportSecurityPersister(); - strict_transport_security_persister_->Initialize( - strict_transport_security_state_.get(), path_); +net::TransportSecurityState* + ProfileImpl::GetTransportSecurityState() { + if (!transport_security_state_.get()) { + transport_security_state_ = new net::TransportSecurityState(); + transport_security_persister_ = + new TransportSecurityPersister(); + transport_security_persister_->Initialize( + transport_security_state_.get(), path_); } - return strict_transport_security_state_.get(); + return transport_security_state_.get(); } PrefService* ProfileImpl::GetPrefs() { diff --git a/chrome/browser/profile.h b/chrome/browser/profile.h index 2a1c065..815dc22 100644 --- a/chrome/browser/profile.h +++ b/chrome/browser/profile.h @@ -22,7 +22,7 @@ #endif namespace net { -class StrictTransportSecurityState; +class TransportSecurityState; class SSLConfigService; } @@ -55,7 +55,7 @@ class SessionService; class SpellCheckHost; class SSLConfigServiceManager; class SSLHostState; -class StrictTransportSecurityPersister; +class TransportSecurityPersister; class SQLitePersistentCookieStore; class TabRestoreService; class TemplateURLFetcher; @@ -174,11 +174,11 @@ class Profile { // called. virtual SSLHostState* GetSSLHostState() = 0; - // Retrieves a pointer to the StrictTransportSecurityState associated with - // this profile. The StrictTransportSecurityState is lazily created the + // Retrieves a pointer to the TransportSecurityState associated with + // this profile. The TransportSecurityState is lazily created the // first time that this method is called. - virtual net::StrictTransportSecurityState* - GetStrictTransportSecurityState() = 0; + virtual net::TransportSecurityState* + GetTransportSecurityState() = 0; // Retrieves a pointer to the FaviconService associated with this // profile. The FaviconService is lazily created the first time @@ -408,7 +408,7 @@ class ProfileImpl : public Profile, virtual VisitedLinkMaster* GetVisitedLinkMaster(); virtual UserScriptMaster* GetUserScriptMaster(); virtual SSLHostState* GetSSLHostState(); - virtual net::StrictTransportSecurityState* GetStrictTransportSecurityState(); + virtual net::TransportSecurityState* GetTransportSecurityState(); virtual ExtensionsService* GetExtensionsService(); virtual ExtensionDevToolsManager* GetExtensionDevToolsManager(); virtual ExtensionProcessManager* GetExtensionProcessManager(); @@ -499,10 +499,10 @@ class ProfileImpl : public Profile, scoped_ptr<ExtensionProcessManager> extension_process_manager_; scoped_refptr<ExtensionMessageService> extension_message_service_; scoped_ptr<SSLHostState> ssl_host_state_; - scoped_refptr<net::StrictTransportSecurityState> - strict_transport_security_state_; - scoped_refptr<StrictTransportSecurityPersister> - strict_transport_security_persister_; + scoped_refptr<net::TransportSecurityState> + transport_security_state_; + scoped_refptr<TransportSecurityPersister> + transport_security_persister_; scoped_ptr<PrefService> prefs_; scoped_refptr<ThumbnailStore> thumbnail_store_; scoped_ptr<TemplateURLFetcher> template_url_fetcher_; diff --git a/chrome/browser/strict_transport_security_persister.cc b/chrome/browser/transport_security_persister.cc index ce6078a..ffc1ab3 100644 --- a/chrome/browser/strict_transport_security_persister.cc +++ b/chrome/browser/transport_security_persister.cc @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "chrome/browser/strict_transport_security_persister.h" +#include "chrome/browser/transport_security_persister.h" #include "base/file_path.h" #include "base/file_util.h" @@ -10,29 +10,29 @@ #include "base/path_service.h" #include "chrome/browser/chrome_thread.h" #include "chrome/common/chrome_paths.h" -#include "net/base/strict_transport_security_state.h" +#include "net/base/transport_security_state.h" -StrictTransportSecurityPersister::StrictTransportSecurityPersister() +TransportSecurityPersister::TransportSecurityPersister() : state_is_dirty_(false) { } -StrictTransportSecurityPersister::~StrictTransportSecurityPersister() { - strict_transport_security_state_->SetDelegate(NULL); +TransportSecurityPersister::~TransportSecurityPersister() { + transport_security_state_->SetDelegate(NULL); } -void StrictTransportSecurityPersister::Initialize( - net::StrictTransportSecurityState* state, const FilePath& profile_path) { - strict_transport_security_state_ = state; +void TransportSecurityPersister::Initialize( + net::TransportSecurityState* state, const FilePath& profile_path) { + transport_security_state_ = state; state_file_ = - profile_path.Append(FILE_PATH_LITERAL("StrictTransportSecurity")); + profile_path.Append(FILE_PATH_LITERAL("TransportSecurity")); state->SetDelegate(this); Task* task = NewRunnableMethod(this, - &StrictTransportSecurityPersister::LoadState); + &TransportSecurityPersister::LoadState); ChromeThread::PostDelayedTask(ChromeThread::FILE, FROM_HERE, task, 1000); } -void StrictTransportSecurityPersister::LoadState() { +void TransportSecurityPersister::LoadState() { AutoLock locked_(lock_); DCHECK(ChromeThread::CurrentlyOn(ChromeThread::FILE)); @@ -40,26 +40,26 @@ void StrictTransportSecurityPersister::LoadState() { if (!file_util::ReadFileToString(state_file_, &state)) return; - strict_transport_security_state_->Deserialise(state); + transport_security_state_->Deserialise(state); } -void StrictTransportSecurityPersister::StateIsDirty( - net::StrictTransportSecurityState* state) { +void TransportSecurityPersister::StateIsDirty( + net::TransportSecurityState* state) { // Runs on arbitary thread, may not block nor reenter - // |strict_transport_security_state_|. + // |transport_security_state_|. AutoLock locked_(lock_); - DCHECK(state == strict_transport_security_state_); + DCHECK(state == transport_security_state_); if (state_is_dirty_) return; // we already have a serialisation scheduled Task* task = NewRunnableMethod(this, - &StrictTransportSecurityPersister::SerialiseState); + &TransportSecurityPersister::SerialiseState); ChromeThread::PostDelayedTask(ChromeThread::FILE, FROM_HERE, task, 1000); state_is_dirty_ = true; } -void StrictTransportSecurityPersister::SerialiseState() { +void TransportSecurityPersister::SerialiseState() { AutoLock locked_(lock_); DCHECK(ChromeThread::CurrentlyOn(ChromeThread::FILE)); @@ -67,7 +67,7 @@ void StrictTransportSecurityPersister::SerialiseState() { state_is_dirty_ = false; std::string state; - if (!strict_transport_security_state_->Serialise(&state)) + if (!transport_security_state_->Serialise(&state)) return; file_util::WriteFile(state_file_, state.data(), state.size()); diff --git a/chrome/browser/strict_transport_security_persister.h b/chrome/browser/transport_security_persister.h index 8a24660..660faa7 100644 --- a/chrome/browser/strict_transport_security_persister.h +++ b/chrome/browser/transport_security_persister.h @@ -2,32 +2,32 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// StrictTransportSecurityState maintains an in memory database containing the -// list of hosts that currently have strict transport security enabled. This +// TransportSecurityState maintains an in memory database containing the +// list of hosts that currently have transport security enabled. This // singleton object deals with writing that data out to disk as needed and // loading it at startup. -// At startup we need to load the strict transport security state from the +// At startup we need to load the transport security state from the // disk. For the moment, we don't want to delay startup for this load, so we -// let the StrictTransportSecurityState run for a while without being loaded. +// let the TransportSecurityState run for a while without being loaded. // This means that it's possible for pages opened very quickly not to get the -// correct strict transport security information. +// correct transport security information. // // To load the state, we schedule a Task on the file thread which loads, -// deserialises and configures the StrictTransportSecurityState. +// deserialises and configures the TransportSecurityState. // -// The StrictTransportSecurityState object supports running a callback function +// The TransportSecurityState object supports running a callback function // when it changes. This object registers the callback, pointing at itself. // -// StrictTransportSecurityState calls... -// StrictTransportSecurityPersister::StateIsDirty +// TransportSecurityState calls... +// TransportSecurityPersister::StateIsDirty // since the callback isn't allowed to block or reenter, we schedule a Task // on the file thread after some small amount of time // // ... // -// StrictTransportSecurityPersister::SerialiseState -// copies the current state of the StrictTransportSecurityState, serialises +// TransportSecurityPersister::SerialiseState +// copies the current state of the TransportSecurityState, serialises // and writes to disk. #ifndef CHROME_BROWSER_STRICT_TRANSPORT_SECURITY_PERSISTER_H_ @@ -36,23 +36,23 @@ #include "base/file_path.h" #include "base/lock.h" #include "base/ref_counted.h" -#include "net/base/strict_transport_security_state.h" +#include "net/base/transport_security_state.h" -class StrictTransportSecurityPersister - : public base::RefCountedThreadSafe<StrictTransportSecurityPersister>, - public net::StrictTransportSecurityState::Delegate { +class TransportSecurityPersister + : public base::RefCountedThreadSafe<TransportSecurityPersister>, + public net::TransportSecurityState::Delegate { public: - StrictTransportSecurityPersister(); - void Initialize(net::StrictTransportSecurityState* state, + TransportSecurityPersister(); + void Initialize(net::TransportSecurityState* state, const FilePath& profile_path); - // Called by the StrictTransportSecurityState when it changes its state. - virtual void StateIsDirty(net::StrictTransportSecurityState*); + // Called by the TransportSecurityState when it changes its state. + virtual void StateIsDirty(net::TransportSecurityState*); private: - friend class base::RefCountedThreadSafe<StrictTransportSecurityPersister>; + friend class base::RefCountedThreadSafe<TransportSecurityPersister>; - ~StrictTransportSecurityPersister(); + ~TransportSecurityPersister(); // a Task callback for when the state needs to be written out. void SerialiseState(); @@ -66,8 +66,8 @@ class StrictTransportSecurityPersister // serialised the state yet. bool state_is_dirty_; - scoped_refptr<net::StrictTransportSecurityState> - strict_transport_security_state_; + scoped_refptr<net::TransportSecurityState> + transport_security_state_; // The path to the file in which we store the serialised state. FilePath state_file_; }; diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi index 11d042f..5ac8be1 100755 --- a/chrome/chrome_browser.gypi +++ b/chrome/chrome_browser.gypi @@ -1436,8 +1436,8 @@ 'browser/ssl/ssl_policy_backend.h', 'browser/ssl/ssl_request_info.h', 'browser/status_bubble.h', - 'browser/strict_transport_security_persister.cc', - 'browser/strict_transport_security_persister.h', + 'browser/transport_security_persister.cc', + 'browser/transport_security_persister.h', 'browser/sync/engine/syncapi.h', 'browser/sync/glue/bookmark_model_worker.cc', 'browser/sync/glue/bookmark_model_worker.h', diff --git a/chrome/test/testing_profile.h b/chrome/test/testing_profile.h index 7d6451c..0e6f16d 100644 --- a/chrome/test/testing_profile.h +++ b/chrome/test/testing_profile.h @@ -85,7 +85,7 @@ class TestingProfile : public Profile { virtual ExtensionProcessManager* GetExtensionProcessManager() { return NULL; } virtual ExtensionMessageService* GetExtensionMessageService() { return NULL; } virtual SSLHostState* GetSSLHostState() { return NULL; } - virtual net::StrictTransportSecurityState* GetStrictTransportSecurityState() { + virtual net::TransportSecurityState* GetTransportSecurityState() { return NULL; } virtual FaviconService* GetFaviconService(ServiceAccessType access) { diff --git a/net/base/https_prober.cc b/net/base/https_prober.cc new file mode 100644 index 0000000..c37f17d --- /dev/null +++ b/net/base/https_prober.cc @@ -0,0 +1,80 @@ +// Copyright (c) 2009 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 "net/base/https_prober.h" + +#include "net/url_request/url_request.h" +#include "net/url_request/url_request_context.h" + +namespace net { + +bool HTTPSProber::HaveProbed(const std::string& host) const { + return probed_.find(host) != probed_.end(); +} + +bool HTTPSProber::InFlight(const std::string& host) const { + return inflight_probes_.find(host) != inflight_probes_.end(); +} + +bool HTTPSProber::ProbeHost(const std::string& host, URLRequestContext* ctx, + HTTPSProberDelegate* delegate) { + if (HaveProbed(host) || InFlight(host)) { + return false; + } + + inflight_probes_[host] = delegate; + + GURL url("https://" + host); + DCHECK_EQ(url.host(), host); + + URLRequest* req = new URLRequest(url, this); + req->set_context(ctx); + req->Start(); + return true; +} + +void HTTPSProber::Success(URLRequest* request) { + DoCallback(request, true); +} + +void HTTPSProber::Failure(URLRequest* request) { + DoCallback(request, false); +} + +void HTTPSProber::DoCallback(URLRequest* request, bool result) { + std::map<std::string, HTTPSProberDelegate*>::iterator i = + inflight_probes_.find(request->original_url().host()); + DCHECK(i != inflight_probes_.end()); + + HTTPSProberDelegate* delegate = i->second; + inflight_probes_.erase(i); + probed_.insert(request->original_url().host()); + delete request; + delegate->ProbeComplete(result); +} + +void HTTPSProber::OnAuthRequired(URLRequest* request, + net::AuthChallengeInfo* auth_info) { + Success(request); +} + +void HTTPSProber::OnSSLCertificateError(URLRequest* request, + int cert_error, + net::X509Certificate* cert) { + request->ContinueDespiteLastError(); +} + +void HTTPSProber::OnResponseStarted(URLRequest* request) { + if (request->status().status() == URLRequestStatus::SUCCESS) { + Success(request); + } else { + Failure(request); + } +} + +void HTTPSProber::OnReadCompleted(URLRequest* request, int bytes_read) { + NOTREACHED(); +} + +} // namespace net diff --git a/net/base/https_prober.h b/net/base/https_prober.h new file mode 100644 index 0000000..327fc16 --- /dev/null +++ b/net/base/https_prober.h @@ -0,0 +1,73 @@ +// Copyright (c) 2009 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 NET_BASE_HTTPS_PROBER_H_ +#define NET_BASE_HTTPS_PROBER_H_ + +#include <map> +#include <set> +#include <string> + +#include "base/singleton.h" +#include "base/task.h" +#include "net/url_request/url_request.h" + +class URLRequestContext; + +namespace net { + +// This should be scoped inside HTTPSProber, but VC cannot compile +// HTTPProber::Delegate when HTTPSProber also inherits from +// URLRequest::Delegate. +class HTTPSProberDelegate { + public: + virtual void ProbeComplete(bool result) = 0; +}; + +// HTTPSProber is a singleton object that manages HTTPS probes. A HTTPS probe +// determines if we can connect to a given host over HTTPS. It's used when +// transparently upgrading from HTTP to HTTPS (for example, for SPDY). +class HTTPSProber : public URLRequest::Delegate { + public: + HTTPSProber() { } + + // HaveProbed returns true if the given host is known to have been probed + // since the browser was last started. + bool HaveProbed(const std::string& host) const; + + // InFlight returns true iff a probe for the given host is currently active. + bool InFlight(const std::string& host) const; + + // ProbeHost starts a new probe for the given host. If the host is known to + // have been probed since the browser was started, false is returned and no + // other action is taken. If a probe to the given host in currently inflight, + // false will be returned, and no other action is taken. Otherwise, a new + // probe is started, true is returned and the Delegate will be called with the + // results (true means a successful handshake). + bool ProbeHost(const std::string& host, URLRequestContext* ctx, + HTTPSProberDelegate* delegate); + + // Implementation of URLRequest::Delegate + void OnAuthRequired(URLRequest* request, + net::AuthChallengeInfo* auth_info); + void OnSSLCertificateError(URLRequest* request, + int cert_error, + net::X509Certificate* cert); + void OnResponseStarted(URLRequest* request); + void OnReadCompleted(URLRequest* request, int bytes_read); + + private: + void Success(URLRequest* request); + void Failure(URLRequest* request); + void DoCallback(URLRequest* request, bool result); + + std::map<std::string, HTTPSProberDelegate*> inflight_probes_; + std::set<std::string> probed_; + + friend struct DefaultSingletonTraits<HTTPSProber>; + DISALLOW_EVIL_CONSTRUCTORS(HTTPSProber); +}; + +} // namespace net +#endif diff --git a/net/base/strict_transport_security_state.h b/net/base/strict_transport_security_state.h deleted file mode 100644 index de7d2b1..0000000 --- a/net/base/strict_transport_security_state.h +++ /dev/null @@ -1,95 +0,0 @@ -// Copyright (c) 2009 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 NET_BASE_STRICT_TRANSPORT_SECURITY_STATE_H_ -#define NET_BASE_STRICT_TRANSPORT_SECURITY_STATE_H_ - -#include <map> -#include <string> - -#include "base/basictypes.h" -#include "base/lock.h" -#include "base/ref_counted.h" -#include "base/time.h" - -class GURL; - -namespace net { - -// StrictTransportSecurityState -// -// Tracks which hosts have enabled StrictTransportSecurityState. After a host -// enables StrictTransportSecurityState, then we refuse to talk to the host -// over HTTP, treat all certificate errors as fatal, and refuse to load any -// mixed content. -// -class StrictTransportSecurityState : - public base::RefCountedThreadSafe<StrictTransportSecurityState> { - public: - StrictTransportSecurityState(); - - // Called when we see an X-Force-TLS header that we should process. Modifies - // our state as instructed by the header. - void DidReceiveHeader(const GURL& url, const std::string& value); - - // Enable StrictTransportSecurity for |host|. - void EnableHost(const std::string& host, base::Time expiry, - bool include_subdomains); - - // Returns whether |host| has had StrictTransportSecurity enabled. - bool IsEnabledForHost(const std::string& host); - - // Returns |true| if |value| parses as a valid X-Force-TLS header value. - // The values of max-age and and includeSubDomains are returned in |max_age| - // and |include_subdomains|, respectively. The out parameters are not - // modified if the function returns |false|. - static bool ParseHeader(const std::string& value, - int* max_age, - bool* include_subdomains); - - struct State { - base::Time expiry; // the absolute time (UTC) when this record expires - bool include_subdomains; // subdomains included? - }; - - class Delegate { - public: - // This function may not block and may be called with internal locks held. - // Thus it must not reenter the StrictTransportSecurityState object. - virtual void StateIsDirty(StrictTransportSecurityState* state) = 0; - }; - - void SetDelegate(Delegate*); - - bool Serialise(std::string* output); - bool Deserialise(const std::string& state); - - private: - friend class base::RefCountedThreadSafe<StrictTransportSecurityState>; - - ~StrictTransportSecurityState() {} - - // If we have a callback configured, call it to let our serialiser know that - // our state is dirty. - void DirtyNotify(); - - // The set of hosts that have enabled StrictTransportSecurity. The keys here - // are SHA256(DNSForm(domain)) where DNSForm converts from dotted form - // ('www.google.com') to the form used in DNS: "\x03www\x06google\x03com" - std::map<std::string, State> enabled_hosts_; - - // Protect access to our data members with this lock. - Lock lock_; - - // Our delegate who gets notified when we are dirtied, or NULL. - Delegate* delegate_; - - static std::string CanonicaliseHost(const std::string& host); - - DISALLOW_COPY_AND_ASSIGN(StrictTransportSecurityState); -}; - -} // namespace net - -#endif // NET_BASE_STRICT_TRANSPORT_SECURITY_STATE_H_ diff --git a/net/base/strict_transport_security_state_unittest.cc b/net/base/strict_transport_security_state_unittest.cc deleted file mode 100644 index 5ebd358..0000000 --- a/net/base/strict_transport_security_state_unittest.cc +++ /dev/null @@ -1,206 +0,0 @@ -// Copyright (c) 2009 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 "net/base/strict_transport_security_state.h" -#include "testing/gtest/include/gtest/gtest.h" - -class StrictTransportSecurityStateTest : public testing::Test { -}; - -TEST_F(StrictTransportSecurityStateTest, BogusHeaders) { - int max_age = 42; - bool include_subdomains = false; - - EXPECT_FALSE(net::StrictTransportSecurityState::ParseHeader( - "", &max_age, &include_subdomains)); - EXPECT_FALSE(net::StrictTransportSecurityState::ParseHeader( - " ", &max_age, &include_subdomains)); - EXPECT_FALSE(net::StrictTransportSecurityState::ParseHeader( - "abc", &max_age, &include_subdomains)); - EXPECT_FALSE(net::StrictTransportSecurityState::ParseHeader( - " abc", &max_age, &include_subdomains)); - EXPECT_FALSE(net::StrictTransportSecurityState::ParseHeader( - " abc ", &max_age, &include_subdomains)); - EXPECT_FALSE(net::StrictTransportSecurityState::ParseHeader( - "max-age", &max_age, &include_subdomains)); - EXPECT_FALSE(net::StrictTransportSecurityState::ParseHeader( - " max-age", &max_age, &include_subdomains)); - EXPECT_FALSE(net::StrictTransportSecurityState::ParseHeader( - " max-age ", &max_age, &include_subdomains)); - EXPECT_FALSE(net::StrictTransportSecurityState::ParseHeader( - "max-age=", &max_age, &include_subdomains)); - EXPECT_FALSE(net::StrictTransportSecurityState::ParseHeader( - " max-age=", &max_age, &include_subdomains)); - EXPECT_FALSE(net::StrictTransportSecurityState::ParseHeader( - " max-age =", &max_age, &include_subdomains)); - EXPECT_FALSE(net::StrictTransportSecurityState::ParseHeader( - " max-age= ", &max_age, &include_subdomains)); - EXPECT_FALSE(net::StrictTransportSecurityState::ParseHeader( - " max-age = ", &max_age, &include_subdomains)); - EXPECT_FALSE(net::StrictTransportSecurityState::ParseHeader( - " max-age = xy", &max_age, &include_subdomains)); - EXPECT_FALSE(net::StrictTransportSecurityState::ParseHeader( - " max-age = 3488a923", &max_age, &include_subdomains)); - EXPECT_FALSE(net::StrictTransportSecurityState::ParseHeader( - "max-age=3488a923 ", &max_age, &include_subdomains)); - EXPECT_FALSE(net::StrictTransportSecurityState::ParseHeader( - "max-ag=3488923", &max_age, &include_subdomains)); - EXPECT_FALSE(net::StrictTransportSecurityState::ParseHeader( - "max-aged=3488923", &max_age, &include_subdomains)); - EXPECT_FALSE(net::StrictTransportSecurityState::ParseHeader( - "max-age==3488923", &max_age, &include_subdomains)); - EXPECT_FALSE(net::StrictTransportSecurityState::ParseHeader( - "amax-age=3488923", &max_age, &include_subdomains)); - EXPECT_FALSE(net::StrictTransportSecurityState::ParseHeader( - "max-age=-3488923", &max_age, &include_subdomains)); - EXPECT_FALSE(net::StrictTransportSecurityState::ParseHeader( - "max-age=3488923;", &max_age, &include_subdomains)); - EXPECT_FALSE(net::StrictTransportSecurityState::ParseHeader( - "max-age=3488923 e", &max_age, &include_subdomains)); - EXPECT_FALSE(net::StrictTransportSecurityState::ParseHeader( - "max-age=3488923 includesubdomain", &max_age, &include_subdomains)); - EXPECT_FALSE(net::StrictTransportSecurityState::ParseHeader( - "max-age=3488923includesubdomains", &max_age, &include_subdomains)); - EXPECT_FALSE(net::StrictTransportSecurityState::ParseHeader( - "max-age=3488923=includesubdomains", &max_age, &include_subdomains)); - EXPECT_FALSE(net::StrictTransportSecurityState::ParseHeader( - "max-age=3488923 includesubdomainx", &max_age, &include_subdomains)); - EXPECT_FALSE(net::StrictTransportSecurityState::ParseHeader( - "max-age=3488923 includesubdomain=", &max_age, &include_subdomains)); - EXPECT_FALSE(net::StrictTransportSecurityState::ParseHeader( - "max-age=3488923 includesubdomain=true", &max_age, &include_subdomains)); - EXPECT_FALSE(net::StrictTransportSecurityState::ParseHeader( - "max-age=3488923 includesubdomainsx", &max_age, &include_subdomains)); - EXPECT_FALSE(net::StrictTransportSecurityState::ParseHeader( - "max-age=3488923 includesubdomains x", &max_age, &include_subdomains)); - EXPECT_FALSE(net::StrictTransportSecurityState::ParseHeader( - "max-age=34889.23 includesubdomains", &max_age, &include_subdomains)); - EXPECT_FALSE(net::StrictTransportSecurityState::ParseHeader( - "max-age=34889 includesubdomains", &max_age, &include_subdomains)); - - EXPECT_EQ(max_age, 42); - EXPECT_FALSE(include_subdomains); -} - -TEST_F(StrictTransportSecurityStateTest, ValidHeaders) { - int max_age = 42; - bool include_subdomains = true; - - EXPECT_TRUE(net::StrictTransportSecurityState::ParseHeader( - "max-age=243", &max_age, &include_subdomains)); - EXPECT_EQ(max_age, 243); - EXPECT_FALSE(include_subdomains); - - EXPECT_TRUE(net::StrictTransportSecurityState::ParseHeader( - " Max-agE = 567", &max_age, &include_subdomains)); - EXPECT_EQ(max_age, 567); - EXPECT_FALSE(include_subdomains); - - EXPECT_TRUE(net::StrictTransportSecurityState::ParseHeader( - " mAx-aGe = 890 ", &max_age, &include_subdomains)); - EXPECT_EQ(max_age, 890); - EXPECT_FALSE(include_subdomains); - - EXPECT_TRUE(net::StrictTransportSecurityState::ParseHeader( - "max-age=123;incLudesUbdOmains", &max_age, &include_subdomains)); - EXPECT_EQ(max_age, 123); - EXPECT_TRUE(include_subdomains); - - EXPECT_TRUE(net::StrictTransportSecurityState::ParseHeader( - "max-age=394082; incLudesUbdOmains", &max_age, &include_subdomains)); - EXPECT_EQ(max_age, 394082); - EXPECT_TRUE(include_subdomains); - - EXPECT_TRUE(net::StrictTransportSecurityState::ParseHeader( - "max-age=39408299 ;incLudesUbdOmains", &max_age, &include_subdomains)); - EXPECT_EQ(max_age, 39408299); - EXPECT_TRUE(include_subdomains); - - EXPECT_TRUE(net::StrictTransportSecurityState::ParseHeader( - "max-age=394082038 ; incLudesUbdOmains", &max_age, &include_subdomains)); - EXPECT_EQ(max_age, 394082038); - EXPECT_TRUE(include_subdomains); - - EXPECT_TRUE(net::StrictTransportSecurityState::ParseHeader( - " max-age=0 ; incLudesUbdOmains ", &max_age, &include_subdomains)); - EXPECT_EQ(max_age, 0); - EXPECT_TRUE(include_subdomains); -} - -TEST_F(StrictTransportSecurityStateTest, SimpleMatches) { - scoped_refptr<net::StrictTransportSecurityState> state( - new net::StrictTransportSecurityState); - const base::Time current_time(base::Time::Now()); - const base::Time expiry = current_time + base::TimeDelta::FromSeconds(1000); - - EXPECT_FALSE(state->IsEnabledForHost("google.com")); - state->EnableHost("google.com", expiry, false); - EXPECT_TRUE(state->IsEnabledForHost("google.com")); -} - -TEST_F(StrictTransportSecurityStateTest, MatchesCase1) { - scoped_refptr<net::StrictTransportSecurityState> state( - new net::StrictTransportSecurityState); - const base::Time current_time(base::Time::Now()); - const base::Time expiry = current_time + base::TimeDelta::FromSeconds(1000); - - EXPECT_FALSE(state->IsEnabledForHost("google.com")); - state->EnableHost("GOOgle.coM", expiry, false); - EXPECT_TRUE(state->IsEnabledForHost("google.com")); -} - -TEST_F(StrictTransportSecurityStateTest, MatchesCase2) { - scoped_refptr<net::StrictTransportSecurityState> state( - new net::StrictTransportSecurityState); - const base::Time current_time(base::Time::Now()); - const base::Time expiry = current_time + base::TimeDelta::FromSeconds(1000); - - EXPECT_FALSE(state->IsEnabledForHost("GOOgle.coM")); - state->EnableHost("google.com", expiry, false); - EXPECT_TRUE(state->IsEnabledForHost("GOOgle.coM")); -} - -TEST_F(StrictTransportSecurityStateTest, SubdomainMatches) { - scoped_refptr<net::StrictTransportSecurityState> state( - new net::StrictTransportSecurityState); - const base::Time current_time(base::Time::Now()); - const base::Time expiry = current_time + base::TimeDelta::FromSeconds(1000); - - EXPECT_FALSE(state->IsEnabledForHost("google.com")); - state->EnableHost("google.com", expiry, true); - EXPECT_TRUE(state->IsEnabledForHost("google.com")); - EXPECT_TRUE(state->IsEnabledForHost("foo.google.com")); - EXPECT_TRUE(state->IsEnabledForHost("foo.bar.google.com")); - EXPECT_TRUE(state->IsEnabledForHost("foo.bar.baz.google.com")); - EXPECT_FALSE(state->IsEnabledForHost("com")); -} - -TEST_F(StrictTransportSecurityStateTest, Serialise1) { - scoped_refptr<net::StrictTransportSecurityState> state( - new net::StrictTransportSecurityState); - std::string output; - state->Serialise(&output); - EXPECT_TRUE(state->Deserialise(output)); -} - -TEST_F(StrictTransportSecurityStateTest, Serialise2) { - scoped_refptr<net::StrictTransportSecurityState> state( - new net::StrictTransportSecurityState); - - const base::Time current_time(base::Time::Now()); - const base::Time expiry = current_time + base::TimeDelta::FromSeconds(1000); - - EXPECT_FALSE(state->IsEnabledForHost("google.com")); - state->EnableHost("google.com", expiry, true); - - std::string output; - state->Serialise(&output); - EXPECT_TRUE(state->Deserialise(output)); - - EXPECT_TRUE(state->IsEnabledForHost("google.com")); - EXPECT_TRUE(state->IsEnabledForHost("foo.google.com")); - EXPECT_TRUE(state->IsEnabledForHost("foo.bar.google.com")); - EXPECT_TRUE(state->IsEnabledForHost("foo.bar.baz.google.com")); - EXPECT_FALSE(state->IsEnabledForHost("com")); -} diff --git a/net/base/strict_transport_security_state.cc b/net/base/transport_security_state.cc index 29f892f..35b930c 100644 --- a/net/base/strict_transport_security_state.cc +++ b/net/base/transport_security_state.cc @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "net/base/strict_transport_security_state.h" +#include "net/base/transport_security_state.h" #include "base/base64.h" #include "base/json/json_reader.h" @@ -18,28 +18,12 @@ namespace net { -StrictTransportSecurityState::StrictTransportSecurityState() +TransportSecurityState::TransportSecurityState() : delegate_(NULL) { } -void StrictTransportSecurityState::DidReceiveHeader(const GURL& url, - const std::string& value) { - int max_age; - bool include_subdomains; - - if (!ParseHeader(value, &max_age, &include_subdomains)) - return; - - base::Time current_time(base::Time::Now()); - base::TimeDelta max_age_delta = base::TimeDelta::FromSeconds(max_age); - base::Time expiry = current_time + max_age_delta; - - EnableHost(url.host(), expiry, include_subdomains); -} - -void StrictTransportSecurityState::EnableHost(const std::string& host, - base::Time expiry, - bool include_subdomains) { +void TransportSecurityState::EnableHost(const std::string& host, + const DomainState& state) { const std::string canonicalised_host = CanonicaliseHost(host); if (canonicalised_host.empty()) return; @@ -48,12 +32,12 @@ void StrictTransportSecurityState::EnableHost(const std::string& host, AutoLock lock(lock_); - State state = {expiry, include_subdomains}; enabled_hosts_[std::string(hashed, sizeof(hashed))] = state; DirtyNotify(); } -bool StrictTransportSecurityState::IsEnabledForHost(const std::string& host) { +bool TransportSecurityState::IsEnabledForHost(DomainState* result, + const std::string& host) { const std::string canonicalised_host = CanonicaliseHost(host); if (canonicalised_host.empty()) return false; @@ -66,7 +50,7 @@ bool StrictTransportSecurityState::IsEnabledForHost(const std::string& host) { base::SHA256HashString(&canonicalised_host[i], &hashed_domain, sizeof(hashed_domain)); - std::map<std::string, State>::iterator j = + std::map<std::string, DomainState>::iterator j = enabled_hosts_.find(std::string(hashed_domain, sizeof(hashed_domain))); if (j == enabled_hosts_.end()) continue; @@ -77,6 +61,8 @@ bool StrictTransportSecurityState::IsEnabledForHost(const std::string& host) { continue; } + *result = j->second; + // If we matched the domain exactly, it doesn't matter what the value of // include_subdomains is. if (i == 0) @@ -90,9 +76,9 @@ bool StrictTransportSecurityState::IsEnabledForHost(const std::string& host) { // "Strict-Transport-Security" ":" // "max-age" "=" delta-seconds [ ";" "includeSubDomains" ] -bool StrictTransportSecurityState::ParseHeader(const std::string& value, - int* max_age, - bool* include_subdomains) { +bool TransportSecurityState::ParseHeader(const std::string& value, + int* max_age, + bool* include_subdomains) { DCHECK(max_age); DCHECK(include_subdomains); @@ -187,8 +173,8 @@ bool StrictTransportSecurityState::ParseHeader(const std::string& value, } } -void StrictTransportSecurityState::SetDelegate( - StrictTransportSecurityState::Delegate* delegate) { +void TransportSecurityState::SetDelegate( + TransportSecurityState::Delegate* delegate) { AutoLock lock(lock_); delegate_ = delegate; @@ -215,16 +201,32 @@ static std::string ExternalStringToHashedDomain(const std::wstring& external) { return out; } -bool StrictTransportSecurityState::Serialise(std::string* output) { +bool TransportSecurityState::Serialise(std::string* output) { AutoLock lock(lock_); DictionaryValue toplevel; - for (std::map<std::string, State>::const_iterator + for (std::map<std::string, DomainState>::const_iterator i = enabled_hosts_.begin(); i != enabled_hosts_.end(); ++i) { DictionaryValue* state = new DictionaryValue; state->SetBoolean(L"include_subdomains", i->second.include_subdomains); state->SetReal(L"expiry", i->second.expiry.ToDoubleT()); + switch (i->second.mode) { + case DomainState::MODE_STRICT: + state->SetString(L"mode", "strict"); + break; + case DomainState::MODE_OPPORTUNISTIC: + state->SetString(L"mode", "opportunistic"); + break; + case DomainState::MODE_SPDY_ONLY: + state->SetString(L"mode", "spdy-only"); + break; + default: + NOTREACHED() << "DomainState with unknown mode"; + delete state; + continue; + } + toplevel.Set(HashedDomainToExternalString(i->first), state); } @@ -232,7 +234,7 @@ bool StrictTransportSecurityState::Serialise(std::string* output) { return true; } -bool StrictTransportSecurityState::Deserialise(const std::string& input) { +bool TransportSecurityState::Deserialise(const std::string& input) { AutoLock lock(lock_); enabled_hosts_.clear(); @@ -252,13 +254,28 @@ bool StrictTransportSecurityState::Deserialise(const std::string& input) { continue; bool include_subdomains; + std::string mode_string; double expiry; if (!state->GetBoolean(L"include_subdomains", &include_subdomains) || + !state->GetString(L"mode", &mode_string) || !state->GetReal(L"expiry", &expiry)) { continue; } + DomainState::Mode mode; + if (mode_string == "strict") { + mode = DomainState::MODE_STRICT; + } else if (mode_string == "opportunistic") { + mode = DomainState::MODE_OPPORTUNISTIC; + } else if (mode_string == "spdy-only") { + mode = DomainState::MODE_SPDY_ONLY; + } else { + LOG(WARNING) << "Unknown TransportSecurityState mode string found: " + << mode_string; + continue; + } + base::Time expiry_time = base::Time::FromDoubleT(expiry); if (expiry_time <= current_time) continue; @@ -267,21 +284,23 @@ bool StrictTransportSecurityState::Deserialise(const std::string& input) { if (hashed.empty()) continue; - State new_state = { expiry_time, include_subdomains }; + DomainState new_state; + new_state.mode = mode; + new_state.expiry = expiry_time; + new_state.include_subdomains = include_subdomains; enabled_hosts_[hashed] = new_state; } return true; } -void StrictTransportSecurityState::DirtyNotify() { +void TransportSecurityState::DirtyNotify() { if (delegate_) delegate_->StateIsDirty(this); } // static -std::string StrictTransportSecurityState::CanonicaliseHost( - const std::string& host) { +std::string TransportSecurityState::CanonicaliseHost(const std::string& host) { // We cannot perform the operations as detailed in the spec here as |host| // has already undergone IDN processing before it reached us. Thus, we check // that there are no invalid characters in the host and lowercase the result. diff --git a/net/base/transport_security_state.h b/net/base/transport_security_state.h new file mode 100644 index 0000000..360eb0b --- /dev/null +++ b/net/base/transport_security_state.h @@ -0,0 +1,111 @@ +// Copyright (c) 2009 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 NET_BASE_TRANSPORT_SECURITY_STATE_H_ +#define NET_BASE_TRANSPORT_SECURITY_STATE_H_ + +#include <map> +#include <string> + +#include "base/basictypes.h" +#include "base/lock.h" +#include "base/ref_counted.h" +#include "base/time.h" + +class GURL; + +namespace net { + +// TransportSecurityState +// +// Tracks which hosts have enabled *-Transport-Security. This object manages +// the in-memory store. A separate object must register itself with this object +// in order to persist the state to disk. +class TransportSecurityState : + public base::RefCountedThreadSafe<TransportSecurityState> { + public: + TransportSecurityState(); + + // A DomainState is the information that we persist about a given domain. + struct DomainState { + enum Mode { + // Strict mode implies: + // * We generate internal redirects from HTTP -> HTTPS. + // * Certificate issues are fatal. + MODE_STRICT = 0, + // Opportunistic mode implies: + // * We'll request HTTP URLs over HTTPS + // * Certificate issues are ignored. + MODE_OPPORTUNISTIC = 1, + // SPDY_ONLY (aka X-Bodge-Transport-Security) is a hopefully temporary + // measure. It implies: + // * We'll request HTTP URLs over HTTPS iff we have SPDY support. + // * Certificate issues are fatal. + MODE_SPDY_ONLY = 2, + }; + Mode mode; + + DomainState() + : mode(MODE_STRICT), + include_subdomains(false) { } + + base::Time expiry; // the absolute time (UTC) when this record expires + bool include_subdomains; // subdomains included? + }; + + // Enable TransportSecurity for |host|. + void EnableHost(const std::string& host, const DomainState& state); + + // Returns true if |host| has TransportSecurity enabled. If that case, + // *result is filled out. + bool IsEnabledForHost(DomainState* result, const std::string& host); + + // Returns |true| if |value| parses as a valid *-Transport-Security + // header value. The values of max-age and and includeSubDomains are + // returned in |max_age| and |include_subdomains|, respectively. The out + // parameters are not modified if the function returns |false|. + static bool ParseHeader(const std::string& value, + int* max_age, + bool* include_subdomains); + + class Delegate { + public: + // This function may not block and may be called with internal locks held. + // Thus it must not reenter the TransportSecurityState object. + virtual void StateIsDirty(TransportSecurityState* state) = 0; + }; + + void SetDelegate(Delegate*); + + bool Serialise(std::string* output); + bool Deserialise(const std::string& state); + + private: + friend class base::RefCountedThreadSafe<TransportSecurityState>; + + ~TransportSecurityState() {} + + // If we have a callback configured, call it to let our serialiser know that + // our state is dirty. + void DirtyNotify(); + + // The set of hosts that have enabled TransportSecurity. The keys here + // are SHA256(DNSForm(domain)) where DNSForm converts from dotted form + // ('www.google.com') to the form used in DNS: "\x03www\x06google\x03com" + std::map<std::string, DomainState> enabled_hosts_; + + // Protect access to our data members with this lock. + Lock lock_; + + // Our delegate who gets notified when we are dirtied, or NULL. + Delegate* delegate_; + + static std::string CanonicaliseHost(const std::string& host); + + DISALLOW_COPY_AND_ASSIGN(TransportSecurityState); +}; + +} // namespace net + +#endif // NET_BASE_TRANSPORT_SECURITY_STATE_H_ diff --git a/net/base/transport_security_state_unittest.cc b/net/base/transport_security_state_unittest.cc new file mode 100644 index 0000000..f52912c --- /dev/null +++ b/net/base/transport_security_state_unittest.cc @@ -0,0 +1,247 @@ +// Copyright (c) 2009 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 "net/base/transport_security_state.h" +#include "testing/gtest/include/gtest/gtest.h" + +class TransportSecurityStateTest : public testing::Test { +}; + +TEST_F(TransportSecurityStateTest, BogusHeaders) { + int max_age = 42; + bool include_subdomains = false; + + EXPECT_FALSE(net::TransportSecurityState::ParseHeader( + "", &max_age, &include_subdomains)); + EXPECT_FALSE(net::TransportSecurityState::ParseHeader( + " ", &max_age, &include_subdomains)); + EXPECT_FALSE(net::TransportSecurityState::ParseHeader( + "abc", &max_age, &include_subdomains)); + EXPECT_FALSE(net::TransportSecurityState::ParseHeader( + " abc", &max_age, &include_subdomains)); + EXPECT_FALSE(net::TransportSecurityState::ParseHeader( + " abc ", &max_age, &include_subdomains)); + EXPECT_FALSE(net::TransportSecurityState::ParseHeader( + "max-age", &max_age, &include_subdomains)); + EXPECT_FALSE(net::TransportSecurityState::ParseHeader( + " max-age", &max_age, &include_subdomains)); + EXPECT_FALSE(net::TransportSecurityState::ParseHeader( + " max-age ", &max_age, &include_subdomains)); + EXPECT_FALSE(net::TransportSecurityState::ParseHeader( + "max-age=", &max_age, &include_subdomains)); + EXPECT_FALSE(net::TransportSecurityState::ParseHeader( + " max-age=", &max_age, &include_subdomains)); + EXPECT_FALSE(net::TransportSecurityState::ParseHeader( + " max-age =", &max_age, &include_subdomains)); + EXPECT_FALSE(net::TransportSecurityState::ParseHeader( + " max-age= ", &max_age, &include_subdomains)); + EXPECT_FALSE(net::TransportSecurityState::ParseHeader( + " max-age = ", &max_age, &include_subdomains)); + EXPECT_FALSE(net::TransportSecurityState::ParseHeader( + " max-age = xy", &max_age, &include_subdomains)); + EXPECT_FALSE(net::TransportSecurityState::ParseHeader( + " max-age = 3488a923", &max_age, &include_subdomains)); + EXPECT_FALSE(net::TransportSecurityState::ParseHeader( + "max-age=3488a923 ", &max_age, &include_subdomains)); + EXPECT_FALSE(net::TransportSecurityState::ParseHeader( + "max-ag=3488923", &max_age, &include_subdomains)); + EXPECT_FALSE(net::TransportSecurityState::ParseHeader( + "max-aged=3488923", &max_age, &include_subdomains)); + EXPECT_FALSE(net::TransportSecurityState::ParseHeader( + "max-age==3488923", &max_age, &include_subdomains)); + EXPECT_FALSE(net::TransportSecurityState::ParseHeader( + "amax-age=3488923", &max_age, &include_subdomains)); + EXPECT_FALSE(net::TransportSecurityState::ParseHeader( + "max-age=-3488923", &max_age, &include_subdomains)); + EXPECT_FALSE(net::TransportSecurityState::ParseHeader( + "max-age=3488923;", &max_age, &include_subdomains)); + EXPECT_FALSE(net::TransportSecurityState::ParseHeader( + "max-age=3488923 e", &max_age, &include_subdomains)); + EXPECT_FALSE(net::TransportSecurityState::ParseHeader( + "max-age=3488923 includesubdomain", &max_age, &include_subdomains)); + EXPECT_FALSE(net::TransportSecurityState::ParseHeader( + "max-age=3488923includesubdomains", &max_age, &include_subdomains)); + EXPECT_FALSE(net::TransportSecurityState::ParseHeader( + "max-age=3488923=includesubdomains", &max_age, &include_subdomains)); + EXPECT_FALSE(net::TransportSecurityState::ParseHeader( + "max-age=3488923 includesubdomainx", &max_age, &include_subdomains)); + EXPECT_FALSE(net::TransportSecurityState::ParseHeader( + "max-age=3488923 includesubdomain=", &max_age, &include_subdomains)); + EXPECT_FALSE(net::TransportSecurityState::ParseHeader( + "max-age=3488923 includesubdomain=true", &max_age, &include_subdomains)); + EXPECT_FALSE(net::TransportSecurityState::ParseHeader( + "max-age=3488923 includesubdomainsx", &max_age, &include_subdomains)); + EXPECT_FALSE(net::TransportSecurityState::ParseHeader( + "max-age=3488923 includesubdomains x", &max_age, &include_subdomains)); + EXPECT_FALSE(net::TransportSecurityState::ParseHeader( + "max-age=34889.23 includesubdomains", &max_age, &include_subdomains)); + EXPECT_FALSE(net::TransportSecurityState::ParseHeader( + "max-age=34889 includesubdomains", &max_age, &include_subdomains)); + + EXPECT_EQ(max_age, 42); + EXPECT_FALSE(include_subdomains); +} + +TEST_F(TransportSecurityStateTest, ValidHeaders) { + int max_age = 42; + bool include_subdomains = true; + + EXPECT_TRUE(net::TransportSecurityState::ParseHeader( + "max-age=243", &max_age, &include_subdomains)); + EXPECT_EQ(max_age, 243); + EXPECT_FALSE(include_subdomains); + + EXPECT_TRUE(net::TransportSecurityState::ParseHeader( + " Max-agE = 567", &max_age, &include_subdomains)); + EXPECT_EQ(max_age, 567); + EXPECT_FALSE(include_subdomains); + + EXPECT_TRUE(net::TransportSecurityState::ParseHeader( + " mAx-aGe = 890 ", &max_age, &include_subdomains)); + EXPECT_EQ(max_age, 890); + EXPECT_FALSE(include_subdomains); + + EXPECT_TRUE(net::TransportSecurityState::ParseHeader( + "max-age=123;incLudesUbdOmains", &max_age, &include_subdomains)); + EXPECT_EQ(max_age, 123); + EXPECT_TRUE(include_subdomains); + + EXPECT_TRUE(net::TransportSecurityState::ParseHeader( + "max-age=394082; incLudesUbdOmains", &max_age, &include_subdomains)); + EXPECT_EQ(max_age, 394082); + EXPECT_TRUE(include_subdomains); + + EXPECT_TRUE(net::TransportSecurityState::ParseHeader( + "max-age=39408299 ;incLudesUbdOmains", &max_age, &include_subdomains)); + EXPECT_EQ(max_age, 39408299); + EXPECT_TRUE(include_subdomains); + + EXPECT_TRUE(net::TransportSecurityState::ParseHeader( + "max-age=394082038 ; incLudesUbdOmains", &max_age, &include_subdomains)); + EXPECT_EQ(max_age, 394082038); + EXPECT_TRUE(include_subdomains); + + EXPECT_TRUE(net::TransportSecurityState::ParseHeader( + " max-age=0 ; incLudesUbdOmains ", &max_age, &include_subdomains)); + EXPECT_EQ(max_age, 0); + EXPECT_TRUE(include_subdomains); +} + +TEST_F(TransportSecurityStateTest, SimpleMatches) { + scoped_refptr<net::TransportSecurityState> state( + new net::TransportSecurityState); + net::TransportSecurityState::DomainState domain_state; + const base::Time current_time(base::Time::Now()); + const base::Time expiry = current_time + base::TimeDelta::FromSeconds(1000); + + EXPECT_FALSE(state->IsEnabledForHost(&domain_state, "google.com")); + domain_state.expiry = expiry; + state->EnableHost("google.com", domain_state); + EXPECT_TRUE(state->IsEnabledForHost(&domain_state, "google.com")); +} + +TEST_F(TransportSecurityStateTest, MatchesCase1) { + scoped_refptr<net::TransportSecurityState> state( + new net::TransportSecurityState); + net::TransportSecurityState::DomainState domain_state; + const base::Time current_time(base::Time::Now()); + const base::Time expiry = current_time + base::TimeDelta::FromSeconds(1000); + + EXPECT_FALSE(state->IsEnabledForHost(&domain_state, "google.com")); + domain_state.expiry = expiry; + state->EnableHost("GOOgle.coM", domain_state); + EXPECT_TRUE(state->IsEnabledForHost(&domain_state, "google.com")); +} + +TEST_F(TransportSecurityStateTest, MatchesCase2) { + scoped_refptr<net::TransportSecurityState> state( + new net::TransportSecurityState); + net::TransportSecurityState::DomainState domain_state; + const base::Time current_time(base::Time::Now()); + const base::Time expiry = current_time + base::TimeDelta::FromSeconds(1000); + + EXPECT_FALSE(state->IsEnabledForHost(&domain_state, "GOOgle.coM")); + domain_state.expiry = expiry; + state->EnableHost("google.com", domain_state); + EXPECT_TRUE(state->IsEnabledForHost(&domain_state, "GOOgle.coM")); +} + +TEST_F(TransportSecurityStateTest, SubdomainMatches) { + scoped_refptr<net::TransportSecurityState> state( + new net::TransportSecurityState); + net::TransportSecurityState::DomainState domain_state; + const base::Time current_time(base::Time::Now()); + const base::Time expiry = current_time + base::TimeDelta::FromSeconds(1000); + + EXPECT_FALSE(state->IsEnabledForHost(&domain_state, "google.com")); + domain_state.expiry = expiry; + domain_state.include_subdomains = true; + state->EnableHost("google.com", domain_state); + EXPECT_TRUE(state->IsEnabledForHost(&domain_state, "google.com")); + EXPECT_TRUE(state->IsEnabledForHost(&domain_state, "foo.google.com")); + EXPECT_TRUE(state->IsEnabledForHost(&domain_state, "foo.bar.google.com")); + EXPECT_TRUE(state->IsEnabledForHost(&domain_state, + "foo.bar.baz.google.com")); + EXPECT_FALSE(state->IsEnabledForHost(&domain_state, "com")); +} + +TEST_F(TransportSecurityStateTest, Serialise1) { + scoped_refptr<net::TransportSecurityState> state( + new net::TransportSecurityState); + std::string output; + state->Serialise(&output); + EXPECT_TRUE(state->Deserialise(output)); +} + +TEST_F(TransportSecurityStateTest, Serialise2) { + scoped_refptr<net::TransportSecurityState> state( + new net::TransportSecurityState); + + net::TransportSecurityState::DomainState domain_state; + const base::Time current_time(base::Time::Now()); + const base::Time expiry = current_time + base::TimeDelta::FromSeconds(1000); + + EXPECT_FALSE(state->IsEnabledForHost(&domain_state, "google.com")); + domain_state.mode = net::TransportSecurityState::DomainState::MODE_STRICT; + domain_state.expiry = expiry; + domain_state.include_subdomains = true; + state->EnableHost("google.com", domain_state); + + std::string output; + state->Serialise(&output); + EXPECT_TRUE(state->Deserialise(output)); + + EXPECT_TRUE(state->IsEnabledForHost(&domain_state, "google.com")); + EXPECT_EQ(domain_state.mode, net::TransportSecurityState::DomainState::MODE_STRICT); + EXPECT_TRUE(state->IsEnabledForHost(&domain_state, "foo.google.com")); + EXPECT_EQ(domain_state.mode, net::TransportSecurityState::DomainState::MODE_STRICT); + EXPECT_TRUE(state->IsEnabledForHost(&domain_state, "foo.bar.google.com")); + EXPECT_EQ(domain_state.mode, net::TransportSecurityState::DomainState::MODE_STRICT); + EXPECT_TRUE(state->IsEnabledForHost(&domain_state, + "foo.bar.baz.google.com")); + EXPECT_EQ(domain_state.mode, net::TransportSecurityState::DomainState::MODE_STRICT); + EXPECT_FALSE(state->IsEnabledForHost(&domain_state, "com")); +} + +TEST_F(TransportSecurityStateTest, Serialise3) { + scoped_refptr<net::TransportSecurityState> state( + new net::TransportSecurityState); + + net::TransportSecurityState::DomainState domain_state; + const base::Time current_time(base::Time::Now()); + const base::Time expiry = current_time + base::TimeDelta::FromSeconds(1000); + + EXPECT_FALSE(state->IsEnabledForHost(&domain_state, "google.com")); + domain_state.mode = net::TransportSecurityState::DomainState::MODE_OPPORTUNISTIC; + domain_state.expiry = expiry; + state->EnableHost("google.com", domain_state); + + std::string output; + state->Serialise(&output); + EXPECT_TRUE(state->Deserialise(output)); + + EXPECT_TRUE(state->IsEnabledForHost(&domain_state, "google.com")); + EXPECT_EQ(domain_state.mode, + net::TransportSecurityState::DomainState::MODE_OPPORTUNISTIC); +} diff --git a/net/net.gyp b/net/net.gyp index 5118981..7f0d88e 100644 --- a/net/net.gyp +++ b/net/net.gyp @@ -75,6 +75,8 @@ 'base/host_resolver_impl.h', 'base/host_resolver_proc.cc', 'base/host_resolver_proc.h', + 'base/https_prober.h', + 'base/https_prober.cc', 'base/io_buffer.cc', 'base/io_buffer.h', 'base/keygen_handler.h', @@ -131,8 +133,8 @@ 'base/ssl_config_service_win.cc', 'base/ssl_config_service_win.h', 'base/ssl_info.h', - 'base/strict_transport_security_state.cc', - 'base/strict_transport_security_state.h', + 'base/transport_security_state.cc', + 'base/transport_security_state.h', 'base/telnet_server.cc', 'base/telnet_server.h', 'base/test_completion_callback.h', @@ -597,7 +599,7 @@ 'base/ssl_client_auth_cache_unittest.cc', 'base/ssl_config_service_mac_unittest.cc', 'base/ssl_config_service_win_unittest.cc', - 'base/strict_transport_security_state_unittest.cc', + 'base/transport_security_state_unittest.cc', 'base/telnet_server_unittest.cc', 'base/test_certificate_data.h', 'base/test_completion_callback_unittest.cc', diff --git a/net/url_request/url_request_context.h b/net/url_request/url_request_context.h index f02bc43..5eac06e 100644 --- a/net/url_request/url_request_context.h +++ b/net/url_request/url_request_context.h @@ -16,7 +16,7 @@ #include "net/base/cookie_store.h" #include "net/base/host_resolver.h" #include "net/base/ssl_config_service.h" -#include "net/base/strict_transport_security_state.h" +#include "net/base/transport_security_state.h" #include "net/ftp/ftp_auth_cache.h" #include "net/proxy/proxy_service.h" #include "net/url_request/request_tracker.h" @@ -36,7 +36,7 @@ class URLRequestContext : : http_transaction_factory_(NULL), ftp_transaction_factory_(NULL), cookie_store_(NULL), - strict_transport_security_state_(NULL) { + transport_security_state_(NULL) { } net::HostResolver* host_resolver() const { @@ -69,8 +69,8 @@ class URLRequestContext : // Gets the cookie policy for this context. net::CookiePolicy* cookie_policy() { return &cookie_policy_; } - net::StrictTransportSecurityState* strict_transport_security_state() { - return strict_transport_security_state_; } + net::TransportSecurityState* transport_security_state() { + return transport_security_state_; } // Gets the FTP authentication cache for this context. net::FtpAuthCache* ftp_auth_cache() { return &ftp_auth_cache_; } @@ -132,8 +132,7 @@ class URLRequestContext : net::FtpTransactionFactory* ftp_transaction_factory_; scoped_refptr<net::CookieStore> cookie_store_; net::CookiePolicy cookie_policy_; - scoped_refptr<net::StrictTransportSecurityState> - strict_transport_security_state_; + scoped_refptr<net::TransportSecurityState> transport_security_state_; net::FtpAuthCache ftp_auth_cache_; std::string accept_language_; std::string accept_charset_; diff --git a/net/url_request/url_request_http_job.cc b/net/url_request/url_request_http_job.cc index 84d5709..755bfa7 100644 --- a/net/url_request/url_request_http_job.cc +++ b/net/url_request/url_request_http_job.cc @@ -14,7 +14,8 @@ #include "base/string_util.h" #include "net/base/cert_status_flags.h" #include "net/base/filter.h" -#include "net/base/strict_transport_security_state.h" +#include "net/base/https_prober.h" +#include "net/base/transport_security_state.h" #include "net/base/load_flags.h" #include "net/base/net_errors.h" #include "net/base/net_util.h" @@ -46,17 +47,24 @@ URLRequestJob* URLRequestHttpJob::Factory(URLRequest* request, return new URLRequestErrorJob(request, net::ERR_INVALID_ARGUMENT); } + net::TransportSecurityState::DomainState domain_state; if (scheme == "http" && - request->context()->strict_transport_security_state() && - request->context()->strict_transport_security_state()->IsEnabledForHost( - request->url().host())) { - DCHECK_EQ(request->url().scheme(), "http"); - url_canon::Replacements<char> replacements; - static const char kNewScheme[] = "https"; - replacements.SetScheme(kNewScheme, - url_parse::Component(0, strlen(kNewScheme))); - GURL new_location = request->url().ReplaceComponents(replacements); - return new URLRequestRedirectJob(request, new_location); + (request->url().port().empty() || port == 80) && + request->context()->transport_security_state() && + request->context()->transport_security_state()->IsEnabledForHost( + &domain_state, request->url().host())) { + if (domain_state.mode == + net::TransportSecurityState::DomainState::MODE_STRICT) { + DCHECK_EQ(request->url().scheme(), "http"); + url_canon::Replacements<char> replacements; + static const char kNewScheme[] = "https"; + replacements.SetScheme(kNewScheme, + url_parse::Component(0, strlen(kNewScheme))); + GURL new_location = request->url().ReplaceComponents(replacements); + return new URLRequestRedirectJob(request, new_location); + } else { + // TODO(agl): implement opportunistic HTTPS upgrade. + } } return new URLRequestHttpJob(request); @@ -483,11 +491,16 @@ bool URLRequestHttpJob::ShouldTreatAsCertificateError(int result) { return false; // Check whether our context is using Strict-Transport-Security. - if (!context_->strict_transport_security_state()) + if (!context_->transport_security_state()) return true; - return !context_->strict_transport_security_state()->IsEnabledForHost( - request_info_.url.host()); + net::TransportSecurityState::DomainState domain_state; + // TODO(agl): don't ignore opportunistic mode. + const bool r = context_->transport_security_state()->IsEnabledForHost( + &domain_state, request_info_.url.host()); + + return !r || domain_state.mode == + net::TransportSecurityState::DomainState::MODE_OPPORTUNISTIC; } void URLRequestHttpJob::NotifyHeadersComplete() { @@ -686,28 +699,125 @@ void URLRequestHttpJob::FetchResponseCookies() { response_cookies_.push_back(value); } +class HTTPSProberDelegate : public net::HTTPSProberDelegate { + public: + HTTPSProberDelegate(const std::string& host, int max_age, + bool include_subdomains, + net::TransportSecurityState* sts) + : host_(host), + max_age_(max_age), + include_subdomains_(include_subdomains), + sts_(sts) { } + + virtual void ProbeComplete(bool result) { + if (result) { + base::Time current_time(base::Time::Now()); + base::TimeDelta max_age_delta = base::TimeDelta::FromSeconds(max_age_); + + net::TransportSecurityState::DomainState domain_state; + domain_state.expiry = current_time + max_age_delta; + domain_state.mode = + net::TransportSecurityState::DomainState::MODE_OPPORTUNISTIC; + domain_state.include_subdomains = include_subdomains_; + + sts_->EnableHost(host_, domain_state); + } -void URLRequestHttpJob::ProcessStrictTransportSecurityHeader() { - DCHECK(response_info_); + delete this; + } - // Only process Strict-Transport-Security from HTTPS responses. - if (request_info_.url.scheme() != "https") - return; + private: + const std::string host_; + const int max_age_; + const bool include_subdomains_; + scoped_refptr<net::TransportSecurityState> sts_; +}; - // Only process Strict-Transport-Security from responses with valid certificates. - if (response_info_->ssl_info.cert_status & net::CERT_STATUS_ALL_ERRORS) - return; +void URLRequestHttpJob::ProcessStrictTransportSecurityHeader() { + DCHECK(response_info_); URLRequestContext* ctx = request_->context(); - if (!ctx || !ctx->strict_transport_security_state()) + if (!ctx || !ctx->transport_security_state()) return; + const bool https = response_info_->ssl_info.is_valid(); + const bool valid_https = + https && + !(response_info_->ssl_info.cert_status & net::CERT_STATUS_ALL_ERRORS); + std::string name = "Strict-Transport-Security"; std::string value; + int max_age; + bool include_subdomains; + void* iter = NULL; while (response_info_->headers->EnumerateHeader(&iter, name, &value)) { - ctx->strict_transport_security_state()->DidReceiveHeader( - request_info_.url, value); + const bool ok = net::TransportSecurityState::ParseHeader( + value, &max_age, &include_subdomains); + if (!ok) + continue; + // We will only accept strict mode if we saw the header from an HTTPS + // connection with no certificate problems. + if (!valid_https) + continue; + base::Time current_time(base::Time::Now()); + base::TimeDelta max_age_delta = base::TimeDelta::FromSeconds(max_age); + + net::TransportSecurityState::DomainState domain_state; + domain_state.expiry = current_time + max_age_delta; + domain_state.mode = net::TransportSecurityState::DomainState::MODE_STRICT; + domain_state.include_subdomains = include_subdomains; + + ctx->transport_security_state()->EnableHost(request_info_.url.host(), + domain_state); + } + + // TODO(agl): change this over when we have fixed things at the server end. + // The string should be "Opportunistic-Transport-Security"; + name = "X-Bodge-Transport-Security"; + + while (response_info_->headers->EnumerateHeader(&iter, name, &value)) { + const bool ok = net::TransportSecurityState::ParseHeader( + value, &max_age, &include_subdomains); + if (!ok) + continue; + // If we saw an opportunistic request over HTTPS, then clearly we can make + // HTTPS connections to the host so we should remember this. + if (https) { + base::Time current_time(base::Time::Now()); + base::TimeDelta max_age_delta = base::TimeDelta::FromSeconds(max_age); + + net::TransportSecurityState::DomainState domain_state; + domain_state.expiry = current_time + max_age_delta; + domain_state.mode = + net::TransportSecurityState::DomainState::MODE_SPDY_ONLY; + domain_state.include_subdomains = include_subdomains; + + ctx->transport_security_state()->EnableHost(request_info_.url.host(), + domain_state); + continue; + } + + if (!request()) + break; + + // At this point, we have a request for opportunistic encryption over HTTP. + // In this case we need to probe to check that we can make HTTPS + // connections to that host. + net::HTTPSProber* const prober = Singleton<net::HTTPSProber>::get(); + if (prober->HaveProbed(request_info_.url.host()) || + prober->InFlight(request_info_.url.host())) { + continue; + } + + net::HTTPSProberDelegate* delegate = + new HTTPSProberDelegate(request_info_.url.host(), max_age, + include_subdomains, + ctx->transport_security_state()); + if (!prober->ProbeHost(request_info_.url.host(), request()->context(), + delegate)) { + delete delegate; + } } } |