利用 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
相关推荐
HLC++9 小时前
Linux的进程间通信
android·linux·服务器
华清远见IT开放实验室10 小时前
实验室建设案例 | 石家庄科技信息职业学院嵌入式实验室——从底层硬件到系统应用,一所应用型高校的嵌入式人才培养这样落地
linux·arm开发·stm32·嵌入式硬件·高校·实验室建设
groundhappy13 小时前
idalib安装和codex ida-mcp配置
linux·开发语言·python
通信小小昕13 小时前
Ubuntu 26.04 中文输入法安装
linux·运维·ubuntu
张小姐的猫15 小时前
【Linux】网络编程 —— HTTP协议(上)
linux·运维·服务器·网络·http·单例模式·策略模式
栩栩云生15 小时前
AI 写代码犯的错,早被写进了错题集
linux·安全·ai编程
imc.1116 小时前
linux基础IO
linux·运维·服务器
BelongPanda18 小时前
Linux Nginx 纯手动 Let‘s Encrypt 泛域名证书配置教程
linux·nginx
酷可达拉斯18 小时前
Linux操作系统-shell编程(0)
linux·运维·服务器·python·云计算
2301_7779983419 小时前
Linux中断机制:操作系统如何高效运行
linux·运维·服务器