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 |