如何通过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
相关产品推荐
相关产品推荐

