Skip to content
KitploitKITPLOIT
ToolsBlog
Submit
ToolsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

··Feeds·Contact·Privacy·© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
hsm — Zig Hardware Security Module library for PIV, CAC, and YubiKey tokens via PC/SC. Supports certificates, PIN management, signing, and decryption. | Kitploit
Tools/GitLabGitLab/devnw/zig/hsm
Encryption/Decryption ToolsCryptographyHardware SecurityAuthentication
GitLabdevnw/zig/hsm

hsm

Zig Hardware Security Module library for PIV, CAC, and YubiKey tokens via PC/SC. Supports certificates, PIN management, signing, and decryption.

View Repository
2 months agoNot yet reviewed

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share

zhsm

A Hardware Security Module (HSM) library for Zig providing PC/SC access to PIV, CAC, and YubiKey tokens.

Status

AspectInfo
API StabilityDevelopment
Zig Version0.16.0
PlatformsLinux, macOS, Windows
LicenseMIT

Features

  • PIV Support (NIST SP 800-73-4)

    • Certificate retrieval (slots 9A, 9C, 9D, 9E)
    • PIN verification, change, and unblock
    • ECDSA P-256/P-384 signing
    • RSA 2048/3072/4096 signing
    • RSA decryption
  • CAC Support (Common Access Card)

    • Modern PIV-compatible CACs (full operations)
    • Legacy CAC PKI applet detection (applet selection only)
  • YubiKey Support

    • PIV applet operations
    • ATR-based detection
    • Optional management commands
  • Security

    • No secrets in logs (redacted debug mode)
    • Sensitive buffer zeroization
    • Strict TLV parsing with depth/length limits
    • Tight error types (no anyerror)
    • Session-bound PIN verification (ATR-based card swap detection)
  • Parallel Operations (via thread_pool)

    • Batch signing across multiple slots
    • Batch decryption across multiple keys
    • Parallel token discovery across readers
    • Parallel certificate retrieval across slots
    • Automatic sequential fallback below threshold (< 2 items)
    • Enabled by default (-Denable_tp=true); disable with -Denable_tp=false

Requirements

  • Zig 0.16.0 or compatible
  • PC/SC Library:
    • Linux: libpcsclite-dev (Debian/Ubuntu) or pcsc-lite-devel (Fedora)
    • macOS: Built-in PCSC.framework
    • Windows: Built-in Winscard.dll

Installing PC/SC on Linux

root@kitploit:~
# Debian/Ubuntu
sudo apt-get install libpcsclite-dev pcscd

# Fedora/RHEL
sudo dnf install pcsc-lite-devel pcsc-lite

# Start the PC/SC daemon
sudo systemctl start pcscd
sudo systemctl enable pcscd

Building

root@kitploit:~
# Build and run unit tests
make

# Or using zig directly
zig build test

# Run simulator integration tests
zig build test -Dintegration_sim=true

# Build examples (requires PC/SC library installed)
zig build examples -Dlink_pcsc=true

Build Options

FIPS-140-3 / PQC mode

With -Dfips=true, every security-relevant cryptographic primitive routes through a linked, validated OpenSSL 3.x FIPS provider via the crypto_backend seam, and the simulator gains post-quantum PIV token operations (ML-DSA-65 signing, ML-KEM-768 encap/decap) that are available only in FIPS mode. The flag is off by default with zero overhead; the fips dependency is resolved only under -Dfips=true.

root@kitploit:~
# Default build — std.crypto backend, no OpenSSL link
zig build test

# FIPS build — OpenSSL FIPS provider (run `make deps` from fips first)
OPENSSL_CONF=/usr/local/ssl/ssl/openssl.cnf \
  zig build test -Dfips=true -Dopenssl_path=/usr/local/ssl

# Both modes in one shot
make test-dual

See docs/FIPS.md for the seam design, the fips-exempt policy, and the PQC token operations.

Security Note: The allow_pcsc_env_override option is disabled by default to prevent malicious library injection attacks (CWE-427). Only enable this for development/testing:

root@kitploit:~
# Production build (secure, env var ignored)
zig build

# Development build (allows HSM_PCSC_LIB_PATH)
zig build -Dallow_pcsc_env_override=true

When enabled, libraries are validated before loading:

  • Must be absolute paths
  • Cannot be world-writable
  • Warns on group-writable files

Quick Start

root@kitploit:~
const std = @import("std");
const hsm = @import("hsm");

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();

    // List available tokens
    const tokens = try hsm.listTokens(allocator, false);
    defer {
        for (tokens) |*t| t.deinit();
        allocator.free(tokens);
    }

    if (tokens.len == 0) {
        std.debug.print("No tokens found\n", .{});
        return;
    }

    // Open the first token
    var token = try hsm.openToken(allocator, tokens[0].id, .{});
    defer token.close();

    // Verify PIN
    try token.verifyPin("123456");

    // Read certificate
    const cert = try token.getCertificate(.authentication);
    defer allocator.free(cert);
    std.debug.print("Certificate: {} bytes\n", .{cert.len});

    // Sign a digest
    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("Signature: {} bytes\n", .{signature.len});
}

