簡體   English   中英

使用 TLS PSK 加密時如何正確檢測流的結尾?

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

我已經准備了一個基於 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?
}

如您所見,我向 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

之后我調用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

<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)

我的問題是:為什么會拋出EOFException

通常InputStream.read()應該在流結束時返回 -1 並且不拋出異常。

使用 TLS PSK 加密時如何正確檢測流的結尾?

從長遠來看,我想將我的測試用例擴展到在嵌入式 Jetty 前充當反向 PSK TLS 代理的程序 - 並且不希望依賴異常來檢測客戶端是否已完成讀取或寫入。

由於未收到所需的 close_notify 警報,因此拋出 EOFException(從 v1.56 開始)。 這意味着 TLS 層不能排除應用程序數據被截斷的可能性。

截斷意味着您到目前為止收到的數據已正確傳輸(根據活動密碼套件),但可能還有更多您沒有收到的數據。 截斷可能是意外的或惡意的。 對於許多應用程序,較晚的數據可能會影響較早數據的含義,因此截斷可能會任意改變語義。

對於某些應用程序協議,可以確定沒有實際截斷(即只是缺少 close_notify)——考慮 HTTP Content-Length 標頭,或者部分或全部截斷的數據可能仍被有用地接受——考慮一個流自定界的獨立消息。 這不能在 TLS 層本身中完成; 或者更確切地說,它是通過要求 close_notify 來完成的!

因此,EOFException 被提升為“[signal] 在輸入期間意外到達文件結尾或流結尾”。 在這一點上,應用程序應該保守地假設數據被截斷,但應用程序特定的機制可能還允許接受部分或全部數據,如上所述。

從(尚未發布)v1.57 開始,我們添加了 TlsNoCloseNotifyException 作為 EOFException 的子類,它只會/總是在這種特定情況下拋出,希望允許更簡單的應用程序代碼。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM