Правильно закрывающий SSLSocket - PullRequest
8 голосов
/ 21 июня 2011

Я хочу реализовать SSL-прокси в Java.Я в основном открываю два сокета browser-proxy, proxy-server и запускаю два потока, которые записывают в proxy-server то, что они читают из browser-proxy, и наоборот.Каждый поток выглядит следующим образом:

while (true) {
   nr = in.read(buffer);
   if (nr == -1) System.out.println(sockin.toString()+" EOF  "+nr);
   if (nr == -1) break;
   out.write(buffer, 0, nr);
}
sockin.shutdownInput();
sockout.shutdownOutput(); // now the second thread will receive -1 on read

Каждый поток закрывает только входной сокет, так что в конечном итоге оба сокета закрываются.

Но что мне делать, если я хочу использовать SSLSocket?Похоже, что методы shutdownOutput/Input там не поддерживаются.Вот исключение, которое я получаю.

Exception in thread "Thread-35" java.lang.UnsupportedOperationException: \
The method shutdownInput() is not supported in SSLSocket
    at com.sun.net.ssl.internal.ssl.BaseSSLSocketImpl.shutdownInput(Unknown Source)

Я придумал следующее:

try {
    while (true) {
       nr = in.read(buffer);
       if (nr == -1) System.out.println(sockin.toString()+" EOF  "+nr);
       if (nr == -1) break;
       out.write(buffer, 0, nr);
    }
    // possible race condition if by some mysterious way both threads would get EOF
    if (!sockin.isClosed()) sockin.close();
    if (!sockout.isClosed()) sockout.close();
} catch (SocketException e) {/*ignore expected "socket closed" exception, */}

Я должен перехватывать и игнорировать исключение конца сокета каждый раз, когда заканчивается мой сокет.

Мои вопросы:

  1. Если shutdownInput() не поддерживается, почему я получаю -1 ответ от SSLSocket.
  2. Есть лилучшее решение для моей агонии?Я не думаю, что есть что-то вменяемое, так как один из потоков может быть уже в методе блокирования read, когда другой поток помечает его как «Я закончил».И единственный способ вывести его из блокирующего потока - закрыть сокет и вызвать исключение «закрытый сокет».

    (Можно предположить, что вы использовали некую нечестивую комбинацию многопоточной передачи сообщений и асинхронных данныхчтение для того, чтобы просигнализировать другой поток, что ваша работа завершена, но это так ужасно, что я боюсь, что полицейский стиля IntelliJ придет за мной только для того, чтобы подумать об этом ...)

Пояснение: Я знаю, что shutdownInput и shutdownOutput не имеет смысла, так как вы не можете иметь полудуплексное соединение TLS, согласно спецификации.Но учитывая это, как я могу завершить соединение TLS в Java без получения исключения?

Пожалуйста, не говорите мне, что shutdownIput не имеет смысла для TLS.Я знаю, что это не то, о чем я спрашиваю .Я спрашиваю, что еще я могу использовать, чтобы правильно закрыть SSLSocket (отсюда и название "Правильно закрывающий SSLSocket".

Ответы [ 3 ]

18 голосов
/ 21 июня 2011

Это был мой первоначальный (временно удаленный) ответ, но он, по-видимому, был недостаточно хорош, так как довольно быстро получил -2 ... Думаю, мне следует добавить больше объяснений.

Невозможно закрыть половину соединения для сокетов SSL / TLS, чтобы соответствовать протоколу TLS, как указано в разделе для предупреждений о закрытии .

Клиенти сервер должен делиться информацией о том, что соединение заканчивается, чтобы избежать атаки усечения.Любая из сторон может инициировать обмен сообщениями закрытия.

close_notify Это сообщение уведомляет получателя о том, что отправитель не будет отправлять больше сообщений по этому соединению.Сеанс становится невозобновляемым, если какое-либо соединение завершается без надлежащих сообщений close_notify с уровнем, равным предупреждению.

Любая из сторон может инициировать закрытие, отправив предупреждение close_notify.Любые данные, полученные после предупреждения о закрытии, игнорируются.

Каждая сторона должна отправить предупреждение close_notify перед закрытием стороны записи соединения.Требуется, чтобы другая сторона ответила собственным предупреждением close_notify и немедленно закрыла соединение, отбрасывая все ожидающие записи.Инициатору закрытия не требуется ждать ответа оповещения close_notify перед закрытием считывающей стороны соединения.

Хотя может потребоваться закрыть только половину соединения (вход или выход)через shutdownInput() / shutdownOutput()) нижележащий уровень TLS все еще должен отправить этот close_notify пакет перед тем, как действительно отключить связь, в противном случае он будет рассматриваться как атака с усечением.довольно просто: используйте close() только на SSLSocket s: он не просто закрывает соединение, как это было бы для обычных сокетов TCP, но он делает это чисто в соответствии со спецификацией SSL / TLS.

