Это был мой первоначальный (временно удаленный) ответ, но он, по-видимому, был недостаточно хорош, так как довольно быстро получил -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
, но протокол приложения также поможет вам определить, когда это уместно.