Расширенное имя_сервера (расширение SNI), не отправленное с помощью jdk1.8.0, но отправленное с помощью jdk1.7.0

Я реализовал клиент JAX-WS с помощью ApacheCXF (v3.0.4), и все работает успешно, но проблема возникает, когда я хочу использовать безопасное соединение (SSL/TLS) с java 8 (jdk1.8.0_25).

В log (-Djavax.net.debug = all) я вижу следующее исключение:

main, handling exception: java.net.SocketException: Connection reset
main, SEND TLSv1.2 ALERT:  fatal, description =    unexpected_message
main, WRITE: TLSv1.2 Alert, length = 2
main, Exception sending alert: java.net.SocketException: Connection reset by peer: socket write error

После анализа депилятора я заметил, что проблема вызвана тем, что с Java 8 имя_сервера (SNI) не отправляется, а с Java 7 оно отправляется, и вызов веб-службы работает успешно.

Журнал Java 8 (-Djavax.net.debug = все): Отсутствует имя "Extension server_name"

[...]
Compression Methods:  { 0 }
Extension elliptic_curves, curve names: {secp256r1, sect163k1, sect163r2, secp192r1, secp224r1, sect233k1, sect233r1, sect283k1, sect283r1, secp384r1, sect409k1, sect409r1, secp521r1, sect571k1, sect571r1, secp160k1, secp160r1, secp160r2, sect163r1, secp192k1, sect193r1, sect193r2, secp224k1, sect239k1, secp256k1}
Extension ec_point_formats, formats: [uncompressed]
Extension signature_algorithms, signature_algorithms: SHA512withECDSA, SHA512withRSA, SHA384withECDSA, SHA384withRSA, SHA256withECDSA, SHA256withRSA, SHA224withECDSA, SHA224withRSA, SHA1withECDSA, SHA1withRSA, SHA1withDSA, MD5withRSA
***
[...]

Java 7 log (-Djavax.net.debug = все) (работает): "Расширение server_name" установлено

[...]
Compression Methods:  { 0 }
Extension elliptic_curves, curve names: {secp256r1, sect163k1, sect163r2, secp192r1, secp224r1, sect233k1, sect233r1, sect283k1, sect283r1, secp384r1, sect409k1, sect409r1, secp521r1, sect571k1, sect571r1, secp160k1, secp160r1, secp160r2, sect163r1, secp192k1, sect193r1, sect193r2, secp224k1, sect239k1, secp256k1}
Extension ec_point_formats, formats: [uncompressed]
Extension signature_algorithms, signature_algorithms: SHA512withECDSA, SHA512withRSA, SHA384withECDSA, SHA384withRSA, SHA256withECDSA, SHA256withRSA, SHA224withECDSA, SHA224withRSA, SHA1withECDSA, SHA1withRSA, SHA1withDSA, MD5withRSA
Extension server_name, server_name: [host_name: testeo.hostname.es]
***
[...]

Отмечается, что с Java 7 установлено расширение имя_страницы, имя_сервера: [имя_хоста: testeo.hostname.es], а затем вызов веб-службы работает успешно.

Почему Java 8 не установил имя_сервера как Java 7? Это проблема конфигурации Java?

Ответы

Ответ 1

Как уже упоминалось, причина связана с ошибкой JDK, где использование setHostnameVerifier() прерывает SNI (имя сервера расширения). https://bugs.openjdk.java.net/browse/JDK-8144566

Наше решение: После тестирования мы обнаружили, что установка соединения SSLSocketFactory практически на все, что связано с дефолтом, по-видимому, устраняет проблему.

Это не работает: HttpsURLConnection.setSSLSocketFactory((SSLSocketFactory) SSLSocketFactory.getDefault());

Это работает: HttpsURLConnection.setSSLSocketFactory(new SSLSocketFactoryFacade());

Итак, чтобы исправить его для клиента JAX-WS, вы можете сделать что-то вроде этого: bindingProvider.getRequestContext().put("com.sun.xml.internal.ws.transport.https.client.SSLSocketFactory", new SSLSocketFactoryFacade());

Наш фасад SSLSocketFactory: (Обратите внимание, что это действительно ничего не делает)

public class SSLSocketFactoryFacade extends SSLSocketFactory {

    SSLSocketFactory sslsf;

    public SSLSocketFactoryFacade() {
        sslsf = (SSLSocketFactory) SSLSocketFactory.getDefault();;
    }

    @Override
    public String[] getDefaultCipherSuites() {
        return sslsf.getDefaultCipherSuites();
    }

    @Override
    public String[] getSupportedCipherSuites() {
        return sslsf.getSupportedCipherSuites();
    }

    @Override
    public Socket createSocket(Socket socket, String s, int i, boolean b) throws IOException {
        return sslsf.createSocket(socket, s, i, b);
    }

