I am working on a Java web application deployed on an Azure App Service instance. And I need to make a call to a REST API that is secured by requiring mutual authentication over SSL. Since this is an app service, I don't have the luxury of adding the certificate and public key to the keystore and truststore respectively, and it has to all be done via code. Although with JCE and SSL, I managed to write the following console application that accesses the secure API successfully (with the help of other StackOverflow Q&A):
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
public class TestPFOM {
public static void main(String[] args) throws KeyStoreException, NoSuchAlgorithmException, CertificateException,
IOException, UnrecoverableKeyException, KeyManagementException {
System.out.println("Start test for mutual authentication");
KeyStore ks = KeyStore.getInstance("PKCS12");
File file = new File(System.getProperty("user.dir") + "/client.company.com.pfx");
System.out.println("Loaded PKCS12 from file");
try (FileInputStream fis = new FileInputStream(file)) {
ks.load(fis, "password".toCharArray());
System.out.println("Loaded keys into keystore");
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
kmf.init(ks, "password".toCharArray());
System.out.println("Initialized KeyStoreManager");
SSLContext sc = SSLContext.getInstance("TLS");
sc.init(kmf.getKeyManagers(), null, new SecureRandom());
System.out.println("initialized SSLContext");
SSLSocketFactory factory = sc.getSocketFactory();
System.out.println("Obtained SSLSocketFactory");
URL url = new URL("https://services.company.com/api/company_data");
HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
System.out.println("Opened secure HTTPS connection");
connection.setSSLSocketFactory(factory);
connection.setRequestMethod("GET");
connection.setRequestProperty("Accept", "application/json");
StringBuilder stringBuilder = new StringBuilder();
int responseCode = connection.getResponseCode();
System.out.println("HTTP response code = " + responseCode);
try (BufferedReader reader = responseCode == 200
? new BufferedReader(new InputStreamReader(connection.getInputStream()))
: new BufferedReader(new InputStreamReader(connection.getErrorStream()))) {
String line = null;
while ((line = reader.readLine()) != null) {
System.out.println(line);
stringBuilder.append(line);
}
} catch (Exception e) {
System.err.println("Error: " + e.getMessage());
}
System.out.println(stringBuilder.toString());
} catch (Exception ex) {
System.err.println("Error: " + ex.getMessage());
}
}
}
Instead of loading the PFX file into the KeyStore, I need to get the certificate from Azure Keyvault which already stores the certificate. The KeyVaultClient (Java client library from Azure) provides me with a mechanism to obtain an X509Certificate object. Is it possible to initiate a KeyStore with a X509Certificate object, instead of from a PFX file?
My goal is to have a reusable SSLContext object available to the request processing mechanism, so I can use it to call the external, secure API when my web application receives a request. And I need to do this without relying on any files and external JVM key/trust stores in the filesystem.
07/05/2018: Follow up to insightful suggestion from GPI I manually built the SSLContext
:
KeyStore keyStore = KeyStore.getInstance("PKCS12");
// Initiate and load empty key store
keyStore.load(null, null);
// clientCert is an X509Certificate object
keyStore.setCertificateEntry("clientCert", clientCert);
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); // PKIX
trustManagerFactory.init(keyStore);
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, trustManagerFactory.getTrustManagers(), null);
But when I use the resulting SSLSocketFactory
in the HTTPS connection, I get the following error:
sun.security.validator.ValidatorException:
PKIX path building failed:
sun.security.provider.certpath.SunCertPathBuilderException:
unable to find valid certification path to requested target