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

问题描述 投票:0回答:1

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

我做错了什么?

...有时服务器在发出“关闭通知”消息后仍然发送数据,这也很奇怪。有人可以帮忙吗?

这是服务器的一个最小示例。异步 boost/asio 实现也会出现此问题。

#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: 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;
}

这是与客户沟通后的输出:

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: The operation was completed successfully

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() 之后调用验证回调。 ...当然已经太晚了,应该在之前发生。

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

c++ openssl boost-asio handshake tls1.3
1个回答
0
投票

回复

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

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

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

在这种情况下,始终有一个读待处理是“自由”的,这将导致隐式处理任何 TLS 事件。

然后,当您在某个时刻希望关闭 SSL 流时,您可以对其发出

s.shutdown()
,它将从挂起的读取中返回 EOF。

确保在链上运行所有操作,以避免 ssl 流上的数据竞争。

取消

有趣的是,安全断开asio SSL套接字的正确方法是什么?建议读取可以被

cancel()
编辑。然而
ssl::stream<>
没有这个操作。

更有趣的是,自 1.77.0 起,

ssl::stream<>
上的异步操作添加了 取消支持的文档。阅读这些内容,您应该能够使用绑定取消槽,或通过底层套接字取消。

代码

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

但我想快速检查

shutdown
是否确实会导致现有的异步
read
操作完成,所以这是那个小测试器:

//#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();
}


¹(

openssl s_client
一直给我 CA 错误,我知道,我应该有一天升级我的 PKI 对等身份验证)

© www.soinside.com 2019 - 2024. All rights reserved.