java 正确关闭 SSLSocket

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/6424998/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-30 15:50:12  来源:igfitidea点击:

Properly closing SSLSocket

javasocketsssl

提问by Elazar Leibovich

I want to implement an SSL proxy in Java. I basically open two sockets browser-proxy,proxy-server, and run two threads which would write to proxy-serverwhat they read from browser-proxy, and vice versa. Each thread looks like this:

我想用 Java 实现一个 SSL 代理。我基本上打开两个套接字browser-proxyproxy-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

Each thread will only close the input socket, so that eventually both sockets are closed.

每个线程只会关闭输入套接字,因此最终两个套接字都关闭。

But what do I do if I want to use an SSLSocket? It seems that the shutdownOutput/Inputmethods are not supported there. Here's the exception I get.

但是,如果我想使用 ,我该怎么办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)

What I came up with is:

我想出的是:

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, */}

I must catch and ignore end of socket exception every time my socket ends.

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

My questions are:

我的问题是:

  1. If shutdownInput()is unsupported, how come I'm getting a -1answer from an SSLSocket.
  2. Is there a better solution for my agony? I don't think there's anything sane, since one of the thread might be already in the blocking readmethod when the other thread marks him "I'm done". And the only way I see to kick him out of the blocking thread is by closing the socket and raising a "closed socket" exception.

    (You could I guess use some unholy combination of multithread message passing and asynchronous data reading in order to signal the other thread your work is done, but this is so horrible that I'm afraid IntelliJ's style cop will come after me just for thinking about it...)

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

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

Clarification:I know that shutdownInputand shutdownOutputdoesn't make sense, since you cannot have a half-duplex TLS connection, per spec. But given that, how can I end a TLS connection in Java without getting an exception?

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

