2016-04-30 62 views
0

我試圖實現一個基本的TLS客戶端/服務器框架。一切看起來都很順利,直到握手之後,測試迴應服務器應該讀取並回寫接收到的任何內容。握手完成後服務器關閉套接字

我已經從-Djavax.net.debug=all兩側運行此協議,並將協議降級到TLSv1(但是從TLSv1.2開始調整)。這是安裝了安全包的GNU/Linux x86-64上的Oracle 8。

直到服務器使用從SSLSocket.getInputStream()獲得的數據流調用BufferedInputReader.read()時,沒有發生任何異常。此時測試客戶端正在等待控制檯輸入發送到回顯測試服務器。

我以前使用GnuTLS在C和C++中實現了TLS 1.2服務器,所以我對所涉及的基礎知識有了基本的瞭解。關於調試這個java實現,我已經通過了像thisthis這樣的東西,我對這個問題的描述將遵循這種模式。

在服務器端的東西開始:

main, READ: TLSv1 Handshake, length = 151 
*** ClientHello, TLSv1 
[...] 
[read] MD5 and SHA1 hashes: len = 151 
[...] 
%% Initialized: [Session-1, SSL_NULL_WITH_NULL_NULL] 
matching alias: tls_server_key 
%% Negotiating: [Session-1, TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA] 
*** ServerHello, TLSv1 

A 「RandomCookie」 被髮送,然後在服務器證書。在這一點上,客戶端已經通過:

*** ClientHello, TLSv1 
[...] 
[write] MD5 and SHA1 hashes: len = 151 
main, WRITE: TLSv1 Handshake, length = 151 
[...] 
main, READ: TLSv1 Handshake, length = 1683 
*** ServerHello, TLSv1 

...讀取相同的「RandomCookie」從服務器發送,然後...

Cipher Suite: TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA 
Compression Method: 0 
Extension renegotiation_info, renegotiated_connection: <empty> 
*** 
%% Initialized: [Session-1, TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA] 
** TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA 
[read] MD5 and SHA1 hashes: len = 81 

然後讀取服務器證書。該客戶端具有與此簽署的CA證書,所以最終有一個Found trusted certificate,然後

*** ECDH ServerKeyExchange 
Server key: Sun EC public key, 256 bits 
    public x coord: 99333168850581082484902625735441572937781175927498004126728233464162626469072 
    public y coord: 8153267160158506973550759395885968557147147981466758879486894574711221505422 
    parameters: secp256r1 [NIST P-256, X9.62 prime256v1] (1.2.840.10045.3.1.7) 
[read] MD5 and SHA1 hashes: len = 459 
[...] 
*** ServerHelloDone 
[read] MD5 and SHA1 hashes: len = 4 
0000: 0E 00 00 00          .... 
*** ECDHClientKeyExchange 
[...] 
[write] MD5 and SHA1 hashes: len = 70 
[...] 
main, WRITE: TLSv1 Handshake, length = 70 
[Raw write]: length = 75 
[...] 
*** Finished 
[...] 
main, WRITE: TLSv1 Handshake, length = 48 
[Raw write]: length = 53 
[...] 
main, READ: TLSv1 Change Cipher Spec, length = 1 

...一些生讀共計53個字節......

main, READ: TLSv1 Handshake, length = 48 
Padded plaintext after DECRYPTION: len = 48 
[...] 
*** Finished 
verify_data: { 188, 99, 75, 234, 162, 27, 147, 7, 173, 51, 170, 34 } 
*** 
%% Cached client session: [Session-1, TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA] 
[read] MD5 and SHA1 hashes: len = 16 

,然後報告它的「保持通話」,通過這個相關的代碼已通過:

sock.setUseClientMode(true); 
sock.startHandshake(); 
in = new BufferedInputStream(sock.getInputStream()); 
out = new BufferedOutputStream(sock.getOutputStream()); 

服務器建立是大致相同,除了插座從SSLServerSocket.accept()(而不是一個來了適當的工廠)和setUseClientMode(false)在握手之前使用。起初我並沒有在任何一方使用setUseClientMode(),然後注意到javadoc說:「在發生任何握手之前必須調用此方法」,儘管這樣做對調試輸出或結果沒有任何影響。

有趣的是在調用startHandshake()和輸入/輸出BufferedStreams,isClosed()報告虛假的客戶端和服務器的創建,而是由服務器下一招後在這裏(這是從「TLSconnection 「類使用服務器和客戶端類):

public int recv (byte[] data) throws IOException 
{ 
    if (sock.isClosed()) return -1; 
    int len = in.read(data, 0, data.length); 
    if (len == -1) shut(); 
    return len; 
}   

