boost asio ssl async_shutdown 总是以错误结束? [英] boost asio ssl async_shutdown always finishes with an error?

查看:155
本文介绍了boost asio ssl async_shutdown 总是以错误结束?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个在 boost 1.55 asio 中编写的小型 ssl 客户端,我想弄清楚为什么 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){如果(!e){m_socket.async_shutdown( boost::bind( &ClientCertificateFinder::handle_shutdown_after_success,这,boost::asio::placeholders::error ) );}别的{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 类别 并且在部分关闭可能发生之前没有被取消,然后连接被安全关闭并且底层传输可以被重用或关闭.简单地关闭最低层可能会使会话容易受到截断攻击的攻击.<​​/p>


协议和 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() 操作成功完成.

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

PartyAPartyB 关闭连接后调用 shutdown() 而无需协商关闭.

在这种情况下,PartyB 违反了关闭程序,因为没有先在流上调用 shutdown() 就关闭底层传输.一旦底层传输关闭,PartyA 尝试启动一个 shutdown().

 PartyA |乙方-------------------------------------+-----------------------------------------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)){//远程节点发送 close_notify 消息失败.}

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

在这种情况下,PartyA 启动关闭.然而,当 PartyB 收到 close_notify 消息时,PartyB 从未使用 shutdown() 明确响应,从而违反了关闭程序> 在关闭底层传输之前.

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

由于 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 |乙方-------------------------------------+-----------------------------------------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 不会等待 PartyBclose_notify 消息响应.根据规范,这种协商关闭是允许的,并且在实现中相当普遍.

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

 PartyA |乙方-------------------------------------+---------------------------------------ssl_stream.handshake(...);|ssl_stream.handshake(...)ssl_stream.async_shutdown(...);|...常量字符缓冲区[] =";|...async_write(ssl_stream, 缓冲区, | ...[](...) { ssl_stream.close();}) |...io_service.run();|...... |ssl_stream.shutdown();

PartyA 将发起一个异步关闭操作,然后发起一个异步写操作.用于写入的缓冲区必须是非零长度(上面使用了空字符);否则,Boost.Asio 会将写入优化为无操作.当shutdown()操作运行时,它会发送close_notifyPartyB,导致SSL关闭PartyAi>的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();}

失败的 async_write() 操作将显式关闭底层传输,导致 async_shutdown() 操作等待 PartyB's close_notify 被取消.

  • 尽管 PartyA 执行了 SSL 规范允许的关闭程序,但在关闭底层传输时显式取消了 shutdown() 操作.因此,shutdown() 操作的错误代码的值为 boost::asio::error::operation_aborted.
  • PartyB 的关机操作将成功完成.

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

I have a small ssl client that I've programmed in boost 1.55 asio, and I'm trying to figure out why boost::asio::ssl::stream::async_shutdown() always fails. The client is very similar (almost identical) to the ssl client examples in the boost documentation, in that it goes through an boost::asio::ip::tcp::resolver::async_resolve() -> boost::asio::ssl::stream::async_connect() -> boost::asio::ssl::stream::async_handshake() callback sequence. All of this works as expected and the async_handshake() callback gets an all-clear boost::system::error_code.

