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

OCSP服务器证书吊销校验:OpenSSL C API选型与mTLS代码修改

双向TLS客户端添加OCSP证书吊销校验方案

问题描述

尝试通过OCSP校验服务器的吊销证书,但无法找到合适的OCSP C API实现。能否帮忙建议如何修改现有的双向TLS(mTLS)代码,以实现基于OCSP的服务器吊销证书校验?

实现方案

基于OpenSSL原生OCSP API,可在客户端完成SSL握手后,主动向CA的OCSP服务器查询服务器证书的吊销状态。以下是具体修改步骤和代码:

1. 添加必要头文件

在客户端代码顶部补充OCSP相关依赖头文件:

#include <openssl/ocsp.h>
#include <openssl/x509v3.h>

2. 实现OCSP校验核心函数

添加独立函数负责提取服务器证书、构造OCSP请求、发送查询并验证结果:

static int verify_server_cert_via_ocsp(SSL *ssl) {
    OCSP_REQ *req = NULL;
    OCSP_RESP *resp = NULL;
    BIO *ocsp_bio = NULL;
    X509 *server_cert = NULL;
    X509 *issuer_cert = NULL;
    STACK_OF(X509) *chain = NULL;
    OCSP_CERTID *cid = NULL;
    int rc = -1;
    int resp_status;
    OCSP_SINGLERESP *single_resp = NULL;
    int cert_status;

    // 获取服务器证书
    server_cert = SSL_get_peer_certificate(ssl);
    if (!server_cert) {
        fprintf(stderr, "无法获取服务器证书\n");
        goto cleanup;
    }

    // 从证书链中提取颁发者CA证书
    chain = SSL_get_peer_cert_chain(ssl);
    if (!chain || sk_X509_num(chain) < 1) {
        fprintf(stderr, "无法获取服务器证书链\n");
        goto cleanup;
    }
    issuer_cert = sk_X509_value(chain, 0);

    // 构造OCSP查询所需的证书ID
    cid = OCSP_cert_to_id(EVP_sha1(), server_cert, issuer_cert);
    if (!cid) {
        fprintf(stderr, "无法构造OCSP证书ID\n");
        goto cleanup;
    }

    // 创建OCSP请求并绑定证书ID
    req = OCSP_REQUEST_new();
    if (!req || !OCSP_request_add0_id(req, cid)) {
        fprintf(stderr, "无法创建OCSP请求\n");
        goto cleanup;
    }
    cid = NULL; // 所有权转移至请求对象,无需单独释放

    // 从服务器证书AIA扩展中提取OCSP服务器地址
    char *ocsp_url = NULL;
    STACK_OF(CONF_VALUE) *ai = X509_get_ext_d2i(server_cert, NID_info_access, NULL, NULL);
    if (ai) {
        CONF_VALUE *val;
        int i;
        for (i = 0; i < sk_CONF_VALUE_num(ai); i++) {
            val = sk_CONF_VALUE_value(ai, i);
            if (!strcmp(val->name, "OCSP") && val->value) {
                ocsp_url = val->value;
                break;
            }
        }
        sk_CONF_VALUE_pop_free(ai, CONF_VALUE_free);
    }
    if (!ocsp_url) {
        fprintf(stderr, "服务器证书未包含OCSP服务器地址\n");
        goto cleanup;
    }

    // 建立与OCSP服务器的连接
    ocsp_bio = BIO_new_connect(ocsp_url);
    if (!ocsp_bio || BIO_do_connect(ocsp_bio) <= 0) {
        fprintf(stderr, "无法连接到OCSP服务器: %s\n", ocsp_url);
        goto cleanup;
    }

    // 发送OCSP请求并接收响应
    resp = OCSP_sendreq_bio(ocsp_bio, ocsp_url, req, 10, NULL);
    if (!resp) {
        fprintf(stderr, "OCSP请求发送失败\n");
        goto cleanup;
    }

    // 验证OCSP响应全局状态
    resp_status = OCSP_response_status(resp);
    if (resp_status != OCSP_RESPONSE_STATUS_SUCCESSFUL) {
        fprintf(stderr, "OCSP响应状态错误: %d\n", resp_status);
        goto cleanup;
    }

    // 提取单证书响应结果(仅处理单证书场景)
    single_resp = OCSP_response_get1_resp(resp);
    if (!single_resp) {
        fprintf(stderr, "无法解析OCSP响应\n");
        goto cleanup;
    }

    // 验证响应有效性与证书ID匹配性
    if (!OCSP_check_validity(single_resp, NULL, NULL, 0, 0)) {
        fprintf(stderr, "OCSP响应有效性验证失败\n");
        goto cleanup;
    }
    if (OCSP_id_cmp(OCSP_SINGLERESP_get0_id(single_resp), OCSP_request_get0_id(req, 0)) != 0) {
        fprintf(stderr, "OCSP响应证书ID不匹配\n");
        goto cleanup;
    }

    // 判断证书吊销状态
    cert_status = OCSP_SINGLERESP_get0_status(single_resp);
    switch (cert_status) {
        case V_OCSP_CERTSTATUS_GOOD:
            printf("服务器证书未被吊销\n");
            rc = 0;
            break;
        case V_OCSP_CERTSTATUS_REVOKED:
            fprintf(stderr, "服务器证书已被吊销\n");
            rc = -1;
            break;
        case V_OCSP_CERTSTATUS_UNKNOWN:
            fprintf(stderr, "无法确定服务器证书状态\n");
            rc = -1;
            break;
        default:
            fprintf(stderr, "未知OCSP证书状态: %d\n", cert_status);
            rc = -1;
            break;
    }

cleanup:
    // 资源清理
    if (req) OCSP_REQUEST_free(req);
    if (resp) OCSP_RESPONSE_free(resp);
    if (ocsp_bio) BIO_free_all(ocsp_bio);
    if (server_cert) X509_free(server_cert);
    if (cid) OCSP_CERTID_free(cid);
    if (single_resp) OCSP_SINGLERESP_free(single_resp);
    return rc;
}

3. 修改客户端主逻辑

在client函数的SSL握手验证通过后,调用OCSP校验函数:

// 原代码:验证SSL握手结果
if (SSL_get_verify_result(ssl) != X509_V_OK) {
    fprintf(stderr, "握手验证失败\n");
    goto fail2;
}

// 添加OCSP吊销校验逻辑
if (verify_server_cert_via_ocsp(ssl) != 0) {
    fprintf(stderr, "OCSP证书吊销校验失败\n");
    goto fail2;
}

4. 优化SSL上下文配置(可选)

将get_client_context中的过时SSL方法替换为安全的TLS方法:

// 替换原SSLv23_client_method()
if (!(ctx = SSL_CTX_new(TLS_client_method()))) {
    fprintf(stderr, "无法创建客户端上下文\n");
    return NULL;
}

编译说明

编译时需链接OpenSSL的OCSP库,示例命令:

gcc client.c -o client -lssl -lcrypto -locsp

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.04 05:37:03