問(wèn)題描述
我有一個(gè)在 boost 1.55 asio 中編寫(xiě)的小型 ssl 客戶端,我想弄清楚為什么 boost::asio::ssl::stream::async_shutdown()
總是失敗.客戶端與 boost 文檔中的 ssl 客戶端示例非常相似(幾乎相同),因?yàn)樗ㄟ^(guò) boost::asio::ip::tcp::resolver::async_resolve()
-> boost::asio::ssl::stream::async_connect()
-> boost::asio::ssl::stream::async_handshake()
回調(diào)序列.所有這些都按預(yù)期工作,并且 async_handshake()
回調(diào)得到一個(gè)完全清晰的 boost::system::error_code
.
從 async_handshake()
回調(diào)中,我調(diào)用 async_shutdown()
(我不傳輸任何數(shù)據(jù) - 這個(gè)對(duì)象更多用于測(cè)試握手):>
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()
然后被調(diào)用,但總是有錯(cuò)誤?asio.misc
中的錯(cuò)誤是 value=2,即文件結(jié)束".我已經(jīng)在各種 ssl 服務(wù)器上嘗試過(guò)這個(gè),但我似乎總是遇到這個(gè) asio.misc
錯(cuò)誤.這不是一個(gè)潛在的 openssl 錯(cuò)誤向我表明我可能以某種方式濫用 asio ......?
有人知道為什么會(huì)發(fā)生這種情況嗎?我的印象是關(guān)閉與 async_shutdown()
的連接是正確的做法,但我想我可以調(diào)用 boost::asio::ssl::stream.lowestlayer().close()
從 openssl 下關(guān)閉套接字,如果這是預(yù)期的方式(實(shí)際上 asio ssl 示例似乎表明這是關(guān)閉的正確方式).
對(duì)于加密安全關(guān)機(jī),雙方必須在 boost::asio::ssl::stream
通過(guò)調(diào)用 shutdown()
或 async_shutdown()
并運(yùn)行 io_service
.如果操作以沒(méi)有 error_code 完成.html" rel="nofollow noreferrer">SSL 類(lèi)別 并且在部分關(guān)閉可能發(fā)生之前沒(méi)有被取消,然后連接被安全關(guān)閉并且底層傳輸可以被重用或關(guān)閉.簡(jiǎn)單地關(guān)閉最低層可能會(huì)使會(huì)話容易受到截?cái)喙舻墓??/p>
協(xié)議和 Boost.Asio API
在標(biāo)準(zhǔn)化的TLS協(xié)議和非標(biāo)準(zhǔn)化的SSLv3 協(xié)議,安全關(guān)閉涉及各方交換 close_notify
消息.在 Boost.Asio API 方面,任何一方都可以通過(guò)調(diào)用 shutdown()
或 async_shutdown()
來(lái)啟動(dòng)關(guān)閉,導(dǎo)致 close_notify
消息發(fā)送給對(duì)方,通知接收方發(fā)起方不會(huì)在 SSL 連接上發(fā)送更多消息.根據(jù)規(guī)范,接收者必須以 close_notify
消息響應(yīng).Boost.Asio 不會(huì)自動(dòng)執(zhí)行此行為,并要求接收者顯式調(diào)用 shutdown()
或 async_shutdown()
.
規(guī)范允許關(guān)閉的發(fā)起者在收到 close_notify
響應(yīng)之前關(guān)閉他們的連接讀取端.這用于應(yīng)用程序協(xié)議不希望重用底層協(xié)議的情況.不幸的是,Boost.Asio 目前 (1.56) 不提供對(duì)此功能的直接支持.在 Boost.Asio 中,shutdown()
操作在出現(xiàn)錯(cuò)誤或一方已發(fā)送和接收 close_notify
消息時(shí)被視為完成.操作完成后,應(yīng)用程序可以重用底層協(xié)議或關(guān)閉它.
場(chǎng)景和錯(cuò)誤代碼
一旦建立了 SSL 連接,關(guān)機(jī)時(shí)會(huì)出現(xiàn)以下錯(cuò)誤代碼:
- 一方發(fā)起關(guān)閉,另一方關(guān)閉或已經(jīng)關(guān)閉底層傳輸,而沒(méi)有關(guān)閉協(xié)議:
- 發(fā)起方的
shutdown()
操作將失敗,并出現(xiàn) SSL 短讀錯(cuò)誤.
- 發(fā)起方的
- 一方發(fā)起關(guān)閉,等待對(duì)方關(guān)閉協(xié)議:
- 發(fā)起者的關(guān)閉操作將完成,錯(cuò)誤值為
boost::asio::error::eof
. - 遠(yuǎn)程方的
shutdown()
操作成功完成.
- 發(fā)起者的關(guān)閉操作將完成,錯(cuò)誤值為
- 一方發(fā)起關(guān)閉然后關(guān)閉底層協(xié)議,無(wú)需等待遠(yuǎn)程方關(guān)閉協(xié)議:
- 發(fā)起者的
shutdown()
操作將被取消,導(dǎo)致boost::asio::error::operation_aborted
錯(cuò)誤.這是以下詳細(xì)信息中提到的解決方法的結(jié)果. - 遠(yuǎn)程方的
shutdown()
操作成功完成.
- 發(fā)起者的
下面詳細(xì)介紹了這些不同的場(chǎng)景.每個(gè)場(chǎng)景都用類(lèi)似游泳線的圖表來(lái)說(shuō)明,指示每一方在完全相同的時(shí)間點(diǎn)正在做什么.
PartyA 在 PartyB 關(guān)閉連接后調(diào)用 shutdown()
而無(wú)需協(xié)商關(guān)閉.
在這種情況下,PartyB 違反了關(guān)閉程序,因?yàn)闆](méi)有先在流上調(diào)用 shutdown()
就關(guān)閉底層傳輸.一旦底層傳輸關(guān)閉,PartyA 嘗試啟動(dòng)一個(gè) shutdown()
.
PartyA |乙方-------------------------------------+-----------------------------------------ssl_stream.handshake(...);|ssl_stream.handshake(...);... |ssl_stream.lowest_layer().close();ssl_stream.shutdown();|
PartyA 將嘗試發(fā)送 close_notify
消息,但寫(xiě)入底層傳輸將失敗,boost::asio::error::eof代碼>.Boost.Asio 將 明確地將底層傳輸?shù)?code>eof
錯(cuò)誤映射到SSL短讀錯(cuò)誤,因?yàn)?i>PartyB違反了SSL關(guān)閉程序.
if ((error.category() == boost::asio::error::get_ssl_category())&&(ERR_GET_REASON(error.value()) == SSL_R_SHORT_READ)){//遠(yuǎn)程節(jié)點(diǎn)發(fā)送 close_notify 消息失敗.}
PartyA 調(diào)用 shutdown()
然后 PartyB 關(guān)閉連接而不協(xié)商關(guān)閉.
在這種情況下,PartyA 啟動(dòng)關(guān)閉.然而,當(dāng) PartyB 收到 close_notify
消息時(shí),PartyB 從未使用 shutdown()
明確響應(yīng),從而違反了關(guān)閉程序> 在關(guān)閉底層傳輸之前.
PartyA |乙方-------------------------------------+---------------------------------------ssl_stream.handshake(...);|ssl_stream.handshake(...);ssl_stream.shutdown();|...|ssl_stream.lowest_layer().close();
由于 Boost.Asio 的 shutdown()
操作在 close_notify
已發(fā)送和接收或發(fā)生錯(cuò)誤時(shí)被視為完成,PartyA將發(fā)送 close_notify
然后等待響應(yīng).PartyB 關(guān)閉底層傳輸而不發(fā)送 close_notify
,這違反了 SSL 協(xié)議.PartyA 的讀取將失敗并顯示 boost::asio::error::eof
,Boost.Asio 會(huì)將其映射到 SSL 短讀取錯(cuò)誤.
PartyA 啟動(dòng) shutdown()
并等待 PartyB 響應(yīng) shutdown()
.
在這種情況下,PartyA 將啟動(dòng)關(guān)閉并等待 PartyB 響應(yīng)關(guān)閉.
PartyA |乙方-------------------------------------+-----------------------------------------ssl_stream.handshake(...);|ssl_stream.handshake(...);ssl_stream.shutdown();|...... |ssl_stream.shutdown();
這是一個(gè)相當(dāng)基本的關(guān)閉,雙方發(fā)送和接收 close_notify
消息.一旦雙方協(xié)商關(guān)閉,底層傳輸可能會(huì)被重用或關(guān)閉.
- PartyA 的關(guān)閉操作將完成,錯(cuò)誤值為
boost::asio::error::eof
. - PartyB 的關(guān)機(jī)操作將成功完成.
PartyA 啟動(dòng) shutdown()
但不等待 PartyB 響應(yīng).
在這種情況下,PartyA 將啟動(dòng)關(guān)閉,然后在發(fā)送 close_notify
后立即關(guān)閉底層傳輸.PartyA 不會(huì)等待 PartyB 以 close_notify
消息響應(yīng).根據(jù)規(guī)范,這種協(xié)商關(guān)閉是允許的,并且在實(shí)現(xiàn)中相當(dāng)普遍.
如上所述,Boost.Asio 不直接支持這種類(lèi)型的關(guān)機(jī).Boost.Asio 的 shutdown()
操作將等待遠(yuǎn)程對(duì)等方發(fā)送其 close_notify
.但是,可以在仍然支持規(guī)范的同時(shí)實(shí)施變通方法.
PartyA |乙方-------------------------------------+---------------------------------------ssl_stream.handshake(...);|ssl_stream.handshake(...)ssl_stream.async_shutdown(...);|...常量字符緩沖區(qū)[] =";|...async_write(ssl_stream, 緩沖區(qū), | ...[](...) { ssl_stream.close();}) |...io_service.run();|...... |ssl_stream.shutdown();
PartyA 將發(fā)起一個(gè)異步關(guān)閉操作,然后發(fā)起一個(gè)異步寫(xiě)操作.用于寫(xiě)入的緩沖區(qū)必須是非零長(zhǎng)度(上面使用了空字符);否則,Boost.Asio 會(huì)將寫(xiě)入優(yōu)化為無(wú)操作.當(dāng)shutdown()
操作運(yùn)行時(shí),它會(huì)發(fā)送close_notify
給PartyB,導(dǎo)致SSL關(guān)閉PartyA的寫(xiě)端i>的SSL流,然后異步等待PartyB的close_notify
.但是,由于 PartyA 的 SSL 流的寫(xiě)入端已關(guān)閉,async_write()
操作將失敗,并顯示 SSL 錯(cuò)誤,表明協(xié)議已關(guān)閉.
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()
操作將顯式關(guān)閉底層傳輸,導(dǎo)致 async_shutdown()
操作等待 PartyB's close_notify
被取消.
- 盡管 PartyA 執(zhí)行了 SSL 規(guī)范允許的關(guān)閉程序,但在關(guān)閉底層傳輸時(shí)顯式取消了
shutdown()
操作.因此,shutdown()
操作的錯(cuò)誤代碼的值為boost::asio::error::operation_aborted
. - PartyB 的關(guān)機(jī)操作將成功完成.
總而言之,Boost.Asio 的 SSL 關(guān)閉操作有點(diǎn)棘手.正確關(guān)閉期間發(fā)起方和遠(yuǎn)程對(duì)等方的錯(cuò)誤代碼之間的不一致可能使處理有點(diǎn)尷尬.一般來(lái)說(shuō),只要錯(cuò)誤代碼的類(lèi)別不是 SSL 類(lèi)別,協(xié)議就會(huì)被安全關(guān)閉.
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.
- The initiator's
- 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.
- The initiator's shutdown operation will complete with an error value of
- 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 ofboost::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.
- The initiator's
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, theshutdown()
operation's error code will have a value ofboost::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.
這篇關(guān)于boost asio ssl async_shutdown 總是以錯(cuò)誤結(jié)束?的文章就介紹到這了,希望我們推薦的答案對(duì)大家有所幫助,也希望大家多多支持html5模板網(wǎng)!