2
votes

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
2
just came across this thread. I am trying to do something similar so wanted to know were you able to resolve this?NoviceProgrammer

2 Answers

2
votes

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?

Yes it is. The steps are

1) Load the Azure certificate in a Cert object (probably a X509Certificate)
2) Create a new KeyStore instance (whatever the format, JKS or PKCS12)
3) Init this new KeyStore by calling load with a null input stream, this will make a new, empty store.
4) Manually add the Azure certificate as a trusted entry in the KeyStore with a call to setCertificateEntry
5) Use this keystore as the base of your TrustManagerFactory

1
votes

YES you can create a KeyStore from a cert BUT NO you cannot use it for client auth aka mutual auth.

Java uses the KeyStore class and related files to store (in general) three different though related kinds of things as detailed in Javadoc for the class. To authenticate yourself, you must have a PrivateKeyEntry which contains, as its name might suggest, a private key, PLUS at least one certificate and usually a chain of multiple certificates. A TrustedCertEntry is used to authenticate other parties, and in particular the other endpoint (peer) of an SSL/TLS connection; when you are the SSL/TLS client, as here, a TrustedCertEntry is used to authenticate the server by validating the server's cert. (The third possibility, SecretKeyEntry, is not used for SSL/TLS, and not even supported by PKCS12 keystore type as implemented by Java and commonly used.)

With an X509Certificate object, you can create a TrustedCertEntry, and the code you got from GPI does so. A TrustedCertEntry (in a KeyStore) is only usable to authenticate the other party, in this situation the server. To authenticate yourself, the client, to the server, you need not simply a certificate but a private key and certificate chain, packaged as a PrivateKeyEntry, from which you create a KeyManager put in the SSLContext and used by JSSE, as per your first code.

AFAICS Azure vault represents keys as com.microsoft.azure.keyvault.webkey.JSONWebKey, which appears to be limited to RSA or AES. If RSA, there are two toRSA methods (overloads) that from the description should produce a KeyPair, which I presume means java.security.KeyPair, containing the private key you need, unless there are limitations not stated where I looked. I don't see any way to get a certificate chain directly, but it appears certificate entries have issuer links, which should be sufficient for you to build the chain, although I'm not in a position to test/verify this myself.