From the async_handshake() callback, I call async_shutdown() (I don't transfer any data - this object is more for testing the handshake):

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() is then called, but always with an error? The error is value=2 in asio.misc, which is 'End of file'. I've tried this with a variety of ssl servers, and I always seem to get this asio.misc error. That this isn't an underlying openssl error suggests to me that I might be misusing asio in some way...?

Anyone know why this might be happening? I was under the impression that shutting down the connection with async_shutdown() was The Right Thing To Do, but I guess I could just call boost::asio::ssl::stream.lowestlayer().close() to close the socket out from under openssl if that's the expected way to do this (and indeed the asio ssl examples seem to indicate that this is the right way of shutting down).

解决方案

For a cryptographically secure shutdown, both parties musts execute shutdown operations on the boost::asio::ssl::stream by either invoking shutdown() or async_shutdown() and running the io_service. If the operation completes with an error_code that does not have an SSL category and was not cancelled before part of the shutdown could occur, then the connection was securely shutdown and the underlying transport may be reused or closed. Simply closing the lowest layer may make the session vulnerable to a truncation attack.


The Protocol and Boost.Asio API

In the standardized TLS protocol and the non-standardized SSLv3 protocol, a secure shutdown involves parties exchanging close_notify messages. In terms of the Boost.Asio API, either party may initiate a shutdown by invoking shutdown() or async_shutdown(), causing a close_notify message to be sent to the other party, informing the recipient that the initiator will not send more messages on the SSL connection. Per the specification, the recipient must respond with a close_notify message. Boost.Asio does not automatically perform this behavior, and requires the recipient to explicitly invoke shutdown() or async_shutdown().

The specification permits the initiator of the shutdown to close their read side of the connection before receiving the close_notify response. This is used in cases where the application protocol does not wish to reuse the underlying protocol. Unfortunately, Boost.Asio does not currently (1.56) provide direct support for this capability. In Boost.Asio, the shutdown() operation is considered complete upon error or if the party has sent and received a close_notify message. Once the operation has completed, the application may reuse the underlying protocol or close it.

Scenarios and Error Codes

Once an SSL connection has been established, the following error codes occur during shutdown:

  • One party initiates a shutdown and the remote party closes or has already closed the underlying transport without shutting down the protocol:
    • The initiator's shutdown() operation will fail with an SSL short read error.
  • One party initiates a shutdown and waits for the remote party to shutdown the protocol:
    • The initiator's shutdown operation will complete with an error value of boost::asio::error::eof.
    • The remote party's shutdown() operation completes with success.
  • One party initiates a shutdown then closes the underlying protocol without waiting for the remote party to shutdown the protocol:
    • The initiator's shutdown() operation will be cancelled, resulting in an error of boost::asio::error::operation_aborted. This is the result of a workaround noted in the details below.
    • The remote party's shutdown() operation completes with success.

These various scenarios are captured in detailed below. Each scenario is illustrated with a swim-line like diagram, indicating what each party is doing at the exact same point in time.

PartyA invokes shutdown() after PartyB closes connection without negotiating shutdown.

In this scenario, PartyB violates the shutdown procedure by closing the underlying transport without first invoking shutdown() on the stream. Once the underlying transport has been closed, the PartyA attempts to initiate a shutdown().

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

PartyA will attempt to send a close_notify message, but the write to the underlying transport will fail with boost::asio::error::eof. Boost.Asio will explicitly map the underlying transport's eof error to an SSL short read error, as PartyB violated the SSL shutdown procedure.

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 invokes shutdown() then PartyB closes connection without negotiating shutdown.

In this scenario, PartyA initiates a shutdown. However, while PartyB receives the close_notify message, PartyB violates the shutdown procedure by never explicitly responding with a shutdown() before closing the underlying transport.

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

As Boost.Asio's shutdown() operation is considered complete once a close_notify has been both sent and received or an error occurs, PartyA will send a close_notify then wait for a response. PartyB closes the underlying transport without sending a close_notify, violating the SSL protocol. PartyA's read will fail with boost::asio::error::eof, and Boost.Asio will map it to an SSL short read error.

PartyA initiates shutdown() and waits for PartyB to respond with a shutdown().

In this scenario, PartyA will initiate a shutdown and wait for PartyB to respond with a shutdown.

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

This is a fairly basic shutdown, where both parties send and receive a close_notify message. Once the shutdown has been negotiated by both parties, the underlying transport may either be reused or closed.

  • PartyA's shutdown operation will complete with an error value of boost::asio::error::eof.
  • PartyB's shutdown operation will complete with success.

PartyA initiates shutdown() but does not wait for PartyB to responsd.

In this scenario, PartyA will initiate a shutdown and then immediately close the underlying transport once close_notify has been sent. PartyA does not wait for PartyB to respond with a close_notify message. This type of negotiated shutdown is allowed per the specification and fairly common amongst implementations.

As mentioned above, Boost.Asio does not directly support this type of shutdown. Boost.Asio's shutdown() operation will wait for the remote peer to send its close_notify. However, it is possible to implement a workaround while still upholding the specification.

 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 will initiate an asynchronous shutdown operation and then initiate an asynchronous write operation. The buffer used for the write must be of a non-zero length (null character is used above); otherwise, Boost.Asio will optimize the write to a no-op. When the shutdown() operation runs, it will send close_notify to PartyB, causing SSL to close the write side of PartyA's SSL stream, and then asynchronously wait for PartyB's close_notify. However, as the write side of PartyA's SSL stream has closed, the async_write() operation will fail with an SSL error indicating the protocol has been shutdown.

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

The failed async_write() operation will then explicitly close the underlying transport, causing the async_shutdown() operation that is waiting for PartyB's close_notify to be cancelled.

  • Although PartyA performed a shutdown procedure permitted by the SSL specification, the shutdown() operation was explicitly cancelled when underlying transport was closed. Hence, the shutdown() operation's error code will have a value of boost::asio::error::operation_aborted.
  • PartyB's shutdown operation will complete with success.

In summary, Boost.Asio's SSL shutdown operations are a bit tricky. The inconstancies between the initiator and remote peer's error codes during proper shutdowns can make handling a bit awkward. As a general rule, as long as the error code's category is not an SSL category, then the protocol was securely shutdown.

这篇关于boost asio ssl async_shutdown 总是以错误结束?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