Подтвердить что ты не робот

Boost asio ssl async_shutdown всегда заканчивается с ошибкой?

У меня есть небольшой клиент ssl, который я запрограммировал в boost 1.55 asio, и я пытаюсь понять, почему boost::asio::ssl::stream::async_shutdown() всегда терпит неудачу. Клиент очень похож (почти идентичный) на примеры клиентов ssl в документации по ускорению, поскольку он проходит последовательность обратного вызова boost::asio::ip::tcp::resolver::async_resolve()boost::asio::ssl::stream::async_connect()boost::asio::ssl::stream::async_handshake(). Все это работает так, как ожидалось, и обратный вызов async_handshake() получает всплывающее окно boost::system::error_code.

Из обратного вызова async_handshake() я вызываю async_shutdown() (я не передаю никаких данных - этот объект больше подходит для проверки рукопожатия):

void ClientCertificateFinder::handle_handshake(const boost::system::error_code& e)
{
    if ( !e )
    {
        m_socket.async_shutdown( boost::bind( &ClientCertificateFinder::handle_shutdown_after_success, 
            this, 
            boost::asio::placeholders::error ) );
    }
    else
    {
        m_handler( e, IssuerNameList() );
    }
}

handle_shutdown_after_success() затем вызывается, но всегда с ошибкой? Ошибка - value = 2 в asio.misc, что является "Конец файла". Я пробовал это с помощью множества серверов ssl, и я всегда получаю эту ошибку asio.misc. То, что это не основная ошибка openssl, подсказывает мне, что я мог бы неправильно использовать asio...?

Кто-нибудь знает, почему это может произойти? У меня создалось впечатление, что закрытие соединения с async_shutdown() было правильным делом, но я думаю, я мог просто позвонить boost::asio::ssl::stream.lowestlayer().close(), чтобы закрыть сокет из-под opensl, если это ожидаемый способ сделать это (и действительно примеры asio ssl, похоже, указывают на то, что это правильный способ закрытия).

4b9b3361

Ответ 1

Для криптографически безопасного отключения обе стороны должны выполнить операции останова на boost::asio::ssl::stream путем вызова shutdown() или async_shutdown() и запуска <io_service, Если операция завершена с помощью error_code, которая не имеет категории SSL и не была отменена до того, как часть завершения может произойти, соединение было надежно отключено, а базовый транспорт может быть повторно использован или закрыт. Простое закрытие самого низкого уровня может сделать сеанс уязвимым для атаки усечения.


Протокол и Boost.Asio API

В стандартизованном протоколе TLS и нестандартном протоколе SSLv3 безопасное завершение включает в себя обмен сторонами close_notify. В терминах API Boost.Asio любая из сторон может инициировать выключение путем вызова shutdown() или async_shutdown(), в результате чего сообщение close_notify отправляется другой стороне, сообщая получателю, что инициатор не отправит больше сообщений по SSL-соединению. По спецификациям получатель должен ответить сообщением close_notify. Boost.Asio автоматически не выполняет это поведение и требует, чтобы получатель явно вызывал shutdown() или async_shutdown().

Спецификация позволяет инициатору выключения закрыть свою сторону чтения соединения до получения ответа close_notify. Это используется в тех случаях, когда протокол приложения не желает повторно использовать базовый протокол. К сожалению, Boost.Asio в настоящее время (1.56) не обеспечивает прямую поддержку этой возможности. В Boost.Asio операция shutdown() считается завершенной при ошибке или если сторона отправила и получила сообщение close_notify. По завершении операции приложение может повторно использовать базовый протокол или закрыть его.

Сценарии и коды ошибок

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

  • Одна сторона инициирует выключение, а удаленная сторона закрывает или уже закрыла базовый транспорт, не отключая протокол:
    • Операция инициатора shutdown() завершится с ошибкой короткого чтения SSL.
  • Одна сторона инициирует выключение и ждет, когда удаленная сторона выключит протокол:
    • Операция завершения инициализации завершится со значением ошибки boost::asio::error::eof.
    • Операция удаленной стороны shutdown() завершается с успехом.
  • Одна сторона инициирует выключение, затем закрывает базовый протокол, не дожидаясь, когда удаленная сторона выключит протокол:
    • Операция инициатора shutdown() будет отменена, что приведет к ошибке boost::asio::error::operation_aborted. Это результат обходного пути, описанного ниже.
    • Операция удаленной стороны shutdown() завершается с успехом.

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

PartyA вызывает shutdown() после PartyB закрывает соединение без согласования выключения.

В этом случае PartyB нарушает процедуру выключения, закрывая базовый транспорт без первого вызова shutdown() в потоке. Когда основной транспорт закрыт, PartyA пытается инициировать shutdown().

 PartyA                              | PartyB
-------------------------------------+----------------------------------------
 ssl_stream.handshake(...);          | ssl_stream.handshake(...);
 ...                                 | ssl_stream.lowest_layer().close();
 ssl_stream.shutdown();              |

PartyA попытается отправить сообщение close_notify, но запись в базовый транспорт завершится с boost::asio::error::eof. Boost.Asio явно сопоставляет лежащую в основе транспортную ошибку eof с ошибкой короткого чтения SSL, поскольку PartyB нарушает процедуру выключения SSL.

