TLSv1.3 握手后:服务器仅在连接关闭时验证客户端证书

TLSv1.3 post-handshake: server only verifies the client certificate when the connection is closed

提问人:SBond 提问时间:10/16/2023 最后编辑:SBond 更新时间:10/21/2023 访问量:144

问:

信息 / TL;博士:

问题仍然存在,但解决方法位于本文末尾

问题:

我有一个问题,我不知道该怎么做。我正在使用 Boost 1.83 和 OpenSSL 3.1,我正在尝试创建具有握手后支持的 TLS 服务器。换句话说,对于某些客户端请求,我希望在 TLS 握手后触发客户端身份验证。TLS 消息交换似乎有效,但处理顺序不正确。目前,服务器发送响应,然后验证客户端的真实性,因为 OpenSSL 在关闭 TLS 连接之前不会触发回调。

我做错了什么?

有时服务器在“关闭通知”消息后仍然发送数据,这也很奇怪。谁能帮忙?

有关服务器和匹配客户端的最小示例,请参阅下文。 异步 boost/asio 实现也会出现此问题。

以下是与客户端通信后的输出:

server ready
acceptor ok
handshake ok
read ok; client request message (30 bytes received): hello server, authenticate me!
2nd handshake ok
write ok; server response message (27 bytes transmitted): this is the server response
shutdown the connection...
client certificate valid: /C=DE/ST=RootCA_ST/L=RootCA_L/O=RootCA_O/OU=RootCA_OU/CN=RootCA_CN
client certificate valid: /C=DE/ST=TLS_Client_ST/L=TLS_Client_L/O=TLS_Client_O/OU=TLS_Client_OU/CN=TLS_Client_CN
shutdown: The operation was completed successfully

END

下面是包含其他调试信息的输出:

server ready
acceptor ok
TLS status: before SSL initialization
TLS status: SSLv3/TLS read client hello
TLS status: SSLv3/TLS write server hello
TLS status: SSLv3/TLS write server hello
TLS status: SSLv3/TLS write change cipher spec
TLS status: SSLv3/TLS write change cipher spec
TLS status: TLSv1.3 write encrypted extensions
TLS status: TLSv1.3 write encrypted extensions
TLS status: TLSv1.3 write encrypted extensions
TLS status: SSLv3/TLS write certificate
TLS status: SSLv3/TLS write certificate
TLS status: SSLv3/TLS write certificate
TLS status: TLSv1.3 write server certificate verify
TLS status: TLSv1.3 write server certificate verify
TLS status: TLSv1.3 write server certificate verify
TLS status: SSLv3/TLS write finished
TLS status: SSLv3/TLS write finished
TLS status: SSLv3/TLS write finished
TLS status: TLSv1.3 early data
TLS status: TLSv1.3 early data
TLS status: TLSv1.3 early data
TLS status: SSLv3/TLS read finished
TLS status: SSLv3/TLS write session ticket
TLS status: SSLv3/TLS write session ticket
TLS status: SSLv3/TLS write session ticket
TLS status: SSLv3/TLS write session ticket
TLS status: SSLv3/TLS write session ticket
TLS status: SSLv3/TLS write session ticket
handshake ok
TLS status: SSL negotiation finished successfully
TLS status: SSL negotiation finished successfully
read ok; client request message (30 bytes received): hello server, authenticate me!
TLS status: SSLv3/TLS write certificate request
TLS status: SSLv3/TLS write certificate request
TLS status: SSLv3/TLS write certificate request
2nd handshake ok
TLS status: SSL negotiation finished successfully
TLS status: SSL negotiation finished successfully
write ok; server response message (27 bytes transmitted): this is the server response
shutdown the connection...
TLS status: SSL negotiation finished successfully
TLS status: SSL negotiation finished successfully
TLS status: SSL negotiation finished successfully
TLS status: SSL negotiation finished successfully
TLS status: SSL negotiation finished successfully
TLS status: SSLv3/TLS read client certificate
client certificate valid: /C=DE/ST=RootCA_ST/L=RootCA_L/O=RootCA_O/OU=RootCA_OU/CN=RootCA_CN
client certificate valid: /C=DE/ST=TLS_Client_ST/L=TLS_Client_L/O=TLS_Client_O/OU=TLS_Client_OU/CN=TLS_Client_CN
TLS status: SSLv3/TLS read client certificate
TLS status: SSLv3/TLS read client certificate
TLS status: SSLv3/TLS read certificate verify
TLS status: SSLv3/TLS read certificate verify
TLS status: SSLv3/TLS read certificate verify
TLS status: SSLv3/TLS read finished
TLS status: SSLv3/TLS write session ticket
TLS status: SSLv3/TLS write session ticket
TLS status: SSLv3/TLS write session ticket
TLS status: SSLv3/TLS write session ticket
TLS status: SSLv3/TLS write session ticket
TLS status: SSLv3/TLS write session ticket
shutdown: An existing connection was forcibly closed by the remote host

END

以下是 Wireshark 输出,当到达服务器的某些代码部分(127.0.0.1:客户端;127.0.0.5:服务器)时,带有标记:

No.     Time            Source     Destination   Protocol  Length   Info
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------
      1 0.000662       127.0.0.1   127.0.0.5     TLSv1.3   287      Client Hello

<TCP: acceptor>
<TLS: handshake>

      2 16.029938      127.0.0.5   127.0.0.1     TLSv1.3   1478     Server Hello, Change Cipher Spec, Application Data, Application Data, Application Data, Application Data
      3 16.032142      127.0.0.1   127.0.0.5     TLSv1.3   124      Change Cipher Spec, Application Data
      4 16.032351      127.0.0.5   127.0.0.1     TLSv1.3   554      Application Data, Application Data
      5 16.032378      127.0.0.1   127.0.0.5     TLSv1.3   79       Application Data   --> this is the client request message

<TLS: read>
<TLS: post-handshake authentication of the client --> SSL_verify_client_post_handshake()>
<TLS: post-handshake authentication of the client --> 2nd handshake>

      6 51.594902      127.0.0.5   127.0.0.1     TLSv1.3   143      Application Data
      7 51.598791      127.0.0.1   127.0.0.5     TLSv1.3   1323     Application Data, Application Data, Application Data

<TLS: write>

      8 62.972860      127.0.0.5   127.0.0.1     TLSv1.3   93       Application Data   --> this is the server response message ()

<TLS: shutdown>

      9 68.777818      127.0.0.5   127.0.0.1     TLSv1.3   68       Application Data   --> close_notify of the server
     10 68.779306      127.0.0.1   127.0.0.5     TLSv1.3   68       Application Data   --> close_notify of the client
     11 68.780150      127.0.0.5   127.0.0.1     TLSv1.3   1674     Application Data, Application Data   --> well... I dont know how this happens here.

< --- verifyCertificate is invoked --- >
<end of function>

这很奇怪......在代码中的“握手后身份验证”之后,服务器发送证书请求并从客户端获取该请求,但仅在 shutdown() 之后调用验证回调。...当然为时已晚,它应该在<TLS:写入>之前发生。

有什么想法吗?我还需要以某种方式“指示”/配置OpenSSL吗?

回复 sehe

你好,sehe。感谢您的支持和为我花费的时间。我也想过使用取消,但不幸的是它在我的用例中不起作用。我不再理解我的代码的行为,这让我感到沮丧。 也许我是瞎子,看不到真正的问题。不幸的是,我找不到任何第三方代码或示例来查看其他人是如何解决的。

服务器的行为很奇怪...... 如果在第二次握手后调用socket.write(),则在服务器代码中为'TLS: post-handshake authentication'),则在shutdown()之后验证客户端证书(请参阅上面的服务器跟踪)。

如果在第二次握手后调用 socket.read(),则会立即验证客户端证书,但 read() (或 read_some()) 会阻止,因为客户端不会发送更多内容:

