javaspring-bootcertificateresttemplateself-signed-certificate

Ignoring self-signed certificates in SpringBoot using RestTemplate


I'm working on a SpringBoot application which needs to make RESTful calls to an internal API that uses a self-signed certificate.

I'm only having this issue in DEV and QA since in UAT and PROD they're using a properly signed certificate.

I'm developing on a Windows 10 machine and using Java 8.


I've tried the below with no luck:

Added certificate to Windows trusted certificates

  1. I downloaded the certificate from Chrome (in the address bar where it shows that the certificate is not valid).
  2. Then, in Windows Explorer, I right-clicked the certificate file and selected Install Certificate and followed the wizard.

Adding code to ignore SSL verification

I called the SSLUtils.buildRestTemplate method when creating a RestTemplate.

package com.company.project.utils.ssl;

import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.util.Map.Entry;

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

import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.TrustStrategy;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;

import com.company.project.beans.ssl.SslBypassConfiguration;

/**
 * This class contains several methods for manipulating SSL certificate verification.
 */
public class SSLUtils
{

    /* PRIVATE CONSTANTS */
    private static Logger logger = LogManager.getLogger(SSLUtils.class);

    /* PRIVATE VARIABLES */
    private static HostnameVerifier defaultHostnameVerifier = HttpsURLConnection.getDefaultHostnameVerifier();

    /* PUBLIC METHODS */
    /**
     * This method will set custom SSL certificate verification which will
     * only forgo SSL certificate verification for white-listed hostnames.
     * 
     * @param sslBypassConfiguration
     * The {@link SslBypassConfiguration} that contains the details needed.
     * 
     * @return
     * The boolean flag to denote if the operation was successful.
     * 
     * @throws NoSuchAlgorithmException
     * If no Provider supports aSSLContextSpi implementation for the
     * specified protocol.
     * @throws KeyManagementException
     * If the initialization fails.
     */
    public static boolean setCustomSslChecking(final SslBypassConfiguration sslBypassConfiguration)
            throws NoSuchAlgorithmException, KeyManagementException
    {
        // If the SSL bypass is enabled, then keep going.
        if (sslBypassConfiguration.isSslVerificationBypassEnabled())
        {
            // If there are some hostnames to white-list, then keep going.
            if ((sslBypassConfiguration.getWhitelistedHostnames() != null) && (sslBypassConfiguration.getWhitelistedHostnames().size() > 0))
            {
                final StringBuilder sb = new StringBuilder("Hostnames Being White-Listed:\n");

                // Loop over all white-listed hostnames and log them.
                for (Entry<String, String> whitelistedHostname : sslBypassConfiguration.getWhitelistedHostnames().entrySet())
                {
                    sb.append(whitelistedHostname.getKey())
                    .append(" (")
                    .append(whitelistedHostname.getValue())
                    .append(")");
                }

                logger.warn(sb.toString());
            }
            else
            {
                logger.warn("SSL certificate verification bypass is enabled, but no white-listed hostnames have been specified.");
            }

            // Create the hostname verifier to be used.
            final WhitelistHostnameVerifier whitelistHostnameVerifier = new WhitelistHostnameVerifier(sslBypassConfiguration);

            // Create the trust manager to be used.
            final X509TrustManager trustManager = new TrustingX509TrustManager();

            // Assign the custom hostname verifier and trust manager.
            SSLUtils.setCustomSslChecking(whitelistHostnameVerifier, trustManager);

            return true;
        }

        return false;
    }

    /**
     * This method will set custom SSL certificate verification.
     * 
     * @param hostnameVerifier
     * The {@link javax.net.ssl.HostnameVerifier} that will be used to verify hostnames.
     * @param trustManager
     * The {@link X509TrustManager} that will be used to verify certificates.
     * 
     * @throws NoSuchAlgorithmException
     * If no Provider supports aSSLContextSpi implementation for the specified protocol.
     * @throws KeyManagementException
     * If the initialization fails.
     */
    public static void setCustomSslChecking(
            final javax.net.ssl.HostnameVerifier hostnameVerifier,
            final X509TrustManager trustManager)
            throws NoSuchAlgorithmException, KeyManagementException
    {
        // Get an instance of the SSLContent.
        final SSLContext sslContent = SSLContext.getInstance("SSL"); // TLS

        // Set the state using the specified TrustManager.
        sslContent.init(null, new TrustManager[] {trustManager}, null);

        // Set the derived SSL socket factory.
        HttpsURLConnection.setDefaultSSLSocketFactory(sslContent.getSocketFactory());

        // Define the default hostname verifier.
        javax.net.ssl.HttpsURLConnection.setDefaultHostnameVerifier(hostnameVerifier);
    }

