【问题标题】:Ignoring SSL validation in Java忽略 Java 中的 SSL 验证
【发布时间】:2012-11-08 09:21:38
【问题描述】:

我必须使用无效的 SSL 证书调用托管在 Web 服务器上的 HTTP 服务。在开发中,我使用 keytool 导入证书,但证书在每个客户端安装时都会有所不同,所以我不能只是捆绑它。

前言:我确实知道跳过 SSL 验证真的很难看。在这种特定情况下,我什至不需要 SSL,系统中的所有其他通信都通过简单的 HTTP。所以我真的不在乎 MITM 攻击之类的。攻击者不需要破坏 SSL,因为数据没有 SSL。这是对我无法控制的旧系统的支持。

我将HttpURLConnectionSSLSocketFactory 一起使用,后者具有NaiveTrustManagerNaiveHostnameVerifier。这适用于我尝试过的一些自签名服务器,但不适用于客户的站点。我得到的错误是:

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 代码忽略无效的证书。

有什么想法吗?

【问题讨论】:

  • 您是否意识到忽略证书(和主机名)验证会打开与潜在 MITM 攻击的连接?如果您必须忽略证书错误,您的(管理)安全程序似乎存在缺陷。此外,您应该查看 Certicom TLS 选项,因为您显然没有使用默认的 JSSE 提供程序。
  • 我很清楚 MITM 攻击。事实上,如果有人能够对该网络进行 MITM 攻击,我们面临的问题比他拦截通信要大得多。起初,我正在考虑在软件中检索证书的步骤,如果经过用户验证,则将其存储以供进一步使用。然而,与我试图解决的小问题相比,这是一个更大的挑战。我会看看 Certicom TLS 选项。
  • WebLogic 用 SSL 做了可怕的事情,我很遗憾你不得不尝试处理它。他们不遵循 Java 标准,有自己的搞砸和破坏的方式来搞砸一切。
  • 您非常担心有人可以窃听,但您也假设他们无法更改流量 (MITM)。如果你担心前者,我不确定我会对后者做出这个假设。 (如果你真的只是想防止窃听,而不是 MITM,最好使用匿名密码套件,而不是证书。)
  • @Bruno 你误解了我的意思。我不在乎偷听。完全没有。在同一个网络上,还有更重要的通信未加密。因此,如果有人能够在那里放置 MITM,那么他甚至不需要走那么远就能获得多汁的数据。这就是为什么我不关心这个 SSL 证书的原因。

标签: java ssl weblogic


【解决方案1】:

其实上面的代码没有错。问题似乎在于 Weblogic 和这个 Certicom TLS 模块。当我查看 server 选项、SSLAdvanced 时,我发现我可以指定一个自定义 HostnameVerifier (SSLMBean.HostnameVerifier) 但唯一的元素暗示干扰证书验证的能力已被弃用。

我在 Weblogic 之外尝试了上面的代码,它运行良好(虽然修复了帖子中的 HostnameVerifier)。

然后我尝试按照 ipolevoy 在this other question 中的建议将“-DUseSunHttpHandler=true”添加到 Weblogic 参数中。它开始工作了。

话虽如此,在 Oracle Service Bus 服务器上切换 HTTP 处理程序似乎有点冒险。几周后很可能会有副作用回来咬我......

我还尝试定义自己的 trustStore 并将其指向包含所需密钥的 jssecacert。 Weblogic 也忽略了它,因为它对每个服务器都有自己的 trustStore 设置。所以我求助于要求管理员手动导入所需的密钥或将 Weblogic 指向我自己的商店。

【讨论】:

  • 顺便说一句,我刚刚发现 HostnameVerifier 也无法自定义并且失败并出现“javax.net.ssl.SSLKeyException: [Security:090504]Certificate chain received from intranet.company.com - xxxx 主机名验证检查失败。证书包含 *.company.com 但检查预期的 Intranet.company.com”。 Weblogic,我恨你。真的。
【解决方案2】:

实际上,这是 10.3.5 以下 Weblogic 版本中的一个已知错误,Oracle 提供了一个补丁来解决这个问题。有关详细信息,请参阅 My Oracle Support 中的文档 1474989.1。

上述修复是 Oracle 不推荐(但受支持)的解决方法,它会起作用,但不是首选解决方案。

首选的解决方案是下载 Oracle 文章中提到的补丁,并将 SSL 主机名验证器替换为 Weblogic 10.3.5 及更高版本中的新补丁。如果您希望在支持方面保持与 Oracle 的合规性,这就是您要走的路。

【讨论】:

    猜你喜欢
    • 2016-08-04
    • 2019-09-02
    • 1970-01-01
    • 2012-01-31
    • 1970-01-01
    • 2021-03-26
    • 2021-01-19
    • 2014-09-07
    • 1970-01-01
    相关资源
    最近更新 更多