我試圖實現一個基本的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實現,我已經通過了像this和this這樣的東西,我對這個問題的描述將遵循這種模式。
在服務器端的東西開始:
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
);
}
}
它看起來像服務器正在做一個乾淨的關閉(發送close_notify),所以我建議你的代碼中的東西實際上是在明確關閉連接。 –
@SteffenUllrich我同意你*除了*當然我擁有所有的代碼。我已經添加了我認爲證明的事實,即它最終並未在上面關閉。 – delicateLatticeworkFever