API Overview

Types

Functions

Errors

All operations return errors from HsmError:

  • PC/SC: PcscUnavailable, ReaderGone, CardRemoved, Timeout
  • PIN: PinIncorrect, PinLocked, PinLengthInvalid
  • Capability: NotSupported, SlotNotFound, AlgorithmNotSupported
  • Data: InvalidTlv, CertificateNotFound, InvalidDigestLength
  • Security: SecurityConditionNotSatisfied,

Parallel Operations Module

The parallel module provides batch HSM operations using the thread_pool library. It is built as a separate Zig module and available when -Denable_tp=true (the default). Set -Denable_tp=false to compile out the seam entirely; the public functions still exist and fall back to a sequential implementation.

Below the threshold, operations execute sequentially without thread pool overhead. The underlying PKCS#11 calls are scaffolding; integrate your PKCS#11 backend by implementing the signData, decryptData, discoverToken, and retrieveCert functions in src/parallel.zig.

Testing

Unit Tests

root@kitploit:~
# Run all unit tests
zig build test

Simulator Integration Tests

The library includes a PIV card simulator for testing without hardware:

root@kitploit:~
# Run simulator tests
zig build test -Dintegration_sim=true

The simulator uses embedded test keys (see src/sim/key_material.zig). No external key files or environment variables are needed.

Hardware-in-Loop (HIL) Tests

For testing with real hardware:

root@kitploit:~
# Read-only tests (certificate reading)
HSM_HIL=1 zig build test -Dhsm_hil=true

# Tests requiring PIN
HSM_HIL=1 HSM_PIN=123456 zig build test -Dhsm_hil=true

# Dangerous tests (write operations) - USE WITH CAUTION
HSM_HIL=1 HSM_PIN=123456 HSM_DANGEROUS=1 zig build test -Dhsm_hil=true

Fuzz Testing

root@kitploit:~
# Continuous fuzzing (stop manually)
zig build test --fuzz -- --test-filter fuzz

Examples

root@kitploit:~
# Build examples (requires PC/SC library installed)
zig build examples -Dlink_pcsc=true

# List tokens
./zig-out/bin/list_tokens
./zig-out/bin/list_tokens --sim  # Use simulator

# Sign with PIV (PIN entered via interactive TTY prompt)
./zig-out/bin/piv_sign
./zig-out/bin/piv_sign --sim

# Or supply PIN via environment variable (less secure)
HSM_PIN=123456 ./zig-out/bin/piv_sign --sim

Security Notes

  1. PIN Handling: PINs are never stored in token structures and are zeroized after use.

  2. TLV Parsing: All TLV parsing has depth (10) and length (64KB) limits to prevent resource exhaustion.

  3. Error Handling: Unknown status words result in explicit errors, not silent failures.

  4. Logging: Debug logging never includes sensitive data (PINs, keys, etc.).

  5. Memory: Sensitive buffers are zeroized using hsm.zeroize() which prevents compiler optimization.

  6. PC/SC Library Loading: The library loads PC/SC from trusted absolute paths. An explicit override is available via HSM_PCSC_LIB_PATH (must be absolute).

Observability

hsm ships an opt-in OpenTelemetry seam for span tracing of HSM operations. The seam is off by default and produces zero overhead when off — the build never resolves the otel dependency, every helper compiles to a comptime-known no-op, and the produced artifacts contain no otel or observability linked symbols.

Enabling

root@kitploit:~
zig build test                  # default: -Dwith_otel=false (no-op)
zig build test -Dwith_otel=true # opt in: spans emitted

When enabled, every public Token.sign / Token.decrypt call emits an hsm.{operation} span (e.g. hsm.sign, hsm.decrypt) with two attributes:

  • crypto.algorithm — algorithm tag (e.g. ecdsa_p256, rsa2048_pkcs1v15).
  • crypto.key.id — slot identifier (e.g. authentication, signature, key_management, card_auth).

Security contract

The seam is engineered around one rule: NEVER export cryptographic material. Span attributes are exported off-host (typically to an OTLP collector) and end up in traces/logs that may have weaker access controls than the HSM operation itself.

  • Private key bytes, plaintext, ciphertext, signatures, digests, and PINs are NEVER attached to spans by this library.
  • Only operation type, slot identifier, and algorithm name leave the process via telemetry.
  • If your application's slot identifiers are themselves sensitive (e.g. correlated with user identity), redact or hash them before passing to hsm.observability.startHsmOperationSpan.

Process bootstrap

root@kitploit:~
const hsm = @import("hsm");

