libcurl CURLINFO_TLS_SESSION 详解:获取 TLS 会话内部指针与兼容性迁移指南
【免费下载链接】curlA command line tool and library for transferring data with URL syntax, supporting DICT, FILE, FTP, FTPS, GOPHER, GOPHERS, HTTP, HTTPS, IMAP, IMAPS, LDAP, LDAPS, MQTT, MQTTS, POP3, POP3S, RTSP, SCP, SFTP, SMB, SMBS, SMTP, SMTPS, TELNET, TFTP, WS and WSS. libcurl offers a myriad of powerful features项目地址: https://gitcode.com/GitHub_Trending/cu/curl
导读
CURLINFO_TLS_SESSION是 libcurl 提供的一类curl_easy_getinfo查询选项,用于在 HTTPS 等 TLS 握手完成后,取出底层 SSL 库(如 OpenSSL、wolfSSL、GnuTLS)内部会话结构体的指针,从而让应用程序直接访问证书链、协议版本等底层信息。本文基于 CURLINFO_TLS_SESSION 官方文档,结合 libcurl 源码实现、头文件定义 与仓库内的示例代码,完整讲解该选项的用法、不同 SSL 后端下internals指针的具体类型差异、它在 7.48.0 版本被CURLINFO_TLS_SSL_PTR取代的历史背景,以及新老接口的迁移要点。
读完本文,你将掌握:如何调用该选项获取 TLS 会话信息、如何根据backend枚举值安全地类型转换底层指针、OpenSSL 与 wolfSSL 场景下新旧选项返回指针的差异,以及新代码应如何使用推荐接口CURLINFO_TLS_SSL_PTR并规避已知限制。
一、选项概览与基本用法
1.1 功能定位
CURLINFO_TLS_SESSION用于向curl_easy_getinfo查询当前 easy handle 上"正在使用"的 TLS 会话信息。查询结果是一个指向struct curl_tlssessioninfo的指针,该结构体包含两个成员(定义见 include/curl/curl.h#L2911-L2917):
struct curl_tlssessioninfo { curl_sslbackend backend; /* 标识握手所用的 SSL 库 */ void *internals; /* 指向该 SSL 库内部会话结构的指针 */ };backend:一个枚举值,标识 libcurl 实际编译链接的 SSL 后端。枚举的完整定义位于 include/curl/curl.h#L154-L178,包括CURLSSLBACKEND_NONE(未启用 TLS 支持时)、CURLSSLBACKEND_OPENSSL、CURLSSLBACKEND_GNUTLS、CURLSSLBACKEND_WOLFSSL、CURLSSLBACKEND_SCHANNEL、CURLSSLBACKEND_SECURETRANSPORT、CURLSSLBACKEND_MBEDTLS等。其中 OpenSSL 的分支实现(BoringSSL、LibreSSL、AWS-LC)统一报告为CURLSSLBACKEND_OPENSSL,wolfSSL 的前身 CyaSSL 则统一报告为CURLSSLBACKEND_WOLFSSL。internals:void 指针,指向所使用 SSL 库的内部会话结构。它的具体类型随backend不同而变化,直接强转前必须先检查backend。
1.2 函数签名
#include <curl/curl.h> CURLcode curl_easy_getinfo(CURL *handle, CURLINFO_TLS_SESSION, struct curl_tlssessioninfo **session);handle:执行过 TLS 握手的 easy handle;session:输出参数,调用后指向一个struct curl_tlssessioninfo *;- 返回值:
CURLE_OK(0)表示成功,非零表示出错,具体错误码参见 libcurl-errors。
1.3 官方示例(GnuTLS 场景)
仓库中的官方示例 sessioninfo.c 展示了完整用法:在写回调write_cb中调用curl_easy_getinfo(curl, CURLINFO_TLS_SESSION, &info),然后根据info->backend判断后端,把info->internals当作gnutls_session_t使用,并用 GnuTLS API 打印对端证书链:
static size_t write_cb(char *ptr, size_t size, size_t nmemb, void *stream) { const struct curl_tlssessioninfo *info; CURLcode result; (void)stream; (void)ptr; result = curl_easy_getinfo(curl, CURLINFO_TLS_SESSION, &info); if(result == CURLE_OK) { unsigned int cert_list_size; const gnutls_datum_t *chainp; switch(info->backend) { case CURLSSLBACKEND_GNUTLS: /* info->internals 此时就是 gnutls_session_t */ chainp = gnutls_certificate_get_peers(info->internals, &cert_list_size); if(chainp && cert_list_size) { /* 遍历证书链,使用 gnutls_x509_crt_import / gnutls_x509_crt_print 打印 */ } break; case CURLSSLBACKEND_NONE: default: break; } } return size * nmemb; }该示例使用CURLOPT_WRITEFUNCTION在数据传输回调内查询会话信息,这一模式能保证查询发生在 TLS 会话仍与 easy handle 关联的有效窗口期内(详见下文"指针的生命周期")。注意示例文件头部注明:当前该示例要求 libcurl 以 GnuTLS 编译(USE_GNUTLS),程序还需链接-lgnutls。
二、不同 SSL 后端下 internals 指针的类型
internals指向的是"当前正在使用"的那个 SSL 连接对象。由于 libcurl 可编译链接多种 TLS 库,同一字段在不同后端下对应完全不同的底层类型,必须依据backend成员进行区分。下表汇总了CURLINFO_TLS_SESSION(即本文档)与CURLINFO_TLS_SSL_PTR(推荐接口)在各后端下的类型对照:
| SSL 后端 | CURLINFO_TLS_SESSION 的 internals | CURLINFO_TLS_SSL_PTR 的 internals |
|---|---|---|
| OpenSSL(含 BoringSSL / LibreSSL / AWS-LC) | SSL_CTX * | SSL * |
| wolfSSL(含 CyaSSL) | WOLFSSL_CTX * | WOLFSSL * |
| GnuTLS | gnutls_session_t | gnutls_session_t |
| mbedTLS | mbedtls_ssl_context * | mbedtls_ssl_context * |
| Schannel | CtxtHandle * | CtxtHandle * |
| SecureTransport | SecureTransport 内部会话句柄 | SecureTransport 内部会话句柄 |
关键结论:除 OpenSSL 与 wolfSSL 两个后端外,CURLINFO_TLS_SESSION与CURLINFO_TLS_SSL_PTR返回的内容完全一致(这一对照关系在两份文档中均有明确记载,见 CURLINFO_TLS_SSL_PTR.md 的 DESCRIPTION 章节)。
2.1 OpenSSL 后端的差异:SSL_CTX * vs SSL *
当backend为CURLSSLBACKEND_OPENSSL时:
CURLINFO_TLS_SESSION返回的internals是SSL_CTX *(SSL 上下文对象,描述整个会话共用的配置);CURLINFO_TLS_SSL_PTR返回的internals是SSL *(具体的 SSL 连接对象,描述当前这条连接)。
如需从SSL *反推SSL_CTX *,可直接调用 OpenSSL 的函数SSL_get_SSL_CTX(3)。因此文档明确建议:除非需要兼容旧版 libcurl,否则一律使用CURLINFO_TLS_SSL_PTR,因为SSL *是与具体连接绑定的对象,能反映握手后的实际会话状态(例如协商出的协议版本、会话票据等),信息更贴近当前连接。
2.2 wolfSSL 后端的差异:WOLFSSL_CTX * vs WOLFSSL *
wolfSSL(原 CyaSSL)场景下的情况与 OpenSSL 类似:
CURLINFO_TLS_SESSION返回WOLFSSL_CTX *;CURLINFO_TLS_SSL_PTR返回WOLFSSL *。
可通过 wolfSSL 的wolfSSL_get_SSL_CTX(3)从WOLFSSL *获取WOLFSSL_CTX *。同理,新代码应优先使用CURLINFO_TLS_SSL_PTR。
三、源码实现:该选项在 libcurl 内部如何工作
3.1 getinfo 分发逻辑
在 lib/getinfo.c#L577-L593 中,两个选项共用同一段分发代码,唯一的区别是查询类型:
case CURLINFO_TLS_SESSION: case CURLINFO_TLS_SSL_PTR: { int query = (info == CURLINFO_TLS_SSL_PTR) ? CF_QUERY_SSL_INFO : CF_QUERY_SSL_CTX_INFO; struct curl_tlssessioninfo **tsip = (struct curl_tlssessioninfo **) param_slistp; struct curl_tlssessioninfo *tsi = &data->tsi; /* we are exposing a pointer to internal memory with unknown * lifetime here. */ *tsip = tsi; if(!Curl_conn_get_ssl_info(data,>#include <curl/curl.h> #include <openssl/ssl.h> static CURL *curl; static size_t wf(char *ptr, size_t size, size_t nmemb, void *stream) { const struct curl_tlssessioninfo *info = NULL; CURLcode result = curl_easy_getinfo(curl, CURLINFO_TLS_SSL_PTR, &info); if(info && !result) { if(CURLSSLBACKEND_OPENSSL == info->backend) { printf("OpenSSL ver. %s\n", SSL_get_version((SSL*)info->internals)); } } return size * nmemb; } int main(int argc, char *argv[]) { CURLcode result = CURLE_OK; curl = curl_easy_init(); if(curl) { curl_easy_setopt(curl, CURLOPT_URL, "https://example.com"); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, wf); result = curl_easy_perform(curl); curl_cleanup(curl); } return (int)result; }注意:必须先判断info != NULL且result == CURLE_OK,再比较info->backend,最后才把internals强转为对应类型——因为internals的类型完全由backend决定,跨后端强转属于未定义行为。
6.2 官方测试用例佐证
仓库的集成测试 tests/data/test1587(关键词CURLINFO_TLS_SESSION)在 OpenSSL + HTTPS 环境下同时验证了CURLINFO_TLS_SESSION与CURLINFO_TLS_SSL_PTR两个选项,测试客户端为 tests/libtest/lib1587.c,测试要求 libcurl 具备SSL与OpenSSL特性。测试覆盖了两个选项在 OpenSSL 后端下返回指针的差异(SSL_CTX *vsSSL *),是理解本主题行为差异最直接的运行级证据。
6.3 已知限制(LIMITATIONS)
新接口CURLINFO_TLS_SSL_PTR继承并扩展了本文档相关的全部注意事项,文档 CURLINFO_TLS_SSL_PTR.md 的 LIMITATIONS 章节 列出以下限制,使用时应心中有数:
- 多会话场景只能取到第一个:easy handle 可能同时存在多个进行中的 SSL 会话——典型场景是 FTP over SSL(控制通道 + 数据通道均可能走 TLS)。当前接口只能取到第一个 in-use 会话指针,无法获取第二个会话。
- 明文协议升级/降级场景未经充分测试:FTP、SMTP、POP3、IMAP 配合
CURLOPT_USE_SSL使用时,从明文升级到 TLS 的过程中,可能在你能取到 SSL 指针之前,认证等敏感数据就已经在升级后的连接上发出。 - 重协商(renegotiation)风险:若底层 SSL 库允许不安全重协商或允许证书变更的重协商,证书可能在重协商中发生变化,而在你取到(可能已变更的)SSL 指针之前,数据可能已按新证书继续收发。
- 不要用它轮询证书变更:文档给出的安全替代方案是使用
CURLOPT_SSL_CTX_FUNCTION设置验证回调(在支持的 TLS 后端下),该方案更安全,不承受上述任一问题。
七、总结
CURLINFO_TLS_SESSION是 libcurl 中接触底层 TLS 库内部对象的最早入口,其核心价值在于通过struct curl_tlssessioninfo暴露握手所用的 SSL 后端与内部会话指针,让开发者能够用 OpenSSL/GnuTLS/wolfSSL 等库的原生 API 做证书提取、协议版本查询、手动校验等深度操作。但受历史设计影响,它在 OpenSSL 与 wolfSSL 后端返回的是SSL_CTX */WOLFSSL_CTX *这类上下文对象,且自 7.48.0 起被CURLINFO_TLS_SSL_PTR取代。新项目应直接使用CURLINFO_TLS_SSL_PTR(返回与具体连接绑定的SSL */WOLFSSL *),在回调中查询、先核对backend再转换类型,并规避多会话、升级连接与重协商场景下的已知限制。
参考文件索引
- 本文档:docs/libcurl/opts/CURLINFO_TLS_SESSION.md
- 推荐替代接口文档:docs/libcurl/opts/CURLINFO_TLS_SSL_PTR.md
- 结构体与枚举定义:include/curl/curl.h#L2911-L2917、include/curl/curl.h#L154-L178
- getinfo 实现:lib/getinfo.c#L577-L593
- 连接过滤链查询:lib/vtls/vtls.c#L1238-L1246
- 官方示例:docs/examples/sessioninfo.c
- 集成测试:tests/data/test1587、tests/libtest/lib1587.c
【免费下载链接】curlA command line tool and library for transferring data with URL syntax, supporting DICT, FILE, FTP, FTPS, GOPHER, GOPHERS, HTTP, HTTPS, IMAP, IMAPS, LDAP, LDAPS, MQTT, MQTTS, POP3, POP3S, RTSP, SCP, SFTP, SMB, SMBS, SMTP, SMTPS, TELNET, TFTP, WS and WSS. libcurl offers a myriad of powerful features项目地址: https://gitcode.com/GitHub_Trending/cu/curl
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考