正确关闭SSLSocket

Ela*_*ich 8 java sockets ssl

我想在Java中实现SSL代理.我基本上打开两个套接字browser-proxy,proxy-server然后运行两个线程来写入proxy-server它们读取的内容browser-proxy,反之亦然.每个线程看起来像这样:

while (true) {
   nr = in.read(buffer);
   if (nr == -1) System.out.println(sockin.toString()+" EOF  "+nr);
   if (nr == -1) break;
   out.write(buffer, 0, nr);
}
sockin.shutdownInput();
sockout.shutdownOutput(); // now the second thread will receive -1 on read
Run Code Online (Sandbox Code Playgroud)

每个线程只关闭输入套接字,以便最终关闭两个套接字.

但是,如果我想使用它,我该怎么办SSLSocket?似乎shutdownOutput/Input那里不支持这些方法.这是我得到的例外.

Exception in thread "Thread-35" java.lang.UnsupportedOperationException: \
The method shutdownInput() is not supported in SSLSocket
    at com.sun.net.ssl.internal.ssl.BaseSSLSocketImpl.shutdownInput(Unknown Source)
Run Code Online (Sandbox Code Playgroud)

我想出的是:

try {
    while (true) {
       nr = in.read(buffer);
       if (nr == -1) System.out.println(sockin.toString()+" EOF  "+nr);
       if (nr == -1) break;
       out.write(buffer, 0, nr);
    }
    // possible race condition if by some mysterious way both threads would get EOF
    if (!sockin.isClosed()) sockin.close();
    if (!sockout.isClosed()) sockout.close();
} catch (SocketException e) {/*ignore expected "socket closed" exception, */}
Run Code Online (Sandbox Code Playgroud)

每次我的套接字结束时,我必须捕获并忽略套接字异常的结束.

我的问题是:

  1. 如果shutdownInput()不支持,我怎么得到一个-1答案SSLSocket.
  2. 对我的痛苦有更好的解决方案吗?我认为没有任何理智,因为read当另一个线程标记他"我已经完成"时,其中一个线程可能已经在阻塞方法中.我看到将他踢出阻塞线程的唯一方法是关闭套接字并引发"关闭套接字"异常.

    (你可以猜测使用多线程消息传递和异步数据读取的一些邪恶组合,以便向你的工作完成另一个线程发出信号,但这太可怕了,我担心IntelliJ的风格警察只会因为思考而追随我它...)

澄清:我知道shutdownInput并且shutdownOutput没有意义,因为根据规范你不能有半双工TLS连接.但鉴于此,如何在没有异常的情况下结束Java中的TLS连接?

