c++ ssl boost openssl boost-asio

c++ - boost asio ssl async_shutdown siempre termina con un error?



openssl boost-asio (1)

Tengo un pequeño cliente ssl que he programado en boost 1.55 asio, y estoy tratando de descubrir por qué boost::asio::ssl::stream::async_shutdown() siempre falla. El cliente es muy similar (casi idéntico) a los ejemplos de cliente ssl en la documentación de impulso, ya que pasa por un boost::asio::ip::tcp::resolver::async_resolve() -> boost::asio::ssl::stream::async_connect() -> boost::asio::ssl::stream::async_handshake() secuencia de devolución de llamada. Todo esto funciona como se esperaba y la devolución de llamada async_handshake() obtiene un boost::system::error_code claro boost::system::error_code .

Desde la devolución de llamada async_shutdown() , llamo async_shutdown() (no transfiero ningún dato; este objeto es más para probar el apretón de manos):

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() , pero siempre con un error? El error es value = 2 en asio.misc , que es ''Fin del archivo''. He intentado esto con una variedad de servidores SSL, y siempre me asio.misc este error asio.misc . Que esto no es un error de OpenSL subyacente me sugiere que podría estar haciendo un mal uso de Asio de alguna manera ...

Alguien sabe por qué esto podría estar sucediendo? Tenía la impresión de que cerrar la conexión con async_shutdown() era lo correcto, pero creo que podría llamar a boost::asio::ssl::stream.lowestlayer().close() para cerrar el socket out from under openssl si esa es la forma esperada de hacer esto (y de hecho los ejemplos de asio ssl parecen indicar que esta es la forma correcta de cerrar).


Para un apagado criptográficamente seguro, ambas partes deben ejecutar operaciones de apagado en boost::asio::ssl::stream invocando shutdown() o async_shutdown() y ejecutando io_service . Si la operación se completa con un error_code que no tiene una categoría SSL y no se canceló antes de que pudiera ocurrir parte del apagado, entonces la conexión se cerró de forma segura y el transporte subyacente puede reutilizarse o cerrarse. Simplemente cerrar la capa más baja puede hacer que la sesión sea vulnerable a un ataque de truncamiento .

El protocolo y la API Boost.Asio

En el protocolo TLS estandarizado y el protocolo SSLv3 no estandarizado, un cierre seguro implica que las partes intercambien mensajes close_notify . En términos de la API Boost.Asio, cualquiera de las partes puede iniciar un cierre invocando shutdown() o async_shutdown() , haciendo que se close_notify un mensaje close_notify a la otra parte, informando al destinatario que el iniciador no enviará más mensajes en el Conexión SSL Según la especificación, el destinatario debe responder con un mensaje close_notify . Boost.Asio no realiza automáticamente este comportamiento y requiere que el destinatario invoque explícitamente shutdown() o async_shutdown() .

La especificación permite que el iniciador del apagado cierre su lado de lectura de la conexión antes de recibir la respuesta close_notify . Esto se usa en casos en los que el protocolo de aplicación no desea reutilizar el protocolo subyacente. Desafortunadamente, Boost.Asio no proporciona actualmente (1.56) soporte directo para esta capacidad. En Boost.Asio, la operación shutdown() se considera completa en caso de error o si la parte ha enviado y recibido un mensaje close_notify . Una vez que la operación se ha completado, la aplicación puede reutilizar el protocolo subyacente o cerrarlo.

Escenarios y códigos de error

Una vez que se ha establecido una conexión SSL, se producen los siguientes códigos de error durante el apagado:

  • Una parte inicia un cierre y la parte remota cierra o ya ha cerrado el transporte subyacente sin cerrar el protocolo:
    • La operación shutdown() del iniciador fallará con un error de lectura corta SSL.
  • Una de las partes inicia un apagado y espera a que la parte remota cierre el protocolo:
    • La operación de apagado del iniciador se completará con un valor de error de boost::asio::error::eof .
    • La operación shutdown() la parte remota se completa con éxito.
  • Una parte inicia un apagado y luego cierra el protocolo subyacente sin esperar a que la parte remota cierre el protocolo:
    • La operación shutdown() del iniciador se cancelará, dando como resultado un error de boost::asio::error::operation_aborted . Este es el resultado de una solución alternativa indicada en los detalles a continuación.
    • La operación shutdown() la parte remota se completa con éxito.

