基于Boost ASIO SSL实现单IP多HTTPS站点的SNI证书适配方案咨询
实现思路
Boost.Asio的SSL组件本质是对OpenSSL原生接口的封装,没有直接暴露SNI相关接口,但完全可以通过调用OpenSSL原生API实现SNI多证书匹配,不需要依赖Boost官方的封装支持。
具体修改步骤
1. 新增依赖与成员变量
首先引入OpenSSL原生头文件,替换原有单SSL上下文结构为多上下文映射:
#include <openssl/ssl.h> #include <unordered_map> // 类私有成员修改,删除原有单一ctx_变量,替换为: std::shared_ptr<boost::asio::ssl::context> default_ctx_; std::unordered_map<std::string, std::shared_ptr<boost::asio::ssl::context>> domain_ctx_map_;
2. 实现SNI回调函数
在类中新增静态C风格回调,用于在TLS握手阶段获取客户端发送的SNI域名,切换对应SSL上下文:
static int sni_callback(SSL *ssl, int *ad, void *arg) { if (!ssl) return SSL_TLSEXT_ERR_NOACK; const char* server_name = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name); if (!server_name) return SSL_TLSEXT_ERR_OK; // 无SNI用默认证书 auto* server_inst = static_cast<httpTLS_server_*>(arg); auto iter = server_inst->domain_ctx_map_.find(server_name); if (iter != server_inst->domain_ctx_map_.end()) { // 匹配到对应域名证书,切换上下文 SSL_set_SSL_CTX(ssl, iter->second->native_handle()); } return SSL_TLSEXT_ERR_OK; }
3. 新增多证书加载接口
新增添加域名证书的方法,同时兼容原有单证书逻辑:
// 新增公有方法:添加域名对应证书,domain传*表示默认证书 template<typename F> void add_domain_cert(const std::string& domain, F&& pwd_cb, std::string certificate_chain_file, std::string private_key_file, std::string tmp_dh_file = "") { auto ctx = std::make_shared<boost::asio::ssl::context>(boost::asio::ssl::context::tls_server); unsigned long ssl_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::single_dh_use; ctx->set_options(ssl_options); ctx->set_password_callback(std::forward<F>(pwd_cb)); ctx->use_certificate_chain_file(std::move(certificate_chain_file)); ctx->use_private_key_file(std::move(private_key_file), boost::asio::ssl::context::pem); if (!tmp_dh_file.empty()) { ctx->use_tmp_dh_file(std::move(tmp_dh_file)); } if (domain == "*") { default_ctx_ = ctx; // 给默认上下文注册SNI回调 SSL_CTX_set_tlsext_servername_callback(default_ctx_->native_handle(), sni_callback); SSL_CTX_set_app_data(default_ctx_->native_handle(), this); } else { domain_ctx_map_[domain] = ctx; } } // 原有init_ssl_context可直接兼容: template<typename F> void init_ssl_context(bool ssl_enable_v3, F&& f, std::string certificate_chain_file, std::string private_key_file, std::string tmp_dh_file) { add_domain_cert("*", std::forward<F>(f), std::move(certificate_chain_file), std::move(private_key_file), std::move(tmp_dh_file)); }
4. 修改连接初始化逻辑
修改start_accept方法中传入SSL上下文的部分,替换为默认上下文即可:
auto new_conn = std::make_shared<connection_ssl<Socket_ssl>>( io_service_pool_.get_io_service(), max_req_buf_size_, keep_alive_timeout_, https_handler_, static_dir_, upload_check_?&upload_check_ : nullptr, *default_ctx_ );
注意事项
- 要求OpenSSL版本 >= 1.0.1,已默认支持SNI扩展
- 如需支持泛域名证书,可将精确匹配逻辑改为后缀匹配,例如将
*.example.com的证书存储在key为.example.com的映射项中,拿到SNI后判断后缀匹配即可 - 所有SSL上下文的密码回调、DH参数等配置可根据域名单独调整,无需统一
内容的提问来源于stack exchange,提问作者Neo Chen
相关产品推荐
相关产品推荐

