利用 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
相关推荐
dessler3 小时前
Hadoop HDFS-JournalNode(jn)详细介绍
linux·运维·hdfs
keep__go4 小时前
postgresql9.2.4 离线安装
linux·运维·数据库·postgresql
笑口常开xpr5 小时前
救命!Shell用了100次还不懂底层?爆肝300行代码从0造“壳”,fork/exec/重定向全扒光,Linux系统编程直接开挂!
linux·linux实现简易shell
帅帅梓6 小时前
ansible-角色
linux·运维·自动化·ansible
IT成长日记9 小时前
【Linux基础】Linux系统管理:深入理解Linux运行级别及其应用
linux·运维·服务器·操作系统·运行级别
白鹭11 小时前
Mysql主从同步
linux·运维·数据库·mysql
麦子邪13 小时前
C语言中奇技淫巧07-使用GCC栈保护选项检测程序栈溢出
linux·c语言·开发语言
我认不到你13 小时前
JVM分析(OOM、死锁、死循环)(JProfiler、arthas、jdk调优工具(命令行))
java·linux·开发语言·jvm·spring boot
初学者_xuan13 小时前
零基础Linux操作基础小白快速掌握Shell脚本bash的配置文件
linux·运维·bash·shell脚本