当前位置: 首页 > backend >正文

利用 openssl api 实现 TLS 双向认证

1. 环境

  1. openssl1.1.1g

wget https://github.com/openssl/openssl/releases/download/OpenSSL_1_1_1g/openssl-1.1.1g.tar.gz
sha256 为: ddb04774f1e32f0c49751e21b67216ac87852ceb056b75209af2443400636d46

  1. Linux 环境

2. 静态编译 openssl

tar -zxvf openssl-1.1.1g.tar.gz
cd  openssl-1.1.1g./config -fPIC no-shared
make -j 4

3. TLS 使用到的证书生成

#!/bin/bash# 生成 CA
openssl genrsa -out ca.key 4096
openssl req -x509 -new -nodes -key ca.key -sha256 -days 3650 -out ca.crt \-subj "/C=CN/ST=Beijing/L=Beijing/O=MyOrg/CN=MyCA"# 生成服务器证书
openssl genrsa -out server.key 4096
openssl req -new -key server.key -out server.csr \-subj "/C=CN/ST=Beijing/L=Beijing/O=MyOrg/CN=localhost"
openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial \-out server.crt -days 365 -sha256# 生成客户端证书
openssl genrsa -out client.key 4096
openssl req -new -key client.key -out client.csr \-subj "/C=CN/ST=Beijing/L=Beijing/O=MyOrg/CN=MyClient"
openssl x509 -req -in client.csr -CA ca.crt -CAkey ca.key -CAcreateserial \-out client.crt -days 365 -sha256# 清理临时文件
rm -f server.csr client.csr ca.srlecho "证书生成完成!"