哪裏isClosed()現在返回true,因爲在服務器上調試跟蹤得出結論這是有道理的:

[write] MD5 and SHA1 hashes: len = 16 
main, WRITE: TLSv1 Handshake, length = 48 
[Raw write]: length = 53 
%% Cached server session: [Session-1, TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA] 
main, called close() 
main, called closeInternal(true) 
main, SEND TLSv1 ALERT: warning, description = close_notify 
[...] 
main, WRITE: TLSv1 Alert, length = 32 
[...] 
main, called closeSocket(true) 

我已經也做到了這一點使用handshakeCompletedListener()簡單地寫入標準錯誤,這裏是什麼,它對是:

%% Cached server session: [Session-1, TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA] 
main, called close() 
main, called closeInternal(true) 
main 

HANDSHAKE COMPLETED 

, SEND TLSv1 ALERT: warning, description = close_notify 

對我來說,這似乎是:

  • 客戶端連接。
  • 服務器接受。
  • 發生TLS握手期間:
    • 密碼套件已成功協商。
    • 客戶端驗證服務器證書
  • 服務器隨意關閉套接字。
  • 客戶端被掛起。

我經歷過這種變化的時間和再次:

  • 沒有拋出的異常。
  • 沒有顯然調試輸出中的錯誤。
  • 沒有任何解釋爲什麼服務器任意關閉套接字。

爲三類(連接,服務器,客戶端),大約是250線,再加上約一百元爲每個測試實例(客戶端和服務器),所以我還沒有張貼在這裏所有的完整代碼,但顯示的是在調試會話期間正在播放的內容。

我假設這與證書或關鍵錯誤沒有任何關係,因爲沒有任何東西可以表明這種情況,或握手失敗,或任何人因任何原因拒絕任何其他人。


針對斯特芬·烏爾裏希的評論說:「它看起來像服務器正在做一個乾淨關閉(發送close_notify消息),所以我會建議的東西在你的代碼實際上是做一個明確的關閉」 - 這是我想首先同樣的事情 - 這裏是測試服務器的代碼,這導致高達sock.isClosed()返回true部分:

while (true) { 
     TLSconnection con = null; 
     try { con = server.acceptConnection(true); } 
     catch (Throwable ex) { 
      ex.printStackTrace(); 
      continue; 
     } 

     if (con != null) { 
      System.out.println (
       "Accepted: " 
       +TestCommon.describeConnection(con.getDetails()) 
      ); 
     } 

     while (con != null) { 
     // Echo. 
      try { 
       int check = con.recv(buffer); 
       if (check == -1) { 
        System.out.println("Disconnected."); 

什麼字面上發生在與此相關的是「接受」位被打印出來;該方法describeConnection()僅僅是:

static String describeConnection (TLSconnection.Details details) 
{ 
    if (details.addr == null) return "Not connected."; 
    StringBuffer sb = new StringBuffer(details.addr.getHostAddress()+":") 
     .append(details.remotePort).append(" (local ") 
     .append(details.localPort).append(")"); 
    return sb.toString(); 
} 

然後con.recv()方法完全前面所示,其中isClosed()現在返回true之一。同樣,在臨時過程中沒有發生nullPointerException,換句話說,在accept和recv之間的某個地方,JVM正在關閉套接字。

(的TLSserver)的acceptConnection()方法是:

public TLSconnection acceptConnection (boolean handshake) 
throws TLSexception { 
    try (SSLSocket client = (SSLSocket)listenSock.accept()) { 
     client.setUseClientMode(false); 
     return new TLSconnection(client, handshake); 
    } catch (Throwable ex) { 
     throw new TLSexception (
      "Accept failed:", 
      ex 
     ); 
    } 
}  
+1

它看起來像服務器正在做一個乾淨的關閉(發送close_notify),所以我建議你的代碼中的東西實際上是在明確關閉連接。 –

+0

@SteffenUllrich我同意你*除了*當然我擁有所有的代碼。我已經添加了我認爲證明的事實,即它最終並未在上面關閉。 – delicateLatticeworkFever

回答

1

Socket.isClosed()返回true,如果你關閉插座。不是同伴。結論:你關閉了socket。

NB setUseClientMode()沒有必要。你所做的只是斷言默認值。如果被調用,必須在握手之前調用它。startHandshake()也是沒有必要的,它是自動的。

+0

你說得對。我在try-with-resources子句中的listen套接字上調用了'accept',然後從try中返回接受的套接字。 – delicateLatticeworkFever

相關問題