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

为何C程序无法验证下载的Google证书?

证书加载失败问题排查与解决

环境与操作背景

在Linux环境下通过mingw交叉编译Windows程序,使用wine执行,先通过以下命令下载Google证书:

mkdir -p ./release/certificates
echo | openssl s_client -servername google.com -connect google.com:443| sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' > ./release/certificates/google.crt

编写C程序尝试验证该证书,核心验证逻辑在InitCTX函数中:

char* path = malloc(MAX_PATH);
memset(path, 0, MAX_PATH);
GetModuleFileName(0, path, MAX_PATH);
PathRemoveFileSpec(path);
sprintf(path,"%s\\%s",path,"certificates");
printf("Cert path %s\n",path);
int value = SSL_CTX_load_verify_locations(ctx,"google.crt",path);
if(value == 0) {
    printf("Certificate error\n");
    exit(1);
}

完整程序代码如下:

#define _WINSOCK_DEPRECATED_NO_WARNINGS
#define _CRT_SECURE_NO_WARNINGS

#include <winsock2.h>
#include <ws2tcpip.h>
#include <shlwapi.h>

#include <stdio.h>

#include <openssl/ssl.h>
#include <openssl/err.h>


// Need to link with Ws2_32.lib, Mswsock.lib, and Advapi32.lib
#pragma comment (lib, "Ws2_32.lib")
//#pragma comment (lib, "Mswsock.lib")
//#pragma comment (lib, "AdvApi32.lib")


#define DEFAULT_BUFLEN 512
#define FAIL    -1

#define WIN32_LEAN_AND_MEAN

SOCKET OpenConnection(char* hostname, char* port)
{
    WSADATA wsaData;
    SOCKET ConnectSocket = INVALID_SOCKET;
    struct addrinfo* result = NULL;
    struct addrinfo* ptr = NULL;
    struct addrinfo hints;
    char recvbuf[DEFAULT_BUFLEN];
    int iResult;
    int recvbuflen = DEFAULT_BUFLEN;

    printf("\nInitializing Winsock");
    // Initialize Winsock
    iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
    if (iResult != 0) {
        printf("WSAStartup failed with error: %d\n", iResult);
        return 1;
    }

    ZeroMemory(&hints, sizeof(hints));
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_protocol = IPPROTO_TCP;

    // Resolve the server address and port
    iResult = getaddrinfo(hostname, port, &hints, &result);
    if (iResult != 0) {
        printf("getaddrinfo failed with error: %d\n", iResult);
        WSACleanup();
        return 1;
    }

    for(ptr=result; ptr != NULL ;ptr=ptr->ai_next) {

        // Create a SOCKET for connecting to server
        ConnectSocket = socket(ptr->ai_family, ptr->ai_socktype, 
            ptr->ai_protocol);
        if (ConnectSocket == INVALID_SOCKET) {
            printf("socket failed with error: %ld\n", WSAGetLastError());
            WSACleanup();
            return 1;
        }

        // Connect to server.
        iResult = connect( ConnectSocket, ptr->ai_addr, (int)ptr->ai_addrlen);
        if (iResult == SOCKET_ERROR) {
            closesocket(ConnectSocket);
            ConnectSocket = INVALID_SOCKET;
            continue;
        }
        break;
    }

    return ConnectSocket;
}


SSL_CTX* InitCTX(void)
{
    OpenSSL_add_all_algorithms(); 
    SSL_load_error_strings();
    const SSL_METHOD* method = SSLv23_method();
    SSL_CTX* ctx = SSL_CTX_new(method);
    SSL_CTX_set_options(ctx, SSL_OP_ALL | SSL_OP_NO_TLSv1_1 | SSL_OP_NO_TLSv1);
    
    if (ctx == NULL)
    {
        ERR_print_errors_fp(stderr);
        abort();
    }
    
    char* path = malloc(MAX_PATH);
    memset(path, 0, MAX_PATH);
    GetModuleFileName(0, path, MAX_PATH);
    PathRemoveFileSpec(path);
    sprintf(path,"%s\\%s",path,"certificates");
    printf("Cert path %s\n",path);
    int value = SSL_CTX_load_verify_locations(ctx,"google.crt",path);
    if(value == 0) {
        printf("Certificate error\n");
        exit(1);
    }

    return ctx;
}


void ShowCerts(SSL* ssl)
{
    X509* cert;
    char* line;
    cert = SSL_get_peer_certificate(ssl); /* get the server's certificate */
    if (cert != NULL)
    {
        printf("Server certificates:\n");
        line = X509_NAME_oneline(X509_get_subject_name(cert), 0, 0);
        printf("Subject: %s\n", line);
        //free(line);       /* free the malloc'ed string */
        line = X509_NAME_oneline(X509_get_issuer_name(cert), 0, 0);
        printf("Issuer: %s\n", line);
        //free(line);       /* free the malloc'ed string */
        X509_free(cert);     /* free the malloc'ed certificate copy */
    } else {
        printf("Info: No client certificates configured.\n");
    } 
}

void releaseSocket( SSL_CTX* ctx, int server)
{
    /* close socket */
    closesocket(server);   
    /* release context */
    SSL_CTX_free(ctx);        
    putchar('\n');
}

