You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在C++中完成认证并下载数据库服务器SSL证书

问题根因

你直接照搬HTTPS场景的OpenSSL连接逻辑拿不到数据库证书,核心原因非常明确:几乎所有数据库服务都不会在TCP连接建立后直接启动TLS握手。

  • HTTPS 443端口默认TCP三次握手完成就立刻进入TLS协商流程,所以直接用BIO_new_ssl_connect建连发起握手就能拿到证书
  • 不管是MySQL、PostgreSQL还是SQL Server,都是先走明文协议交互:客户端先发启动包,服务端返回支持的能力列表,客户端明确发送SSL启用请求后,双方才会切换到TLS层。你上来直接发TLS Client Hello,服务端会把这识别为无效协议包直接断连,BIO_do_connect()自然会报错。
可行实现方案

方案1:复用现有ODBC驱动能力获取证书(推荐,不用重复造轮子)

你本来就在用ODBC做连接查询,完全没必要自己从头实现数据库协议和TLS交互,驱动本身已经把这些逻辑封装好了:

  • 先配置ODBC连接串,开启SSL连接选项,把SSL校验模式设为verify-ca(不同驱动参数名略有差异,比如SQL Server对应参数是TrustServerCertificate=no),先给CA证书路径配置一个临时空文件路径
  • 通过ODBC的连接属性接口注册证书回调:
    • MySQL ODBC驱动可调用SQLSetConnectAttr设置SSL证书回调,握手阶段驱动会把服务端返回的证书链直接传给回调函数
    • PostgreSQL ODBC驱动支持注册PGsslcertcallback,回调触发时就能拿到完整的服务端证书
    • 部分ODBC驱动如果没有暴露证书回调,可以直接在驱动的SSL日志目录里找到缓存的服务端证书,或者临时用自签CA做本地代理抓包拿证书(仅限你有权限访问的服务,禁止做未授权操作)
  • 回调里拿到X509证书对象后,直接用PEM_write_bio_X509把证书存成PEM格式文件即可。身份认证逻辑也可以直接在回调里实现:校验证书的指纹、颁发者、主机名匹配规则是否符合预期,校验通过再返回0让驱动继续连接流程,校验失败直接返回错误断连即可。

方案2:手动实现协议前置交互+OpenSSL握手拿证书(无ODBC依赖场景)

如果需要脱离ODBC自主实现,先对应查阅你所用数据库的协议规范,走完SSL协商前的明文交互,再把socket交给OpenSSL处理:

  1. 建立普通TCP连接到数据库主机端口,不要直接挂载SSL BIO
  2. 按照对应数据库协议格式,发送SSL启用请求包:
    • MySQL场景:发送长度为8的包,前3字节是包长度0x00 0x00 0x00,第4字节是序列号0x01,后面4字节是SSL能力标记0x00 0x00 0x00 0x80
    • PostgreSQL场景:发送8字节的SSL请求包,前4字节为大端序整数80877103(固定SSL请求码)
  3. 等待服务端返回单字节响应:如果返回S代表同意进入SSL握手,其他返回值代表服务端未启用SSL
  4. 此时再把已经建立好的TCP socket绑定到OpenSSL的SSL BIO上,调用SSL_connect()完成握手,就不会再出现之前的连接失败问题
  5. 握手完成后调用SSL_get_peer_certificate()就能拿到服务端的证书对象,直接转成PEM格式存储到本地即可
  6. 身份认证环节:拿到证书后先校验证书有效期、主机名匹配度、证书指纹是否和预期一致,校验通过后再发送用户名密码做数据库登录,校验失败直接断开连接即可。

核心实现代码片段(以MySQL为例):

// 1. 建立普通TCP连接
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
// 省略connect到数据库主机端口的标准socket逻辑...

// 2. 发送MySQL SSL协商请求包
unsigned char ssl_req[] = {0x00, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x00};
send(sockfd, ssl_req, sizeof(ssl_req), 0);

// 3. 读取服务端SSL协商响应
unsigned char resp;
recv(sockfd, &resp, 1, 0);
if (resp != 'S') {
    close(sockfd);
    throw std::runtime_error("target server does not support SSL");
}

// 4. 将已连接的TCP socket绑定到OpenSSL
SSL_CTX* ctx = SSL_CTX_new(TLS_client_method());
SSL* ssl = SSL_new(ctx);
BIO* bio = BIO_new_socket(sockfd, BIO_NOCLOSE);
SSL_set_bio(ssl, bio, bio);

// 5. 完成TLS握手
int ret = SSL_connect(ssl);
if (ret != 1) {
    // 省略握手错误处理逻辑
}

// 6. 获取服务端证书并存储
X509* cert = SSL_get_peer_certificate(ssl);
if (cert) {
    BIO* out = BIO_new_file("db_server_cert.pem", "w");
    PEM_write_bio_X509(out, cert);
    BIO_free(out);

    // 此处执行证书校验逻辑:校验指纹、域名、有效期
    // 校验通过后再发送账号密码完成数据库认证
    X509_free(cert);
}

// 省略后续资源清理逻辑

注意:绝对不要跳过证书校验步骤就直接发送账号密码,否则会存在中间人攻击风险。拿到证书后建议先和运维侧核对证书指纹,确认是目标服务的合法证书后,再把它加到ODBC的信任CA列表里,开启强制SSL校验模式。

内容的提问来源于stack exchange,提问作者user17276231

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.30 14:54:19