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

我已经准备了一个基于Bouncy Castle的MockPSKTlsClient 的简单TLS PSK客户端测试用例 。

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? } 

如您所见,我将一个GET / HTTP/1.1字符串发送到openssl服务器,该服务器的启动方式为:

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

之后我调用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); } } 

openssl s_server答案复制到屏幕上,并且最后会惊奇地抛出一个EOFException

 TLS-PSK client negotiated TLS 1.2 Established session: 68e647e3276f345e82effdb7cc04649f6872d245ae01489c08ed109c5906dd16 HTTP/1.0 200 ok Content-type: text/html  
 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  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) 

我的问题是:为什么抛出EOFException

通常, InputStream.read()应该在流的末尾返回-1而不抛出exception。

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

从长远来看,我想将我的测试用例扩展到在嵌入式Jetty前面作为反向PSK TLS代理的程序 - 并且不希望依赖exception来检测客户端是完成读取还是写入。

抛出EOFException(从v1.56开始),因为未收到所需的close_notify警报。 这意味着TLS层不能排除应用程序数据被截断的可能性。

截断意味着到目前为止您收到的数据是正确的(根据活动密码套件)传输,但可能有更多数据没有收到。 截断可能是偶然的或恶意的。 对于许多应用程序,后来的数据可能会影响早期数据的含义,因此截断可能会随意改变语义。

对于某些应用程序协议,可能会确定没有实际的截断(即只是缺少close_notify) – 考虑HTTP Content-Length标头,或者部分或全部截断的数据可能仍然被有效接受 – 考虑一个流自我划分,独立的信息。 这不能在TLS层本身完成; 或者更确切地说,它是通过要求close_notify来完成的!

因此,EOFException被提升为“[signal]在输入期间意外地达到了文件的结尾或流的结尾”。 此时,应用程序应保守地假设数据被截断,但应用程序特定的机制可能允许接受部分或全部数据,如上所述。

从(尚未发布)v1.57开始,我们添加了TlsNoCloseNotifyException作为EOFException的子类,它只会/在这种特定情况下被抛出,希望允许更简单的应用程序代码。

Interesting Posts