请不要告诉我shutdownIput对TLS没有意义.我知道,这不是我要问的.我问我还能使用什么,以便SSLSocket正确关闭(因此标题为"正确关闭SSLSocket".

Bru*_*uno 19

这是我最初的(暂时删除的)答案,但它显然不够好,因为它很快就被-2了......我想我应该添加更多的解释.

无法关闭SSL/TLS套接字的一半连接以符合TLS协议,如关闭警报一节中所述.

客户端和服务器必须共享连接结束的知识,以避免截断攻击.任何一方都可以发起关闭消息的交换.

close_notify此消息通知收件人发件人不会再在此连接上发送任何消息.如果在没有正确级别等于警告的close_notify消息的情况下终止任何连接,则会话将变得不可恢复.

任何一方都可以通过发送close_notify警报来发起关闭.关闭警报后收到的任何数据都将被忽略.

在关闭连接的写入端之前,每一方都需要发送close_notify警报.要求另一方使用自己的close_notify警报进行响应,并立即关闭连接,丢弃任何挂起的写入.在关闭连接的读取端之前,close的发起者不需要等待响应的close_notify警报.

虽然您可能只想关闭连接的一半(通过shutdownInput()/ 输入或输出shutdownOutput()),但底层TLS层仍需要close_notify在真正关闭通信之前发送此数据包,否则,它将被视为截断攻击.

解决方法是相当简单:只使用close()SSLSocketS:它不只是关闭连接,因为它会为正常的TCP套接字,但它干净根据SSL/TLS规范做的.

编辑:其他解释.

简短的回答仍然是:使用close(),您可能还需要从SSL/TLS上的协议中找出何时适合这样做.

(只是为了澄清,你要做的实际上是一个"中间人"(MITM)代理.你需要将客户端配置为信任代理的证书,可能就像它是服务器一样证书,如果要透明地发生.HTTP连接如何通过HTTP代理工作是一个不同的问题.)

在您对其他相关问题的一些评论中,我得到的印象是您认为不返回-1是"打破TLS协议".这不是关于协议,而是关于API:编程结构(类,方法,函数,...)的方式被提供给TLS堆栈的(程序员)用户以便能够使用TLS.SSLSocket仅仅是API的一部分,它为程序员提供了以类似于plain的方式使用SSL/TLS的能力Socket.TLS规范根本不讨论套接字.虽然构建TLS(及其前身SSL)的目的是为了能够提供这种抽象,但最终SSLSocket只是:抽象.符合OOP设计原则,SSLSocket继承Socket并尽可能贴近行为Socket.但是,由于SSL/TLS的工作方式(并且因为SSLSocket有效地位于法线之上Socket),因此无法精确映射所有要素.

特别是,从普通TCP套接字到SSL/TLS套接字的转换区域不能完全透明地建模.在设计SSLSocket类时必须做出一些任意选择(例如握手如何发生):它是在(a)向SSLSocket用户暴露太多TLS特异性,(b)使TLS机制起作用和( c)将所有这些映射到超类(Socket)提供的现有接口.这些选择不可避免地会对其功能产生一些影响SSLSocket,这就是为什么它的Javadoc API页面具有相对大量的文本. SSLSocket.close()就API而言,必须遵守描述Socket.close().InputStream/ OutputStream得到的/ SSLSocket与底层平原上的/不同Socket,除非你明确转换它,否则你可能看不到.

SSLSocket被设计为尽可能紧密地使用,就好像它是一个普通的Socket,并且InputStream你得到的设计可能与基于文件的输入流一样紧密.顺便说一下,这不是特定于Java的.即使是C语言中的Unix套接字也与文件描述符一起使用read().这些抽象很方便,大部分时间都可以工作,只要你知道它们的局限性.

当谈到read(),即使是在C,这个概念EOF来自于档案结尾的终止,但你没有实际处理的文件.TCP套接字的问题在于,虽然您可以检测到远程方在发送时选择关闭连接的时间,但FIN如果它没有发送任何内容,则无法检测到它没有.当从套接字中读取任何内容时,无法区分非活动连接和断开连接之间的区别.因此,当谈到网络编程时,依赖于-1InputStreamif中读取,但你永远不会完全依赖于它,否则你可能最终得到未发布的,死的连接.虽然对于一方正确关闭一半TCP连接(以某种方式,例如远程方读取-1InputStream或等效的方式,如Java中的有序与中止连接发布中所述),这是"礼貌的" ,处理此案例是不够的.这就是为什么TCP之上的协议通常被设计为指示它们何时完成发送数据:例如,SMTP发送QUIT(并具有特定终结符),HTTP 1.1使用Content-Length或分块编码.

(顺便说一句,如果您对提供的抽象不满意SSLSocket,请尝试SSLEngine直接使用.它可能会更难,并且它不会改变TLS规范关于发送的内容close_notify.)

通常使用TCP,您应该在应用程序协议级别知道何时停止发送和接收数据(以避免未发布的连接和/或依赖于超时错误).TLS规范中的这句话使得这种通常良好的做法在TLS方面具有更强的要求:" 要求对方以自己的close_notify警报响应并关闭连接,立即丢弃任何待处理的写入. "你将必须以某种方式通过应用程序协议进行同步,或者远程方可能仍然需要编写内容(特别是如果您允许流水线请求/响应).

如果您正在编写的代理用于拦截HTTPS连接(作为MITM),则可以查看请求的内容.即使HTTP请求是流水线的,您也可以计算目标服务器发送的响应数(并通过Content-Length或chunks分隔符预期它们何时结束).

但是,您将无法拥有纯粹透明的通用TLS"拦截器".TLS旨在确保双方之间的运输,而不是中间的一方.虽然实现这种保护(避免使用MITM)的大多数机制都是通过服务器证书完成的,但其他一些TLS机制将会妨碍(关闭警报就是其中之一).请记住,您正在有效地创建两个不同的TLS连接,考虑到TLS的目的,它们很难合并,因为它不应该令人惊讶.

SSLSocket.close()是关闭a的正确方法SSLSocket,但应用程序协议也将帮助您确定何时适合这样做.