c++ - boost asio ssl async_shutdown总是以错误结束?

标签 c++ ssl boost openssl boost-asio

我有一个小型的ssl客户端,我已经在boost 1.55 asio中进行了编程,我试图弄清楚boost::asio::ssl::stream::async_shutdown()为什么总是失败。该客户端与boost文档中的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(),但总是出错?错误是asio.misc中的value = 2,它是“文件末尾”。我已经在各种ssl服务器上进行了尝试,但似乎总是收到asio.misc错误。那不是潜在的openssl错误向我暗示我可能以某种方式滥用了asio ...?

有人知道为什么会这样吗?我的印象是,关闭与async_shutdown()的连接是正确的事情,但是我想我可以调用boost::asio::ssl::stream.lowestlayer().close()从openssl下关闭套接字(如果这是这样做的预期方式)(实际上是asio ssl示例似乎表明这是关闭的正确方法)。

最佳答案

对于加密安全的关闭,双方必须通过调用boost::asio::ssl::streamshutdown()并运行async_shutdown() io_service 执行关闭操作。如果操作以没有SSL categoryerror_code完成,并且未在部分关闭发生之前被取消,则说明该连接已安全关闭,并且基础传输可以重新使用或关闭。简单地关闭最低层可能会使 session 容易受到truncation attack攻击。

协议(protocol)和Boost.Asio API

在标准化TLS协议(protocol)和非标准化SSLv3协议(protocol)中,安全关闭涉及各方交换close_notify消息。就Boost.Asio API而言,任何一方都可以通过调用shutdown()async_shutdown()来启动关闭操作,从而将close_notify消息发送给另一方,从而通知接收者发起者将不会在SSL连接上发送更多消息。根据规范,接收者必须以close_notify消息作为响应。 Boost.Asio不会自动执行此行为,并且要求接收者显式调用shutdown()async_shutdown()

该规范允许关闭的启动器在收到close_notify响应之前关闭其连接的读取侧。在应用协议(protocol)不希望重用基础协议(protocol)的情况下使用此方法。不幸的是,Boost.Asio当前(1.56)尚未为此功能提供直接支持。在Boost.Asio中,如果发生错误或当事方已发送和接收shutdown()消息,则close_notify操作被视为完成。操作完成后,应用程序可以重新使用基础协议(protocol)或将其关闭。

方案和错误代码

建立SSL连接后,关机期间会出现以下错误代码:

  • 一方发起关闭,而远程方在不关闭协议(protocol)的情况下关闭或已经关闭了基础传输:
  • 启动器的shutdown()操作将失败,并出现SSL短读取错误。
  • 一方发起关闭并等待远程方关闭协议(protocol):
  • 启动程序的关闭操作将以boost::asio::error::eof的错误值完成。
  • 远程用户的shutdown()操作成功完成。
  • 一方发起关闭,然后关闭基础协议(protocol),而无需等待远程方关闭协议(protocol):
  • 启动器的shutdown()操作将被取消,从而导致boost::asio::error::operation_aborted错误。这是以下详细信息中指出的解决方法的结果。
  • 远程用户的shutdown()操作成功完成。

  • 这些详细情况将在下面详细介绍。每个场景都用类似游标线的图来说明,指示每个参与者在完全相同的时间点正在做什么。

    在甲方关闭连接而不协商关机的情况下,甲方调用shutdown()

    在这种情况下,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。由于PartyB违反了SSL关闭程序,Boost.Asio会将底层传输的eof错误explicitly map转换为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关闭连接而不协商关闭。

    在这种情况下,甲方发起关闭。但是,当PartyB收到close_notify消息时,PartyB违反了关闭过程,因为在关闭基础传输之前从未明确使用shutdown()做出响应。

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

    一旦Boost.Asio的shutdown()发送和接收完毕或发生错误,就认为Boost.Asio的close_notify操作已完成,因此PartyA将发送close_notify然后等待响应。甲方B在不发送close_notify的情况下关闭了基础传输,这违反了SSL协议(protocol)。 PartyA的读取将以boost::asio::error::eof失败,Boost.Asio会将其映射为SSL短读取错误。

    PartyA启动shutdown()并等待PartyB用shutdown()响应。

    在这种情况下,甲方将启动关机并等待甲方响应以关机。

     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后立即关闭基础传输。甲方不等待甲方响应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();
    

    甲方将启动异步关闭操作,然后启动异步写操作。用于写操作的缓冲区的长度必须为非零(上面使用了空字符)。否则,Boost.Asio将优化对无操作的写入。当shutdown()操作运行时,它将close_notify发送给PartyB,导致SSL关闭PartyA的SSL流的写端,然后异步等待PartyB的close_notify。但是,由于PartyA的SSL流的写端已关闭,因此async_write()操作将失败,并显示SSL错误,指示该协议(protocol)已关闭。

    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()操作将显式关闭基础传输,从而导致正在等待PartyB的async_shutdown()被取消的close_notify操作。
  • 尽管PartyA执行了SSL规范允许的关闭过程,但在关闭基础传输时,明确取消了shutdown()操作。因此,shutdown()操作的错误代码将具有boost::asio::error::operation_aborted的值。
  • PartyB的关闭操作将成功完成。


  • 总而言之,Boost.Asio的SSL关闭操作有些棘手。在适当关闭期间,发起方和远程对等方的错误代码之间的不一致会使处理有些尴尬。通常,只要错误代码的类别不是SSL类别,就可以安全地关闭协议(protocol)。

    关于c++ - boost asio ssl async_shutdown总是以错误结束?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25587403/

    相关文章:

    c++ - 简单的网络编程程序 - 不工作

    c++ - 当我打开 visual studio 时,MFC 日期时间选择器格式发生变化

    java - 使用 WSDL2Java 类时出现认证错误

    c++ - 32位系统读取4GB文件的方法

    c++ - 可调用类成员检测器惯用法和 C++11 final

    c++ - 窗外填充背景QT

    mysql - Heroku SSL 连接错误不支持的协议(protocol)

    ssl - 如何从现有的叶证书生成中间证书和根证书?

    c++ - 结合 boost unordered_map、bind 和 std::find_if

    c++ - 如何在 boost::program_options 中接受空值