    @Override
    public Socket createSocket(String s, int i) throws IOException, UnknownHostException {
        return sslsf.createSocket(s, i);
    }

    @Override
    public Socket createSocket(String s, int i, InetAddress inetAddress, int i1) throws IOException, UnknownHostException {
        return sslsf.createSocket(s, i, inetAddress, i1);
    }

    @Override
    public Socket createSocket(InetAddress inetAddress, int i) throws IOException {
        return createSocket(inetAddress, i);
    }

    @Override
    public Socket createSocket(InetAddress inetAddress, int i, InetAddress inetAddress1, int i1) throws IOException {
        return createSocket(inetAddress, i, inetAddress1, i1);
    }
}

Ответ 2

Вы, или лежащие в основе libs (это делает WS lib), могут использовать setHostnameVerifier (..)

В java8 есть ошибка: если используется setHostnameVerifier (..), SNI не выполняется с клиентской стороны.

https://bugs.openjdk.java.net/browse/JDK-8072464

Ответ 3

Пожалуйста, используйте JDK-версию 8u141 и выше, где эта проблема исправлена. Пожалуйста, просмотрите JDK 8u141 Bugs Fixes для более подробной информации

Ответ 4

Прежде всего, это свойство "server_name" ассоциируется с расширением SNI (Server Name Indicication). В документации на Java 8 JSSE говорится об этом здесь.

Документация включает примерный код, который показывает, как установить отправленные имена серверов. Код предназначен для Java 8.

Однако я не могу понять, почему (по-видимому) Java 7 устанавливает имя сервера по умолчанию, а Java 8 - нет. (Простым способом понять это будет использование отладчика, чтобы выяснить, как создается и инициализируется объект движка SSL.)

Ответ 5

Я попробовал решение, предоставленное Бенджамином Парри, но оно не сработало для меня. Немного покопавшись, я также нашел это решение, которое выглядит очень похоже, однако SSLSocketFactoryFacade вручную вставляет правильный заголовок SSL вместо того, чтобы быть чистым проходом. Ниже приведен мой окончательный код, который немного отличается, но следует отдать должное Гиришу Камату на javabreaks за основную идею:

    private static class SSLSocketFactoryFacade extends SSLSocketFactory {
    private SSLSocketFactory sslsf;
    private SSLParameters sslParameters;

    public SSLSocketFactoryFacade(String hostName) {
        sslParameters = new SSLParameters();
        sslParameters.setServerNames(Arrays.asList(new SNIHostName(hostName)));
        sslsf = (SSLSocketFactory) SSLSocketFactory.getDefault();;
    }

    public Socket createSocket() throws IOException {
        Socket socket = sslsf.createSocket();
        ((SSLSocket) socket).setSSLParameters(sslParameters);
        return socket;
    }

    public Socket createSocket(InetAddress arg0, int arg1, InetAddress arg2, int arg3) throws IOException {
        Socket socket = sslsf.createSocket(arg0, arg1, arg2, arg3);
        ((SSLSocket) socket).setSSLParameters(sslParameters);
        return socket;
    }

    public Socket createSocket(InetAddress arg0, int arg1) throws IOException {
        Socket socket = sslsf.createSocket(arg0, arg1);
        ((SSLSocket) socket).setSSLParameters(sslParameters);
        return socket;
    }

    public Socket createSocket(Socket arg0, InputStream arg1, boolean arg2) throws IOException {
        Socket socket = sslsf.createSocket(arg0, arg1, arg2);
        ((SSLSocket) socket).setSSLParameters(sslParameters);
        return socket;
    }

    public Socket createSocket(Socket arg0, String arg1, int arg2, boolean arg3) throws IOException {
        Socket socket = sslsf.createSocket(arg0, arg1, arg2, arg3);
        ((SSLSocket) socket).setSSLParameters(sslParameters);
        return socket;
    }

    public Socket createSocket(String arg0, int arg1, InetAddress arg2, int arg3)
            throws IOException, UnknownHostException {
        Socket socket = sslsf.createSocket(arg0, arg1, arg2, arg3);
        ((SSLSocket) socket).setSSLParameters(sslParameters);
        return socket;
    }

    public Socket createSocket(String arg0, int arg1) throws IOException, UnknownHostException {
        Socket socket = sslsf.createSocket(arg0, arg1);
        ((SSLSocket) socket).setSSLParameters(sslParameters);
        return socket;
    }

    public String[] getDefaultCipherSuites() {
        return sslsf.getDefaultCipherSuites();
    }

    public String[] getSupportedCipherSuites() {
        return sslsf.getSupportedCipherSuites();
    }
}

И тогда я могу позвонить

sslConnection.setSSLSocketFactory(new SSLSocketFactoryFacade(sslConnection.getURL().getHost()));

где sslConnection - соединение HttpsURLConnection.