Apache HttpClient 5.0 HTTPS proxy: "No tunnel unless connected" - java

I am using the latest version of JDK8
My CloseableHttpAsyncClient is created by doing the following
try{
sslContext = SSLContexts.custom()
.setProvider(Conscrypt.newProvider())
.build();
}catch (Exception e){
e.printStackTrace();
}
final PoolingAsyncClientConnectionManager cm = PoolingAsyncClientConnectionManagerBuilder.create()
.setTlsStrategy(new ConscryptClientTlsStrategy(sslContext))
.build();
BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider();
if (proxy.isAuth()) {
credentialsProvider.setCredentials(
new AuthScope(proxy.getIp(), Integer.parseInt(proxy.getPort())),
new UsernamePasswordCredentials(proxy.getUsername(), proxy.getPassword().toCharArray()));
}
HttpHost p = new HttpHost(proxy.getIp(), Integer.parseInt(proxy.getPort()), "http");
asyncClient = HttpAsyncClients.custom()
.setVersionPolicy(HttpVersionPolicy.FORCE_HTTP_1)
.setConnectionManager(cm)
.setUserAgent(Utils.USER_AGENT)
.setDefaultCookieStore(cookieStore)
.setDefaultCredentialsProvider(credentialsProvider)
.setProxy(p)
.build();
asyncClient.start();
I am then trying to do a post request which works without a proxy. The request with the proxy also works with the url using http and not https. I receive the following error java.lang.IllegalStateException: No tunnel unless connected when using https and a proxy.

Related

Springboot resttemplate with ssl configuration via proxy

I have a requirement to use springboot rest template which calls 3rd party and connects over 2-way ssl but it should go through the proxy, but I am getting "
Encountered connectivity issue while reaching APIsun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
" exception. This is very generic exception. Keystore and certificate are accessible. Without proxy I can able to call same 3rd party API with same set of certificates in different environement. So no issue with certs and location.
Looks like proxy is not able to forward/find certificates to server. Anyone knows how to solve this? following is the code for creating rest template.
HttpClientBuilder httpClientBuilder = null;
if(proxyEnabled){
httpClientBuilder = getHttpClientBuilderWithProxy();
} else{
httpClientBuilder = getHttpClientBuilderWithoutProxy();
}
CloseableHttpClient client = null;
if(isSslEnabled){
logger.info("SSL enabled for closable http client");
SSLConnectionSocketFactory csf = new SSLConnectionSocketFactory( new SSLContextBuilder()
.loadKeyMaterial( ResourceUtils.getFile(keyStore) , keyStorePassword.toCharArray(), keyStorePassword.toCharArray())
.loadTrustMaterial(ResourceUtils.getFile(trustStore), trustStorePassword.toCharArray()) .build());
client = httpClientBuilder
.setSSLSocketFactory(csf)
.setProxyAuthenticationStrategy(new ProxyAuthenticationStrategy())
.build();
} else{
logger.info("SSL disabled for closable http client");
client = httpClientBuilder
.build();
}
clientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory(client);
private HttpClientBuilder getHttpClientBuilderWithoutProxy(){
return HttpClientBuilder.create()
.disableAutomaticRetries();
}
private HttpClientBuilder getHttpClientBuilderWithProxy(){
HttpHost proxy = new HttpHost(httpProxyHost, httpProxyPort);
return HttpClientBuilder.create()
.setProxy(proxy)
.disableAutomaticRetries();
}
I am expecting it to call 3rd party API with proxy and ssl.

How to use HttpClientBuilder with Http proxy?

