2016-04-30 2 views
0

Я пытаюсь реализовать базовую структуру клиент/сервер TLS. Кажется, все идет хорошо, пока после рукопожатия, и в этот момент сервер тестового эха должен читать и записывать все, что он получает.Сервер закрывает сокет после завершения установления связи

Я запустил это с обеих сторон с помощью -Djavax.net.debug=all, а протокол понижен до TLSv1 (но удаляется от TLSv1.2). Это Oracle 8 на GNU/Linux x86-64 с установленными пакетами безопасности.

Исключений, выбрасываемых с обеих сторон, до тех пор, пока сервер не назовет BufferedInputReader.read(), используя поток, полученный от SSLSocket.getInputStream(). На этом этапе тестовый клиент сидит, ожидая ввода в консоль для отправки на эхо-тестовый сервер.

Я ранее реализовал серверы TLS 1.2 на C и C++ с использованием GnuTLS, поэтому у меня есть базовое понимание основных принципов. Что касается отладки этой реализации 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 

А «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 

и сообщает, что это «Connected», пройдя через этот соответствующий код:

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() теперь возвращает значение ИСТИНА, что имеет смысл, поскольку отладка трассировки на сервере вывод:

[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 

Для меня это, кажется, что:

  • Клиент подключается.
  • Сервер принимает.
  • ДУС рукопожатия происходит во время которых:
    • Cipher свиты успешно провели переговоры.
    • клиент проверяет сертификат сервера
  • Сервер произвольно закрывает сокет.
  • Клиент остается висящим.

Я через вариацию этого в течение нескольких часов и снова:

  • Там нет исключений брошенных.
  • Нет ошибок ошибок в отладочном выходе.
  • Невозможно объяснить, почему сервер произвольно закрывает сокет.

Полный код для трех классов (соединение, сервер, клиент) составляет около 250 строк, а также около ста каждой для тестовых экземпляров (клиент и сервер), поэтому я не размещал все это здесь, но показано, что находится в игре во время сеанса отладки.

Я предполагаю, что это не может иметь никакого отношения к ошибкам с сертификатом или ключами, поскольку нет ничего, чтобы указать на это, или что рукопожатие не удалось или что кто-либо отверг кого-либо по какой-либо причине.


В ответ на комментарий Steffen Ульриха, что «Похоже, что сервер делает чистое выключение (посылает close_notify), поэтому я хотел бы предложить, что-то в вашем коде на самом деле делает явные близко» - который является То же самое я думаю, первый - вот часть кода тест-сервера, который ведет к sock.isClosed() возвращает истину:

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() теперь возвращает истинный. Опять же, нет исключений nullPointerExceptions, которые происходят в промежуточный период и т. Д. Другими словами, где-то между этим 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() возвращает истину, если вы закрыл сокет. Не сверстник. Вывод: вы закрыли сокет.

NB setUseClientMode() не требуется. Все, что вы делаете, утверждает значения по умолчанию. Он должен быть вызван до рукопожатия , если вызвано вообще.startHandshake() также не требуется, это автоматический.

+0

Вы правы. Я вызывал 'accept' в гнезде для прослушивания в предложении try-with-resources, а затем возвращал принятый сокет из try. – delicateLatticeworkFever

Смежные вопросы