Как я могу использовать TLS 1.2 в Java 6 с Apache HttpClient?

Я работаю над проектом Java 6 с использованием Apache HttpClient 3.5 и Spring Web Services 2.1.0.

My Spring WebServicesTemplate отправляет запросы WebServices с помощью apache HttpClient. Однако служба, с которой я обращаюсь, отказалась от TLS 1.0 в пользу TLS 1.2. Java 6 не поддерживает TLS 1.2. Меня привели к bouncycastle, который должен дать мне поддержку TLS 1.2.

Как я могу интегрировать bouncycastle с Spring или моим HttpClient, чтобы отправлять запросы, поддерживающие TLS 1.2? Я нашел решение, которое предоставляет расширенный SSLSocketFactory. Однако HttpClient принимает только SSLConnectionSocketFactory.

Обновление до Java 8 невозможно в краткосрочной перспективе, но я могу сделать это долгосрочным приоритетом, если кто-то подтвердит, что это решит мою проблему.


person zalpha314    schedule 19.01.2016    source источник
comment
Вы уверены, что имеете в виду Apache HttpClient 3.5?   -  person Bruno    schedule 20.01.2016
comment
@Bruno, извините, в моем проекте есть commons-httpclient и httpcomponents ApacheClient. Я их перепутал. В Spring используется HttpClient - httpcomponents ApacheClient 4.3.5.   -  person zalpha314    schedule 20.01.2016
comment
Ответ, помеченный как принятый, на самом деле не отвечает на вопрос, он позволяет избежать проблемы, просто обновив до Java 8. Некоторые из нас не могут просто так сделать. Удалось ли вам решить эту проблему без обновления до Java 8?   -  person ThatAintWorking    schedule 01.02.2016
comment
Да, посмотрите мой новый ответ.   -  person zalpha314    schedule 01.02.2016


Ответы (2)


Я придумал решение, которое не требует обновления до Java 8. В качестве заявления об отказе от ответственности я очень мало знаю о безопасности https и уверен, что есть некоторые проблемы, которые может заметить более знающий человек. Этот метод требует использования встроенного HttpsURLConnection, который очень примитивен, если вы привыкли использовать такие вещи, как Unirest или даже apache HttpClient.

Сначала вам нужно добавить BouncyCastle к вам pom.

<dependency>
    <groupId>org.bouncycastle</groupId>
    <artifactId>bcprov-jdk15on</artifactId>
    <version>1.54</version>
</dependency>

Затем вы создадите расширение SSLSocketFactory. Предупреждение! Эта фабрика сокетов принимает все сертификаты, поэтому используйте их на свой страх и риск.

import java.io.*;
import java.net.UnknownHostException;
import java.security.*;
import java.security.cert.*;
import java.util.*;

import javax.net.ssl.*;
import javax.security.cert.X509Certificate;

import org.bouncycastle.crypto.tls.*;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

public class TLSSocketConnectionFactory extends SSLSocketFactory {

