I am trying to perform Mutual Authentication using java. I have tried with curl and it is working with the following commands:
curl --cert-type P12 --cert my-p12-file.p12:password https://server-url --header "Content-Type:application/json" --data '{"key":"value"}'
or
curl --cert client.crt --key my-key.pem --cacert RootCA.cer https://server-url --header "Content-Type:application/json" --data '{"key":"value"}'
Both commands are working fine and giving the proper output. I have tried the same functionality in Postman and it is working there as well. When I tried it programmatically it is working in nodejs but not in Java. I have followed many blogs/tutorials/SO answers but nothing has worked. Here is the Java code:
import org.apache.http.Header;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicHeader;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.util.EntityUtils;
import javax.net.ssl.SSLContext;
import java.io.File;
public class DemoSO {
private static CloseableHttpClient httpClient;
public static void main(String[] args) throws Exception {
System.setProperty("javax.net.debug", "ssl");
String trustStore = "root-certificate-truststore.jks";
String keyStore = "keystore-v0.jks";
String extraPwd = "password";
SSLContext sslContext = SSLContexts.custom().
loadKeyMaterial(new File(keyStore),extraPwd.toCharArray(),extraPwd.toCharArray())
.loadTrustMaterial(new File(trustStore))
.build();
SSLConnectionSocketFactory sslConSocFactory = new SSLConnectionSocketFactory(sslContext, new NoopHostnameVerifier());
httpClient = HttpClients.custom().setSSLSocketFactory(sslConSocFactory)
.setSSLContext(sslContext)
.build();
HttpPost httpPost = new HttpPost("https://server-url");
String requestBody = "{\"key\":\"value\"}";
StringEntity entity = new StringEntity(requestBody);
Header header = new BasicHeader("Content-Type", "application/json");
entity.setContentType(header);
httpPost.setHeader(header);
httpPost.setEntity(entity);
HttpResponse response = httpClient.execute(httpPost);
for (Header allHeader : response.getAllHeaders()) {
System.out.println(allHeader.toString());
}
System.out.println(response.getAllHeaders().toString());
System.out.println(response.getStatusLine());
System.out.println(EntityUtils.toString(response.getEntity()));
}
}
The above code sends a POST request to https://server-url. The trustStore was generated from the Root certificate using the following commands:
openssl x509 -inform der -in RootCA.cer -outform pem -out root-certificate.pem
(Converted the .cer to .pem)
keytool -import -file root-certificate.pem -alias root-certificate -keystore root-certificate-truststore.jks
The keyStore was generated using the .p12 file, which was working fine through openssl, postman and node program.
keytool -importkeystore -srckeystore my-p12-file.p12 -destkeystore keystore-v0.jks -srcstoretype PKCS12 -deststoretype jks -deststorepass password
Any help or suggestion to implement in java will be appreciated.
Thanks.
[Edit1]
Forgot to add the response. It is giving 401 Unauthorized. This is the same error it was giving previously in cURL and postman , but when I passed the .p12 file it started working properly and giving 200 in the response code.
<ErrorCode>401-Unauthorized</ErrorCode>
<ErrorMessageCode>3103</ErrorMessageCode>
<ErrorMessage>Not enough authentication factor, 1 is required!</ErrorMessage>
Debug mode shows the following error:
upcoming handshake states: client finished[20]
upcoming handshake states: server change_cipher_spec[-1]
upcoming handshake states: server finished[20]
*** ServerHelloDone
[read] MD5 and SHA1 hashes: len = 4
0000: 0E 00 00 00 ....
Warning: no suitable certificate found - continuing without client authentication
*** Certificate chain
<Empty>
***
update handshake state: certificate[11]
upcoming handshake states: client_key_exc
javax.net.debug? (In java 11 up usessl,keymanager,handshakerather than justssl) @user207421: the client-side keystore isn't sent but it is used for authentication, assuming the server requests it which the server here apaprently does