Commit 1b0e3685 authored by bnc's avatar bnc Committed by Commit bot

Verify alternative server certificate validity for origin.

Verify that the certificate presented by the alternative server is valid for the
origin when using HTTP/2, both when opening a new connection or when pooling to
an existing one.

Patch Set 1:
 * Enable hopping to a different host for HTTP/2 (not for QUIC).
 * Add unittests for both new connection and pooling cases.  Note in trybot
   output that *Valid tests pass and *Invalid tests fail.

Patch Set 2 and up:
 * Verify certificate validity in HttpStreamFactoryImpl::Job.

BUG=474217

Review URL: https://codereview.chromium.org/1074193003

Cr-Commit-Position: refs/heads/master@{#327293}
parent 57167a6b
...@@ -636,6 +636,11 @@ NET_ERROR(PROXY_HTTP_1_1_REQUIRED, -366) ...@@ -636,6 +636,11 @@ NET_ERROR(PROXY_HTTP_1_1_REQUIRED, -366)
// The PAC script terminated fatally and must be reloaded. // The PAC script terminated fatally and must be reloaded.
NET_ERROR(PAC_SCRIPT_TERMINATED, -367) NET_ERROR(PAC_SCRIPT_TERMINATED, -367)
// The certificate offered by the alternative server is not valid for the
// origin, a violation of HTTP Alternative Services specification Section 2.1,
// https://tools.ietf.org/id/draft-ietf-httpbis-alt-svc-06.html#host_auth.
NET_ERROR(ALTERNATIVE_CERT_NOT_VALID_FOR_ORIGIN, -368)
// The cache does not have the requested entry. // The cache does not have the requested entry.
NET_ERROR(CACHE_MISS, -400) NET_ERROR(CACHE_MISS, -400)
......
...@@ -18,6 +18,7 @@ ...@@ -18,6 +18,7 @@
#include "base/memory/scoped_ptr.h" #include "base/memory/scoped_ptr.h"
#include "base/memory/weak_ptr.h" #include "base/memory/weak_ptr.h"
#include "base/run_loop.h" #include "base/run_loop.h"
#include "base/stl_util.h"
#include "base/strings/string_util.h" #include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h" #include "base/strings/utf_string_conversions.h"
#include "base/test/test_file_util.h" #include "base/test/test_file_util.h"
...@@ -11847,6 +11848,162 @@ TEST_P(HttpNetworkTransactionTest, DoNotUseSpdySessionForHttp) { ...@@ -11847,6 +11848,162 @@ TEST_P(HttpNetworkTransactionTest, DoNotUseSpdySessionForHttp) {
EXPECT_FALSE(trans2.GetResponseInfo()->was_fetched_via_spdy); EXPECT_FALSE(trans2.GetResponseInfo()->was_fetched_via_spdy);
} }
class AltSvcCertificateVerificationTest : public HttpNetworkTransactionTest {
public:
void Run(bool pooling, bool valid) {
HostPortPair origin(valid ? "mail.example.org" : "invalid.example.org",
443);
HostPortPair alternative("www.example.org", 443);
base::FilePath certs_dir = GetTestCertsDirectory();
scoped_refptr<X509Certificate> cert(
ImportCertFromFile(certs_dir, "spdy_pooling.pem"));
ASSERT_TRUE(cert.get());
bool common_name_fallback_used;
EXPECT_EQ(valid,
cert->VerifyNameMatch(origin.host(), &common_name_fallback_used));
EXPECT_TRUE(
cert->VerifyNameMatch(alternative.host(), &common_name_fallback_used));
SSLSocketDataProvider ssl(ASYNC, OK);
ssl.SetNextProto(GetParam());
ssl.cert = cert;
session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl);
// If pooling, then start a request to alternative first to create a
// SpdySession.
std::string url0 = "https://www.example.org:443";
// Second request to origin, which has an alternative service, and could
// open a connection to the alternative host or pool to the existing one.
std::string url1("https://");
url1.append(origin.host());
url1.append(":443");
scoped_ptr<SpdyFrame> req0;
scoped_ptr<SpdyFrame> req1;
scoped_ptr<SpdyFrame> resp0;
scoped_ptr<SpdyFrame> body0;
scoped_ptr<SpdyFrame> resp1;
scoped_ptr<SpdyFrame> body1;
std::vector<MockWrite> writes;
std::vector<MockRead> reads;
if (pooling) {
req0.reset(spdy_util_.ConstructSpdyGet(url0.c_str(), false, 1, LOWEST));
req1.reset(spdy_util_.ConstructSpdyGet(url1.c_str(), false, 3, LOWEST));
writes.push_back(CreateMockWrite(*req0, 0));
writes.push_back(CreateMockWrite(*req1, 3));
resp0.reset(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
body0.reset(spdy_util_.ConstructSpdyBodyFrame(1, true));
resp1.reset(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 3));
body1.reset(spdy_util_.ConstructSpdyBodyFrame(3, true));
reads.push_back(CreateMockRead(*resp0, 1));
reads.push_back(CreateMockRead(*body0, 2));
reads.push_back(MockRead(ASYNC, ERR_IO_PENDING, 4));
reads.push_back(CreateMockRead(*resp1, 5));
reads.push_back(CreateMockRead(*body1, 6));
reads.push_back(MockRead(ASYNC, OK, 7));
} else {
req1.reset(spdy_util_.ConstructSpdyGet(url1.c_str(), false, 1, LOWEST));
writes.push_back(CreateMockWrite(*req1, 0));
resp1.reset(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
body1.reset(spdy_util_.ConstructSpdyBodyFrame(1, true));
reads.push_back(CreateMockRead(*resp1, 1));
reads.push_back(CreateMockRead(*body1, 2));
reads.push_back(MockRead(ASYNC, OK, 3));
}
OrderedSocketData data(vector_as_array(&reads), reads.size(),
vector_as_array(&writes), writes.size());
session_deps_.socket_factory->AddSocketDataProvider(&data);
// Connection to the origin fails.
MockConnect mock_connect(ASYNC, ERR_CONNECTION_REFUSED);
StaticSocketDataProvider data_refused;
data_refused.set_connect_data(mock_connect);
session_deps_.socket_factory->AddSocketDataProvider(&data_refused);
session_deps_.use_alternate_protocols = true;
scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps_));
base::WeakPtr<HttpServerProperties> http_server_properties =
session->http_server_properties();
AlternativeService alternative_service(
AlternateProtocolFromNextProto(GetParam()), alternative);
http_server_properties->SetAlternativeService(origin, alternative_service,
1.0);
// First request to alternative.
if (pooling) {
scoped_ptr<HttpTransaction> trans0(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
HttpRequestInfo request0;
request0.method = "GET";
request0.url = GURL(url0);
request0.load_flags = 0;
TestCompletionCallback callback0;
int rv = trans0->Start(&request0, callback0.callback(), BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback0.WaitForResult();
EXPECT_EQ(OK, rv);
}
// Second request to origin.
scoped_ptr<HttpTransaction> trans1(
new HttpNetworkTransaction(DEFAULT_PRIORITY, session.get()));
HttpRequestInfo request1;
request1.method = "GET";
request1.url = GURL(url1);
request1.load_flags = 0;
TestCompletionCallback callback1;
int rv = trans1->Start(&request1, callback1.callback(), BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
rv = callback1.WaitForResult();
if (valid) {
EXPECT_EQ(OK, rv);
} else {
if (pooling) {
EXPECT_EQ(ERR_CONNECTION_REFUSED, rv);
} else {
EXPECT_EQ(ERR_ALTERNATIVE_CERT_NOT_VALID_FOR_ORIGIN, rv);
}
}
}
};
INSTANTIATE_TEST_CASE_P(NextProto,
AltSvcCertificateVerificationTest,
testing::Values(kProtoSPDY31,
kProtoSPDY4_14,
kProtoSPDY4));
// The alternative service host must exhibit a certificate that is valid for the
// origin host. Test that this is enforced when pooling to an existing
// connection.
TEST_P(AltSvcCertificateVerificationTest, PoolingValid) {
Run(true, true);
}
TEST_P(AltSvcCertificateVerificationTest, PoolingInvalid) {
Run(true, false);
}
// The alternative service host must exhibit a certificate that is valid for the
// origin host. Test that this is enforced when opening a new connection.
TEST_P(AltSvcCertificateVerificationTest, NewConnectionValid) {
Run(false, true);
}
TEST_P(AltSvcCertificateVerificationTest, NewConnectionInvalid) {
Run(false, false);
}
TEST_P(HttpNetworkTransactionTest, DoNotUseSpdySessionForHttpOverTunnel) { TEST_P(HttpNetworkTransactionTest, DoNotUseSpdySessionForHttpOverTunnel) {
const std::string https_url = "https://www.example.org:8080/"; const std::string https_url = "https://www.example.org:8080/";
const std::string http_url = "http://www.example.org:8080/"; const std::string http_url = "http://www.example.org:8080/";
......
...@@ -159,11 +159,6 @@ AlternativeService HttpStreamFactoryImpl::GetAlternativeServiceFor( ...@@ -159,11 +159,6 @@ AlternativeService HttpStreamFactoryImpl::GetAlternativeServiceFor(
if (alternative_service.protocol == UNINITIALIZED_ALTERNATE_PROTOCOL) if (alternative_service.protocol == UNINITIALIZED_ALTERNATE_PROTOCOL)
return kNoAlternativeService; return kNoAlternativeService;
// TODO(bnc): Make sure that callers connect to the specified host, and that
// certificate requirements are enforced. Then remove the following two
// lines.
if (alternative_service.host != origin.host())
return kNoAlternativeService;
if (http_server_properties.IsAlternativeServiceBroken(alternative_service)) { if (http_server_properties.IsAlternativeServiceBroken(alternative_service)) {
HistogramAlternateProtocolUsage(ALTERNATE_PROTOCOL_USAGE_BROKEN); HistogramAlternateProtocolUsage(ALTERNATE_PROTOCOL_USAGE_BROKEN);
return kNoAlternativeService; return kNoAlternativeService;
...@@ -198,6 +193,10 @@ AlternativeService HttpStreamFactoryImpl::GetAlternativeServiceFor( ...@@ -198,6 +193,10 @@ AlternativeService HttpStreamFactoryImpl::GetAlternativeServiceFor(
} }
DCHECK_EQ(QUIC, alternative_service.protocol); DCHECK_EQ(QUIC, alternative_service.protocol);
// TODO(bnc): Make sure that certificate requirements are enforced when using
// QUIC, then remove the following two lines.
if (alternative_service.host != origin.host())
return kNoAlternativeService;
if (!session_->params().enable_quic) if (!session_->params().enable_quic)
return kNoAlternativeService; return kNoAlternativeService;
......
...@@ -539,9 +539,14 @@ int HttpStreamFactoryImpl::Job::RunLoop(int result) { ...@@ -539,9 +539,14 @@ int HttpStreamFactoryImpl::Job::RunLoop(int result) {
return ERR_IO_PENDING; return ERR_IO_PENDING;
default: default:
DCHECK(result != ERR_ALTERNATIVE_CERT_NOT_VALID_FOR_ORIGIN ||
IsSpdyAlternate());
if (job_status_ != STATUS_BROKEN) { if (job_status_ != STATUS_BROKEN) {
DCHECK_EQ(STATUS_RUNNING, job_status_); DCHECK_EQ(STATUS_RUNNING, job_status_);
job_status_ = STATUS_FAILED; job_status_ = STATUS_FAILED;
// TODO(bnc): If (result == ERR_ALTERNATIVE_CERT_NOT_VALID_FOR_ORIGIN),
// then instead of marking alternative service broken, mark (origin,
// alternative service) couple as invalid.
MaybeMarkAlternativeServiceBroken(); MaybeMarkAlternativeServiceBroken();
} }
base::MessageLoop::current()->PostTask( base::MessageLoop::current()->PostTask(
...@@ -626,6 +631,8 @@ int HttpStreamFactoryImpl::Job::DoStart() { ...@@ -626,6 +631,8 @@ int HttpStreamFactoryImpl::Job::DoStart() {
} }
origin_url_ = origin_url_ =
stream_factory_->ApplyHostMappingRules(request_info_.url, &server_); stream_factory_->ApplyHostMappingRules(request_info_.url, &server_);
valid_spdy_session_pool_.reset(new ValidSpdySessionPool(
session_->spdy_session_pool(), origin_url_, IsSpdyAlternate()));
net_log_.BeginEvent( net_log_.BeginEvent(
NetLog::TYPE_HTTP_STREAM_JOB, NetLog::TYPE_HTTP_STREAM_JOB,
...@@ -805,9 +812,11 @@ int HttpStreamFactoryImpl::Job::DoInitConnection() { ...@@ -805,9 +812,11 @@ int HttpStreamFactoryImpl::Job::DoInitConnection() {
// Check first if we have a spdy session for this group. If so, then go // Check first if we have a spdy session for this group. If so, then go
// straight to using that. // straight to using that.
if (CanUseExistingSpdySession()) { if (CanUseExistingSpdySession()) {
base::WeakPtr<SpdySession> spdy_session = base::WeakPtr<SpdySession> spdy_session;
session_->spdy_session_pool()->FindAvailableSession(spdy_session_key, int result = valid_spdy_session_pool_->FindAvailableSession(
net_log_); spdy_session_key, net_log_, &spdy_session);
if (result != OK)
return result;
if (spdy_session) { if (spdy_session) {
// If we're preconnecting, but we already have a SpdySession, we don't // If we're preconnecting, but we already have a SpdySession, we don't
// actually need to preconnect any sockets, so we're done. // actually need to preconnect any sockets, so we're done.
...@@ -993,6 +1002,9 @@ int HttpStreamFactoryImpl::Job::DoInitConnectionComplete(int result) { ...@@ -993,6 +1002,9 @@ int HttpStreamFactoryImpl::Job::DoInitConnectionComplete(int result) {
if (!ssl_started && result < 0 && IsAlternate()) { if (!ssl_started && result < 0 && IsAlternate()) {
job_status_ = STATUS_BROKEN; job_status_ = STATUS_BROKEN;
// TODO(bnc): if (result == ERR_ALTERNATIVE_CERT_NOT_VALID_FOR_ORIGIN), then
// instead of marking alternative service broken, mark (origin, alternative
// service) couple as invalid.
MaybeMarkAlternativeServiceBroken(); MaybeMarkAlternativeServiceBroken();
return result; return result;
} }
...@@ -1115,21 +1127,24 @@ int HttpStreamFactoryImpl::Job::DoCreateStream() { ...@@ -1115,21 +1127,24 @@ int HttpStreamFactoryImpl::Job::DoCreateStream() {
return set_result; return set_result;
} }
SpdySessionPool* spdy_pool = session_->spdy_session_pool();
SpdySessionKey spdy_session_key = GetSpdySessionKey(); SpdySessionKey spdy_session_key = GetSpdySessionKey();
base::WeakPtr<SpdySession> spdy_session = base::WeakPtr<SpdySession> spdy_session;
spdy_pool->FindAvailableSession(spdy_session_key, net_log_); int result = valid_spdy_session_pool_->FindAvailableSession(
spdy_session_key, net_log_, &spdy_session);
if (result != OK) {
return result;
}
if (spdy_session) { if (spdy_session) {
return SetSpdyHttpStream(spdy_session, direct); return SetSpdyHttpStream(spdy_session, direct);
} }
spdy_session = result = valid_spdy_session_pool_->CreateAvailableSessionFromSocket(
spdy_pool->CreateAvailableSessionFromSocket(spdy_session_key, spdy_session_key, connection_.Pass(), net_log_, spdy_certificate_error_,
connection_.Pass(), using_ssl_, &spdy_session);
net_log_, if (result != OK) {
spdy_certificate_error_, return result;
using_ssl_); }
if (!spdy_session->HasAcceptableTransportSecurity()) { if (!spdy_session->HasAcceptableTransportSecurity()) {
spdy_session->CloseSessionOnError( spdy_session->CloseSessionOnError(
ERR_SPDY_INADEQUATE_TRANSPORT_SECURITY, ""); ERR_SPDY_INADEQUATE_TRANSPORT_SECURITY, "");
...@@ -1465,6 +1480,48 @@ void HttpStreamFactoryImpl::Job::MaybeMarkAlternativeServiceBroken() { ...@@ -1465,6 +1480,48 @@ void HttpStreamFactoryImpl::Job::MaybeMarkAlternativeServiceBroken() {
} }
} }
HttpStreamFactoryImpl::Job::ValidSpdySessionPool::ValidSpdySessionPool(
SpdySessionPool* spdy_session_pool,
GURL& origin_url,
bool is_spdy_alternate)
: spdy_session_pool_(spdy_session_pool),
origin_url_(origin_url),
is_spdy_alternate_(is_spdy_alternate) {
}
int HttpStreamFactoryImpl::Job::ValidSpdySessionPool::FindAvailableSession(
const SpdySessionKey& key,
const BoundNetLog& net_log,
base::WeakPtr<SpdySession>* spdy_session) {
*spdy_session = spdy_session_pool_->FindAvailableSession(key, net_log);
return CheckAlternativeServiceValidityForOrigin(*spdy_session);
}
int HttpStreamFactoryImpl::Job::ValidSpdySessionPool::
CreateAvailableSessionFromSocket(const SpdySessionKey& key,
scoped_ptr<ClientSocketHandle> connection,
const BoundNetLog& net_log,
int certificate_error_code,
bool is_secure,
base::WeakPtr<SpdySession>* spdy_session) {
*spdy_session = spdy_session_pool_->CreateAvailableSessionFromSocket(
key, connection.Pass(), net_log, certificate_error_code, is_secure);
return CheckAlternativeServiceValidityForOrigin(*spdy_session);
}
int HttpStreamFactoryImpl::Job::ValidSpdySessionPool::
CheckAlternativeServiceValidityForOrigin(
base::WeakPtr<SpdySession> spdy_session) {
// For a SPDY alternate Job, server_.host() might be different than
// origin_url_.host(), therefore it needs to be verified that the former
// provides a certificate that is valid for the latter.
if (!is_spdy_alternate_ || !spdy_session ||
spdy_session->VerifyDomainAuthentication(origin_url_.host())) {
return OK;
}
return ERR_ALTERNATIVE_CERT_NOT_VALID_FOR_ORIGIN;
}
ClientSocketPoolManager::SocketGroupType ClientSocketPoolManager::SocketGroupType
HttpStreamFactoryImpl::Job::GetSocketGroup() const { HttpStreamFactoryImpl::Job::GetSocketGroup() const {
std::string scheme = origin_url_.scheme(); std::string scheme = origin_url_.scheme();
......
...@@ -140,6 +140,50 @@ class HttpStreamFactoryImpl::Job { ...@@ -140,6 +140,50 @@ class HttpStreamFactoryImpl::Job {
STATUS_SUCCEEDED STATUS_SUCCEEDED
}; };
// Wrapper class for SpdySessionPool methods to enforce certificate
// requirements for SpdySessions.
class ValidSpdySessionPool {
public:
ValidSpdySessionPool(SpdySessionPool* spdy_session_pool,
GURL& origin_url,
bool is_spdy_alternate);
// Returns OK if a SpdySession was not found (in which case |spdy_session|
// is set to nullptr), or if one was found (in which case |spdy_session| is
// set to it) and it has an associated SSL certificate with is valid for
// |origin_url_|, or if this requirement does not apply because the Job is
// not a SPDY alternate job. Returns the appropriate error code otherwise,
// in which case |spdy_session| should not be used.
int FindAvailableSession(const SpdySessionKey& key,
const BoundNetLog& net_log,
base::WeakPtr<SpdySession>* spdy_session);
// Creates a SpdySession and sets |spdy_session| to point to it. Returns OK
// if the associated SSL certificate is valid for |origin_url_|, or if this
// requirement does not apply because the Job is not a SPDY alternate job.
// Returns the appropriate error code otherwise, in which case
// |spdy_session| should not be used.
int CreateAvailableSessionFromSocket(
const SpdySessionKey& key,
scoped_ptr<ClientSocketHandle> connection,
const BoundNetLog& net_log,
int certificate_error_code,
bool is_secure,
base::WeakPtr<SpdySession>* spdy_session);
private:
// Returns OK if |spdy_session| has an associated SSL certificate with is
// valid for |origin_url_|, or if this requirement does not apply because
// the Job is not a SPDY alternate job, or if |spdy_session| is null.
// Returns appropriate error code otherwise.
int CheckAlternativeServiceValidityForOrigin(
base::WeakPtr<SpdySession> spdy_session);
SpdySessionPool* const spdy_session_pool_;
const GURL origin_url_;
const bool is_spdy_alternate_;
};
void OnStreamReadyCallback(); void OnStreamReadyCallback();
void OnWebSocketHandshakeStreamReadyCallback(); void OnWebSocketHandshakeStreamReadyCallback();
// This callback function is called when a new SPDY session is created. // This callback function is called when a new SPDY session is created.
...@@ -327,6 +371,8 @@ class HttpStreamFactoryImpl::Job { ...@@ -327,6 +371,8 @@ class HttpStreamFactoryImpl::Job {
// preconnect. // preconnect.
int num_streams_; int num_streams_;
scoped_ptr<ValidSpdySessionPool> valid_spdy_session_pool_;
// Initialized when we create a new SpdySession. // Initialized when we create a new SpdySession.
base::WeakPtr<SpdySession> new_spdy_session_; base::WeakPtr<SpdySession> new_spdy_session_;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment