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 /net | |
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
Diffstat (limited to 'net')
-rw-r--r-- | net/base/https_prober.cc | 80 | ||||
-rw-r--r-- | net/base/https_prober.h | 73 | ||||
-rw-r--r-- | net/base/strict_transport_security_state.h | 95 | ||||
-rw-r--r-- | net/base/strict_transport_security_state_unittest.cc | 206 | ||||
-rw-r--r-- | net/base/transport_security_state.cc (renamed from net/base/strict_transport_security_state.cc) | 89 | ||||
-rw-r--r-- | net/base/transport_security_state.h | 111 | ||||
-rw-r--r-- | net/base/transport_security_state_unittest.cc | 247 | ||||
-rw-r--r-- | net/net.gyp | 8 | ||||
-rw-r--r-- | net/url_request/url_request_context.h | 11 | ||||
-rw-r--r-- | net/url_request/url_request_http_job.cc | 160 |
10 files changed, 710 insertions, 370 deletions
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; + } } } |