SpringBootDev
SpringBootDev

Reputation: 49

Two-way mutual SSL authentication

I've been tasked with implementing functionality in a Spring Boot REST API to contact another API (XML webservice). The outside API uses two-way SSL authentication. I've been given the correct certificate to implement on our side, and I've implemented the Java code. But whenever I run the code I get "Received fatal alert: handshake_failure". I've loaded the jks keystore into the SSLContext like this:

FileInputStream truststoreFile = new FileInputStream("/Users/myUser/Desktop/myProject/myProjectName/src/main/resources/keystore-name.jks");
        TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
        KeyStore truststore = KeyStore.getInstance(KeyStore.getDefaultType());
        char[] trustorePassword = "keyStorePassword".toCharArray();
        truststore.load(truststoreFile, trustorePassword);
        trustManagerFactory.init(truststore);
        SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
        KeyManager[] keyManagers = {};//if you have key managers;
        sslContext.init(keyManagers, trustManagerFactory.getTrustManagers(), new SecureRandom());

Would I actually have to configure anything else to enable mutual two-way SSL from our API, acting like I client in this scenario? I thought I could just like the cert keystore and go. But maybe I need to do something else to enable this?

Upvotes: 1

Views: 5919

Answers (2)

gailmiya
gailmiya

Reputation: 1

Thank you for this! I was in the exact same situation with coding a Spring Boot application having to contact another API with two-way authentication. I adapted Saravana's code snippet with very few modifications, and it solved a problem I had been stuck on all day. I thought I would share my adaptation in case it helps anyone.

Keystore file (declared further up as a class attribute) is read in as a property:

@Value("${externalAPI.keyStoreFile}")
private Resource keyStoreFile;

The external API provided us with the p12 file needed for secure communication. I placed the file in the resources directory and put an entry in application.properties:

externalAPI.keyStoreFile=classpath:keystore/fromExternalAPI.p12

And here is the adapted code snippet from Saravana:

        // Load the keystore
        KeyStore keyStore = KeyStore.getInstance("PKCS12");
        InputStream keyStoreStream = new FileInputStream(keyStoreFile.getFile());
        keyStore.load(keyStoreStream, keyStorePassword.toCharArray());

        // Add keystore to key manager
        KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
        keyManagerFactory.init(keyStore, keyStorePassword.toCharArray());

        // Create SSLContext with KeyManager and TrustManager
        SSLContext sslContext = SSLContext.getInstance("TLS");
        sslContext.init(keyManagerFactory.getKeyManagers(), null, new SecureRandom());
        SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(sslContext);

        final HttpClientConnectionManager cm = PoolingHttpClientConnectionManagerBuilder.create()
                .setSSLSocketFactory(sslConnectionSocketFactory)
                .build();

        CloseableHttpClient httpClient = HttpClients.custom()
                .setConnectionManager(cm).build();
        ClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(httpClient);

        RestTemplate restTemplate = new RestTemplate(new BufferingClientHttpRequestFactory(requestFactory));
        restTemplate.setUriTemplateHandler(new DefaultUriBuilderFactory(externalApiUrl));

        // Set an interceptor to log the requests and responses to/from external API
        List<ClientHttpRequestInterceptor> interceptors
                = restTemplate.getInterceptors();
        if (CollectionUtils.isEmpty(interceptors)) {
            interceptors = new ArrayList<>();
        }
        interceptors.add(new LogRequestResponseFilter());
        restTemplate.setInterceptors(interceptors);

        // Create the restClient based on the rest template
        // so we can use the new features of RestClient
        restClient = RestClient.create(restTemplate);

Appreciate you guys sharing your knowledge! It was a huge help!

Upvotes: 0

Saravana Kumar
Saravana Kumar

Reputation: 322

You are using the file shared with you in the wrong context. That file is a Keystore containing the client certificate and corresponding key.

TrustStore - Tells which CAs should be trusted by the client (you).

Keystore - Tells the server about the client (you).

In order for the mutual TLS handshake to pass through, you need to load the Keystore and set it in KeyManager like below.

// Load the Keystore
KeyStore keyStore = KeyStore.getInstance("JKS");
InputStream keystoreStream = new FileInputStream(pathToJKSFile);
keyStore.load(keystoreStream, keystorePassword.toCharArray());

// Add Keystore to KeyManager
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyManagerFactory.init(keyStore, keystorePassword.toCharArray());

// Create SSLContext with KeyManager and TrustManager
SSLContext context = SSLContext.getInstance("TLS");
context.init(keyManagerFactory.getKeyManagers(), null, new SecureRandom());
SSLSocketFactory sslSocketFactory = context.getSocketFactory();

// Now, use this SSLSocketFactory while making the HTTPS request

Upvotes: 3

Related Questions