
Biblioteca de Módulo de Seguridad de Hardware Zig para tokens PIV, CAC y YubiKey a través de PC/SC. Soporta certificados, gestión de PIN, firma y descifrado.
Una biblioteca Módulo de Seguridad de Hardware (HSM) para Zig que proporciona acceso PC/SC a tokens PIV, CAC y YubiKey.
| Aspecto | Información |
|---|---|
| Estabilidad de API | Desarrollo |
| Versión de Zig | 0.16.0 |
| Plataformas | Linux, macOS, Windows |
| Licencia | MIT |
Soporte PIV (NIST SP 800-73-4)
Soporte CAC (Common Access Card)
Soporte YubiKey
Seguridad
anyerror)Operaciones paralelas (mediante thread_pool)
-Denable_tp=true); desactivar con -Denable_tp=falselibpcsclite-dev (Debian/Ubuntu) o pcsc-lite-devel (Fedora)# Debian/Ubuntu
sudo apt-get install libpcsclite-dev pcscd
# Fedora/RHEL
sudo dnf install pcsc-lite-devel pcsc-lite
# Iniciar el daemon PC/SC
sudo systemctl start pcscd
sudo systemctl enable pcscd
# Compilar y ejecutar pruebas unitarias
make
# O usando zig directamente
zig build test
# Ejecutar pruebas de integración con simulador
zig build test -Dintegration_sim=true
# Compilar ejemplos (requiere la biblioteca PC/SC instalada)
zig build examples -Dlink_pcsc=true
Con -Dfips=true, cada primitiva criptográfica relevante para la seguridad
se enruta a través de un proveedor FIPS de OpenSSL 3.x enlazado y validado mediante
la interfaz crypto_backend, y el simulador obtiene operaciones de token PIV
postcuánticas (firma ML-DSA-65, encapsulamiento/desencapsulamiento ML-KEM-768) que
están disponibles solo en modo FIPS. La bandera está desactivada por defecto con
cero sobrecarga; la dependencia fips se resuelve solo con -Dfips=true.
# Compilación por defecto — backend std.crypto, sin enlace OpenSSL
zig build test
# Compilación FIPS — proveedor FIPS de OpenSSL (ejecutar `make deps` desde fips primero)
OPENSSL_CONF=/usr/local/ssl/ssl/openssl.cnf \
zig build test -Dfips=true -Dopenssl_path=/usr/local/ssl
# Ambos modos en un solo comando
make test-dual
Consulte docs/FIPS.md para el diseño de la interfaz, la
política de exención FIPS y las operaciones de token PQC.
Nota de seguridad: La opción allow_pcsc_env_override está desactivada por defecto para evitar ataques de inyección de bibliotecas maliciosas (CWE-427). Actívela solo para desarrollo/pruebas:
# Compilación de producción (segura, la variable de entorno se ignora)
zig build
# Compilación de desarrollo (permite HSM_PCSC_LIB_PATH)
zig build -Dallow_pcsc_env_override=true
Cuando está activada, las bibliotecas se validan antes de cargarse:
const std = @import("std");
const hsm = @import("hsm");
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
// Listar tokens disponibles
const tokens = try hsm.listTokens(allocator, false);
defer {
for (tokens) |*t| t.deinit();
allocator.free(tokens);
}
if (tokens.len == 0) {
std.debug.print("No se encontraron tokens\n", .{});
return;
}
// Abrir el primer token
var token = try hsm.openToken(allocator, tokens[0].id, .{});
defer token.close();
// Verificar PIN
try token.verifyPin("123456");
// Leer certificado
const cert = try token.getCertificate(.authentication);
defer allocator.free(cert);
std.debug.print("Certificado: {} bytes\n", .{cert.len});
// Firmar un 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("Firma: {} bytes\n", .{signature.len});
}
Todas las operaciones devuelven errores de HsmError:
PcscUnavailable, ReaderGone, CardRemoved, TimeoutPinIncorrect, PinLocked, PinLengthInvalidNotSupported, SlotNotFound, AlgorithmNotSupportedInvalidTlv, CertificateNotFound, InvalidDigestLengthSecurityConditionNotSatisfied, El módulo parallel proporciona operaciones HSM por lotes utilizando la biblioteca thread_pool. Está construido como un módulo Zig separado y está disponible cuando -Denable_tp=true (valor por defecto). Establezca -Denable_tp=false para compilar sin la interfaz por completo; las funciones públicas aún existen y recurren a una implementación secuencial.
Por debajo del umbral, las operaciones se ejecutan secuencialmente sin sobrecarga del grupo de hilos. Las llamadas PKCS#11 subyacentes son andamios; integre su backend PKCS#11 implementando las funciones signData, decryptData, discoverToken y retrieveCert en src/parallel.zig.
# Ejecutar todas las pruebas unitarias
zig build test
La biblioteca incluye un simulador de tarjeta PIV para pruebas sin hardware:
# Ejecutar pruebas del simulador
zig build test -Dintegration_sim=true
El simulador utiliza claves de prueba integradas (ver src/sim/key_material.zig). No se necesitan archivos de clave externos ni variables de entorno.
Para pruebas con hardware real:
# Pruebas de solo lectura (lectura de certificados)
HSM_HIL=1 zig build test -Dhsm_hil=true
# Pruebas que requieren PIN
HSM_HIL=1 HSM_PIN=123456 zig build test -Dhsm_hil=true
# Pruebas peligrosas (operaciones de escritura) - USAR CON PRECAUCIÓN
HSM_HIL=1 HSM_PIN=123456 HSM_DANGEROUS=1 zig build test -Dhsm_hil=true
# Fuzzing continuo (detener manualmente)
zig build test --fuzz -- --test-filter fuzz
# Compilar ejemplos (requiere la biblioteca PC/SC instalada)
zig build examples -Dlink_pcsc=true
# Listar tokens
./zig-out/bin/list_tokens
./zig-out/bin/list_tokens --sim # Usar simulador
# Firmar con PIV (PIN ingresado mediante prompt interactivo TTY)
./zig-out/bin/piv_sign
./zig-out/bin/piv_sign --sim
# O suministrar PIN mediante variable de entorno (menos seguro)
HSM_PIN=123456 ./zig-out/bin/piv_sign --sim
Manejo de PIN: Los PIN nunca se almacenan en las estructuras del token y se ceroizan después de su uso.
Análisis TLV: Todo el análisis TLV tiene límites de profundidad (10) y longitud (64 KB) para evitar agotamiento de recursos.
Manejo de errores: Las palabras de estado desconocidas resultan en errores explícitos, no en fallos silenciosos.
Registros: El registro de depuración nunca incluye datos sensibles (PIN, claves, etc.).
Memoria: Los búferes sensibles se ceroizan usando hsm.zeroize() que impide la optimización del compilador.
Carga de la biblioteca PC/SC: La biblioteca carga PC/SC desde rutas absolutas de confianza. Se puede anular explícitamente mediante HSM_PCSC_LIB_PATH (debe ser absoluta).
hsm incluye una interfaz optativa de OpenTelemetry para trazado de segmentos en
operaciones HSM. La interfaz está desactivada por defecto y produce cero sobrecarga
cuando está apagada — la compilación nunca resuelve la dependencia otel, cada
función auxiliar se compila en un no-op conocido en tiempo de compilación, y los
artefactos producidos no contienen símbolos enlazados de otel ni observability.
zig build test # por defecto: -Dwith_otel=false (no-op)
zig build test -Dwith_otel=true # opt-in: se emiten segmentos
Cuando está activada, cada llamada pública a Token.sign / Token.decrypt emite
un segmento hsm.{operación} (ej. hsm.sign, hsm.decrypt) con dos
atributos:
crypto.algorithm — etiqueta del algoritmo (ej. ecdsa_p256,
rsa2048_pkcs1v15).crypto.key.id — identificador del slot (ej. authentication,
signature, key_management, card_auth).La interfaz está diseñada en torno a una regla: NUNCA exportar material criptográfico. Los atributos de los segmentos se exportan fuera del anfitrión (típicamente a un colector OTLP) y terminan en trazas/registros que pueden tener controles de acceso más débiles que la propia operación HSM.
hsm.observability.startHsmOperationSpan.const hsm = @import("hsm");
pub fn main() !void {
// Estacionar el valor Otel en una dirección estable — ya sea una variable
// en el marco de pila de main() durante la vida del proceso, o una
// asignación en el montón. La función auxiliar init solo está presente
// cuando `-Dwith_otel=true`; bajo la compilación desactivada,
// hsm.observability_init se resuelve a un struct vacío y este bloque
// se compila en un no-op.
if (comptime hsm.observability.enabled) {
var otel = try hsm.observability_init.Otel.init(allocator, "my-service");
defer otel.deinit();
otel.installGlobals();
}
// ... el resto de main, incluyendo cualquier llamada a hsm.Token.sign —
// cada una ahora emite un segmento `hsm.sign` adjunto a su servicio.
}
La interfaz lee las variables de entorno OTEL_* (muestreador, punto final del
exportador, service.name, etc.) según la especificación de variables de entorno de
OpenTelemetry.
Valores por defecto: muestreador parentbased_traceidratio al 5%, exportador
OTLP/HTTP-protobuf a http://localhost:4318.
La receta de integración completa (cableado de compilación, banco de pruebas,
verificador de no-op) está documentada en el repositorio de despliegue de otel:
otel/docs/integration/RECIPE.md.
Deben superarse tres puertas antes de fusionar cambios que toquen la interfaz:
zig build test # bandera por defecto = false
zig build test -Dwith_otel=true # bandera activada
scripts/verify-consumer-noop.sh # verificación de fuga de símbolos
La puerta verify-consumer-noop.sh compila la biblioteca con
-Dwith_otel=false y recorre zig-out/ con nm --defined-only,
fallando si algún símbolo otel/observability sobrevive en los
artefactos. Refleja el otel/scripts/verify-consumer- noop.sh entre repositorios (que solo se ejecuta en el diseño de repos
hermanos <root>/otel//<root>/hsm/) para que los contribuyentes sin
ese diseño puedan seguir verificando la interfaz localmente.
src/
├── hsm.zig # API y tipos principales
├── root.zig # Punto de entrada del módulo
├── apdu.zig # Codificación/decodificación APDU
├── tlv.zig # Análisis BER-TLV
├── parallel.zig # Operaciones paralelas (thread_pool)
├── pcsc/
│ └── pcsc.zig # Capa de transporte PC/SC (Linux/macOS/Windows)
├── piv/
│ └── piv.zig # Implementación PIV
├── cac/
│ └── cac.zig # Implementación CAC
├── yubikey/
│ └── yubikey.zig # Detección de YubiKey
└── sim/
├── sim.zig # Punto de entrada del simulador
├── transport.zig # Transporte simulado
├── piv_card.zig # Tarjeta PIV simulada
└── key_material.zig # Claves de prueba integradas
tests/
├── integration.zig # Pruebas de integración básicas
├── sim_integration.zig # Pruebas de integración con simulador
└── hil_tests.zig # Pruebas con hardware real
examples/
├── list_tokens.zig # Ejemplo de descubrimiento de tokens
└── piv_sign.zig # Ejemplo de firma
Consulte CONTRIBUTING.md para obtener pautas.
Licencia MIT - consulte LICENSE para más detalles.
Construido con Zig 0.16.0 | PIV | CAC | YubiKey | PC/SC
| Opción | Valor por defecto | Descripción | Impacto en seguridad |
|---|
integration_sim | false | Ejecutar pruebas de integración con simulador | Ninguno |
hsm_hil | false | Ejecutar pruebas con hardware real (requiere token real) | Ninguno |
link_pcsc | false | Enlazar biblioteca PC/SC para ejemplos | Ninguno |
include_simulator | Debug: trueRelease: false | Incluir simulador PIV con claves de prueba | CWE-321: Claves de prueba en producción |
allow_pcsc_env_override | false | RIESGO DE SEGURIDAD: Permitir variable de entorno HSM_PCSC_LIB_PATH | CWE-427: Carga de bibliotecas no confiables |
fips | false | Enrutar criptografía de seguridad a través del proveedor FIPS-140-3 enlazado (sin sobrecarga cuando está desactivado) | Ninguno cuando está desactivado |
openssl_path | (sin establecer) | Prefijo de instalación de OpenSSL para instalaciones no estándar (ej. /usr/local/ssl) | Ninguno |
| Tipo | Descripción |
|---|
TokenKind | Tipo de token: .piv, .cac, .yubikey, .unknown |
TokenInfo | Metadatos del token descubierto |
Token | Manejador de token abierto para operaciones |
Slot | Slot de clave: .authentication (9A), .signature (9C), .key_management (9D), .card_auth (9E) |
Algorithm | Algoritmo criptográfico: .ecdsa_p256, .ecdsa_p384, .rsa2048_pkcs1v15, etc. |
Capabilities | Banderas de capacidades del token |
PcscScope | Ámbito del contexto PC/SC: .user (por defecto), .system |
| Función | Descripción |
|---|
listTokens(allocator, use_sim) | Listar tokens disponibles |
openToken(allocator, id, opts) | Abrir un token por ID |
token.reconnect() | Reconectar al token y restablecer estado de autenticación |
token.verifyPin(pin) | Verificar PIN |
token.changePin(old, new) | Cambiar PIN |
token.unblockPin(puk, new_pin) | Desbloquear PIN con PUK |
token.getCertificate(slot) | Obtener certificado codificado en DER |
token.sign(slot, alg, digest) | Firmar un digest |
token.decrypt(slot, alg, ciphertext) | Descifrar datos |
token.capabilities() | Obtener capacidades del token |
token.close() | Cerrar conexión del token |
AuthenticationFailed| Función | Descripción | Umbral |
|---|
parallelSign(allocator, inputs, results) | Firma por lotes en múltiples slots | 2+ elementos |
parallelDecrypt(allocator, inputs, results) | Descifrado por lotes en múltiples claves | 2+ elementos |
parallelTokenDiscover(readers, results) | Descubrimiento de tokens en todos los lectores | 2+ lectores |
parallelCertRetrieve(allocator, requests, results) | Obtención de certificados en múltiples slots | 2+ solicitudes |