我有一个问题,我不知道该怎么办。我正在使用 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 吗?
回复
当然我必须在服务器上执行 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 对等身份验证)