int main(int argc, char* argv[])
{
    printf("Initializing Connection");
    char buf[1024];
    
    SSL_library_init();
    char* hostname = "google.com";
    char* portnum = "443";

    SSL_CTX* ctx = InitCTX();
    int server = OpenConnection(hostname, portnum);
    SSL* ssl = SSL_new(ctx);      /* create new SSL connection state */
    SSL_set_fd(ssl, server);    /* attach the socket descriptor */


    if (SSL_connect(ssl) == FAIL) {
        ERR_print_errors_fp(stderr);
    } else {
        const char* cpRequestMessage = "GET / HTTP/1.1\r\nHost: www.google.com\r\nUser-Agent: curl/7.54.0\r\nConnection: close\r\nAccept: */*\r\n\r\n";

        printf("\n\nConnected with %s encryption\n", SSL_get_cipher(ssl));
       
        /* get any certs */
        ShowCerts(ssl);   
        /* encrypt & send message */
        printf("REQUEST:\n\n%s\n",cpRequestMessage);
        SSL_write(ssl, cpRequestMessage, strlen(cpRequestMessage));  

        /* get reply & decrypt */
        int bytes = SSL_read(ssl, buf, sizeof(buf));
        int error = SSL_get_error(ssl,bytes);
        switch (error)
        {
            case SSL_ERROR_SSL:
                puts("SSL ERROR SSL");
                releaseSocket(ctx,server);
                return 1;
            case SSL_ERROR_SYSCALL:
                puts("SSL ERROR SYSCALL");
                releaseSocket(ctx,server);
                return 1;
            case SSL_ERROR_WANT_ASYNC_JOB:
                puts("SSL ERROR WANT ASYNC_LOOKUP");
                releaseSocket(ctx,server);
                return 1;
            case SSL_ERROR_WANT_ASYNC:
                puts("SSL ERROR WANT X509_LOOKUP");
                releaseSocket(ctx,server);
                return 1;
            case SSL_ERROR_WANT_X509_LOOKUP:
                puts("SSL ERROR WANT X509_LOOKUP");
                releaseSocket(ctx,server);
                return 1;
            case SSL_ERROR_WANT_WRITE:
                puts("SSL ERROR WANT WRITE");
                releaseSocket(ctx,server);
                return 1;
            case SSL_ERROR_WANT_READ:
                puts("SSL ERROR WANT READ");
                releaseSocket(ctx,server);
                return 1;
            case SSL_ERROR_ZERO_RETURN:
                puts("SSL ERROR SSL_ERROR_ZERO_RETURN");
                releaseSocket(ctx,server);
                return 1;
            case SSL_ERROR_NONE:
            default:
                break;
        }
        puts("RESPONSE\n");
        for(int i=0;i<bytes;i++){
            putchar(buf[i]);
        }
        
        /* release connection state */
        SSL_free(ssl);       
    }

    releaseSocket(ctx,server);   
    putchar('\n');
    return 0;
}

问题现象

运行程序后输出如下错误:

Initializing ConnectionCert path Z:\home\pcmagas\job\Kwdikas\my_win_app\release\certificates
Certificate error

已通过wine cmd确认google.crt存在于上述路径中,但程序仍无法加载证书。

问题原因分析

  1. 路径参数使用错误:SSL_CTX_load_verify_locations函数的第一个参数是单个证书文件的路径(相对路径基于程序当前工作目录),第二个参数是存放CA证书的目录(需经过c_rehash处理)。当前代码中,第一个参数传入相对路径"google.crt",但程序工作目录不一定是certificates目录,导致无法找到文件;同时第二个参数传入目录但未做哈希处理,OpenSSL无法识别目录中的证书。

  2. Wine路径映射与相对路径冲突:Wine下的Z:盘映射Linux目录,但程序中的相对路径"google.crt"会指向Wine的当前工作目录(而非Z:盘的certificates目录),导致文件查找失败。

  3. 证书目录未哈希:如果通过目录加载证书,OpenSSL要求目录内的证书文件名是证书的哈希值(通过c_rehash生成),否则无法遍历识别证书。

解决方法

方法一:使用证书绝对路径加载

修改InitCTX函数中的证书加载逻辑,构造证书的完整绝对路径,直接指定给SSL_CTX_load_verify_locations:

char* path = malloc(MAX_PATH);
memset(path, 0, MAX_PATH);
GetModuleFileName(0, path, MAX_PATH);
PathRemoveFileSpec(path);
// 构造证书文件的绝对路径
char cert_file[MAX_PATH];
sprintf(cert_file, "%s\\certificates\\google.crt", path);
printf("Cert file path %s\n", cert_file);
int value = SSL_CTX_load_verify_locations(ctx, cert_file, NULL);
if(value == 0) {
    printf("Certificate error\n");
    exit(1);
}

方法二:正确配置证书目录并执行哈希处理

如果需要通过目录加载多个证书:

  1. 在Linux下对证书目录执行c_rehash(确保mingw的OpenSSL工具链已安装):
cd ./release/certificates
c_rehash .
  1. 修改代码,仅传入目录路径,将证书文件参数设为NULL:
int value = SSL_CTX_load_verify_locations(ctx, NULL, path);

方法三:切换程序工作目录

在加载证书前,将程序工作目录切换到证书所在目录:

char* path = malloc(MAX_PATH);
memset(path, 0, MAX_PATH);
GetModuleFileName(0, path, MAX_PATH);
PathRemoveFileSpec(path);
sprintf(path,"%s\\%s",path,"certificates");
printf("Cert path %s\n",path);
// 切换工作目录
SetCurrentDirectory(path);
int value = SSL_CTX_load_verify_locations(ctx, "google.crt", NULL);
if(value == 0) {
    printf("Certificate error\n");
    exit(1);
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.25 20:20:02