提升asio ssl async_shutdown总是以错误结束?

Ted*_*ton 28 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() );
    }
}
Run Code Online (Sandbox Code Playgroud)

handle_shutdown_after_success()然后调用,但始终有错误?错误是值= 2英寸asio.misc,即"文件结束".我用各种ssl服务器试过这个,我似乎总是得到这个asio.misc错误.这不是一个潜在的openssl错误告诉我,我可能会以某种方式滥用asio ......?

任何人都知道为什么会发生这种情况?我的印象是关闭连接async_shutdown()是正确的事情要做,但我想我可以打电话boost::asio::ssl::stream.lowestlayer().close()从openssl下关闭套接字,如果这是预期的方式这样做(事实上asio ssl示例似乎表明这是正确的关闭方式).

Tan*_*ury 54

对于加密安全关闭,双方必须boost::asio::ssl::stream通过调用shutdown()async_shutdown()运行来执行关闭操作io_service.如果操作以error_code没有SSL类别的操作完成,并且在部分关闭之前未被取消,则连接被安全关闭,并且可以重用或关闭底层传输.简单地关闭最低层可能会使会话容易受到截断攻击.


Protocol和Boost.Asio API

在标准化TLS协议和非标准化SSLv3协议中,安全关闭涉及各方交换close_notify消息.就Boost.Asio API而言,任何一方都可以通过调用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()操作成功完成.

下面详细描述了这些不同的场景.每个场景都用类似游泳线的图表来说明,表明每一方在完全相同的时间点正在做什么.

PartyBshutdown()在没有协商关闭的情况下关闭连接后调用PartyA.

在这种情况下,PartyB通过关闭基础传输而不首先调用shutdown()流来违反关闭过程.一旦基础运输工具关闭,PartyA就会尝试启动shutdown().

 PartyA                              | PartyB
-------------------------------------+----------------------------------------
 ssl_stream.handshake(...);          | ssl_stream.handshake(...);
 ...                                 | ssl_stream.lowest_layer().close();
 ssl_stream.shutdown();              |
Run Code Online (Sandbox Code Playgroud)

PartyA将尝试发送close_notify消息,但对底层传输的写入将失败boost::asio::error::eof.由于PartyB违反了SSL关闭程序,Boost.Asio将显式地将底层传输的eof错误映射到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.
}
Run Code Online (Sandbox Code Playgroud)

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();
Run Code Online (Sandbox Code Playgroud)

由于Boost.Asio的shutdown()操作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();
Run Code Online (Sandbox Code Playgroud)

这是一个相当基本的关闭,双方发送和接收close_notify消息.一旦双方协商关闭,基础传输可以重用或关闭.

  • PartyA的关闭操作将以错误值为完成boost::asio::error::eof.
  • PartyB的关机操作将成功完成.

甲方启动shutdown(),但不会等待乙方向responsd.

在这种情况下,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();
Run Code Online (Sandbox Code Playgroud)

PartyA将启动异步关闭操作,然后启动异步写操作.用于写入的缓冲区必须是非零长度(上面使用空字符); 否则,Boost.Asio将优化写入无操作.当shutdown()操作运行时,它将发送close_notifyPartyB,导致SSL关闭PartyA的SSL流的写入端,然后异步等待PartyBclose_notify.但是,由于PartyA的SSL流的写入端已关闭,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();
}
Run Code Online (Sandbox Code Playgroud)

失败的async_write()则操作将直接关闭底层传输,造成async_shutdown()正在等待操作乙方close_notify将被取消.

  • 虽然PartyA执行了SSL规范允许的关闭过程,但是shutdown()当关闭基础传输时,操作被明确取消.因此,shutdown()操作的错误代码的值为boost::asio::error::operation_aborted.
  • PartyB的关机操作将成功完成.

总之,Boost.Asio的SSL关闭操作有点棘手.在适当的关闭期间,启动器和远程对等方的错误代码之间的不一致可能使处理有点尴尬.作为一般规则,只要错误代码的类别不是SSL类别,那么协议就会安全关闭.

  • 在花了几十个小时试图弄清楚如何处理ssl :: stream对象的优雅关闭后,这终于给了我正在寻找的答案.克里斯托弗应该在asio文档中添加一个特殊部分来澄清. (6认同)
  • 出色的答案,并为将来提供参考!谢谢! (2认同)
  • @Vinnie Fair点。这些图并不是要表示堆栈,而是要捕获事件的顺序(如果是堆栈,则io_service.run()将使缓冲区保持活动状态)。我认为使用null_buffers会更改检测SSL流的本地写端何时已关闭(SSL_write会因关闭错误而失败)的期望条件,如[`ssl_stream.async_write_some`](http:/ /www.boost.org/doc/libs/1_60_0/doc/html/boost_asio/reference/ssl__stream/async_write_some.html)中带有“ null_buffers”的内容将被优化为无操作,从不尝试使用SSL_write。 (2认同)
  • 似乎关闭也可能因类别 boost::asio::ssl::error::detail::stream_category (而不是 ssl_category)的“ssl::error::stream_truncated”(即 SSL_R_SHORT_READ)而失败? (2认同)