server ready
acceptor ok
TLS status: before SSL initialization
TLS status: SSLv3/TLS read client hello
TLS status: SSLv3/TLS write server hello
TLS status: SSLv3/TLS write server hello
TLS status: SSLv3/TLS write change cipher spec
TLS status: SSLv3/TLS write change cipher spec
TLS status: TLSv1.3 write encrypted extensions
TLS status: TLSv1.3 write encrypted extensions
TLS status: TLSv1.3 write encrypted extensions
TLS status: SSLv3/TLS write certificate
TLS status: SSLv3/TLS write certificate
TLS status: SSLv3/TLS write certificate
TLS status: TLSv1.3 write server certificate verify
TLS status: TLSv1.3 write server certificate verify
TLS status: TLSv1.3 write server certificate verify
TLS status: SSLv3/TLS write finished
TLS status: SSLv3/TLS write finished
TLS status: SSLv3/TLS write finished
TLS status: TLSv1.3 early data
TLS status: TLSv1.3 early data
TLS status: TLSv1.3 early data
TLS status: SSLv3/TLS read finished
TLS status: SSLv3/TLS write session ticket
TLS status: SSLv3/TLS write session ticket
TLS status: SSLv3/TLS write session ticket
TLS status: SSLv3/TLS write session ticket
TLS status: SSLv3/TLS write session ticket
TLS status: SSLv3/TLS write session ticket
handshake ok
TLS status: SSL negotiation finished successfully
TLS status: SSL negotiation finished successfully
read ok; client request message (30 bytes received): hello server, authenticate me!
TLS status: SSLv3/TLS write certificate request
TLS status: SSLv3/TLS write certificate request
TLS status: SSLv3/TLS write certificate request
2nd handshake ok
TLS status: SSL negotiation finished successfully
TLS status: SSL negotiation finished successfully
TLS status: SSLv3/TLS read client certificate
client certificate valid: /C=DE/ST=RootCA_ST/L=RootCA_L/O=RootCA_O/OU=RootCA_OU/CN=RootCA_CN
client certificate valid: /C=DE/ST=TLS_Client_ST/L=TLS_Client_L/O=TLS_Client_O/OU=TLS_Client_OU/CN=TLS_Client_CN
TLS status: SSLv3/TLS read client certificate
TLS status: SSLv3/TLS read client certificate
TLS status: SSLv3/TLS read certificate verify
TLS status: SSLv3/TLS read certificate verify
TLS status: SSLv3/TLS read certificate verify
TLS status: SSLv3/TLS read finished
TLS status: SSLv3/TLS write session ticket
TLS status: SSLv3/TLS write session ticket
TLS status: SSLv3/TLS write session ticket
TLS status: SSLv3/TLS write session ticket
TLS status: SSLv3/TLS write session ticket
TLS status: SSLv3/TLS write session ticket

如果在第二次握手后调用 socket.read(),并且客户端在此之后发送第二条消息,则 read() 将不再阻塞,但证书将根本不会被验证:

server ready
acceptor ok
TLS status: before SSL initialization
TLS status: SSLv3/TLS read client hello
TLS status: SSLv3/TLS write server hello
TLS status: SSLv3/TLS write server hello
TLS status: SSLv3/TLS write change cipher spec
TLS status: SSLv3/TLS write change cipher spec
TLS status: TLSv1.3 write encrypted extensions
TLS status: TLSv1.3 write encrypted extensions
TLS status: TLSv1.3 write encrypted extensions
TLS status: SSLv3/TLS write certificate
TLS status: SSLv3/TLS write certificate
TLS status: SSLv3/TLS write certificate
TLS status: TLSv1.3 write server certificate verify
TLS status: TLSv1.3 write server certificate verify
TLS status: TLSv1.3 write server certificate verify
TLS status: SSLv3/TLS write finished
TLS status: SSLv3/TLS write finished
TLS status: SSLv3/TLS write finished
TLS status: TLSv1.3 early data
TLS status: TLSv1.3 early data
TLS status: TLSv1.3 early data
TLS status: SSLv3/TLS read finished
TLS status: SSLv3/TLS write session ticket
TLS status: SSLv3/TLS write session ticket
TLS status: SSLv3/TLS write session ticket
TLS status: SSLv3/TLS write session ticket
TLS status: SSLv3/TLS write session ticket
TLS status: SSLv3/TLS write session ticket
handshake ok
TLS status: SSL negotiation finished successfully
TLS status: SSL negotiation finished successfully
read ok; client request message (30 bytes received): hello server, authenticate me!
TLS status: SSLv3/TLS write certificate request
TLS status: SSLv3/TLS write certificate request
TLS status: SSLv3/TLS write certificate request
2nd handshake ok
TLS status: SSL negotiation finished successfully
TLS status: SSL negotiation finished successfully
read ok; client request message (30 bytes received): hello server, authenticate me!
TLS status: SSL negotiation finished successfully
TLS status: SSL negotiation finished successfully
write ok; server response message (27 bytes transmitted): this is the server response
shutdown the connection...
TLS status: SSL negotiation finished successfully
TLS status: SSL negotiation finished successfully
TLS status: SSL negotiation finished successfully
TLS status: SSL negotiation finished successfully
TLS status: SSL negotiation finished successfully
shutdown: application data after close notify (SSL routines)

END

....使用 socket.async_read_some() 时,服务器也不会阻止,但客户端证书也不会被验证:

server ready
acceptor ok
TLS status: before SSL initialization
TLS status: SSLv3/TLS read client hello
TLS status: SSLv3/TLS write server hello
TLS status: SSLv3/TLS write server hello
TLS status: SSLv3/TLS write change cipher spec
TLS status: SSLv3/TLS write change cipher spec
TLS status: TLSv1.3 write encrypted extensions
TLS status: TLSv1.3 write encrypted extensions
TLS status: TLSv1.3 write encrypted extensions
TLS status: SSLv3/TLS write certificate
TLS status: SSLv3/TLS write certificate
TLS status: SSLv3/TLS write certificate
TLS status: TLSv1.3 write server certificate verify
TLS status: TLSv1.3 write server certificate verify
TLS status: TLSv1.3 write server certificate verify
TLS status: SSLv3/TLS write finished
TLS status: SSLv3/TLS write finished
TLS status: SSLv3/TLS write finished
TLS status: TLSv1.3 early data
TLS status: TLSv1.3 early data
TLS status: TLSv1.3 early data
TLS status: SSLv3/TLS read finished
TLS status: SSLv3/TLS write session ticket
TLS status: SSLv3/TLS write session ticket
TLS status: SSLv3/TLS write session ticket
TLS status: SSLv3/TLS write session ticket
TLS status: SSLv3/TLS write session ticket
TLS status: SSLv3/TLS write session ticket
handshake ok
TLS status: SSL negotiation finished successfully
TLS status: SSL negotiation finished successfully
read ok; client request message (30 bytes received): hello server, authenticate me!
TLS status: SSLv3/TLS write certificate request
TLS status: SSLv3/TLS write certificate request
TLS status: SSLv3/TLS write certificate request
2nd handshake ok
TLS status: SSL negotiation finished successfully
TLS status: SSL negotiation finished successfully
write ok; server response message (27 bytes transmitted): this is the server response
shutdown the connection...
TLS status: SSL negotiation finished successfully
TLS status: SSL negotiation finished successfully
TLS status: SSL negotiation finished successfully
TLS status: SSL negotiation finished successfully
shutdown: decryption failed or bad record mac (SSL routines)

以下是完整的示例代码和测试证书以及密钥

TLSv1.3 服务器

#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>
#include <boost/bind.hpp>
#include <string>
#include <vector>
#include <iostream>

