提问人:SBond 提问时间:10/16/2023 最后编辑:SBond 更新时间:10/21/2023 访问量:144
TLSv1.3 握手后:服务器仅在连接关闭时验证客户端证书
TLSv1.3 post-handshake: server only verifies the client certificate when the connection is closed
问:
信息 / 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的帮助和时间:)
答:
注意
对问题中添加的信息做出的新回答。
回复
当然,我必须在服务器上执行 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<>
法典
我没有真正将客户端证书配置为使用您的代码进行实际测试¹
但是我想快速检查一下这确实会导致现有的异步操作完成,所以这里是那个小测试器: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 对等体身份验证)
对于编辑过的问题,我能够使用添加的信息进行测试,为使问题如此完整而感到自豪。
我还能够通过简单地重新排列第二次读取直到响应写入之后来规避症状,这样无论如何都可以进行验证。
请注意,第二次握手似乎是可选的:客户端 ert 也会在没有它的情况下进行验证:
我得出了以下简化的源集,其中我修改了客户端以发送多个请求:
文件
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; }
它的工作方式正如我所希望的那样:
旁注
当然,如果您以异步方式编写服务器,那么启动写入/读取的顺序并不重要,因为它们可能会重叠(尽管它们之间可能不会重叠读取或写入)。我没有看到你的异步服务器代码,所以我无法进一步评论你为什么声称“异步升压/asio 实现也会出现问题”。
评论
经过长时间的思考,我得出的结论是,确保验证先于身份验证响应的唯一方法是在协议中编写另一条消息。
- 我选择了简单的“验证请求”/“验证响应”往返,
- 仅仅在最低级别的插座上是不够的
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,使其失败:
在纯文本中:
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)
评论