Search code examples
javaapachesslapache-commons-httpclientsap-cloud-platform

HCP JEE6 Application with Destination fails when server does not support TLS 1.0


We are consuming RESTful services external to a Hana Cloud Platform Java application. We are able to interact with these destinations setup in HCP via the Apache HttpClient (v4.1.3) provided as part of the application JEE6 profile and other HCP libraries, the application is configured to use JRE 7.

The integration infrastructure vendor we are connecting to, disabled TLSv1.0 recently and since then we are getting errors when trying to connect to the REST services.

This is the stacktrace:

javax.net.ssl.SSLPeerUnverifiedException: peer not authenticated
at sun.security.ssl.SSLSessionImpl.getPeerCertificates(SSLSessionImpl.java:421)
at org.apache.http.conn.ssl.AbstractVerifier.verify(AbstractVerifier.java:128)
at org.apache.http.conn.ssl.SSLSocketFactory.connectSocket(SSLSocketFactory.java:397)
at org.apache.http.impl.conn.DefaultClientConnectionOperator.openConnection(DefaultClientConnectionOperator.java:148)
at org.apache.http.impl.conn.AbstractPoolEntry.open(AbstractPoolEntry.java:150)
at org.apache.http.impl.conn.AbstractPooledConnAdapter.open(AbstractPooledConnAdapter.java:121)
at org.apache.http.impl.client.DefaultRequestDirector.tryConnect(DefaultRequestDirector.java:575)
at org.apache.http.impl.client.DefaultRequestDirector.execute(DefaultRequestDirector.java:425)
at com.sap.core.connectivity.httpdestination.client.RequestDirectorExtender.execute(RequestDirectorExtender.java:47)
at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:820)
at com.sap.core.connectivity.httpdestination.impl.AbstractHttpClientWrapper$2.execute(AbstractHttpClientWrapper.java:141)
at com.sap.core.connectivity.httpdestination.impl.AbstractHttpClientWrapper$2.execute(AbstractHttpClientWrapper.java:1)
at com.sap.core.connectivity.httpdestination.impl.AbstractHttpClientWrapper.executeOperation(AbstractHttpClientWrapper.java:300)
at com.sap.core.connectivity.httpdestination.impl.AbstractHttpClientWrapper.execute(AbstractHttpClientWrapper.java:277)
at com.sap.core.connectivity.httpdestination.impl.AbstractHttpClientWrapper.execute(AbstractHttpClientWrapper.java:132)
at com.sap.core.connectivity.httpdestination.impl.AbstractHttpClientWrapper.execute(AbstractHttpClientWrapper.java:126)
at my.domain.hcp.HttpRequestSupport.service(HttpRequestSupport.java:124)
at my.domain.gap.proxy.ProxyServlet.service(ProxyServlet.java:36)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:848)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:303)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
at com.sap.core.communication.server.CertValidatorFilter.doFilter(CertValidatorFilter.java:156)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:218)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:122)
at org.eclipse.virgo.web.enterprise.security.valve.OpenEjbSecurityInitializationValve.invoke(OpenEjbSecurityInitializationValve.java:44)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:614)
at com.sap.core.jpaas.security.auth.service.lib.AbstractAuthenticator.invoke(AbstractAuthenticator.java:170)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:169)
at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:956)
at com.sap.core.tenant.valve.TenantValidationValve.invokeNextValve(TenantValidationValve.java:168)
at com.sap.core.tenant.valve.TenantValidationValve.invoke(TenantValidationValve.java:94)
at com.sap.js.statistics.tomcat.valve.RequestTracingValve.invoke(RequestTracingValve.java:38)
at com.sap.core.js.monitoring.tomcat.valve.RequestTracingValve.invoke(RequestTracingValve.java:27)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:103)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:116)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:442)
at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1083)
at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:640)
at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:316)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:807)

We have tried adding the JVM arguments to the Java application to force it to use TLSv1.1 or TLSv1.2 and not use TLSv1.0:

-Dhttps.protocols=TLSv1.1,TLSv1.2

Setting the JVM argument does nothing, it seems like the Apache HttpClient library ignores this setting. Is there another way to force the Apache HttpClient (v4.1.3) to a newer version of TLS?


