I have noticed that there is no documentation that clearly defines how to set up a client in mutual authentication without using the jvm (cacerts). Even here on StackOverflow that configuration mode is the most widespread, and the only one. I have inquired about how to do it, it becomes a bit complex due to the use of OpenSSL, but the result is worth it. Next I leave you a step-by-step document so that you can configure your client in this way.
MUTUAL AUTHENTICATION
Mutual authentication requires both client and server to present their certificates and validate each other's certificate.
The client certificate can be issued by the entity that owns the server or by a certifying institution.
(you can find a lot of information online, starting from wikipedia)
PURPOSE OF THIS GUIDE
Existing documentation for configuring client certificate inside the JVM (cacerts) is abundant. In fact it is the default.
This configuration has some disadvantages such as:
- SECURITY: All programs with access to that JVM would be "certified" to access the server.
- PORTABILITY/MAINTAINABILITY: The certificate must be installed in every environment (development, in every developer,/test/production). And it should be reinstalled when you change the JVM.
This document will explain how to configure both certificates within the project, without making any changes to the JVM.
SOFTWARE REQUIRED
OpenSSL (you can download it from here, to be safe don't download it from third parties)
PRECONDITIONS
You must have:
- Client certificate: .crt / .cer
- Key for client certificate
- Passphrase/Password for the client certificate
- Server certificate .pem (You can get it in several ways as explained here.)
PROCEDURE
If you already meet the conditions, here are the instructions:
KeyStore (Client Certificate)
The KeyStore is a single file, in this case with the extension pfx (it can also be .jks), which includes the Client Certificate + the Key.
It is generated by openssl like this:
openssl pkcs12 -export -out keystore.pfx -inkey clientCertificateKey.key -in clientCertificate.crt
openssl will ask for the password for the client certificate
If you have a root CA and intermediate certificates, include these as well using various -in parameters, for example:
openssl pkcs12 -export -out keystore.pfx -inkey clientCertificateKey.key -in clientCertificate.crt -in intermediate.crt -in rootca.crt
TrustStore (Server Certificate)
Once you have the server certificate, you must first convert it to a cer file as explained below:
.pem to .cer
openssl x509 -outform der -in serverCertificate.pem -out serverCertificate.crt
.cer to .jks
keytool -import -alias $CERT_ALIAS -file serverCertificate.crt -keystore truststore.jks -deststorepass $CERT_PASSWORD
You must define $CERT_ALIAS and $CERT_PASSWORD
At this point you should have the KeyStore (keystore.pfx) and the TrustStore (truststore.jks). So let's see how they are used in the code.
ENCODING
Later we will see, in part and then in full, how to use these files in a method.
Add the keystore.pfx and truststore.jks files inside the project, in the path of your Main class.
How to get the KeyManager
KeyStore keyStore = KeyStore.getInstance("PKCS12");
keyStore.load((Main.class.getResourceAsStream("keystore.pfx")), "password".toCharArray());
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(keyStore, "password".toCharArray());
KeyManager[] kms = kmf.getKeyManagers();
How to get TrustManager (IDEM KeyManager)
KeyStore trustStore = KeyStore.getInstance("JKS");
trustStore.load((Main.class.getResourceAsStream("truststore.jks")), "password".toCharArray());
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(trustStore);
TrustManager[] tms = tmf.getTrustManagers();
Complete method with connection
In the example case the server requested:
- TLSv1.2 protocol
- Request from POST
- Headers
- KeyId (provided by the server, fixed for all communications)
- X-Request-ID (UUID)
The following method is intended to illustrate the sections of code that must be defined in order to communicate with a server via mutual authentication according to the premises established above. Error handling is out of scope - I'll leave that part up to you, as is defining use of other libraries, etc ;-)
private static String postJson (String urlS, String json) {
String response = "";
KeyStore keyStore = KeyStore.getInstance("PKCS12");
keyStore.load((Main.class.getResourceAsStream("keystore.pfx")), "password".toCharArray());
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(keyStore, "password".toCharArray());
KeyManager[] kms = kmf.getKeyManagers();
KeyStore trustStore = KeyStore.getInstance("JKS");
trustStore.load((Main.class.getResourceAsStream("truststore.jks")), "password".toCharArray());
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(trustStore);
TrustManager[] tms = tmf.getTrustManagers();
SSLContext sc;
sc = SSLContext.getInstance("TLSv1.2");
sc.init(kms, tms, new java.security.SecureRandom());
URL url = new URL(urlS);
HttpsURLConnection urlConn = (HttpsURLConnection) url.openConnection();
urlConn.setSSLSocketFactory(sc.getSocketFactory());
urlConn.setRequestMethod("POST");
urlConn.setRequestProperty("KeyId", "myKeyID");
urlConn.setRequestProperty("X-Request-ID", UUID.randomUUID().toString());
urlConn.setRequestProperty("Content-Type", "application/json");
urlConn.setDoOutput(true);
OutputStream os = urlConn.getOutputStream();
os.write(json.getBytes());
os.flush();
os.close();
if (urlConn.getResponseCode() != -1) {
boolean connection_ok = (urlConn.getResponseCode() == HttpURLConnection.HTTP_OK);
if (connection_ok) {
StringBuilder responseBuilder = new StringBuilder();
String responseLine = null;
while ((responseLine = br.readLine()) != null) {
responseBuilder.append(responseLine.trim());
}
response = responseBuilder.toString();
}
}
return response;
}
If you find it useful, don't forget to leave your "Like"!
For any questions, leave me your comment.
Thanks.