РЕДАКТИРОВАТЬ : Дополнительные пояснения .

Короткий ответ по-прежнему: используйте close(), вам также может понадобиться узнать, когда это уместно, из протокола наtop of SSL / TLS.

(Просто чтобы уточнить, что вы пытаетесь сделать, это фактически прокси-сервер "MITM"). Вам нужно настроить клиент наТРУЗИt сертификат прокси, возможно, как если бы это был сертификат сервера, если это должно происходить прозрачно.Как HTTPS-соединения работают через HTTP-прокси - это другой вопрос .)

В некоторых ваших комментариях к вашему другому связанному вопросу у меня сложилось впечатление, что вы подумалито, что не возвращалось -1, было «нарушением протокола TLS».Дело не в протоколе, а в API: способ, которым структуры программирования (классы, методы, функции, ...) предоставляются (программисту) пользователю стека TLS для возможности использования TLS.SSLSocket - это просто часть API, предоставляющая программистам возможность использовать SSL / TLS аналогично обычному Socket.Спецификация TLS вообще не говорит о сокетах.В то время как TLS (и его предшественник, SSL) были созданы с целью сделать возможным предоставление такого рода абстракции, в конце дня SSLSocket - это всего лишь абстракция.В соответствии с принципами проектирования ООП, SSLSocket наследует Socket и старается максимально близко придерживаться поведения Socket.Однако из-за способа работы SSL / TLS (а также из-за того, что SSLSocket эффективно располагается поверх обычного Socket), не может быть точного сопоставления всех объектов.

В частности, областьперехода от обычного сокета TCP к сокету SSL / TLS не может быть прозрачно смоделирован.Некоторые произвольные выборы (например, как происходит рукопожатие) должны были быть сделаны при разработке класса SSLSocket: это компромисс между тем, чтобы (а) не подвергать слишком много специфичности TLS пользователю SSLSocket, (б) сделатьмеханизм TLS работает и (c) отображает все это на существующий интерфейс, предоставляемый суперклассом (Socket).Этот выбор неизбежно влияет на функциональность SSLSocket, и поэтому его страница API Javadoc имеет относительно большой объем текста. SSLSocket.close(), с точки зрения API, должен соответствовать описанию Socket.close(). InputStream / OutputStream, полученный из SSLSocket, отличается от значения из базовой равнины Socket, которое (если вы не преобразовали его явно ) вы можете не увидеть.

SSLSocket спроектирован так, чтобы его можно было использовать как можно точнее, как если бы он был простым Socket, а полученный InputStream разработан так, чтобы вести себя максимально близко к потоку ввода на основе файлов. Кстати, это не относится к Java. Даже сокеты Unix в C используются с файловым дескриптором и read(). Эти абстракции удобны и работают большую часть времени, если вы знаете, каковы их ограничения.

