Using Java to realize mTLS call
- 2021-12-09 09:03:56
- OfStack
This article will use Java as a client to interact with services protected by mTLS.
In order to configure our Java client for ssl, we need to set up 1
SSLContext
. This simplifies things because
SSLContext
Available for various http clients.
Since we have client public and private keys, we need to convert the private key from PEM format to DER.
openssl pkcs8 -topk8 -inform PEM -outform PEM -in /path/to/generated/client.key -out /path/to/generated/client.key.pkcs8 -nocrypt
The next step is to load the client key into the Java code and create an KeyManagerFactory:
String privateKeyPath = <font>"/path/to/generated/client.key.pkcs8"</font><font>;
String publicKeyPath = </font><font>"/path/to/generated/client.crt"</font><font>;
<b>final</b> byte[] publicData = Files.readAllBytes(Path.of(publicKeyPath));
<b>final</b> byte[] privateData = Files.readAllBytes(Path.of(privateKeyPath));
String privateString = <b>new</b> String(privateData, Charset.defaultCharset())
.replace(</font><font>"-----BEGIN PRIVATE KEY-----"</font><font>, </font><font>""</font><font>)
.replaceAll(System.lineSeparator(), </font><font>""</font><font>)
.replace(</font><font>"-----END PRIVATE KEY-----"</font><font>, </font><font>""</font><font>);
byte[] encoded = Base64.getDecoder().decode(privateString);
<b>final</b> CertificateFactory certificateFactory = CertificateFactory.getInstance(</font><font>"X.509"</font><font>);
<b>final</b> Collection<? <b>extends</b> Certificate> chain = certificateFactory.generateCertificates(
<b>new</b> ByteArrayInputStream(publicData));
Key key = KeyFactory.getInstance(</font><font>"RSA"</font><font>).generatePrivate(<b>new</b> PKCS8EncodedKeySpec(encoded));
KeyStore clientKeyStore = KeyStore.getInstance(</font><font>"jks"</font><font>);
<b>final</b> <b>char</b>[] pwdChars = </font><font>"test"</font><font>.toCharArray();
clientKeyStore.load(<b>null</b>, <b>null</b>);
clientKeyStore.setKeyEntry(</font><font>"test"</font><font>, key, pwdChars, chain.toArray(<b>new</b> Certificate[0]));
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(</font><font>"SunX509"</font><font>);
keyManagerFactory.init(clientKeyStore, pwdChars);
In the above clip,
We read bytes from the file. We created a certificate chain from the public key. We created a key instance using the private key. Created a keystore using chain and key Created 1KeyManagerFactory
Now we have created 1
KeyManagerFactory
We can use it to create 1
SSLContext
Because we use self-signed certificates, we need to use the
TrustManager
. In this example, the trust manager will accept all certificates provided by the server.
TrustManager[] acceptAllTrustManager = {
<b>new</b> X509TrustManager() {
<b>public</b> X509Certificate[] getAcceptedIssuers() {
<b>return</b> <b>new</b> X509Certificate[0];
}
<b>public</b> <b>void</b> checkClientTrusted(
X509Certificate[] certs, String authType) {
}
<b>public</b> <b>void</b> checkServerTrusted(
X509Certificate[] certs, String authType) {
}
}
};
The ssl context is then initialized.
SSLContext sslContext = SSLContext.getInstance(<font>"TLS"</font><font>);
sslContext.init(keyManagerFactory.getKeyManagers(), acceptAllTrustManager, <b>new</b> java.security.SecureRandom());
Client code:
HttpClient client = HttpClient.newBuilder()
.sslContext(sslContext)
.build();
HttpRequest exactRequest = HttpRequest.newBuilder()
.uri(URI.create(<font>"https://127.0.0.1"</font><font>))
.GET()
.build();
<b>var</b> exactResponse = client.sendAsync(exactRequest, HttpResponse.BodyHandlers.ofString())
.join();
System.out.println(exactResponse.statusCode());
We will receive a 404 code, which means that our request successfully made an mTLS handshake.
Note: If the server is using the local Nginx service, we need to disable host name authentication.
<b>final</b> Properties props = System.getProperties();
props.setProperty(<font>"jdk.internal.httpclient.disableHostnameVerification"</font><font>, Boolean.TRUE.toString());
In other clients, this may require setting 1 that accepts all connections
HostVerifier
.
HostnameVerifier allHostsValid = <b>new</b> HostnameVerifier() {
<b>public</b> <b>boolean</b> verify(String hostname, SSLSession session) {
<b>return</b> <b>true</b>;
}
};