Please, don't tell me shutdownIputdoesn't make sense for TLS. I know that, this is not what I'm asking. I'm asking what else can I use, in order to close SSLSocketproperly (hence the title, "Properly closing SSLSocket".

请不要告诉我shutdownIputTLS 没有意义。我知道,这不是我要问的。我在问我还能用什么来SSLSocket正确关闭(因此标题为“正确关闭 SSLSocket”。

回答by Bruno

This was my initial (temporarily deleted) answer, but it was apparently not good enough, since it was -2'ed quite quickly... I guess I should add more explanations.

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

It's not possible to close half of the connection for SSL/TLS sockets to comply with the TLS protocol, as specified in the section on closure alerts.

关闭警报部分所述,无法关闭 SSL/TLS 套接字的一半连接以符合 TLS 协议。

The client and the server must share knowledge that the connection is ending in order to avoid a truncation attack. Either party may initiate the exchange of closing messages.

close_notifyThis message notifies the recipient that the sender will not send any more messages on this connection. The session becomes unresumable if any connection is terminated without proper close_notify messages with level equal to warning.

Either party may initiate a close by sending a close_notify alert. Any data received after a closure alert is ignored.

Each party is required to send a close_notify alert before closing the write side of the connection. It is required that the other party respond with a close_notify alert of its own and close down the connection immediately, discarding any pending writes. It is not required for the initiator of the close to wait for the responding close_notify alert before closing the read side of the connection.

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

close_notify此消息通知收件人,发件人将不再在此连接上发送任何消息。如果任何连接在没有级别等于警告的适当 close_notify 消息的情况下终止,会话将无法恢复。

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

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

Although you may want to close only half of the connection (input or output via shutdownInput()/shutdownOutput()), the underlying TLS layer still needs to send this close_notifypacket before really shutting down the communication, otherwise, it will be considered as a truncation attack.

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

The fix is quite simple: only use close()on SSLSockets: it doesn't just close the connection as it would for normal TCP sockets, but it does it cleanly according to the SSL/TLS specification.

修复非常简单:仅close()SSLSockets 上使用:它不只是像普通 TCP 套接字那样关闭连接,而是根据 SSL/TLS 规范干净利落地做到这一点。

EDIT: Additional explanations.

编辑附加说明

The short answer is still: use close(), you may also need to find out when it's appropriate to do so from the protocol on top of SSL/TLS.

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

(Just to clarify, what you're trying to do is effectively a "Man-In-The-Middle" (MITM) proxy. You would need the client to be configured to trust the proxy's certificate, possibly as if it was the server certificate if this is meant to happen transparently. How HTTPS connections work through an HTTP proxy is a different question.)

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

In some of your comments to your other related question, I got the impression that you thought that not returning -1was "breaking the TLS protocol". This isn't really about the protocol, but about the API: the way programming structures (classes, methods, functions, ...) are provided to (programmer) user of the TLS stack to be able to use of TLS. SSLSocketis merely part of an API to provide programmers with the ability to use SSL/TLS in a way similar to plain Socket. The TLS specification doesn't talk about sockets at all. While TLS (and its precessor, SSL) were built with the aim to make it possible to provide this sort of abstraction, at the end of the day SSLSocketis just that: an abstraction. In line with the OOP design principles, SSLSocketinherits Socketand tries to stick as closely as possible to the behaviour of Socket. However, because of the way SSL/TLS works (and because an SSLSocketeffectively sits on top of a normal Socket), there cannot be an exact mapping of all features.

在您对其他相关问题的一些评论中,我的印象是您认为不返回-1是“破坏了 TLS 协议”。这实际上不是关于协议,而是关于 API:向 TLS 堆栈的(程序员)用户提供编程结构(类、方法、函数等)的方式,以便能够使用 TLS。SSLSocket只是 API 的一部分,为程序员提供以类似于普通Socket. TLS 规范根本不讨论套接字。虽然 TLS(及其前身 SSL)的构建旨在使提供这种抽象成为可能,但归根结底SSLSocket就是:抽象。符合OOP设计原则,SSLSocket继承Socket并尽量贴近 的行为Socket。但是,由于 SSL/TLS 的工作方式(并且因为SSLSocket有效地位于普通 之上Socket),不可能有所有功能的精确映射。

In particular, the area of transition from a normal TCP socket to an SSL/TLS socket can't quite be modelled transparently. Some arbitrary choices (e.g. how the handshake takes place) had to be made when designing the SSLSocketclass: it's a compromise between not (a) exposing too much of the specificity of TLS to the SSLSocketuser, (b) making the TLS mechanism work and (c) mapping all this to the existing interface provided by the superclass (Socket). These choices inevitably have some impact on the functionality of the SSLSocket, and that's why its Javadoc API pagehas a relatively large amount of text. SSLSocket.close(), in terms of API, has to abide by the description of Socket.close(). The InputStream/OutputStreamobtained from the SSLSocketare not the same as the one from the underlying plain Socket,which (unless you've converted it explicitly) you might not see.

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

SSLSocketis designed to be usable as closely as possibly as if it was a plain Socket, and the InputStreamyou get is designed to behave as closely as possibly to a file-based input stream. This isn't specific to Java, by the way. Even Unix sockets in C are used with a file descriptor and read(). These abstractions are convenient and work most of the time, so long as you know what their limitations are.

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

When it comes to read(), even in C, the notion of EOFcomes from an end-of-file termination, but you're not actually dealing with files. The problem with TCP sockets is that, while you can detect when the remote party has chosen to close the connection when it sends FIN, you can't detect it hasn't, if it doesn't send anything. When reading nothing from a socket, there is no way to tell the difference between an inactive and a broken connection. As such, when it comes to network programming, relying on reading -1from the InputStreamif fine, but you never rely solely on that, otherwise you may end up with unreleased, dead connections. While it's "polite" for a party to close the half TCP connection properly (in a way such as the remote party reads -1on its InputStreamor something equivalent, as described in Orderly Versus Abortive Connection Release in Java), handling this case isn't sufficient. This is why protocols on top of TCP are generally designed to give an indication as to when they're done sending data: for example, SMTP sends QUIT(and has specific terminators), HTTP 1.1 uses Content-Lengthor chunked encoding.

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

(By the way, if you're not satisfied with the abstraction provided by SSLSocket, try to use SSLEnginedirectly. It's likely to be harder, and it won't change what the TLS specification says about sending close_notify.)

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

In general with TCP, you should know, at the application protocol level, when to stop sending and receiving data (to avoid unreleased connections and/or relying on timeout errors). This sentence in the TLS specification makes this generally good practice a stronger requirement when it comes to TLS: "It is required that the other party respond with a close_notify alert of its own and close down the connection immediately discarding any pending writes." You will have to synchronize somehow, via the application protocol, or the remote party may still have something to write (especially if you're allowing pipelined requests/responses).

一般来说,对于 TCP,您应该知道,在应用程序协议级别,何时停止发送和接收数据(以避免未释放的连接和/或依赖超时错误)。当涉及到 TLS 时,TLS 规范中的这句话使这种通常良好的实践成为更强的要求:“要求另一方使用自己的 close_notify 警报进行响应,并立即关闭连接并丢弃任何挂起的写入。”您将必须以某种方式通过应用程序协议进行同步,否则远程方可能仍有一些东西要写(特别是如果您允许流水线请求/响应)。

If the proxy you're writing is for intercepting HTTPS connections (as a MITM), you can look into the content of the requests. Even if the HTTP requests are pipelined, you can count the number of responses sent by the target server (and expect when they end, via Content-Length or chunks delimiters).

如果您编写的代理用于拦截 HTTPS 连接(作为 MITM),您可以查看请求的内容。即使 HTTP 请求是管道化的,您也可以计算目标服务器发送的响应数量(并期望它们何时结束,通过 Content-Length 或块分隔符)。

However, you won't be able to have a purely transparent, generic TLS "interceptor". TLS is designed to secure the transport between two parties and not to have one in the middle. While most of the mechanism to achieve that protection (avoiding a MITM) is done via the server certificate, some other TLS mechanisms will get in the way (closure alert is one of them). Remember that you're effectively creating two distinct TLS connections, the fact that they're hard to merge as one shouldn't be surprising, considering the purpose of TLS.

但是,您将无法拥有纯粹透明的通用 TLS“拦截器”。TLS 旨在保护两方之间的传输,而不是中间有一个。虽然实现这种保护(避免 MITM)的大部分机制是通过服务器证书完成的,但其他一些 TLS 机制也会受到阻碍(关闭警报就是其中之一)。请记住,您正在有效地创建两个不同的 TLS 连接,考虑到 TLS 的目的,它们难以合并为一个这一事实应该不足为奇。

SSLSocket.close()is the right way to close an SSLSocket, but the application protocol will also help you determine when it's appropriate to do so.

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

回答by President James K. Polk

SSL semantics for closing connections are different than TCP, so that shutdownInput and shutdownOutput don't really make sense, at least at the level of control available in SSLSocket. The SSLSocket basically relieves you of the burden of SSL record processing and SSL handshake message processing. It also handles SSL alert processing, and one of these alerts is the close_notify alert. Here is the text from RFC 2246 on the meaning of this alert.

关闭连接的 SSL 语义与 TCP 不同,因此 shutdownInput 和 shutdownOutput 没有真正意义,至少在 SSLSocket 中可用的控制级别。SSLSocket 基本上减轻了您的 SSL 记录处理和 SSL 握手消息处理的负担。它还处理 SSL 警报处理,这些警报之一是 close_notify 警报。以下是 RFC 2246 中有关此警报含义的文本。

"...Either party may initiate a close by sending a close_notify alert.
   Any data received after a closure alert is ignored.

   Each party is required to send a close_notify alert before closing
   the write side of the connection. It is required that the other party
   respond with a close_notify alert of its own and close down the
   connection immediately, discarding any pending writes. It is not
   required for the initiator of the close to wait for the responding
   close_notify alert before closing the read side of the connection...."

Java does provide suicidalists with another API, the SSLEngine API. Don't touch it, it will hurt you.

Java 确实为自杀者提供了另一个 API,即 SSLEngine API。不要碰它,它会伤害你。

回答by cha2lenger

In order to solve your problem you have to do the following:

为了解决您的问题,您必须执行以下操作:

OutputStream sockOutOStream = sockout.getOutputStream();
sockOutOStream.write(new byte[0]);
sockOutOStream.flush();
sockout.close();

On the other end the party which reads from the socket will receive the -1-length message instead of SocketException thrown.

在另一端,从套接字读取的一方将收到 -1 长度的消息,而不是抛出的 SocketException。