Правильное закрытие SSLSocket

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

 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 

Каждый stream закрывает входной разъем, так что в конце концов оба разъема будут закрыты.

Но что мне делать, если я хочу использовать 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. Есть ли лучшее решение для моей агонии? Я не думаю, что есть что-то разумное, так как один из streamов может быть уже в методе read блокировки, когда другой stream отмечает его «Я сделан». И единственный способ, которым я вижу, чтобы выгнать его из блокирующего streamа, – это закрыть сокет и создать исключение «закрытого сокета».

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

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

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

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

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

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

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

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

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

Хотя вы можете закрыть только половину соединения (ввод или вывод через shutdownInput() / shutdownOutput() ), базовому слою TLS по-прежнему необходимо отправить этот пакет close_notify прежде чем действительно отключить связь, в противном случае это будет рассматриваться как атака усечения.

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

EDIT : дополнительные пояснения .

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

(Просто для того, чтобы уточнить, что вы пытаетесь сделать, фактически является прокси-сервером «Man-In-The-Middle» (MITM). Вам нужно, чтобы клиент был настроен на доверие сертификату прокси, возможно, как если бы это был сервер если это должно произойти прозрачно. Как HTTPS-соединения работают через HTTP-прокси – это другой вопрос .)

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

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

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

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

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

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

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

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

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

Семантика 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, SSLEngine API. Не трогайте его, это повредит вам.

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

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

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

  • Как выйти из вызова recv () блокировки?
  • Почему писать закрытый TCP-разъем хуже, чем читать?
  • close () не закрывает гнездо правильно
  • Что вызывает мой java.net.SocketException: сброс соединения?
  • Что представляет собой самый большой размер безопасного пакета UDP в Интернете?
  • Несколько клиентов асинхронного сервера
  • Как определить IP-адрес моего маршрутизатора / шлюза в Java?
  • NetworkStream.ReadAsync с маркером отмены никогда не отменяет
  • Как исправить java.net.SocketException: Неработающая труба?
  • java.net.SocketException: сброс соединения
  • Использование .Net 4.5 Функция Async для программирования сокетов
  • Давайте будем гением компьютера.