Using Java to realize mTLS call

  • 2021-12-09 09:03:56
  • OfStack

Directory

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 1 KeyManagerFactory

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>;
    }
};

Related articles: