admin管理员组文章数量:1313389
I'm porting a Java client library to a C++ static library. These libraries provide an SDK to communicate with a server that is written in Java using Netty
.
I am using boost 1.87 with C++23.
#pragma once
#include "Config.hpp"
#include "util/concurrent/ThreadPool.hpp"
#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>
#include <filesystem>
#include <memory>
#include <string>
using namespace boost::asio;
namespace fs = std::filesystem;
namespace sdk {
class HttpClient {
public:
explicit HttpClient(std::shared_ptr<ThreadPool> threadPool)
: m_threadPool(std::move(threadPool)),
m_ioContext(m_threadPool->getIoContext()),
m_sslContext(ssl::context::tlsv12_client),
m_sslSocket(m_ioContext, m_sslContext),
m_host(Config::getData()->host),
m_port(Config::getData()->port)
{
auto configData = Config::getData();
// so dumb. why can't I just use the context directly?
// i have to use the native handle to load the certificate and key
// because boost::asio is not sending the client certificate
SSL *sslHandle = m_sslSocket.native_handle();
if (!sslHandle) {
throw std::runtime_error("Failed to retrieve SSL handle!");
}
if (SSL_use_certificate_file(sslHandle, configData->keystore.path.c_str(), SSL_FILETYPE_PEM) <= 0) {
// m_sslContext.use_certificate_chain_file(Config::getData()->keystore.path); // <== does not work
throw std::runtime_error("Failed to load client certificate from: " + configData->keystore.path);
}
else if (SSL_use_PrivateKey_file(sslHandle, configData->key.path.c_str(), SSL_FILETYPE_PEM) <= 0) {
// m_sslContext.use_private_key_file(Config::getData()->key.path, ssl::context::pem); // <== does not work
throw std::runtime_error("Failed to load client private key from: " + configData->key.path);
}
}
void connect()
{
ip::tcp::resolver resolver(m_ioContext);
auto endpoints = resolver.resolve(m_host, std::to_string(m_port));
LOG(info) << "Resolving and connecting to: " << m_host << ":" << m_port;
auto handshake_worker = [this](const boost::system::error_code &ec) {
if (!ec) {
LOG(info) << "SSL handshake successful!";
}
else {
LOG(error) << "SSL handshake failed: " << ec.message();
stop();
}
};
auto connect_worker = [this, handshake_worker](const boost::system::error_code &ec,
const ip::tcp::endpoint &endpoint) {
if (!ec) {
LOG(info) << "Connected to: " << endpoint;
m_sslSocket.async_handshake(ssl::stream_base::client, handshake_worker);
}
else {
LOG(error) << "Connection failed: " << ec.message();
stop();
}
};
async_connect(m_sslSocket.lowest_layer(), endpoints, connect_worker);
}
void stop()
{
if (m_sslSocket.lowest_layer().is_open()) {
boost::system::error_code ec;
m_sslSocket.lowest_layer().shutdown(ip::tcp::socket::shutdown_both, ec);
m_sslSocket.lowest_layer().close();
}
m_threadPool->stop();
}
virtual ~HttpClient() { stop(); }
private:
std::shared_ptr<ThreadPool> m_threadPool;
io_context &m_ioContext;
ssl::context m_sslContext;
ssl::stream<ip::tcp::socket> m_sslSocket;
std::string m_host;
int m_port;
};
} // namespace sdk
For some strange reason calls to the boost::asio::ssl::context
instance to set the certificate and privatekey file do not work. It fails in the handshaking.
The java server logs that no certificate chain has been sent.
javax.ssl|DEBUG|11|nioEventLoopGroup-2-2|2025-01-30 22:52:33.911 UTC|CertificateMessage.java:372|Consuming client Certificate handshake message (
"Certificates": <empty list>
)
javax.ssl|ERROR|11|nioEventLoopGroup-2-2|2025-01-30 22:52:33.913 UTC|TransportContext.java:358|Fatal (BAD_CERTIFICATE): Empty client certificate chain (
"throwable" : {
However, if I grab the native SSL handle from boost::asio::ssl::context
and call the native APIs directly to set the certificate and private key files, everything works. The client certificate is sent and handshaking succeeds.
I can only conclude I'm missing a step or doing something in the wrong order, but I'm unable to tell what. I have also tried the following, with no success
// Configure SSL settings
m_sslContext.set_options(
ssl::context::default_workarounds |
ssl::context::no_sslv2 |
ssl::context::no_sslv3 |
ssl::context::single_dh_use
);
// Enforce mutual TLS authentication
m_sslContext.set_verify_mode(
ssl::verify_peer |
ssl::verify_fail_if_no_peer_cert);
I'm just too anal retentive to leave the work around in place and walk away from this.
I'm porting a Java client library to a C++ static library. These libraries provide an SDK to communicate with a server that is written in Java using Netty
.
I am using boost 1.87 with C++23.
#pragma once
#include "Config.hpp"
#include "util/concurrent/ThreadPool.hpp"
#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>
#include <filesystem>
#include <memory>
#include <string>
using namespace boost::asio;
namespace fs = std::filesystem;
namespace sdk {
class HttpClient {
public:
explicit HttpClient(std::shared_ptr<ThreadPool> threadPool)
: m_threadPool(std::move(threadPool)),
m_ioContext(m_threadPool->getIoContext()),
m_sslContext(ssl::context::tlsv12_client),
m_sslSocket(m_ioContext, m_sslContext),
m_host(Config::getData()->host),
m_port(Config::getData()->port)
{
auto configData = Config::getData();
// so dumb. why can't I just use the context directly?
// i have to use the native handle to load the certificate and key
// because boost::asio is not sending the client certificate
SSL *sslHandle = m_sslSocket.native_handle();
if (!sslHandle) {
throw std::runtime_error("Failed to retrieve SSL handle!");
}
if (SSL_use_certificate_file(sslHandle, configData->keystore.path.c_str(), SSL_FILETYPE_PEM) <= 0) {
// m_sslContext.use_certificate_chain_file(Config::getData()->keystore.path); // <== does not work
throw std::runtime_error("Failed to load client certificate from: " + configData->keystore.path);
}
else if (SSL_use_PrivateKey_file(sslHandle, configData->key.path.c_str(), SSL_FILETYPE_PEM) <= 0) {
// m_sslContext.use_private_key_file(Config::getData()->key.path, ssl::context::pem); // <== does not work
throw std::runtime_error("Failed to load client private key from: " + configData->key.path);
}
}
void connect()
{
ip::tcp::resolver resolver(m_ioContext);
auto endpoints = resolver.resolve(m_host, std::to_string(m_port));
LOG(info) << "Resolving and connecting to: " << m_host << ":" << m_port;
auto handshake_worker = [this](const boost::system::error_code &ec) {
if (!ec) {
LOG(info) << "SSL handshake successful!";
}
else {
LOG(error) << "SSL handshake failed: " << ec.message();
stop();
}
};
auto connect_worker = [this, handshake_worker](const boost::system::error_code &ec,
const ip::tcp::endpoint &endpoint) {
if (!ec) {
LOG(info) << "Connected to: " << endpoint;
m_sslSocket.async_handshake(ssl::stream_base::client, handshake_worker);
}
else {
LOG(error) << "Connection failed: " << ec.message();
stop();
}
};
async_connect(m_sslSocket.lowest_layer(), endpoints, connect_worker);
}
void stop()
{
if (m_sslSocket.lowest_layer().is_open()) {
boost::system::error_code ec;
m_sslSocket.lowest_layer().shutdown(ip::tcp::socket::shutdown_both, ec);
m_sslSocket.lowest_layer().close();
}
m_threadPool->stop();
}
virtual ~HttpClient() { stop(); }
private:
std::shared_ptr<ThreadPool> m_threadPool;
io_context &m_ioContext;
ssl::context m_sslContext;
ssl::stream<ip::tcp::socket> m_sslSocket;
std::string m_host;
int m_port;
};
} // namespace sdk
For some strange reason calls to the boost::asio::ssl::context
instance to set the certificate and privatekey file do not work. It fails in the handshaking.
The java server logs that no certificate chain has been sent.
javax.ssl|DEBUG|11|nioEventLoopGroup-2-2|2025-01-30 22:52:33.911 UTC|CertificateMessage.java:372|Consuming client Certificate handshake message (
"Certificates": <empty list>
)
javax.ssl|ERROR|11|nioEventLoopGroup-2-2|2025-01-30 22:52:33.913 UTC|TransportContext.java:358|Fatal (BAD_CERTIFICATE): Empty client certificate chain (
"throwable" : {
However, if I grab the native SSL handle from boost::asio::ssl::context
and call the native APIs directly to set the certificate and private key files, everything works. The client certificate is sent and handshaking succeeds.
I can only conclude I'm missing a step or doing something in the wrong order, but I'm unable to tell what. I have also tried the following, with no success
// Configure SSL settings
m_sslContext.set_options(
ssl::context::default_workarounds |
ssl::context::no_sslv2 |
ssl::context::no_sslv3 |
ssl::context::single_dh_use
);
// Enforce mutual TLS authentication
m_sslContext.set_verify_mode(
ssl::verify_peer |
ssl::verify_fail_if_no_peer_cert);
I'm just too anal retentive to leave the work around in place and walk away from this.
Share Improve this question edited Jan 31 at 0:10 tdemay asked Jan 30 at 23:59 tdemaytdemay 7489 silver badges27 bronze badges1 Answer
Reset to default 1The problem is you're setting context parameters AFTER constructing the ssl stream object.
The difference between what you're doing with OpenSSL directly is that you're using
SSL_use_certificate_file
vs.SSL_CTX_use_PrivateKey_file
SSL_use_PrivateKey_file
vs.SSL_CTX_use_PrivateKey_file
Here's a self-contained example that fixes the initialization order by using a create_ssl_context()
factory. Note I've stubbed the verification to allow my self-signed test certs.
Live On Coliru
#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>
#include <iostream>
#include <syncstream>
namespace asio = boost::asio;
namespace ssl = asio::ssl;
using asio::ip::tcp;
#define LOG(level) std::osyncstream(std::cout) << "\n[" #level "] " << __FUNCTION__ << ":" << __LINE__ << " "
namespace sdk {
static constexpr uint16_t SERVICE_PORT = 4433;
namespace Config {
struct Keystore {
std::string path;
};
struct Key {
std::string path;
};
struct Data {
std::string host;
int port;
Keystore keystore;
Key key;
};
auto getData() -> std::shared_ptr<Data const> {
static auto data = std::make_shared<Data>(Data{
"localhost",
SERVICE_PORT,
Keystore{"certs/client_cert.pem"},
Key{"certs/client_cert.pem"},
});
return data;
}
} // namespace Config
using boost::system::error_code;
asio::awaitable<void> server() try {
auto ex = co_await asio::this_coro::executor;
tcp::acceptor acc_(ex, {{}, SERVICE_PORT});
ssl::context ctx(ssl::context::tlsv12_server);
ctx.set_password_callback([](std::size_t, ssl::context_base::password_purpose) { return "test"; });
ctx.set_verify_callback([](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);
LOG(info) << "Server Verifying: " << subject_name << " preverified: " << preverified;
return true;
});
ctx.set_verify_mode(ssl::context::verify_peer | ssl::context::verify_fail_if_no_peer_cert);
ctx.use_certificate_file("certs/server_cert.pem", ssl::context::pem);
ctx.use_private_key_file("certs/server_cert.pem", ssl::context::pem);
for (;;) {
auto s = co_await acc_.async_accept();
LOG(info) << "Accepted connection from: " << s.remote_endpoint();
ssl::stream<tcp::socket> ss(std::move(s), ctx);
co_await ss.async_handshake(ssl::stream_base::server);
LOG(info) << "Handshake successful";
break; // let's just accept one connection
};
} catch(boost::system::system_error const& se) {
LOG(error) << "Exception: " << se.code().message();
}
struct ThreadPool {
explicit ThreadPool(size_t threads) : m_impl(threads) {}
virtual ~ThreadPool() { join(); }
void join() { m_impl.join(); }
void stop() { m_impl.stop(); }
using executor_type = asio::thread_pool::executor_type;
executor_type get_executor() { return m_impl.get_executor(); }
private:
asio::thread_pool m_impl;
};
class HttpClient {
public:
explicit HttpClient(asio::any_io_executor ex)
: m_io(ex)
, m_ctx(create_ssl_context())
, m_host(Config::getData()->host)
, m_port(Config::getData()->port) {}
void connect() {
tcp::resolver resolver(m_io);
auto endpoints = resolver.resolve(m_host, std::to_string(m_port));
LOG(info) << "Client resolving and connecting to: " << m_host << ":" << m_port;
auto on_handshake = [this](error_code ec) {
LOG(info) << "Client SSL handshake " << ec.message();
stop();
};
auto on_connect = [this, on_handshake](error_code ec, tcp::endpoint const& endpoint) {
if (!ec) {
LOG(info) << "Client Connected to: " << endpoint;
m_sslSocket.async_handshake(ssl::stream_base::client, on_handshake);
} else {
LOG(error) << "Client Connection failed: " << ec.message();
stop();
}
};
async_connect(m_sslSocket.lowest_layer(), endpoints, on_connect);
}
void stop() {
if (m_sslSocket.lowest_layer().is_open()) {
error_code ec;
m_sslSocket.lowest_layer().shutdown(tcp::socket::shutdown_both, ec);
m_sslSocket.lowest_layer().close();
}
}
virtual ~HttpClient() { stop(); }
private:
asio::any_io_executor m_io;
ssl::context m_ctx;
ssl::stream<tcp::socket> m_sslSocket{m_io, m_ctx};
std::string m_host;
int m_port;
static ssl::context create_ssl_context() {
ssl::context ctx(ssl::context::tlsv12_client);
ctx.set_verify_callback([](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);
LOG(info) << "Client Verifying: " << subject_name << " preverified: " << preverified;
return true;
});
ctx.set_verify_mode(ssl::context::verify_peer | ssl::context::verify_fail_if_no_peer_cert);
auto configData = Config::getData();
ctx.use_private_key_file(configData->key.path, ssl::context::pem);
ctx.use_certificate_file(Config::getData()->keystore.path, ssl::context::pem);
return ctx;
}
};
} // namespace sdk
int main() {
auto threadPool = std::make_shared<sdk::ThreadPool>(16);
auto ex = threadPool->get_executor();
auto client = std::make_unique<sdk::HttpClient>(ex);
// start server
co_spawn(ex, sdk::server, asio::detached);
// connect the client
client->connect();
threadPool->join();
LOG(info) << "Done!\n";
}
Printing e.g.
[info] connect:104 Client resolving and connecting to: localhost:4433
[info] operator():113 Client Connected to: 127.0.0.1:4433
[info] server:65 Accepted connection from: 127.0.0.1:51810
[info] operator():148 Client Verifying: /CN=root preverified: 0
[info] operator():148 Client Verifying: /CN=root preverified: 1
[info] operator():55 Server Verifying: /CN=Client_User preverified: 0
[info] operator():55 Server Verifying: /CN=Client_User preverified: 0
[info] operator():55 Server Verifying: /CN=Client_User preverified: 1
[info] server:70 Handshake successful
[info] operator():107 Client SSL handshake Success
[info] main:176 Done!
Live:
本文标签: cboostasiosslcontext not sending client certificatesStack Overflow
版权声明:本文标题:c++ - boost::asio::ssl::context not sending client certificates - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1741931402a2405594.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论