class TlsServer
{
public:
    TlsServer(boost::asio::io_service& ioService, uint16_t port)
        :
        m_ioService(ioService),
        m_acceptor(ioService, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), port)),
        m_context(boost::asio::ssl::context::tlsv13_server)
    {
        // set certificates
        m_context.load_verify_file("rootCA_cert.pem");
        m_context.use_certificate_chain_file("server_cert.pem");
        m_context.use_private_key_file("server_key.pem", boost::asio::ssl::context::pem);

        // certificate callback
        SSL_CTX_set_verify(m_context.native_handle(), SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT | SSL_VERIFY_POST_HANDSHAKE, nullptr);
        m_context.set_verify_callback(boost::bind(&TlsServer::verifyCertificate, this, _1, _2));

        std::cout << "server ready" << std::endl;
        wholeProcedure();
    }

    void wholeProcedure()
    {
        boost::asio::ssl::stream<boost::asio::ip::tcp::socket> socket(m_ioService, m_context);
        boost::system::error_code ec;

        // additional informations (optinal)
        SSL_set_msg_callback(socket.native_handle(),
            [](int write_p, int version, int content_type, const void* buf, size_t len, SSL* ssl, void* arg)
            {std::cout << "TLS status: \t" << SSL_state_string_long(ssl) << std::endl; });

        // TCP: acceptor
        m_acceptor.accept(socket.lowest_layer(), ec);
        if (ec)
        {
            std::cout << "acceptor error: " << ec.message() << std::endl;
            return;
        }
        else
        {
            std::cout << "acceptor ok" << std::endl;
        }

        // TLS: handshake
        socket.handshake(boost::asio::ssl::stream_base::server, ec);
        if (ec)
        {
            std::cout << "handshake error: " << ec.message() << std::endl;
            return;
        }
        else
        {
            std::cout << "handshake ok" << std::endl;
        }

        // TLS: read (receive client request)
        std::vector<char> buffer(2048);
        std::size_t bytesReceived = socket.read_some(boost::asio::buffer(buffer, buffer.size()), ec);
        if (ec)
        {
            std::cout << "read error: " << ec.message() << std::endl;
            return;
        }
        else
        {
            std::string requestStr(buffer.begin(), buffer.begin() + bytesReceived);
            std::cout << "read ok; client request message (" << bytesReceived << " bytes received): " << requestStr << std::endl;
        }

        // TLS: post-handshake authentication of the client
        if (SSL_verify_client_post_handshake(socket.native_handle()) == 0)
        {
            std::cout << "post-handshake error: " << ec.message() << std::endl;
            return;
        }
        socket.handshake(boost::asio::ssl::stream_base::server, ec);
        if (ec)
        {
            std::cout << "2nd handshake error: " << ec.message() << std::endl;
            return;
        }
        else
        {
            std::cout << "2nd handshake ok" << std::endl;
        }

        // TLS: 2nd read (receive client request)
        bytesReceived = socket.read_some(boost::asio::buffer(buffer, buffer.size()), ec);
        if (ec)
        {
            std::cout << "read error: " << ec.message() << std::endl;
            return;
        }
        else
        {
            std::string requestStr(buffer.begin(), buffer.begin() + bytesReceived);
            std::cout << "read ok; client request message (" << bytesReceived << " bytes received): " << requestStr << std::endl;
        }

        // TLS: write (send server response)
        std::string responseStr = "this is the server response";
        std::vector<unsigned char> responseMsg(responseStr.begin(), responseStr.end());
        std::size_t bytesTransmitted = boost::asio::write(socket, boost::asio::buffer(responseMsg, responseMsg.size()), ec);
        if (ec)
        {
            std::cout << "write error: " << ec.message() << std::endl;
            return;
        }
        else
        {
            std::cout << "write ok; server response message (" << bytesTransmitted << " bytes transmitted): " << responseStr << std::endl;
        }

        // TLS: shutdown
        std::cout << "shutdown the connection..." << std::endl;
        socket.shutdown(ec);
        std::cout << "shutdown: " << ec.message() << std::endl;
    }

    bool verifyCertificate(bool preverified, boost::asio::ssl::verify_context& ctx)
    {
        // get subject name of the certificate
        char subject_name[256];
        X509* cert = X509_STORE_CTX_get_current_cert(ctx.native_handle());
        X509_NAME_oneline(X509_get_subject_name(cert), subject_name, 256);

        if (preverified == false)
        {
            std::cout << "client certificate invalid: " << subject_name << std::endl;
            return false;
        }
        else
        {
            std::cout << "client certificate valid: " << subject_name << std::endl;
            return true;
        }
    }

private:
    boost::asio::io_service& m_ioService;
    boost::asio::ip::tcp::acceptor m_acceptor;
    boost::asio::ssl::context m_context;
};

int main()
{
    try
    {
        uint16_t port = 2405;
        boost::asio::io_service ioService;
        TlsServer tlsServer(ioService, port);
        ioService.run();
    }
    catch (std::exception& e)
    {
        std::cerr << "Exception: " << e.what() << "\n";
    }

    std::cout << "\nEND" << std::endl;
    std::cin.get();
    return 0;
}

TLSv1.3 客户端

#include <iostream>
#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>
#include <boost/bind.hpp>

class TlsClient
{
public:
    TlsClient(boost::asio::io_service& io_service, boost::asio::ssl::context& context, const boost::asio::ip::tcp::resolver::results_type& endpoint)
        :
        m_buffer(2048),
        m_socket(io_service, context)
    {
        m_socket.set_verify_mode(boost::asio::ssl::verify_peer);
        m_socket.set_verify_callback(boost::bind(&TlsClient::verifyCertificate, this, _1, _2));

        SSL_set_post_handshake_auth(m_socket.native_handle(), true); // enable post-handshake authentication
        TlsClient::connect(endpoint);
    }

    void connect(const boost::asio::ip::tcp::resolver::results_type& endpoint)
    {
        boost::asio::async_connect(m_socket.lowest_layer(), endpoint,
            [this](const boost::system::error_code& error, const boost::asio::ip::tcp::endpoint& endpoint)
            {
            if (error)
            {
            std::cout << "connect() failed: " << error.message() << std::endl;
            return;
            }
            handshake();
            }
        );
    }

    void handshake()
    {
        m_socket.async_handshake(boost::asio::ssl::stream_base::client,
            [this](const boost::system::error_code& error)
            {
            if (error)
            {
            std::cout << "handshake() failed: " << error.message() << std::endl;
            return;
            }
            sendRequest();
            }
        );
    }

    void sendRequest()
    {
        char request[] = "hello server, authenticate me!";
        boost::asio::async_write(m_socket, boost::asio::buffer(request, std::strlen(request)),
            [this](const boost::system::error_code& error, std::size_t bytesTransmitted)
            {
            if (error)
            {
            std::cout << "sendRequest() failed: " << error.message() << std::endl;
            return;
            }
            std::cout << "sendRequest() successful (" << bytesTransmitted << " bytes transmitted)" << std::endl;
            receiveResponse();
            }
        );
    }

    void receiveResponse()
    {
        m_socket.async_read_some(boost::asio::buffer(m_buffer, m_buffer.size()),
            [this](const boost::system::error_code& error, std::size_t bytesReceived)
            {
            if (!error)
            {
            std::cout << "receiveResponse() successful (" << bytesReceived << " bytes received)" << std::endl;
            std::cout << "server response message: ";
            std::cout.write(m_buffer.data(), bytesReceived);
            std::cout << std::endl;
            receiveResponse();
            }
            else if ((error) && (error == boost::asio::error::eof)) // 'end of file' is fine
            {
            std::cout << "receiveResponse(): close_notify received" << std::endl;
            shutdown();
            }
            else
            {
            std::cout << "receiveResponse() failed: " << error.message() << std::endl;
            shutdown();
            }
            }
        );
    }

    void shutdown()
    {
        m_socket.async_shutdown(
            [this](const boost::system::error_code& error)
            {
            if (error)
            {
            std::cout << "shutdown() failed: " << error.message() << std::endl;
            }
            }
        );
    }

