問題描述
我有一個在 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
.如果操作以沒有 error_code 完成.html" rel="nofollow noreferrer">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()
操作成功完成.
- 發起者的
下面詳細介紹了這些不同的場景.每個場景都用類似游泳線的圖表來說明,指示每一方在完全相同的時間點正在做什么.
PartyA 在 PartyB 關閉連接后調用 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 不會等待 PartyB 以 close_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_notify
給PartyB,導致SSL關閉PartyA的寫端i>的SSL流,然后異步等待PartyB的close_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.
- 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.
這篇關于boost asio ssl async_shutdown 總是以錯誤結束?的文章就介紹到這了,希望我們推薦的答案對大家有所幫助,也希望大家多多支持html5模板網!