Когда дело доходит до read(), даже в C понятие EOF происходит от конца файла, но вы на самом деле не имеете дело с файлами. Проблема с TCP-сокетами заключается в том, что, хотя вы можете определить, когда удаленная сторона решила закрыть соединение, когда она отправляет FIN, вы не можете обнаружить его, если он ничего не отправляет. При чтении ничего из сокета невозможно определить разницу между неактивным и разорванным соединением. Таким образом, когда дело доходит до сетевого программирования, полагайтесь на чтение -1 из InputStream, если это нормально, но вы никогда не полагаетесь исключительно на это, иначе вы можете получить неизданные, неработающие соединения. В то время как сторонам «вежливо» правильно закрывать половину TCP-соединения (например, удаленная сторона читает -1 на своем InputStream или что-то в этом роде, как описано в Упорядоченная версия против сбойного соединения в Java) ), обработки этого случая недостаточно. Вот почему протоколы поверх TCP обычно предназначены для индикации того, когда они закончили отправку данных : например, SMTP отправляет QUIT (и имеет определенные терминаторы), HTTP 1.1 использует Content-Length или кусочную кодировку.

(Кстати, если вас не устраивает абстракция, предоставляемая SSLSocket, попробуйте использовать SSLEngine напрямую. Вероятно, это будет сложнее, и это не изменит того, что спецификация TLS говорит об отправке close_notify.)

В общем случае с TCP на уровне протокола приложения вы должны знать, когда прекратить отправку и получение данных (чтобы избежать невыпущенных соединений и / или полагаться на ошибки тайм-аута). Это предложение в спецификации TLS делает эту хорошую практику более строгим требованием, когда речь идет о TLS: « Требуется, чтобы другая сторона ответила собственным предупреждением close_notify и закрыла соединение, немедленно отбрасывая все ожидающие записи. » Вам нужно будет как-то синхронизироваться через протокол приложения, или удаленной стороне все еще есть, что написать (особенно если вы разрешаете конвейерные запросы / ответы).

Если прокси-сервер, который вы пишете, предназначен для перехвата HTTPS-соединений (как MITM), вы можете посмотреть на содержание запросов. Даже если запросы HTTP передаются по конвейеру, вы можете подсчитать количество ответов, отправленных целевой сервер (и ожидайте, когда они завершатся, через Content-Length или разделители чанков).

Однако вы не сможете иметь чисто прозрачный универсальный «перехватчик» TLS. TLS предназначен для обеспечения безопасности транспортировки между двумя сторонами, а не для посередине. Хотя большая часть механизма для достижения этой защиты (избегая MITM) выполняется через сертификат сервера, некоторые другие механизмы TLS будут мешать (одним из них является предупреждение о закрытии). Помните, что вы фактически создаете два отдельных TLS-соединения, и тот факт, что их трудно объединить, не должно вызывать удивления, учитывая назначение TLS.

SSLSocket.close() - это правильный способ закрыть SSLSocket, но протокол приложения также поможет вам определить, когда это уместно.

0 голосов
/ 26 августа 2011

Чтобы решить вашу проблему, вы должны сделать следующее:

OutputStream sockOutOStream = sockout.getOutputStream();
sockOutOStream.write(new byte[0]);
sockOutOStream.flush();
sockout.close();

На другом конце сторона, которая читает из сокета, получит сообщение -1 длины вместо брошенного SocketException.

0 голосов
/ 23 июня 2011

Семантика SSL для закрытия соединений отличается от TCP, поэтому shutdownInput и shutdownOutput на самом деле не имеют смысла, по крайней мере на уровне управления, доступном в SSLSocket. SSLSocket в основном освобождает вас от бремени обработки записей SSL и обработки сообщений рукопожатия SSL. Он также обрабатывает обработку предупреждений SSL, и одним из этих предупреждений является предупреждение close_notify. Вот текст из RFC 2246 о значении этого предупреждения.

"...Either party may initiate a close by sending a close_notify alert.
   Any data received after a closure alert is ignored.

   Each party is required to send a close_notify alert before closing
   the write side of the connection. It is required that the other party
   respond with a close_notify alert of its own and close down the
   connection immediately, discarding any pending writes. It is not
   required for the initiator of the close to wait for the responding
   close_notify alert before closing the read side of the connection...."

Java предоставляет самоубийцам другой API, API-интерфейс SSLEngine. Не трогай это, тебе будет больно.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...