
PIV、CAC、YubiKeyトークン向けのZigハードウェアセキュリティモジュールライブラリ(PC/SC経由)。証明書、PIN管理、署名、復号に対応。
Zig向けのハードウェアセキュリティモジュール(HSM)ライブラリ。PC/SC経由でPIV、CAC、YubiKeyトークンにアクセスします。
| 項目 | 情報 |
|---|---|
| API安定性 | 開発中 |
| Zigバージョン | 0.16.0 |
| 対応プラットフォーム | Linux、macOS、Windows |
| ライセンス | MIT |
PIVサポート(NIST SP 800-73-4)
CACサポート(Common Access Card)
YubiKeyサポート
セキュリティ
anyerrorなし)並列操作(thread_pool経由)
-Denable_tp=true); 無効化するには -Denable_tp=falselibpcsclite-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カプセル化/非カプセル化)が追加されます。このフラグはデフォルトでオフになっており、オーバーヘッドはゼロです。fips依存関係は-Dfips=trueの場合にのみ解決されます。
# デフォルトビルド — std.cryptoバックエンド、OpenSSLリンクなし
zig build test
# FIPSビルド — OpenSSL FIPSプロバイダ(最初にfipsディレクトリで`make deps`を実行)
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は、HSM操作のスパントレースを行うためのオプトインのOpenTelemetryシームを提供します。このシームはデフォルトでオフになっており、オフのときはオーバーヘッドゼロです。ビルドではotel依存関係が解決されず、すべてのヘルパーはコンパイル時に既知のno-opにコンパイルされ、生成されるアーティファクトにはotelやobservabilityのリンクされたシンボルは含まれません。
zig build test # デフォルト: -Dwith_otel=false (no-op)
zig build test -Dwith_otel=true # オプトイン: スパンが出力される
有効にすると、すべての公開Token.sign / Token.decrypt呼び出しは、2つの属性を持つhsm.{operation}スパン(例: hsm.sign、hsm.decrypt)を出力します:
crypto.algorithm — アルゴリズムタグ(例: ecdsa_p256、rsa2048_pkcs1v15)crypto.key.id — スロット識別子(例: authentication、signature、key_management、card_auth)このシームは1つのルールに基づいて設計されています:暗号素材を決してエクスポートしない。スパン属性はオフホスト(通常はOTLPコレクター)にエクスポートされ、HSM操作自体よりもアクセス制御が弱い可能性があるトレース/ログに格納されます。
hsm.observability.startHsmOperationSpanに渡す前に、それらを編集またはハッシュしてください。const hsm = @import("hsm");
pub fn main() !void {
// Otel値を安定したアドレスに配置 — main()のスタックフレーム内の`var`(プロセスライフタイム)またはヒープ割り当て。
// initヘルパーは`-Dwith_otel=true`の場合のみ存在します。無効ビルドでは、
// hsm.observability_initは空の構造体に解決され、このブロックはno-opにコンパイルされます。
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`スパンを出力するようになります。
}
このシームは、OpenTelemetry環境変数仕様に従ってOTEL_*環境変数(サンプラー、エクスポーターエンドポイント、service.nameなど)を読み取ります。デフォルト: 5%のparentbased_traceidratioサンプラー、http://localhost:4318へのOTLP/HTTP-protobufエクスポーター。
完全な統合レシピ(ビルド配線、テストハーネス、no-op検証)は、otelロールアウトリポジトリに文書化されています:otel/docs/integration/RECIPE.md。
シームに触れる変更をマージする前に、3つのゲートを通過させる必要があります:
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 | Debug: trueRelease: 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リクエスト以上 |