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

如何通过OpenSSL从客户端验证服务器证书是否吊销(C代码实现)

用OpenSSL C代码验证带OCSP Stapling的吊销证书

针对你描述的场景(服务器证书已吊销且通过OCSP Stapling返回了响应,证书仅包含OCSP服务器URL),客户端可以通过以下步骤完成验证:

核心逻辑

服务器通过OCSP Stapling在TLS握手阶段主动发送OCSP响应,客户端无需额外向OCSP服务器发起请求,直接使用这个响应验证证书吊销状态即可。整个流程分为获取Stapled响应和验证响应有效性两部分。

具体实现步骤及代码示例

1. 配置SSL上下文

首先初始化并配置SSL上下文,确保启用证书验证,并准备好信任的CA证书(用于验证OCSP响应的签名):

#include <openssl/ssl.h>
#include <openssl/ocsp.h>
#include <openssl/x509.h>

// 初始化OpenSSL(1.1.0及以上版本可省略)
OPENSSL_init_ssl(OPENSSL_INIT_LOAD_SSL_STRINGS, NULL);

// 创建SSL上下文
SSL_CTX *ctx = SSL_CTX_new(TLS_client_method());
if (!ctx) {
    // 错误处理
    return -1;
}

// 加载信任的CA证书(用于验证服务器证书和OCSP响应)
if (!SSL_CTX_load_verify_locations(ctx, "ca-cert.pem", NULL)) {
    // 错误处理
    SSL_CTX_free(ctx);
    return -1;
}

// 启用严格的证书验证模式
SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL);

2. 完成TLS握手并获取Stapled OCSP响应

建立连接并完成握手后,调用SSL_get_tlsext_status_ocsp_resp获取服务器发送的OCSP响应:

// 创建SSL对象并绑定socket(此处省略socket连接代码)
SSL *ssl = SSL_new(ctx);
SSL_set_fd(ssl, sockfd);

// 执行TLS握手
if (SSL_connect(ssl) != 1) {
    // 错误处理
    SSL_free(ssl);
    SSL_CTX_free(ctx);
    close(sockfd);
    return -1;
}

// 获取Stapled OCSP响应
unsigned char *ocsp_resp_data = NULL;
int ocsp_resp_len = SSL_get_tlsext_status_ocsp_resp(ssl, &ocsp_resp_data);
if (ocsp_resp_len <= 0) {
    // 服务器未发送Stapling响应,可选择 fallback 到主动请求OCSP服务器
    // 此处根据场景需求处理,比如直接判定验证失败或发起OCSP请求
    SSL_free(ssl);
    SSL_CTX_free(ctx);
    close(sockfd);
    return -1;
}

3. 解析并验证OCSP响应

使用OpenSSL的OCSP API解析响应,验证其签名有效性、与服务器证书的匹配性,以及吊销状态:

// 解析OCSP响应
OCSP_RESPONSE *ocsp_resp = d2i_OCSP_RESPONSE(NULL, &ocsp_resp_data, ocsp_resp_len);
if (!ocsp_resp) {
    // 错误处理
    OPENSSL_free(ocsp_resp_data);
    SSL_free(ssl);
    SSL_CTX_free(ctx);
    close(sockfd);
    return -1;
}

// 检查OCSP响应基本状态
int resp_status = OCSP_response_status(ocsp_resp);
if (resp_status != OCSP_RESPONSE_STATUS_SUCCESSFUL) {
    // 响应状态异常,验证失败
    OCSP_RESPONSE_free(ocsp_resp);
    OPENSSL_free(ocsp_resp_data);
    SSL_free(ssl);
    SSL_CTX_free(ctx);
    close(sockfd);
    return -1;
}

// 获取OCSP响应体
OCSP_BASICRESP *basic_resp = OCSP_response_get1_basic(ocsp_resp);
if (!basic_resp) {
    // 错误处理
    OCSP_RESPONSE_free(ocsp_resp);
    OPENSSL_free(ocsp_resp_data);
    SSL_free(ssl);
    SSL_CTX_free(ctx);
    close(sockfd);
    return -1;
}

