0

I am programming a client with mutual authentication. The Server has provided me with certificate (.cer), key and password. With these I can connect through Insomnia without problems. Through openSSL I have generated a pfx file that I use to connect from FireFox (and what am I trying to use here). However I can't connect from JAVA as I always get the message "PKIX..." (getOutputStream line). Below my code:

private static void testConnection(String urlS) throws Exception {
        KeyStore keyStore;
        keyStore = KeyStore.getInstance("PKCS12");
        keyStore.load((Main.class.getResourceAsStream("serverCertificate.pfx")), "password".toCharArray());
        KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
        kmf.init(keyStore, "password".toCharArray());
        KeyManager[] kms = kmf.getKeyManagers();

        KeyStore trustStore = KeyStore.getInstance("JKS");
        TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
        tmf.init(keyStore);

        TrustManager[] tms = tmf.getTrustManagers();

        SSLContext sc;
        sc = SSLContext.getInstance("TLSv1.2");
        sc.init(kms, tms, new java.security.SecureRandom());

        HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
        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", "XXX");
        urlConn.setRequestProperty("Content-Type", "application/json");
        urlConn.setDoOutput(true);
        String jsonString = "my json string  here";
        OutputStream os = urlConn.getOutputStream();
        os.write(jsonString.getBytes());
        os.flush();
        os.close();

        int response = urlConn.getResponseCode();

    }

It's wrong?

PS: I've seen the question in this link and I think it's not the same problem because my code is different and also I don't intend to create a keystore and add it to the jvm (if possible), as suggested in the solution.

masterbor
  • 1
  • 1

1 Answers1

0

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.

masterbor
  • 1
  • 1