    bool verifyCertificate(bool preverified, boost::asio::ssl::verify_context& ctx)
    {
        // get subject name of the certificate
        char subject_name[256];
        X509* cert = X509_STORE_CTX_get_current_cert(ctx.native_handle());
        X509_NAME_oneline(X509_get_subject_name(cert), subject_name, 256);
        if (preverified == false)
        {
            std::cout << "certificate invalid: " << subject_name << std::endl;
            return false;
        }
        else
        {
            std::cout << "certificate valid: " << subject_name << std::endl;
            return true;
        }
    }

private:
    boost::asio::ssl::stream<boost::asio::ip::tcp::socket> m_socket;
    std::vector<char> m_buffer;
};

int main()
{
    try
    {
        boost::asio::io_context io_context;
        boost::asio::ip::tcp::resolver resolver(io_context);
        auto endpoint = resolver.resolve("127.0.0.5", "2405");

        boost::asio::ssl::context ctx(boost::asio::ssl::context::tlsv13_client);
        ctx.load_verify_file("rootCA_cert.pem");
        ctx.use_certificate_chain_file("client_cert.pem");
        ctx.use_private_key_file("client_key.pem", boost::asio::ssl::context::pem);

        TlsClient client(io_context, ctx, endpoint);
        io_context.run();
    }
    catch (std::exception& e)
    {
        std::cerr << "Exception: " << e.what() << "\n";
    }

    std::cout << "\nEND" << std::endl;
    std::cin.get();
    return 0;
}

证书和密钥(Base64/PEM 格式)

client_cert.pem
================================================================
-----BEGIN CERTIFICATE-----
MIICKzCCAd2gAwIBAgIIaFncvW99ybMwBQYDK2VwMG8xCzAJBgNVBAYTAkRFMRIw
EAYDVQQIDAlSb290Q0FfU1QxETAPBgNVBAcMCFJvb3RDQV9MMREwDwYDVQQKDAhS
b290Q0FfTzESMBAGA1UECwwJUm9vdENBX09VMRIwEAYDVQQDDAlSb290Q0FfQ04w
IBcNMjMwMTAxMDAwMDAwWhgPMjA5OTEyMzEyMzU5NTlaMIGDMQswCQYDVQQGEwJE
RTEWMBQGA1UECAwNVExTX0NsaWVudF9TVDEVMBMGA1UEBwwMVExTX0NsaWVudF9M
MRUwEwYDVQQKDAxUTFNfQ2xpZW50X08xFjAUBgNVBAsMDVRMU19DbGllbnRfT1Ux
FjAUBgNVBAMMDVRMU19DbGllbnRfQ04wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNC
AAQE/ZKwGG1daBXWmH9bS8Y3cEOHBkkjJVs+BAv+9lFfB99Doc/UdS1ERcIS7B0I
KpZCs1PB3XYIz2jcXaZ/0GSqo1EwTzAMBgNVHRMBAf8EAjAAMB0GA1UdDgQWBBRP
hb4ZxX4LXb13WkFlfC7G3wc9TzALBgNVHQ8EBAMCA7gwEwYDVR0lBAwwCgYIKwYB
BQUHAwIwBQYDK2VwA0EA6awYvVghrPtPsDcAVzKSuZKojxiuoVXFTE+nIcB1uCBp
5ZGJ8Uj5qWrxe55Xq+M4ffOZZUsb4dBKGxUdTy1BAQ==
-----END CERTIFICATE-----

client_key.pem
================================================================
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEINcbzvL7XAAT2iZVFzjsOqWd7TvDW9jMF/w6E/2+cXkboAoGCCqGSM49
AwEHoUQDQgAEBP2SsBhtXWgV1ph/W0vGN3BDhwZJIyVbPgQL/vZRXwffQ6HP1HUt
REXCEuwdCCqWQrNTwd12CM9o3F2mf9Bkqg==
-----END EC PRIVATE KEY-----

server_cert.pem
================================================================
-----BEGIN CERTIFICATE-----
MIICRTCCAfegAwIBAgIIe2Nr+QCIaNgwBQYDK2VwMG8xCzAJBgNVBAYTAkRFMRIw
EAYDVQQIDAlSb290Q0FfU1QxETAPBgNVBAcMCFJvb3RDQV9MMREwDwYDVQQKDAhS
b290Q0FfTzESMBAGA1UECwwJUm9vdENBX09VMRIwEAYDVQQDDAlSb290Q0FfQ04w
IBcNMjMwMTAxMDAwMDAwWhgPMjA5OTEyMzEyMzU5NTlaMIGDMQswCQYDVQQGEwJE
RTEWMBQGA1UECAwNVExTX1NlcnZlcl9TVDEVMBMGA1UEBwwMVExTX1NlcnZlcl9M
MRUwEwYDVQQKDAxUTFNfU2VydmVyX08xFjAUBgNVBAsMDVRMU19TZXJ2ZXJfT1Ux
FjAUBgNVBAMMDVRMU19TZXJ2ZXJfQ04wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNC
AAQqNsRBYBNkqPYV2TZHNQx5jLF+2gUwaeTYVqGfxJILqwQpIO3hmcEncPtvm9wI
gL8TnV32r53M+d4Dh4GnL9puo2swaTAMBgNVHRMBAf8EAjAAMB0GA1UdDgQWBBR/
ETSLqThTfoMF4mJosj8yjkFQyDALBgNVHQ8EBAMCA+gwEwYDVR0lBAwwCgYIKwYB
BQUHAwEwGAYDVR0RBBEwD4INVExTX1NlcnZlcl9DTjAFBgMrZXADQQD/6dOs1YHU
q3BnvtI9WI4vPVsuLlhblWR3z6mea3ZXUufvkPsyr2NdNFBMX+GHHxkLvOFNIhX+
dsp7k2j2wLoK
-----END CERTIFICATE-----

server_key.pem
================================================================
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIMfzK4phpnO22uVPYxCg7EjnNFub9OH/ds61chjxX0CAoAoGCCqGSM49
AwEHoUQDQgAEKjbEQWATZKj2Fdk2RzUMeYyxftoFMGnk2Fahn8SSC6sEKSDt4ZnB
J3D7b5vcCIC/E51d9q+dzPneA4eBpy/abg==
-----END EC PRIVATE KEY-----

rootCA_cert.pem
================================================================
-----BEGIN CERTIFICATE-----
MIIB1TCCAYegAwIBAgIIdq9JvkPbtBAwBQYDK2VwMG8xCzAJBgNVBAYTAkRFMRIw
EAYDVQQIDAlSb290Q0FfU1QxETAPBgNVBAcMCFJvb3RDQV9MMREwDwYDVQQKDAhS
b290Q0FfTzESMBAGA1UECwwJUm9vdENBX09VMRIwEAYDVQQDDAlSb290Q0FfQ04w
IBcNMjMwMTAxMDAwMDAwWhgPMjA5OTEyMzEyMzU5NTlaMG8xCzAJBgNVBAYTAkRF
MRIwEAYDVQQIDAlSb290Q0FfU1QxETAPBgNVBAcMCFJvb3RDQV9MMREwDwYDVQQK
DAhSb290Q0FfTzESMBAGA1UECwwJUm9vdENBX09VMRIwEAYDVQQDDAlSb290Q0Ff
Q04wKjAFBgMrZXADIQA56tHp15pu+Y+/oQi5czzu2+uWEDZNZKMkyZMVgfKCjKM/
MD0wDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUqECiKkuXkVhe3HXeSlOoAFb1
iUowCwYDVR0PBAQDAgEGMAUGAytlcANBAMdLUzNMj4YPiNNbU+tCHRUyU3z7HgvD
LxRJvjGsgDx/wt/ATK5FgM1Oyo8H5PtUle42AFNYf/mABtH7QPGFmQI=
-----END CERTIFICATE-----

解决方法

我想要什么:

以下是对我而言重要的内容的简要描述:

我有一个 TLSv1.3 服务器和一些 TLSv1.3 客户端,其中一些需要身份验证,有些则不需要。在处理客户端请求后决定需求。例如,当客户端请求某些资源或数据时,就是这种情况。

其基本思想是通过握手后身份验证(PHA)来解决这个问题。但是,它目前正在引起问题。

解决方法:

