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

如何通过OpenSSL API从SSL_CTX的证书链中提取叶子证书?

如何从OpenSSL的SSL_CTX证书链中获取叶子证书并打印元数据

问题描述

我在C程序中使用OpenSSL API与远程服务器建立双向TLS(Mutual-TLS)连接。建立连接时,客户端使用用户提供的任意X509 PEM证书(自签名证书、CA签发的叶子证书或链式证书均可),通过SSL_CTX_use_certificate_chain_file()将其加载到SSL_CTX中,该函数支持加载单证书或证书链。

我的目标是在终端打印叶子证书的元数据,示例输出如下:

% my_tls_client -key foo.key -cert foo.crt myserver.com:2566
Attempting to connect to server myserver.com port:2566
Using host certificate as follows:
    serial: aa:bb:cc:dd:ee
    subject: foo...
    valid from: DATE1
    valid until: DATE2
    WARNING: The provided certificate is about to expire in X days !
    Connected to server successfully!

为此,我需要先从SSL_CTX的X509_STORE中识别并获取叶子(主机)证书。请问是否存在可从证书链中获取叶子证书的OpenSSL API?我了解到一种方法建议使用SSL_CTX_get0_certificate(),但未在OpenSSL文档中找到该方法的相关参考;另一个APIX509_STORE_CTX_get_current_cert()似乎也无法专门获取叶子证书。

解决方案

1. 直接使用SSL_CTX_get0_certificate()获取叶子证书

SSL_CTX_get0_certificate()是OpenSSL 1.0.2及以上版本的合法API,专门用于获取SSL_CTX中设置的叶子证书(证书链的第一个证书)。你没找到文档可能是因为查看的版本不匹配,该函数在对应版本的官方文档中是存在的。

调用示例:

X509 *leaf_cert = SSL_CTX_get0_certificate(ctx);
if (leaf_cert == NULL) {
    fprintf(stderr, "Failed to get leaf certificate from SSL_CTX\n");
    return -1;
}

2. 从已建立的SSL连接对象获取叶子证书

如果已经完成TLS连接建立,也可以直接从SSL对象中提取当前使用的叶子证书,双向TLS场景下同样适用:

X509 *leaf_cert = SSL_get0_certificate(ssl);
if (leaf_cert == NULL) {
    fprintf(stderr, "Failed to get leaf certificate from SSL object\n");
    return -1;
}

3. 手动遍历证书链(备选方案)

如果需要确认证书链中的叶子证书(比如处理特殊的链顺序),可以通过以下逻辑判断:

  • 自签名证书:subject与issuer字段完全相同
  • 非自签名叶子证书:通常是证书链的第一个证书(SSL_CTX_use_certificate_chain_file会按文件顺序加载,叶子在前)

遍历示例代码:

STACK_OF(X509) *chain = NULL;
SSL_CTX_get0_chain_certs(ctx, &chain);
if (chain == NULL) {
    fprintf(stderr, "Failed to get certificate chain\n");
    return -1;
}

X509 *leaf_cert = NULL;
for (int i = 0; i < sk_X509_num(chain); i++) {
    X509 *cert = sk_X509_value(chain, i);
    // 判断是否为自签名证书
    if (X509_NAME_cmp(X509_get_subject_name(cert), X509_get_issuer_name(cert)) == 0) {
        leaf_cert = cert;
        break;
    }
    // 非自签名证书取链中第一个
    if (i == 0) {
        leaf_cert = cert;
        break;
    }
}

4. 打印叶子证书元数据示例

拿到叶子证书后,可通过OpenSSL API提取并打印所需元数据:

// 打印证书序列号
ASN1_INTEGER *serial = X509_get_serialNumber(leaf_cert);
char serial_buf[256];
i2a_ASN1_INTEGER(NULL, serial, serial_buf, sizeof(serial_buf));
printf("    serial: %s\n", serial_buf);

// 打印证书subject字段
char subject_buf[1024];
X509_NAME_oneline(X509_get_subject_name(leaf_cert), subject_buf, sizeof(subject_buf));
printf("    subject: %s\n", subject_buf);

// 打印有效期
char nb_buf[64], na_buf[64];
ASN1_TIME *not_before = X509_get_notBefore(leaf_cert);
ASN1_TIME *not_after = X509_get_notAfter(leaf_cert);
ASN1_TIME_to_string(not_before, nb_buf, sizeof(nb_buf));
ASN1_TIME_to_string(not_after, na_buf, sizeof(na_buf));
printf("    valid from: %s\n", nb_buf);
printf("    valid until: %s\n", na_buf);

// 计算并提示过期预警
time_t now = time(NULL);
time_t not_after_t = ASN1_TIME_to_time_t(not_after);
long days_left = (not_after_t - now) / (60 * 60 * 24);
if (days_left < 30 && days_left >= 0) {
    printf("    WARNING: The provided certificate is about to expire in %ld days !\n", days_left);
} else if (days_left < 0) {
    printf("    WARNING: The provided certificate has expired !\n");
}

注:ASN1_TIME_to_string和ASN1_TIME_to_time_t是OpenSSL 1.1.0及以上版本提供的函数,低版本需要手动解析ASN1_TIME结构。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 22:20:28