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

添加SSL_CTX_set_verify后服务器无法获取客户端证书,连接异常

问题分析与解决方案

你遇到的问题核心是开启SSL_VERIFY_PEER后,服务器要求客户端提供合法证书,但你的两端配置都没满足双向认证的要求,导致握手失败,且无法获取客户端证书。下面一步步拆解问题并给出修复方案:

1. 核心问题点

当你设置SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, 0);时,服务器会强制要求客户端提供证书并验证其有效性,但你的代码存在以下关键缺失:

  • 服务器没有明确要求客户端发送证书(缺少验证深度配置),也未配置信任的CA来验证客户端证书的合法性
  • 客户端错误加载了服务器的证书/密钥,完全没有配置自己的客户端证书
  • 你忽略了SSL_accept和SSL_connect的返回值,无法定位握手失败的具体原因

2. 具体修复步骤

第一步:生成客户端证书

首先需要用CA(比如你现有Servercert.sh脚本生成的CA)签发客户端证书,得到client.cert(客户端公钥证书)和client.key(客户端私钥),确保客户端证书由服务器信任的CA签发。

第二步:修复服务器端代码

修改socketsInListenMode函数,补充双向认证的必要配置:

SSL *socketsInListenMode(SSL_CTX * &ctx,int portno) {
    ctx = InitCTX(); /* initialize SSL */
    LoadCertificates(ctx, "server.cert","server.key");
    
    // 开启客户端证书验证,强制要求客户端提供证书
    SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL);
    // 设置验证深度,至少为1(验证客户端证书及其签发CA)
    SSL_CTX_set_verify_depth(ctx, 1);
    // 加载信任的CA证书,用于验证客户端证书合法性
    if (SSL_CTX_load_verify_locations(ctx, "ca.cert", NULL) != 1) {
        ERR_print_errors_fp(stderr);
        abort();
    }

    sockaddr_in serv_addr={},cli_addr={};
    int sockListenfd = socket(AF_INET, SOCK_STREAM, 0);
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = INADDR_ANY;
    serv_addr.sin_port = htons(portno);
    int bindret=bind(sockListenfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr));
    cout<<bindret<<endl;
    if(bindret < 0) return 0;
    listen(sockListenfd,10);
    socklen_t s=0;
    int newsockfd = accept(sockListenfd, (struct sockaddr *)&cli_addr, &s);
    
    SSL *ssl = SSL_new(ctx); /* get new SSL state with context */
    SSL_set_fd(ssl, newsockfd); /* set connection socket to SSL state */
    
    // 检查SSL握手结果,不能忽略错误
    int accept_ret = SSL_accept(ssl);
    if (accept_ret <= 0) {
        ERR_print_errors_fp(stderr);
        close(newsockfd);
        SSL_free(ssl);
        close(sockListenfd);
        return NULL;
    }

    // 现在可以正常获取客户端证书了
    X509 *client_cert = SSL_get_peer_certificate(ssl);
    if (client_cert != NULL) {
        cout << "Received client certificate: " << endl;
        X509_NAME_print_ex_fp(stdout, X509_get_subject_name(client_cert), 0, XN_FLAG_ONELINE);
        cout << endl;
        X509_free(client_cert); // 记得释放证书资源
    } else {
        cerr << "No client certificate received, handshake failed!" << endl;
    }

    close( sockListenfd );
    sockListenfd = 0;
    return ssl;
}

第三步:修复客户端代码

客户端需要加载自己的证书/密钥,并信任服务器的CA:

SSL *secureConnect(SSL_CTX * &ctx,int portno, string strIP) {
    ctx = InitCTX();
    // 加载客户端自己的证书和密钥,不是服务器的!
    LoadCertificates(ctx, "client.cert","client.key");
    
    // 客户端加载信任的CA,用于验证服务器证书合法性
    if (SSL_CTX_load_verify_locations(ctx, "ca.cert", NULL) != 1) {
        ERR_print_errors_fp(stderr);
        abort();
    }
    // 客户端开启服务器证书验证
    SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL);

    SSL *ssl = SSL_new(ctx); /* create new SSL connection state */
    int sfd=SocketConnect(portno,strIP); //socketConnect
    if(-1 == sfd) return NULL;
    SSL_set_fd(ssl, sfd);
    
    // 检查SSL握手结果
    int connect_ret = SSL_connect(ssl);
    if (connect_ret <= 0) {
        ERR_print_errors_fp(stderr);
        close(sfd);
        SSL_free(ssl);
        return NULL;
    }

    return ssl;
}

3. 为什么移除SSL_VERIFY_PEER就正常?

当你移除这个配置时,服务器默认不要求客户端提供证书,也不验证客户端身份,所以SSL握手可以顺利完成,但此时SSL_get_peer_certificate(ssl)必然返回NULL——因为服务器根本没向客户端索要证书。

4. 额外注意事项

  • 确保所有证书文件(server.cert、server.key、client.cert、client.key、ca.cert)的路径正确,私钥文件权限建议设为600(仅所有者可读)
  • 始终检查SSL相关函数的返回值,SSL_accept、SSL_connect、SSL_write、SSL_read的返回值都可能包含错误信息,通过ERR_print_errors_fp(stderr)可以打印具体错误,方便调试
  • 如果你不需要强制要求客户端提供证书,只是想可选获取客户端证书,可以把服务器端的验证模式改为SSL_VERIFY_PEER(去掉SSL_VERIFY_FAIL_IF_NO_PEER_CERT),这样客户端可以选择提供或不提供证书,服务器都能完成握手

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.29 08:46:17