私はboost 1.55 asioでプログラムした小さなsslクライアントがあり、boost::asio::ssl::stream::async_shutdown()
が常に失敗する理由を理解しようとしています。クライアントは、boost::asio::ip::tcp::resolver::async_resolve()
-> boost::asio::ssl::stream::async_connect()
-> boost::asio::ssl::stream::async_handshake()
を経由するという点で、boostドキュメントのsslクライアントの例と非常に似ています(ほぼ同じ)。コールバックシーケンス。これはすべて期待どおりに機能し、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の例は、これがシャットダウンの正しい方法であることを示しているようです)。
暗号化された安全なシャットダウンの場合、両者はshutdown()
またはasync_shutdown()
を呼び出してboost::asio::ssl::stream
を実行することにより、 io_service
に対してシャットダウン操作を実行する必要があります。 SSLカテゴリ のないerror_code
で操作が完了し、シャットダウンの一部が発生する前にキャンセルされなかった場合、接続は安全にシャットダウンされ、基になるトランスポートが再利用またはクローズされます。最下層を単に閉じると、セッションが 切り捨て攻撃 に対して脆弱になる可能性があります。
標準化された [〜#〜] tls [〜#〜] プロトコルおよび非標準化された SSLv プロトコルでは、安全なシャットダウンには、パーティが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()
操作が正常に完了しました。これらのさまざまなシナリオを以下で詳しく説明します。各シナリオはスイムラインのような図で示され、各当事者がまったく同じ時点で何をしているのかを示しています。
shutdown()
を呼び出します PartyB シャットダウンをネゴシエートせずに接続を閉じます。このシナリオでは、 PartyB 最初にストリームでshutdown()
を呼び出さずに基礎となるトランスポートを閉じることにより、シャットダウン手順に違反します。基になるトランスポートが閉じられると、 PartyAshutdown()
を開始しようとします。
PartyA | PartyB
-------------------------------------+----------------------------------------
ssl_stream.handshake(...); | ssl_stream.handshake(...);
... | ssl_stream.lowest_layer().close();
ssl_stream.shutdown(); |
PartyAclose_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))
{
// Remote peer failed to send a close_notify message.
}
shutdown()
を呼び出します PartyB シャットダウンをネゴシエートせずに接続を閉じます。このシナリオでは、 PartyA シャットダウンを開始します。ただし、 PartyBclose_notify
メッセージを受け取り、 PartyB 基になるトランスポートを閉じる前にshutdown()
で明示的に応答しないことにより、シャットダウン手順に違反します。
PartyA | PartyB
-------------------------------------+---------------------------------------
ssl_stream.handshake(...); | ssl_stream.handshake(...);
ssl_stream.shutdown(); | ...
| ssl_stream.lowest_layer().close();
Boost.Asioのshutdown()
操作は、close_notify
が送受信された後、またはエラーが発生したときに完了したと見なされるため、 PartyAclose_notify
を送信し、応答を待ちます。 PartyB SSLプロトコルに違反して、close_notify
を送信せずに基礎となるトランスポートを閉じます。 PartyAの読み取りはboost::asio::error::eof
で失敗し、Boost.AsioはそれをSSLの短い読み取りエラーにマップします。
shutdown()
を開始して待機します PartyBshutdown()
で応答します。このシナリオでは、 PartyA シャットダウンを開始し、待機します PartyB シャットダウンで応答します。
PartyA | PartyB
-------------------------------------+----------------------------------------
ssl_stream.handshake(...); | ssl_stream.handshake(...);
ssl_stream.shutdown(); | ...
... | ssl_stream.shutdown();
これはかなり基本的なシャットダウンであり、両者はclose_notify
メッセージを送受信します。シャットダウンが両方の当事者によってネゴシエートされると、基になるトランスポートは再利用またはクローズされます。
boost::asio::error::eof
のエラー値で完了します。shutdown()
を開始しますが、待機しません PartyB 応答する。このシナリオでは、 PartyA シャットダウンを開始し、close_notify
が送信されるとすぐに基になるトランスポートを閉じます。 PartyA 待っていない PartyBclose_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();
PartyA 非同期シャットダウン操作を開始してから、非同期書き込み操作を開始します。書き込みに使用されるバッファーは、ゼロ以外の長さでなければなりません(上記ではヌル文字が使用されています)。それ以外の場合、Boost.Asioは、no-opへの書き込みを最適化します。 shutdown()
操作が実行されると、close_notify
が PartyB、SSLの書き込み側を閉じる PartyAの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キャンセルされるのclose_notify
。
shutdown()
操作が明示的にキャンセルされました。したがって、shutdown()
操作のエラーコードの値はboost::asio::error::operation_aborted
になります。要約すると、Boost.AsioのSSLシャットダウン操作は少し注意が必要です。適切なシャットダウン中のイニシエータとリモートピアのエラーコード間の不一致により、処理が少し厄介になることがあります。原則として、エラーコードのカテゴリがSSLカテゴリでない限り、プロトコルは安全にシャットダウンされました。