    /**
     * This method will set the default SSL certificate verification.
     * 
     * @throws NoSuchAlgorithmException
     * If no Provider supports aSSLContextSpi implementation for the specified protocol.
     * @throws KeyManagementException
     * If the initialization fails.
     */
    public static void setDefaultSslChecking()
            throws NoSuchAlgorithmException, KeyManagementException
    {
        // Get an instance of the SSLContent.
        final SSLContext sslContent = SSLContext.getInstance("SSL"); // TLS

        // Return it to the initial state (discovered by reflection, now hardcoded).
        sslContent.init(null, null, null);

        // Set the default SSL socket factory.
        HttpsURLConnection.setDefaultSSLSocketFactory(sslContent.getSocketFactory());

        // Define the default hostname verifier.
        javax.net.ssl.HttpsURLConnection.setDefaultHostnameVerifier(SSLUtils.defaultHostnameVerifier);
    }

    /**
     * This method will build a new {@link RestTemplate}.
     * 
     * @param sslBypassConfiguration
     * The {@link SslBypassConfiguration}.
     * 
     * @return
     * The {@link RestTemplate}.
     * 
     * @throws KeyManagementException
     * @throws NoSuchAlgorithmException
     * @throws KeyStoreException
     */
    public static RestTemplate buildRestTemplate(final SslBypassConfiguration sslBypassConfiguration)
            throws KeyManagementException, NoSuchAlgorithmException, KeyStoreException
    {
        if ((sslBypassConfiguration == null) || (!sslBypassConfiguration.isSslVerificationBypassEnabled()))
        {
            return new RestTemplate();
        }

        final TrustStrategy acceptingTrustStrategy = new TrustStrategy()
        {
            @Override
            public boolean isTrusted(final java.security.cert.X509Certificate[] chain, final String authType)
                    throws java.security.cert.CertificateException
            {
                return true;
            }
        };

        final HttpClientBuilder httpClientBuilder = HttpClients.custom();


        final SSLContext sslContext = org.apache.http.ssl.SSLContexts.custom().loadTrustMaterial(null, acceptingTrustStrategy).build();
        final SSLConnectionSocketFactory csf = new SSLConnectionSocketFactory(sslContext);

        httpClientBuilder.setSSLSocketFactory(csf);
        httpClientBuilder.setSSLHostnameVerifier(new WhitelistHostnameVerifier(sslBypassConfiguration));

        final HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();

        requestFactory.setHttpClient(httpClientBuilder.build());

        return new RestTemplate(requestFactory);
    }

}

Possible Idea

Is there an application that could accept HTTP connections and route them to an externally hosted HTTPS endpoint?

This application would have to ignore any certificate issues.


Solution

  • The simplest configuration that turn off all ssl verification is:

    with a factory

        HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
    

    trust all TLS with the org.apache.http.conn.ssl.NoopHostnameVerifier class from

    `<dependency>
        <groupId>org.apache.httpcomponents</groupId>
        <artifactId>httpclient</artifactId>
        <version>4.5.3</version>
    </dependency>` 
    

    Create a new client

        SSLContext sslcontext = SSLContexts.custom() .loadTrustMaterial(null, (chain, authType) -> true) .build(); 
        SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslcontext, new String[]{"TLSv1"}, null, new NoopHostnameVerifier()); 
        CloseableHttpClient httpClient = HttpClients.custom().setSSLSocketFactory(sslsf).build();
    

    Create a new resttemplate with this new client

        requestFactory.setHttpClient(httpClient);
        RestTemplate restTemplate = new RestTemplate(requestFactory);
    

    resuming:

    HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
        
    SSLContext sslcontext = SSLContexts.custom() .loadTrustMaterial(null, (chain, authType) -> true) .build(); 
    SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslcontext, new String[]{"TLSv1"}, null, new NoopHostnameVerifier()); 
    CloseableHttpClient httpClient = HttpClients.custom().setSSLSocketFactory(sslsf).build();
        
        
    requestFactory.setHttpClient(httpClient);
    RestTemplate restTemplate = new RestTemplate(requestFactory);