Estos diversos escenarios se capturan en detalle a continuación. Cada escenario se ilustra con un diagrama similar a una línea de natación, que indica lo que cada parte está haciendo exactamente en el mismo momento.

PartyA invoca shutdown() después de que PartyB cierra la conexión sin negociar el apagado.

En este escenario, PartyB viola el procedimiento de apagado al cerrar el transporte subyacente sin invocar primero shutdown() en la transmisión. Una vez que se ha cerrado el transporte subyacente, el PartyA intenta iniciar un shutdown() .

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

PartyA intentará enviar un mensaje close_notify , pero la escritura en el transporte subyacente fallará con boost::asio::error::eof . Boost.Asio asignará explícitamente el error de eof del transporte subyacente a un error de lectura corta SSL, ya que PartyB violó el procedimiento de apagado de 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. }

PartyA invoca shutdown() luego PartyB cierra la conexión sin negociar el apagado.

En este escenario, PartyA inicia un cierre. Sin embargo, mientras PartyB recibe el mensaje close_notify , PartyB viola el procedimiento de apagado al no responder explícitamente con shutdown() antes de cerrar el transporte subyacente.

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

Como la operación shutdown() Boost.Asio se considera completa una vez que se ha enviado y recibido una notificación close_notify o se produce un error, PartyA enviará una close_notify luego esperará una respuesta. PartyB cierra el transporte subyacente sin enviar un close_notify , violando el protocolo SSL. La lectura de PartyA fallará con boost::asio::error::eof , y Boost.Asio lo asignará a un error de lectura corta SSL.

PartyA inicia shutdown() y espera a que PartyB responda con shutdown() .

En este escenario, PartyA iniciará un cierre y esperará a que PartyB responda con un cierre.

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

Este es un cierre bastante básico, donde ambas partes envían y reciben un mensaje close_notify . Una vez que el cierre ha sido negociado por ambas partes, el transporte subyacente puede reutilizarse o cerrarse.

  • La operación de apagado de PartyA se completará con un valor de error de boost::asio::error::eof .
  • La operación de apagado de PartyB se completará con éxito.

PartyA inicia shutdown() pero no espera a que PartyB responda.

En este escenario, PartyA iniciará un apagado y luego cerrará inmediatamente el transporte subyacente una vez que se haya enviado close_notify . PartyA no espera a que PartyB responda con un mensaje close_notify . Este tipo de cierre negociado está permitido según la especificación y es bastante común entre las implementaciones.

Como se mencionó anteriormente, Boost.Asio no admite directamente este tipo de apagado. La operación shutdown() Boost.Asio esperará a que el par remoto envíe su close_notify . Sin embargo, es posible implementar una solución mientras se mantiene la especificación.

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 iniciará una operación de apagado asincrónico y luego iniciará una operación de escritura asincrónica. El búfer utilizado para la escritura debe ser de longitud distinta de cero (el carácter nulo se usa arriba); de lo contrario, Boost.Asio optimizará la escritura en un no-op. Cuando se ejecuta la operación shutdown() , enviará close_notify a PartyB , lo que hará que SSL cierre el lado de escritura de la transmisión SSL de PartyA y luego asincrónicamente espere a cerrar la notificación de close_notify . Sin embargo, como el lado de escritura del flujo SSL de PartyA se ha cerrado, la operación async_write() fallará con un error SSL que indica que el protocolo se ha cerrado.

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

La operación async_write() fallida async_write() explícitamente el transporte subyacente, causando la operación async_shutdown() que está esperando que se cancele el close_notify de close_notify .

  • Aunque PartyA realizó un procedimiento de apagado permitido por la especificación SSL, la operación shutdown() se canceló explícitamente cuando se cerró el transporte subyacente. Por lo tanto, el código de error de la operación shutdown() tendrá un valor de boost::asio::error::operation_aborted .
  • La operación de apagado de PartyB se completará con éxito.

En resumen, las operaciones de apagado SSL de Boost.Asio son un poco complicadas. Las inconsistencias entre el iniciador y los códigos de error de los pares remotos durante los apagados adecuados pueden hacer que el manejo sea un poco incómodo. Como regla general, siempre que la categoría del código de error no sea una categoría SSL, el protocolo se cerró de forma segura.