当服务器不支持 TLS 1.0 时,带有目标的 HCP JEE6 应用程序失败

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

我们正在使用 Hana Cloud Platform Java 应用程序外部的 RESTful 服务。我们能够通过作为应用程序 JEE6 配置文件和其他 HCP 库的一部分提供的 Apache HttpClient (v4.1.3) 与 HCP 中的这些目标设置进行交互,该应用程序配置为使用 JRE 7。

我们正在连接的集成基础架构供应商最近禁用了 TLSv1.0,从那时起我们在尝试连接到 REST 服务时遇到错误。

这是堆栈跟踪:

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.execute(AbstractHttpClientWrapper.java:141)
at com.sap.core.connectivity.httpdestination.impl.AbstractHttpClientWrapper.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)

我们已尝试将 JVM 参数添加到 Java 应用程序以强制它使用 TLSv1.1 或 TLSv1.2 而不是使用 TLSv1.0:

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

设置 JVM 参数没有任何作用,Apache HttpClient 库似乎忽略了此设置。是否有另一种方法可以强制 Apache HttpClient (v4.1.3) 使用更新版本的 TLS?

事实证明,可以扩展class org.apache.http.conn.ssl.SSLSocketFactory 并将其注册到HttpClient 以强制客户端使用特定协议。事实上,您可以使用相同的机制来更改密码、超时等。

添加此附加代码以从 HttpDestination 创建 HttpClient:

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;
}

并扩展 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;
    }

}