使用 OpenSSL/Boost,可以让服务器在 TLS 握手开始时请求客户端证书。即使客户端不发送证书,也可以将服务器配置为继续握手:

context.set_verify_mode(boost::asio::ssl::verify_peer); // boost variant
SSL_CTX_set_verify(context, SSL_VERIFY_PEER); // OpenSSL variant

如果客户端执行此操作,则会立即对其进行身份验证。

如果我在握手后处理客户端请求消息并且需要身份验证,那么我可以检查是否已完成此操作:

    if (SSL_get_peer_certificate(m_socket.native_handle()) != NULL)
    {
        if (SSL_get_verify_result(m_socket.native_handle()) == X509_V_OK) // warning: also returns 'X509_V_OK' if no cert present 
        {
            // ok: certificate received and valid
        }
        else
        {
            // fail: certificate received, but invalid
        }
    }
    else
    {
        // fail: no certificate received
    }

就我而言,这个解决方案已经足够好了,也许它也会帮助其他人。

亲切的问候,SBond

PS:......谢谢Sehe的帮助和时间:)

C++ openSSL boost-ASIO 握手 TLS1.3

评论

0赞 sehe 10/17/2023
我没有使用过握手后的对等验证。您是否有指向现有工作 openssl 代码(不一定是 C++ 或 Asio)的链接来显示它应该如何工作?也许包含一些密钥/证书材料可能有助于重现。
0赞 SBond 10/17/2023
经过 2 天的搜索,我解决了这个问题。它既简单又愚蠢。...我在 Wireshark 中看到 TLS 消息(客户端证书)被发送到服务器,但当然我必须在服务器上执行 read() 操作来处理它们。...由于我只在服务器端发送数据,所以我没有检查是否还有来自客户端的传入数据。由于 OpenSSL 在关机时在内部执行 read(),因此这些消息是在 TLS 连接关闭后处理的。这也解释了奇数数据包顺序。
0赞 sehe 10/17/2023
请将其作为答案发布,以便人们可以找到它。它也可能为我(也可能是其他人)节省了相当多的时间,
0赞 SBond 10/17/2023
是的。...不幸的是,我仍在为这个问题而苦苦挣扎。当我调用 boost 函数“socket.read_some()”时,它会处理我已经收到的 TLS 握手消息并启动客户端证书验证。但是,此函数会阻塞,直到客户端发送更多数据。但这不会再发生了,因为客户端只是在等待服务器的响应。我目前正在徒劳地寻找一个OpenSSL API,我可以使用它来触发已经收到的TLS握手消息的处理,而不会阻止TLS过程的其余部分。我还没有解决方案

答:

1赞 sehe 10/18/2023 #1

注意

对问题中添加的信息做出的新回答

回复

当然,我必须在服务器上执行 read() 操作来处理它们 [...] 由于 OpenSSL 在关机时在内部执行 read(),因此这些消息是在 TLS 连接关闭后处理的

当我调用 boost 函数“socket.read_some()”时,它会处理我已经收到的 TLS 握手消息并启动客户端证书验证。但是,此函数会阻塞,直到客户端发送更多数据

我认为合理的标准选项是使用异步 IO 操作。

在这种情况下,始终具有待处理读取是“免费”的,这将导致隐式处理任何 TLS 事件。

然后,当您希望关闭 SSL 流时,您可以对它发出问题,它将从挂起的读取中返回 EOF。s.shutdown()

请务必在链上运行所有操作,以避免 ssl 流上的数据争用。

取消

有趣的是,安全断开 asio SSL 套接字的正确方法是什么? 建议读取可以是 -ed。但是没有该操作。cancel()ssl::stream<>

更有趣的是,从 1.77.0 开始,异步操作已经添加了其取消支持的文档。阅读这些内容后,您应该能够使用绑定的取消槽,或通过底层套接字取消。ssl::stream<>

法典

我没有真正将客户端证书配置为使用您的代码进行实际测试¹

但是我想快速检查一下这确实会导致现有的异步操作完成,所以这里是那个小测试器:shutdownread

//#define BOOST_ASIO_ENABLE_HANDLER_TRACKING 1
#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>
#include <iostream>

namespace asio = boost::asio;
namespace ssl  = asio::ssl;
using asio::ip::tcp;
using namespace std::literals;

asio::awaitable<void> demo_ssl_client(uint16_t port) {
    std::cout << "coro start" << std::endl;

    constexpr auto token = asio::deferred;
    try {
        using S              = ssl::stream<tcp::socket>;
        using C              = ssl::context;

        C ctx(C::tlsv13_client);
        ctx.set_default_verify_paths();

        auto ex = co_await asio::this_coro::executor;
        S    s(ex, ctx);
        co_await s.lowest_layer().async_connect({{}, port}, token);
        std::cout << "connected " << s.lowest_layer().remote_endpoint() << std::endl;
        co_await s.async_handshake(S::client, token);
        co_await async_write(s, asio::buffer("hello world\n"sv), token);

        char dummy[1]{};
        s.async_read_some(asio::buffer(dummy, 1), [](auto ec, size_t xfer) {
            std::cout << "async_read_some: " << ec.message() << ", " << xfer << std::endl;
        });

        std::cout << "happily idling" << std::endl;
        co_await asio::steady_timer{ex, 3s}.async_wait(token);

        std::cout << "time for shutdown" << std::endl;
        co_await async_write(s, asio::buffer("bye world\n"sv), token);

        {
            boost::system::error_code ec;
            s.lowest_layer().cancel(ec); // cancel async_read_some
        }

        auto [ec] = co_await s.async_shutdown(as_tuple(token));
        std::cout << "shutdown completed (" << ec.message() << ")" << std::endl;
    } catch (boost::system::system_error const& se) {
        std::cout << "error: " << se.code().message() << std::endl;
    }

    co_await post(token);
    std::cout << "coro exit" << std::endl;
}

int main() {
    asio::io_context ioc;

    co_spawn(ioc, demo_ssl_client(7878), asio::detached);

    ioc.run();
}

enter image description here


¹ (openssl s_client一直给我 CA 错误,我知道,我应该在这些日子里启动我的 PKI 对等体身份验证)

1赞 sehe 10/20/2023 #2

对于编辑过的问题,我能够使用添加的信息进行测试,为使问题如此完整而感到自豪。

我还能够通过简单地重新排列第二次读取直到响应写入之后来规避症状,这样无论如何都可以进行验证。

请注意,第二次握手似乎是可选的:客户端 ert 也会在没有它的情况下进行验证:

enter image description here

