I'm using the Apache HTTP client (version 4.5.13) in Java 8 to perform a POST call that requires the client to authenticate using a certificate certificate, that I have stored in a .PFX file.
This is the code I'm using:
public static void performClientRequest() throws Exception {
//Trust Strategy to accept any server certificate
TrustStrategy trustStrategy = new TrustStrategy() {
public boolean isTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
return true;
}
};
//Load PFX client certificate
KeyStore clientStore = KeyStore.getInstance("PKCS12");
InputStream instream = new FileInputStream("C:\\client.pfx");
try {
clientStore.load(instream, null);
} finally {
instream.close();
}
//Create ssl context with key store and trust strategy
SSLContext sslContext = SSLContexts.custom()
.loadKeyMaterial(clientStore, null)
.loadTrustMaterial(trustStrategy)
.build();
//Create ssl socket factory from context
SSLConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE);
//Create HTTP client
HttpClient httpClient = HttpClients.custom()
.setSSLSocketFactory(sslSocketFactory)
.build();
//Perform call
URI url = new URI("https://mysite.foo");
HttpPost request = new HttpPost(url);
request.setHeader("Content-Type","application/json");
request.setHeader("Accept", "application/json");
String body = "...";
StringEntity bodyEntity = new StringEntity(body);
request.setEntity(bodyEntity);
HttpResponse response = httpClient.execute(request);
HttpEntity entity = response.getEntity();
System.out.println("----------------------------------------");
System.out.println(response.getStatusLine());
EntityUtils.consume(entity);
}
I've used this code in in the past and it worked perfectly back then, but now I'm trying to reuse it and it just doesn't send the certificate, the server replies with:
HTTP/1.1 403 No client certificate supplied
How can I debug this and discover why the certificate is not being sent?
Note: I implemented a similar call both in C# and using Postman, and in both cases it works perfectly, so the client certificate authentication to the server is working, it's just not working in my Java implementation.
So, I don't know if this is a bug or intended behavior (if so, why?), but apparently the PFX file must be password-protected, then it gets sent correctly. I could not make this work with a non-protected PFX file and passing null as the password like I was doing in the code I posted in the question.
So the problem is solved, but I would be curious if anyone could comment on WHY this happens.
Related
I am building an adapter application that should sent the data to an external service using HTTPS. While testing locally (against a mock server), I am saving the certificate in a local truststore and loading it using loadTrustMaterial(file, password) method. The difference in productive environment is that the application is running in OpenShift and the certificate is saved in ConfigMap.
Question:
How can I retrieve that certificate from ConfigMap in OpenShift (is there any url to the cert?), so my application can call external service?
Here are some code snippets for clarity:
private HttpResponse connectToExternalService(String xml) {
StringEntity stringEntity = null;
try {
stringEntity = new StringEntity(xml);
SSLContext sslContext = this.createSSLContext();
SSLConnectionSocketFactory factory = new SSLConnectionSocketFactory(sslContext);
CloseableHttpClient client = HttpClients.custom().setSSLSocketFactory(factory).build();
HttpPost post = new HttpPost(url);
post.setEntity(stringEntity);
post.setHeader("Content-Type", "text/xml");
return client.execute(post);
} catch (IOException e) {
}
}
private SSLContext createSSLContext() {
File file = new File("path to the truststore");
try {
char[] password = "password".toCharArray();
return SSLContexts.custom().loadTrustMaterial(file, password).build();
} catch (NoSuchAlgorithmException | KeyManagementException | KeyStoreException e) {
}
}
As config map being used to hold the certificate, the same can be mounted as file inside the container. Please refer k8s documentation to load as file. Post that add it trust store during container start.
We have a generic application which delivers message to different POST endpoints. And we are using
CloseableHttpAsyncClient for this purpose. Its been built/initialized as follows,
private static CloseableHttpAsyncClient get() {
CloseableHttpAsyncClient lInstance;
IOReactorConfig ioReactorConfig = IOReactorConfig.custom()
.setIoThreadCount(100)
.setConnectTimeout(10000)
.setSoTimeout(10000).build();
ConnectingIOReactor ioReactor = null;
try {
ioReactor = new DefaultConnectingIOReactor(ioReactorConfig);
} catch (IOReactorException e) {
logger_.logIfEnabled(Level.ERROR, e);
}
PoolingNHttpClientConnectionManager connManager = new PoolingNHttpClientConnectionManager(ioReactor);
connManager.setDefaultMaxPerRoute(50);
connManager.setMaxTotal(5000);
connManager.closeIdleConnections(10000, TimeUnit.MILLISECONDS);
baseRequestConfig = RequestConfig.custom().setConnectTimeout(10000)
.setConnectionRequestTimeout(10000)
.setSocketTimeout(10000).build();
lInstance = HttpAsyncClients.custom().setDefaultRequestConfig(baseRequestConfig)
.setConnectionManager(connManager).build();
lInstance.start();
return lInstance;
}
This is prebuilt and initialized. As an when a new request arrives to our application, based on message, authentication type, a new postRequest is built httpPost = new HttpPost(builder.build());
After setting the required header, payload etc. exiting httpClient is used to send the request.
httpClient.execute(httpPost, httpContext, null);
Now, the question is based on the our new requirement to support client certificate based authentication. And since our current approach is to create httpClient in the beginning, the question is how to change the behaviour of httpClient to send client certificate to some endpoints and work as it is for other endpoints which doesn't require certificate to be send?
I know I can introduce SSLContext to CloseableHttpAsyncClient while creating, but at the time of creating I don't have any information that we have any endpoint which requires certificate based authentication. And we can have many endpoints which would be supporting client certificate and that would be known at runtime.
I woudlike to execute GET request :
HttpGet request = new HttpGet(URL_SECURED_BY_BASIC_AUTHENTICATION);
URIBuilder uriBuilder = new URIBuilder(request.getURI())
.addParameter("code", "001")
.addParameter("name", "AAA")
String auth = user + ":" + mdp;
byte[] encodedAuth = Base64.encodeBase64(
auth.getBytes(StandardCharsets.ISO_8859_1));
String authHeader = "Basic " + new String(encodedAuth);
request.setHeader(HttpHeaders.AUTHORIZATION, authHeader);
HttpClient client = HttpClientBuilder.create().build();
HttpResponse response = client.execute((HttpUriRequest) uriBuilder);
int statusCode = response.getStatusLine().getStatusCode();
When I try to catch my HTTPResponse client.execute((HttpUriRequest) uriBuilder);
I have this error :
java.lang.ClassCastException: class org.apache.http.client.utils.URIBuilder cannot be cast to class org.apache.http.client.methods.HttpUriRequest (org.apache.http.client.utils.URIBuilder and org.apache.http.client.methods.HttpUriRequest are in unnamed module of loader 'app')
You are facing the error because you are trying to cast an instance of URIBuilder to HttpUriRequest.
You need to create an appropriate HttpUriRequest implementation in order to execute your HTTP request.
In your use case I suppose it should looks like this:
URIBuilder uriBuilder = new URIBuilder(URL_SECURED_BY_BASIC_AUTHENTICATION)
.addParameter("code", "001")
.addParameter("name", "AAA");
URI uri = uriBuilder.build();
HttpGet request = new HttpGet(uri);
String auth = user + ":" + mdp;
byte[] encodedAuth = Base64.encodeBase64(
auth.getBytes(StandardCharsets.ISO_8859_1));
String authHeader = "Basic " + new String(encodedAuth);
request.setHeader(HttpHeaders.AUTHORIZATION, authHeader);
HttpClient client = HttpClientBuilder.create().build();
HttpResponse response = client.execute(request);
int statusCode = response.getStatusLine().getStatusCode();
It seems for your comment that you are trying to connect to a site using SSL: the problem is that your Java code does not trust the server.
You need to configure a valid certificate chain and instruct your code to use it in order to solve the problem.
Apache Client does not rely on the standard JSSE mechanism for this purpose. Instead, you need to configure a SSLContext with an appropriate TrustManager. Please, see the following code (the first part is derived from this extraordinary documentation fragment in the Android developer site):
// Load your server certificate
CertificateFactory cf = CertificateFactory.getInstance("X.509");
InputStream input = new BufferedInputStream(
new FileInputStream("server.crt")
);
Certificate certificate;
try {
certificate = cf.generateCertificate(input);
} finally {
input.close();
}
// Create an in-memory KeyStore containing the server certificate
// It is required in order to configure the TrustManager
String keyStoreType = KeyStore.getDefaultType(); // JKS
KeyStore keyStore = KeyStore.getInstance(keyStoreType);
keyStore.load(null, null);
keyStore.setCertificateEntry("cert", certificate);
// Create a TrustManager that trusts the server certificates in the KeyStore
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(
TrustManagerFactory.getDefaultAlgorithm()
);
trustManagerFactory.init(keyStore);
// Create an SSLContext that uses our TrustManager
SSLContext context = SSLContext.getInstance("TLS");
context.init(null, trustManagerFactory.getTrustManagers(), null);
// Now, the actual Apache Client part
//Create a SSLConnectionSocketFactory and pass it the above created SSLContext
SSLConnectionSocketFactory factory =
new SSLConnectionSocketFactory(sslcontext, new NoopHostnameVerifier()
);
//Create the actual HttpClient
CloseableHttpClient client = HttpClients
.custom()
.setSSLSocketFactory(factory)
.build()
;
// Use this client to perform your HTTP invocation
HttpResponse response = client.execute(request);
int statusCode = response.getStatusLine().getStatusCode();
You can obtain the server certificate from your browser or with tools like openssl. Please, see this great SO question.
This is the exception that I'm having
Host name 'bla bla bla.com' does not match the certificate subject provided by the peer (CN=*.bla bla bla.com, OU=PositiveSSL Wildcard, OU=Domain Control Validated)
I already saw this question:
Ignoring SSL certificate in Apache HttpClient 4.3
and I did as it suggests, but it didn't work. I have seen many question related to the problem but they are all deprecated.
This is my code:
SSLContextBuilder builder = new SSLContextBuilder();
builder.loadTrustMaterial(null, new TrustSelfSignedStrategy());
SSLConnectionSocketFactory sslsf =
new SSLConnectionSocketFactory(builder.build());
CloseableHttpClient httpclient =
HttpClients.custom().setSSLSocketFactory(sslsf).build();
HttpGet httpGet = new HttpGet("https://b.blablabla.com");
CloseableHttpResponse response1 = httpclient.execute(httpGet);
try {
System.out.println(response1.getStatusLine());
HttpEntity entity1 = response1.getEntity();
// do something useful with the response body
// and ensure it is fully consumed
EntityUtils.consume(entity1);
} finally {
response1.close();
}
How can I bypass this certificate thing? This is just for testing; it is not a real production environment.
Beside localhost you can add own custom hostnames to your development-machine. Use the C:/windows/system32/etc/hosts to add the hostname anjadavid.blablabla.com in example.
Now open your browser and go to https://anjadavid.blablabla.com and the error disappears.
I'm trying to send a Https Post request to a server that is using a self signed certificate and i'm receving an exception with the error: peer not authenticated.
I googled and found that the cause of the problem is that the server is using a self signed ceritficate. how can I supress this error ?
I'm using the following function to send the post request:
public String sendPost(final String request, final String postData) throws ClientProtocolException, IOException {
String result = null;
CloseableHttpClient httpclient = HttpClients.createDefault();
HttpPost httpPost = new HttpPost(request);
ByteArrayEntity postDataEntity = new ByteArrayEntity(postData.getBytes());
httpPost.setEntity(postDataEntity);
CloseableHttpResponse response = httpclient.execute(httpPost);
try {
HttpEntity entity = response.getEntity();
result = EntityUtils.toString(entity);
EntityUtils.consume(entity);
} finally {
response.close();
}
return result;
}
what am I missing to supress this error ? I don't want to try and catch this exception.
I want to configure it properly so self signed certificate will be accepted. I'm using
Httpclient 4.1.
thank you!
Many answers that you will find to this question on the web (including ufk's answer) will work, but are not at all secure, because they completely ignore the self-signed server certificate.
This removes much of the benefit of an SSL connection, and opens you up to a man-in-the-middle attack.
What you probably want to do instead is to trust a specific self-signed server certificate, rather than blindly accepting any server certificate.
The key to this is putting a copy of the server's certificate chain into the trust store when creating the SSL context.
The code for doing this is a bit too long to post here, but as it happens, I'm currently working on a blog post about doing this on Android. The blog post isn't published yet, but the sample code is available on GitHub.
public String sendPost(final String request, final String postData) throws ClientProtocolException, IOException, NoSuchAlgorithmException, KeyManagementException {
String result = null;
SSLContext sslContext = SSLContext.getInstance("SSL");
// set up a TrustManager that trusts everything
sslContext.init(null, new TrustManager[] { new X509TrustManager() {
public X509Certificate[] getAcceptedIssuers() {
System.out.println("getAcceptedIssuers =============");
return null;
}
public void checkClientTrusted(X509Certificate[] certs,
String authType) {
System.out.println("checkClientTrusted =============");
}
public void checkServerTrusted(X509Certificate[] certs,
String authType) {
System.out.println("checkServerTrusted =============");
}
} }, new SecureRandom());
CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(new SSLSocketFactory(sslContext)).build();
HttpPost httpPost = new HttpPost(request);
ByteArrayEntity postDataEntity = new ByteArrayEntity(postData.getBytes());
httpPost.setEntity(postDataEntity);
CloseableHttpResponse response = httpclient.execute(httpPost);
try {
HttpEntity entity = response.getEntity();
result = EntityUtils.toString(entity);
EntityUtils.consume(entity);
} finally {
response.close();
}
return result;
}