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

时间:2014-08-30 22:51:55

标签: 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中的错误为值= 2,即文件结尾'。我已经尝试过使用各种ssl服务器,而且我似乎总是遇到这个asio.misc错误。这不是一个潜在的openssl错误告诉我,我可能会以某种方式滥用asio ......?

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

1 个答案:

答案 0 :(得分:49)

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


协议和Boost.Asio API

在标准化TLS协议和非标准化SSLv3协议中,安全关闭涉及各方交换close_notify消息。就Boost.Asio API而言,任何一方都可以通过调用shutdown()async_shutdown()来启动关闭,从而导致将close_notify消息发送给另一方,通知收件人initiator不会在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()操作成功完成。

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

PartyB 关闭连接而未协商关闭后,

PartyA 调用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将explicitly map基础传输的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.
}

PartyA 调用shutdown()然后 PartyB 关闭连接而不协商关闭。

在这种情况下, PartyA 启动关机。但是,当 PartyB 收到close_notify消息时, PartyB 违反了关闭过程,因为在关闭基础传输之前从不显式响应shutdown()。< / p>

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

由于Boost.Asio的shutdown()操作在发送和接收close_notify或发生错误后被视为已完成, PartyA 将发送{ {1}}然后等待回复。 PartyB 关闭基础传输而不发送close_notify,违反了SSL协议。 PartyA 的读取将失败并显示close_notify,Boost.Asio会将其映射为SSL短读错误。

PartyA 发起boost::asio::error::eof并等待 PartyB shutdown()回复。

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

shutdown()

这是一个相当基本的关闭,双方发送和接收 PartyA | PartyB -------------------------------------+---------------------------------------- ssl_stream.handshake(...); | ssl_stream.handshake(...); ssl_stream.shutdown(); | ... ... | ssl_stream.shutdown(); 消息。一旦双方协商关闭,基础传输可以重复使用或关闭。

  • PartyA 的关机操作将完成,错误值为close_notify
  • PartyB 的关机操作将成功完成。

PartyA 发起boost::asio::error::eof但不等待 PartyB 负责。

在这种情况下, PartyA 将启动关闭,然后在shutdown()发送后立即关闭基础传输。 PartyA 不等待 PartyB close_notify消息回复。根据规范允许这种类型的协商关闭,并且在实现中相当普遍。

如上所述,Boost.Asio不直接支持这种类型的关闭。 Boost.Asio的close_notify操作将等待远程对等方发送其shutdown()。但是,可以在坚持规范的同时实施变通方法。

close_notify

PartyA 将启动异步关闭操作,然后启动异步写入操作。用于写入的缓冲区必须是非零长度(上面使用空字符);否则,Boost.Asio将优化写入无操作。当 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(); 操作运行时,它会将shutdown()发送到 PartyB ,从而导致SSL关闭 PartyA SSL的写入端流,然后异步等待 PartyB close_notify。但是,由于 PartyA 的SSL流的写入端已关闭,close_notify操作将失败,并显示SSL错误,指示协议已关闭。

async_write()

失败的if ((error.category() == boost::asio::error::get_ssl_category()) && (SSL_R_PROTOCOL_IS_SHUTDOWN == ERR_GET_REASON(error.value()))) { ssl_stream.lowest_layer().close(); } 操作将显式关闭底层传输,导致等待 PartyB async_write()的{​​{1}}操作被取消。

  • 虽然 PartyA 执行了SSL规范允许的关闭过程,但在关闭基础传输时明确取消了async_shutdown()操作。因此,close_notify操作的错误代码的值为shutdown()
  • PartyB 的关机操作将成功完成。

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