Reputation: 49
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
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
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