土法炼钢兴趣小组的算法知识备份

【Libevent】双向认证 (mTLS) 实战指南

源码下载

本文相关源码已整理,共 2 个文件。

打开下载目录 →

目录

在零信任网络架构中,双向认证 (Mutual TLS, mTLS) 是服务间通信的标配。与传统的单向认证(仅客户端验证服务端)不同,mTLS 要求服务端也必须验证客户端的身份。

本文将详细介绍如何在 Libevent 中基于 OpenSSL 实现 mTLS。

1. 证书准备

在开始编码之前,我们需要一套测试证书。通常包含: 1. CA (Certificate Authority): 用于签署服务端和客户端证书。 2. Server Cert/Key: 服务端持有。 3. Client Cert/Key: 客户端持有。

快速生成测试证书

# 1. 生成 CA
openssl req -new -x509 -days 365 -nodes -out ca.crt -keyout ca.key -subj "/CN=MyRootCA"

# 2. 生成服务端证书
openssl req -new -nodes -out server.csr -keyout server.key -subj "/CN=localhost"
openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt -days 365

# 3. 生成客户端证书
openssl req -new -nodes -out client.csr -keyout client.key -subj "/CN=client-app"
openssl x509 -req -in client.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out client.crt -days 365

2. 服务端实现

在 Libevent 中,mTLS 的核心在于正确配置 SSL_CTX,然后将其传递给 bufferevent_openssl_socket_new

2.1 配置 SSL_CTX

关键步骤是设置 SSL_VERIFY_PEER 并加载 CA 证书以验证客户端。

#include <openssl/ssl.h>
#include <openssl/err.h>
#include <event2/bufferevent_ssl.h>

SSL_CTX *create_mtls_context(const char *ca_cert, const char *server_cert, const char *server_key) {
  SSL_CTX *ctx = SSL_CTX_new(TLS_server_method());
  if (!ctx) return NULL;

  // 1. 加载服务端自身的证书和私钥
  if (SSL_CTX_use_certificate_chain_file(ctx, server_cert) <= 0 ||
  SSL_CTX_use_PrivateKey_file(ctx, server_key, SSL_FILETYPE_PEM) <= 0) {
  fprintf(stderr, "Failed to load server cert/key\n");
  SSL_CTX_free(ctx);
  return NULL;
  }

  // 2. 加载 CA 证书(用于验证客户端证书)
  if (!SSL_CTX_load_verify_locations(ctx, ca_cert, NULL)) {
  fprintf(stderr, "Failed to load CA cert\n");
  SSL_CTX_free(ctx);
  return NULL;
  }

  // 3. 强制开启客户端验证
  // SSL_VERIFY_PEER: 要求客户端发送证书
  // SSL_VERIFY_FAIL_IF_NO_PEER_CERT: 如果客户端没发证书,握手失败
  SSL_CTX_set_verify(ctx, 
  SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, 
  NULL); // 第三个参数是 verify_callback,可设为 NULL 使用默认验证

  return ctx;
}

2.2 创建 Bufferevent

listener_cb 中,我们使用配置好的 ctx 创建 SSL bufferevent。

void listener_cb(struct evconnlistener *listener, evutil_socket_t fd,
  struct sockaddr *sa, int socklen, void *user_data) {
  struct event_base *base = user_data;
  SSL_CTX *ctx = global_ssl_ctx; // 假设这是全局的或者从 user_data 传入的

  SSL *ssl = SSL_new(ctx);
  struct bufferevent *bev;

  // 创建 SSL bufferevent
  bev = bufferevent_openssl_socket_new(base, fd, ssl,
  BUFFEREVENT_SSL_ACCEPTING,
  BEV_OPT_CLOSE_ON_FREE);

  if (!bev) {
  fprintf(stderr, "Error constructing bufferevent!\n");
  event_base_loopbreak(base);
  return;
  }

  bufferevent_setcb(bev, read_cb, NULL, event_cb, NULL);
  bufferevent_enable(bev, EV_READ | EV_WRITE);
}

2.3 验证客户端身份 (Authorization)

仅仅验证证书签名是不够的(任何持有该 CA 签发证书的人都能连接)。通常我们还需要检查证书中的 Common Name (CN)Subject Alternative Name (SAN)

这可以在握手成功后的 event_cb 中进行:

void event_cb(struct bufferevent *bev, short events, void *ctx) {
  if (events & BEV_EVENT_CONNECTED) {
  // 握手成功
  SSL *ssl = bufferevent_openssl_get_ssl(bev);
  X509 *cert = SSL_get_peer_certificate(ssl);
  
  if (cert) {
  char common_name[256];
  X509_NAME_get_text_by_NID(X509_get_subject_name(cert), 
  NID_commonName, 
  common_name, 
  sizeof(common_name));
  
  printf("Client connected with CN: %s\n", common_name);
  
  // 在这里做业务鉴权,例如:
  if (strcmp(common_name, "admin-service") != 0) {
  printf("Unauthorized client!\n");
  bufferevent_free(bev); // 断开连接
  X509_free(cert);
  return;
  }
  
  X509_free(cert);
  }
  } else if (events & BEV_EVENT_ERROR) {
  // 处理错误...
  }
}

3. 客户端实现

客户端同样需要配置 SSL_CTX,加载客户端证书,并信任 CA。

SSL_CTX *create_client_context(const char *ca_cert, const char *client_cert, const char *client_key) {
  SSL_CTX *ctx = SSL_CTX_new(TLS_client_method());
  
  // 加载客户端证书
  SSL_CTX_use_certificate_chain_file(ctx, client_cert);
  SSL_CTX_use_PrivateKey_file(ctx, client_key, SSL_FILETYPE_PEM);
  
  // 加载 CA 以验证服务端
  SSL_CTX_load_verify_locations(ctx, ca_cert, NULL);
  SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL);
  
  return ctx;
}

4. 测试验证

启动服务端后,使用 curl 进行测试:

# 成功连接
curl --cacert ca.crt --cert client.crt --key client.key https://localhost:8080

# 失败连接(无证书)
curl --cacert ca.crt https://localhost:8080
# 预期报错: alert bad certificate

5. 总结

在 Libevent 中实现 mTLS 并不复杂,主要工作量在于 OpenSSL 的配置。 * 服务端: 必须设置 SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT。 * 鉴权: 握手成功后,务必检查证书内容(如 CN),确保持有合法证书的客户端也是你有权访问的客户端。

完整代码: 05-mtls-server.c | 05-gen-certs.sh

返回 Libevent 专题索引


By .