添加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
相关产品推荐
相关产品推荐

