为什么 SSL 服务器套接字连接会在 Java 中阻塞,而非 SSL 服务器套接字不会?

Why does an SSL Server Socket connection block in Java whereas a non SSL Server Socket does not?

考虑一个线程上的非 SSL 套接字服务器和客户端的以下代码:

import java.io.DataInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class ServerClient {
    public static void main(String[] args) throws IOException {
        ServerSocket ss = new ServerSocket(0); // open a random free port.

        Socket c = new Socket(ss.getInetAddress(), ss.getLocalPort());

        Socket s = ss.accept();

        final byte[] bytes = "Hello World!".getBytes();
        final OutputStream out = c.getOutputStream();
        System.out.println("writing to stream");
        out.write(bytes.length);
        out.write(bytes);

        System.out.println("reading from stream");

        final DataInputStream in = new DataInputStream(s.getInputStream());
        int len = in.read();
        final byte[] b = new byte[len];
        in.readFully(b);
        System.out.println(new String(b));

        c.close();
        ss.close();
    }
}

这会产生以下输出:

writing to stream
reading from stream
Hello World!

此进程打开了一个服务器套接字 - 与客户端套接字连接。将数据传递到套接字,然后关闭。传递数据没有问题。

考虑使用 SSL 套接字证明观点的版本:

import javax.net.ssl.SSLServerSocket;
import javax.net.ssl.SSLServerSocketFactory;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
import java.util.logging.Logger;

public class SSLServerClient {

    private static Logger log = Logger.getLogger("InfoLogging");

    public static void main(String[] args) throws IOException {

        System.setProperty("javax.net.ssl.keyStore", "/path/KeyStore.jks");
        System.setProperty("javax.net.ssl.keyStorePassword", "password");

        SSLServerSocketFactory sslserversocketfactory = (SSLServerSocketFactory) SSLServerSocketFactory.getDefault();

        SSLServerSocket serverListeningSSLSocket = (SSLServerSocket) sslserversocketfactory.createServerSocket(4380);
        log.info("Server started");

        SSLSocketFactory sslSocketFactory=(SSLSocketFactory) SSLSocketFactory.getDefault();
        SSLSocket clientSocket =  (SSLSocket) sslSocketFactory.createSocket(serverListeningSSLSocket.getInetAddress(),
                serverListeningSSLSocket.getLocalPort());

        SSLSocket serverCommsSSLSocket = (SSLSocket) serverListeningSSLSocket.accept();
        log.info("new client");

        final byte[] bytes = "Hello World!".getBytes();
        final OutputStream out = clientSocket.getOutputStream();
        System.out.println("writing to stream");
        out.write(bytes.length);
        out.write(bytes);

        System.out.println("reading from stream");

        final DataInputStream in = new DataInputStream(serverCommsSSLSocket.getInputStream());
        int len = in.read();
        final byte[] b = new byte[len];
        in.readFully(b);
        System.out.println(new String(b));

        clientSocket.close();
        serverCommsSSLSocket.close();
        serverListeningSSLSocket.close();
    }
}

这给出了以下输出:

Nov 21, 2018 10:23:51 PM com.gamble.ssl.SSLServerClient main INFO: Server started
Nov 21, 2018 10:23:52 PM com.gamble.ssl.SSLServerClient main INFO: new client
writing to stream

即它在服务器套接字启动时阻塞。

我的问题是:为什么 SSL 服务器套接字连接会在 Java 中阻塞,而非 SSL 服务器套接字则不会?

"flip to plaintext?" 是什么意思?

这是您正在做的事情:

  • 创建 SSL 服务器套接字
  • 创建连接到 SSL 服务器套接字的普通套接字
  • 从客户端发送一些数据
  • 正在服务器端读取一些数据

现在,SSL 套接字需要加密数据传输。但是您未发送的数据未加密,因此它不是有效的 SSL 并且会引发异常 - 正如它所说,"Unrecognized SSL message" 因为您没有发送有效的 SSL 消息,而 "plaintext connection?" 是一个提示关于可能出错的地方。

您必须在连接的两端都使用 SSL,否则它们之间无法正常通信。

问题的简单答案是:

Because an SSL Handshake is asynchronous and the non SSL socket doesn't have to do that step.

好的 - 原始 SSL 代码有两个问题:

  1. 没有信任库:

    System.setProperty( "javax.net.ssl.trustStore", "/path/KeyStore.jks");
    
  2. 因为握手是同步的 - 您必须在不同的线程上启动客户端。即:

    (new Thread() {
      public void run() {
        // do stuff
      }
     }).start();
    

因此代码如下所示:

    import javax.net.ssl.SSLServerSocket;
    import javax.net.ssl.SSLServerSocketFactory;
    import javax.net.ssl.SSLSocket;
    import javax.net.ssl.SSLSocketFactory;
    import java.io.DataInputStream;
    import java.io.IOException;
    import java.io.OutputStream;
    import java.util.logging.Logger;

    public class SSLServerClient {

        public static void main(String[] args) throws IOException {

            System.setProperty("javax.net.ssl.keyStore", "/path/KeyStore.jks");
            System.setProperty( "javax.net.ssl.trustStore", "/path/KeyStore.jks");

            System.setProperty("javax.net.ssl.keyStorePassword", "password");

            SSLServerSocketFactory sslserversocketfactory = (SSLServerSocketFactory) SSLServerSocketFactory.getDefault();

            SSLServerSocket serverListeningSSLSocket = (SSLServerSocket) sslserversocketfactory.createServerSocket(4380);
            System.out.println("--server started");

            SSLSocketFactory sslSocketFactory=(SSLSocketFactory) SSLSocketFactory.getDefault();
            SSLSocket clientSocket =  (SSLSocket) sslSocketFactory.createSocket(serverListeningSSLSocket.getInetAddress(),
                    serverListeningSSLSocket.getLocalPort());

            SSLSocket serverCommsSSLSocket = (SSLSocket) serverListeningSSLSocket.accept();
            System.out.println("--new client");

            final byte[] bytes = "--Hello World!".getBytes();
            final OutputStream out = clientSocket.getOutputStream();
            System.out.println("--Gotten output stream");
            final DataInputStream in = new DataInputStream(serverCommsSSLSocket.getInputStream());

            (new Thread() {
                public void run() {
                    System.out.println("--reading from stream");

                    int len = 0;
                    try {
                        len = in.read();
                        final byte[] b = new byte[len];
                        in.readFully(b);
                        System.out.println(new String(b));

                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }).start();


            System.out.println("--writing to stream");
            out.write(bytes.length);
            System.out.println("--writing to stream - length");
            out.write(bytes);

            clientSocket.close();
            serverCommsSSLSocket.close();
            serverListeningSSLSocket.close();
        }
    }

输出看起来像

    --server started
    --new client
    --Gotten output stream
    --writing to stream
    --reading from stream
    --writing to stream - length
    --Hello World!

    Process finished with exit code 0

完成

(注意我在输出中添加了前导 -- 以帮助读取 SSL 调试输出。