为何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存在于上述路径中,但程序仍无法加载证书。
问题原因分析
路径参数使用错误:
SSL_CTX_load_verify_locations函数的第一个参数是单个证书文件的路径(相对路径基于程序当前工作目录),第二个参数是存放CA证书的目录(需经过c_rehash处理)。当前代码中,第一个参数传入相对路径"google.crt",但程序工作目录不一定是certificates目录,导致无法找到文件;同时第二个参数传入目录但未做哈希处理,OpenSSL无法识别目录中的证书。Wine路径映射与相对路径冲突:Wine下的Z:盘映射Linux目录,但程序中的相对路径
"google.crt"会指向Wine的当前工作目录(而非Z:盘的certificates目录),导致文件查找失败。证书目录未哈希:如果通过目录加载证书,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); }
方法二:正确配置证书目录并执行哈希处理
如果需要通过目录加载多个证书:
- 在Linux下对证书目录执行
c_rehash(确保mingw的OpenSSL工具链已安装):
cd ./release/certificates c_rehash .
- 修改代码,仅传入目录路径,将证书文件参数设为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
相关产品推荐
相关产品推荐

