c++ Boost asio error: no shared cipher

2020-07-28 02:49发布

问题:

I am currently setting a RESTful API with boost asio.

Connecting from a client works fine via HTTP. But if I try to connect via HTTPS I get an error on the server side: "no shared cipher". The error seems to come from the openssl implementation, but I have no idea what to make of it. My first guess would be that that no cypher algorithm is set, but I cannot see how this can be done in asio.

This is what I put in the code and where the error occurs:

auto acceptHandler = boost::bind(&self::onAccept, this, connection,
                                 boost::asio::placeholders::error);
connection->async_accept(m_acceptor, acceptHandler);

m_sslContext.set_options(
        context::default_workarounds | context::no_sslv2 | context::single_dh_use);
m_sslContext.use_certificate_file(filename, context::pem);
m_sslContext.use_private_key_file(filename, context::pem);

Anyone ever had this before or got it working?

回答1:

I had the same problem and solved it this way.You have to generate a private key and a certificate file for your server. Here is the procedure to do so :

// Generate a new ssl private key :
$openssl genrsa -out privkey.pem 1024

// Create a certificate signing request using your new key
$openssl req -new -key privkey.pem -out certreq.csr

// Self-sign your CSR with your own private key:
$openssl x509 -req -days 3650 -in certreq.csr -signkey privkey.pem -out newcert.pem

// Install the signed certificate and private key for use by an ssl server
// This allows you to use a single file for certificate and private key
$( openssl x509 -in newcert.pem; cat privkey.pem ) > server.pem

// If you use a dh temp file :
$openssl dhparam -outform PEM -out dh512.pem 512

Then, copy the server.pem and dh512.pem files in your server execution directory.

If you use the tmp dh file you also have to add this line of code m_sslContext.use_tmp_dh_file("dh512.pem");



回答2:

I got same error when feed unconfigured boost::asio::ssl::context object to boost::asio::ssl::stream object constructor, which then used as resulted socket of accepting:

server()
    : m_acceptor(/*args*/)
    , m_context(boost::asio::ssl::context::tlsv1_server)
    , m_stream(m_io_service, m_context)
{
    // `m_context` configuring, BUT `m_stream` is unaffected
    m_acceptor.async_accept(m_stream.lowest_layer(), accept_result_handler);
}
// run somewhere `m_io_service.run()`, or other processor of `async` operations.

And after actual accept connection and process handshake on it the handshake handler receive boost::system::error_code with value 336109761 and message no shared cipher.

So first create and configure boost::asio::ssl::context and then construct boost::asio::ssl::stream with it:

typedef boost::asio::ip::tcp::socket socket_type;
typedef boost::asio::ssl::stream<socket_type> stream_type;

std::shared_ptr<stream_type> m_stream;

server()
    : m_acceptor(/*args*/)
    , m_context(boost::asio::ssl::context::tlsv1_server)
{
    // `m_context` configuring
    m_stream = std::make_shared<stream_type>(m_io_service, m_context);
    m_acceptor.async_accept(m_stream->lowest_layer(), accept_result_handler);
}

And don't forget create new stream for each new connection, context can be the same.



回答3:

As @asiocity points out, the essential part is to fully configure the ssl::context before initialising the ssl::stream.

It is hard to initialise them both in an initialiser list while still deferring the ssl::stream until the ssl::context is fully configured.

One way I use is to subclass the ssl::context and override the constructor so that it can be fully configured in the initializer list, before the ssl::stream is initialised.

e.g.

class server_context : public boost::asio::ssl::context {
  public: server_context(boost::asio::ssl::context::method m, std::string certfile);
};

...

server_context::server_context(boost::asio::ssl::context::method m, std::string certfile)
: boost::asio::ssl::context(m)
{
  use_private_key_file(certfile);
  use_certificate_chain_file(certfile);
}

...

class myclass {
  private:
    server_context : ssl_context_;
    boost::asio::ssl::stream<boost::asio::ip::tcp::socket&> stream_;
  public:
    myclass(boost::asio::ip::tcp::socket&, std::string certfile);

...

myclass::myclass(std::string certfile) 
: ssl_context_(boost::asio::ssl::context::sslv23, certfile),
  stream_(socket, ssl_context_) 
{
  ...
}

However that seems a lot of work to avoid a shared_ptr

I'm wishing/hoping for some constructor-mixin/template trick that would allow me to do this with just a custom initializer list rather than a subclass.