
libcurl CURLINFO_CERTINFO 详解从 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_CERTINFO 是 libcurl 提供的 TLS 证书链信息查询接口配合 CURLOPT_CERTINFO 选项可以在 HTTPS 等 TLS 连接建立后按证书链顺序提取每一张证书的 Subject、Issuer、序列号、有效期、公钥参数乃至完整 PEM 内容等结构化文本信息。本文以 curl 项目官方文档 CURLINFO_CERTINFO 为骨架结合 curl.h、getinfo.c、vtls.c 与各 TLS 后端实现源码讲清接口用法、数据结构、字段含义、后端差异与底层实现原理。读完本文你将能够用一段可运行的 C 代码打印出目标站点的完整证书链并理解其背后的数据流。一、接口速览函数签名与数据结构CURLINFO_CERTINFO 是 curl_easy_getinfo(3) 的查询选项用于获取服务器证书链的信息。其调用形式为#include curl/curl.h CURLcode curl_easy_getinfo(CURL *handle, CURLINFO_CERTINFO, struct curl_certinfo **chainp);调用时传入一个struct curl_certinfo *指针的地址返回后该指针被设置为指向一个保存服务器证书链信息的结构体。前提是发起请求时已经通过CURLOPT_CERTINFO开启了证书信息收集详见下文。返回的数据结构在 include/curl/curl.h 中定义如下/* info about the certificate chain, for SSL backends that support it. Asked for with CURLOPT_CERTINFO / CURLINFO_CERTINFO */ struct curl_certinfo { int num_of_certs; /* number of certificates with information */ struct curl_slist **certinfo; /* for each index in this array, there is a linked list with textual information for a certificate in the format name:content. eg Subject:foo, Issuer:bar, etc. */ };num_of_certs证书链中证书的数量即certinfo数组的元素个数。链中证书通常按从叶证书服务器证书到根证书的顺序排列具体顺序与 TLS 后端及服务器配置有关。certinfo一个指针数组数组长度为num_of_certs。每个元素指向一个struct curl_slist链表链表中的每一项是一条文本信息格式统一为name:content例如Subject:CNwww.example.com、Issuer:CUS, OLets Encrypt等。每张证书的条目内容因 TLS 后端和证书本身而异。在 include/curl/curl.h 中CURLINFO_CERTINFO被定义为CURLINFO_PTR 34属于指针类型的信息项——这也解释了为什么返回值是一个指针而非普通数值。相关的配套选项CURLOPT_CERTINFOdocs/libcurl/opts/CURLOPT_CERTINFO.md传输前通过curl_easy_setopt传入1L开启证书信息收集默认值为0关闭。必须开启它curl_easy_getinfo查询CURLINFO_CERTINFO才有数据可读。该选项在 TLS 协议HTTPS、FTPS、IMAPS、SMTPS 等下有效并依赖具体 TLS 后端的支持能力。二、使用前提先通过 CURLOPT_CERTINFO 开启收集CURLINFO_CERTINFO本身只负责“读取”数据收集需要靠CURLOPT_CERTINFO选项提前开启。官方文档 CURLOPT_CERTINFO 说明传入一个值为1的 long 即可启用 libcurl 的证书链信息收集器之后 libcurl 会在 TLS 握手阶段提取证书链中每张证书的大量信息供传输结束后通过curl_easy_getinfo与CURLINFO_CERTINFO取回。在源码层面lib/setopt.c 处理CURLOPT_CERTINFO时会先检测当前 TLS 后端是否支持证书信息收集能力SSLSUPP_CERTINFOcase CURLOPT_CERTINFO: #ifdef USE_SSL if(Curl_ssl_supports(data, SSLSUPP_CERTINFO)) s-ssl.certinfo enabled; else #endif return CURLE_NOT_BUILT_IN; break;这意味着如果 libcurl 编译时使用的 TLS 后端不支持该特性curl_easy_setopt(curl, CURLOPT_CERTINFO, 1L)会直接返回CURLE_NOT_BUILT_IN而不是静默忽略。开发者应当检查该调用的返回值以确认功能可用。三、完整示例打印目标站点的证书链官方文档 CURLINFO_CERTINFO 给出了核心示例仓库中的 docs/examples/certinfo.c 则提供了一个更完整的可编译版本额外包含curl_global_init/curl_global_cleanup与写回调避免响应体干扰输出。下面是在官方示例基础上补充了错误检查与初始化清理的完整代码#include stdio.h #include curl/curl.h /* 丢弃响应体仅关注证书信息 */ static size_t write_cb(char *ptr, size_t size, size_t nmemb, void *stream) { (void)stream; (void)ptr; return size * nmemb; } int main(void) { CURL *curl; CURLcode result; result curl_global_init(CURL_GLOBAL_ALL); if(result ! CURLE_OK) return (int)result; curl curl_easy_init(); if(curl) { curl_easy_setopt(curl, CURLOPT_URL, https://www.example.com/); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_cb); /* 连接到任意 HTTPS 站点无论证书是否受信任 */ curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L); /* 开启证书链信息收集关键步骤 */ curl_easy_setopt(curl, CURLOPT_CERTINFO, 1L); result curl_easy_perform(curl); if(result CURLE_OK) { struct curl_certinfo *ci NULL; result curl_easy_getinfo(curl, CURLINFO_CERTINFO, ci); if(result CURLE_OK ci) { int i; printf(%d certs!\n, ci-num_of_certs); for(i 0; i ci-num_of_certs; i) { struct curl_slist *slist; printf(-- cert[%d] --\n, i); for(slist ci-certinfo[i]; slist; slist slist-next) printf(%s\n, slist-data); } } } curl_easy_cleanup(curl); } curl_global_cleanup(); return (int)result; }代码要点必须设置CURLOPT_CERTINFO为1L且放在curl_easy_perform之前否则查询返回的结构中num_of_certs为 0取不到任何数据。示例中关闭了CURLOPT_SSL_VERIFYPEER与CURLOPT_SSL_VERIFYHOST目的是允许连接到自签名或不受信任证书的站点也能完成握手并提取链信息生产环境中通常保留默认的证书校验。curl_easy_getinfo返回的结构体指针指向 libcurl 内部管理的内存data-info.certs无需、也不应手动 freelibcurl 会在合适时机自行释放详见下文实现原理。示例代码不调用curl_slist_free_all正是因为这个原因。结果结构体在curl_easy_cleanup之后即失效请确保在同一 handle 的生命周期内完成读取。编译方式以 gcc 为例gcc -o certinfo certinfo.c -lcurl ./certinfo输出形如字段随 TLS 后端与证书内容变化3 certs! -- cert[0] -- Subject:CNwww.example.com Issuer:CUS, OLets Encrypt, CNR10 Version:2 Serial Number:04:1e:... Signature Algorithm:sha256WithRSAEncryption Public Key Algorithm:rsaEncryption ... Start date:Sep 8 00:00:00 2024 GMT Expire date:Dec 7 00:00:00 2024 GMT RSA Public Key:(2048 bits) ... Cert: -----BEGIN CERTIFICATE----- ... -----END CERTIFICATE----- -- cert[1] -- Subject:CUS, OLets Encrypt, CNR10 Issuer:CUS, OInternet Security Research Group, CNISRG Root X1 ...四、字段清单一张证书会包含哪些 name:content 条目文档明确指出每条信息的格式是name:content如Subject:Foo、Issuer:Bar并且条目内容因 SSL 后端和证书本身而异。以 OpenSSL 后端为例lib/vtls/openssl.c 中的ossl_certchain()函数逐张提取了以下字段字段名含义提取方式OpenSSL 后端Subject证书主体持有者DNX509_NAME_print_ex(..., X509_get_subject_name(...))Issuer证书颁发者 DNX509_NAME_print_ex(..., X509_get_issuer_name(...))VersionX.509 版本号十六进制X509_get_version()Serial Number证书序列号十六进制X509_get_serialNumber()Signature Algorithm签名算法 OIDX509_get0_signature()/i2a_ASN1_OBJECT()Public Key Algorithm公钥算法 OIDX509_PUBKEY_get0_param()X.509 v3 扩展如Subject Alternative Name、Basic Constraints等X509V3_ext()遍历X509_get0_extensions()Start date生效时间ASN1_TIME_print(X509_get0_notBefore())Expire date到期时间ASN1_TIME_print(X509_get0_notAfter())RSA/DSA/DH 公钥参数如RSA Public Key:(2048 bits)、RSA Public-Key:及模数/指数pubkey_show()/print_pubkey_BN()宏Signature签名值十六进制字节以:分隔遍历ASN1_STRING_get0_data(psig)Cert该证书的完整 PEM 文本PEM_write_bio_X509()其中公钥参数的提取值得展开OpenSSL 后端会根据EVP_PKEY_id(pubkey)的结果对 RSAget_pkey_rsa、DSAget_pkey_dsa、DHget_pkey_dh分别调用pubkey_show()将模数n、指数e等大整数以BN_print形式写入条目例如RSA Public Key:(2048 bits) RSA Public-Key:(2048 bit) Modulus: 00:ab:cd:... Exponent:65537 (0x10001)注意其它后端GnuTLS、Schannel、Rustls、mbedTLS 等产出的字段集合并不完全相同——这正是文档强调“items vary depending on the SSL backend and the certificate”的原因。编写依赖具体字段的代码时应对未知字段保持容错。五、支持的 TLS 后端与版本历史根据文档头部的元信息与 HISTORY 章节Added-in: 7.19.1——CURLINFO_CERTINFO自 libcurl 7.19.1 起加入。文档声明支持的后端OpenSSL、GnuTLS、Schannel、Rustls。历史补充GnuTLS 支持自7.42.0加入SchannelWindows 原生 TLS支持自7.50.0加入mbedTLS 支持自8.9.0加入。在源码中lib/vtls/目录下openssl.c、gtls.c、schannel.c、rustls.c、mbedtls.c都实现了证书信息收集且多个后端都做了证书数量上限保护MAX_ALLOWED_CERT_AMOUNT。以 OpenSSL 后端为例openssl.c 会先获取对端证书链若数量超过上限则返回CURLE_SSL_CONNECT_ERRORnumcerts sk_X509_num(sk); if(numcerts MAX_ALLOWED_CERT_AMOUNT) { failf(data, %d certificates is more than allowed (%d), (int)numcerts, MAX_ALLOWED_CERT_AMOUNT); return CURLE_SSL_CONNECT_ERROR; }CURLOPT_CERTINFO的启用同样依赖后端能力在 lib/setopt.c 中若后端不支持SSLSUPP_CERTINFO设置选项会返回CURLE_NOT_BUILT_IN。因此跨平台程序应先检查curl_easy_setopt的返回值并对“不支持”的情况做降级处理。六、底层实现从握手到取回的数据流理解CURLINFO_CERTINFO的内部数据流有助于正确使用它。整条链路涉及三处关键代码1. 存储位置data-info.certs证书信息存放在每个 easy handle 的struct Curl_easy中。见 lib/urldata.hstruct curl_certinfo certs; /* info about the certs. Asked for with */2. 收集握手阶段由 TLS 后端填充各 TLS 后端在握手完成后把证书信息逐条写入data-info.certs其公共操作封装在 lib/vtls/vtls.c 中Curl_ssl_init_certinfo(data, num)按链中证书数量num分配struct curl_slist **数组先释放旧数据Curl_ssl_free_certinfo再curlx_calloc分配数组并设置num_of_certs。见 vtls.c。Curl_ssl_push_certinfo_len(data, certnum, label, value, valuelen)把一条label:value文本追加到第certnum张证书对应的 slist 链表中。它内部用dynbuf拼接label、:与定长value再通过Curl_slist_append_nodup挂到链表尾部失败时释放整个链表并返回CURLE_OUT_OF_MEMORY。见 vtls.c。Curl_ssl_free_certinfo(data)遍历链表逐个curl_slist_free_all再释放数组本身、重置num_of_certs 0。见 vtls.c。以 OpenSSL 后端为例ossl_certchain()openssl.c的流程为SSL_get_peer_cert_chain(ssl)取对端证书链检查数量上限后Curl_ssl_init_certinfo分配数组循环每张证书用 BIO 内存缓冲配合X509_NAME_print_ex、ASN1_INTEGER、ASN1_TIME_print等 OpenSSL API 格式化各字段逐一push_certinfo写入任一步出错则调用Curl_ssl_free_certinfo清理残留数据并返回错误。3. 取回getinfo 返回内部指针curl_easy_getinfo(curl, CURLINFO_CERTINFO, ci)的处理位于 lib/getinfo.c 的getinfo_slist()分支case CURLINFO_CERTINFO: /* Return the a pointer to the certinfo struct. Not really an slist pointer but we can pretend it is here */ ptr.to_certinfo data-info.certs; *param_slistp ptr.to_slist; break;可见它只是把data-info.certs的地址“伪装”成 slist 指针返回给调用者没有发生数据拷贝。这带来两个重要结论返回的结构体由 libcurl 内部持有生命周期与 easy handle 绑定不要手动释放其中的链表在curl_easy_cleanup之前完成读取多次调用curl_easy_getinfo返回的是同一块内部数据。七、在 curl 命令行工具中的使用%{certs}变量CURLINFO_CERTINFO不仅面向 C 程序curl 命令行工具也通过它实现了-wwrite-out输出变量%{certs}。在 src/tool_writeout.c 中工具会惰性获取证书信息static void certinfo(struct per_transfer *per) { if(!per-certinfo) { const struct curl_certinfo *certinfo; CURLcode result curl_easy_getinfo(per-curl, CURLINFO_CERTINFO, certinfo); per-certinfo (!result certinfo) ? certinfo : NULL; } }随后在输出%{certs}时tool_writeout.c它会遍历每张证书的 slist跳过cert:前缀字段并把其余name:content条目逐行拼接。命令行用法示例curl -k --certinfo -w \n%{certs}\n -o /dev/null https://www.example.com/--certinfo对应CURLOPT_CERTINFO选项%{certs}的输出内容即curl_certinfo结构的文本化呈现。注意%{certs}需要CURLINFO_CERTINFO底层能力支持且仅在 TLS 连接成功建立后才有数据。八、测试与验证证书链顺序检查仓库的单元测试 tests/libtest/lib3102.c 展示了curl_certinfo的典型消费方式也说明了字段格式的稳定性它读取每张证书的Subject与Issuer条目验证链中证书顺序是否正确前一张证书的 Subject 应等于后一张证书的 Issuerstatic bool is_chain_in_order(struct curl_certinfo *cert_info) { const char *last_issuer NULL; int cert; /* Chains with only a single certificate are always in order */ if(cert_info-num_of_certs 1) return TRUE; /* Enumerate each certificate in the chain */ for(cert 0; cert cert_info-num_of_certs; cert) { const struct curl_slist *slist cert_info-certinfo[cert]; const char *issuer NULL; const char *subject NULL; /* Find the certificate issuer and subject by enumerating each field */ for(; slist (!issuer || !subject); slist slist-next) { static const char issuer_prefix[] Issuer:; static const char subject_prefix[] Subject:; ... } } ... }该测试的写法可以借鉴为生产代码的通用模式遍历 slist用strncmp匹配Issuer:、Subject:等前缀来定位字段而不是假设条目顺序。九、返回值与错误处理curl_easy_getinfo返回CURLcode文档明确CURLE_OK (0)查询成功非零发生错误具体含义参见 libcurl-errors 文档如CURLE_UNKNOWN_OPTION、内存分配失败等。同样curl_easy_setopt(curl, CURLOPT_CERTINFO, 1L)的返回值也需要检查尤其是当目标平台 TLS 后端不支持该特性时会得到CURLE_NOT_BUILT_IN。健壮的程序应检查CURLOPT_CERTINFO设置是否返回CURLE_OK检查curl_easy_perform返回CURLE_OK握手成功后再查询证书信息查询后判空ci可能为NULL读取时对字段前缀做容错匹配不依赖固定条目顺序。十、注意事项小结必须先开CURLOPT_CERTINFO再 perform否则num_of_certs为 0、无数据可读该选项默认关闭。返回指针归 libcurl 所有不要手动释放 slist也不要跨过curl_easy_cleanup之后继续使用。字段内容与后端相关OpenSSL/GnuTLS/Schannel/Rustls/mbedTLS 的字段集合不完全一致文档与源码均强调这一点解析代码须容错。仅 TLS 协议有效非 TLS如纯 HTTP、FTP连接不会有证书信息。适用于审计类场景证书链检查、指纹采集、证书有效期监控、链顺序校验如 lib3102.c 所示都是CURLINFO_CERTINFO的典型应用更底层的 SSL 句柄访问可参考CURLINFO_TLS_SSL_PTR系列接口。【免费下载链接】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),仅供参考