https连接的asio服务器上的ssl错误

时间:2020-09-23 13:14:38

标签: c++ server openssl boost-asio handshake

我从gitlab(https://gitlab.com/eidheim/Simple-Web-Server)上的Eidheim那里获得了SimpleWebServer,我想使该https服务器也可以处理http和特殊的普通套接字查询。它们都可以正常工作,但是在进行压力测试时,https有很多错误,在更改之前我没有看到。顺便说一句:它始终是相同的查询,并且作为单个查询,它可以正常工作。编辑:服务器使用自签名证书。

每秒大约有3个https查询(不多),偶尔会出现以下错误:

  • sslv3警报证书未知
  • 崩溃:SSL_accept(openssl / ssl.h)-> libssl-1_1.dll-> libcrypto-1_1.dll
  • C:\ curl \ src \ curl.exe -k https://192.168.0.228:3012/status curl:(35)OpenSSL SSL_connect:SSL_ERROR_SYSCALL连接到192.168.0.228:3012

我正在使用-k在批处理循环中使用curl测试不安全的连接:

:START
C:\curl\src\curl.exe -k https://192.168.0.228:3012/status
ping 127.0.0.1 -n 2 > nul
goto START

我更改了服务器的握手和接受状态。我正好读取了流的一个字节: ->确定是否是来自套接字查询的信号字节 ->否则,如果第一个字节不等于0x16(TLS握手从0x16开始,没有它只是一个HTTP查询) ->否则应该是https查询

仅对于https,我会读取其余的握手数据,并继续执行async_handshake。 最初,async_handshake自己读取握手数据,而不是由我提供sslbuffer,我担心这会导致不稳定。

编辑:要查看困扰我的ssl握手,请跳到// -----------------------我的第三次更改

auto connection = create_connection(*io_service, context);

acceptor->async_accept(connection->socket->lowest_layer(), [this, connection](const error_code &ec) {
auto lock = connection->handler_runner->continue_lock();
if(!lock)
    return;

if(ec != error::operation_aborted)
    this->accept();

auto session = std::make_shared<Session>(config.max_request_streambuf_size, connection);

if(!ec) {
    asio::ip::tcp::no_delay option(true);
    error_code ec;
    session->connection->socket->lowest_layer().set_option(option, ec);
//-----------------------MY FIRST CHANGE - read first byte
    //read some bytes, needed before the handshake
    const unsigned int bytesToRead = 1;
    int size_of_the_data = 100;
    std::vector<unsigned char> _raw_buffer(size_of_the_data);
    asio::mutable_buffers_1 sslBuffer(asio::buffer(_raw_buffer, size_of_the_data));

    asio::async_read(session->connection->socket->next_layer(), boost::asio::buffer(sslBuffer, bytesToRead), asio::transfer_exactly(bytesToRead), [this, sslBuffer, bytesToRead, session](const error_code& ecRead, std::size_t /*bytes_transferred*/){

    //Get the read data from the buffer in a readable form
    unsigned char * firstByte = asio::buffer_cast<unsigned char*>(sslBuffer);

//-----------------------MY SECOND CHANGE - use first byte to determine if https, http or socket
    if (SocketQuery::CheckForSocketQuery(firstByte[0])) {
        session->connection->cancel_timeout();
        auto lock = session->connection->handler_runner->continue_lock();
        if (!lock)
        return;
        session->isHttps = false;
        this->read_socket(session, firstByte[0]);
    }
    else if (!SocketQuery::is_client_TLS_handshake(firstByte))
    {
        session->connection->cancel_timeout();
        auto lock = session->connection->handler_runner->continue_lock();
        if (!lock)
        return;
        session->isHttps = false;
        this->read_http(session, firstByte[0]);
    }
//-----------------------MY THIRD CHANGE - read the handshake manually and pass it to async_handshake
    else
    {
        try
        {
            //read handshake, 4000 Bytes should be way more than any handshake needs (which is something between 200 and 400 bytes usually)
            session->connection->socket->next_layer().async_read_some(boost::asio::buffer(sslBuffer + bytesToRead, 4000), [this, sslBuffer, bytesToRead, session](const error_code& ecReadHandshake, std::size_t bytesOfHandshake) {
                try
                {
                    bytesOfHandshake += bytesToRead;
                    session->connection->set_timeout(config.timeout_request);

                    //Use overload of async_handshake with buffer as second parameter
                    //Note that the async callback lambda is expected to take the buffer and buffer size as you see below
                    session->connection->socket->async_handshake(asio::ssl::stream_base::server, asio::buffer(sslBuffer, bytesOfHandshake), [this, sslBuffer, session](const error_code& ecHttps, std::size_t bufferSize) {
                        try
                        {
                            session->connection->cancel_timeout();
                            auto lock = session->connection->handler_runner->continue_lock();
                            if (!lock)
                                return;

                            if (!ecHttps)
                            {
                                this->read(session);
                            }
                            else if (this->on_error)
                            {
                                this->on_error(session->request, ecHttps);
                                wxLogMessage("server error: " + wxString(ecHttps.message()));
                            }
                            else
                            {
                                wxLogMessage("server handshake error: " + wxString(ecHttps.message()));
                            }
                        }
                        catch (const std::exception& e)
                        {
                            wxLogMessage("cought server handshake o3 error: " + wxString(e.what()));
                        }
                    });
                }
                catch (const std::exception& e)
                {
                    wxLogMessage("cought server handshake o2 error: " + wxString(e.what()));
                }
            });
        }
        catch (const std::exception& e)
        {
            wxLogMessage("cought server handshake o1 error: " + wxString(e.what()));
        }
    }
    });
}
else if(this->on_error)
    this->on_error(session->request, ec);
      
});

我试图通过try / catches for https阻止服务器崩溃,但是崩溃发生在ssl accept的更深层,到目前为止,我无法停止服务器崩溃。 我之前读过,有可能async_read尝试从套接字读取所有实际数据之前。也许我认为尝试从套接字读取4000字节进行握手是安全的,这是错误的。知道如何改进吗?

编辑:我刚刚发现:如果证书相当长,则握手数据集可以大于4000字节。因此,另一个问题是:如何从https标头数据中划分握手数据?还有双换行符吗?

1 个答案:

答案 0 :(得分:0)

好吧,我真的不能确定这是否是最好的解决方案,但它当然是更好的解决方案。我找不到所有问题的答案,因此,如果还有其他人仍想解释一些错误,我全耳听着。

我找不到ssl和标头之间的分隔符,因此我尝试了适用于标头和正文分隔的同一分隔符。 对于ssl数据,我现在使用asio :: async_read_until并读取直到双端行“ \ r \ n \ r \ n”。 更改之后,https的崩溃急剧下降。

auto connection = create_connection(*io_service, context);

acceptor->async_accept(connection->socket->lowest_layer(), [this, connection](const error_code &ec) {
auto lock = connection->handler_runner->continue_lock();
if(!lock)
    return;

if(ec != error::operation_aborted)
    this->accept();

auto session = std::make_shared<Session>(config.max_request_streambuf_size, connection);

if(!ec) {
    asio::ip::tcp::no_delay option(true);
    error_code ec;
    session->connection->socket->lowest_layer().set_option(option, ec);

    //read some bytes, needed before the handshake
    const unsigned int bytesToRead = 1;
    int size_of_the_data = 100;

    std::shared_ptr<asio::streambuf> streambuf = std::make_shared<asio::streambuf>();

    asio::async_read(session->connection->socket->next_layer(), *streambuf, asio::transfer_exactly(bytesToRead), [this, streambuf, bytesToRead, session](const error_code& ecRead, std::size_t bytes_transferred) mutable {

    //Get the read data from the buffer in a readable form
    const std::vector<unsigned char> vectorBuffer = { asio::buffers_begin(streambuf->data()), asio::buffers_end(streambuf->data()) };


    if (SocketQuery::CheckForSocketQuery(vectorBuffer.at(0))) {
        session->connection->cancel_timeout();
        auto lock = session->connection->handler_runner->continue_lock();
        if (!lock)
        return;
        session->isHttps = false;
        this->read_socket(session, vectorBuffer.at(0));
    }
    else if (!SocketQuery::is_client_TLS_handshake(vectorBuffer.at(0)))
    {
        session->connection->cancel_timeout();
        auto lock = session->connection->handler_runner->continue_lock();
        if (!lock)
        return;
        session->isHttps = false;
        this->read_http(session, vectorBuffer.at(0));
    }
    else
    {
        try
        {
            auto readUntil = "\r\n\r\n";
            asio::async_read_until(*session->connection->socket, *streambuf, readUntil, [this, streambuf, bytesToRead, session](const error_code&ecReadHandshake, std::size_t bytesOfHandshake) mutable {
                try
                {
                const std::vector<unsigned char> vectorBuf = { asio::buffers_begin(streambuf->data()), asio::buffers_end(streambuf->data()) };

                bytesOfHandshake += bytesToRead;
                session->connection->set_timeout(config.timeout_request);

                //Use overload of async_handshake with buffer as second parameter
                //Note that the async callback lambda is expected to take the buffer and buffer size as you see below
                session->connection->socket->async_handshake(asio::ssl::stream_base::server, asio::buffer(vectorBuf, bytesOfHandshake), [this,session](const error_code& ecHttps, std::size_t bufferSize) mutable {
                try
                {
                    session->connection->cancel_timeout();
                    auto lock = session->connection->handler_runner->continue_lock();
                    if (!lock)
                        return;

                    if (!ecHttps)
                    {
                        this->read(session);
                    }
                    else if (this->on_error)
                    {
                        this->on_error(session->request, ecHttps);
                        wxLogMessage("server error: " + wxString(ecHttps.message()));
                    }
                    else
                    {
                        wxLogMessage("server handshake error: " + wxString(ecHttps.message()));
                    }
                }
                catch (const std::exception& e)
                {
                    wxLogMessage("cought server handshake o3 error: " + wxString(e.what()));
                }
                });
            }
            catch (const std::exception& e)
            {
                wxLogMessage("cought server handshake o2 error: " + wxString(e.what()));
            }
        });
        }
        catch (const std::exception& e)
        {
            wxLogMessage("cought server handshake o1 error: " + wxString(e.what()));
        }
    }
    });
}
else if(this->on_error)
    this->on_error(session->request, ec);
      
});

编辑:我现在发现了逐字节的解释,因此有一种更好的方式来读取握手。 (如果再次遇到问题,我可能会更改代码) https://tls.ulfheim.net/