我得出了以下简化的源集,其中我修改了客户端以发送多个请求:

  • 文件server.cpp

     #include <boost/asio.hpp>
     #include <boost/asio/ssl.hpp>
     #include <iomanip>
     #include <iostream>
    
     namespace asio = boost::asio;
     namespace ssl  = asio::ssl;
     using asio::ip::tcp;
     using boost::system::error_code;
     using namespace std::placeholders;
     using namespace std::literals;
    
     class TlsServer {
       public:
         TlsServer(asio::any_io_executor ex, uint16_t port)
             : m_acceptor(ex, tcp::endpoint({}, port))
             , m_context(ssl::context::tlsv13_server) //
         {
             // set certificates
             m_context.load_verify_file("rootCA_cert.pem");
             m_context.use_certificate_chain_file("server_cert.pem");
             m_context.use_private_key_file("server_key.pem", ssl::context::pem);
    
             // certificate callback
             SSL_CTX_set_verify(m_context.native_handle(),
                                SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT | SSL_VERIFY_POST_HANDSHAKE,
                                nullptr);
             m_context.set_verify_callback(bind(&TlsServer::verifyCertificate, this, _1, _2));
    
             std::cout << "server ready" << std::endl;
             wholeProcedure();
         }
    
         void wholeProcedure() {
             ssl::stream<tcp::socket> socket(m_acceptor.get_executor(), m_context);
             error_code               ec;
    
             // additional informations (optinal)
             SSL_set_msg_callback(socket.native_handle(),
                                  [](int /*write_p*/, int /*version*/, int /*content_type*/, void const* /*buf*/,
                                     size_t /*len*/, SSL* ssl, void* /*arg*/) {
                                      std::cout << "TLS status: " << SSL_state_string_long(ssl) << std::endl;
                                  });
    
             // TCP: acceptor
             m_acceptor.accept(socket.lowest_layer(), ec);
             std::cout << "acceptor: " << ec.message() << std::endl;
    
             // TLS: handshake
             socket.handshake(ssl::stream_base::server, ec);
             std::cout << "handshake: " << ec.message() << std::endl;
    
             // TLS: read (receive client request)
    
             auto receive = [&socket, i = 0]() mutable {
                 i += 1;
                 error_code  ec;
                 std::string req(2048, '\0');
                 size_t      n = socket.read_some(asio::buffer(req), ec);
                 std::cout << "read #" << i << ": " << ec.message() << std::endl;
                 if (!ec) {
                     req.resize(n);
                     std::cout << "read ok; client request #" << i << ": " << quoted(req) << std::endl;
                 }
             };
    
             receive();
    
             // TLS: post-handshake authentication of the client
             std::cout << "--- initiating post-handshake peer auth" << std::endl;
             bool ok = SSL_verify_client_post_handshake(socket.native_handle());
             std::cout << "post-handshake: " << (ok ? "OK" : "Failed") << std::endl;
    
             //socket.handshake(ssl::stream_base::server, ec);
             //std::cout << "2nd handshake: " << ec.message() << std::endl;
    
             // TLS: write (send server response)
             /*size_t n =*/asio::write(socket, asio::buffer("this is the server response"sv), ec);
             std::cout << "write: " << ec.message() << std::endl;
    
             // TLS: 2nd read (receive client request)
             receive();
    
             // TLS: shutdown
             std::cout << "shutdown the connection..." << std::endl;
             socket.shutdown(ec);
             std::cout << "shutdown: " << ec.message() << std::endl;
         }
    
         bool verifyCertificate(bool preverified, ssl::verify_context& ctx) {
             // get subject name of the certificate
             char  subject_name[256];
             X509* cert = X509_STORE_CTX_get_current_cert(ctx.native_handle());
             X509_NAME_oneline(X509_get_subject_name(cert), subject_name, 256);
    
             std::cout << "client certificate " << (preverified ? "valid" : "invalid") << ": " << subject_name
                       << std::endl;
             return preverified;
         }
    
       private:
         tcp::acceptor m_acceptor;
         ssl::context  m_context;
     };
    
     int main() {
         try {
             asio::io_context ioc;
             TlsServer        tlsServer(ioc.get_executor(), 2405);
             ioc.run();
         } catch (std::exception const& e) {
             std::cerr << "Exception: " << e.what() << "\n";
         }
    
         std::cout << "\nEND" << std::endl;
     }
    
  • 文件client.cpp

     #include <boost/asio.hpp>
     #include <boost/asio/ssl.hpp>
     #include <deque>
     #include <iomanip>
     #include <iostream>
    
     namespace asio = boost::asio;
     namespace ssl  = asio::ssl;
     using asio::ip::tcp;
     using boost::system::error_code;
     using namespace std::placeholders;
    
     class TlsClient {
       public:
         TlsClient(asio::any_io_executor ex, ssl::context& context, tcp::resolver::results_type endpoint)
             : m_socket(ex, context) {
             m_socket.set_verify_mode(ssl::verify_peer);
             m_socket.set_verify_callback(bind(&TlsClient::verifyCertificate, this, _1, _2));
    
             SSL_set_post_handshake_auth(m_socket.native_handle(), true); // enable post-handshake authentication
             connect(endpoint);
         }
    
         void connect(tcp::resolver::results_type endpoint) {
             asio::async_connect( //
                 m_socket.lowest_layer(), endpoint, [this](error_code ec, tcp::endpoint) {
                     if (ec) {
                         std::cout << "connect() failed: " << ec.message() << std::endl;
                         return;
                     }
                     handshake();
                 });
         }
    
         void handshake() {
             m_socket.async_handshake( //
                 ssl::stream_base::client, [this](error_code ec) {
                     if (ec) {
                         std::cout << "handshake() failed: " << ec.message() << std::endl;
                         return;
                     }
                     sendRequest();
                 });
         }
    
         void sendRequest() {
             if (m_requests.empty()) {
                 return shutdown();
             }
    
             asio::async_write( //
                 m_socket, asio::buffer(m_requests.front()), [this](error_code ec, size_t n) {
                     if (ec) {
                         std::cout << "sendRequest() failed: " << ec.message() << std::endl;
                         return;
                     }
                     std::cout << "sendRequest() successful (" << n << " bytes transmitted)" << std::endl;
                     m_requests.pop_front();
                     receiveResponse();
                 });
             std::cout << "sendRequest() initiated for " << std::quoted(m_requests.front()) << std::endl;
         }
    
         void receiveResponse() {
             m_buffer.resize(2048);
             m_socket.async_read_some(asio::buffer(m_buffer), [this](error_code ec, size_t n) {
                 std::cout << "receiveResponse() " << ec.message() << " (" << n << " bytes received)" << std::endl;
                 if (!ec) {
                     m_buffer.resize(n);
                     std::cout << "server response message: " << std::quoted(m_buffer) << std::endl;
                     return sendRequest();
                 } else if (ec == asio::error::eof) { // 'end of file' is fine
                     std::cout << "receiveResponse(): close_notify received" << std::endl;
                     shutdown();
                 }
             });
         }
    
         void shutdown() {
             m_socket.async_shutdown([/*this*/](error_code ec) {
                 if (ec) {
                     std::cout << "shutdown() failed: " << ec.message() << std::endl;
                 }
             });
         }
    
         bool verifyCertificate(bool preverified, ssl::verify_context& ctx) {
             char  subject_name[256];
             X509* cert = X509_STORE_CTX_get_current_cert(ctx.native_handle());
             X509_NAME_oneline(X509_get_subject_name(cert), subject_name, 256);
    
             std::cout << "certificate " << (preverified ? "valid" : "invalid") << ": " << subject_name
                       << std::endl;
             return preverified;
         }
    
       private:
         std::deque<std::string_view> m_requests{"authentication request", "application query"};
         ssl::stream<tcp::socket>     m_socket;
         std::string                  m_buffer;
     };
    
     int main() {
         try {
             asio::io_context ioc;
             auto             endpoint = tcp::resolver(ioc).resolve("127.0.0.5", "2405");
    
             ssl::context ctx(ssl::context::tlsv13_client);
             ctx.load_verify_file("rootCA_cert.pem");
             ctx.use_certificate_chain_file("client_cert.pem");
             ctx.use_private_key_file("client_key.pem", ssl::context::pem);
    
             TlsClient client(ioc.get_executor(), ctx, endpoint);
             ioc.run();
         } catch (std::exception const& e) {
             std::cerr << "Exception: " << e.what() << "\n";
         }
    
         std::cout << "\nEND" << std::endl;
     }
    

它的工作方式正如我所希望的那样:

enter image description here

旁注

当然,如果您以异步方式编写服务器,那么启动写入/读取的顺序并不重要,因为它们可能会重叠(尽管它们之间可能不会重叠读取或写入)。我没有看到你的异步服务器代码,所以我无法进一步评论你为什么声称“异步升压/asio 实现也会出现问题”。

评论

