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

OpenSSL服务器如何临时兼容使用过期/旧CA证书的客户端连接?

问题分析与解决方案

你的当前方案不可行,核心原因是旧CA未被加入服务器的信任存储,导致客户端用旧CA签发的证书连接时,OpenSSL在预验证阶段就直接失败,根本不会触发verify_callback。具体问题点:

  • SSL_CTX_load_verify_locations仅加载了新CA,旧CA不在服务器的信任链中,客户端证书的签名验证在进入回调前就被拒绝。
  • SSL_CTX_set_client_CA_list只是向客户端告知服务器接受哪些CA的证书(用于客户端选择证书),并非将旧CA加入信任存储,不影响验证逻辑。

正确实现步骤

1. 将旧CA加入服务器信任存储

需要同时把新CA和旧CA都加入服务器的证书信任库,让OpenSSL在预验证阶段能检查客户端证书是否由新旧CA之一签发:

// 加载新CA到信任存储
SSL_CTX_load_verify_locations(ctx, "ca_new.crt", NULL);

// 加载旧CA并添加到信任存储
X509_STORE *store = SSL_CTX_get_cert_store(ctx);
FILE *old_ca_file = fopen("ca_old.crt", "r");
if (old_ca_file) {
    X509 *old_ca = PEM_read_X509(old_ca_file, NULL, NULL, NULL);
    fclose(old_ca_file);
    if (old_ca) {
        X509_STORE_add_cert(store, old_ca);
        X509_free(old_ca);
    }
}

2. 调整verify_callback实现

在回调中区分客户端证书是旧CA还是新CA签发的,同时处理旧CA过期的情况:

// 自定义索引,用于标记旧客户端连接(需提前用SSL_CTX_set_ex_data_index注册)
static int old_client_index = -1;

int verify_callback(int pre_verify_ok, X509_STORE_CTX *ctx) {
    SSL *ssl = X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx());
    
    if (pre_verify_ok) {
        // 预验证通过,判断签发CA
        X509 *issuer_cert = X509_STORE_CTX_get0_issuer(ctx);
        FILE *old_ca_file = fopen("ca_old.crt", "r");
        if (old_ca_file) {
            X509 *old_ca = PEM_read_X509(old_ca_file, NULL, NULL, NULL);
            fclose(old_ca_file);
            if (old_ca) {
                // 对比签发者的哈希值,判断是否为旧CA
                unsigned char old_hash[SHA256_DIGEST_LENGTH], issuer_hash[SHA256_DIGEST_LENGTH];
                X509_digest(old_ca, EVP_sha256(), old_hash, NULL);
                X509_digest(issuer_cert, EVP_sha256(), issuer_hash, NULL);
                
                if (memcmp(old_hash, issuer_hash, SHA256_DIGEST_LENGTH) == 0) {
                    // 标记为旧客户端连接
                    SSL_set_ex_data(ssl, old_client_index, (void*)1);
                }
                X509_free(old_ca);
            }
        }
        return 1;
    } else {
        // 预验证失败,检查是否为旧CA过期导致
        int err = X509_STORE_CTX_get_error(ctx);
        if (err == X509_V_ERR_CERT_HAS_EXPIRED) {
            X509 *client_cert = X509_STORE_CTX_get_current_cert(ctx);
            FILE *old_ca_file = fopen("ca_old.crt", "r");
            if (old_ca_file) {
                X509 *old_ca = PEM_read_X509(old_ca_file, NULL, NULL, NULL);
                fclose(old_ca_file);
                if (old_ca) {
                    // 验证客户端证书签名是否能被旧CA通过
                    if (X509_verify(client_cert, X509_get_pubkey(old_ca)) == 1) {
                        SSL_set_ex_data(ssl, old_client_index, (void*)1);
                        X509_free(old_ca);
                        return 1;
                    }
                    X509_free(old_ca);
                }
            }
        }
        // 其他验证失败情况直接拒绝
        return 0;
    }
}

// 初始化阶段注册自定义索引
old_client_index = SSL_CTX_set_ex_data_index(ctx, NULL);

3. 业务逻辑中限制旧客户端操作

在连接处理流程中,根据标记判断是否仅允许证书更新操作:

void handle_client_connection(SSL *ssl) {
    int is_old_client = (int)SSL_get_ex_data(ssl, old_client_index);
    if (is_old_client) {
        // 仅返回证书更新指令,拒绝其他业务请求
        send(SSL_get_fd(ssl), "UPDATE_CERT", strlen("UPDATE_CERT"), 0);
        SSL_shutdown(ssl);
        SSL_free(ssl);
        return;
    }
    // 正常处理新客户端的业务请求
    handle_normal_business(ssl);
}

额外注意事项

  • 若旧客户端需要验证服务器证书,你当前用新CA签发的server_new.crt会被旧客户端拒绝(因为旧客户端信任旧CA)。这种情况下,服务器需要在证书链中同时包含旧CA和新CA,或者临时启用双证书配置(同时支持旧CA签的服务器证书和新CA签的服务器证书)。
  • 旧CA的有效期过期后,即使签名验证通过,pre_verify_ok会因证书过期变为0,此时需要在回调中单独处理过期错误,手动验证签名有效性。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.30 03:25:16