我有一个小型的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::stream
或shutdown()
并运行async_shutdown()
对 io_service
执行关闭操作。如果操作以没有SSL category的error_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连接后,关机期间会出现以下错误代码:
shutdown()
操作将失败,并出现SSL短读取错误。 boost::asio::error::eof
的错误值完成。 shutdown()
操作成功完成。 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
消息。双方协商关闭后,基础传输可以重新使用或关闭。boost::asio::error::eof
的错误值完成。 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
操作。shutdown()
操作。因此,shutdown()
操作的错误代码将具有boost::asio::error::operation_aborted
的值。 总而言之,Boost.Asio的SSL关闭操作有些棘手。在适当关闭期间,发起方和远程对等方的错误代码之间的不一致会使处理有些尴尬。通常,只要错误代码的类别不是SSL类别,就可以安全地关闭协议(protocol)。
关于c++ - boost asio ssl async_shutdown总是以错误结束?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25587403/