
Библиотека аппаратного модуля безопасности на Zig для токенов PIV, CAC и YubiKey через PC/SC. Поддерживает сертификаты, управление PIN-кодом, подпись и расшифровку.
Библиотека модуля аппаратной безопасности (HSM) для Zig, обеспечивающая доступ через 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 все критически важные криптографические примитивы
направляются через связанный валидированный FIPS-провайдер OpenSSL 3.x через
прослойку crypto_backend, а симулятор получает пост-квантовые операции PIV-токена
(подпись ML-DSA-65, инкапсуляция/декапсуляция ML-KEM-768), доступные только в
режиме FIPS. Флаг выключен по умолчанию с нулевыми накладными расходами;
зависимость fips подключается только при -Dfips=true.
# Стандартная сборка — бэкенд std.crypto, без линковки OpenSSL
zig build test
# Сборка FIPS — провайдер OpenSSL FIPS (сначала выполните `make deps` из fips)
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 предоставляет пакетные операции HSM с использованием библиотеки thread_pool. Он построен как отдельный модуль Zig и доступен при -Denable_tp=true (по умолчанию). Установите -Denable_tp=false, чтобы полностью исключить прослойку из сборки; публичные функции всё равно существуют и переходят к последовательной реализации.
При количестве ниже порога операции выполняются последовательно без накладных расходов thread_pool. Базовые вызовы PKCS#11 являются каркасом; интегрируйте свой бэкенд PKCS#11, реализовав функции signData, decryptData, discoverToken и retrieveCert в src/parallel.zig.
# Запуск всех модульных тестов
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) и длине (64 КБ) для предотвращения исчерпания ресурсов.
Обработка ошибок: Неизвестные коды состояния приводят к явным ошибкам, а не к молчаливым сбоям.
Протоколирование: Отладочное протоколирование никогда не включает конфиденциальные данные (PIN, ключи и т.д.).
Память: Чувствительные буферы обнуляются с помощью hsm.zeroize(), что предотвращает оптимизацию компилятора.
Загрузка библиотеки PC/SC: Библиотека загружает PC/SC из доверенных абсолютных путей. Явное переопределение доступно через HSM_PCSC_LIB_PATH (должен быть абсолютным).
hsm поставляется с опциональной прослойкой OpenTelemetry для трассировки операций HSM.
Прослойка по умолчанию отключена и не создаёт никаких накладных расходов в выключенном состоянии — при сборке не разрешается зависимость otel, все вспомогательные функции компилируются в известные на этапе компиляции пустые операции, а в итоговых артефактах нет связанных символов otel или observability.
zig build test # по умолчанию: -Dwith_otel=false (пустая операция)
zig build test -Dwith_otel=true # включение: испускаются спаны
При включении каждый публичный вызов Token.sign / Token.decrypt испускает спан hsm.{операция} (например, hsm.sign, hsm.decrypt) с двумя атрибутами:
crypto.algorithm — тег алгоритма (например, ecdsa_p256, rsa2048_pkcs1v15).crypto.key.id — идентификатор слота (например, authentication, signature, key_management, card_auth).Прослойка построена вокруг одного правила: НИКОГДА не экспортировать криптографический материал. Атрибуты спанов экспортируются на внешний хост (обычно в OTLP-коллектор) и попадают в трассы/журналы, чьи средства контроля доступа могут быть слабее, чем у самой операции HSM.
hsm.observability.startHsmOperationSpan.const hsm = @import("hsm");
pub fn main() !void {
// Разместите значение Otel по стабильному адресу — либо переменная
// в стеке main() на всё время жизни процесса, либо выделение в куче.
// Вспомогательная инициализация присутствует только при
// `-Dwith_otel=true`; при отключённой сборке hsm.observability_init
// разрешается в пустую структуру, и этот блок компилируется в пустую
// операцию.
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`, привязанный к вашему
// сервису.
}
Прослойка читает переменные окружения OTEL_* (сэмплер, конечная точка экспортёра, service.name и т.д.) согласно спецификации переменных окружения OpenTelemetry. Значения по умолчанию: сэмплер parentbased_traceidratio с вероятностью 5%, экспортёр OTLP/HTTP-protobuf на http://localhost:4318.
Полный рецепт интеграции (настройка сборки, тестовый стенд, верификатор пустых операций) описан в репозитории развёртывания otel: otel/docs/integration/RECIPE.md.
Перед слиянием изменений, затрагивающих прослойку, должны пройти три этапа:
zig build test # флаг по умолчанию = false
zig build test -Dwith_otel=true # флаг включён
scripts/verify-consumer-noop.sh # проверка утечки символов
Проверка verify-consumer-noop.sh собирает библиотеку с -Dwith_otel=false и просматривает zig-out/ с помощью nm --defined-only, завершаясь ошибкой, если какой-либо символ 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) | Открытие токена по идентификатору |
token.reconnect() | Переподключение к токену и сброс состояния аутентификации |
token.verifyPin(pin) | Проверка PIN |
token.changePin(old, new) | Изменение PIN |
token.unblockPin(puk, new_pin) | Разблокировка PIN с помощью PUK |
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+ запроса |