Я пытаюсь реализовать базовую структуру клиент/сервер 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
);
}
}
Похоже, что сервер выполняет чистое выключение (отправляет close_notify), поэтому я бы предположил, что что-то в вашем коде фактически выполняет явное закрытие соединения. –
@SteffenUllrich Я согласен с тобой * кроме * конечно, у меня есть весь код. Я добавил то, что я считаю демонстративным из-за того, что он не закрыт там выше, в конце. – delicateLatticeworkFever