I'm testing a spoofing MITM SSL proxy based on boost::asio and boost::asio::stream::ssl, using boost 1.55

I register the tlsext_servername_callback, so that I can observe the called name during SSL handshake with the client.

In the callback, I have the information needed to either get/create an appropriate SSL_CTX with certificates to use with the client, or start an SSL handshake with the server, to obtain the server certificate for re-signing and then put that back into the SSL_CTX to be passed to the client, before returning the tlsext_servername callback.

It is clear that I must block the tlsext_servername callback from returning until I have the new SSL_CTX ready, which could take some time if I need to wait on a handshake with the real server.

I can block return by performing a blocking handshake with the server, but then I consume one of the io_service threads merely blocking, and I don't want to waste them like that.

I can successfully run it all as a new thread using blocking IO, in this form:

ssl_connect(...) {
  boost::thread([...]() ({
    sni_callback = [...](std::string server_name) {
      server.handshake(...);
      SSL_set_SSL_CTX(client.native_handle(), server.ctx));
    };

    client_socket.handshake(...);
    handler(); // really done by io_service.post
  }).detach();
}

But I want to use stackful coroutines in the belief that thay are less costly and that I will be able to have more connections in progress (though I may have to link with BoringSSL).

Using a coroutine spawn, I can convert the handshake with the server, like this:

ssl_connect(...) {
  boost::asio::spawn(my_strand, [...](boost::asio::yield_context yield) {
    sni_callback = [...](std::string server_name) {
      server.async_handshake(..., yield[ec]); // <---- works nicely
      SSL_set_SSL_CTX(client.native_handle(), server.ctx));
    };

    client_socket.handshake(...);
    handler(); // really done by io_service.post
  });
}

but the handshake with the client must remain blocking, otherwise, with:

    client_socket.async_handshake(..., yield[ec]);

I get segfaults in the coroutine with unhelpful stack traces, mentioning only some SSL cleanup functions. However if the client connection hangs, then the client handshake can still hang and block a worker thread.

My guess is that if the handshake with the client is not blocking, the SNI callback does not occur on the coroutine, and so I cannot properly block the return and possibly the use of yield in the SNI callback is invalid.

If there is a solution that doesn't involve patching either libssl or stream::ssl, it will involve some asio_invoke_handler hooks.

However, for that to work, the coroutine would have to be activated, but not to complete the async_handshake call. Instead it would be activated to handle the handshake read completion, leading to the parsing, SNI callback & return, further handshake write, and yield back to the io_service. I don't know if the coroutine/strand implementation can even do that -- be activated for work and not for return.

So my first question is: is it likely possible to make coroutine do this?

Looking in boost source detail/io.hpp for case engine::want_input_and_retry I notice:

            next_layer_.async_read_some(
                boost::asio::buffer(core_.input_buffer_),
                BOOST_ASIO_MOVE_CAST(io_op)(*this));

which seems to make no attempt to use asio_hanlder_invoke to allow it to be hooked, so that might need fixing.

What sort of asio_handler_invoke hook would cause the handler to be invoked on the coroutine? Could strand.dispatch() do it? If it would, I'm not sure how to write such a hook either to work with argument dependent lookup, or  to invoke  in the coroutine.

Otherwise I may as well stick to starting a scope-limited thread, and using blocking handshake operations.

But I like to know the limits...

Samjam