Игнорирование проверки SSL в Java

Мне нужно вызвать службу HTTP, размещенную на веб-сервере, с недопустимым SSL-сертификатом. В dev я импортирую сертификат с помощью keytool, но при каждой установке клиента сертификат будет отличаться, поэтому я не могу просто связать его.

Предисловие: я DO знает, что пропуская проверку SSL действительно уродливо. В этом конкретном случае я бы даже не нуждался в SSL, а все другие сообщения в системе - это простой HTTP. Поэтому мне действительно не нравятся атаки MITM или такие. Злоумышленнику не нужно заходить так далеко, чтобы разбить SSL, потому что для данных не существует SSL. Это поддержка устаревшей системы, над которой я не контролирую.

Я использую HttpURLConnection с SSLSocketFactory, который имеет NaiveTrustManager и NaiveHostnameVerifier. Это работает на некоторых самоподписанных серверах, которые я пытался, но не на сайте клиента. Ошибка, которую я получаю:

javax.net.ssl.SSLKeyException: [Security:090477]Certificate chain received from xxxxxxxxxx was not trusted causing SSL handshake failure.
    at com.certicom.tls.interfaceimpl.TLSConnectionImpl.fireException(Unknown Source)
    at com.certicom.tls.interfaceimpl.TLSConnectionImpl.fireAlertSent(Unknown Source)
    at com.certicom.tls.record.handshake.HandshakeHandler.fireAlert(Unknown Source)
    at com.certicom.tls.record.handshake.HandshakeHandler.fireAlert(Unknown Source)
    at com.certicom.tls.record.handshake.ClientStateReceivedServerHello.handle(Unknown Source)
    at com.certicom.tls.record.handshake.HandshakeHandler.handleHandshakeMessage(Unknown Source)
    at com.certicom.tls.record.handshake.HandshakeHandler.handleHandshakeMessages(Unknown Source)
    at com.certicom.tls.record.MessageInterpreter.interpretContent(Unknown Source)
    at com.certicom.tls.record.MessageInterpreter.decryptMessage(Unknown Source)
    at com.certicom.tls.record.ReadHandler.processRecord(Unknown Source)
    at com.certicom.tls.record.ReadHandler.readRecord(Unknown Source)
    at com.certicom.tls.record.ReadHandler.readUntilHandshakeComplete(Unknown Source)
    at com.certicom.tls.interfaceimpl.TLSConnectionImpl.completeHandshake(Unknown Source)
    at com.certicom.tls.record.WriteHandler.write(Unknown Source)
    at com.certicom.io.OutputSSLIOStreamWrapper.write(Unknown Source)
    at java.io.BufferedOutputStream.flushBuffer(BufferedOutputStream.java:65)
    at java.io.BufferedOutputStream.flush(BufferedOutputStream.java:123)
    at java.io.FilterOutputStream.flush(FilterOutputStream.java:123)
    at weblogic.net.http.HttpURLConnection.writeRequests(HttpURLConnection.java:154)
    at weblogic.net.http.HttpURLConnection.getInputStream(HttpURLConnection.java:358)
    at weblogic.net.http.SOAPHttpsURLConnection.getInputStream(SOAPHttpsURLConnection.java:37)
    at weblogic.net.http.HttpURLConnection.getResponseCode(HttpURLConnection.java:947)
    at (my own code)

Мой SimpleSocketFactory выглядит следующим образом:

public static final SSLSocketFactory getSocketFactory()
{
    if ( sslSocketFactory == null ) {
        try {
            // get ssl context
            SSLContext sc = SSLContext.getInstance("SSL");

            // Create a trust manager that does not validate certificate chains
            TrustManager[] trustAllCerts = new TrustManager[]{
                new NaiveTrustManager() {
                    public java.security.cert.X509Certificate[] getAcceptedIssuers() {
                        log.debug("getAcceptedIssuers");
                        return new java.security.cert.X509Certificate[0];
                    }
                    public void checkClientTrusted(
                        java.security.cert.X509Certificate[] certs, String authType) {
                        log.debug("checkClientTrusted");
                    }
                    public void checkServerTrusted(
                        java.security.cert.X509Certificate[] certs, String authType) {
                        log.debug("checkServerTrusted");
                    }
                }
            };

            sc.init(null, trustAllCerts, new java.security.SecureRandom());
            // EDIT: fixed the following line that was redeclaring SSLSocketFactory sslSocketFactory, returning null every time. Same result though.
            sslSocketFactory = sc.getSocketFactory();

            HttpsURLConnection.setDefaultSSLSocketFactory(sslSocketFactory);
            // EDIT: The following line has no effect
            //HttpsURLConnection.setDefaultHostnameVerifier(new NaiveHostNameVerifier());

        } catch (KeyManagementException e) {
            log.error ("No SSL algorithm support: " + e.getMessage(), e);
        } catch (NoSuchAlgorithmException e) {
            log.error ("Exception when setting up the Naive key management.", e);
        }
    }
    return sslSocketFactory;
}