// 获取服务器证书及其颁发者证书
X509 *server_cert = SSL_get_peer_certificate(ssl);
X509 *issuer_cert = NULL;
// 从信任CA池中查找服务器证书的颁发者
STACK_OF(X509) *ca_certs = SSL_CTX_get_cert_store(ctx)->certs;
for (int i = 0; i < sk_X509_num(ca_certs); i++) {
    X509 *ca = sk_X509_value(ca_certs, i);
    if (X509_check_issued(ca, server_cert) == X509_V_OK) {
        issuer_cert = X509_dup(ca);
        break;
    }
}
if (!issuer_cert) {
    // 找不到颁发者证书,验证失败
    OCSP_BASICRESP_free(basic_resp);
    OCSP_RESPONSE_free(ocsp_resp);
    OPENSSL_free(ocsp_resp_data);
    X509_free(server_cert);
    SSL_free(ssl);
    SSL_CTX_free(ctx);
    close(sockfd);
    return -1;
}

// 验证OCSP响应的签名有效性
OCSP_VERIFY_CTX verify_ctx;
OCSP_VERIFY_CTX_init(&verify_ctx);
OCSP_VERIFY_CTX_set_certs(&verify_ctx, issuer_cert, server_cert);
OCSP_VERIFY_CTX_set_flags(&verify_ctx, OCSP_TRUSTOTHER);

int verify_result = OCSP_basic_verify(basic_resp, NULL, &verify_ctx, 0);
if (verify_result != 1) {
    // OCSP响应签名验证失败
    OCSP_VERIFY_CTX_cleanup(&verify_ctx);
    X509_free(issuer_cert);
    OCSP_BASICRESP_free(basic_resp);
    OCSP_RESPONSE_free(ocsp_resp);
    OPENSSL_free(ocsp_resp_data);
    X509_free(server_cert);
    SSL_free(ssl);
    SSL_CTX_free(ctx);
    close(sockfd);
    return -1;
}

// 检查证书吊销状态
OCSP_SINGLERESP *single_resp = OCSP_basic_get0_resp(basic_resp, 0);
if (!single_resp) {
    // 错误处理
    OCSP_VERIFY_CTX_cleanup(&verify_ctx);
    X509_free(issuer_cert);
    OCSP_BASICRESP_free(basic_resp);
    OCSP_RESPONSE_free(ocsp_resp);
    OPENSSL_free(ocsp_resp_data);
    X509_free(server_cert);
    SSL_free(ssl);
    SSL_CTX_free(ctx);
    close(sockfd);
    return -1;
}

int cert_status = OCSP_single_get0_status(single_resp, NULL, NULL, NULL, NULL);
if (cert_status == V_OCSP_CERTSTATUS_REVOKED) {
    // 证书已吊销,符合测试场景预期
    printf("服务器证书已吊销\n");
} else if (cert_status == V_OCSP_CERTSTATUS_GOOD) {
    printf("服务器证书有效\n");
} else {
    printf("OCSP无法确定证书状态\n");
}

// 资源清理
OCSP_VERIFY_CTX_cleanup(&verify_ctx);
X509_free(issuer_cert);
OCSP_BASICRESP_free(basic_resp);
OCSP_RESPONSE_free(ocsp_resp);
OPENSSL_free(ocsp_resp_data);
X509_free(server_cert);
SSL_free(ssl);
SSL_CTX_free(ctx);
close(sockfd);

关键注意事项

  • 信任CA证书:必须确保客户端加载了服务器证书的颁发CA证书,否则无法验证OCSP响应的签名合法性。
  • Fallback处理:如果服务器未返回Stapling响应,可根据证书中的OCSP URL,使用OCSP_sendreq_new等API主动发起OCSP请求验证吊销状态。
  • 错误处理:代码中需完善各类错误分支的处理,避免资源泄漏。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.11 01:52:49