Reputation: 1407
I have a Boost Beast asio based websocket server, which (in abbreviated form) starts like this
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));
and then, I have
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)
{
// Using Boost's asio paradigm, websocket binds its completion handlers to a shared ptr of this object.
// This allows the object to extend its lifetime beyond this scope until it's operations are done.
// Once the all async operations are done, no reference remains, finally ending the lifetime of this object.
const auto ws(std::make_shared<websocket>(std::move(socket), ssl_context_, auth_));
// Initiate websocket handshake
ws->handshake();
// Continue with accepting the next available connection
acceptor_.async_accept(io_context_, boost::beast::bind_front_handler(&server::accept_handler, this));
}
else
{
LOG_ERROR("Acceptor error: " << error.message());
}
}
This works great for validating client certificates on initial connection. The problem is that this websocket server expects clients to be connected for a long time, possibly longer than the validity of the client certificate. In other words, I need to periodically re-check the client certificate after the initial connection is made. What is the best way to do that?
UPDATE 1: post @sehe comment
I agree my question seems related to https://stackoverflow.com/a/77327517/4071435. I switched from ssl_context_.set_verify_mode()
to SSL_CTX_set_verify()
because the former does not support SSL_VERIFY_POST_HANDSHAKE
. Then, in my downstream websocket async write handler (just a convenient place), I added SSL_verify_client_post_handshake(websocket_.next_layer().native_handle());
but that always returns 0, which is a failure. So, I seem to be missing something.
My test client is a python3-websockets
based application
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)
What else am I missing?
UPDATE 2: Better, but still missing something.
The problem with update1 above stemmed from the fact that SSL_verify_client_post_handshake()
is a TLSv1.3 feature. The reason the code seems to not work is because openssl s_client and pythons-websocket both connect with TLSv1.2. When I add -tls1_3
to openssl s_client to force TLSv1.3, boost beast websocket rejects the connection with unsupported protocol
.
This led me to find (an obvious in hindsight) problem. I had initialized my ssl_context with boost::asio::ssl::context::tlsv12
. I changed it to boost::asio::ssl::context::tlsv13
and both clients were able to connect with TLSv1.3!
This also changed the behavior of SSL_verify_client_post_handshake()
. The first invocation now returns 1 (YAY!!!), but the next invocation, about 1 second later returns 0. Seems like I'm very close now, but still missing something ...
UPDATE 3: It works!
Actually, update 2 was working. The second call to SSL_verify_client_post_handshake()
returning 0 is just an indication that the previous request wasn't yet completed.
Upvotes: 0
Views: 86
Reputation: 1
To verify the client certificate more than once on a connection, you can implement a custom verification callback that checks the certificate at each handshake. Make sure to handle the state of the connection properly to avoid issues.
Upvotes: -3