简体   繁体   English

使用 TLS PSK 加密时如何正确检测流的结尾?

[英]How to detect an end of stream properly, when TLS PSK encryption is used?

I have prepared a simple TLS PSK client test case based on MockPSKTlsClient by Bouncy Castle.我已经准备了一个基于 Bouncy Castle 的MockPSKTlsClient 的简单 TLS PSK 客户端测试用例

In the main method I call:在我调用的main方法中:

public static void main(String[] args) throws IOException {
    SecureRandom random      = new SecureRandom();
    TlsPSKIdentity identity  = new BasicTlsPSKIdentity("Client_identity", Hex.decode("1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A"));
    Socket socket            = new Socket(InetAddress.getLocalHost(), 12345);
    TlsClientProtocol proto  = new TlsClientProtocol(socket.getInputStream(), socket.getOutputStream(), random);
    MockPSKTlsClient client  = new MockPSKTlsClient(null, identity);
    proto.connect(client);

    OutputStream clearOs = proto.getOutputStream();
    InputStream clearIs = proto.getInputStream();
    clearOs.write("GET / HTTP/1.1\r\n\r\n".getBytes("UTF-8"));
    Streams.pipeAll(clearIs, System.out);   // why is java.io.EOFException thrown?
}

As you can see, I send a GET / HTTP/1.1 string to the openssl server, which is started as:如您所见,我向 openssl 服务器发送了一个GET / HTTP/1.1字符串,该服务器启动为:

# openssl s_server \
        -psk 1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A \
        -psk_hint Client_identity\
        -cipher PSK-AES256-CBC-SHA \
        -debug -state -nocert -accept 12345 -tls1_2 -www

After that I call Streams.pipeAll() method, which is merely:之后我调用Streams.pipeAll()方法,它仅仅是:

public static void pipeAll(InputStream inStr, OutputStream outStr)
    throws IOException
{
    byte[] bs = new byte[BUFFER_SIZE];
    int numRead;
    while ((numRead = inStr.read(bs, 0, bs.length)) >= 0) // Why is EOFException thrown?
    {
        outStr.write(bs, 0, numRead);
    }
}

This copies openssl s_server answer to the screen and also surprisingly throws an EOFException at the end:这将openssl s_server答案复制到屏幕上,并在最后令人惊讶地抛出一个EOFException

TLS-PSK client negotiated TLS 1.2
Established session: 68e647e3276f345e82effdb7cc04649f6872d245ae01489c08ed109c5906dd16
HTTP/1.0 200 ok
Content-type: text/html

<HTML><BODY BGCOLOR="#ffffff">
<pre>

s_server -psk 1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A -psk_hint Client_identity -cipher PSK-AES256-CBC-SHA -debug -state -nocert -accept 12345 -tls1_2 -www 
Secure Renegotiation IS supported
Ciphers supported in s_server binary
TLSv1/SSLv3:PSK-AES256-CBC-SHA       
---
Ciphers common between both SSL end points:
PSK-AES256-CBC-SHA
Signature Algorithms: RSA+SHA1:RSA+SHA224:RSA+SHA256:RSA+SHA384:RSA+SHA512:DSA+SHA1:DSA+SHA224:DSA+SHA256:DSA+SHA384:DSA+SHA512:ECDSA+SHA1:ECDSA+SHA224:ECDSA+SHA256:ECDSA+SHA384:ECDSA+SHA512
Shared Signature Algorithms: RSA+SHA1:RSA+SHA224:RSA+SHA256:RSA+SHA384:RSA+SHA512:DSA+SHA1:DSA+SHA224:DSA+SHA256:DSA+SHA384:DSA+SHA512:ECDSA+SHA1:ECDSA+SHA224:ECDSA+SHA256:ECDSA+SHA384:ECDSA+SHA512
---
New, TLSv1/SSLv3, Cipher is PSK-AES256-CBC-SHA
SSL-Session:
    Protocol  : TLSv1.2
    Cipher    : PSK-AES256-CBC-SHA
    Session-ID: 68E647E3276F345E82EFFDB7CC04649F6872D245AE01489C08ED109C5906DD16
    Session-ID-ctx: 01000000
    Master-Key: B023F1053230C2938E1D3FD6D73FEB41DEC3FC1068A390FE6DCFD60A6ED666CA2AD0CD1DAD504A087BE322DD2C870C0C
    Key-Arg   : None
    PSK identity: Client_identity
    PSK identity hint: Client_identity
    SRP username: None
    Start Time: 1479312253
    Timeout   : 7200 (sec)
    Verify return code: 0 (ok)