    static {
        if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
            Security.addProvider(new BouncyCastleProvider());
        }
    }

    @Override
    public Socket createSocket(Socket socket, final String host, int port,
            boolean arg3) throws IOException {
        if (socket == null) {
            socket = new Socket();
        }
        if (!socket.isConnected()) {
            socket.connect(new InetSocketAddress(host, port));
        }

        final TlsClientProtocol tlsClientProtocol = new     TlsClientProtocol(socket.getInputStream(), socket.getOutputStream(), new     SecureRandom());

        return _createSSLSocket(host, tlsClientProtocol);
    }

    @Override public String[] getDefaultCipherSuites() { return null; }
    @Override public String[] getSupportedCipherSuites() { return null; }
    @Override public Socket createSocket(String host, int port) throws IOException, UnknownHostException { throw new UnsupportedOperationException(); }
    @Override public Socket createSocket(InetAddress host, int port) throws IOException { throw new UnsupportedOperationException(); }
    @Override public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException, UnknownHostException { return null; }
    @Override public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException { throw new UnsupportedOperationException(); }

    private SSLSocket _createSSLSocket(final String host, final TlsClientProtocol tlsClientProtocol) {
        return new SSLSocket() {
            private java.security.cert.Certificate[] peertCerts;

            @Override public InputStream getInputStream() throws IOException { return tlsClientProtocol.getInputStream(); }
            @Override public OutputStream getOutputStream() throws IOException { return tlsClientProtocol.getOutputStream(); }
            @Override public synchronized void close() throws IOException { tlsClientProtocol.close(); }
            @Override public void addHandshakeCompletedListener( HandshakeCompletedListener arg0) { }
            @Override public boolean getEnableSessionCreation() { return false; }
            @Override public String[] getEnabledCipherSuites() { return null; }
            @Override public String[] getEnabledProtocols() { return null; }
            @Override public boolean getNeedClientAuth() { return false; }

            @Override
            public SSLSession getSession() {
                return new SSLSession() {

                    @Override
                    public int getApplicationBufferSize() {
                        return 0;
                    }

                    @Override public String getCipherSuite() { throw new UnsupportedOperationException(); }
                    @Override public long getCreationTime() { throw new UnsupportedOperationException(); }
                    @Override public byte[] getId() { throw new UnsupportedOperationException(); }
                    @Override public long getLastAccessedTime() { throw new UnsupportedOperationException(); }
                    @Override public java.security.cert.Certificate[] getLocalCertificates() { throw new UnsupportedOperationException(); }
                    @Override public Principal getLocalPrincipal() { throw new UnsupportedOperationException(); }
                    @Override public int getPacketBufferSize() { throw new UnsupportedOperationException(); }
                    @Override public X509Certificate[] getPeerCertificateChain() throws SSLPeerUnverifiedException { return null; }
                    @Override public java.security.cert.Certificate[] getPeerCertificates() throws SSLPeerUnverifiedException { return peertCerts; }
                    @Override public String getPeerHost() { throw new UnsupportedOperationException(); }
                    @Override public int getPeerPort() { return 0; }
                    @Override public Principal getPeerPrincipal() throws SSLPeerUnverifiedException { return null; }
                    @Override public String getProtocol() { throw new UnsupportedOperationException(); }
                    @Override public SSLSessionContext getSessionContext() { throw new UnsupportedOperationException(); }
                    @Override public Object getValue(String arg0) { throw new UnsupportedOperationException(); }
                    @Override public String[] getValueNames() { throw new UnsupportedOperationException(); }
                    @Override public void invalidate() { throw new UnsupportedOperationException(); }
                    @Override public boolean isValid() { throw new UnsupportedOperationException(); }
                    @Override public void putValue(String arg0, Object arg1) { throw new UnsupportedOperationException(); }
                    @Override public void removeValue(String arg0) { throw new UnsupportedOperationException(); }
            };
        }

        @Override public String[] getSupportedProtocols() { return null; }
        @Override public boolean getUseClientMode() { return false; }
        @Override public boolean getWantClientAuth() { return false; }
        @Override public void removeHandshakeCompletedListener(HandshakeCompletedListener arg0) { }
        @Override public void setEnableSessionCreation(boolean arg0) { }
        @Override public void setEnabledCipherSuites(String[] arg0) { }
        @Override public void setEnabledProtocols(String[] arg0) { }
        @Override public void setNeedClientAuth(boolean arg0) { }
        @Override public void setUseClientMode(boolean arg0) { }
        @Override public void setWantClientAuth(boolean arg0) { }
        @Override public String[] getSupportedCipherSuites() { return null; }

            @Override
            public void startHandshake() throws IOException {
                tlsClientProtocol.connect(new DefaultTlsClient() {

                    @SuppressWarnings("unchecked")
                    @Override
                    public Hashtable<Integer, byte[]> getClientExtensions() throws IOException {
                        Hashtable<Integer, byte[]> clientExtensions = super.getClientExtensions();
                        if (clientExtensions == null) {
                            clientExtensions = new Hashtable<Integer, byte[]>();
                        }

                        //Add host_name
                        byte[] host_name = host.getBytes();

                        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
                        final DataOutputStream dos = new DataOutputStream(baos);
                        dos.writeShort(host_name.length + 3);
                        dos.writeByte(0);
                        dos.writeShort(host_name.length);
                        dos.write(host_name);
                        dos.close();
                        clientExtensions.put(ExtensionType.server_name, baos.toByteArray());
                        return clientExtensions;
                    }

                    @Override
                    public TlsAuthentication getAuthentication() throws IOException {
                        return new TlsAuthentication() {

                            @Override
                            public void notifyServerCertificate(Certificate serverCertificate) throws IOException {
                                try {
                                    KeyStore ks = _loadKeyStore();

                                    CertificateFactory cf = CertificateFactory.getInstance("X.509");
                                    List<java.security.cert.Certificate> certs = new LinkedList<java.security.cert.Certificate>();
                                    boolean trustedCertificate = false;
                                    for ( org.bouncycastle.asn1.x509.Certificate c : serverCertificate.getCertificateList()) {
                                        java.security.cert.Certificate cert = cf.generateCertificate(new ByteArrayInputStream(c.getEncoded()));
                                        certs.add(cert);

                                        String alias = ks.getCertificateAlias(cert);
                                        if(alias != null) {
                                            if (cert instanceof java.security.cert.X509Certificate) {
                                                try {
                                                    ( (java.security.cert.X509Certificate) cert).checkValidity();
                                                    trustedCertificate = true;
                                                } catch(CertificateExpiredException cee) {
                                                   // Accept all the certs!
                                                }
                                            }
                                        } else {
                                            // Accept all the certs!
                                        }

                                    }
                                    if (!trustedCertificate) {
                                        // Accept all the certs!
                                    }
                                    peertCerts = certs.toArray(new java.security.cert.Certificate[0]);
                                } catch (Exception ex) {
                                    ex.printStackTrace();
                                    throw new IOException(ex);
                                }
                            }

                            @Override
                            public TlsCredentials getClientCredentials(CertificateRequest certificateRequest) throws IOException {
                                return null;
                            }

                            private KeyStore _loadKeyStore() throws Exception {
                                FileInputStream trustStoreFis = null;
                                try {
                                    KeyStore localKeyStore = null;

                                    String trustStoreType = System.getProperty("javax.net.ssl.trustStoreType")!=null?System.getProperty("javax.net.ssl.trustStoreType"):KeyStore.getDefaultType();
                                    String trustStoreProvider = System.getProperty("javax.net.ssl.trustStoreProvider")!=null?System.getProperty("javax.net.ssl.trustStoreProvider"):"";

                                    if (trustStoreType.length() != 0) {
                                        if (trustStoreProvider.length() == 0) {
                                            localKeyStore = KeyStore.getInstance(trustStoreType);
                                        } else {
                                            localKeyStore = KeyStore.getInstance(trustStoreType, trustStoreProvider);
                                        }

                                        char[] keyStorePass = null;
                                        String str5 = System.getProperty("javax.net.ssl.trustStorePassword")!=null?System.getProperty("javax.net.ssl.trustStorePassword"):"";

                                        if (str5.length() != 0) {
                                            keyStorePass = str5.toCharArray();
                                        }

                                        localKeyStore.load(trustStoreFis, keyStorePass);

                                        if (keyStorePass != null) {
                                            for (int i = 0; i < keyStorePass.length; i++) {
                                                keyStorePass[i] = 0;
                                            }
                                        }
                                    }
                                    return localKeyStore;
                                } finally {
                                    if (trustStoreFis != null) {
                                        trustStoreFis.close();
                                    }
                                }
                            }
                        };
                    }

                });
            } // startHandshake
        };
    }
}

Затем вам нужно использовать новую фабрику сокетов. Если вы используете Spring для обработки вызовов веб-служб, вы можете расширить существующий HttpUrlConnectionMessageSender, чтобы отправлять запросы за вас. Просто не забудьте обновить bean-компонент отправителя сообщения в конфигурации Spring, чтобы использовать этот класс.

import java.io.IOException;
import java.net.*;

import javax.net.ssl.HttpsURLConnection;

import org.springframework.ws.transport.WebServiceConnection;
import org.springframework.ws.transport.http.*;


public class HttpsUrlConnectionMessageSender extends HttpUrlConnectionMessageSender {

    @Override
    public WebServiceConnection createConnection(URI uri) throws IOException {

        URL url = uri.toURL();
        URLConnection connection = url.openConnection();
        if (!(connection instanceof HttpsURLConnection)) {
            throw new HttpTransportException("URI [" + uri + "] is not an HTTPS URL");
        } else {
            HttpsURLConnection httpsURLConnection = (HttpsURLConnection) connection;
            httpsURLConnection.setSSLSocketFactory(new TLSSocketConnectionFactory());
            prepareConnection(httpsURLConnection);
            return new HttpsUrlConnection(httpsURLConnection);
        }
    }

    private static class HttpsUrlConnection extends HttpUrlConnection {
        private HttpsUrlConnection(HttpsURLConnection connection) {
            super(connection);
        }
    }
}

В качестве альтернативы, если вы не используете Spring или Rest, вы можете вручную создать HttpsURLConnection, как это сделано в классе выше, и отправлять свои сообщения, используя поток вывода.

Помните, что если служба, с которой вы общаетесь, использует файлы cookie, вам необходимо установить общесистемный менеджер файлов cookie. Однако диспетчер файлов cookie Java 6 содержит ошибки. Вам может потребоваться скопировать исходный код из Java 8 CookieManager, чтобы ваши файлы cookie работали.

if (CookieHandler.getDefault() == null) {
    CookieHandler.setDefault(new CookieManager());
}
person zalpha314    schedule 01.02.2016

Основная проблема, с которой вы столкнетесь с Bouncy Castle, заключается в том, что его реализация TLS не реализует JSSE API. На практике это означает, что вы не получите никакой поддержки для SSLSocket, SSLSocketFactory (или HttpsURLConnection, но это не имеет значения, когда вы используете HTTP-клиент Apache).

Кроме того, TLS - не самая лучшая задокументированная функция Bouncy Castle, которая усложнит вашу работу. Примеры приведены в ответах на этот другой вопрос.

Вы могли бы каким-то образом написать свою собственную Socket реализацию, которая обертывает необходимые вызовы TLS API Bouncy Castle. Затем вы можете вернуть такие сокеты из своей пользовательской реализации org.apache.http.conn.ssl.SSLConnectionSocketFactory (вы, вероятно, можете использовать фиктивные значения в конструкторах, если ваш подкласс делает что-то совершенно другое для своего метода, который в любом случае возвращает Socket).

Это может быть осуществимо, но, вероятно, потребует значительных усилий. Обновление до Java 8 (который поддерживает TLS 1.2 с традиционными классами JSSE), как правило, будет проще (в зависимости от ваших ограничений).

person Bruno    schedule 19.01.2016
comment
Возможно, будет проще (не говоря уже о том, что это очень необходимо) обновить jdk 8. Я знаю, что у моего проекта будут проблемы с совместимостью, но я начну исследовать. Спасибо за ответ. - person zalpha314; 20.01.2016