如何在Boost Beast WebSocket连接后定期重新验证客户端证书?
我有一个基于Boost Beast Asio的WebSocket服务器,其初始化代码(简化版)如下:
ssl_context_.set_options( boost::asio::ssl::context::default_workarounds | boost::asio::ssl::context::no_sslv2 | boost::asio::ssl::context::no_sslv3 | boost::asio::ssl::context::no_tlsv1 | boost::asio::ssl::context::no_tlsv1_1 ); ssl_context_.use_certificate_file( auth->server_cert, boost::asio::ssl::context::file_format::pem ); ssl_context_.use_private_key_file( auth->server_key, boost::asio::ssl::context::file_format::pem ); ssl_context_.load_verify_file(auth->ca); ssl_context_.set_verify_mode( boost::asio::ssl::verify_peer | boost::asio::ssl::verify_fail_if_no_peer_cert ); ssl_context_.set_verify_callback( std::bind( &verify_certificate_cb, std::placeholders::_1, std::placeholders::_2 ) ); try { const boost::asio::ip::tcp::endpoint endpoint(boost::asio::ip::make_address(ip), port); acceptor_.open(endpoint.protocol()); acceptor_.set_option(boost::asio::socket_base::reuse_address(true)); acceptor_.bind(endpoint); acceptor_.listen(); } catch(const boost::system::system_error &e) { LOG_ERROR("Acceptor error: " << e.what()); return; } acceptor_.async_accept(io_context_, boost::beast::bind_front_handler(&server::accept_handler, this));
后续相关代码如下:
static bool verify_certificate_cb(bool preverified, boost::asio::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); LOG_INFO("TLS connection verification for: " << subject_name); return preverified; } void server::accept_handler(const boost::system::error_code& error, boost::asio::ip::tcp::socket socket) noexcept { if (!error) { // 使用Boost Asio的范式,websocket将其完成处理程序绑定到该对象的shared_ptr // 这允许对象的生命周期超出当前作用域,直到其操作完成 // 一旦所有异步操作完成,不再有引用,对象生命周期结束 const auto ws(std::make_shared<websocket>(std::move(socket), ssl_context_, auth_)); // 启动websocket握手 ws->handshake(); // 继续接受下一个可用连接 acceptor_.async_accept(io_context_, boost::beast::bind_front_handler(&server::accept_handler, this)); } else { LOG_ERROR("Acceptor error: " << error.message()); } }
该实现能在初始连接时正常验证客户端证书,但问题在于服务器允许客户端长期连接,连接时长可能超过客户端证书的有效期,因此需要在初始连接建立后定期重新检查客户端证书,请问最佳实现方式是什么?
更新1
参考@sehe的评论后,我将ssl_context_.set_verify_mode()替换为SSL_CTX_set_verify(),因为前者不支持SSL_VERIFY_POST_HANDSHAKE。随后在WebSocket异步写入处理函数中添加了SSL_verify_client_post_handshake(websocket_.next_layer().native_handle());,但该调用始终返回0(失败),我似乎遗漏了某些配置。
我的测试客户端是基于python3-websockets的应用,代码如下:
import websocket, ssl import time my_context = ssl.create_default_context() my_context.load_verify_locations('/usr/share/www/daikin-txrx-ws/public_html/daikin-txrx-ws-ca.crt') my_context.load_cert_chain( '/usr/share/www/daikin-txrx-ws/public_html/daikin-txrx-ws-client.crt', '/usr/share/www/daikin-txrx-ws/public_html/daikin-txrx-ws-client.key') my_context.check_hostname = False my_context.post_handshake_auth = True my_context.verify_mode = ssl.CERT_REQUIRED ws = websocket.WebSocket(sslopt={'context': my_context}) ws.connect('wss://localhost:889'); while True: ws.send("Hello, Server") print(ws.recv()) time.sleep(1)
我还遗漏了什么配置?
更新2
情况有所改善,但仍存在问题。更新1中的问题源于SSL_verify_client_post_handshake()是TLSv1.3特性,而openssl s_client和python-websockets默认使用TLSv1.2连接。当我在openssl s_client中添加-tls1_3强制使用TLSv1.3时,Boost Beast WebSocket拒绝连接并提示“unsupported protocol”。
随后我发现了一个事后看来很明显的问题:我原本使用boost::asio::ssl::context::tlsv12初始化ssl_context,将其改为boost::asio::ssl::context::tlsv13后,两个客户端均能通过TLSv1.3连接!此时SSL_verify_client_post_handshake()的首次调用返回1(成功),但约1秒后的第二次调用返回0,我似乎还差最后一步...
更新3
问题已解决!实际上更新2的实现是有效的,第二次调用SSL_verify_client_post_handshake()返回0仅表示前一次验证请求尚未完成。
内容的提问来源于stack exchange,提问作者Paul Grinberg

