
Bibliothèque Zig pour module de sécurité matérielle pour les jetons PIV, CAC et YubiKey via PC/SC. Prend en charge les certificats, la gestion du code PIN, la signature et le déchiffrement.
Une bibliothèque de module de sécurité matériel (HSM) pour Zig fournissant un accès PC/SC aux jetons PIV, CAC et YubiKey.
| Aspect | Info |
|---|---|
| Stabilité de l'API | Développement |
| Version Zig | 0.16.0 |
| Plateformes | Linux, macOS, Windows |
| Licence | MIT |
Support PIV (NIST SP 800-73-4)
Support CAC (Common Access Card)
Support YubiKey
Sécurité
anyerror)Opérations parallèles (via thread_pool)
-Denable_tp=true) ; désactiver avec -Denable_tp=falselibpcsclite-dev (Debian/Ubuntu) ou pcsc-lite-devel (Fedora)# 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
# 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
Avec -Dfips=true, chaque primitive cryptographique sensible à la sécurité transite par un fournisseur FIPS OpenSSL 3.x lié et validé via la couture crypto_backend, et le simulateur obtient des opérations de jeton PIV post-quantique (signature ML-DSA-65, encapsulage/désencapsulage ML-KEM-768) disponibles uniquement en mode FIPS. Le drapeau est désactivé par défaut avec zéro surcharge ; la dépendance fips n'est résolue qu'avec -Dfips=true.
# 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
Voir docs/FIPS.md pour la conception de la couture, la politique d'exemption FIPS et les opérations de jeton PQC.
Note de sécurité : L'option allow_pcsc_env_override est désactivée par défaut pour prévenir les attaques par injection de bibliothèque malveillante (CWE-427). Activez-la uniquement pour le développement/les tests :
# Production build (secure, env var ignored)
zig build
# Development build (allows HSM_PCSC_LIB_PATH)
zig build -Dallow_pcsc_env_override=true
Lorsqu'activée, les bibliothèques sont validées avant le chargement :
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});
}
Toutes les opérations renvoient des erreurs de HsmError :
PcscUnavailable, ReaderGone, CardRemoved, TimeoutPinIncorrect, PinLocked, PinLengthInvalidNotSupported, SlotNotFound, AlgorithmNotSupportedInvalidTlv, CertificateNotFound, InvalidDigestLengthSecurityConditionNotSatisfied, Le module parallel fournit des opérations HSM par lots à l'aide de la bibliothèque thread_pool. Il est construit comme un module Zig séparé et disponible lorsque -Denable_tp=true (par défaut). Utilisez -Denable_tp=false pour compiler sans cette couture ; les fonctions publiques existent toujours et se replient sur une implémentation séquentielle.
En dessous du seuil, les opérations s'exécutent séquentiellement sans surcharge du thread_pool. Les appels PKCS#11 sous-jacents sont des échafaudages ; intégrez votre backend PKCS#11 en implémentant les fonctions signData, decryptData, discoverToken et retrieveCert dans src/parallel.zig.
# Run all unit tests
zig build test
La bibliothèque comprend un simulateur de carte PIV pour les tests sans matériel :
# Run simulator tests
zig build test -Dintegration_sim=true
Le simulateur utilise des clés de test intégrées (voir src/sim/key_material.zig). Aucun fichier de clé externe ni variable d'environnement n'est nécessaire.
Pour les tests avec du matériel réel :
# 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
# Continuous fuzzing (stop manually)
zig build test --fuzz -- --test-filter fuzz
# 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
Gestion du PIN : Les PIN ne sont jamais stockés dans les structures de jetons et sont mis à zéro après utilisation.
Analyse TLV : Toute analyse TLV est limitée en profondeur (10) et en longueur (64 Ko) pour éviter l'épuisement des ressources.
Gestion des erreurs : Les mots d'état inconnus entraînent des erreurs explicites, pas des échecs silencieux.
Journalisation : La journalisation de débogage n'inclut jamais de données sensibles (PIN, clés, etc.).
Mémoire : Les tampons sensibles sont mis à zéro à l'aide de hsm.zeroize() qui empêche l'optimisation du compilateur.
Chargement de la bibliothèque PC/SC : La bibliothèque charge PC/SC à partir de chemins absolus de confiance. Une substitution explicite est disponible via HSM_PCSC_LIB_PATH (doit être absolu).
hsm est livré avec une couture OpenTelemetry optionnelle pour le traçage des opérations HSM. La couture est désactivée par défaut et ne génère aucune surcharge lorsqu'elle est désactivée — la compilation ne résout jamais la dépendance otel, chaque helper se compile en une opération sans effet connu à la compilation, et les artefacts produits ne contiennent aucun symbole lié à otel ou observability.
zig build test # default: -Dwith_otel=false (no-op)
zig build test -Dwith_otel=true # opt in: spans emitted
Lorsqu'elle est activée, chaque appel public Token.sign / Token.decrypt émet une span hsm.{operation} (par ex. hsm.sign, hsm.decrypt) avec deux attributs :
crypto.algorithm — étiquette d'algorithme (par ex. ecdsa_p256, rsa2048_pkcs1v15).crypto.key.id — identifiant du slot (par ex. authentication, signature, key_management, card_auth).La couture est conçue autour d'une règle : NE JAMAIS exporter de matériel cryptographique. Les attributs des spans sont exportés hors machine (généralement vers un collecteur OTLP) et se retrouvent dans des traces/logs pouvant avoir des contrôles d'accès plus faibles que l'opération HSM elle-même.
hsm.observability.startHsmOperationSpan.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.
}
La couture lit les variables d'environnement OTEL_* (échantillonneur, point de terminaison de l'exportateur, service.name, etc.) conformément à la spécification des variables d'environnement OpenTelemetry. Valeurs par défaut : échantillonneur parentbased_traceidratio à 5 %, exportateur OTLP/HTTP-protobuf vers http://localhost:4318.
La recette d'intégration complète (câblage de compilation, harnais de test, vérificateur sans opération) est documentée dans le dépôt de déploiement otel : otel/docs/integration/RECIPE.md.
Trois portes doivent être passées avant de fusionner les modifications qui touchent à la couture :
zig build test # default flag = false
zig build test -Dwith_otel=true # flag on
scripts/verify-consumer-noop.sh # symbol-leak check
La porte verify-consumer-noop.sh compile la bibliothèque avec -Dwith_otel=false et parcourt zig-out/ avec nm --defined-only, échouant si un symbole otel/observability a survécu dans les artefacts. Elle reflète le otel/scripts/verify-consumer-noop.sh inter-dépôt (qui ne s'exécute que dans la disposition de dépôt frère <root>/otel//<root>/hsm/) afin que les contributeurs sans cette disposition puissent toujours verrouiller la couture localement.
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
Voir CONTRIBUTING.md pour les directives.
Licence MIT - voir LICENSE pour les détails.
Construit avec Zig 0.16.0 | PIV | CAC | YubiKey | PC/SC
| Option | Par défaut | Description | Impact sécurité |
|---|
integration_sim | false | Exécuter les tests d'intégration du simulateur | Aucun |
hsm_hil | false | Exécuter des tests matériel en boucle (nécessite un jeton réel) | Aucun |
link_pcsc | false | Lier la bibliothèque PC/SC pour les exemples | Aucun |
include_simulator | Debug: trueRelease: false | Inclure le simulateur PIV avec des clés de test | CWE-321 : Clés de test en production |
allow_pcsc_env_override | false | RISQUE DE SÉCURITÉ : Permettre la variable d'environnement HSM_PCSC_LIB_PATH | CWE-427 : Chargement de bibliothèque non fiable |
fips | false | Acheminer la crypto sécurisée via le fournisseur FIPS-140-3 lié (zéro surcharge lorsque désactivé) | Aucun |
openssl_path | (non défini) | Préfixe d'installation OpenSSL pour les installations non standard (par ex. /usr/local/ssl) | Aucun |
| Type | Description |
|---|
TokenKind | Type de jeton : .piv, .cac, .yubikey, .unknown |
TokenInfo | Métadonnées du jeton découvert |
Token | Handle de jeton ouvert pour les opérations |
Slot | Slot de clé : .authentication (9A), .signature (9C), .key_management (9D), .card_auth (9E) |
Algorithm | Algorithme cryptographique : .ecdsa_p256, .ecdsa_p384, .rsa2048_pkcs1v15, etc. |
Capabilities | Drapeaux de capacités du jeton |
PcscScope | Portée du contexte PC/SC : .user (par défaut), .system |
| Fonction | Description |
|---|
listTokens(allocator, use_sim) | Liste les jetons disponibles |
openToken(allocator, id, opts) | Ouvre un jeton par son ID |
token.reconnect() | Se reconnecte au jeton et réinitialise l'état d'authentification |
token.verifyPin(pin) | Vérifie le PIN |
token.changePin(old, new) | Change le PIN |
token.unblockPin(puk, new_pin) | Débloque le PIN avec le PUK |
token.getCertificate(slot) | Obtient le certificat encodé en DER |
token.sign(slot, alg, digest) | Signe un condensat |
token.decrypt(slot, alg, ciphertext) | Déchiffre des données |
token.capabilities() | Obtient les capacités du jeton |
token.close() | Ferme la connexion au jeton |
AuthenticationFailed| Fonction | Description | Seuil |
|---|
parallelSign(allocator, inputs, results) | Signature par lots sur plusieurs slots | 2+ éléments |
parallelDecrypt(allocator, inputs, results) | Déchiffrement par lots sur plusieurs clés | 2+ éléments |
parallelTokenDiscover(readers, results) | Découverte de jetons sur plusieurs lecteurs | 2+ lecteurs |
parallelCertRetrieve(allocator, requests, results) | Récupération de certificats sur plusieurs slots | 2+ requêtes |