NaiveHostnameVerifier имеет способ ограничить допустимые хосты, но он оставил нуль, поэтому в основном принимаем что-либо:

public class NaiveHostnameVerifier implements HostnameVerifier {
    String[] patterns;

    public NaiveHostnameVerifier () {
        this.patterns=null;
    }

    public NaiveHostnameVerifier (String[] patterns) {
        this.patterns = patterns;
    }

    public boolean verify(String urlHostName,SSLSession session) {
        if (patterns==null || patterns.length==0) {
            return true;
        } else {
            for (String pattern : patterns) {
                if (urlHostName.matches(pattern)) {
                    return true;
                }
            }
            return false;
        }
    }
}

Использование таково:

    try {
        conn = (HttpURLConnection)url.openConnection();
        if (conn instanceof HttpsURLConnection) {
                ((HttpsURLConnection)conn).setSSLSocketFactory(SimpleSSLSocketFactory.getSocketFactory());
                // EDIT: added this line, the HV has to be set on connection, not on the factory.
                ((HttpsURLConnection)conn).setHostnameVerifier(new NaiveHostnameVerifier());
        }
        conn.setDoInput(true);
        conn.setDoOutput(true);
        conn.setRequestMethod("POST");
        conn.setRequestProperty("Content-type","application/x-www-form-urlencoded");
        conn.connect();

        StringBuffer sbContent = new StringBuffer();
        // (snip)
        DataOutputStream stream = new DataOutputStream(conn.getOutputStream ());
        stream.writeBytes(sbContent.toString());
        stream.flush();
        stream.close();
    } catch (ClassCastException e) {
        log.error("The URL does not seem to point to a HTTP connection");
        return null;
    } catch (IOException e) {
        log.error("Error accessing the requested URL", e);
        return null;
    }

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

Любая идея?

Ответ 1

На самом деле нет ничего плохого в коде выше. Проблема, похоже, связана с Weblogic и этим модулем Certicom TLS. Когда я смотрю параметры сервера, SSL и Advanced, я вижу, что могу указать собственный HostnameVerifier (SSLMBean.HostnameVerifier), но единственный элемент, предлагающий возможность помешать проверке сертификата, устарел.

Я попробовал вышеуказанный код за пределами Weblogic, и он работал красиво (исправлено HostnameVerifier в сообщении, хотя).

Затем я попытался добавить "-DUseSunHttpHandler = true" к параметрам Weblogic, как предложил ipolevoy в этот другой вопрос. Он начал работать.

Говоря это, переключение HTTP-обработчика на сервер Oracle Service Bus кажется немного рискованным. Там могут быть побочные эффекты, которые возвращаются, чтобы укусить меня через несколько недель...

Я также попытался определить собственное хранилище trustStore и указать его на jssecacert, в котором содержится необходимый ключ. Он также игнорировался Weblogic, потому что у него есть собственный параметр trustStore для каждого сервера. Поэтому я прибегаю к тому, чтобы администратор вручную импортировал нужные ключи или указал Weblogic в свой собственный магазин.

Ответ 2

На самом деле, это ошибка в версии Weblogic ниже 10.3.5, для которой есть доступный патч от Oracle. Подробнее см. Документ 1474989.1 в разделе "Поддержка моего Oracle".

Исправление выше - это не рекомендованное (но поддерживаемое) обходное решение Oracle, которое будет работать, но не является предпочтительным решением.

Предпочтительным решением является загрузка исправления, упомянутого в статье Oracle, и замена верификатора имени хоста SSL на новый, который также является частью Weblogic 10.3.5 и выше. Если вы хотите оставаться совместимым с Oracle в плане поддержки, это путь.