1156 lines
39 KiB
C
1156 lines
39 KiB
C
/**
|
||
* @file tls_client.c
|
||
* @brief TLS 1.2/1.3 客户端实现
|
||
* @author Assistant
|
||
* @date 2024
|
||
* @version 1.0.0
|
||
*/
|
||
|
||
#include <stdio.h>
|
||
#include <stdlib.h>
|
||
#include <string.h>
|
||
#include <unistd.h>
|
||
#include <errno.h>
|
||
#include <sys/socket.h>
|
||
#include <netinet/in.h>
|
||
#include <netinet/tcp.h>
|
||
#include <arpa/inet.h>
|
||
#include <fcntl.h>
|
||
#include <openssl/ssl.h>
|
||
#include <openssl/err.h>
|
||
#include <openssl/x509.h>
|
||
#include <openssl/x509_vfy.h>
|
||
#include <time.h>
|
||
|
||
#define MAX_BUFFER_SIZE 4096
|
||
#define TEST_DATA_SIZE 1024
|
||
#define MAX_RETRIES 3
|
||
#define CONNECT_TIMEOUT 10
|
||
|
||
// 全局SSL上下文
|
||
SSL_CTX *ssl_ctx = NULL;
|
||
|
||
/**
|
||
* @brief 格式化时间戳为易读格式
|
||
*/
|
||
void print_timestamp(time_t timestamp) {
|
||
struct tm *tm_info;
|
||
char time_str[32];
|
||
|
||
tm_info = gmtime(×tamp);
|
||
strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M:%S UTC", tm_info);
|
||
printf("[%s]", time_str);
|
||
}
|
||
|
||
/**
|
||
* @brief SSL信息回调函数
|
||
*/
|
||
void ssl_info_callback(const SSL *ssl, int type, int val) {
|
||
const char *str;
|
||
static time_t last_time = 0;
|
||
time_t current_time;
|
||
|
||
// 检查SSL对象是否有效
|
||
if (!ssl) {
|
||
printf("SSL回调: SSL对象为空\n");
|
||
return;
|
||
}
|
||
|
||
current_time = time(NULL);
|
||
|
||
switch (type) {
|
||
case SSL_CB_LOOP:
|
||
str = SSL_state_string_long(ssl);
|
||
if (str) {
|
||
// 计算与上次状态的时间差
|
||
if (last_time > 0) {
|
||
double time_diff = difftime(current_time, last_time);
|
||
if (time_diff > 0.5) { // 只显示超过0.5秒的延迟
|
||
print_timestamp(current_time);
|
||
printf(" SSL状态: %s (延迟: %.1f秒)\n", str, time_diff);
|
||
} else {
|
||
print_timestamp(current_time);
|
||
printf(" SSL状态: %s\n", str);
|
||
}
|
||
} else {
|
||
print_timestamp(current_time);
|
||
printf(" SSL状态: %s\n", str);
|
||
}
|
||
last_time = current_time;
|
||
}
|
||
break;
|
||
case SSL_CB_ALERT:
|
||
str = SSL_alert_type_string_long(val);
|
||
if (str) {
|
||
print_timestamp(current_time);
|
||
printf(" SSL警告: %s\n", str);
|
||
}
|
||
break;
|
||
case SSL_CB_EXIT:
|
||
if (val == 0) {
|
||
str = SSL_state_string_long(ssl);
|
||
if (str) {
|
||
print_timestamp(current_time);
|
||
printf(" SSL退出: %s\n", str);
|
||
}
|
||
} else if (val < 0) {
|
||
str = SSL_state_string_long(ssl);
|
||
if (str) {
|
||
print_timestamp(current_time);
|
||
printf(" SSL错误: %s\n", str);
|
||
}
|
||
}
|
||
break;
|
||
case SSL_CB_HANDSHAKE_START:
|
||
print_timestamp(current_time);
|
||
printf(" SSL握手开始\n");
|
||
last_time = current_time;
|
||
break;
|
||
case SSL_CB_HANDSHAKE_DONE:
|
||
print_timestamp(current_time);
|
||
printf(" SSL握手完成\n");
|
||
break;
|
||
default:
|
||
// 过滤掉重复的4097类型回调
|
||
if (type != 4097) {
|
||
print_timestamp(current_time);
|
||
printf(" SSL回调: 类型=%d, 值=%d\n", type, val);
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @brief SSL消息回调函数
|
||
*/
|
||
void ssl_msg_callback(int write_p, int version, int content_type, const void *buf, size_t len, SSL *ssl, void *arg) {
|
||
const char *direction = write_p ? "发送" : "接收";
|
||
const char *content_name = "";
|
||
time_t current_time;
|
||
|
||
// 检查参数有效性
|
||
if (!buf || len == 0) {
|
||
return;
|
||
}
|
||
|
||
current_time = time(NULL);
|
||
|
||
switch (content_type) {
|
||
case SSL3_RT_CHANGE_CIPHER_SPEC:
|
||
content_name = "ChangeCipherSpec";
|
||
break;
|
||
case SSL3_RT_ALERT:
|
||
content_name = "Alert";
|
||
break;
|
||
case SSL3_RT_HANDSHAKE:
|
||
content_name = "Handshake";
|
||
break;
|
||
case SSL3_RT_APPLICATION_DATA:
|
||
content_name = "ApplicationData";
|
||
break;
|
||
default:
|
||
content_name = "Unknown";
|
||
break;
|
||
}
|
||
|
||
// 只显示重要的握手消息,过滤掉重复的Unknown消息
|
||
if (content_type == SSL3_RT_HANDSHAKE || content_type == SSL3_RT_CHANGE_CIPHER_SPEC) {
|
||
print_timestamp(current_time);
|
||
printf(" SSL消息: %s %s (版本: %d, 长度: %zu)\n", direction, content_name, version, len);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @brief 初始化OpenSSL库
|
||
* @param debug_level 调试级别 (0=无调试, 1=基本, 2=详细, 3=完整)
|
||
*/
|
||
void init_openssl(int debug_level) {
|
||
SSL_library_init();
|
||
SSL_load_error_strings();
|
||
OpenSSL_add_all_algorithms();
|
||
|
||
if (debug_level > 0) {
|
||
// 启用OpenSSL调试
|
||
if (debug_level >= 1) {
|
||
printf("启用OpenSSL基本调试\n");
|
||
}
|
||
if (debug_level >= 2) {
|
||
printf("启用OpenSSL详细调试\n");
|
||
}
|
||
if (debug_level >= 3) {
|
||
printf("启用OpenSSL完整调试\n");
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @brief 清理OpenSSL资源
|
||
*/
|
||
void cleanup_openssl() {
|
||
if (ssl_ctx) {
|
||
SSL_CTX_free(ssl_ctx);
|
||
}
|
||
EVP_cleanup();
|
||
ERR_free_strings();
|
||
}
|
||
|
||
/**
|
||
* @brief 创建SSL上下文
|
||
* @param cert_file 客户端证书文件路径
|
||
* @param key_file 客户端私钥文件路径
|
||
* @param ca_file CA证书文件路径
|
||
* @param verify_peer 是否验证服务器证书
|
||
* @param tls_version TLS版本 (1=TLS1.2, 2=TLS1.3, 0=自动)
|
||
* @return SSL_CTX* SSL上下文指针,失败返回NULL
|
||
*/
|
||
SSL_CTX* create_ssl_context(const char* cert_file, const char* key_file, const char* ca_file, int verify_peer, int tls_version, int debug_level) {
|
||
SSL_CTX *ctx;
|
||
|
||
// 创建SSL上下文,支持TLS 1.2和1.3
|
||
ctx = SSL_CTX_new(TLS_client_method());
|
||
if (!ctx) {
|
||
fprintf(stderr, "无法创建SSL上下文\n");
|
||
return NULL;
|
||
}
|
||
|
||
// 设置调试回调(如果启用调试)
|
||
if (debug_level >= 2) {
|
||
SSL_CTX_set_info_callback(ctx, ssl_info_callback);
|
||
}
|
||
if (debug_level >= 3) {
|
||
SSL_CTX_set_msg_callback(ctx, ssl_msg_callback);
|
||
}
|
||
|
||
// 根据指定版本设置TLS版本
|
||
if (tls_version == 1) {
|
||
// 只使用TLS 1.2
|
||
if (SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION) != 1 ||
|
||
SSL_CTX_set_max_proto_version(ctx, TLS1_2_VERSION) != 1) {
|
||
fprintf(stderr, "设置TLS 1.2版本失败\n");
|
||
SSL_CTX_free(ctx);
|
||
return NULL;
|
||
}
|
||
printf("配置为TLS 1.2模式\n");
|
||
} else if (tls_version == 2) {
|
||
// 只使用TLS 1.3
|
||
if (SSL_CTX_set_min_proto_version(ctx, TLS1_3_VERSION) != 1 ||
|
||
SSL_CTX_set_max_proto_version(ctx, TLS1_3_VERSION) != 1) {
|
||
fprintf(stderr, "设置TLS 1.3版本失败\n");
|
||
SSL_CTX_free(ctx);
|
||
return NULL;
|
||
}
|
||
printf("配置为TLS 1.3模式\n");
|
||
} else {
|
||
// 自动模式:支持TLS 1.2和1.3
|
||
if (SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION) != 1) {
|
||
fprintf(stderr, "设置最小TLS版本失败\n");
|
||
SSL_CTX_free(ctx);
|
||
return NULL;
|
||
}
|
||
|
||
if (SSL_CTX_set_max_proto_version(ctx, TLS1_3_VERSION) != 1) {
|
||
fprintf(stderr, "设置最大TLS版本失败\n");
|
||
SSL_CTX_free(ctx);
|
||
return NULL;
|
||
}
|
||
printf("配置为TLS 1.2/1.3自动模式\n");
|
||
}
|
||
|
||
// 加载客户端证书
|
||
printf("加载客户端证书: %s\n", cert_file);
|
||
if (SSL_CTX_use_certificate_file(ctx, cert_file, SSL_FILETYPE_PEM) != 1) {
|
||
fprintf(stderr, "加载客户端证书失败: %s\n", cert_file);
|
||
ERR_print_errors_fp(stderr);
|
||
SSL_CTX_free(ctx);
|
||
return NULL;
|
||
}
|
||
printf("客户端证书加载成功\n");
|
||
|
||
// 加载客户端私钥
|
||
printf("加载客户端私钥: %s\n", key_file);
|
||
if (SSL_CTX_use_PrivateKey_file(ctx, key_file, SSL_FILETYPE_PEM) != 1) {
|
||
fprintf(stderr, "加载客户端私钥失败: %s\n", key_file);
|
||
ERR_print_errors_fp(stderr);
|
||
SSL_CTX_free(ctx);
|
||
return NULL;
|
||
}
|
||
printf("客户端私钥加载成功\n");
|
||
|
||
// 验证私钥与证书匹配
|
||
printf("验证私钥与证书匹配...\n");
|
||
if (SSL_CTX_check_private_key(ctx) != 1) {
|
||
fprintf(stderr, "私钥与证书不匹配\n");
|
||
ERR_print_errors_fp(stderr);
|
||
SSL_CTX_free(ctx);
|
||
return NULL;
|
||
}
|
||
printf("私钥与证书匹配验证成功\n");
|
||
|
||
// 加载CA证书用于验证服务器证书
|
||
if (ca_file && SSL_CTX_load_verify_locations(ctx, ca_file, NULL) != 1) {
|
||
fprintf(stderr, "加载CA证书失败: %s\n", ca_file);
|
||
SSL_CTX_free(ctx);
|
||
return NULL;
|
||
}
|
||
|
||
// 设置验证模式
|
||
if (verify_peer) {
|
||
SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL);
|
||
} else {
|
||
SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, NULL);
|
||
printf("警告: 已禁用服务器证书验证\n");
|
||
}
|
||
|
||
// 设置验证深度
|
||
SSL_CTX_set_verify_depth(ctx, 4);
|
||
|
||
return ctx;
|
||
}
|
||
|
||
/**
|
||
* @brief 创建TCP连接
|
||
* @param hostname 服务器主机名
|
||
* @param port 服务器端口
|
||
* @return int 套接字文件描述符,失败返回-1
|
||
*/
|
||
int create_tcp_connection(const char* hostname, int port) {
|
||
int sockfd;
|
||
struct sockaddr_in server_addr;
|
||
int opt = 1;
|
||
|
||
// 创建套接字
|
||
sockfd = socket(AF_INET, SOCK_STREAM, 0);
|
||
if (sockfd < 0) {
|
||
perror("创建套接字失败");
|
||
return -1;
|
||
}
|
||
|
||
// 设置套接字选项,避免RST包问题
|
||
if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) {
|
||
perror("设置SO_REUSEADDR失败");
|
||
close(sockfd);
|
||
return -1;
|
||
}
|
||
|
||
// 设置TCP_NODELAY选项,减少延迟
|
||
if (setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, &opt, sizeof(opt)) < 0) {
|
||
perror("设置TCP_NODELAY失败");
|
||
close(sockfd);
|
||
return -1;
|
||
}
|
||
|
||
// 设置服务器地址
|
||
memset(&server_addr, 0, sizeof(server_addr));
|
||
server_addr.sin_family = AF_INET;
|
||
server_addr.sin_port = htons(port);
|
||
|
||
// 解析主机名
|
||
if (inet_pton(AF_INET, hostname, &server_addr.sin_addr) <= 0) {
|
||
fprintf(stderr, "无效的IP地址: %s\n", hostname);
|
||
close(sockfd);
|
||
return -1;
|
||
}
|
||
|
||
// 连接到服务器
|
||
if (connect(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
|
||
perror("连接服务器失败");
|
||
close(sockfd);
|
||
return -1;
|
||
}
|
||
|
||
printf("成功连接到服务器 %s:%d\n", hostname, port);
|
||
return sockfd;
|
||
}
|
||
|
||
/**
|
||
* @brief 分析系统调用错误的详细信息
|
||
* @param ssl SSL连接对象
|
||
* @param ret SSL函数返回值
|
||
* @return void
|
||
*/
|
||
void analyze_syscall_error(SSL* ssl, int ret) {
|
||
int sockfd = SSL_get_fd(ssl);
|
||
int error = 0;
|
||
socklen_t len = sizeof(error);
|
||
|
||
// 获取套接字错误
|
||
if (sockfd > 0 && getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, &len) == 0) {
|
||
if (error != 0) {
|
||
fprintf(stderr, "套接字错误代码: %d (%s)\n", error, strerror(error));
|
||
|
||
// 详细分析套接字错误
|
||
switch (error) {
|
||
case ECONNREFUSED:
|
||
fprintf(stderr, " -> 连接被拒绝,服务器可能未运行或端口错误\n");
|
||
break;
|
||
case ETIMEDOUT:
|
||
fprintf(stderr, " -> 连接超时,网络可能有问题或服务器响应慢\n");
|
||
break;
|
||
case EHOSTUNREACH:
|
||
fprintf(stderr, " -> 主机不可达,网络路由问题\n");
|
||
break;
|
||
case ENETUNREACH:
|
||
fprintf(stderr, " -> 网络不可达,网络连接问题\n");
|
||
break;
|
||
case ECONNRESET:
|
||
fprintf(stderr, " -> 连接被重置,服务器主动断开连接\n");
|
||
break;
|
||
case EPIPE:
|
||
fprintf(stderr, " -> 管道破裂,连接意外断开\n");
|
||
break;
|
||
case EINTR:
|
||
fprintf(stderr, " -> 系统调用被中断\n");
|
||
break;
|
||
case EAGAIN:
|
||
fprintf(stderr, " -> 资源暂时不可用,可能需要重试\n");
|
||
break;
|
||
#ifdef EWOULDBLOCK
|
||
#if EAGAIN != EWOULDBLOCK
|
||
case EWOULDBLOCK:
|
||
fprintf(stderr, " -> 资源暂时不可用,可能需要重试\n");
|
||
break;
|
||
#endif
|
||
#endif
|
||
default:
|
||
fprintf(stderr, " -> 其他系统错误\n");
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 检查errno
|
||
if (errno != 0) {
|
||
fprintf(stderr, "系统errno: %d (%s)\n", errno, strerror(errno));
|
||
}
|
||
|
||
// 检查SSL内部错误队列
|
||
unsigned long ssl_err;
|
||
while ((ssl_err = ERR_get_error()) != 0) {
|
||
char err_buf[256];
|
||
ERR_error_string_n(ssl_err, err_buf, sizeof(err_buf));
|
||
fprintf(stderr, "SSL内部错误: %s\n", err_buf);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @brief 执行TLS握手(带重试机制)
|
||
* @param ssl SSL连接对象
|
||
* @param max_retries 最大重试次数
|
||
* @param base_delay 基础延迟时间(秒)
|
||
* @return int 成功返回1,失败返回0
|
||
*/
|
||
int perform_tls_handshake_with_retry(SSL* ssl, int max_retries, int base_delay) {
|
||
int ret;
|
||
time_t start_time, end_time, step_time;
|
||
double handshake_time, step_duration;
|
||
int retry_count = 0;
|
||
int sockfd = SSL_get_fd(ssl);
|
||
|
||
printf("开始TLS握手(最大重试次数: %d)...\n", max_retries);
|
||
start_time = time(NULL);
|
||
|
||
// 设置握手超时
|
||
if (sockfd > 0) {
|
||
struct timeval timeout;
|
||
timeout.tv_sec = 30; // 30秒超时
|
||
timeout.tv_usec = 0;
|
||
setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));
|
||
setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout));
|
||
printf("设置握手超时: 30秒\n");
|
||
}
|
||
|
||
while (retry_count <= max_retries) {
|
||
if (retry_count > 0) {
|
||
int delay = base_delay * (1 << (retry_count - 1)); // 指数退避
|
||
printf("第 %d 次重试,等待 %d 秒...\n", retry_count, delay);
|
||
sleep(delay);
|
||
}
|
||
|
||
step_time = time(NULL);
|
||
printf("正在执行SSL_connect()... (尝试 %d/%d)\n", retry_count + 1, max_retries + 1);
|
||
|
||
// 执行SSL握手
|
||
ret = SSL_connect(ssl);
|
||
|
||
end_time = time(NULL);
|
||
handshake_time = difftime(end_time, start_time);
|
||
step_duration = difftime(end_time, step_time);
|
||
|
||
printf("SSL_connect() 耗时: %.2f 秒\n", step_duration);
|
||
|
||
if (ret == 1) {
|
||
// 握手成功
|
||
printf("TLS握手成功!\n");
|
||
printf("总握手耗时: %.2f 秒\n", handshake_time);
|
||
printf("重试次数: %d\n", retry_count);
|
||
printf("协议版本: %s\n", SSL_get_version(ssl));
|
||
printf("密码套件: %s\n", SSL_get_cipher(ssl));
|
||
|
||
// 验证服务器证书
|
||
X509* cert = SSL_get_peer_certificate(ssl);
|
||
if (cert) {
|
||
printf("服务器证书验证成功\n");
|
||
X509_free(cert);
|
||
} else {
|
||
printf("警告: 无法获取服务器证书\n");
|
||
}
|
||
|
||
return 1;
|
||
}
|
||
|
||
// 握手失败,分析错误
|
||
int err = SSL_get_error(ssl, ret);
|
||
fprintf(stderr, "TLS握手失败,错误代码: %d (尝试 %d/%d)\n", err, retry_count + 1, max_retries + 1);
|
||
|
||
// 详细的错误诊断
|
||
switch (err) {
|
||
case SSL_ERROR_SSL:
|
||
fprintf(stderr, "SSL协议错误,可能是证书验证失败\n");
|
||
ERR_print_errors_fp(stderr);
|
||
break;
|
||
|
||
case SSL_ERROR_SYSCALL:
|
||
fprintf(stderr, "系统调用错误,详细分析:\n");
|
||
analyze_syscall_error(ssl, ret);
|
||
|
||
// 根据系统错误判断是否值得重试
|
||
if (sockfd > 0) {
|
||
int error = 0;
|
||
socklen_t len = sizeof(error);
|
||
if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, &len) == 0) {
|
||
switch (error) {
|
||
case ECONNREFUSED:
|
||
case EHOSTUNREACH:
|
||
case ENETUNREACH:
|
||
fprintf(stderr, "网络连接问题,建议检查服务器地址和端口\n");
|
||
break;
|
||
case ETIMEDOUT:
|
||
fprintf(stderr, "连接超时,可能是网络延迟或服务器负载高\n");
|
||
break;
|
||
case ECONNRESET:
|
||
case EPIPE:
|
||
fprintf(stderr, "连接被重置,可能是服务器问题\n");
|
||
break;
|
||
case EAGAIN:
|
||
fprintf(stderr, "资源暂时不可用,适合重试\n");
|
||
break;
|
||
#ifdef EWOULDBLOCK
|
||
#if EAGAIN != EWOULDBLOCK
|
||
case EWOULDBLOCK:
|
||
fprintf(stderr, "资源暂时不可用,适合重试\n");
|
||
break;
|
||
#endif
|
||
#endif
|
||
default:
|
||
fprintf(stderr, "其他系统错误\n");
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
break;
|
||
|
||
case SSL_ERROR_WANT_READ:
|
||
case SSL_ERROR_WANT_WRITE:
|
||
fprintf(stderr, "需要更多数据,这通常是正常的握手过程\n");
|
||
// 对于WANT_READ/WANT_WRITE,通常不需要重试,这是正常的握手流程
|
||
if (retry_count == 0) {
|
||
fprintf(stderr, "这是正常的握手流程,不需要重试\n");
|
||
return 0;
|
||
}
|
||
break;
|
||
|
||
default:
|
||
fprintf(stderr, "未知SSL错误\n");
|
||
ERR_print_errors_fp(stderr);
|
||
break;
|
||
}
|
||
|
||
retry_count++;
|
||
|
||
// 如果不是最后一次尝试,提供重试建议
|
||
if (retry_count <= max_retries) {
|
||
fprintf(stderr, "\n重试建议:\n");
|
||
fprintf(stderr, "1. 检查网络连接\n");
|
||
fprintf(stderr, "2. 验证服务器地址和端口\n");
|
||
fprintf(stderr, "3. 检查防火墙设置\n");
|
||
fprintf(stderr, "4. 尝试使用 --no-verify 选项\n");
|
||
fprintf(stderr, "5. 检查证书文件是否正确\n");
|
||
}
|
||
}
|
||
|
||
// 所有重试都失败了
|
||
fprintf(stderr, "\n所有重试尝试都失败了\n");
|
||
fprintf(stderr, "最终解决建议:\n");
|
||
fprintf(stderr, "1. 使用 --no-verify 选项禁用证书验证\n");
|
||
fprintf(stderr, "2. 检查服务器证书是否有效\n");
|
||
fprintf(stderr, "3. 确保CA证书文件正确\n");
|
||
fprintf(stderr, "4. 检查系统时间是否正确\n");
|
||
fprintf(stderr, "5. 检查网络连接和防火墙设置\n");
|
||
fprintf(stderr, "6. 尝试使用不同的TLS版本\n");
|
||
|
||
return 0;
|
||
}
|
||
|
||
/**
|
||
* @brief 执行TLS握手(保持向后兼容)
|
||
* @param ssl SSL连接对象
|
||
* @return int 成功返回1,失败返回0
|
||
*/
|
||
int perform_tls_handshake(SSL* ssl) {
|
||
// 使用默认重试参数:最多重试3次,基础延迟2秒
|
||
return perform_tls_handshake_with_retry(ssl, 3, 2);
|
||
}
|
||
|
||
/**
|
||
* @brief 发送测试数据
|
||
* @param ssl SSL连接对象
|
||
* @return int 成功返回1,失败返回0
|
||
*/
|
||
int send_test_data(SSL* ssl) {
|
||
char test_data[TEST_DATA_SIZE];
|
||
int bytes_sent;
|
||
time_t start_time, end_time;
|
||
double send_time;
|
||
|
||
// 生成1KB测试数据
|
||
for (int i = 0; i < TEST_DATA_SIZE; i++) {
|
||
test_data[i] = 'A' + (i % 26);
|
||
}
|
||
|
||
printf("发送 %d 字节测试数据...\n", TEST_DATA_SIZE);
|
||
start_time = time(NULL);
|
||
|
||
// 发送数据
|
||
bytes_sent = SSL_write(ssl, test_data, TEST_DATA_SIZE);
|
||
|
||
end_time = time(NULL);
|
||
send_time = difftime(end_time, start_time);
|
||
printf("SSL_write() 耗时: %.2f 秒\n", send_time);
|
||
|
||
if (bytes_sent <= 0) {
|
||
int err = SSL_get_error(ssl, bytes_sent);
|
||
fprintf(stderr, "发送数据失败,错误代码: %d\n", err);
|
||
return 0;
|
||
}
|
||
|
||
printf("成功发送 %d 字节数据\n", bytes_sent);
|
||
return 1;
|
||
}
|
||
|
||
/**
|
||
* @brief 接收服务器响应
|
||
* @param ssl SSL连接对象
|
||
* @return int 成功返回1,失败返回0
|
||
*/
|
||
int receive_response(SSL* ssl) {
|
||
char buffer[MAX_BUFFER_SIZE];
|
||
int bytes_received;
|
||
time_t start_time, end_time;
|
||
double receive_time;
|
||
int timeout_seconds = 30; // 30秒超时
|
||
|
||
printf("等待服务器响应(超时: %d秒)...\n", timeout_seconds);
|
||
start_time = time(NULL);
|
||
|
||
// 设置socket超时
|
||
int sockfd = SSL_get_fd(ssl);
|
||
if (sockfd > 0) {
|
||
struct timeval timeout;
|
||
timeout.tv_sec = timeout_seconds;
|
||
timeout.tv_usec = 0;
|
||
setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));
|
||
}
|
||
|
||
// 接收数据
|
||
bytes_received = SSL_read(ssl, buffer, MAX_BUFFER_SIZE - 1);
|
||
|
||
end_time = time(NULL);
|
||
receive_time = difftime(end_time, start_time);
|
||
printf("SSL_read() 耗时: %.2f 秒\n", receive_time);
|
||
|
||
// 检查是否超时
|
||
if (receive_time >= timeout_seconds) {
|
||
printf("⚠ 警告: 服务器响应超时(%.2f秒)\n", receive_time);
|
||
}
|
||
|
||
if (bytes_received <= 0) {
|
||
int err = SSL_get_error(ssl, bytes_received);
|
||
fprintf(stderr, "接收数据失败,错误代码: %d\n", err);
|
||
return 0;
|
||
}
|
||
|
||
buffer[bytes_received] = '\0';
|
||
printf("接收到 %d 字节响应:\n", bytes_received);
|
||
printf("--- 响应内容 ---\n");
|
||
printf("%s\n", buffer);
|
||
printf("--- 响应结束 ---\n");
|
||
|
||
// 检查是否收到预期的HTTP响应
|
||
if (strstr(buffer, "HTTP/1.1 200 OK") && strstr(buffer, "Hello from TLS server!")) {
|
||
printf("✓ 收到预期的HTTP响应\n");
|
||
} else {
|
||
printf("⚠ 响应内容与预期不符\n");
|
||
}
|
||
|
||
return 1;
|
||
}
|
||
|
||
/**
|
||
* @brief 执行单次TLS测试
|
||
* @param hostname 服务器主机名
|
||
* @param port 服务器端口
|
||
* @param cert_file 客户端证书文件
|
||
* @param key_file 客户端私钥文件
|
||
* @param ca_file CA证书文件
|
||
* @param verify_peer 是否验证服务器证书
|
||
* @param tls_version TLS版本
|
||
* @param debug_level 调试级别
|
||
* @param max_retries 最大重试次数
|
||
* @param retry_delay 重试延迟
|
||
* @return int 成功返回1,失败返回0
|
||
*/
|
||
int perform_tls_test(const char* hostname, int port, const char* cert_file,
|
||
const char* key_file, const char* ca_file, int verify_peer,
|
||
int tls_version, int debug_level, int max_retries, int retry_delay) {
|
||
int sockfd;
|
||
SSL* ssl;
|
||
SSL_CTX* local_ctx = NULL;
|
||
int success = 0;
|
||
|
||
// 创建TCP连接
|
||
sockfd = create_tcp_connection(hostname, port);
|
||
if (sockfd < 0) {
|
||
return 0;
|
||
}
|
||
|
||
// 创建本地SSL上下文(如果提供了tls_version参数)
|
||
if (tls_version > 0) {
|
||
printf("为TLS版本 %d 创建SSL上下文...\n", tls_version);
|
||
local_ctx = create_ssl_context(cert_file, key_file, ca_file, verify_peer, tls_version, debug_level);
|
||
if (!local_ctx) {
|
||
fprintf(stderr, "创建SSL上下文失败\n");
|
||
close(sockfd);
|
||
return 0;
|
||
}
|
||
printf("SSL上下文创建成功\n");
|
||
}
|
||
|
||
// 创建SSL连接
|
||
ssl = SSL_new(local_ctx ? local_ctx : ssl_ctx);
|
||
if (!ssl) {
|
||
fprintf(stderr, "创建SSL连接失败\n");
|
||
if (local_ctx) {
|
||
SSL_CTX_free(local_ctx);
|
||
}
|
||
close(sockfd);
|
||
return 0;
|
||
}
|
||
|
||
// 将套接字绑定到SSL连接
|
||
SSL_set_fd(ssl, sockfd);
|
||
|
||
// 执行TLS握手(使用指定的重试参数)
|
||
if (perform_tls_handshake_with_retry(ssl, max_retries, retry_delay)) {
|
||
// 发送测试数据
|
||
if (send_test_data(ssl)) {
|
||
// 接收响应
|
||
if (receive_response(ssl)) {
|
||
success = 1;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 安全关闭SSL连接和套接字
|
||
if (ssl) {
|
||
// 检查SSL连接状态
|
||
int shutdown_state = SSL_get_shutdown(ssl);
|
||
printf("SSL关闭前状态: %d\n", shutdown_state);
|
||
|
||
if (shutdown_state == 0) {
|
||
// 连接仍然活跃,需要优雅关闭
|
||
printf("开始优雅关闭SSL连接...\n");
|
||
|
||
// 设置SSL关闭模式为双向关闭
|
||
SSL_set_shutdown(ssl, SSL_SENT_SHUTDOWN | SSL_RECEIVED_SHUTDOWN);
|
||
|
||
// 尝试优雅关闭SSL连接
|
||
int ret = SSL_shutdown(ssl);
|
||
printf("第一次SSL_shutdown返回: %d\n", ret);
|
||
|
||
if (ret == 0) {
|
||
// 需要再次调用SSL_shutdown完成双向关闭
|
||
printf("执行第二次SSL关闭...\n");
|
||
ret = SSL_shutdown(ssl);
|
||
printf("第二次SSL_shutdown返回: %d\n", ret);
|
||
}
|
||
|
||
// 等待一段时间确保关闭完成
|
||
usleep(100000); // 等待100ms
|
||
}
|
||
|
||
// 释放SSL对象
|
||
SSL_free(ssl);
|
||
}
|
||
|
||
// 关闭套接字
|
||
if (sockfd > 0) {
|
||
// 检查套接字状态
|
||
int error = 0;
|
||
socklen_t len = sizeof(error);
|
||
if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, &len) == 0) {
|
||
if (error != 0) {
|
||
printf("套接字错误: %d\n", error);
|
||
}
|
||
}
|
||
|
||
// 设置套接字选项,避免TIME_WAIT状态
|
||
int opt = 1;
|
||
if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) {
|
||
perror("设置SO_REUSEADDR失败");
|
||
}
|
||
|
||
// 设置LINGER选项,确保数据发送完成
|
||
struct linger linger_opt;
|
||
linger_opt.l_onoff = 1;
|
||
linger_opt.l_linger = 5; // 等待5秒
|
||
if (setsockopt(sockfd, SOL_SOCKET, SO_LINGER, &linger_opt, sizeof(linger_opt)) < 0) {
|
||
perror("设置SO_LINGER失败");
|
||
}
|
||
|
||
// 关闭套接字
|
||
close(sockfd);
|
||
}
|
||
|
||
if (local_ctx) {
|
||
SSL_CTX_free(local_ctx);
|
||
}
|
||
|
||
return success;
|
||
}
|
||
|
||
/**
|
||
* @brief 执行TLS版本循环测试
|
||
* @param hostname 服务器主机名
|
||
* @param port 服务器端口
|
||
* @param cert_file 客户端证书文件
|
||
* @param key_file 客户端私钥文件
|
||
* @param ca_file CA证书文件
|
||
* @param verify_peer 是否验证服务器证书
|
||
* @param test_count 测试次数 (-1表示无限循环)
|
||
* @param interval 测试间隔
|
||
* @param continue_on_failure 失败时是否继续
|
||
* @param debug_level 调试级别
|
||
* @param max_retries 最大重试次数
|
||
* @param retry_delay 重试延迟
|
||
* @return int 成功返回1,失败返回0
|
||
*/
|
||
int perform_tls_version_test(const char* hostname, int port, const char* cert_file,
|
||
const char* key_file, const char* ca_file, int verify_peer,
|
||
int test_count, int interval, int continue_on_failure,
|
||
int debug_level, int max_retries, int retry_delay) {
|
||
int test_num = 0;
|
||
int success_count = 0;
|
||
int total_tests = 0;
|
||
time_t start_time, end_time;
|
||
|
||
printf("=== TLS版本循环测试开始 ===\n");
|
||
printf("服务器: %s:%d\n", hostname, port);
|
||
printf("测试模式: TLS 1.2 -> TLS 1.3 -> 重复\n");
|
||
printf("测试次数: %s\n", test_count == -1 ? "无限循环" : "有限次数");
|
||
printf("测试间隔: %d 秒\n", interval);
|
||
printf("========================\n\n");
|
||
|
||
start_time = time(NULL);
|
||
|
||
while (test_count == -1 || test_num < test_count) {
|
||
test_num++;
|
||
total_tests++;
|
||
|
||
// 测试TLS 1.2
|
||
printf("\n--- 第 %d 次测试 - TLS 1.2 ---\n", test_num);
|
||
if (perform_tls_test(hostname, port, cert_file, key_file, ca_file, verify_peer, 1, debug_level, max_retries, retry_delay)) {
|
||
printf("✓ TLS 1.2 测试成功\n");
|
||
success_count++;
|
||
} else {
|
||
printf("✗ TLS 1.2 测试失败\n");
|
||
if (!continue_on_failure) {
|
||
printf("TLS 1.2 测试失败,退出测试循环\n");
|
||
break; // 失败时退出循环
|
||
} else {
|
||
printf("TLS 1.2 测试失败,继续测试\n");
|
||
}
|
||
}
|
||
|
||
// 等待间隔
|
||
if (interval > 0) {
|
||
sleep(interval);
|
||
}
|
||
|
||
// 测试TLS 1.3
|
||
printf("\n--- 第 %d 次测试 - TLS 1.3 ---\n", test_num);
|
||
if (perform_tls_test(hostname, port, cert_file, key_file, ca_file, verify_peer, 2, debug_level, max_retries, retry_delay)) {
|
||
printf("✓ TLS 1.3 测试成功\n");
|
||
success_count++;
|
||
} else {
|
||
printf("✗ TLS 1.3 测试失败\n");
|
||
if (!continue_on_failure) {
|
||
printf("TLS 1.3 测试失败,退出测试循环\n");
|
||
break; // 失败时退出循环
|
||
} else {
|
||
printf("TLS 1.3 测试失败,继续测试\n");
|
||
}
|
||
}
|
||
|
||
// 如果不是最后一次测试,等待指定间隔
|
||
if (test_count == -1 || test_num < test_count) {
|
||
if (interval > 0) {
|
||
printf("等待 %d 秒后进行下次测试...\n", interval);
|
||
sleep(interval);
|
||
}
|
||
}
|
||
}
|
||
|
||
end_time = time(NULL);
|
||
|
||
// 打印测试统计
|
||
printf("\n=== 测试统计 ===\n");
|
||
printf("总测试次数: %d (TLS 1.2: %d次, TLS 1.3: %d次)\n", total_tests * 2, total_tests, total_tests);
|
||
printf("成功次数: %d\n", success_count);
|
||
printf("失败次数: %d\n", total_tests * 2 - success_count);
|
||
printf("成功率: %.2f%%\n", total_tests > 0 ? (double)success_count / (total_tests * 2) * 100 : 0);
|
||
printf("总耗时: %ld 秒\n", end_time - start_time);
|
||
printf("===============\n");
|
||
|
||
return success_count > 0 ? 1 : 0;
|
||
}
|
||
|
||
/**
|
||
* @brief 打印使用说明
|
||
*/
|
||
void print_usage(const char* program_name) {
|
||
printf("用法: %s [选项]\n", program_name);
|
||
printf("选项:\n");
|
||
printf(" -h <hostname> 服务器主机名或IP地址 (默认: 127.0.0.1)\n");
|
||
printf(" -p <port> 服务器端口 (默认: 443)\n");
|
||
printf(" -c <cert_file> 客户端证书文件 (必需)\n");
|
||
printf(" -k <key_file> 客户端私钥文件 (必需)\n");
|
||
printf(" -a <ca_file> CA证书文件 (可选)\n");
|
||
printf(" -n <count> 测试次数 (默认: 无限循环)\n");
|
||
printf(" -i <interval> 测试间隔秒数 (默认: 1)\n");
|
||
printf(" -v 详细输出\n");
|
||
printf(" -x 禁用服务器证书验证\n");
|
||
printf(" -t <version> TLS版本 (1=TLS1.2, 2=TLS1.3, 3=循环测试1.2和1.3)\n");
|
||
printf(" -f 失败时继续测试(默认:失败时退出)\n");
|
||
printf(" -d <level> OpenSSL调试级别 (0=无, 1=基本, 2=详细, 3=完整)\n");
|
||
printf(" -r <retries> 握手重试次数 (默认: 3)\n");
|
||
printf(" -w <delay> 重试基础延迟秒数 (默认: 2)\n");
|
||
printf(" --no-verify 禁用服务器证书验证\n");
|
||
printf(" --help 显示此帮助信息\n");
|
||
printf("\n示例:\n");
|
||
printf(" %s -h 192.168.1.100 -p 8443 -c client.crt -k client.key\n", program_name);
|
||
printf(" %s -h example.com -p 443 -c client.crt -k client.key -a ca.crt -n 10\n", program_name);
|
||
printf(" %s -h server.com -c client.crt -k client.key -r 5 -w 3\n", program_name);
|
||
}
|
||
|
||
int main(int argc, char *argv[]) {
|
||
char hostname[256] = "127.0.0.1";
|
||
int port = 443;
|
||
char cert_file[256] = "";
|
||
char key_file[256] = "";
|
||
char ca_file[256] = "";
|
||
int test_count = -1; // -1表示无限循环
|
||
int interval = 1;
|
||
int verbose = 0;
|
||
int no_verify = 0; // 是否禁用服务器证书验证
|
||
int tls_version = 3; // 默认循环测试TLS 1.2和1.3
|
||
int continue_on_failure = 0; // 失败时是否继续测试
|
||
int debug_level = 0; // OpenSSL调试级别
|
||
int max_retries = 3; // 默认重试次数
|
||
int retry_delay = 2; // 默认重试延迟
|
||
int opt;
|
||
int test_num = 0;
|
||
int success_count = 0;
|
||
int total_tests = 0;
|
||
time_t start_time, end_time;
|
||
|
||
// 预处理长选项,将它们转换为短选项
|
||
for (int i = 1; i < argc; i++) {
|
||
if (strcmp(argv[i], "--no-verify") == 0) {
|
||
no_verify = 1;
|
||
// 移除这个参数,避免getopt处理
|
||
for (int j = i; j < argc - 1; j++) {
|
||
argv[j] = argv[j + 1];
|
||
}
|
||
argc--;
|
||
i--;
|
||
} else if (strcmp(argv[i], "--help") == 0) {
|
||
print_usage(argv[0]);
|
||
return 0;
|
||
}
|
||
}
|
||
|
||
// 解析命令行参数
|
||
while ((opt = getopt(argc, argv, "h:p:c:k:a:n:i:vxt:fd:r:w:")) != -1) {
|
||
switch (opt) {
|
||
case 'h':
|
||
strncpy(hostname, optarg, sizeof(hostname) - 1);
|
||
break;
|
||
case 'p':
|
||
port = atoi(optarg);
|
||
break;
|
||
case 'c':
|
||
strncpy(cert_file, optarg, sizeof(cert_file) - 1);
|
||
break;
|
||
case 'k':
|
||
strncpy(key_file, optarg, sizeof(key_file) - 1);
|
||
break;
|
||
case 'a':
|
||
strncpy(ca_file, optarg, sizeof(ca_file) - 1);
|
||
break;
|
||
case 'n':
|
||
test_count = atoi(optarg);
|
||
break;
|
||
case 'i':
|
||
interval = atoi(optarg);
|
||
break;
|
||
case 'v':
|
||
verbose = 1;
|
||
break;
|
||
case 'x':
|
||
no_verify = 1;
|
||
break;
|
||
case 't':
|
||
tls_version = atoi(optarg);
|
||
if (tls_version < 1 || tls_version > 3) {
|
||
fprintf(stderr, "错误: TLS版本必须是1、2或3\n");
|
||
print_usage(argv[0]);
|
||
return 1;
|
||
}
|
||
break;
|
||
case 'f':
|
||
continue_on_failure = 1;
|
||
break;
|
||
case 'd':
|
||
debug_level = atoi(optarg);
|
||
if (debug_level < 0 || debug_level > 3) {
|
||
fprintf(stderr, "错误: 调试级别必须是0-3\n");
|
||
print_usage(argv[0]);
|
||
return 1;
|
||
}
|
||
break;
|
||
case 'r':
|
||
max_retries = atoi(optarg);
|
||
if (max_retries < 0 || max_retries > 10) {
|
||
fprintf(stderr, "错误: 重试次数必须是0-10\n");
|
||
print_usage(argv[0]);
|
||
return 1;
|
||
}
|
||
break;
|
||
case 'w':
|
||
retry_delay = atoi(optarg);
|
||
if (retry_delay < 1 || retry_delay > 60) {
|
||
fprintf(stderr, "错误: 重试延迟必须是1-60秒\n");
|
||
print_usage(argv[0]);
|
||
return 1;
|
||
}
|
||
break;
|
||
default:
|
||
print_usage(argv[0]);
|
||
return 1;
|
||
}
|
||
}
|
||
|
||
// 检查必需参数
|
||
if (strlen(cert_file) == 0 || strlen(key_file) == 0) {
|
||
fprintf(stderr, "错误: 必须指定客户端证书和私钥文件\n");
|
||
print_usage(argv[0]);
|
||
return 1;
|
||
}
|
||
|
||
// 初始化OpenSSL
|
||
init_openssl(debug_level);
|
||
|
||
// 根据TLS版本选择测试模式
|
||
if (tls_version == 3) {
|
||
// 循环测试TLS 1.2和1.3
|
||
return perform_tls_version_test(hostname, port, cert_file, key_file,
|
||
ca_file[0] ? ca_file : NULL, !no_verify,
|
||
test_count, interval, continue_on_failure, debug_level, max_retries, retry_delay);
|
||
} else {
|
||
// 单版本测试
|
||
// 创建SSL上下文
|
||
ssl_ctx = create_ssl_context(cert_file, key_file, ca_file[0] ? ca_file : NULL, !no_verify, tls_version, debug_level);
|
||
if (!ssl_ctx) {
|
||
fprintf(stderr, "创建SSL上下文失败\n");
|
||
cleanup_openssl();
|
||
return 1;
|
||
}
|
||
}
|
||
|
||
printf("=== TLS客户端测试程序 ===\n");
|
||
printf("服务器: %s:%d\n", hostname, port);
|
||
printf("客户端证书: %s\n", cert_file);
|
||
printf("客户端私钥: %s\n", key_file);
|
||
if (strlen(ca_file) > 0) {
|
||
printf("CA证书: %s\n", ca_file);
|
||
}
|
||
printf("测试次数: %s\n", test_count == -1 ? "无限循环" : "有限次数");
|
||
if (test_count != -1) {
|
||
printf("计划测试: %d 次\n", test_count);
|
||
}
|
||
printf("测试间隔: %d 秒\n", interval);
|
||
printf("========================\n\n");
|
||
|
||
start_time = time(NULL);
|
||
|
||
// 开始测试循环
|
||
while (test_count == -1 || test_num < test_count) {
|
||
test_num++;
|
||
total_tests++;
|
||
|
||
printf("\n--- 第 %d 次测试 ---\n", test_num);
|
||
|
||
if (verbose) {
|
||
printf("开始时间: %s", ctime(&start_time));
|
||
}
|
||
|
||
// 执行TLS测试
|
||
if (perform_tls_test(hostname, port, cert_file, key_file, ca_file, !no_verify, tls_version, debug_level, max_retries, retry_delay)) {
|
||
printf("✓ 第 %d 次测试成功\n", test_num);
|
||
success_count++;
|
||
} else {
|
||
printf("✗ 第 %d 次测试失败\n", test_num);
|
||
if (!continue_on_failure) {
|
||
printf("测试失败,退出测试循环\n");
|
||
break; // 失败时退出循环
|
||
} else {
|
||
printf("测试失败,继续测试\n");
|
||
}
|
||
}
|
||
|
||
// 如果不是最后一次测试,等待指定间隔
|
||
if (test_count == -1 || test_num < test_count) {
|
||
if (interval > 0) {
|
||
printf("等待 %d 秒后进行下次测试...\n", interval);
|
||
sleep(interval);
|
||
}
|
||
}
|
||
}
|
||
|
||
end_time = time(NULL);
|
||
|
||
// 打印测试统计
|
||
printf("\n=== 测试统计 ===\n");
|
||
printf("总测试次数: %d\n", total_tests);
|
||
printf("成功次数: %d\n", success_count);
|
||
printf("失败次数: %d\n", total_tests - success_count);
|
||
printf("成功率: %.2f%%\n", total_tests > 0 ? (double)success_count / total_tests * 100 : 0);
|
||
printf("总耗时: %ld 秒\n", end_time - start_time);
|
||
printf("===============\n");
|
||
|
||
// 清理资源
|
||
cleanup_openssl();
|
||
|
||
return 0;
|
||
}
|