How to Securely Disconnect an Asio Ssl Socket

need to call ssl::stream::shutdown when closing boost asio ssl socket?

It is cleanest to make shutdown() calls on both the ssl::stream and its lowest_layer(). The first ends the SSL connection and the second ends the TCP connection. If you're getting an error on the SSL shutdown, it may be that the other side is not being as graceful in ending the connection.

boost::asio cleanly disconnecting

I think you should probably have a call to socket.shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec) in there before the call to socket.close().

The boost::asio documentation for basic_stream_socket::close states:

For portable behaviour with respect to graceful closure of a connected socket, call shutdown() before closing the socket.

This should ensure that any pending operations on the socket are properly cancelled and any buffers are flushed prior to the call to socket.close.

Using SSL sockets and non-SSL sockets simultaneously in Boost.Asio?

There's a couple of ways you can do this. In the past, I've done something like

if ( sslEnabled )
boost::asio::async_write( secureSocket_ );
} else {
boost::asio::async_write( secureSocket_.lowest_layer() );
}

Which can get messy pretty quickly with a lot of if/else statements. You could also create an abstract class (pseudo code - oversimplified)

class Socket
{
public:
virtual void connect( ... );
virtual void accept( ... );
virtual void async_write( ... );
virtual void async_read( ... );
private:
boost::asio::ip::tcp::socket socket_;
};

Then create a derived class SecureSocket to operate on a secureSocket_ instead of socket_. I don't think it would be duplicating a lot of code, and it's probably cleaner than if/else whenever you need to async_read or async_write.

boost asio ssl async_shutdown always finishes with an error?

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.



Related Topics



Leave a reply



Submit