---
  13 items in the session cache
   0 client connects (SSL_connect())
   0 client renegotiates (SSL_connect())
   0 client connects that finished
  14 server accepts (SSL_accept())
   0 server renegotiates (SSL_accept())
  13 server accepts that finished
   0 session cache hits
   0 session cache misses
   0 session cache timeouts
   0 callback cache hits
   0 cache full overflows (128 allowed)
---
no client certificate available
</BODY></HTML>

TLS-PSK client raised alert: fatal(2), internal_error(80)
> Failed to read record
java.io.EOFException
    at org.bouncycastle.crypto.tls.TlsProtocol.safeReadRecord(Unknown Source)
    at org.bouncycastle.crypto.tls.TlsProtocol.readApplicationData(Unknown Source)
    at org.bouncycastle.crypto.tls.TlsInputStream.read(Unknown Source)
    at de.afarber.tlspskclient2.Main.pipeAll(Main.java:52)
    at de.afarber.tlspskclient2.Main.main(Main.java:44)
Exception in thread "main" java.io.IOException: Internal TLS error, this could be an attack
    at org.bouncycastle.crypto.tls.TlsProtocol.failWithError(Unknown Source)
    at org.bouncycastle.crypto.tls.TlsProtocol.safeReadRecord(Unknown Source)
    at org.bouncycastle.crypto.tls.TlsProtocol.readApplicationData(Unknown Source)
    at org.bouncycastle.crypto.tls.TlsInputStream.read(Unknown Source)
    at de.afarber.tlspskclient2.Main.pipeAll(Main.java:52)
    at de.afarber.tlspskclient2.Main.main(Main.java:44)

My question is: why is EOFException thrown?我的问题是:为什么会抛出EOFException

Usually InputStream.read() is supposed to return -1 at the end of stream and not throw an exception.通常InputStream.read()应该在流结束时返回 -1 并且不抛出异常。

How to detect an end of stream properly, when TLS PSK encryption is used?使用 TLS PSK 加密时如何正确检测流的结尾?

In the long term I would like to extend my test case to a program acting as reverse PSK TLS proxy in front of embedded Jetty - and would prefer not to rely on exceptions to detect that the client is done reading or writing.从长远来看,我想将我的测试用例扩展到在嵌入式 Jetty 前充当反向 PSK TLS 代理的程序 - 并且不希望依赖异常来检测客户端是否已完成读取或写入。

EOFException is thrown (as of v1.56) because the required close_notify alert was not received.由于未收到所需的 close_notify 警报,因此抛出 EOFException(从 v1.56 开始)。 This means that the TLS layer cannot exclude the possibility that the application data was truncated.这意味着 TLS 层不能排除应用程序数据被截断的可能性。

Truncation means that the data you received so far was correctly (per the active cipher suite) transmitted, but there may have been more data that you didn't receive.截断意味着您到目前为止收到的数据已正确传输(根据活动密码套件),但可能还有更多您没有收到的数据。 Truncation may be accidental or malicious.截断可能是意外的或恶意的。 For many applications, later data may affect the meaning of earlier data, so a truncation may arbitrarily alter semantics.对于许多应用程序,较晚的数据可能会影响较早数据的含义,因此截断可能会任意改变语义。

For some application protocols, it may be possible to determine that there was no actual truncation (ie just missing close_notify) - consider the HTTP Content-Length header, or that some or all of the truncated data might still be usefully accepted - consider a stream of self-delimiting, independent messages.对于某些应用程序协议,可以确定没有实际截断(即只是缺少 close_notify)——考虑 HTTP Content-Length 标头,或者部分或全部截断的数据可能仍被有用地接受——考虑一个流自定界的独立消息。 This cannot be done in the TLS layer itself;这不能在 TLS 层本身中完成; or rather, it is done by requiring close_notify!或者更确切地说,它是通过要求 close_notify 来完成的!

So, EOFException is raised to "[signal] that an end of file or end of stream has been reached unexpectedly during input".因此,EOFException 被提升为“[signal] 在输入期间意外到达文件结尾或流结尾”。 At this point the application should conservatively assume the data was truncated, but application-specific mechanisms may yet allow the acceptance of part or all of the data as explained above.在这一点上,应用程序应该保守地假设数据被截断,但应用程序特定的机制可能还允许接受部分或全部数据,如上所述。

As of (not yet released) v1.57 we have added TlsNoCloseNotifyException as a subclass of EOFException, that will only/always be thrown in this specific case, hopefully allowing for simpler application code.从(尚未发布)v1.57 开始,我们添加了 TlsNoCloseNotifyException 作为 EOFException 的子类,它只会/总是在这种特定情况下抛出,希望允许更简单的应用程序代码。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM