diff options
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; + } } } |