10

In my java application I need to send POST requests to a server sitting behind https. On the machine where my java application is running there is a java trust store in: /usr/local/comp.jks that contains the certificate for the server I need to interact with (its already imported).

The problem is that I cannot control how the JVM is started that will run my java application - e.g. by adding:

-Djavax.net.ssl.trustStore=/usr/local/comp.jks to the VM arguments.

Is it possible to load the trust store in the above path at runtime from my application after the JVM has started so I can authenticate against the https site?

I have only found guides on how to import certificates at runtime but that I cannot use - also since I don't have the password for /usr/local/comp.jks

Below my current implementation (in groovy):

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.security.KeyManagementException;
import java.security.KeyStore
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException
import java.security.cert.X509Certificate
import java.util.Base64;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;


public class HttpsClientImpl extends AbstractHttpClient  {
  private String username = null;
  private String password = null;

  public HttpsClientImpl (String username, String password)  {
    this.username=username;
    this.password=password;

  }

  @Override
  public String sendRequest(String request, String method) {
    System.setProperty( "javax.net.ssl.trustStore", "/usr/local/comp.jks" );
    URL url = new URL(request);
    HttpsURLConnection con = (HttpsURLConnection) url.openConnection()
    // Set auth
    byte[] name = (username + ":" + password).getBytes();
    String authStr = Base64.getEncoder().encodeToString(name);
    con.setRequestProperty("Authorization", "Basic " + authStr)

    con.setRequestMethod(method);
    writeResult(con);
    return con.getResponseCode();
  }


  private void writeResult(HttpsURLConnection con) throws IOException {
    if(con!=null){
      BufferedReader br = null;
      if (200 <= con.getResponseCode() && con.getResponseCode() <= 299) {
        br = new BufferedReader(new InputStreamReader(con.getInputStream()));
      } else {
        br = new BufferedReader(new InputStreamReader(con.getErrorStream()));
      }
      try {
        String input;
        while ((input = br.readLine()) != null){
          System.out.println(input);
        }
        br.close();
      } catch (IOException e) {
        e.printStackTrace();
      }
    }
  }
}

When I run that I get:

sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
    at sun.security.provider.certpath.SunCertPathBuilder.build(SunCertPathBuilder.java:141)
    at sun.security.provider.certpath.SunCertPathBuilder.engineBuild(SunCertPathBuilder.java:126)
    at java.security.cert.CertPathBuilder.build(CertPathBuilder.java:280)
    at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:382)
Caused: sun.security.validator.ValidatorException: PKIX path building failed
u123
  • 15,603
  • 58
  • 186
  • 303
  • Possible duplicate of [java SSL and cert keystore](https://stackoverflow.com/questions/5871279/java-ssl-and-cert-keystore) – Vadzim Jul 09 '18 at 18:55

2 Answers2

7

Assuming you haven't instantiated any SSL connections yet, you can simply call

System.setProperty( "javax.net.ssl.trustStore", "/usr/local/comp.jks" );

You'll probably also need to set javax.net.ssl.trustStorePassword and maybe javax.net.ssl.trustStoreType.

If the default SSL infrastructure has alredy been instantiated, you'll probably have to create your own SSLContext and SSLSocketFactory using your keystore.

Andrew Henle
  • 32,625
  • 3
  • 24
  • 56
  • As I wrote in my post I don't have the password for the trust store. So is it only possible to do this if I have the password? – u123 Feb 14 '18 at 16:37
  • 1
    @u123 The site *sends you its cert*. Get the cert from the site itself: https://superuser.com/a/641396/441365 Then convert the `*.pem` file into your own Java keystore: https://stackoverflow.com/a/13992135/4756299 – Andrew Henle Feb 14 '18 at 17:27
  • Ok so there is no way of using the existing trust store: /usr/local/comp.jks that already contains the correct certificates? – u123 Feb 14 '18 at 17:28
  • @u123 *Ok so there is no way of using the existing trust store: ...* Not if you don't have the password. – Andrew Henle Feb 14 '18 at 17:28
  • I don't believe you need a trust store password to use it. Trust information is public. – John Calcote Jun 07 '19 at 02:09
  • You may not need a truststore password. But your truststore decides which servers (or on server side which clients) you trust. So only with the password you can add entries. Which is also a security feature. – Arquillian Aug 04 '21 at 17:42
  • @Arquillian A truststore having a password is a pretty poor security feature. All the certificates in a trust store are publicly available. And if malicious users have write access to your truststore to the point it needs a password to protect it from changes, you have bigger problems than protecting public data with some password can protect you from... – Andrew Henle Aug 04 '21 at 22:00
  • @AndrewHenle I guess its really best practise to not use passwords for trust stores. But I am surprise anyways that even the api doesnt support passwords: trustManagerFactory.init(trustStore); <- doesnt take a password – Arquillian Aug 05 '21 at 16:21
4

You can load the truststore in you class. What I would suggest is to use both your truststore and load the JDK truststore and use both. Here I am giving and example regarding how you can do it.

public class TrustManagerComposite implements X509TrustManager {

    private final List<X509TrustManager> compositeTrustmanager;

    public TrustManagerComposite() {
        List<X509TrustManager> trustManagers = new ArrayList<>();
        try (InputStream truststoreInput = PATH_TO_YOUR_TRUSTSTORE) {
            trustManagers.add(getCustomTrustmanager(truststoreInput));
            trustManagers.add(getDefaultTrustmanager());
        } catch (Exception e) {
            //log it
        }
        compositeTrustmanager = trustManagers;
    }

    private static X509TrustManager getCustomTrustmanager(InputStream trustStream) throws Exception {
        return createTrustManager(trustStream);
    }

    private static X509TrustManager getDefaultTrustmanager() throws Exception {
        return createTrustManager(null);
    }

    private static X509TrustManager createTrustManager(InputStream trustStream) throws Exception {
        // Now get trustStore
        KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());

        // load the stream to your store
        trustStore.load(trustStream, null);

        // initialize a trust manager factory with the trusted store
        TrustManagerFactory trustFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
        trustFactory.init(trustStore);

        // get the trust managers from the factory
        TrustManager[] trustManagers = trustFactory.getTrustManagers();
        for (TrustManager trustManager : trustManagers) {
            if (trustManager instanceof X509TrustManager) {
                return (X509TrustManager) trustManager;
            }
        }
        return null;
    }

    @Override
    public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
        for (X509TrustManager trustManager : compositeTrustmanager) {
            try {
                trustManager.checkClientTrusted(chain, authType);
                return;
            } catch (CertificateException e) {
                // maybe the next trust manager will trust it, don't break the loop
            }
        }
        throw new CertificateException("None of the TrustManagers trust this certificate chain");
    }

    @Override
    public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
        for (X509TrustManager trustManager : compositeTrustmanager) {
            try {
                trustManager.checkServerTrusted(chain, authType);
                return;
            } catch (CertificateException e) {
                // maybe the next trust manager will trust it, don't break the loop
            }
        }
        throw new CertificateException("None of the TrustManagers trust this certificate chain");
    }

    @Override
    public X509Certificate[] getAcceptedIssuers() {
        List<X509Certificate> certs = new ArrayList<>();
        for (X509TrustManager trustManager : compositeTrustmanager) {
            for (X509Certificate cert : trustManager.getAcceptedIssuers()) {
                certs.add(cert);
            }
        }
        return certs.toArray(new X509Certificate[0]);
    }
}
Junaed
  • 1,457
  • 13
  • 15