
Biblioteca Zig de Módulo de Segurança de Hardware para tokens PIV, CAC e YubiKey via PC/SC. Suporta certificados, gerenciamento de PIN, assinatura e descriptografia.
Uma biblioteca Hardware Security Module (HSM) para Zig que fornece acesso PC/SC a tokens PIV, CAC e YubiKey.
| Aspecto | Informação |
|---|---|
| Estabilidade da API | Desenvolvimento |
| Versão Zig | 0.16.0 |
| Plataformas | Linux, macOS, Windows |
| Licença | MIT |
Suporte PIV (NIST SP 800-73-4)
Suporte CAC (Common Access Card)
Suporte YubiKey
Segurança
anyerror)Operações Paralelas (via thread_pool)
-Denable_tp=true); desative com -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
# Iniciar o daemon PC/SC
sudo systemctl start pcscd
sudo systemctl enable pcscd
# Compilar e executar testes unitários
make
# Ou usando zig diretamente
zig build test
# Executar testes de integração do simulador
zig build test -Dintegration_sim=true
# Compilar exemplos (requer biblioteca PC/SC instalada)
zig build examples -Dlink_pcsc=true
Com -Dfips=true, toda primitiva criptográfica relevante para segurança
é roteada através de um provedor FIPS OpenSSL 3.x vinculado e validado,
via a costura crypto_backend, e o simulador ganha operações de token
PIV pós-quânticas (assinatura ML-DSA-65, encapsulamento/desencapsulamento
ML-KEM-768) que estão disponíveis apenas no modo FIPS. A flag está
desativada por padrão, com sobrecarga zero; a dependência fips é
resolvida apenas sob -Dfips=true.
# Compilação padrão — backend std.crypto, sem vínculo OpenSSL
zig build test
# Compilação FIPS — provedor FIPS OpenSSL (execute `make deps` a partir de fips primeiro)
OPENSSL_CONF=/usr/local/ssl/ssl/openssl.cnf \
zig build test -Dfips=true -Dopenssl_path=/usr/local/ssl
# Ambos os modos de uma vez
make test-dual
Consulte docs/FIPS.md para o design da costura, a
política de isenção de FIPS e as operações de token PQC.
Nota de Segurança: A opção allow_pcsc_env_override está desativada por padrão para evitar ataques de injeção de bibliotecas maliciosas (CWE-427). Ative-a apenas para desenvolvimento/teste:
# Compilação de produção (segura, variável de ambiente ignorada)
zig build
# Compilação de desenvolvimento (permite HSM_PCSC_LIB_PATH)
zig build -Dallow_pcsc_env_override=true
Quando ativada, as bibliotecas são validadas antes do carregamento:
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 disponíveis
const tokens = try hsm.listTokens(allocator, false);
defer {
for (tokens) |*t| t.deinit();
allocator.free(tokens);
}
if (tokens.len == 0) {
std.debug.print("Nenhum token encontrado\n", .{});
return;
}
// Abrir o primeiro token
var token = try hsm.openToken(allocator, tokens[0].id, .{});
defer token.close();
// Verificar PIN
try token.verifyPin("123456");
// Ler certificado
const cert = try token.getCertificate(.authentication);
defer allocator.free(cert);
std.debug.print("Certificado: {} bytes\n", .{cert.len});
// Assinar um 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("Assinatura: {} bytes\n", .{signature.len});
}
Todas as operações retornam erros de HsmError:
PcscUnavailable, ReaderGone, CardRemoved, TimeoutPinIncorrect, PinLocked, PinLengthInvalidNotSupported, SlotNotFound, AlgorithmNotSupportedInvalidTlv, CertificateNotFound, InvalidDigestLengthSecurityConditionNotSatisfied, O módulo parallel fornece operações HSM em lote usando a biblioteca thread_pool. Ele é compilado como um módulo Zig separado e está disponível quando -Denable_tp=true (o padrão). Defina -Denable_tp=false para compilar a costura completamente; as funções públicas ainda existem e recorrem a uma implementação sequencial.
Abaixo do limiar, as operações executam sequencialmente sem sobrecarga do thread pool. As chamadas PKCS#11 subjacentes são scaffolding; integre seu backend PKCS#11 implementando as funções signData, decryptData, discoverToken e retrieveCert em src/parallel.zig.
# Executar todos os testes unitários
zig build test
A biblioteca inclui um simulador de cartão PIV para teste sem hardware:
# Executar testes do simulador
zig build test -Dintegration_sim=true
O simulador usa chaves de teste embutidas (veja src/sim/key_material.zig). Nenhum arquivo de chave externo ou variáveis de ambiente são necessárias.
Para testes com hardware real:
# Testes somente leitura (leitura de certificado)
HSM_HIL=1 zig build test -Dhsm_hil=true
# Testes que exigem PIN
HSM_HIL=1 HSM_PIN=123456 zig build test -Dhsm_hil=true
# Testes perigosos (operações de escrita) - USE COM CUIDADO
HSM_HIL=1 HSM_PIN=123456 HSM_DANGEROUS=1 zig build test -Dhsm_hil=true
# Fuzzing contínuo (parar manualmente)
zig build test --fuzz -- --test-filter fuzz
# Compilar exemplos (requer 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
# Assinar com PIV (PIN inserido via prompt interativo TTY)
./zig-out/bin/piv_sign
./zig-out/bin/piv_sign --sim
# Ou fornecer PIN via variável de ambiente (menos seguro)
HSM_PIN=123456 ./zig-out/bin/piv_sign --sim
Manipulação de PIN: PINs nunca são armazenados em estruturas de token e são zerados após o uso.
Análise TLV: Toda análise TLV tem limites de profundidade (10) e comprimento (64KB) para evitar exaustão de recursos.
Tratamento de Erros: Palavras de status desconhecidas resultam em erros explícitos, não falhas silenciosas.
Registro (Logging): O registro de depuração nunca inclui dados sensíveis (PINs, chaves, etc.).
Memória: Buffers sensíveis são zerados usando hsm.zeroize(), que impede a otimização do compilador.
Carregamento da Biblioteca PC/SC: A biblioteca carrega PC/SC de caminhos absolutos confiáveis. Uma substituição explícita está disponível via HSM_PCSC_LIB_PATH (deve ser absoluto).
hsm inclui uma costura OpenTelemetry opcional para rastreamento de spans de operações HSM. A costura está desativada por padrão e produz sobrecarga zero quando desativada — a compilação nunca resolve a dependência otel, todo helper compila para um no-op conhecido em tempo de compilação, e os artefatos produzidos não contêm símbolos otel ou observability vinculados.
zig build test # padrão: -Dwith_otel=false (no-op)
zig build test -Dwith_otel=true # opt-in: spans emitidos
Quando ativada, cada chamada pública Token.sign / Token.decrypt emite um span hsm.{operação} (ex.: hsm.sign, hsm.decrypt) com dois atributos:
crypto.algorithm — tag do algoritmo (ex.: ecdsa_p256, rsa2048_pkcs1v15).crypto.key.id — identificador do slot (ex.: authentication, signature, key_management, card_auth).A costura é projetada em torno de uma regra: NUNCA exportar material criptográfico. Atributos de span são exportados para fora do host (tipicamente para um coletor OTLP) e acabam em rastreamentos/logs que podem ter controles de acesso mais fracos que a própria operação HSM.
hsm.observability.startHsmOperationSpan.const hsm = @import("hsm");
pub fn main() !void {
// Estacionar o valor Otel em um endereço estável — seja uma `var` no
// stack frame de main() pela vida do processo, ou uma alocação no heap.
// O helper init só está presente quando `-Dwith_otel=true`; na compilação
// desativada, hsm.observability_init se resolve para um struct vazio e
// este bloco compila para um no-op.
if (comptime hsm.observability.enabled) {
var otel = try hsm.observability_init.Otel.init(allocator, "my-service");
defer otel.deinit();
otel.installGlobals();
}
// ... resto do main, incluindo quaisquer chamadas hsm.Token.sign — cada uma
// agora emite um span `hsm.sign` anexado ao seu serviço.
}
A costura lê variáveis de ambiente OTEL_* (amostrador, endpoint do exportador, service.name, etc.) de acordo com a especificação de variáveis de ambiente do OpenTelemetry. Padrões: amostrador parentbased_traceidratio a 5%, exportador OTLP/HTTP-protobuf para http://localhost:4318.
A receita completa de integração (fiação de compilação, harness de teste, verificador no-op) está documentada no repositório de rollout otel: otel/docs/integration/RECIPE.md.
Três portões devem passar antes de mesclar alterações que toquem a costura:
zig build test # flag padrão = false
zig build test -Dwith_otel=true # flag ativada
scripts/verify-consumer-noop.sh # verificação de vazamento de símbolo
O portão verify-consumer-noop.sh compila a biblioteca com -Dwith_otel=false e percorre zig-out/ com nm --defined-only, falhando se qualquer símbolo otel/observability sobreviver nos artefatos. Ele espelha o otel/scripts/verify-consumer-noop.sh entre repositórios (que só é executado no layout de repositório irmão <root>/otel//<root>/hsm/) para que contribuidores sem esse layout ainda possam bloquear a costura localmente.
src/
├── hsm.zig # API e tipos principais
├── root.zig # Ponto de entrada do módulo
├── apdu.zig # Codificação/decodificação APDU
├── tlv.zig # Análise BER-TLV
├── parallel.zig # Operações paralelas (thread_pool)
├── pcsc/
│ └── pcsc.zig # Camada de transporte PC/SC (Linux/macOS/Windows)
├── piv/
│ └── piv.zig # Implementação PIV
├── cac/
│ └── cac.zig # Implementação CAC
├── yubikey/
│ └── yubikey.zig # Deteção YubiKey
└── sim/
├── sim.zig # Ponto de entrada do simulador
├── transport.zig # Transporte simulado
├── piv_card.zig # Cartão PIV simulado
└── key_material.zig # Chaves de teste embutidas
tests/
├── integration.zig # Testes de integração básicos
├── sim_integration.zig # Testes de integração do simulador
└── hil_tests.zig # Testes com hardware real
examples/
├── list_tokens.zig # Exemplo de descoberta de tokens
└── piv_sign.zig # Exemplo de assinatura
Consulte CONTRIBUTING.md para diretrizes.
Licença MIT - consulte LICENSE para detalhes.
Construído com Zig 0.16.0 | PIV | CAC | YubiKey | PC/SC
| Opção | Padrão | Descrição | Impacto na Segurança |
|---|
integration_sim | false | Executar testes de integração do simulador | Nenhum |
hsm_hil | false | Executar testes com hardware real (requer token real) | Nenhum |
link_pcsc | false | Vincular biblioteca PC/SC para exemplos | Nenhum |
include_simulator | Debug: trueRelease: false | Incluir simulador PIV com chaves de teste | CWE-321: Chaves de teste em produção |
allow_pcsc_env_override | false | RISCO DE SEGURANÇA: Permitir variável de ambiente HSM_PCSC_LIB_PATH | CWE-427: Carregamento de biblioteca não confiável |
fips | false | Roteamento de criptografia de segurança através do provedor FIPS-140-3 vinculado (sobrecarga zero quando desativado) | Nenhum quando desativado |
openssl_path | (não definido) | Prefixo de instalação do OpenSSL para instalações não padrão (ex.: /usr/local/ssl) | Nenhum |
| Tipo | Descrição |
|---|
TokenKind | Tipo de token: .piv, .cac, .yubikey, .unknown |
TokenInfo | Metadados de token descoberto |
Token | Handle de token aberto para operações |
Slot | Slot de chave: .authentication (9A), .signature (9C), .key_management (9D), .card_auth (9E) |
Algorithm | Algoritmo criptográfico: .ecdsa_p256, .ecdsa_p384, .rsa2048_pkcs1v15, etc. |
Capabilities | Flags de capacidade do token |
PcscScope | Escopo do contexto PC/SC: .user (padrão), .system |
| Função | Descrição |
|---|
listTokens(allocator, use_sim) | Listar tokens disponíveis |
openToken(allocator, id, opts) | Abrir um token por ID |
token.reconnect() | Reconectar ao token e redefinir estado de autenticação |
token.verifyPin(pin) | Verificar PIN |
token.changePin(old, new) | Alterar PIN |
token.unblockPin(puk, new_pin) | Desbloquear PIN com PUK |
token.getCertificate(slot) | Obter certificado codificado em DER |
token.sign(slot, alg, digest) | Assinar um digest |
token.decrypt(slot, alg, ciphertext) | Descriptografar dados |
token.capabilities() | Obter capacidades do token |
token.close() | Fechar conexão do token |
AuthenticationFailed| Função | Descrição | Limiar |
|---|
parallelSign(allocator, inputs, results) | Assinatura em lote entre slots | 2+ itens |
parallelDecrypt(allocator, inputs, results) | Descriptografia em lote entre chaves | 2+ itens |
parallelTokenDiscover(readers, results) | Descoberta de tokens entre leitores | 2+ leitores |
parallelCertRetrieve(allocator, requests, results) | Recuperação de certificados entre slots | 2+ solicitações |