一个基于 Zig 的硬件安全模块 (HSM) 库,通过 PC/SC 访问 PIV、CAC 和 YubiKey 令牌。
| 方面 | 信息 |
|---|---|
| API 稳定性 | 开发中 |
| Zig 版本 | 0.16.0 |
| 平台 | Linux、macOS、Windows |
| 许可证 | MIT |
PIV 支持 (NIST SP 800-73-4)
CAC 支持(通用访问卡)
YubiKey 支持
安全性
anyerror)并行操作(通过 thread_pool)
-Denable_tp=true);使用 -Denable_tp=false 禁用libpcsclite-dev(Debian/Ubuntu)或 pcsc-lite-devel(Fedora)# Debian/Ubuntu
sudo apt-get install libpcsclite-dev pcscd
# Fedora/RHEL
sudo dnf install pcsc-lite-devel pcsc-lite
# 启动 PC/SC 守护进程
sudo systemctl start pcscd
sudo systemctl enable pcscd
# 构建并运行单元测试
make
# 或直接使用 zig
zig build test
# 运行模拟器集成测试
zig build test -Dintegration_sim=true
# 构建示例(需要安装 PC/SC 库)
zig build examples -Dlink_pcsc=true
使用 -Dfips=true,每个与安全相关的加密原语通过 crypto_backend 接缝路由到已链接且经过验证的 OpenSSL 3.x FIPS 提供程序,并且模拟器获得仅在 FIPS 模式下可用的后量子 PIV 令牌操作(ML-DSA-65 签名、ML-KEM-768 封装/解封装)。该标志默认关闭,零开销;仅在 -Dfips=true 下解析 fips 依赖关系。
# 默认构建 — std.crypto 后端,无 OpenSSL 链接
zig build test
# FIPS 构建 — OpenSSL FIPS 提供程序(先运行 `make deps` from fips)
OPENSSL_CONF=/usr/local/ssl/ssl/openssl.cnf \
zig build test -Dfips=true -Dopenssl_path=/usr/local/ssl
# 两种模式一次完成
make test-dual
参见 docs/FIPS.md 了解接缝设计、FIPS 豁免策略和 PQC 令牌操作。
安全说明: allow_pcsc_env_override 选项默认禁用,以防止恶意库注入攻击 (CWE-427)。仅在开发/测试时启用:
# 生产构建(安全,环境变量被忽略)
zig build
# 开发构建(允许 HSM_PCSC_LIB_PATH)
zig build -Dallow_pcsc_env_override=true
启用后,库在加载前会经过验证:
const std = @import("std");
const hsm = @import("hsm");
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
// 列出可用令牌
const tokens = try hsm.listTokens(allocator, false);
defer {
for (tokens) |*t| t.deinit();
allocator.free(tokens);
}
if (tokens.len == 0) {
std.debug.print("未找到令牌\n", .{});
return;
}
// 打开第一个令牌
var token = try hsm.openToken(allocator, tokens[0].id, .{});
defer token.close();
// 验证 PIN
try token.verifyPin("123456");
// 读取证书
const cert = try token.getCertificate(.authentication);
defer allocator.free(cert);
std.debug.print("证书:{} 字节\n", .{cert.len});
// 签名摘要
var digest: [32]u8 = undefined;
std.crypto.hash.sha2.Sha256.hash("Hello, PIV!", &digest, .{});
const signature = try token.sign(.authentication, .ecdsa_p256, &digest);
defer allocator.free(signature);
std.debug.print("签名:{} 字节\n", .{signature.len});
}
所有操作返回 HsmError 中的错误:
PcscUnavailable、ReaderGone、CardRemoved、TimeoutPinIncorrect、PinLocked、PinLengthInvalidNotSupported、SlotNotFound、AlgorithmNotSupportedInvalidTlv、CertificateNotFound、InvalidDigestLengthSecurityConditionNotSatisfied、parallel 模块使用 thread_pool 库提供批量 HSM 操作。它作为一个独立的 Zig 模块构建,在 -Denable_tp=true(默认)时可用。设置 -Denable_tp=false 将完全编译出该接缝;公共函数仍然存在并回退到顺序实现。
低于阈值时,操作顺序执行,无线程池开销。底层的 PKCS#11 调用是脚手架;通过在 src/parallel.zig 中实现 signData、decryptData、discoverToken 和 retrieveCert 函数来集成您的 PKCS#11 后端。
# 运行所有单元测试
zig build test
该库包含一个 PIV 卡模拟器,用于无硬件测试:
# 运行模拟器测试
zig build test -Dintegration_sim=true
模拟器使用嵌入式测试密钥(参见 src/sim/key_material.zig)。无需外部密钥文件或环境变量。
用于真实硬件测试:
# 只读测试(证书读取)
HSM_HIL=1 zig build test -Dhsm_hil=true
# 需要 PIN 的测试
HSM_HIL=1 HSM_PIN=123456 zig build test -Dhsm_hil=true
# 危险测试(写操作) - 请谨慎使用
HSM_HIL=1 HSM_PIN=123456 HSM_DANGEROUS=1 zig build test -Dhsm_hil=true
# 持续模糊测试(手动停止)
zig build test --fuzz -- --test-filter fuzz
# 构建示例(需要安装 PC/SC 库)
zig build examples -Dlink_pcsc=true
# 列出令牌
./zig-out/bin/list_tokens
./zig-out/bin/list_tokens --sim # 使用模拟器
# 使用 PIV 签名(PIN 通过交互式 TTY 提示输入)
./zig-out/bin/piv_sign
./zig-out/bin/piv_sign --sim
# 或通过环境变量提供 PIN(安全性较低)
HSM_PIN=123456 ./zig-out/bin/piv_sign --sim
PIN 处理:PIN 永远不会存储在令牌结构中,并且在使用后清零。
TLV 解析:所有 TLV 解析都有深度 (10) 和长度 (64KB) 限制,以防止资源耗尽。
错误处理:未知状态词导致显式错误,而非静默失败。
日志记录:调试日志绝不包含敏感数据(PIN、密钥等)。
内存:使用 hsm.zeroize() 清零敏感缓冲区,可防止编译器优化。
PC/SC 库加载:库从受信任的绝对路径加载 PC/SC。可以通过 HSM_PCSC_LIB_PATH 进行显式覆盖(必须是绝对路径)。
hsm 附带一个可选加入的 OpenTelemetry 接缝,用于 HSM 操作的追踪跨度。该接缝默认关闭,关闭时产生零开销——构建永远不会解析 otel 依赖项,每个辅助函数都会编译为编译时已知的空操作,生成的产品不包含任何 otel 或 observability 链接符号。
zig build test # 默认:-Dwith_otel=false(空操作)
zig build test -Dwith_otel=true # 可选加入:发送跨度
启用后,每个公共 Token.sign / Token.decrypt 调用都会发出一个 hsm.{operation} 跨度(例如 hsm.sign、hsm.decrypt),包含两个属性:
crypto.algorithm — 算法标签(例如 ecdsa_p256、rsa2048_pkcs1v15)。crypto.key.id — 插槽标识符(例如 authentication、signature、key_management、card_auth)。该接缝围绕一个规则设计:绝不导出加密材料。跨度属性被导出到外部主机(通常是 OTLP 收集器),并最终进入可能具有比 HSM 操作本身更弱访问控制的追踪/日志中。
hsm.observability.startHsmOperationSpan 之前对其进行编辑或哈希处理。const hsm = @import("hsm");
pub fn main() !void {
// 将 Otel 值存放在稳定地址上——可以是 main() 栈帧中的 `var`
// 以供进程生命周期使用,也可以是堆分配。该初始化辅助函数仅在
// `-Dwith_otel=true` 时存在;在禁用的构建下,
// hsm.observability_init 解析为空结构体,此块编译为空操作。
if (comptime hsm.observability.enabled) {
var otel = try hsm.observability_init.Otel.init(allocator, "my-service");
defer otel.deinit();
otel.installGlobals();
}
// ... main 的其余部分,包括任何 hsm.Token.sign 调用——每个调用
// 现在都会发出一个附加到您服务的 `hsm.sign` 跨度。
}
该接缝读取 OTEL_* 环境变量(采样器、导出程序端点、service.name 等),遵循 OpenTelemetry 环境变量规范。默认值:基于父级的 traceidratio 采样器,采样率为 5%,OTLP/HTTP-protobuf 导出程序到 http://localhost:4318。
完整的集成配方(构建布线、测试框架、空操作验证器)记录在 otel 部署仓库中:
otel/docs/integration/RECIPE.md。
在合并影响接缝的更改之前,必须通过三道验证门:
zig build test # 默认标志 = false
zig build test -Dwith_otel=true # 标志开启
scripts/verify-consumer-noop.sh # 符号泄漏检查
verify-consumer-noop.sh 验证门使用 -Dwith_otel=false 构建库,并使用 nm --defined-only 遍历 zig-out/,如果任何 otel/observability 符号残留在产物中则失败。它镜像了跨仓库的 otel/scripts/verify-consumer-noop.sh(仅在兄弟仓库布局 <root>/otel//<root>/hsm/ 中运行),因此没有该布局的贡献者仍然可以在本地对接缝进行验证。
src/
├── hsm.zig # 主 API 和类型
├── root.zig # 模块入口点
├── apdu.zig # APDU 编码/解码
├── tlv.zig # BER-TLV 解析
├── parallel.zig # 并行操作(thread_pool)
├── pcsc/
│ └── pcsc.zig # PC/SC 传输层(Linux/macOS/Windows)
├── piv/
│ └── piv.zig # PIV 实现
├── cac/
│ └── cac.zig # CAC 实现
├── yubikey/
│ └── yubikey.zig # YubiKey 检测
└── sim/
├── sim.zig # 模拟器入口点
├── transport.zig # 模拟传输层
├── piv_card.zig # 模拟 PIV 卡
└── key_material.zig # 嵌入式测试密钥
tests/
├── integration.zig # 基本集成测试
├── sim_integration.zig # 模拟器集成测试
└── hil_tests.zig # 硬件在环测试
examples/
├── list_tokens.zig # 令牌发现示例
└── piv_sign.zig # 签名示例
参见 CONTRIBUTING.md 获取指南。
MIT 许可证 - 详见 LICENSE。
基于 Zig 0.16.0 构建 | PIV | CAC | YubiKey | PC/SC
| 选项 | 默认值 | 描述 | 安全影响 |
|---|
integration_sim | false | 运行模拟器集成测试 | 无 |
hsm_hil | false | 运行硬件在环测试(需要真实令牌) | 无 |
link_pcsc | false | 链接示例的 PC/SC 库 | 无 |
include_simulator | 调试:true发布: false | 包含带有测试密钥的 PIV 模拟器 | CWE-321:在生产中使用测试密钥 |
allow_pcsc_env_override | false | 安全风险:允许 HSM_PCSC_LIB_PATH 环境变量 | CWE-427:不可信库加载 |
fips | false | 将安全加密路由到已链接的 FIPS-140-3 提供程序(关闭时零开销) | 关闭时无影响 |
openssl_path | (未设置) | 非标准安装的 OpenSSL 安装前缀(例如 /usr/local/ssl) | 无 |
| 类型 | 描述 |
|---|
TokenKind | 令牌类型:.piv、.cac、.yubikey、.unknown |
TokenInfo | 发现的令牌元数据 |
Token | 用于操作的已打开令牌句柄 |
Slot | 密钥插槽:.authentication (9A)、.signature (9C)、.key_management (9D)、.card_auth (9E) |
Algorithm | 加密算法:.ecdsa_p256、.ecdsa_p384、.rsa2048_pkcs1v15 等 |
Capabilities | 令牌能力标志 |
PcscScope | PC/SC 上下文范围:.user(默认)、.system |
| 函数 | 描述 |
|---|
listTokens(allocator, use_sim) | 列出可用令牌 |
openToken(allocator, id, opts) | 按 ID 打开令牌 |
token.reconnect() | 重新连接到令牌并重置认证状态 |
token.verifyPin(pin) | 验证 PIN |
token.changePin(old, new) | 修改 PIN |
token.unblockPin(puk, new_pin) | 使用 PUK 解锁 PIN |
token.getCertificate(slot) | 获取 DER 编码的证书 |
token.sign(slot, alg, digest) | 签名摘要 |
token.decrypt(slot, alg, ciphertext) | 解密数据 |
token.capabilities() | 获取令牌能力 |
token.close() | 关闭令牌连接 |
AuthenticationFailed| 函数 | 描述 | 阈值 |
|---|
parallelSign(allocator, inputs, results) | 跨插槽批量签名 | 2+ 个项目 |
parallelDecrypt(allocator, inputs, results) | 跨密钥批量解密 | 2+ 个项目 |
parallelTokenDiscover(readers, results) | 跨读卡器令牌发现 | 2+ 个读卡器 |
parallelCertRetrieve(allocator, requests, results) | 跨插槽证书检索 | 2+ 个请求 |