I am trying to set proxy for a request I am making using HttpClientBuilder as follows:
CredentialsProvider credsProvider = new BasicCredentialsProvider();
UsernamePasswordCredentials usernamePasswordCredentials = new UsernamePasswordCredentials(proxyUser, proxyPassword);
credsProvider.setCredentials(new AuthScope(proxyHost, proxyPort), usernamePasswordCredentials);
builder.useSystemProperties();
builder.setProxy(new HttpHost(proxyHost, proxyPort));
builder.setDefaultCredentialsProvider(credsProvider);
builder.setProxyAuthenticationStrategy(new ProxyAuthenticationStrategy());
where builder is:
HttpClientBuilder builder = HttpClientBuilder.create();
However, I get this exception when I execute this request:
java.lang.RuntimeException: org.apache.http.conn.UnsupportedSchemeException: http protocol is not supported
Caused by: org.apache.http.conn.UnsupportedSchemeException: http protocol is not supported
at org.apache.http.impl.conn.DefaultHttpClientConnectionOperator.connect(DefaultHttpClientConnectionOperator.java:108) ~[httpclient-4.5.1.jar:4.5.1]
at org.apache.http.impl.conn.BasicHttpClientConnectionManager.connect(BasicHttpClientConnectionManager.java:338) ~[httpclient-4.5.1.jar:4.5.1]
at org.apache.http.impl.execchain.MainClientExec.establishRoute(MainClientExec.java:388) ~[httpclient-4.5.1.jar:4.5.1]
at org.apache.http.impl.execchain.MainClientExec.execute(MainClientExec.java:236) ~[httpclient-4.5.1.jar:4.5.1]
at org.apache.http.impl.execchain.ProtocolExec.execute(ProtocolExec.java:184) ~[httpclient-4.5.1.jar:4.5.1]
at org.apache.http.impl.execchain.RetryExec.execute(RetryExec.java:88) ~[httpclient-4.5.1.jar:4.5.1]
at org.apache.http.impl.execchain.RedirectExec.execute(RedirectExec.java:110) ~[httpclient-4.5.1.jar:4.5.1]
at org.apache.http.impl.client.InternalHttpClient.doExecute(InternalHttpClient.java:184) ~[httpclient-4.5.1.jar:4.5.1]
at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:82) ~[httpclient-4.5.1.jar:4.5.1]
at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:107) ~[httpclient-4.5.1.jar:4.5.1]
at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:55) ~[httpclient-4.5.1.jar:4.5.1]
(exception shortened for brevity)
Since this is an HTTP proxy, I don't want to change the scheme to HTTPS, which anyways won't work. How do I get this working?
java.lang.RuntimeException:
org.apache.http.conn.UnsupportedSchemeException: http protocol is not
supported
Why this problem occurs?
Ans: This actually happens because you forget to register a connection socket factory for the 'http' scheme.
Plain 'http' scheme must be used to establish an intermediate connection
to the proxy itself before 'https' tunneling could be employed.
For operational purpose, you can try this code:
CloseableHttpClient client = HttpClients.custom()
.setRoutePlanner(new
SystemDefaultRoutePlanner(ProxySelector.getDefault()))
.build();
I would also suggest simple code for your research. Hope it can save you.
ClientExecuteProxy.java
package org.apache.http.examples.client;
import org.apache.http.HttpHost;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
/**
* How to send a request via proxy.
*
* #since 4.0
*/
public class ClientExecuteProxy {
public static void main(String[] args)throws Exception {
CloseableHttpClient httpclient = HttpClients.createDefault();
try {
HttpHost target = new HttpHost("httpbin.org", 443, "https");
HttpHost proxy = new HttpHost("127.0.0.1", 8080, "http");
RequestConfig config = RequestConfig.custom()
.setProxy(proxy)
.build();
HttpGet request = new HttpGet("/");
request.setConfig(config);
System.out.println("Executing request " + request.getRequestLine() + " to " + target + " via " + proxy);
CloseableHttpResponse response = httpclient.execute(target, request);
try {
System.out.println("----------------------------------------");
System.out.println(response.getStatusLine());
System.out.println(EntityUtils.toString(response.getEntity()));
} finally {
response.close();
}
} finally {
httpclient.close();
}
}
}
Are you using using CloudantClient java API for Cloudant DB?
Ans:
If YES, then It turned out the issue with HTTP when setting a proxy was a bug at our end (sorry about that). We released 1.2.1 with the fix for this problem. You can download jar file from here. (Collected from mike-rhodes's answer)
UPDATE
How do I specify the credentials for the proxy here?
From HTTP authentication,
By default, httpclient will not provide credentials preemptively, it will first create a HTTP request without authentication parameters. This is by design, as a security precaution, and as part of the spec. But, this causes issues if you don't retry the connection, or wherever you're connecting to expects you to send authentication details on the first connection. It also causes extra latency to a request, as you need to make multiple calls, and causes 401s to appear in the logs.
The workaround is to use an authentication cache to pretend that you've already connected to the server once. This means you'll only make one HTTP call.
CloseableHttpClient httpclient = HttpClientBuilder.create().build();
HttpHost targetHost = new HttpHost("localhost", 80, "http");
CredentialsProvider credsProvider = new BasicCredentialsProvider();
credsProvider.setCredentials(
new AuthScope(targetHost.getHostName(), targetHost.getPort()),
new UsernamePasswordCredentials("username", "password"));
// Create AuthCache instance
AuthCache authCache = new BasicAuthCache();
// Generate BASIC scheme object and add it to the local auth cache
BasicScheme basicAuth = new BasicScheme();
authCache.put(targetHost, basicAuth);
// Add AuthCache to the execution context
HttpClientContext context = HttpClientContext.create();
context.setCredentialsProvider(credsProvider);
context.setAuthCache(authCache);
HttpGet httpget = new HttpGet("/");
for (int i = 0; i < 3; i++) {
CloseableHttpResponse response = httpclient.execute(
targetHost, httpget, context);
try {
HttpEntity entity = response.getEntity();
} finally {
response.close();
}
}
N.B: You need to trust the host you're connecting to, and if you're
using HTTP, your username and password will be sent in cleartext
(well, base64, but that doesn't count).
You should also be using a much more specific Authscope rather than
relying on AuthScope.ANY_HOST and AuthScope.ANY_PORT like in your
example.
Credit goes to Cetra
Related Links:
HttpClientBuilder basic auth
Apache HttpClient 4.1 - Proxy Authentication
What you have should be very close to working. I would make the following simple changes:
builder.useSystemProperties();
Delete the call to useSystemProperties. It isn't documented well, but when you set the Proxy (as you do in the next line), it overrides this, so just remove that line.
builder.setProxy(new HttpHost(proxyHost, proxyPort));
Call the HttpHost constructor with the explicit 'scheme' parameter. This is where you are getting the error, so make it explicit:
String proxyScheme = "http";
builder.setProxy(new HttpHost(proxyHost, proxyPort, proxyScheme));
Note: you did not say, but based on the usage of "BasicCredentialsProvider", this is only giving you "Basic" authentication. Basic is only encoded and is not really secure. For Digest or NTLM or Kerberos you will need different code.
I think the problem is with your HttpClient, not the proxy. Did you try to create your HttpClient by using HttpClientBuilder.build()
HttpClient client = builder.build();
ChallengeState.PROXY would provide proxy-authorization header.
However since v4.3, the code is deprecated. It still works in v4.5.
HttpHost proxyHost = this.getProxyHttpHost(config);
authCache.put(proxyHost, new BasicScheme(ChallengeState.PROXY));
Another way to have proxy-authorization header
credsProvider.setCredentials(new AuthScope("127.0.0.1","8080"),
new UsernamePasswordCredentials("username", "password"));
builder.setProxyAuthenticationStrategy(new ProxyAuthenticationStrategy());
builder.setDefaultCredentialsProvider(credsProvider);

HttpGet working in SE application but not in Container

My test application works when running it from a SE application but when using the same code in a web based application it refuses to work.
example code:
SSLContextBuilder builder = new SSLContextBuilder();
builder.loadTrustMaterial(null, new TrustSelfSignedStrategy());
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(builder.build());
HttpHost target = new HttpHost("test.test.com", 443, "https");
HttpHost proxy = new HttpHost("41.41.41.41", 3128, "http");
final RequestConfig requestConfig = RequestConfig.custom()
.setConnectTimeout(5000)
.setConnectionRequestTimeout(5000)
.setSocketTimeout(5000).setProxy(proxy)
.build();
CloseableHttpClient httpclient = HttpClients.custom()
.setSSLSocketFactory(sslsf)
.setDefaultRequestConfig(requestConfig)
.build();
HttpGet httpGet = new HttpGet(validateUrl);
httpGet.setHeader("Authorization", "Basic " + authenticationStringEncrypted);
CloseableHttpResponse response = httpclient.execute(target, httpGet);
In the web application it gets to this part:
CloseableHttpClient httpclient = HttpClients.custom()
.setSSLSocketFactory(sslsf)
.setDefaultRequestConfig(requestConfig)
.build();
Then it stops, no exceptions are thrown and this block is surrounded with a try-catch, using multicatch to catch all exceptions.
} catch (NoSuchAlgorithmException | KeyManagementException | KeyStoreException | IOException ex) {
ex.printStackTrace();
}
The web based application is deployed to glassfish 4.1.

401 unauthorized with SSL and HttpClient library

When i try to login app with ssl
SSLContext sslContext = SSLContexts.custom().loadTrustMaterial(null, new TrustSelfSignedStrategy())
.build();
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext, new String[]{"TLSv1"},
null, SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
HttpPost httppost = new HttpPost("https://otherwebapp.my.com/login");
BasicCredentialsProvider provider = new BasicCredentialsProvider();
provider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials("admin", "admin"));
CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(sslsf).setDefaultCredentialsProvider(provider).build();
CloseableHttpResponse response = httpclient.execute(httppost);
I get 401 Unauthorized
When i login by POST in Chrome Advanced Rest Client view
everything works well.
I was looking differences through Wireshark monitor traffic
and not found difference between sending of my code and of chrome Advanced Rest Client view
If someone has ideas, plz help me