if ((error.category() == boost::asio::error::get_ssl_category())
     && (ERR_GET_REASON(error.value()) == SSL_R_SHORT_READ))
{
  // Remote peer failed to send a close_notify message.
}

PartyA вызывает shutdown(), затем PartyB закрывает соединение без согласования выключения.

В этом случае PartyA инициирует выключение. Тем не менее, в то время как PartyB получает сообщение close_notify, PartyB нарушает процедуру выключения, никогда явно не отвечая на shutdown() перед закрытием основного транспорта.

 PartyA                              | PartyB
-------------------------------------+---------------------------------------
 ssl_stream.handshake(...);          | ssl_stream.handshake(...);
 ssl_stream.shutdown();              | ...
                                     | ssl_stream.lowest_layer().close();

Поскольку операция Boost.Asio shutdown() считается завершенной после того, как a close_notify был отправлен и принят или произошла ошибка, PartyA отправит close_notify, а затем ждет ответа. PartyB закрывает базовый транспорт, не отправляя close_notify, нарушая протокол SSL. Ошибка PartyA с ошибкой boost::asio::error::eof, а Boost.Asio сопоставляет его с короткой ошибкой чтения SSL.

PartyA инициирует shutdown() и ждет PartyBдля ответа с помощью shutdown().

В этом случае PartyA инициирует завершение работы и ждет ответа PartyB с отключением.

 PartyA                              | PartyB
-------------------------------------+----------------------------------------
 ssl_stream.handshake(...);          | ssl_stream.handshake(...);
 ssl_stream.shutdown();              | ...
 ...                                 | ssl_stream.shutdown();

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

  • Операция завершения операции
  • PartyA будет иметь значение ошибки boost::asio::error::eof. Операция закрытия
  • PartyB завершится успешно.

PartyA инициирует shutdown(), но не ждет ответа PartyB.

В этом случае PartyA инициирует завершение работы и сразу же закрывает базовый транспорт после отправки close_notify. PartyA не ожидает ответа PartyB сообщением close_notify. Этот тип согласованного выключения разрешен по спецификации и довольно распространен среди реализаций.

Как упоминалось выше, Boost.Asio напрямую не поддерживает этот тип выключения. Операция Boost.Asio shutdown() ожидает, пока удаленный одноранговый узел отправит свой close_notify. Тем не менее, можно реализовать обходной путь, сохраняя при этом спецификацию.

 PartyA                              | PartyB
-------------------------------------+---------------------------------------
 ssl_stream.handshake(...);          | ssl_stream.handshake(...)
 ssl_stream.async_shutdown(...);     | ...
 const char buffer[] = "";           | ...
 async_write(ssl_stream, buffer,     | ...
  [](...) { ssl_stream.close(); })   | ...
 io_service.run();                   | ...
 ...                                 | ssl_stream.shutdown();

PartyA инициирует асинхронную операцию выключения, а затем инициирует асинхронную операцию записи. Буфер, используемый для записи, должен иметь ненулевую длину (нулевой символ используется выше); в противном случае Boost.Asio оптимизирует запись в no-op. Когда выполняется операция shutdown(), она отправит close_notify в PartyB, заставив SSL закрыть сторону записи потока PartyA SSL, а затем асинхронно ждать PartyB close_notify. Однако по мере того, как поток записи в потоке SSL PartyA закрыт, операция async_write() завершится с ошибкой SSL, указывающей, что протокол был отключен.

if ((error.category() == boost::asio::error::get_ssl_category())
     && (SSL_R_PROTOCOL_IS_SHUTDOWN == ERR_GET_REASON(error.value())))
{
  ssl_stream.lowest_layer().close();
}

Неисправная операция async_write() затем явно закрывает базовый транспорт, в результате чего операция async_shutdown(), ожидающая отмены PartyB close_notify.

  • Хотя PartyA выполнила процедуру выключения, разрешенную спецификацией SSL, операция shutdown() была явно отменена, когда основной транспорт был закрыт. Следовательно, код ошибки shutdown() будет иметь значение boost::asio::error::operation_aborted. Операция закрытия
  • PartyB завершится успешно.

Таким образом, операции отключения Boost.Asio SSL немного сложны. Неисправности между кодами ошибок инициатора и удаленного однорангового соединения при правильном отключении могут сделать обработку немного неудобной. Как правило, до тех пор, пока категория кода ошибки не является категорией SSL, протокол был надежно отключен.

Ответ 2

PartyA инициирует shutdown() и ожидает, что PartyB ответит отключением().

В этом случае PartyA инициирует завершение работы и ожидает ответа PartyB с отключением.

 PartyA                              | PartyB
-------------------------------------+----------------------------------------
 ssl_stream.handshake(...);          | ssl_stream.handshake(...);
 ssl_stream.shutdown();              | ...
 ...                                 | ssl_stream.shutdown();

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

Операция завершения PartyA завершится с сообщением об ошибке boost:: asio:: error:: eof. Операция закрытия PartyB завершится успешно.

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