对于一些奇怪的原因,请调用
#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
实例设置证书,而私有键文件不起作用。它在握手中失败了。
Java服务器日志没有发送证书链。
boost::asio::ssl::context
,如果我从
javax.net.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.net.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" : {
中获取本机SSL句柄,并直接调用本机API以设置证书和私钥文件,则一切都可以。客户证书已发送并握手成功。
我只能得出结论,我错过了一个步骤或以错误的顺序做某事,但是我无法分辨什么。我也尝试了以下内容,没有成功
boost::asio::ssl::context
我太肛门了,无法将作品留在原地,然后走开了。
问题是您在构造SSL流对象之后设置上下文参数。直接使用OpenSSL的工作是您正在使用
// 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);
SSL_use_certificate_file
SSL_use_PrivateKey_file
SSL_CTX_use_PrivateKey_file
工厂来修复初始化顺序。请注意,我已经对验证进行了固定的验证,以允许我的自我签名的测试证书。 live在coliru上
create_ssl_context()
#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";
}
live: