开源之夏05:命令行应用与SDF接口集成

进入九月,核心工作转向命令行应用的开发、69 个 SDF 接口的全面对齐,以及将新开发的 TSAPI_SDF_* 接口集成到 Tongsuo 中。

命令行应用开发

Tongsuo 的命令行框架

Tongsuo/OpenSSL 的命令行工具采用选项表驱动 + 枚举令牌 + 自动 help 生成的模式,遵循 POSIX/GNU 约定。curl、ffmpeg 等经典工具都使用类似理念,各语言生态也有对应实现(Python 的 argparse/click、Go 的 cobra、Rust 的 clap)。

核心数据结构:

1
2
3
4
5
6
typedef struct options_st {
const char *name; // 选项名
int retval; // 枚举令牌
int valtype; // 值类型:s(字符串), n(数字), p(正数), <(输入文件) 等
const char *helpstr; // 帮助文本
} OPTIONS;

主函数骨架:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
typedef enum { OPT_X_FIRST = 100, OPT_NAME, OPT_COUNT, OPT_QUIET } OPTION_VALUES;

static const OPTIONS echo_options[] = {
{ OPT_HELP_STR, 1, '-', "Usage: openssl echo [options]\n" },
{ "name", OPT_NAME, 's', "Name to greet" },
{ "count", OPT_COUNT, 'n', "Number of times to print" },
{ "quiet", OPT_QUIET, '-', "Suppress output" },
{ NULL }
};

int echo_main(int argc, char **argv) {
if (!opt_init(argc, argv, echo_options)) return 1;
opt_begin();
int opt;
while ((opt = opt_next()) != OPT_EOF) {
switch (opt) {
case OPT_HELP: opt_help(echo_options); return 0;
case OPT_NAME: name = opt_arg(); break;
// ...
}
}
// 主逻辑
}

对比手写 strcmp(argv[i], "-n") 的方式,这种框架的好处是:选项与帮助文档一处维护(不用改两处)、自动类型校验、分节/续行统一排版、声明式语义清晰。

为什么是 xxx_main?

apps/progs.pl 脚本通过正则 int ([a-z_][a-z0-9_]*)_main(int argc, ...) 自动扫描所有命令源文件,捕获到的前缀即为子命令名。生成的 progs.c 中包含全局注册表:

1
{ FT_general, "echo", echo_main, echo_options, NULL, NULL },

openssl.c 启动时构建命令哈希表,do_cmd() 以 O(1) 查表后调用对应函数指针。这就是为什么命名必须是 xxx_main + xxx_options 的形式。

为什么不做成 20 个独立可执行文件?四个硬指标:

  1. 兼容性:全世界脚本里都是 openssl req ...,不能突然变成 openssl-req
  2. 体积:静态链接 1 份 libcrypto 比 20 份省一个数量级
  3. 性能:哈希表+函数指针 O(1) 分发,比 dlopen 启动快得多
  4. 维护:约定优于配置,零手工列表

命令行功能设计

命令行的功能实现依赖三层调用链:

1
2
TSAPI_* → TSAPI_SDF_* → SDF_*
(业务) (薄转发) (底层)

在开发过程中发现了一个遗留问题:TSAPI 层”写穿”了。按照工程设计,SDF 层的 sdf_lib.c 使用函数映射表构建抽象,让 TSAPI 层屏蔽厂商细节。但前人在 TSAPI 中直接使用了厂商的 sdfe_api.h 符号(如 SDFE_LoginUsr),导致没有硬件就编译报错,TSAPI 和 SDF 层耦合在一起。

破案后发现,之前的蚂蚁密码卡适配根本没怎么遵循 SDF 标准——身份认证通过 login 实现而不是 GetPrivateKeyAccessRight。需要做的是在 SDF 层为所有 TSAPI 需要的功能提供标准化通用接口,把厂商扩展类型限制在 SDF 层内部,TSAPI 层不再 include sdfe_api.h。

exec 函数族的巧用

实现命令行应用过程中,可以直接使用 exec(3) 函数族套壳调用:

1
2
3
#include <unistd.h>
char *argv[] = { "ls", "-l", NULL };
execvp("ls", argv); // 自动在 PATH 中找程序

Unix 提供 execl/execv/execle/execve/execlp/execvp 六种变体,按”参数形式(l/v)× 是否指定环境(- /e)× 是否搜 PATH(- /p)”形成 2×2 矩阵,外加 GNU 扩展 execvpe 补全。

接口测试与集成

测试框架

Tongsuo 自带集成测试框架。util/libcrypto.num 中已经列出了所有 TSAPI_SDF_* 符号及其编号,确保导出接口在 ABI 层面可追踪。测试文件(如 test/sm4_internal_test.c)通过 ADD_TEST(test_xxx) 注册测试函数,make test 或 ninja test 自动批量运行。

为 SDF 模块添加测试的步骤:

  1. 在 test/ 下新建 sdf_internal_test.c
  2. 编写测试函数并用 ADD_TEST 注册
  3. 在 test/build.info 中包含该文件
  4. 编译后 make test 自动执行

69 个接口的全面对齐

对照 GM/T 0018-2023 标准,整理和清点了所有 SDF 接口,包括:

  • 设备管理类(OpenDevice, CloseDevice, GetDeviceInfo)
  • 会话管理类(OpenSession, CloseSession)
  • 对称加密类(Encrypt, Decrypt, CalculateMAC)
  • 哈希类(HashInit, HashUpdate, HashFinal)
  • 密钥管理类(GenerateKey, ImportKey, DestroyKey, 各种封装/解封)
  • 非对称运算类(签名、验签、加解密、密钥协商)
  • 随机数类(GenerateRandom)
  • 文件管理类(ReadFile, WriteFile, CreateFile, DeleteFile)
  • 访问控制类(Get/ReleasePrivateKeyAccessRight)
  • 验证测试类(最后 12 个接口)

后 12 个是验证测试类接口,功能由厂商设备实现,但为了测试其他标准 SDF 接口需要在软实现中提供几个。

符号导出验证

按照 2012 标准,编译完成后应导出 45 个标准接口。通过 nm 工具检查编译产物的符号表,确认所有 TSAPI_SDF_* 接口都正确导出且可见。

接口集成的成功节点

9 月 24 日,在导师帮助下,新增的 TSAPI_SDF_* 接口成功集成到了 Tongsuo 中。之后只需 #include <openssl/sdf.h> 就可以直接使用。

集成过程中使用了调试技巧:添加 --debug 编译选项后可以用 gdb 单步跟踪整个调用链:

1
./Configure --debug enable-sdf-lib enable-sdf-lib-dynamic no-shared

代码格式化规范

导师给了几个实用的提醒:

  1. 括号风格:Google 风格 int function { vs Tongsuo 源码风格 int function \n {,两种都行但保持一致。
  2. 宏定义中 # 后不要带空格(#define 而非 # define),与工程保持一致。
  3. 源文件中的意外空格、空行——不影响功能,但在开源项目中要重视。现代工具(VSCode 自带格式化、pre-commit hook 自动检查)可以解决大部分问题。

开源之夏05:命令行应用与SDF接口集成
https://47.108.189.123/2025/09/24/开源之夏/05-命令行应用与SDF接口集成/
Author
Dong
Posted on
September 24, 2025
Licensed under