3. Linux C 代码调用 openssl api

  1. server.c
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <openssl/ssl.h>
#include <openssl/err.h>#define PORT 4433
#define CERT_FILE "server.crt"
#define KEY_FILE "server.key"
#define CA_FILE "ca.crt"void init_openssl()
{SSL_load_error_strings();OpenSSL_add_ssl_algorithms();
}void cleanup_openssl()
{EVP_cleanup();
}SSL_CTX *create_context()
{const SSL_METHOD *method;SSL_CTX *ctx;method = TLS_server_method();ctx = SSL_CTX_new(method);if (!ctx){perror("Unable to create SSL context");ERR_print_errors_fp(stderr);exit(EXIT_FAILURE);}return ctx;
}void configure_context(SSL_CTX *ctx)
{/* 设置服务器证书 */if (SSL_CTX_use_certificate_file(ctx, CERT_FILE, SSL_FILETYPE_PEM) <= 0){ERR_print_errors_fp(stderr);exit(EXIT_FAILURE);}/* 设置服务器私钥 */if (SSL_CTX_use_PrivateKey_file(ctx, KEY_FILE, SSL_FILETYPE_PEM) <= 0){ERR_print_errors_fp(stderr);exit(EXIT_FAILURE);}/* 验证私钥是否匹配证书 */if (!SSL_CTX_check_private_key(ctx)){fprintf(stderr, "Private key does not match the public certificate\n");exit(EXIT_FAILURE);}/* 设置信任的CA证书 */if (SSL_CTX_load_verify_locations(ctx, CA_FILE, NULL) != 1){ERR_print_errors_fp(stderr);exit(EXIT_FAILURE);}/* 要求验证客户端证书 */SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL);/* 设置客户端CA列表 */STACK_OF(X509_NAME) *cert_names = SSL_load_client_CA_file(CA_FILE);if (cert_names == NULL){ERR_print_errors_fp(stderr);exit(EXIT_FAILURE);}SSL_CTX_set_client_CA_list(ctx, cert_names);
}int main(int argc, char **argv)
{int sock, client;struct sockaddr_in addr;socklen_t len = sizeof(addr);init_openssl();SSL_CTX *ctx = create_context();configure_context(ctx);/* 创建socket */sock = socket(AF_INET, SOCK_STREAM, 0);if (sock < 0){perror("Unable to create socket");exit(EXIT_FAILURE);}addr.sin_family = AF_INET;addr.sin_port = htons(PORT);addr.sin_addr.s_addr = htonl(INADDR_ANY);/* 绑定socket */if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0){perror("Unable to bind");exit(EXIT_FAILURE);}/* 监听 */if (listen(sock, 1) < 0){perror("Unable to listen");exit(EXIT_FAILURE);}printf("Server listening on port %d...\n", PORT);/* 处理连接 */while (1){SSL *ssl;const char reply[] = "Hello from server\n";client = accept(sock, (struct sockaddr *)&addr, &len);if (client < 0){perror("Unable to accept");exit(EXIT_FAILURE);}printf("Client connected: %s:%d\n", inet_ntoa(addr.sin_addr), ntohs(addr.sin_port));ssl = SSL_new(ctx);SSL_set_fd(ssl, client);/* TLS握手 */if (SSL_accept(ssl) <= 0){ERR_print_errors_fp(stderr);close(client);SSL_free(ssl);continue;}/* 验证客户端证书 */X509 *client_cert = SSL_get_peer_certificate(ssl);if (client_cert){printf("Client certificate:\n");char *subject = X509_NAME_oneline(X509_get_subject_name(client_cert), 0, 0);char *issuer = X509_NAME_oneline(X509_get_issuer_name(client_cert), 0, 0);printf("Subject: %s\n", subject);printf("Issuer: %s\n", issuer);OPENSSL_free(subject);OPENSSL_free(issuer);X509_free(client_cert);}else{printf("No client certificate provided\n");close(client);SSL_free(ssl);continue;}/* 检查证书验证结果 */long verify_result = SSL_get_verify_result(ssl);if (verify_result != X509_V_OK){printf("Certificate verification failed: %s\n", X509_verify_cert_error_string(verify_result));close(client);SSL_free(ssl);continue;}/* 发送消息 */SSL_write(ssl, reply, strlen(reply));/* 接收消息 */char buf[1024];int bytes = SSL_read(ssl, buf, sizeof(buf));if (bytes > 0){buf[bytes] = 0;printf("Received: %s", buf);}SSL_shutdown(ssl);SSL_free(ssl);close(client);}close(sock);SSL_CTX_free(ctx);cleanup_openssl();return 0;
}
  1. client.c
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <openssl/ssl.h>
#include <openssl/err.h>#define HOST "127.0.0.1"
#define PORT 4433
#define CERT_FILE "client.crt"  // 需要服务端校验的客户端证书
#define KEY_FILE "client.key"   // 加载的时候使用客户端私钥校验客户端的证书是否合法
#define CA_FILE "ca.crt"  // 校验服务端证书的 CA 证书void init_openssl()
{SSL_load_error_strings();OpenSSL_add_ssl_algorithms();
}void cleanup_openssl()
{EVP_cleanup();
}SSL_CTX *create_context()
{const SSL_METHOD *method;SSL_CTX *ctx;method = TLS_client_method();ctx = SSL_CTX_new(method);if (!ctx){perror("Unable to create SSL context");ERR_print_errors_fp(stderr);exit(EXIT_FAILURE);}return ctx;
}void configure_context(SSL_CTX *ctx)
{/* 设置客户端证书 */if (SSL_CTX_use_certificate_file(ctx, CERT_FILE, SSL_FILETYPE_PEM) <= 0){ERR_print_errors_fp(stderr);exit(EXIT_FAILURE);}/* 设置客户端私钥 */if (SSL_CTX_use_PrivateKey_file(ctx, KEY_FILE, SSL_FILETYPE_PEM) <= 0){ERR_print_errors_fp(stderr);exit(EXIT_FAILURE);}/* 验证私钥是否匹配证书 */if (!SSL_CTX_check_private_key(ctx)){fprintf(stderr, "Private key does not match the public certificate\n");exit(EXIT_FAILURE);}/* 设置信任的CA证书 */if (SSL_CTX_load_verify_locations(ctx, CA_FILE, NULL) != 1){ERR_print_errors_fp(stderr);exit(EXIT_FAILURE);}/* 要求验证服务器证书 */SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL);
}int main(int argc, char **argv)
{int sock;struct sockaddr_in addr;init_openssl();SSL_CTX *ctx = create_context();configure_context(ctx);/* 创建socket */sock = socket(AF_INET, SOCK_STREAM, 0);if (sock < 0){perror("Unable to create socket");exit(EXIT_FAILURE);}addr.sin_family = AF_INET;addr.sin_port = htons(PORT);inet_pton(AF_INET, HOST, &addr.sin_addr);/* 连接服务器 */if (connect(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0){perror("Unable to connect");exit(EXIT_FAILURE);}printf("Connected to server %s:%d\n", HOST, PORT);SSL *ssl = SSL_new(ctx);SSL_set_fd(ssl, sock);/* TLS握手 */if (SSL_connect(ssl) <= 0){ERR_print_errors_fp(stderr);close(sock);SSL_free(ssl);SSL_CTX_free(ctx);cleanup_openssl();return 1;}/* 验证服务器证书 */X509 *server_cert = SSL_get_peer_certificate(ssl);if (server_cert){printf("Server certificate:\n");char *subject = X509_NAME_oneline(X509_get_subject_name(server_cert), 0, 0);char *issuer = X509_NAME_oneline(X509_get_issuer_name(server_cert), 0, 0);printf("Subject: %s\n", subject);printf("Issuer: %s\n", issuer);OPENSSL_free(subject);OPENSSL_free(issuer);X509_free(server_cert);}/* 检查证书验证结果 */long verify_result = SSL_get_verify_result(ssl);if (verify_result != X509_V_OK){printf("Certificate verification failed: %s\n", X509_verify_cert_error_string(verify_result));close(sock);SSL_free(ssl);SSL_CTX_free(ctx);cleanup_openssl();return 1;}/* 接收消息 */char buf[1024];int bytes = SSL_read(ssl, buf, sizeof(buf));if (bytes > 0){buf[bytes] = 0;printf("Received: %s", buf);}/* 发送消息 */const char *msg = "Hello from client\n";SSL_write(ssl, msg, strlen(msg));SSL_shutdown(ssl);SSL_free(ssl);close(sock);SSL_CTX_free(ctx);cleanup_openssl();return 0;
}

CC=gccCFLAGS =
# 换成对应的安装路径
CFLAGS += -I /home/chen/pros/openssl/openssl-1.1.1g/include
CFLAGS += -L /home/chen/pros/openssl/openssl-1.1.1g
CFLAGS += -gall: server  clientserver: server.c$(CC) $(CFLAGS) server.c -o server -lssl -lcrypto -ldl -lpthreadclient: client.c$(CC) $(CFLAGS) client.c -o client -lssl -lcrypto -ldl -lpthreadclean:rm -rf server client
http://www.xdnf.cn/news/19799.html

相关文章:

  • MySQL-MVCC多版本并发控制详解
  • LangChain实战(十二):自定义Tools扩展Agent能力
  • Python+DRVT 从外部调用 Revit:批量创建门
  • Streamable HTTP
  • sv中forever如何结束
  • AI 在金融、医疗、教育、制造业等领域有着广泛的应用,以下是这些领域的一些落地案例
  • STM32HAL 快速入门(十七):UART 硬件结构 —— 从寄存器到数据收发流程
  • 告别剪辑烦恼!3个超实用技巧,让你的视频瞬间高级起来
  • 【音视频】视频秒播优化实践
  • UnityWebRequest 数据获取和提交
  • wpf 只能输入int类型的文本框
  • WebSocket客户端库:websocket-fruge365
  • Ubuntu下把 SD 卡格式化为 FAT32
  • Hostol Magento电商服务器套餐:基于阿里云,预配置高性能环境,一键开店
  • 如何用java给局域网的电脑发送开机数据包
  • B样条曲线,已知曲线上的某个点到起点的距离,确定这个点的参数u的值的方法
  • 新手向:破解VMware迁移难题
  • MP4视频太大如何压缩?分享6种简单便捷的压缩小技巧
  • websocket用于控制在当前页只允许一个用户进行操作,其他用户等待
  • 硬件(一)51单片机
  • 阿里开源首个图像生成基础模型——Qwen-Image本地部署教程,中文渲染能力刷新SOTA
  • HTTP 协议核心组件与安全扩展深度解析
  • 机器学习与深度学习的 Python 基础之 NumPy(2)
  • uniapp+vue3 微信小程序全屏广告组件功能
  • AI IDE+AI 辅助编程,真能让程序员 “告别 996” 吗?
  • 【LeetCode_283】移动零
  • 技术小白如何快速的了解opentenbase?--把握四大特色
  • XE 旧版本 JSON 处理
  • 使用 Uni-app 打包 外链地址APK 及 iOS 注意事项
  • K8S-基础架构