| Index: net/http/http_network_transaction_unittest.cc
|
| diff --git a/net/http/http_network_transaction_unittest.cc b/net/http/http_network_transaction_unittest.cc
|
| index f7614fffd8e1cd7bc355a05dc34985cc677c1820..909c8ec2cc8e505b56432d015b64ce226af57038 100644
|
| --- a/net/http/http_network_transaction_unittest.cc
|
| +++ b/net/http/http_network_transaction_unittest.cc
|
| @@ -12,6 +12,7 @@
|
| #include "base/file_path.h"
|
| #include "base/file_util.h"
|
| #include "base/scoped_ptr.h"
|
| +#include "base/time.h"
|
| #include "base/utf_string_conversions.h"
|
| #include "net/base/auth.h"
|
| #include "net/base/capturing_net_log.h"
|
| @@ -8463,4 +8464,250 @@ TEST_F(HttpNetworkTransactionTest, Proxy_ClientAuthCertCache_Fail) {
|
| }
|
| }
|
|
|
| +// Tests that the client certificate used to authenticate with an HTTPS proxy
|
| +// is different than the client certificate used to authenticate to an SSL
|
| +// endpoint, and that the HTTPS proxy certificate will not be sent to the SSL
|
| +// endpoint.
|
| +TEST_F(HttpNetworkTransactionTest, HTTPSProxyAuthAndSSLClientAuth) {
|
| + SessionDependencies session_deps(
|
| + ProxyService::CreateFixed("https://proxy:70"));
|
| + CapturingBoundNetLog log(CapturingNetLog::kUnbounded);
|
| + session_deps.net_log = log.bound().net_log();
|
| +
|
| + // Certificate request from the HTTPS proxy.
|
| + scoped_refptr<SSLCertRequestInfo> proxy_cert_request(
|
| + new SSLCertRequestInfo());
|
| + proxy_cert_request->host_and_port = "proxy:70";
|
| +
|
| + // The client certificate to use when authenticating with the proxy.
|
| + base::Time start_date = base::Time::Now();
|
| + base::Time expiration_date = start_date + base::TimeDelta::FromDays(1);
|
| + scoped_refptr<X509Certificate> proxy_cert(
|
| + new X509Certificate("proxy", "me", start_date, expiration_date));
|
| +
|
| + // Certificate request from the SSL endpoint.
|
| + scoped_refptr<SSLCertRequestInfo> endpoint_cert_request(
|
| + new net::SSLCertRequestInfo());
|
| + endpoint_cert_request->host_and_port = "www.example.com:443";
|
| +
|
| + // The client certificate to use when authenticating with the SSL endpoint.
|
| + scoped_refptr<X509Certificate> endpoint_cert(
|
| + new X509Certificate("endpoint", "you", start_date, expiration_date));
|
| +
|
| + // ssl_data1 is the initial connection to the HTTPS proxy, which is
|
| + // requesting a client certificate. data1 is needed to initialize the
|
| + // underlying transport socket, but no data is exchanged due to the SSL
|
| + // handshake failure.
|
| + SSLSocketDataProvider ssl_data1(true /* async */,
|
| + net::ERR_SSL_CLIENT_AUTH_CERT_NEEDED);
|
| + ssl_data1.cert_request_info = proxy_cert_request.get();
|
| + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl_data1);
|
| +
|
| + StaticSocketDataProvider data1(NULL, 0, NULL, 0);
|
| + session_deps.socket_factory.AddSocketDataProvider(&data1);
|
| +
|
| + // ssl_data2 is the re-connection to the HTTPS proxy, now with a valid
|
| + // client certificate. data2 contains the data exchanged with the proxy to
|
| + // set up the tunnel, and then the following reads/writes contain the data
|
| + // exchanged with the endpoint, which there should be none, due to the
|
| + // endpoint SSL handshake failure.
|
| + SSLSocketDataProvider ssl_data2(true /* async */, net::OK);
|
| + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl_data2);
|
| +
|
| + MockWrite data_writes2[] = {
|
| + MockWrite("CONNECT www.example.com:443 HTTP/1.1\r\n"
|
| + "Host: www.example.com\r\n"
|
| + "Proxy-Connection: keep-alive\r\n\r\n"),
|
| + };
|
| + MockRead data_reads2[] = {
|
| + MockRead("HTTP/1.1 200 Connection Established\r\n\r\n"),
|
| + };
|
| +
|
| + StaticSocketDataProvider data2(data_reads2, arraysize(data_reads2),
|
| + data_writes2, arraysize(data_writes2));
|
| + session_deps.socket_factory.AddSocketDataProvider(&data2);
|
| +
|
| + // ssl_data3 is the initial connection to the SSL endpoint, which is also
|
| + // requesting a client certificate. There is no data3, because ssl_data3's
|
| + // client socket handle comes from the proxy, which is using data2.
|
| + SSLSocketDataProvider ssl_data3(true /* async */,
|
| + net::ERR_SSL_CLIENT_AUTH_CERT_NEEDED);
|
| + ssl_data3.cert_request_info = endpoint_cert_request.get();
|
| + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl_data3);
|
| +
|
| + // ssl_data4 is the reconnection to the HTTPS proxy, after the connection
|
| + // to the SSL endpoint has failed due to requiring a client certificate.
|
| + // data4 contains the data exchanged with the proxy to set up the tunnel,
|
| + // and then the subsequent reads/writes contains the data exchanged with
|
| + // the endpoint.
|
| + SSLSocketDataProvider ssl_data4(true /* async */, net::OK);
|
| + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl_data4);
|
| +
|
| + MockWrite data_writes4[] = {
|
| + MockWrite("CONNECT www.example.com:443 HTTP/1.1\r\n"
|
| + "Host: www.example.com\r\n"
|
| + "Proxy-Connection: keep-alive\r\n\r\n"),
|
| +
|
| + MockWrite("GET / HTTP/1.1\r\n"
|
| + "Host: www.example.com\r\n"
|
| + "Connection: keep-alive\r\n\r\n"),
|
| + };
|
| + MockRead data_reads4[] = {
|
| + MockRead("HTTP/1.1 200 Connection Established\r\n\r\n"),
|
| +
|
| + MockRead("HTTP/1.1 200 OK\r\n\r\n"),
|
| + MockRead("hello, world"),
|
| + MockRead(false, OK),
|
| + };
|
| + StaticSocketDataProvider data4(data_reads4, arraysize(data_reads4),
|
| + data_writes4, arraysize(data_writes4));
|
| + session_deps.socket_factory.AddSocketDataProvider(&data4);
|
| +
|
| + // ssl_data5 is the SSL handshake with the endpoint, after the proxy has
|
| + // been negotiated. There is no data5, because ssl_data5's client socket
|
| + // handle comes from the proxy, which is using data4.
|
| + SSLSocketDataProvider ssl_data5(true /* async */, net::OK);
|
| + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl_data5);
|
| +
|
| + net::HttpRequestInfo request;
|
| + request.url = GURL("https://www.example.com/");
|
| + request.method = "GET";
|
| + request.load_flags = net::LOAD_NORMAL;
|
| +
|
| + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps));
|
| + scoped_ptr<HttpNetworkTransaction> trans(
|
| + new HttpNetworkTransaction(session));
|
| +
|
| + // Make the initial connection to the HTTPS proxy.
|
| + TestCompletionCallback callback;
|
| + int rv = trans->Start(&request, &callback, net::BoundNetLog());
|
| + ASSERT_EQ(net::ERR_IO_PENDING, rv);
|
| + // Finish the SSL handshake to the HTTPS proxy (ssl_data1).
|
| + rv = callback.WaitForResult();
|
| + ASSERT_EQ(net::ERR_SSL_CLIENT_AUTH_CERT_NEEDED, rv);
|
| +
|
| + // Make sure that the certificate request matches that from ssl_data1.
|
| + const HttpResponseInfo* response = trans->GetResponseInfo();
|
| + ASSERT_TRUE(response);
|
| + ASSERT_TRUE(response->cert_request_info);
|
| + ASSERT_EQ("proxy:70", response->cert_request_info->host_and_port);
|
| +
|
| + // Restart the connection to the HTTPS proxy, now with a client
|
| + // certificate (ssl_data2).
|
| + rv = trans->RestartWithCertificate(proxy_cert, &callback);
|
| + ASSERT_EQ(net::ERR_IO_PENDING, rv);
|
| +
|
| + // Make sure that SSL client auth cache is only updated for the proxy and
|
| + // not for the endpoint.
|
| + scoped_refptr<X509Certificate> cached_cert;
|
| + ASSERT_TRUE(session->ssl_client_auth_cache()->Lookup("proxy:70",
|
| + &cached_cert));
|
| + ASSERT_EQ(proxy_cert, cached_cert);
|
| + ASSERT_FALSE(session->ssl_client_auth_cache()->Lookup("www.example.com:443",
|
| + &cached_cert));
|
| +
|
| + // Utility class to interject ourselves into the handshake. |callback|,
|
| + // the TestCompletionCallback, can only wait until the
|
| + // HttpNetworkTransaction callback is invoked. However, on an SSL failure,
|
| + // the MockSSLClientSocket will be destroyed by the ClientSocketHandle,
|
| + // meaning the parameters that were passed in it's construction will no
|
| + // longer be available. This is just a hack-job substitute to demonstrate
|
| + // the problem without having to shave the Mock*Factory yak to allow us to
|
| + // break after creating a socket, and DOES NOT represent code I'd ever
|
| + // check in.
|
| + class WaitForSSLCreationObserver : public MessageLoop::TaskObserver {
|
| + public:
|
| + WaitForSSLCreationObserver(SessionDependencies* session_deps,
|
| + size_t expected_ssl_sockets)
|
| + : session_deps_(session_deps),
|
| + expected_ssl_sockets_(expected_ssl_sockets) {
|
| + }
|
| +
|
| + // TaskObserver
|
| + virtual void WillProcessTask(const Task* task) OVERRIDE {}
|
| + virtual void DidProcessTask(const Task* task) OVERRIDE {
|
| + if (session_deps_->socket_factory.ssl_client_sockets().size() >=
|
| + expected_ssl_sockets_) {
|
| + MessageLoop::current()->QuitNow();
|
| + }
|
| + }
|
| + private:
|
| + SessionDependencies* session_deps_;
|
| + size_t expected_ssl_sockets_;
|
| + };
|
| +
|
| + // Finish the SSL handshake with the HTTPS proxy and begin
|
| + // the SSL handshake with the endpoint (ssl_data3).
|
| + WaitForSSLCreationObserver wait_for_endpoint(&session_deps, 3);
|
| + MessageLoop::current()->AddTaskObserver(&wait_for_endpoint);
|
| + MessageLoop::current()->Run();
|
| + MessageLoop::current()->RemoveTaskObserver(&wait_for_endpoint);
|
| +
|
| + // Make sure that the SSL handshake to the endpoint is NOT set to send the
|
| + // client certificate used for the proxy.
|
| + MockSSLClientSocket* endpoint_socket =
|
| + session_deps.socket_factory.GetMockSSLClientSocket(2);
|
| + ASSERT_TRUE(endpoint_socket);
|
| + ASSERT_EQ("www.example.com:443",
|
| + endpoint_socket->GetHostAndPort().ToString());
|
| + ASSERT_FALSE(endpoint_socket->GetSSLConfig().send_client_cert);
|
| + ASSERT_FALSE(endpoint_socket->GetSSLConfig().client_cert);
|
| +
|
| + rv = callback.WaitForResult();
|
| + ASSERT_EQ(net::ERR_SSL_CLIENT_AUTH_CERT_NEEDED, rv);
|
| +
|
| + // Make sure that the certificate request matches that from ssl_data3.
|
| + response = trans->GetResponseInfo();
|
| + ASSERT_TRUE(response);
|
| + ASSERT_TRUE(response->cert_request_info);
|
| + ASSERT_EQ("www.example.com:443",
|
| + response->cert_request_info->host_and_port);
|
| +
|
| + // Restart the SSL handshake with the endpoint. This will cause ssl_data4
|
| + // to be consumed, because |trans| has to first re-establish a connection
|
| + // with the proxy.
|
| + rv = trans->RestartWithCertificate(endpoint_cert, &callback);
|
| + ASSERT_EQ(net::ERR_IO_PENDING, rv);
|
| +
|
| + // Ensure that the SSL socket for the HTTPS proxy is still configured to
|
| + // use the proxy client certificate, and NOT the client certificate for
|
| + // the endpoint.
|
| + MockSSLClientSocket* proxy_socket =
|
| + session_deps.socket_factory.GetMockSSLClientSocket(3);
|
| + ASSERT_TRUE(proxy_socket);
|
| + ASSERT_EQ("proxy:70", proxy_socket->GetHostAndPort().ToString());
|
| + ASSERT_TRUE(proxy_socket->GetSSLConfig().send_client_cert);
|
| + ASSERT_EQ(proxy_cert, proxy_socket->GetSSLConfig().client_cert);
|
| +
|
| + // Finish the SSL handshake with HTTPS proxy and start the SSL handshake
|
| + // with the endpoint. This will consume ssl_data5.
|
| + WaitForSSLCreationObserver wait_for_endpoint_again(&session_deps, 5);
|
| + MessageLoop::current()->AddTaskObserver(&wait_for_endpoint_again);
|
| + MessageLoop::current()->Run();
|
| + MessageLoop::current()->RemoveTaskObserver(&wait_for_endpoint_again);
|
| +
|
| + // Ensure that the SSL socket for the SSL endpoint is configured to use
|
| + // the endpoint client certificate, and NOT the client certificate for the
|
| + // HTTPS proxy.
|
| + endpoint_socket = session_deps.socket_factory.GetMockSSLClientSocket(4);
|
| + ASSERT_TRUE(endpoint_socket);
|
| + ASSERT_EQ("www.example.com:443",
|
| + endpoint_socket->GetHostAndPort().ToString());
|
| + ASSERT_TRUE(endpoint_socket->GetSSLConfig().send_client_cert);
|
| + ASSERT_EQ(endpoint_cert, endpoint_socket->GetSSLConfig().client_cert);
|
| +
|
| + rv = callback.WaitForResult();
|
| + ASSERT_EQ(net::OK, rv);
|
| +
|
| + // Make sure that the request was consumed from data4.
|
| + response = trans->GetResponseInfo();
|
| + ASSERT_TRUE(response);
|
| + EXPECT_EQ(200, response->headers->response_code());
|
| + EXPECT_EQ(HttpVersion(1, 1), response->headers->GetHttpVersion());
|
| + std::string response_data;
|
| + rv = ReadTransaction(trans.get(), &response_data);
|
| + EXPECT_EQ(net::OK, rv);
|
| + EXPECT_EQ("hello, world", response_data);
|
| +}
|
| +
|
| } // namespace net
|
|
|