打开和关闭不写入任何数据的SSLSocket时出错


问题内容

一个简单的服务器

listen = getServer();
Logger.getAnonymousLogger().info("Listening to "+listen.toString());
SSLSocket client = (SSLSocket)listen.accept();
// adding this line fixes everything - client.write(42);
client.close();

和一个简单的客户

SocketFactory sockMaker = SSLSocketFactory.getDefault();
Socket server = sockMaker.createSocket("localhost", 1443);
int retval = server.getInputStream().read();
assert retval == -1;
server.close();

如果我不向SSL套接字写入任何内容,则客户端会引发异常:

Exception in thread "main" javax.net.ssl.SSLException:\
  Received close_notify during handshake
at com.sun.net.ssl.internal.ssl.Alerts.getSSLException(Alerts.java:190)

我不知道为什么。SSL / TLS规范是否要求您将内容写入套接字?

请参阅完整示例


问题答案:

您不必这样写任何东西,但是如果立即关闭它,它将生成close_notify警报(尽管它被称为“警报”,但这是关闭TLS /
SSL套接字的常规方法的一部分)。

另外,SSL / TLS套接字被设计为“几乎”像普通的TCP套接字一样工作,但是由于SSL /
TLS的工作方式,有一些细节它们不能(也不能)。尤其是,在SSL / TLS连接开始时,会发生SSL /
TLS握手,这涉及在发送任何应用程序数据之前从每一方进行的多次读取/写入。

SSLSocket文档说:

可以通过以下三种方式之一启动此连接上的初始握手:

  • startHandshake明确开始握手的呼叫,或者
  • 尝试在此套接字上读取或写入应用程序数据会导致隐式握手,或者
  • getSession如果没有当前有效的会话,则调用尝试建立会话,并且隐式握手已完成。

本质上,getInputStream().read()您的示例中的客户端发起了握手,这导致服务器继续进行accept() 并在其一侧执行握手
。但是,由于您通常在服务器端将其关闭(通常是立即关闭),因此您甚至没有时间完成握手。因此,close_notify在握手期间发送,这会导致您得到异常。如果您尝试从服务器端进行读取或写入,则握手至少已经完成。

编辑 :在@EJP的评论之后,我应该澄清一下我的意思:

  • createSocket("localhost", 1443)客户端上的客户端建立连接,服务器通过接受连接accept()
  • getInputStream().read()在客户端使它发起握手。因此,它将ClientHelloTLS消息发送到服务器。
  • 因为服务器close()在接受套接字后立即使用,所以它会发送close_notify警报。因为服务器尚未开始读取/写入,所以它尚未开始握手(因此无法完成握手)。

需要注意的是目的ServerSocket.accept(),这SSLServerSocket工具,是创建一个SSLSocket,不一定用它做任何事情。该SSLServerSocket配置,但诉讼与握手的范围了。一方面,听起来像是使SSLSocket行为像普通的TCP套接字一样更加透明。另一方面,它意味着从底层TCP流中读取数据,因此会产生副作用。我还没有尝试过,但是仍然可以将SSLSocketby创建的SSLServerSocket配置为
client socket 。毕竟,如RFC
2246词汇表所述

“客户端:启动与服务器的TLS连接的应用程序实体。这可能或可能不意味着客户端启动了基础的传输连接。”
从API的角度来看,这绝对会影响透明度以及何时进行握手。

(编写用于SSL /
TLS套接字的API映射到普通TCP套接字的API是一项艰巨的工作,Java并不会为此做得太糟糕。真正的“乐趣”始于使用SSLEngine和NIO通道的异步TLS
。考虑到任何一方都可以随时发起新的握手,甚至会变得更好:就TLS而言,以上级别的含义尚未定义,这可能会导致尴尬的问题。)