首页 > 解决方案 > 官方的 Oracle SSLSocketClient.java 演示代码不安全吗?

问题描述

从此链接,给出了 SSLSocketClient.java 的演示:

import java.net.*;
import java.io.*;
import javax.net.ssl.*;

/*
 * This example demostrates how to use a SSLSocket as client to
 * send a HTTP request and get response from an HTTPS server.
 * It assumes that the client is not behind a firewall
 */

public class SSLSocketClient {

    public static void main(String[] args) throws Exception {
        try {
            SSLSocketFactory factory =
                (SSLSocketFactory)SSLSocketFactory.getDefault();
            SSLSocket socket =
                (SSLSocket)factory.createSocket("www.verisign.com", 443);

            /*
             * send http request
             *
             * Before any application data is sent or received, the
             * SSL socket will do SSL handshaking first to set up
             * the security attributes.
             *
             * SSL handshaking can be initiated by either flushing data
             * down the pipe, or by starting the handshaking by hand.
             *
             * Handshaking is started manually in this example because
             * PrintWriter catches all IOExceptions (including
             * SSLExceptions), sets an internal error flag, and then
             * returns without rethrowing the exception.
             *
             * Unfortunately, this means any error messages are lost,
             * which caused lots of confusion for others using this
             * code.  The only way to tell there was an error is to call
             * PrintWriter.checkError().
             */
            socket.startHandshake();

            PrintWriter out = new PrintWriter(
                                  new BufferedWriter(
                                  new OutputStreamWriter(
                                  socket.getOutputStream())));

            out.println("GET / HTTP/1.0");
            out.println();
            out.flush();

            /*
             * Make sure there were no surprises
             */
            if (out.checkError())
                System.out.println(
                    "SSLSocketClient:  java.io.PrintWriter error");

            /* read response */
            BufferedReader in = new BufferedReader(
                                    new InputStreamReader(
                                    socket.getInputStream()));

            String inputLine;
            while ((inputLine = in.readLine()) != null)
                System.out.println(inputLine);

            in.close();
            out.close();
            socket.close();

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

我有两个问题:

  1. 根据这个官方文档,如果我们使用的是原始 SSLSocketFactory 而不是 HttpsURLConnection,则在握手过程中不会强制执行主机名验证。因此,主机名验证应手动完成。

使用原始 SSLSocket 和 SSLEngine 类时,您应该始终在发送任何数据之前检查对等方的凭据。SSLSocket 和 SSLEngine 类不会自动验证 URL 中的主机名是否与对等方凭据中的主机名匹配。如果未验证主机名,则可能会通过 URL 欺骗来利用应用程序。从 JDK 7 开始,可以在 SSL/TLS 握手期间处理端点识别/验证过程。请参阅 SSLParameters.getEndpointIdentificationAlgorithm 方法。

这是否意味着演示不安全?

  1. 我看到了在 Java 7 中添加主机名验证的解决方案:

    SSLParameters sslParams = new SSLParameters(); sslParams.setEndpointIdentificationAlgorithm("HTTPS"); sslSocket.setSSLParameters(sslParams);

当算法指定为“HTTPS”时,握手将验证主机名。否则(仅使用原始 SSLSockeFactory 算法为空),根本没有调用主机名验证。我很好奇我可以按如下方式修复它:

SSLSocketFactory factory =
                    (SSLSocketFactory)SSLSocketFactory.getDefault();
SSLSocket socket =
                    (SSLSocket)factory.createSocket("www.verisign.com", 443);
HostnameVerifier hv = HttpsURLConnection.getDefaultHostnameVerifier();
if(!hv.verify(socket.getSession().getPeerHost(),socket.getSession())){
    threw CertificateException("Hostname does not match!")
}

我看到HttpsURLConnection.getDefaultHostnameVerifier()可以返回一个默认的 HostnameVerifier,我可以用它来做验证吗?我看到很多人在谈论使用自定义 HostnameVerifier。我不明白是否有默认的为什么我们需要自定义它?

标签: javassljsse

解决方案


边界作为答案,但评论时间太长了。

(1) 是的,对于 HTTPS(如您引用的段落之后的段落中所述),这是一个安全漏洞;可能此示例是在 Java 7 之前编写的,此后没有更新。您可以提交错误报告以供他们更新。(当然有一些使用 SSL/TLS 的应用程序验证主机名,例如 SNMPS 和 LDAPS,甚至没有URL,但仍然可以使用 Java JSSE 实现。)

(2) HTTP 错误或错误:

  • PrintWriter 使用因平台而异的 JVM 的 lineSeparator,但 HTTP 标准(RFC 2068、2616、7230)要求所有平台上的请求标头都使用 CRLF,尽管一些服务器(可能包括 google)将接受传统 Postel 之后的 just-LF格言“在你发送的东西上要保守,在你收到的东西上要自由”;

  • 读取端假定所有数据都是面向行的,并且不会被规范化 EOL 损坏,这对于 HTTP标头某些正文(如 text/html)是正确的,当请求没有接受(或接受编码)时,您将从大多数网络服务器获得),但不能保证;

  • 读取端还假设所有数据都可以安全地从 JVM 默认“字符集”解码和重新编码;这对于 HTTP 标头(实际上是 7 位 ASCII)是正确的,但不是很多/大多数主体:特别是处理 8859 或类似 UTF8 会破坏其中的大部分,而将 UTF8 处理为 8859 或 CP1252 会 mojibake 它。

(3) HTTP/1.0 已正式过时,尽管它仍然受到广泛支持,并且演示非常简单,所以我先放一张。


推荐阅读