【问题标题】:Error when opening and closing an SSLSocket w/o writing any data在不写入任何数据的情况下打开和关闭 SSLSocket 时出错
【发布时间】:2011-09-25 22:07:41
【问题描述】:

一个简单的服务器

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 Socket 写入任何内容,则会在客户端抛出异常:

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 规范是否要求您将内容写入套接字?

请参阅full example

【问题讨论】:

  • 您的命名约定对于clientserver 套接字是什么有点混乱。此外,客户端代码在你的 GitHub 上的SSLNullServer 目录中,你的服务器代码在你的SSLNullClient 目录中。
  • @Bruno,你的观点是有效的,我的错。关于命名约定,Socket client 应该是 Socket toClient 才有意义。

标签: java sockets ssl


【解决方案1】:

您不必在套接字中写入任何内容,但如果您立即关闭它,它将生成一个close_notify 警报(虽然它被称为“警报”,它是关闭一个正常方式的一部分TLS/SSL 套接字)。

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

documentation for SSLSocket 说:

第一次握手 连接可以在其中之一启动 三种方式:

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

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

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

  • 客户端createSocket("localhost", 1443)建立连接,服务器通过accept()接受。
  • 客户端的getInputStream().read() 使其启动握手。因此,它会向服务器发送ClientHello TLS 消息。
  • 因为服务器在接受套接字后直接使用close(),它发送一个close_notify 警报。因为服务器还没有开始读/写,它还没有开始握手(因此没有完成)。

请注意,SSLServerSocket 实现的ServerSocket.accept() 的目的是创建一个SSLSocket,不一定要对它做任何事情。 SSLServerSocket 配置它,但继续握手超出了范围。一方面,这听起来可能使SSLSocket 的行为更加透明,就像普通的 TCP 套接字一样;另一方面,它意味着从底层 TCP 流中读取,因此会产生副作用。 我没有尝试过,但SSLServerSocket 创建的SSLSocket 可能仍然可以配置为客户端套接字。毕竟正如RFC 2246 glossary 所说: “客户端:启动与服务器的 TLS 连接的应用程序实体。这可能暗示或可能不暗示客户端启动了底层传输连接。” 这肯定会对透明度和何时执行产生影响从 API 的角度来看握手。

(为 SSL/TLS 套接字编写一个映射到普通 TCP 套接字 API 的 API 是一个棘手的练习,Java 在这方面做得还不错。真正的“乐趣”从使用 @ 的异步 TLS 开始987654344@ 和 NIO 通道。考虑到任何一方都可以随时发起新的握手,它变得更好:就 TLS 而言,上述级别的含义是不确定的,这可能导致awkward problems。)

【讨论】:

  • 您可以在关闭服务器端之前考虑使用addHandshakeCompletedListener 进行同步(不确定这是否是本实验的目标)。至少这可以让您在握手期间不发送该警报。
  • 一句话中的问题是,.close 并不意味着握手。这意味着“关闭套接字,如果没有握手 - 太糟糕了”。我期待“关闭”也意味着握手。
  • @Bruno,一个更简单的解决方法是在关闭连接之前在服务器端强制握手。 (我正在尝试调试SSLSocket中的另一个异常,并尝试了几个简单的案例)。
  • close 表示close_notify,无论握手是否完成。我不确定为什么close 暗示握手是有意义的:这通常是为了启动 TLS 连接或重新协商它的一些参数;当你想结束连接时它并不是特别有用(这是close的目的)。
  • @Bruno,我希望SSLSocket 不允许破坏协议。如果允许在握手之前关闭连接 - 我希望read 在发送close_notified 时返回-1,即使没有握手。如果不允许在握手之前发送close_notify,我确实希望close 暗示握手,以免破坏协议。
【解决方案2】:

情况无效。您正在尝试读取未发送的数据。这是一个应用程序协议错误。布鲁诺回答中的所有陈述也适用。客户端正在尝试握手;服务器正在尝试关闭连接。可以说,如果还没有完成,服务器关闭可以启动握手,但它没有。

正如您所指出的,另一种解决方法是在任一端调用 startHandshake()。

【讨论】:

  • “布鲁诺回答中的所有陈述也适用。”:我刚刚编辑了我的回答,所以我不确定你是否仍然同意...
  • @Bruno 我现在更同意了 ;-)
  • 首先,这种情况肯定是有效的,因为read应该能识别EOF,所以我做的事情一般来说是完全可以的。其次,正如我告诉布鲁诺的那样。如果 TLS 允许您在不握手的情况下发送close_notify,则客户端的read 应该返回-1,并且不应该抛出(因为协议不需要握手),如果TLS 需要在@ 之前握手987654328@,然后close 应该在关闭前完成握手。
  • @Elazar,InputStream 上的-1 的语义只有在建立 TLS 连接后才能生效。如果在此示例中,您在此之前关闭了连接,因此应该会出现异常。此外,正如我在updated answer to your previous question 中所说,不要过分依赖阅读-1:您也不可避免地需要处理异常。
  • @Bruno,我完全同意你在另一个答案中写的。我的问题是 API,而不是协议。我要说的是,如果您向用户隐藏握手 - 您不应该让抽象将这一事实作为例外泄漏给用户。如果握手应该在没有用户干预的情况下懒惰地发生,那么你应该在连接时发生(这实际上是有道理的),而不是在第一次阅读时发生,或者在使用 close 时自动发生,或者至少记录 @ 987654334@ 并抛出无效的close
猜你喜欢
  • 2011-01-19
  • 1970-01-01
  • 2013-02-08
  • 1970-01-01
  • 2023-01-04
  • 1970-01-01
  • 2019-12-11
  • 2014-11-16
  • 1970-01-01
相关资源
最近更新 更多