pub fn main() !void {
    // Park the Otel value on a stable address — either a `var` in
    // main()'s stack frame for the process lifetime, or a heap
    // allocation. The init helper is only present when
    // `-Dwith_otel=true`; under the disabled build,
    // hsm.observability_init resolves to an empty struct and this
    // block compiles to a no-op.
    if (comptime hsm.observability.enabled) {
        var otel = try hsm.observability_init.Otel.init(allocator, "my-service");
        defer otel.deinit();
        otel.installGlobals();
    }
    // ... rest of main, including any hsm.Token.sign calls — each one
    // now emits an `hsm.sign` span attached to your service.
}

The seam reads OTEL_* environment variables (sampler, exporter endpoint, service.name, etc.) per the OpenTelemetry environment-variable spec. Defaults: parentbased_traceidratio sampler at 5%, OTLP/HTTP-protobuf exporter to http://localhost:4318.

Recipe + verification

The full integration recipe (build wiring, test harness, no-op verifier) is documented in the otel rollout repo: otel/docs/integration/RECIPE.md.

Three gates must pass before merging changes that touch the seam:

root@kitploit:~
zig build test                                 # default flag = false
zig build test -Dwith_otel=true                # flag on
scripts/verify-consumer-noop.sh                # symbol-leak check

The verify-consumer-noop.sh gate builds the library with -Dwith_otel=false and walks zig-out/ with nm --defined-only, failing if any otel/observability symbol survived into the artifacts. It mirrors the cross-repo otel/scripts/verify-consumer- noop.sh (which only runs in the sibling-repo layout <root>/otel//<root>/hsm/) so contributors without that layout can still gate the seam locally.

Project Structure

root@kitploit:~
src/
├── hsm.zig              # Main API and types
├── root.zig             # Module entry point
├── apdu.zig             # APDU encoding/decoding
├── tlv.zig              # BER-TLV parsing
├── parallel.zig         # Parallel operations (thread_pool)
├── pcsc/
│   └── pcsc.zig         # PC/SC transport layer (Linux/macOS/Windows)
├── piv/
│   └── piv.zig          # PIV implementation
├── cac/
│   └── cac.zig          # CAC implementation
├── yubikey/
│   └── yubikey.zig      # YubiKey detection
└── sim/
    ├── sim.zig          # Simulator entry point
    ├── transport.zig    # Simulated transport
    ├── piv_card.zig     # Simulated PIV card
    └── key_material.zig # Embedded test keys

tests/
├── integration.zig      # Basic integration tests
├── sim_integration.zig  # Simulator integration tests
└── hil_tests.zig        # Hardware-in-loop tests

examples/
├── list_tokens.zig      # Token discovery example
└── piv_sign.zig         # Signing example

References

  • NIST SP 800-73-4 - PIV Specification
  • FIPS 201-3 - PIV Standard
  • PC/SC Workgroup - PC/SC Specifications
  • Yubico PIV Tool - YubiKey PIV Documentation

Contributing

See CONTRIBUTING.md for guidelines.

License

MIT License - see LICENSE for details.


Built with Zig 0.16.0 | PIV | CAC | YubiKey | PC/SC

Download Tool
OptionDefaultDescriptionSecurity Impact
integration_simfalseRun simulator integration testsNone
hsm_hilfalseRun hardware-in-loop tests (requires real token)None
link_pcscfalseLink PC/SC library for examplesNone
include_simulatorDebug: true
Release: false
Include PIV simulator with test keysCWE-321: Test keys in production
allow_pcsc_env_overridefalseSECURITY RISK: Allow HSM_PCSC_LIB_PATH env varCWE-427: Untrusted library loading
fipsfalseRoute security crypto through the linked FIPS-140-3 provider (zero overhead when off)None when off
openssl_path(unset)OpenSSL install prefix for non-standard installs (e.g. /usr/local/ssl)None
TypeDescription
TokenKindToken type: .piv, .cac, .yubikey, .unknown
TokenInfoDiscovered token metadata
TokenOpen token handle for operations
SlotKey slot: .authentication (9A), .signature (9C), .key_management (9D), .card_auth (9E)
AlgorithmCrypto algorithm: .ecdsa_p256, .ecdsa_p384, .rsa2048_pkcs1v15, etc.
CapabilitiesToken capability flags
PcscScopePC/SC context scope: .user (default), .system
FunctionDescription
listTokens(allocator, use_sim)List available tokens
openToken(allocator, id, opts)Open a token by ID
token.reconnect()Reconnect to token and reset auth state
token.verifyPin(pin)Verify PIN
token.changePin(old, new)Change PIN
token.unblockPin(puk, new_pin)Unblock PIN with PUK
token.getCertificate(slot)Get DER-encoded certificate
token.sign(slot, alg, digest)Sign a digest
token.decrypt(slot, alg, ciphertext)Decrypt data
token.capabilities()Get token capabilities
token.close()Close token connection
AuthenticationFailed
FunctionDescriptionThreshold
parallelSign(allocator, inputs, results)Batch signing across slots2+ items
parallelDecrypt(allocator, inputs, results)Batch decryption across keys2+ items
parallelTokenDiscover(readers, results)Token discovery across readers2+ readers
parallelCertRetrieve(allocator, requests, results)Certificate retrieval across slots2+ requests