- 新增 acme.h / acme.c:ACME RFC 8555 协议客户端基础实现 - JWS ES256 签名(账户密钥管理) - ACME 目录发现、Nonce 管理 - 账户创建/复用(newAccount) - 订单创建(newOrder) - 授权获取与挑战响应(HTTP-01) - 订单轮询与证书下载 - 一键签发封装 acme_issue_certificate() - 新增 tests/unit/test_acme.c:9 项单元测试 - Makefile:添加 acme.c 编译、-lcurl 链接、test_acme 测试规则 当前状态:ACME 骨架完成,JWS 签名需后续完善以通过 Let's Encrypt 验证。下阶段完成 HTTP-01 挑战服务器集成。
This commit is contained in:
parent
c2a5deec9a
commit
4a6e7adc17
18
Makefile
18
Makefile
@ -6,8 +6,8 @@ COCO_INCLUDE ?= $(COCO_DIR)/include
|
||||
COCO_LIB ?= $(COCO_DIR)/build
|
||||
|
||||
CC = gcc
|
||||
CFLAGS = -Wall -Wextra -O2 -std=c11 -D_GNU_SOURCE -I$(COCO_INCLUDE)
|
||||
LDFLAGS = -L$(COCO_LIB) -lcoco -lpthread -lm -luring -lz -lbrotlienc -lssl -lcrypto -lnghttp2 -ldl
|
||||
CFLAGS = -Wall -Wextra -O2 -std=c11 -D_GNU_SOURCE -I$(COCO_INCLUDE) -I/usr/include/x86_64-linux-gnu
|
||||
LDFLAGS = -L$(COCO_LIB) -lcoco -lpthread -lm -luring -lz -lbrotlienc -lssl -lcrypto -lnghttp2 -ldl -lcurl
|
||||
|
||||
# 调试模式
|
||||
DEBUG ?= 0
|
||||
@ -20,7 +20,7 @@ PREFIX ?= /usr/local
|
||||
BINDIR = $(PREFIX)/bin
|
||||
|
||||
# 源文件
|
||||
SRCS = main.c server.c http.c static.c log.c config.c multipart.c tls.c http2.c access_log.c websocket.c platform.c middleware.c plugin.c proxy.c proxy_tls.c healthcheck.c middleware_ext.c load_balance.c grpc.c http3.c sse.c fastcgi.c fcgi_handler.c cache.c dashboard.c
|
||||
SRCS = main.c server.c http.c static.c log.c config.c multipart.c tls.c http2.c access_log.c websocket.c platform.c middleware.c plugin.c proxy.c proxy_tls.c healthcheck.c middleware_ext.c load_balance.c grpc.c http3.c sse.c fastcgi.c fcgi_handler.c cache.c dashboard.c acme.c
|
||||
OBJS = $(SRCS:.c=.o)
|
||||
TARGET = cocoon
|
||||
|
||||
@ -90,8 +90,8 @@ unit-test: $(UNIT_TEST_BINS)
|
||||
fi
|
||||
|
||||
# 单元测试编译规则
|
||||
$(UNIT_TEST_DIR)/test_server: $(UNIT_TEST_DIR)/test_server.c server.c http.c static.c log.c config.c multipart.c tls.c http2.c access_log.c websocket.c platform.c middleware.c plugin.c proxy.c proxy_tls.c healthcheck.c sse.c fastcgi.c fcgi_handler.c cache.c dashboard.c $(UNITY_SRC)
|
||||
$(CC) $(CFLAGS) -I. -I$(UNIT_TEST_DIR)/../unity -o $@ $(UNIT_TEST_DIR)/test_server.c server.c http.c static.c log.c config.c multipart.c tls.c http2.c access_log.c websocket.c platform.c middleware.c plugin.c proxy.c proxy_tls.c healthcheck.c sse.c fastcgi.c fcgi_handler.c cache.c dashboard.c $(UNITY_SRC) $(LDFLAGS)
|
||||
$(UNIT_TEST_DIR)/test_server: $(UNIT_TEST_DIR)/test_server.c server.c http.c static.c log.c config.c multipart.c tls.c http2.c access_log.c websocket.c platform.c middleware.c plugin.c proxy.c proxy_tls.c healthcheck.c sse.c fastcgi.c fcgi_handler.c cache.c dashboard.c acme.c $(UNITY_SRC)
|
||||
$(CC) $(CFLAGS) -I. -I$(UNIT_TEST_DIR)/../unity -o $@ $(UNIT_TEST_DIR)/test_server.c server.c http.c static.c log.c config.c multipart.c tls.c http2.c access_log.c websocket.c platform.c middleware.c plugin.c proxy.c proxy_tls.c healthcheck.c sse.c fastcgi.c fcgi_handler.c cache.c dashboard.c acme.c $(UNITY_SRC) $(LDFLAGS)
|
||||
|
||||
$(UNIT_TEST_DIR)/test_multipart: $(UNIT_TEST_DIR)/test_multipart.c multipart.c $(UNITY_SRC)
|
||||
$(CC) $(CFLAGS) -I. -I$(UNIT_TEST_DIR)/../unity -o $@ $(UNIT_TEST_DIR)/test_multipart.c multipart.c $(UNITY_SRC) -lm
|
||||
@ -99,8 +99,8 @@ $(UNIT_TEST_DIR)/test_multipart: $(UNIT_TEST_DIR)/test_multipart.c multipart.c $
|
||||
$(UNIT_TEST_DIR)/test_http: $(UNIT_TEST_DIR)/test_http.c http.c log.c $(UNITY_SRC)
|
||||
$(CC) $(CFLAGS) -I. -I$(UNIT_TEST_DIR)/../unity -o $@ $(UNIT_TEST_DIR)/test_http.c http.c log.c $(UNITY_SRC) -lm
|
||||
|
||||
$(UNIT_TEST_DIR)/test_static: $(UNIT_TEST_DIR)/test_static.c http.c log.c tls.c access_log.c platform.c cache.c $(UNITY_SRC)
|
||||
$(CC) $(CFLAGS) -I. -I$(UNIT_TEST_DIR)/../unity -o $@ $(UNIT_TEST_DIR)/test_static.c http.c log.c tls.c access_log.c platform.c cache.c $(UNITY_SRC) $(LDFLAGS)
|
||||
$(UNIT_TEST_DIR)/test_static: $(UNIT_TEST_DIR)/test_static.c http.c log.c tls.c access_log.c platform.c cache.c acme.c $(UNITY_SRC)
|
||||
$(CC) $(CFLAGS) -I. -I$(UNIT_TEST_DIR)/../unity -o $@ $(UNIT_TEST_DIR)/test_static.c http.c log.c tls.c access_log.c platform.c cache.c acme.c $(UNITY_SRC) $(LDFLAGS)
|
||||
|
||||
$(UNIT_TEST_DIR)/test_websocket: $(UNIT_TEST_DIR)/test_websocket.c websocket.c log.c platform.c $(UNITY_SRC)
|
||||
$(CC) $(CFLAGS) -I. -I$(UNIT_TEST_DIR)/../unity -o $@ $(UNIT_TEST_DIR)/test_websocket.c websocket.c log.c platform.c $(UNITY_SRC) $(LDFLAGS)
|
||||
@ -150,6 +150,10 @@ $(UNIT_TEST_DIR)/test_cache: $(UNIT_TEST_DIR)/test_cache.c cache.c log.c $(UNITY
|
||||
$(UNIT_TEST_DIR)/test_dashboard: $(UNIT_TEST_DIR)/test_dashboard.c dashboard.c sse.c log.c $(UNITY_SRC)
|
||||
$(CC) $(CFLAGS) -I. -I$(UNIT_TEST_DIR)/../unity -o $@ $(UNIT_TEST_DIR)/test_dashboard.c dashboard.c sse.c log.c $(UNITY_SRC) $(LDFLAGS)
|
||||
|
||||
# ACME 测试
|
||||
$(UNIT_TEST_DIR)/test_acme: $(UNIT_TEST_DIR)/test_acme.c acme.c log.c $(UNITY_SRC)
|
||||
$(CC) $(CFLAGS) -I. -I$(UNIT_TEST_DIR)/../unity -o $@ $(UNIT_TEST_DIR)/test_acme.c acme.c log.c $(UNITY_SRC) $(LDFLAGS)
|
||||
|
||||
# 安装
|
||||
install: $(TARGET)
|
||||
install -d $(BINDIR)
|
||||
|
||||
258
acme.h
Normal file
258
acme.h
Normal file
@ -0,0 +1,258 @@
|
||||
/**
|
||||
* acme.h - ACME / Let's Encrypt 自动证书模块接口
|
||||
*
|
||||
* 实现 RFC 8555 ACME 协议客户端,支持 HTTP-01 挑战。
|
||||
* 自动完成证书签发和续期,无需手动配置。
|
||||
*
|
||||
* 设计:
|
||||
* - 独立 ACME 客户端上下文,管理账户、订单、证书
|
||||
* - JWS 签名使用 OpenSSL EVP 接口
|
||||
* - HTTP-01 挑战响应通过回调集成到服务器
|
||||
* - 后台协程定时检查证书过期并自动续期
|
||||
*
|
||||
* @author xfy
|
||||
*/
|
||||
|
||||
#ifndef COCOON_ACME_H
|
||||
#define COCOON_ACME_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <time.h>
|
||||
|
||||
/* ACME 目录端点结构 */
|
||||
typedef struct {
|
||||
char newNonce[512];
|
||||
char newAccount[512];
|
||||
char newOrder[512];
|
||||
char revokeCert[512];
|
||||
char keyChange[512];
|
||||
} acme_directory_t;
|
||||
|
||||
/* ACME 账户信息 */
|
||||
typedef struct {
|
||||
char kid[512]; /* 账户 URL (Key ID) */
|
||||
char contact[256]; /* 联系邮箱 */
|
||||
bool termsAgreed; /* 是否同意服务条款 */
|
||||
} acme_account_t;
|
||||
|
||||
/* ACME 授权 */
|
||||
typedef struct {
|
||||
char domain[256]; /* 授权域名 */
|
||||
char status[32]; /* pending / valid / invalid / deactivated / expired / revoked */
|
||||
char token[256]; /* HTTP-01 挑战 token */
|
||||
char uri[512]; /* 授权 URI */
|
||||
} acme_authz_t;
|
||||
|
||||
/* ACME 订单 */
|
||||
typedef struct {
|
||||
char uri[512]; /* 订单 URI */
|
||||
char status[32]; /* pending / ready / processing / valid / invalid */
|
||||
char finalize[512]; /* finalize 端点 */
|
||||
char certificate[512]; /* 证书下载端点 */
|
||||
time_t expires; /* 过期时间 */
|
||||
acme_authz_t *authz; /* 授权数组 */
|
||||
size_t num_authz; /* 授权数量 */
|
||||
} acme_order_t;
|
||||
|
||||
/* ACME 客户端上下文 */
|
||||
typedef struct acme_ctx acme_ctx_t;
|
||||
|
||||
/**
|
||||
* acme_create - 创建 ACME 客户端上下文
|
||||
*
|
||||
* @param directory_url ACME 目录 URL(如 Let's Encrypt 生产环境或测试环境)
|
||||
* @param account_key_pem 账户私钥 PEM 字符串(EC P-256),NULL 则自动生成
|
||||
* @return ACME 上下文,失败返回 NULL
|
||||
*/
|
||||
acme_ctx_t *acme_create(const char *directory_url, const char *account_key_pem);
|
||||
|
||||
/**
|
||||
* acme_destroy - 销毁 ACME 客户端上下文
|
||||
*
|
||||
* @param ctx ACME 上下文
|
||||
*/
|
||||
void acme_destroy(acme_ctx_t *ctx);
|
||||
|
||||
/**
|
||||
* acme_get_directory - 获取 ACME 服务器目录
|
||||
*
|
||||
* 向 directory_url 发送 GET 请求,解析返回的 JSON 目录。
|
||||
*
|
||||
* @param ctx ACME 上下文
|
||||
* @param out 输出目录结构
|
||||
* @return 0 成功,-1 失败
|
||||
*/
|
||||
int acme_get_directory(acme_ctx_t *ctx, acme_directory_t *out);
|
||||
|
||||
/**
|
||||
* acme_get_nonce - 获取新的 nonce
|
||||
*
|
||||
* ACME 协议要求每个 POST 请求都包含一个 Replay-Nonce。
|
||||
*
|
||||
* @param ctx ACME 上下文
|
||||
* @param nonce_url newNonce 端点 URL
|
||||
* @return 0 成功,-1 失败
|
||||
*/
|
||||
int acme_get_nonce(acme_ctx_t *ctx, const char *nonce_url);
|
||||
|
||||
/**
|
||||
* acme_create_account - 创建或查找 ACME 账户
|
||||
*
|
||||
* 如果账户已存在(服务器返回 200),则复用现有账户。
|
||||
*
|
||||
* @param ctx ACME 上下文
|
||||
* @param email 联系邮箱(如 "admin@example.com")
|
||||
* @param terms_agreed 是否同意服务条款
|
||||
* @return 0 成功,-1 失败
|
||||
*/
|
||||
int acme_create_account(acme_ctx_t *ctx, const char *email, bool terms_agreed);
|
||||
|
||||
/**
|
||||
* acme_create_order - 创建证书订单
|
||||
*
|
||||
* @param ctx ACME 上下文
|
||||
* @param domains 域名数组
|
||||
* @param num_domains 域名数量
|
||||
* @param out 输出订单结构(调用者负责释放 out->authz)
|
||||
* @return 0 成功,-1 失败
|
||||
*/
|
||||
int acme_create_order(acme_ctx_t *ctx, const char **domains, size_t num_domains,
|
||||
acme_order_t *out);
|
||||
|
||||
/**
|
||||
* acme_order_free - 释放订单结构中的动态内存
|
||||
*
|
||||
* @param order 订单结构
|
||||
*/
|
||||
void acme_order_free(acme_order_t *order);
|
||||
|
||||
/**
|
||||
* acme_fetch_authz - 获取授权详情和挑战
|
||||
*
|
||||
* @param ctx ACME 上下文
|
||||
* @param authz 授权结构(输入 uri,输出 token 等)
|
||||
* @return 0 成功,-1 失败
|
||||
*/
|
||||
int acme_fetch_authz(acme_ctx_t *ctx, acme_authz_t *authz);
|
||||
|
||||
/**
|
||||
* acme_respond_challenge - 响应 HTTP-01 挑战
|
||||
*
|
||||
* 通知 ACME 服务器可以验证挑战了。
|
||||
*
|
||||
* @param ctx ACME 上下文
|
||||
* @param authz 授权结构
|
||||
* @return 0 成功,-1 失败
|
||||
*/
|
||||
int acme_respond_challenge(acme_ctx_t *ctx, const acme_authz_t *authz);
|
||||
|
||||
/**
|
||||
* acme_poll_authz - 轮询授权状态直到完成或失败
|
||||
*
|
||||
* @param ctx ACME 上下文
|
||||
* @param authz 授权结构
|
||||
* @param timeout_ms 超时毫秒
|
||||
* @return 0 成功(valid),-1 失败
|
||||
*/
|
||||
int acme_poll_authz(acme_ctx_t *ctx, acme_authz_t *authz, int timeout_ms);
|
||||
|
||||
/**
|
||||
* acme_finalize_order - 发送 CSR 完成订单
|
||||
*
|
||||
* @param ctx ACME 上下文
|
||||
* @param order 订单结构
|
||||
* @param domains 域名数组
|
||||
* @param num_domains 域名数量
|
||||
* @return 0 成功,-1 失败
|
||||
*/
|
||||
int acme_finalize_order(acme_ctx_t *ctx, acme_order_t *order,
|
||||
const char **domains, size_t num_domains);
|
||||
|
||||
/**
|
||||
* acme_poll_order - 轮询订单状态直到证书就绪
|
||||
*
|
||||
* @param ctx ACME 上下文
|
||||
* @param order 订单结构
|
||||
* @param timeout_ms 超时毫秒
|
||||
* @return 0 成功(valid),-1 失败
|
||||
*/
|
||||
int acme_poll_order(acme_ctx_t *ctx, acme_order_t *order, int timeout_ms);
|
||||
|
||||
/**
|
||||
* acme_download_certificate - 下载签发的证书链
|
||||
*
|
||||
* @param ctx ACME 上下文
|
||||
* @param cert_url 证书下载 URL
|
||||
* @param out_pem 输出 PEM 证书链(调用者负责 free)
|
||||
* @return 0 成功,-1 失败
|
||||
*/
|
||||
int acme_download_certificate(acme_ctx_t *ctx, const char *cert_url, char **out_pem);
|
||||
|
||||
/**
|
||||
* acme_get_thumbprint - 获取账户密钥的 JWK Thumbprint
|
||||
*
|
||||
* HTTP-01 挑战响应内容 = token + "." + base64url(thumbprint)
|
||||
*
|
||||
* @param ctx ACME 上下文
|
||||
* @param out 输出 thumbprint(base64url 编码,调用者负责 free)
|
||||
* @return 0 成功,-1 失败
|
||||
*/
|
||||
int acme_get_thumbprint(acme_ctx_t *ctx, char **out);
|
||||
|
||||
/**
|
||||
* acme_issue_certificate - 一键签发证书(完整流程)
|
||||
*
|
||||
* 封装目录发现 → 创建账户 → 创建订单 → 挑战 → 下载证书 的完整流程。
|
||||
* 此函数会阻塞,需要在协程中调用。
|
||||
*
|
||||
* @param ctx ACME 上下文
|
||||
* @param domains 域名数组
|
||||
* @param num_domains 域名数量
|
||||
* @param email 联系邮箱
|
||||
* @param cert_pem 输出证书 PEM(调用者负责 free)
|
||||
* @param key_pem 输出私钥 PEM(调用者负责 free)
|
||||
* @return 0 成功,-1 失败
|
||||
*/
|
||||
int acme_issue_certificate(acme_ctx_t *ctx, const char **domains, size_t num_domains,
|
||||
const char *email, char **cert_pem, char **key_pem);
|
||||
|
||||
/* HTTP 请求辅助(内部使用,暴露给单元测试) */
|
||||
typedef struct {
|
||||
int status;
|
||||
char *body;
|
||||
size_t body_len;
|
||||
char location[512];
|
||||
char replay_nonce[256];
|
||||
} acme_http_response_t;
|
||||
|
||||
/**
|
||||
* acme_http_post_jws - 发送带 JWS 签名的 POST 请求
|
||||
*
|
||||
* @param ctx ACME 上下文
|
||||
* @param url 请求 URL
|
||||
* @param payload JSON payload(可为 NULL 表示空)
|
||||
* @param out 输出响应
|
||||
* @return 0 成功,-1 失败
|
||||
*/
|
||||
int acme_http_post_jws(acme_ctx_t *ctx, const char *url, const char *payload,
|
||||
acme_http_response_t *out);
|
||||
|
||||
/**
|
||||
* acme_http_get - 发送 GET 请求
|
||||
*
|
||||
* @param ctx ACME 上下文
|
||||
* @param url 请求 URL
|
||||
* @param out 输出响应
|
||||
* @return 0 成功,-1 失败
|
||||
*/
|
||||
int acme_http_get(acme_ctx_t *ctx, const char *url, acme_http_response_t *out);
|
||||
|
||||
/**
|
||||
* acme_http_response_free - 释放 HTTP 响应资源
|
||||
*
|
||||
* @param resp 响应结构
|
||||
*/
|
||||
void acme_http_response_free(acme_http_response_t *resp);
|
||||
|
||||
#endif /* COCOON_ACME_H */
|
||||
154
tests/unit/test_acme.c
Normal file
154
tests/unit/test_acme.c
Normal file
@ -0,0 +1,154 @@
|
||||
/**
|
||||
* test_acme.c - ACME 模块单元测试
|
||||
*
|
||||
* @author xfy
|
||||
*/
|
||||
|
||||
#include "unity.h"
|
||||
#include "acme.h"
|
||||
#include "log.h"
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/pem.h>
|
||||
|
||||
/* 测试用的 EC 私钥 PEM (有效格式但随机数据) */
|
||||
static const char *TEST_KEY_PEM =
|
||||
"-----BEGIN EC PRIVATE KEY-----\n"
|
||||
"MHQCAQEEIBHkPjHK+3VjZV+p7rKDcAEO2db7t4V1b01QfZVJHfHmoAcGBSuBBAAK\n"
|
||||
"oUQDQgAEX0hOmL0zQ0y1P1uOHZPVjR1xBZQlpVZ1PjY2V0tZ0V0tZ0V0tZ0V0\n"
|
||||
"tZ0V0tZ0V0tZ0V0tZ0V0tZ0V0tZw==\n"
|
||||
"-----END EC PRIVATE KEY-----\n";
|
||||
|
||||
void setUp(void) {
|
||||
log_set_level(LOG_LEVEL_ERROR);
|
||||
}
|
||||
|
||||
void tearDown(void) {
|
||||
}
|
||||
|
||||
/* 测试: acme_create / acme_destroy */
|
||||
void test_acme_create_destroy(void) {
|
||||
acme_ctx_t *ctx = acme_create("https://acme-staging-v02.api.letsencrypt.org/directory", NULL);
|
||||
TEST_ASSERT_NOT_NULL(ctx);
|
||||
acme_destroy(ctx);
|
||||
}
|
||||
|
||||
/* 测试: acme_create 自带私钥 */
|
||||
void test_acme_create_with_key(void) {
|
||||
acme_ctx_t *ctx = acme_create("https://acme-staging-v02.api.letsencrypt.org/directory", TEST_KEY_PEM);
|
||||
/* 这个 PEM 是伪造的,可能解析失败,但至少测试了代码路径 */
|
||||
acme_destroy(ctx);
|
||||
}
|
||||
|
||||
/* 测试: acme_get_directory (需要网络,可能失败) */
|
||||
void test_acme_get_directory(void) {
|
||||
acme_ctx_t *ctx = acme_create("https://acme-staging-v02.api.letsencrypt.org/directory", NULL);
|
||||
TEST_ASSERT_NOT_NULL(ctx);
|
||||
|
||||
acme_directory_t dir;
|
||||
int ret = acme_get_directory(ctx, &dir);
|
||||
|
||||
/* 网络可能不可用,允许失败 */
|
||||
if (ret == 0) {
|
||||
TEST_ASSERT_TRUE(dir.newNonce[0] != '\0');
|
||||
TEST_ASSERT_TRUE(dir.newAccount[0] != '\0');
|
||||
TEST_ASSERT_TRUE(dir.newOrder[0] != '\0');
|
||||
}
|
||||
|
||||
acme_destroy(ctx);
|
||||
}
|
||||
|
||||
/* 测试: acme_get_nonce (需要网络) */
|
||||
void test_acme_get_nonce(void) {
|
||||
acme_ctx_t *ctx = acme_create("https://acme-staging-v02.api.letsencrypt.org/directory", NULL);
|
||||
TEST_ASSERT_NOT_NULL(ctx);
|
||||
|
||||
/* 先获取目录 */
|
||||
acme_directory_t dir;
|
||||
if (acme_get_directory(ctx, &dir) == 0) {
|
||||
int ret = acme_get_nonce(ctx, dir.newNonce);
|
||||
/* nonce 获取成功或失败都可以接受(网络问题) */
|
||||
(void)ret;
|
||||
}
|
||||
|
||||
acme_destroy(ctx);
|
||||
}
|
||||
|
||||
/* 测试: acme_create_account (需要网络) */
|
||||
void test_acme_create_account(void) {
|
||||
acme_ctx_t *ctx = acme_create("https://acme-staging-v02.api.letsencrypt.org/directory", NULL);
|
||||
TEST_ASSERT_NOT_NULL(ctx);
|
||||
|
||||
/* 获取目录 */
|
||||
acme_directory_t dir;
|
||||
if (acme_get_directory(ctx, &dir) == 0) {
|
||||
int ret = acme_create_account(ctx, "test@example.com", true);
|
||||
/* 可能 200(已有账户) 或 201(新账户),网络问题也可能失败 */
|
||||
(void)ret;
|
||||
}
|
||||
|
||||
acme_destroy(ctx);
|
||||
}
|
||||
|
||||
/* 测试: 订单结构内存管理 */
|
||||
void test_acme_order_free(void) {
|
||||
acme_order_t order;
|
||||
memset(&order, 0, sizeof(order));
|
||||
|
||||
/* 模拟有 authz */
|
||||
order.authz = (acme_authz_t *)calloc(2, sizeof(acme_authz_t));
|
||||
order.num_authz = 2;
|
||||
strcpy(order.authz[0].domain, "example.com");
|
||||
strcpy(order.authz[0].token, "token1");
|
||||
strcpy(order.authz[1].domain, "www.example.com");
|
||||
strcpy(order.authz[1].token, "token2");
|
||||
|
||||
acme_order_free(&order);
|
||||
TEST_ASSERT_NULL(order.authz);
|
||||
TEST_ASSERT_EQUAL_size_t(0, order.num_authz);
|
||||
}
|
||||
|
||||
/* 测试: HTTP 响应内存释放 */
|
||||
void test_acme_http_response_free(void) {
|
||||
acme_http_response_t resp;
|
||||
memset(&resp, 0, sizeof(resp));
|
||||
resp.body = strdup("test body");
|
||||
resp.body_len = 9;
|
||||
|
||||
acme_http_response_free(&resp);
|
||||
TEST_ASSERT_NULL(resp.body);
|
||||
TEST_ASSERT_EQUAL_size_t(0, resp.body_len);
|
||||
}
|
||||
|
||||
/* 测试: acme_issue_certificate 参数检查 (不实际执行) */
|
||||
void test_acme_issue_certificate_params(void) {
|
||||
/* 这个测试主要是确保函数签名正确,不会崩溃 */
|
||||
/* 实际调用需要完整的 ACME 交互,不适合单元测试 */
|
||||
TEST_ASSERT_TRUE(1);
|
||||
}
|
||||
|
||||
/* 测试: 目录 URL 存储 */
|
||||
void test_acme_directory_url(void) {
|
||||
const char *url = "https://example.com/acme/directory";
|
||||
acme_ctx_t *ctx = acme_create(url, NULL);
|
||||
TEST_ASSERT_NOT_NULL(ctx);
|
||||
/* 内部字段不直接暴露,但至少能创建成功 */
|
||||
acme_destroy(ctx);
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
UNITY_BEGIN();
|
||||
|
||||
RUN_TEST(test_acme_create_destroy);
|
||||
RUN_TEST(test_acme_create_with_key);
|
||||
RUN_TEST(test_acme_get_directory);
|
||||
RUN_TEST(test_acme_get_nonce);
|
||||
RUN_TEST(test_acme_create_account);
|
||||
RUN_TEST(test_acme_order_free);
|
||||
RUN_TEST(test_acme_http_response_free);
|
||||
RUN_TEST(test_acme_issue_certificate_params);
|
||||
RUN_TEST(test_acme_directory_url);
|
||||
|
||||
return UNITY_END();
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user