Apache HttpAsyncClient

I am testing the Apache HttpAsyncClient, in particular I want to make an asynchronous HTTP POST Request where authentication is needed. I use this example as reference. So far I found out how to set Application type and body but can't find out how to set the credentials.
I try to add Authentication credentials with
HttpAsyncClientBuilder create = HttpAsyncClientBuilder.create();
create.setTargetAuthenticationStrategy(new TargetAuthenticationStrategy());
BasicCredentialsProvider basicCredentialsProvider = new BasicCredentialsProvider();
Credentials defaultcreds = new UsernamePasswordCredentials("user", "password");
basicCredentialsProvider.setCredentials(new AuthScope("http://localhost", 7351), defaultcreds);
create.setDefaultCredentialsProvider(basicCredentialsProvider);
final CloseableHttpAsyncClient httpclient = create.build();
httpclient.start();
...
But I always get
Sep 11, 2013 4:21:35 PM org.apache.http.impl.auth.HttpAuthenticator handleAuthChallenge
WARNING: Malformed challenge: Authentication challenge is empty
I have not found an example which explains how to set authentication data for the CloseableHttpAsyncClient. Anyone can help me out?
You can set a credentials provider either at the client level if you want it to be shared by all requests by default
CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
CloseableHttpAsyncClient httpclient = HttpAsyncClients.custom()
.setDefaultCredentialsProvider(credentialsProvider)
.build();
httpclient.start();
try {
HttpGet request = new HttpGet("http://www.apache.org/");
Future<HttpResponse> future = httpclient.execute(request, null);
HttpResponse response = future.get();
System.out.println("Response: " + response.getStatusLine());
System.out.println("Shutting down");
} finally {
httpclient.close();
}
System.out.println("Done");
or set it at the request level, if you want it to apply to a particular request only
CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
CloseableHttpAsyncClient httpclient = HttpAsyncClients.createDefault();
httpclient.start();
try {
HttpGet request = new HttpGet("http://www.apache.org/");
HttpClientContext context = HttpClientContext.create();
context.setCredentialsProvider(credentialsProvider);
Future<HttpResponse> future = httpclient.execute(request, context, null);
HttpResponse response = future.get();
System.out.println("Response: " + response.getStatusLine());
System.out.println("Shutting down");
} finally {
httpclient.close();
}
System.out.println("Done");
Please also note that Malformed challenge: Authentication challenge is empty warning is likely caused by the server sending a malformed (empty) auth challenge rather than HttpClient configuration. Providing user credentials for the request may not necessarily resolve the issue.

Categories