Solution

  • It turns out that it is possible to extend the class org.apache.http.conn.ssl.SSLSocketFactory and register it with the HttpClient to force the client to use a specific protocol. In fact you are able to use the same mechanism to change the cyphers, timeouts etc.

    Add this additional code to create a HttpClient from the HttpDestination:

    private static final String CA_CERTS_PATH = System.getProperties().getProperty("java.home") + File.separator +
            "lib" + File.separator + "security" + File.separator + "cacerts";
    
    private HttpClient createHttpClient(HttpDestination httpDestination)
            throws DestinationException, KeyStoreException, IOException, CertificateException, NoSuchAlgorithmException, KeyManagementException {
    
        HttpClient httpClient = httpDestination.createHttpClient();
        try (FileInputStream fis = new FileInputStream(CA_CERTS_PATH)) {
            KeyStore keyStore = KeyStore.getInstance("JKS");
            keyStore.load(fis, "changeit".toCharArray());
    
            TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
            tmf.init(keyStore);
    
            SSLContext ctx = SSLContext.getInstance("TLS");
            ctx.init(null, tmf.getTrustManagers(), new SecureRandom());
    
            // Instantiate the custom SSLSocketFactory
            SSLSocketFactory sslSocketFactory = new CustomSSLSocketFactory(
                    ctx,
                    SSLSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER,
                    new String[]{"TLSv1.1", "TLSv1.2"}
            );
    
            // Register the https scheme with the custom SSLSocketFactory
            Scheme httpsScheme = new Scheme("https", 443, sslSocketFactory);
            httpClient.getConnectionManager().getSchemeRegistry().register(httpsScheme);
        }
        return httpClient;
    }
    

    And extend the SSLSocketFactory class:

    import org.apache.http.conn.ssl.SSLSocketFactory;
    import org.apache.http.conn.ssl.X509HostnameVerifier;
    import org.apache.http.params.HttpParams;
    
    import javax.net.ssl.SSLContext;
    import javax.net.ssl.SSLSocket;
    import java.io.IOException;
    import java.net.InetAddress;
    import java.net.InetSocketAddress;
    import java.net.Socket;
    
    /**
     * Custom SSLSocketFactory to limit connections to specific TLS protocols.
     *
     * @author Juan Heyns
     */
    public class CustomSSLSocketFactory extends SSLSocketFactory {
    
        private final String[] tlsProtocols;
    
        public CustomSSLSocketFactory(SSLContext sslContext, X509HostnameVerifier hostnameVerifier, String[] tlsProtocols) {
            super(sslContext, hostnameVerifier);
            this.tlsProtocols = tlsProtocols;
        }
    
        @Override
        public Socket connectSocket(Socket socket, InetSocketAddress remoteAddress, InetSocketAddress localAddress, HttpParams params) throws IOException {
            return prepareSocket(super.connectSocket(socket, remoteAddress, localAddress, params));
        }
    
        @Override
        public Socket createLayeredSocket(Socket socket, String host, int port, boolean autoClose) throws IOException {
            return prepareSocket(super.createLayeredSocket(socket, host, port, autoClose));
        }
    
        @Override
        public Socket createSocket(HttpParams params) throws IOException {
            return prepareSocket(super.createSocket(params));
        }
    
        @Override
        @Deprecated
        public Socket createSocket() throws IOException {
            return prepareSocket(super.createSocket());
        }
    
        @Override
        @Deprecated
        public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException {
            return prepareSocket(super.createSocket(socket, host, port, autoClose));
        }
    
        @Override
        @Deprecated
        public Socket connectSocket(Socket socket, String host, int port, InetAddress localAddress, int localPort, HttpParams params) throws IOException {
            return prepareSocket(super.connectSocket(socket, host, port, localAddress, localPort, params));
        }
    
        /**
         * Any socket returned from this class will first be configured.
         *
         * @param socket
         * @return
         */
        private Socket prepareSocket(Socket socket) {
            if (socket instanceof SSLSocket) {
                SSLSocket sslSocket = (SSLSocket) socket;
                sslSocket.setEnabledProtocols(tlsProtocols);
            }
            return socket;
        }
    
    }