0赞 SBond 10/20/2023
谢谢你的努力,sehe。我真的很感激。您的解决方案已经改善了这种情况:)但问题的核心仍然存在,因为在这里,服务器首先发送响应,然后对客户端进行身份验证。所以它应该是相反的。我为自己找到了一个解决方法,我将在我的第一篇文章中写到。它不能解决有关握手后身份验证的问题,但我可以走得更远。
0赞 SBond 10/20/2023
关于“异步升压/ASIO”的小评论:我最初在(async_handshake、async_read、...它在那里不起作用,我希望它能采用同步/成功的方法。..但没有:)
0赞 sehe 10/20/2023
是的,我对这些问题很感兴趣。出于好奇,我自己在这里做了一个异步服务器,它似乎工作正常,包括在身份验证之前处理多个带有响应的请求,甚至处理重复的身份验证请求:stackoverflow.com/a/77332104/85371
0赞 sehe 10/21/2023
更好的尝试,实际上确保了顺序,但代价是扩展了应用程序协议 stackoverflow.com/a/77333090/85371
1赞 sehe 10/21/2023 #3

经过长时间的思考,我得出的结论是,确保验证先于身份验证响应的唯一方法是在协议中编写另一条消息。

  • 我选择了简单的“验证请求”/“验证响应”往返,
  • 仅仅在最低级别的插座上是不够的async_wait(read|write)
  • 也没有发出空写入 (send(""))

我使用了一个事件来异步等待验证响应。如果您不想在完成验证步骤之前允许发送任何其他邮件,这可能过于复杂。在这种情况下,只需在收到无效的验证响应时关闭连接即可。

  • 文件server.cpp

     #include <boost/asio.hpp>
     #include <boost/asio/ssl.hpp>
     #include <deque>
     #include <iomanip>
     #include <iostream>
    
     namespace asio = boost::asio;
     namespace ssl  = asio::ssl;
     using asio::ip::tcp;
     using boost::system::error_code;
     using Socket = ssl::stream<tcp::socket>;
    
     static bool verifyCertificate(bool preverified, ssl::verify_context& ctx) {
         // get subject name of the certificate
         char  subject_name[256];
         X509* cert = X509_STORE_CTX_get_current_cert(ctx.native_handle());
         X509_NAME_oneline(X509_get_subject_name(cert), subject_name, 256);
    
         std::cout << "client certificate " << (preverified ? "valid" : "invalid") << ": " << subject_name
                   << std::endl;
         return preverified;
     }
    
     static void tlsDebug(int, int, int, void const*, size_t, SSL* ssl, void*) {
         static_cast<void>(ssl);
         // std::cout << "TLS status: " << SSL_state_string_long(ssl) << std::endl;
     }
    
     struct Session : std::enable_shared_from_this<Session> {
         Session(Socket socket) : socket(std::move(socket)) {}
    
         void Run() {
             // TLS: handshake
             auto self = shared_from_this();
             socket.async_handshake(ssl::stream_base::server, [this, self](error_code ec) {
                 std::cout << "handshake: " << ec.message() << std::endl;
                 reader();
             });
         }
    
       private:
         using time_point = std::chrono::steady_clock::time_point;
         Socket                  socket;
         asio::steady_timer      verification_response{socket.get_executor(), time_point::max()};
         size_t                  request_counter = 0;
         std::deque<std::string> outbox;
         std::string             req;
    
         void writer() {
             if (outbox.empty()) {
                 std::cout << "writer idle (queue: " << outbox.size() << ")" << std::endl;
                 return;
             }
    
             auto self = shared_from_this();
             // TLS: write
             asio::async_write(                //
                 socket,                       //
                 asio::buffer(outbox.front()), //
                 [this, self](error_code ec, size_t /*n*/) {
                     std::cout << "wrote: '" << outbox.front() << "' (" << ec.message() << ")" << std::endl;
                     if (!ec) {
                         outbox.pop_front();
                         writer();
                     }
                 });
         }
    
         void shutdown() {
             // TLS: shutdown
             std::cout << "shutdown the connection..." << std::endl;
             auto self = shared_from_this();
             socket.async_shutdown(
                 [self](error_code ec) { std::cout << "shutdown: " << ec.message() << std::endl; });
         }
    
         void send(std::string msg) {
             outbox.push_back(std::move(msg));
             if (outbox.size() == 1) {
                 std::cout << "writer start (queue: " << outbox.size() << ")" << std::endl;
                 writer();
             }
         }
    
         bool is_verified() const { return verification_response.expiry() <= std::chrono::steady_clock::now(); }
    
         void reader() {
             request_counter += 1;
             // TLS: read (receive client request)
             req.resize(2048);
             auto self = shared_from_this();
             socket.async_read_some(asio::buffer(req), [this, self](error_code ec, size_t n) {
                 std::cout << "read #" << request_counter << ": " << ec.message() << std::endl;
    
                 if (ec) {
                     std::cout << "reader completed" << std::endl;
                     return shutdown();
                 }
    
                 req.resize(n);
                 std::cout << "client request #" << request_counter << ": '" << req << "'" << std::endl;
    
                 if (req == "authentication request") {
                     if (is_verified()) {
                         send("ALREADY AUTHENTICATED");
                         return reader();
                     }
    
                     std::cout << "--- initiating post-handshake peer auth" << std::endl;
                     bool ok = SSL_verify_client_post_handshake(socket.native_handle());
                     std::cout << "post-handshake: " << (ok ? "OK" : "Failed") << std::endl;
    
                     send("VERIFICATION REQUEST");
                     reader(); // await VERIFICATION RESPONSE
    
                     verification_response.async_wait([this, self](error_code ec) {
                         if (ec == asio::error::operation_aborted) {
                             socket.async_handshake( //
                                 ssl::stream_base::server, [this, self](error_code ec) {
                                     std::cout << "2nd handshake: " << ec.message() << std::endl;
                                     if (!ec) {
                                         verification_response.expires_at(std::chrono::steady_clock::now());
                                         send("AUTHENTICATED");
                                         reader();
                                     }
                                 });
                         } else {
                             std::cerr << "Verification failed" << std::endl;
                             shutdown();
                         }
                     });
                 } else if (req == "VERIFICATION RESPONSE") {
                     verification_response.cancel_one(); // can only signal pending auth
                 } else {
                     if (is_verified())
                         send("Authenticated response to '" + req + "'");
                     else
                         send("Generic response to '" + req + "'");
                     reader();
                 }
             });
         }
     };
    
     struct TlsServer {
         TlsServer(asio::any_io_executor ex, uint16_t port) : acc(ex, {{}, port}) {}
    
         void Start() {
             // set certificates
             ctx.load_verify_file("rootCA_cert.pem");
             ctx.use_certificate_chain_file("server_cert.pem");
             ctx.use_private_key_file("server_key.pem", ssl::context::pem);
    
             // certificate callback
             SSL_CTX_set_verify(ctx.native_handle(),
                                SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT | SSL_VERIFY_POST_HANDSHAKE,
                                nullptr);
             ctx.set_verify_callback(verifyCertificate);
    
             std::cout << "server ready" << std::endl;
             accept_loop();
         }
    
       private:
         tcp::acceptor            acc;
         ssl::context             ctx{ssl::context::tlsv13_server};
         ssl::stream<tcp::socket> s{acc.get_executor(), ctx};
    
         void accept_loop() {
             s = {acc.get_executor(), ctx};
             SSL_set_msg_callback(s.native_handle(), tlsDebug);
    
             acc.async_accept(s.lowest_layer(), [this](error_code ec) {
                 std::cout << "acceptor: " << ec.message() << std::endl;
    
                 if (!ec) {
                     std::make_shared<Session>(std::move(s))->Run();
                     accept_loop();
                 }
             });
         }
     };
    
     int main() {
         try {
             asio::io_context ioc;
             TlsServer        s(ioc.get_executor(), 2405);
             s.Start();
             ioc.run();
         } catch (std::exception const& e) {
             std::cerr << "Exception: " << e.what() << "\n";
         }
    
         std::cout << "\nEND" << std::endl;
     }
    
  • 文件client.cpp

     #include <boost/asio.hpp>
     #include <boost/asio/ssl.hpp>
     #include <deque>
     #include <iomanip>
     #include <iostream>
    
     namespace asio = boost::asio;
     namespace ssl  = asio::ssl;
     using asio::ip::tcp;
     using boost::system::error_code;
    
     class TlsClient {
       public:
         TlsClient(asio::any_io_executor ex, ssl::context& context, tcp::resolver::results_type endpoint)
             : m_socket(ex, context) {
             m_socket.set_verify_mode(ssl::verify_peer);
             m_socket.set_verify_callback(verifyCertificate);
    
             SSL_set_post_handshake_auth(m_socket.native_handle(), true); // enable post-handshake authentication
             connect(endpoint);
         }
    
         void connect(tcp::resolver::results_type endpoint) {
             asio::async_connect( //
                 m_socket.lowest_layer(), endpoint, [this](error_code ec, tcp::endpoint) {
                     if (ec) {
                         std::cout << "connect() failed: " << ec.message() << std::endl;
                         return;
                     }
                     handshake();
                 });
         }
    
         void handshake() {
             m_socket.async_handshake( //
                 ssl::stream_base::client, [this](error_code ec) {
                     if (ec) {
                         std::cout << "handshake() failed: " << ec.message() << std::endl;
                         return;
                     }
                     sendRequest();
                 });
         }
    
         void send(std::string msg, bool immediate = false) {
             if (immediate) {
                 m_queue.push_front(std::move(msg));
                 if (m_queue.size() == 1)
                     sendRequest();
             } else {
                 m_queue.push_back(std::move(msg));
                 if (m_queue.size() == 1)
                 delayRequest();
             }
         }
    
         void sendRequest() {
             if (m_queue.empty()) {
                 std::cout << "sendRequest() completed" << std::endl;
                 return shutdown();
             }
    
             asio::async_write( //
                 m_socket, asio::buffer(m_queue.front()), [this](error_code ec, size_t n) {
                     if (ec) {
                         std::cout << "sendRequest() failed: " << ec.message() << std::endl;
                         return;
                     }
                     std::cout << "sendRequest() successful (" << n << " bytes transmitted)" << std::endl;
                     m_queue.pop_front();
                     receiveResponse();
                 });
             std::cout << "sendRequest() initiated for '" << m_queue.front() << "'" << std::endl;
         }
    
         void receiveResponse() {
             m_buffer.resize(2048);
             m_socket.async_read_some(asio::buffer(m_buffer), [this](error_code ec, size_t n) {
                 std::cout << "receiveResponse() " << ec.message() << " (" << n << " bytes received)" << std::endl;
                 if (!ec) {
                     m_buffer.resize(n);
                     std::cout << "server message: '" << m_buffer << "'" << std::endl;
                     if (m_buffer == "VERIFICATION REQUEST") {
                         std::cout << "allowing 2nd hanshake" << std::endl;
                         send("VERIFICATION RESPONSE", true);
                     }
                     return delayRequest();
                 } else if (ec == asio::error::eof) { // 'end of file' is fine
                     std::cout << "receiveResponse(): close_notify received" << std::endl;
                     shutdown();
                 }
             });
         }
    
         void delayRequest() {
             m_timer.expires_from_now((rand() % 3 + 1) * std::chrono::milliseconds(500));
             m_timer.async_wait([this](error_code) { sendRequest(); });
         }
    
         void shutdown() {
             m_socket.async_shutdown([/*this*/](error_code ec) {
                 if (ec) {
                     std::cout << "shutdown() failed: " << ec.message() << std::endl;
                 }
             });
         }
    
         static bool verifyCertificate(bool preverified, ssl::verify_context& ctx) {
             char  subject_name[256];
             X509* cert = X509_STORE_CTX_get_current_cert(ctx.native_handle());
             X509_NAME_oneline(X509_get_subject_name(cert), subject_name, 256);
    
             std::cout << "certificate " << (preverified ? "valid" : "invalid") << ": " << subject_name
                       << std::endl;
             return preverified;
         }
    
       private:
         std::deque<std::string> m_queue{
             "non-auth request", "ping heartbeat",         "authentication request",
             "first query",      "authentication request", "second query",
         };
         ssl::stream<tcp::socket> m_socket;
         asio::steady_timer       m_timer{m_socket.get_executor()};
         std::string              m_buffer;
     };
    
     int main() {
         try {
             asio::io_context ioc;
             auto             endpoint = tcp::resolver(ioc).resolve("127.0.0.5", "2405");
    
             ssl::context ctx(ssl::context::tlsv13_client);
             ctx.load_verify_file("rootCA_cert.pem");
             ctx.use_certificate_chain_file("client_cert.pem");
             ctx.use_private_key_file("client_key.pem", ssl::context::pem);
    
             TlsClient client(ioc.get_executor(), ctx, endpoint);
             ioc.run();
         } catch (std::exception const& e) {
             std::cerr << "Exception: " << e.what() << "\n";
         }
    
         std::cout << "\nEND" << std::endl;
     }
    

