Lavinia
Lavinia

Reputation: 141

Impossible to start multiple HTTPS connections from Spring application

I have a Spring Boot application that tries to open a javax.net.ssl.HttpsURLConnection to a server but the response received is: java.io.IOException: Server returned HTTP response code: 403 for URL: https://serverIP:8443/path

When the keyStore, trustStore and their passwords are set as system properties the request works correctly and the expected JSON response is received:

System.setProperty("javax.net.ssl.keyStore", "src/main/resources/myKeyStore.p12");
System.setProperty("javax.net.ssl.trustStore", "src/main/resources/myTrustStore.truststore");
System.setProperty("javax.net.ssl.keyStorePassword", "myPassword");
System.setProperty("javax.net.ssl.trustStorePassword", "myPassword");

But the 403 response code is received when trying to set the information in SSLContext, instead of setting the system properties, by using this method that returns an SSLContext object:

public static SSLContext getSslContext(String trustStoreFile, String keystoreFile, String password)
            throws GeneralSecurityException, IOException {
        final KeyStore keystore = KeyStore.getInstance("pkcs12"); // also tried with JKS

        try (final InputStream inKeystore = new FileInputStream(keystoreFile)) {
            keystore.load(inKeystore, password.toCharArray());
        }

        try (final InputStream inTruststore = new FileInputStream(trustStoreFile)) {
            keystore.load(inTruststore, password.toCharArray());
        }

        final KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("PKIX"); // also tried with .getDefaultAlgorithm()
        keyManagerFactory.init(keystore, password.toCharArray());

        final TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
        trustManagerFactory.init(keystore);

        X509TrustManager x509Tm = null;
        for (final TrustManager trustManager : trustManagerFactory.getTrustManagers()) {
            if (trustManager instanceof X509TrustManager) {
                x509Tm = (X509TrustManager) trustManager;
                break;
            }
        }

        final X509TrustManager finalTm = x509Tm;
        final X509ExtendedTrustManager customTm = new X509ExtendedTrustManager() {
            @Override
            public java.security.cert.X509Certificate[] getAcceptedIssuers() {
                return finalTm.getAcceptedIssuers();
            }

            @Override
            public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType) {
            }

            @Override
            public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType) {
            }

            @Override
            public void checkClientTrusted(java.security.cert.X509Certificate[] xcs, String string, Socket socket) throws CertificateException {
            }

            @Override
            public void checkServerTrusted(java.security.cert.X509Certificate[] xcs, String string, Socket socket) throws CertificateException {
            }

            @Override
            public void checkClientTrusted(java.security.cert.X509Certificate[] xcs, String string, SSLEngine ssle) throws CertificateException {
            }

            @Override
            public void checkServerTrusted(java.security.cert.X509Certificate[] xcs, String string, SSLEngine ssle) throws CertificateException {
            }
        };

        final SSLContext sslContext = SSLContext.getInstance("TLS"); // also tried with SSL
        sslContext.init(
                keyManagerFactory.getKeyManagers(),
                new TrustManager[]{customTm},
                new SecureRandom());

        final HostnameVerifier allHostsValid = new HostnameVerifier() {
            @Override
            public boolean verify(String hostname, SSLSession session) {
                return true;
            }
        };

        HttpsURLConnection.setDefaultHostnameVerifier(allHostsValid);

        return sslContext;
    }

OBS: The trustStore and keyStore have the same password, that's why the method has only one password parameter and used for both key and trust manager factories.

The way the getSslContext method is called and used is:

        final SSLContext sslContext = SSLContextHelper.getSslContext("src/main/resources/myTrustStore.truststore",
                                                                     "src/main/resources/myKeyStore.p12", 
                                                                     "myPassword");
        final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
        final URL url = new URL("https://serverIP:8443/path");
        final HttpsURLConnection urlConnection = (HttpsURLConnection) url.openConnection();
        urlConnection.setSSLSocketFactory(sslSocketFactory);

        // tried adding some headers to the request
        urlConnection.addRequestProperty("Content-Type", "application/json");
        urlConnection.addRequestProperty("Accept", "application/json");
        urlConnection.addRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:25.0) Gecko/20100101 Firefox/25.0");
        urlConnection.connect();

        final InputStream inputstream = urlConnection.getInputStream();

The error is thrown at the last line when trying to get the inputStream of the URL connection.

Also, I tried using the following classes from org.apache.http: SSLConnectionSocketFactory, HttpClient, HttpGet, HttpResponse but response code is still 403.

I can only think that there is something missing from the SSL configuration because the system properties work. Any suggestions on what I miss setting in the SSLContext/SSLSocketFactory or how can I solve/better debug the problem are welcome! Thanks!

Upvotes: 3

Views: 977

Answers (1)

Lavinia
Lavinia

Reputation: 141

I managed to open the HTTPS connections only by using Spring's RestTemplate (org.springframework.web.client.RestTemplate) that uses the org.apache.http.client.HttpClient.

The method for getting the RestTemplate that has in its SSLContext the keyStore, trustStore and their passwords is the following:

public RestTemplate getRestTemplate(final String keyStoreFile, final String trustStoreFile,
                                    final String password) throws Exception {

    final SSLContext sslContext = SSLContextBuilder.create()
                                                   .loadKeyMaterial(ResourceUtils.getFile(keyStoreFile), password.toCharArray(), password.toCharArray())
                                                   .loadTrustMaterial(ResourceUtils.getFile(trustStoreFile), password.toCharArray())
                                                   .build();

    final HttpClient client = HttpClients.custom()
                                         .setSSLContext(sslContext)
                                         .build();

    final HttpComponentsClientHttpRequestFactory httpComponentsClientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory();
    httpComponentsClientHttpRequestFactory.setHttpClient(client);

    return new RestTemplate(httpComponentsClientHttpRequestFactory);
}

The way that the RestTemplate is used for the HTTPS call is:

final String keyStoreFile = "src/main/resources/myKeyStore.p12";
final String trustStoreFile = "src/main/resources/myTrustStore.truststore";
final String password = "myPassword"; // same password for keyStore and trustStore
final String response = getRestTemplate(keyStoreFile, trustStoreFile, password).getForObject("https://serverIP:8443/path", String.class);
LOGGER.info("Response received: " + response);

Hope this helps anyone, had a lot of struggle with the HTTPS connections :)

Upvotes: 2

Related Questions