okhttp: HTTPS proxy is not supported

What kind of issue is this?

  • Question. This issue tracker is not the place for questions. If you want to ask how to do something, or to understand why something isn’t working the way you expect it to, use Stack Overflow. https://stackoverflow.com/questions/tagged/okhttp

  • Bug report. If you’ve found a bug, spend the time to write a failing test. Bugs with tests get fixed. Here’s an example: https://gist.github.com/swankjesse/981fcae102f513eb13ed

  • Feature Request. Start by telling us what problem you’re trying to solve. Often a solution already exists! Don’t send pull requests to implement new features without first getting our support. Sometimes we leave features out on purpose to keep the project small.

Hello, I tried to use okhttp to connect through an https proxy, but I wasn’t able to. Here is a reproduction:

curl --proxy-insecure -x https://myproxy:3129 https://httpbin.org/get

works like a charm, instead:

OkHttpClient.Builder okHttpClientBuilder = new OkHttpClient().newBuilder();

final Integer proxyPort = 3129;
final Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxyHost, proxyPort));
okHttpClientBuilder.proxy(proxy);
        
okHttpClientBuilder.hostnameVerifier(new HostnameVerifier() {
            @Override
            public boolean verify(String s, SSLSession sslSession) {
                if (proxyHost.equalsIgnoreCase(s)) {
                    return true;
                }
                return OkHostnameVerifier.INSTANCE.verify(s, sslSession);
            }
});
        
okHttpClientBuilder.build().newCall(new Request.Builder().url("https://httpbin.org/get").build()).execute();

fails with the following stacktrace:

Exception in thread "main" java.io.IOException: unexpected end of stream on null
	at okhttp3.internal.http1.Http1Codec.readResponseHeaders(Http1Codec.java:205)
	at okhttp3.internal.connection.RealConnection.createTunnel(RealConnection.java:323)
	at okhttp3.internal.connection.RealConnection.connectTunnel(RealConnection.java:197)
	at okhttp3.internal.connection.RealConnection.connect(RealConnection.java:145)
	at okhttp3.internal.connection.StreamAllocation.findConnection(StreamAllocation.java:192)
	at okhttp3.internal.connection.StreamAllocation.findHealthyConnection(StreamAllocation.java:121)
	at okhttp3.internal.connection.StreamAllocation.newStream(StreamAllocation.java:100)
	at okhttp3.internal.connection.ConnectInterceptor.intercept(ConnectInterceptor.java:42)
	at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:92)
	at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:67)
	at okhttp3.internal.cache.CacheInterceptor.intercept(CacheInterceptor.java:93)
	at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:92)
	at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:67)
	at okhttp3.internal.http.BridgeInterceptor.intercept(BridgeInterceptor.java:93)
	at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:92)
	at okhttp3.internal.http.RetryAndFollowUpInterceptor.intercept(RetryAndFollowUpInterceptor.java:120)
	at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:92)
	at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:67)
	at okhttp3.RealCall.getResponseWithInterceptorChain(RealCall.java:185)
	at okhttp3.RealCall.execute(RealCall.java:69)
	at Test.main(Test.java:34)
Caused by: java.io.EOFException: \n not found: limit=0 content=…
	at okio.RealBufferedSource.readUtf8LineStrict(RealBufferedSource.java:226)
	at okio.RealBufferedSource.readUtf8LineStrict(RealBufferedSource.java:210)
	at okhttp3.internal.http1.Http1Codec.readResponseHeaders(Http1Codec.java:189)
	... 20 more

I digged a bit into the code and I think that the issue is that here, if the proxy is HTTP, it is used socketFactory, while there should be an additional check whether the proxy is running HTTPS or not (and if so, sslSocketFactory should be used instead).

About this issue

  • Original URL
  • State: open
  • Created 6 years ago
  • Reactions: 6
  • Comments: 38 (8 by maintainers)

Most upvoted comments

@lordyavin you can connect to an HTTPS origin resource via an HTTP proxy - which is the normal way to do it, the HTTP proxy becomes a dumb TCP/IP tunnel and the TLS handshake happens between the client and the origin server.

This issue is about connecting to both the origin server and the proxy server over HTTPS. So the data stream ends up double encrypted - you have an encrypted stream between the client and the proxy server, and that is tunnelling an encrypted stream between the client and the origin server.

It’s a pretty unusual thing to want to do - all it really additionally protects is the CONNECT request from the client to the proxy, so it stops a man in the middle knowing which origin server it is you want to proxy to. The CONNECT request could include a Proxy-Authorization header which would be worth protecting.

This might lead you in the right direction:

    OkHttpClient client = new OkHttpClient.Builder()
        .socketFactory(SSLSocketFactory.getDefault())
        .build();

In practice there’s a bunch more work due to features like ALPN and SNI, and having to verify that the proxy server you connect to is the one who’s certificate chain is trusted.

Here what’s happening: HTTP: the client send directly the full request to the proxy, with the proxy-auth headers. The proxy is in charge to forward to server. HTTPS: the client want to send a request to a server, encrypted with the server public key, passing through an http proxy. So before making the request itself, the client have to get the server public key (i.e. make SSL handshake, i.e. etablish a SSL/TLS tunnel to the server) the HTTPS request will be encrypted so the proxy won’t have access to the request headers (in case of a normal proxy) What happens: The client send a HTTP CONNECT to the proxy, indicating the hostname of the server to connect with and also probably the proxy-auth headers The proxy connect the client with the server (here with an internal https server instead of remote one, to be able to decrypt and modify) Client and server (internal one here) makes SSL handshake Client send the HTTPS request to the server, without proxy-auth headers because the proxy is not supposed to have access to it

   private Request createTunnelRequest() {
     return new Request.Builder()
         .url(route.address().url())
         .header("Host", Util.hostHeader(route.address().url(), true))
         .header("Proxy-Connection", "Keep-Alive") // For HTTP/1.0 proxies like Squid.
         .header("User-Agent", Version.userAgent())
         .build();
   }

OKHttp3.9.1,I watch the source code and debug it,I found the createTunnelRequest not add the Proxy-Authorization,so The https request is can not be connected @swankjesse

    private Request createTunnelRequest(Call call) {
         Request.Builder builder = new Request.Builder()
                 .url(route.address().url())
                .header("Host", Util.hostHeader(route.address().url(), true))
                 .header("Proxy-Connection", "Keep-Alive") // For HTTP/1.0 proxies like Squid.
                 .header("User-Agent", Version.userAgent());
         String value = call.request().header("Proxy-Authorization");
         if (value != null) {
             builder.header("Proxy-Authorization", value);
         }
         return builder.build();
     }

it’s worked @mgaido91 i think it’s a bug

@yeyb you can follow @swankjesse 's suggestion. You can find my implementation of his suggestion in nifi here: https://github.com/apache/nifi/commit/37271e82414b9386bb735b61ef54e891300117bf