利用 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

bash 复制代码
tar -zxvf openssl-1.1.1g.tar.gz
cd  openssl-1.1.1g

./config -fPIC no-shared
make -j 4

3. TLS 使用到的证书生成

bash 复制代码
#!/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.srl

echo "证书生成完成!"

3. Linux C 代码调用 openssl api

  1. server.c
cpp 复制代码
#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
cpp 复制代码
#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;
}
makefile 复制代码
CC=gcc

CFLAGS =
# 换成对应的安装路径
CFLAGS += -I /home/chen/pros/openssl/openssl-1.1.1g/include
CFLAGS += -L /home/chen/pros/openssl/openssl-1.1.1g
CFLAGS += -g

all: server  client

server: server.c
	$(CC) $(CFLAGS) server.c -o server -lssl -lcrypto -ldl -lpthread


client: client.c
	$(CC) $(CFLAGS) client.c -o client -lssl -lcrypto -ldl -lpthread


clean:
	rm -rf server client
相关推荐
嵩山小老虎5 小时前
Windows 10/11 安装 WSL2 并配置 VSCode 开发环境(C 语言 / Linux API 适用)
linux·windows·vscode
Fleshy数模5 小时前
CentOS7 安装配置 MySQL5.7 完整教程(本地虚拟机学习版)
linux·mysql·centos
a41324475 小时前
ubuntu 25 安装vllm
linux·服务器·ubuntu·vllm
Genie cloud7 小时前
1Panel SSL证书申请完整教程
服务器·网络协议·云计算·ssl
一只自律的鸡7 小时前
【Linux驱动】bug处理 ens33找不到IP
linux·运维·bug
17(无规则自律)7 小时前
【CSAPP 读书笔记】第二章:信息的表示和处理
linux·嵌入式硬件·考研·高考
!chen7 小时前
linux服务器静默安装Oracle26ai
linux·运维·服务器
REDcker8 小时前
Linux 文件描述符与 Socket 选项操作详解
linux·运维·网络
蒹葭玉树8 小时前
【C++上岸】C++常见面试题目--操作系统篇(第二十八期)
linux·c++·面试
2501_927773078 小时前
imx6驱动
linux·运维·服务器