okhttp icon indicating copy to clipboard operation
okhttp copied to clipboard

HTTPS proxy is not supported

Open mgaido91 opened this issue 6 years ago • 38 comments

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

  • [x] 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

  • [x] 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).

mgaido91 avatar Jan 18 '18 17:01 mgaido91

HTTPS proxies are not supported. I don't think this is a thing. Got a spec for this?

swankjesse avatar Jan 18 '18 17:01 swankjesse

Yes, there are companies using HTTPS proxies. In these environments, this is quite a vital requirement.

mgaido91 avatar Jan 18 '18 17:01 mgaido91

Wow, I've never heard of that. Got a spec? Typically we just follow RFC 2817. https://tools.ietf.org/html/rfc2817

swankjesse avatar Jan 18 '18 17:01 swankjesse

Honestly I don't know if there is a spec and I am not sure where to find it. Here you can find how to set up squid for doing this: http://www.squid-cache.org/Doc/config/https_port/ (of course there are many other proxies supporting this, it is just an example).

mgaido91 avatar Jan 18 '18 22:01 mgaido91

@mgaido91 in that example Squid is not acting as a proxy from the client’s perspective. Try doing the same thing but without a proxy configured.

swankjesse avatar Jan 19 '18 00:01 swankjesse

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

zi6xuan avatar Jan 19 '18 02:01 zi6xuan

@swankjesse what do you mean that it is not acting like a proxy? It is and this is confirmed by the curl command working fine.

mgaido91 avatar Jan 19 '18 07:01 mgaido91

Can you find the spec that curl is following? I don't doubt that curl can do something here but we don't use curl as a spec and this behavior is new to me.

What do Chrome and Firefox do?

swankjesse avatar Jan 19 '18 15:01 swankjesse

https://curl.haxx.se/bug/?i=1127

swankjesse avatar Jan 19 '18 16:01 swankjesse

@swankjesse both Chrome and Firefox support it. Here I found an article about Chromium support (https://www.chromium.org/developers/design-documents/secure-web-proxy). Actually it is often referred as SSL proxy (Firefox terminology) or Secure Web Proxy (OSX terminology).

I have not been able to find any specification and I don't think that there can be, since there is nothing to specify: it is simply an implementation of a HTTP proxy using SSL.

You already found the curl PR for it, so I am not sending you the commit which implements it in curl. As you can see with a quick search with the keywords "SSL Proxy" the support is widely spread.

mgaido91 avatar Jan 19 '18 16:01 mgaido91

@swankjesse anyway here there is a description of a proxy authentication using a SSL proxy: https://tools.ietf.org/html/draft-loreto-httpbis-trusted-proxy20-01#section-3. This shows that IETF considers this use case as valid.

mgaido91 avatar Jan 19 '18 17:01 mgaido91

Got it. Seems like a thing we can do. How do we know whether the caller wants TLS with their proxy connection?

At least for now the easiest way to get this working for yourself is to provide your own socket factory. That will have to do the TLS handshake when connecting the proxy.

swankjesse avatar Jan 20 '18 10:01 swankjesse

I think that the easiest way is to slightly change the .proxy method accepting an enum telling the kind of proxy (HTTP, HTTPS, SOCKS or NONE) and the InetSocketAddress of the proxy. In this way when you create the socket you know how to create it. What do you think?

mgaido91 avatar Jan 20 '18 14:01 mgaido91

We’ve got something similar from Java but it also doesn't know about TLS to the proxy. https://docs.oracle.com/javase/8/docs/api/java/net/Proxy.Type.html

In the interim I recommend doing it yourself with a socket factory.

swankjesse avatar Jan 20 '18 18:01 swankjesse

Thanks for the suggestion. Do you mean passing a socket factory to the OkHttpClient.Builder? May I kindly ask you to show me a fast example? Thank you very much for your answers and your time.

mgaido91 avatar Jan 20 '18 18:01 mgaido91

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.

swankjesse avatar Feb 11 '18 17:02 swankjesse

I find the same problem now ,okhttp Proxy.Type.HTTP don't support https request ,http request is fine. is any solution for this problem?

yeyb avatar Jul 12 '18 09:07 yeyb

anybody can help me ?okhttp https httpProxy

yeyb avatar Jul 12 '18 09:07 yeyb

@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

mgaido91 avatar Jul 12 '18 09:07 mgaido91

okhttp Proxy.Type.HTTP don't support https request ? follow is my code,https request don't work

sslSocketFactory = HttpsUtils.setCertificates(new Buffer().writeUtf8(HttpsCert.NEW_CER_IBU).inputStream()); OkHttpClient okHttpClient = new OkHttpClient.Builder() .connectTimeout(10000L, TimeUnit.MILLISECONDS) .readTimeout(10000L, TimeUnit.MILLISECONDS) .addInterceptor(new LoggerInterceptor("TAG", BuildConfig.BUILD_TYPE.equals("debug"))) /////////////////////////////////////////////////////////////////////////////////////////////// //添加代理 .addNetworkInterceptor(new MyNetworkInterceptor()) //add proxy .proxy(new Proxy(Proxy.Type.HTTP, new InetSocketAddress("", ))) // .proxySelector(new MyProxySelector()) /////////////////////////////////////////////////////////////////////////////////////////////// //https证书 .hostnameVerifier(new HostnameVerifier() { @Override public boolean verify(String hostname, SSLSession session) { return true; } }) .sslSocketFactory(sslSocketFactory) /////////////////////////////////////////////////////////////////////////////////////////////// .build(); OkHttpUtils.initClient(okHttpClient);

yeyb avatar Jul 12 '18 09:07 yeyb

@mgaido91 how to call the method in your sample?

yeyb avatar Jul 12 '18 09:07 yeyb

which method?

mgaido91 avatar Jul 12 '18 09:07 mgaido91

when I set cer ,call which method?

yeyb avatar Jul 12 '18 09:07 yeyb

@yeyb I don't understand your question anyway the answer to your problem is: if you want to support HTTPS proxy, use ProxyType.HTTP and set the socketFactory to a SSLSocketFactory

mgaido91 avatar Jul 12 '18 09:07 mgaido91

@mgaido91 yes this is my problem

yeyb avatar Jul 12 '18 10:07 yeyb

I am android develper,and I don't konw how to set socketFactory to a SSLSocketFactory in your suggestion

yeyb avatar Jul 12 '18 10:07 yeyb

@mgaido91 I am a chinese ,thank you for your help!!!

yeyb avatar Jul 12 '18 10:07 yeyb

@yeyb I am not sure which is the problem you are having, but the example by @swankjesse seems very clear to me, I don't know what else to say...

mgaido91 avatar Jul 12 '18 10:07 mgaido91

@mgaido91 ,my problem is what you say.but as you say,if I want to support HTTPS proxy, use ProxyType.HTTP and set the socketFactory to a SSLSocketFactory , how I make a socketFactory ? my code is set the SSLSocketFactoryto a SSLSocketFactory

yeyb avatar Jul 12 '18 10:07 yeyb

@mgaido91 do I need to call okhttpClient.builder..proxy(new Proxy(Proxy.Type.HTTP, new InetSocketAddress("host", port)))?

yeyb avatar Jul 12 '18 10:07 yeyb