演示,首先使用正确的客户端证书,然后禁用 rootCA,使其失败:

enter image description here

在纯文本中:

server ready
acceptor: Success
handshake: Success
read #1: Success
client request #1: 'non-auth request'
writer start (queue: 1)
wrote: 'Generic response to 'non-auth request'' (Success)
writer idle (queue: 0)
read #2: Success
client request #2: 'ping heartbeat'
writer start (queue: 1)
wrote: 'Generic response to 'ping heartbeat'' (Success)
writer idle (queue: 0)
read #3: Success
client request #3: 'authentication request'
--- initiating post-handshake peer auth
post-handshake: OK
writer start (queue: 1)
wrote: 'VERIFICATION REQUEST' (Success)
writer idle (queue: 0)
client certificate valid: /C=DE/ST=RootCA_ST/L=RootCA_L/O=RootCA_O/OU=RootCA_OU/CN=RootCA_CN
client certificate valid: /C=DE/ST=TLS_Client_ST/L=TLS_Client_L/O=TLS_Client_O/OU=TLS_Client_OU/CN=TLS_Client_CN
read #4: Success
client request #4: 'VERIFICATION RESPONSE'
2nd handshake: Success
writer start (queue: 1)
wrote: 'AUTHENTICATED' (Success)
writer idle (queue: 0)
read #5: Success
client request #5: 'first query'
writer start (queue: 1)
wrote: 'Authenticated response to 'first query'' (Success)
writer idle (queue: 0)
read #6: Success
client request #6: 'authentication request'
writer start (queue: 1)
wrote: 'ALREADY AUTHENTICATED' (Success)
writer idle (queue: 0)
read #7: Success
client request #7: 'second query'
writer start (queue: 1)
wrote: 'Authenticated response to 'second query'' (Success)
writer idle (queue: 0)
read #8: End of file
reader completed
shutdown the connection...
shutdown: Success

与。

server ready
acceptor: Success
handshake: Success
read #1: Success
client request #1: 'non-auth request'
writer start (queue: 1)
wrote: 'Generic response to 'non-auth request'' (Success)
writer idle (queue: 0)
read #2: Success
client request #2: 'ping heartbeat'
writer start (queue: 1)
wrote: 'Generic response to 'ping heartbeat'' (Success)
writer idle (queue: 0)
read #3: Success
client request #3: 'authentication request'
--- initiating post-handshake peer auth
post-handshake: OK
writer start (queue: 1)
wrote: 'VERIFICATION REQUEST' (Success)
writer idle (queue: 0)
client certificate invalid: /C=DE/ST=RootCA_ST/L=RootCA_L/O=RootCA_O/OU=RootCA_OU/CN=RootCA_CN
read #4: certificate verify failed (SSL routines)
reader completed
shutdown the connection...
shutdown: shutdown while in init (SSL routines)

评论

0赞 sehe 10/21/2023
我删除了其他异步解决方案,因为我可以看到它没有解决排序问题。我已经确信 openssl 不允许你确保排序,除非你添加应用程序协议,我在这里演示了这一点。
0赞 SBond 10/21/2023
有趣的是,一些看似很小的问题是如何变得如此失控:)。你帮了我很多。非常感谢。:)
0赞 sehe 10/21/2023
这正是我花时间的原因。只是为了让我知道它是如何工作的,以后不会遇到这样的故障:)