Инструменты выпуска (tessera_issuer)
Tessera проверяет сертификаты на устройстве, но сами сертификаты нужно чем-то
выпускать. Инструменты выпуска закрывают эту сторону: одно Rust-ядро собирает
TBSCertificate с расширениями Tessera, проверяет монотонное сужение рамок
делегирования до подписи и подписывает результат ключом выбранного бэкенда —
токена/HSM (PKCS#11), Vault Transit или локального PKCS#8-файла. С бэкендами
PKCS#11 и Vault инструмент — не кастодиан: приватные ключи через код выпуска
не проходят; файловый бэкенд — осознанный компромисс, ключ живёт в памяти
процесса выпуска (см. threat-model.md §11).
Компоненты:
- Ядро (
tessera_issuer, библиотека) — сборка TBS листа смены и CA организации, проверки рамок, случайные 128-битные серийники, выпуск CRL, журнал выпусков. Ядро pure-Rust и собирается в том числе подwasm32; адаптеры подписи — за feature-флагами. - CLI
issuer— код выпуска для автоматизации (тикет-системы, скрипты) и ручной работы. Ни одна проверка в CLI не переопределяется: запрос, который отверг бы ядро, CLI отвергает точно так же.
Выпуск из браузера — локальный агент подписи и веб-кабинет, работающие с тем же ядром, — поставляется отдельно, в составе коммерческих инструментов (см. «Выпуск из браузера»). Открытый репозиторий поставляет CLI.
Модель ролей — из предъявленного родительского сертификата: любой CA с рамками делегирования (корень парка или CA организации) выпускает и подчинённые CA организаций, и листы смен инженерам — строго в своих рамках делегирования, которые на каждом шаге могут только сужаться. Отдельного «режима по должности» нет.
Семантику самих расширений (host_binding, allowed_roles,
max_integrity, profile_version, delegation_constraints) и их OID см. в
cert-issuance.md — здесь описан инструмент, а не формат.
Поверхность атаки инструментов выпуска разобрана в
threat-model.md §11; дублировать её тут не будем.
Быстрый старт CLI
Заголовок раздела «Быстрый старт CLI»Все выпускающие подкоманды выбирают бэкенд подписи флагами --backend
(pkcs11 — по умолчанию, vault, file), --key (метка ключа CA;
для file — опционален, по умолчанию имя файла ключа), --algorithm
(ecdsa-p256 — по умолчанию, ecdsa-p384, rsa-sha256; для file
алгоритм выводится из самого ключа, а флаг работает как сверка). Времена (--not-before,
--not-after, --this-update, …) задаются в Unix-секундах. Вход (--parent,
--spki, --csr, --issuer) принимается в PEM или DER — формат определяется по
содержимому. Выход — PEM, либо DER при --der.
PIN токена никогда не передаётся аргументом командной строки: инструмент запрашивает его на время операции по лестнице источников — см. Источники секретов.
Выпуск CA организации
Заголовок раздела «Выпуск CA организации»Под корневым сертификатом парка выпускается CA организации с назначением рамок делегирования (роли, потолок уровня МКЦ, потолок TTL, требуемые метки):
issuer issue-ca \ --backend pkcs11 --module /usr/lib/x86_64-linux-gnu/opensc-pkcs11.so \ --key tessera-root --algorithm ecdsa-p256 \ --parent root.pem \ --spki org-ca.spki.der \ --subject "CN=Org North CA,O=Org" \ --not-before 1750000000 --not-after 1900000000 \ --allow-role oper --allow-role serv \ --max-level 5 --max-ttl 14400 \ --require-tag region=north \ --journal issuance.ndjson \ --out org-ca.pemФлаги --allow-role, --require-tag повторяются для нескольких значений.
Рамки выпускаемого CA обязаны быть ⊆ рамок родителя — иначе ядро отказывает
до подписи с указанием измерения (см. монотонное сужение в
cert-issuance.md).
Обязательные рамки: роли и потолок TTL
Заголовок раздела «Обязательные рамки: роли и потолок TTL»--allow-role обязателен и у issue-ca, и у issue-root: список ролей в
рамках делегирования — это закрытый белый список, и пустой список разрешает не
«любую роль», а ни одной. Умолчания здесь быть не может: имена ролей
принадлежат конкретному внедрению, поэтому любое подставленное значение либо
повторяет тот же тупик, либо молча расширяет рамки сверх названного оператором.
--max-ttl ограничивает срок жизни дочернего звена, поэтому у двух операций
разный смысл и разные умолчания:
| Операция | Что ограничивает --max-ttl | Умолчание |
|---|---|---|
issue-root | срок CA организации под корнем парка | 31536000 (год) |
issue-ca | срок листа смены под CA организации | 14400 (4 часа) |
Явный --max-ttl 0 отвергается при разборе аргументов: нулевой потолок требует
от дочернего звена нулевого срока действия, то есть под таким CA не проходит
ни один выпускаемый сертификат.
Оба ограничения снимают один и тот же класс ошибки: сертификат с пустым или нулевым измерением рамок выглядит валидным, попадает в журнал выдачи и не вызывает предупреждений, но вход по нему отказывает всегда, и обнаруживается это уже на устройстве.
Выпуск листа смены
Заголовок раздела «Выпуск листа смены»Публичный ключ листа берётся из явного --spki (тогда --subject обязателен)
или из --csr (тогда субъект и ключ берутся из запроса). Флаги взаимно
исключающие.
Прямой путь (SPKI):
issuer issue-leaf \ --backend pkcs11 --module /usr/lib/.../opensc-pkcs11.so \ --key org-north-ca \ --parent org-ca.pem \ --spki ivanov.spki.der \ --subject "CN=ivanov,O=Org" \ --host "sha256:<host_id_hash>" \ --role oper \ --not-before 1750000000 --not-after 1750086400 \ --max-integrity-level 2 --max-integrity-categories 0x1 \ --journal issuance.ndjson \ --out ivanov.pemПуть по CSR (см. CSR-поток):
issuer issue-leaf \ --backend pkcs11 --module /usr/lib/.../opensc-pkcs11.so \ --key org-north-ca \ --parent org-ca.pem \ --csr ivanov.csr.pem \ --host "sha256:<host_id_hash>" --role oper \ --not-before 1750000000 --not-after 1750086400 \ --journal issuance.ndjson \ --out ivanov.pem--host и --role повторяются. --max-integrity-level опционален
(без него потолок целостности не задаётся); --max-integrity-categories
(битовая маска) учитывается только вместе с уровнем.
Допуск задают два расширения выпускаемого листа, и оба собираются из этих
флагов: pam_cert_host_binding (--host) — на каких устройствах удостоверение
принимается, pam_cert_allowed_roles (--role) — какие роли предъявитель может
активировать. Имя учётной записи входа и есть роль, поэтому второй список
одновременно определяет, в какие ролевые учётные записи пущен предъявитель:
отдельного списка допуска по учётным записям нет.
Выпуск CRL
Заголовок раздела «Выпуск CRL»issuer issue-crl \ --backend pkcs11 --module /usr/lib/.../opensc-pkcs11.so \ --key org-north-ca \ --issuer org-ca.pem \ --this-update 1750000000 --next-update 1750604800 \ --crl-number 7 --last-crl-number 6 \ --revoke 2a:1750000500:1 \ --revoke 3b:1750000600 \ --journal issuance.ndjson \ --out org-ca.crl--crl-number обязан быть строго больше --last-crl-number (монотонность
crlNumber в стейте CA) — иначе отказ. Каждый --revoke — это
serial_hex:unix_date[:reason_code], где reason_code — код причины RFC 5280
(0–6), опционален; флаг повторяется.
Верификация журнала
Заголовок раздела «Верификация журнала»issuer verify-journal --journal issuance.ndjsonПечатает одно из трёх состояний: цепочка цела и полностью подписана; цела, но
хвост не подписан (с номером seq, с которого); нарушена (с позицией первой
невалидной записи — тогда ненулевой код возврата). См.
Журнал выпусков.
Язык сообщений
Заголовок раздела «Язык сообщений»Результатные сообщения оператору локализованы (RU/EN). Локаль: флаг --lang
(ru/en) → TESSERA_ISSUER_LANG → LANG → английский по умолчанию.
Совпадение по префиксу: любое значение, начинающееся на ru, выбирает русский.
Технические идентификаторы (субъект RFC 4514, OID, crlNumber, серийники) не
переводятся. См. Локализация.
CSR-поток
Заголовок раздела «CSR-поток»CSR (PKCS#10) — равноправный с прямым SPKI источник ключа листа. Он снимает необходимость передавать инструменту публичный ключ отдельно и даёт proof-of-possession: инженер генерирует ключ на своём токене и подписывает им запрос.
Сторона инженера — сформировать CSR ключом на токене:
issuer csr \ --backend pkcs11 --module /usr/lib/.../opensc-pkcs11.so \ --key ivanov-token-key --algorithm ecdsa-p256 \ --subject "CN=ivanov,O=Org" \ --spki ivanov.spki.der \ --out ivanov.csr.pemИнструмент только подписывает: публичный ключ инженера (--spki) подаётся
явно, запрос подписывается тем ключом токена, что адресует --key.
Proof-of-possession действителен, только если ключ токена соответствует
--spki — это ответственность инженера, ключи инструмент не генерирует.
Сторона оператора — issue-leaf --csr (см. выше). Что важно:
- Ядро проверяет самоподпись CSR (P-256/RSA, pure-Rust) до выпуска; битая самоподпись → отказ до подписи. CLI дополнительно печатает субъект CSR и статус самоподписи перед выпуском.
- Субъект и публичный ключ берутся из CSR. Скоуп (рамки, привязки, роли) задаёт исключительно оператор флагами — атрибуты CSR на состав расширений не влияют. Иначе CSR стал бы каналом «инженер сам запросил себе шире».
Выпуск из браузера
Заголовок раздела «Выпуск из браузера»Выпуск из браузера — локальный агент подписи на 127.0.0.1, который мостит
браузер к токену/HSM, плюс веб-кабинет (SPA поверх того же WASM-ядра), который
собирает TBS на клиенте и показывает оператору сводку для подтверждения, —
поставляется отдельно, в составе коммерческих инструментов (tessera-enterprise).
Из этого репозитория он не собирается. Контакт — см.
LICENSE.commercial.
Открытый репозиторий поставляет CLI issuer, который работает с тем же ядром и
теми же бэкендами подписи и выполняет те же проверки до подписи. Всё ниже —
бэкенды подписи и журнал выпусков — относится к CLI.
Бэкенды подписи
Заголовок раздела «Бэкенды подписи»Ядро не знает, где ключ: подпись готового TBS уходит за единый интерфейс,
никакой ключевой материал через него не проходит. Бэкенд выбирается --backend.
PKCS#11 (токен и HSM)
Заголовок раздела «PKCS#11 (токен и HSM)»Бэкенд по умолчанию, один код для аппаратных токенов и HSM. Флаги: --module
(путь к .so/.dylib/.dll — обязателен), --token-label (выбор токена,
если их несколько), --key (метка CKA_LABEL ключа CA), --pinentry
(программа pinentry явно).
PIN запрашивается на время операции (Secret + zeroize, не в логах и не в
argv) по лестнице источников — см. Источники секретов.
Неинтерактивный запуск: --pin-file <path> или --pin-stdin.
Для проб и CI подойдёт SoftHSM как программный PKCS#11-модуль. ГОСТ-токены работают через тот же адаптер, если токен отдаёт нужный PKCS#11-механизм.
Источники секретов
Заголовок раздела «Источники секретов»Секреты — PIN токена для PKCS#11, пароль ключа для файлового бэкенда и пароль собираемого контейнера PKCS#12 — запрашиваются по одной лестнице источников:
- Источник, заданный флагом.
--pinentry <path>— внешний pinentry-совместимый диалог;--pin-file <path>/--pin-stdin— PIN токена;--key-passphrase-file <path>/--key-passphrase-stdin— пароль ключа файлового бэкенда;--p12-passphrase-file <path>/--p12-passphrase-stdin/--p12-passphrase-prompt— пароль собираемого контейнера. Заданный источник используется без обращения к остальным. Два явных источника одновременно — ошибка разбора аргументов. Флаг, относящийся к другому бэкенду (например--key-passphrase-fileпри--backend pkcs11), тоже отвергается: названный источник не должен молча подменяться другим. - pinentry, найденный на
PATH(pinentry,pinentry-mac,pinentry-gtk-2,pinentry-qt,pinentry-curses). - Консольный ввод без эха, если процесс подключён к терминалу.
- Переменная окружения
TESSERA_ISSUER_PINилиTESSERA_ISSUER_KEY_PASSPHRASE— последнее средство, при использовании печатается предупреждение: значение переменной видно дочерним процессам и попадает в дампы памяти.
graph TD
A["Нужен секрет"] --> B{"источник задан флагом?"}
B -->|--pinentry| C["внешний диалог"]
B -->|--pin-file / --pin-stdin| D["чтение из файла или потока"]
B -->|нет| E{"pinentry на PATH?"}
E -->|да| C
E -->|нет| F{"есть терминал?"}
F -->|да| G["консольный ввод без эха"]
F -->|нет| H["переменная окружения<br/>+ предупреждение"]
Установка GnuPG или Gpg4win не требуется ни на одной платформе: без pinentry инструмент спрашивает секрет сам. Флага, принимающего секрет значением, не существует: аргументы видны в списке процессов.
Файл секрета. На Linux и macOS файл обязан быть недоступен группе и
остальным (chmod 600) — иначе отказ до чтения содержимого; проверка идёт по
уже открытому файлу, поэтому подменить путь между проверкой и чтением нельзя.
На Windows права файла в сравнимом виде не выражены, проверка там не
выполняется: файл принимается, а в stderr печатается предупреждение об этом.
Защита в этом случае держится на правах каталога, в котором лежит файл, — держите
его там, куда не может войти никто, кроме владельца.
Стандартный ввод. На Linux и macOS секрет читается в затираемый буфер в
обход буфера стандартной библиотеки. На прочих платформах такой обход
недоступен, и прочитанный секрет остаётся в буфере стандартного ввода до конца
работы процесса. Если это существенно, предпочитайте --pin-file и
--key-passphrase-file: у файлового источника такого остатка нет.
Источник рассчитан на конвейер и терминал. Перенаправление стандартного ввода
из частично прочитанного файла поддержанным способом задать секрет не
является: на Linux и macOS путь /dev/stdin устроен по-разному — на Linux он
ведёт через /proc/self/fd/0 и переоткрытие обычного файла начинает чтение с
начала, на macOS дескриптор дублируется вместе с текущей позицией. Поэтому
exec 0<secrets.txt, одна прочитанная строка и затем issuer --pin-stdin дадут
на Linux первую строку файла, а на macOS — следующую непрочитанную. Для чтения
секрета из файла есть --pin-file, который делает это предсказуемо.
Длина секрета ограничена 4096 байтами: источник, не давший перевода строки в этих пределах, отвергается с ошибкой, называющей источник и границу.
Vault / OpenBao Transit
Заголовок раздела «Vault / OpenBao Transit»Подпись готового TBS через HTTP-API Transit. Флаги: --vault-addr
(например https://vault.example:8200 — обязателен), --mount (mount Transit,
по умолчанию transit), --vault-key (имя ключа Transit; по умолчанию равно
--key), --ca-bundle (PEM-бандл доверенных CA вместо системного стора — для
приватных Vault-CA), --prehashed (слать локально вычисленный дайджест с
prehashed=true — для ключей, настроенных на pre-hashed вход).
Токен Vault читается из переменной окружения VAULT_TOKEN (пустой/незаданный →
отказ), передаётся в заголовке X-Vault-Token и не логируется. Для ECDSA
адаптер запрашивает marshaling_algorithm=asn1 (Vault возвращает DER-подпись).
Только Transit, не Vault PKI. Движок Vault PKI непригоден для Tessera:
encoding/asn1в Go не разбирает OID-дуги большеint64, а наши расширения сидят в арке2.25.<UUID>— через Vault PKI такой сертификат не выпустить. Поэтому Transit подписывает уже собранный нами TBS, а не строит сертификат.
Transit не проверяет, что подписывает, — все проверки выпуска выполняются до
подписи в ядре, на клиенте; кто может звать sign, ограничивает политика Vault.
Ключ в файле
Заголовок раздела «Ключ в файле»Бэкенд file подписывает ключом CA из локального файла: --key-file <path>.
Формат — PKCS#8 (PEM или DER), включая зашифрованный (ENCRYPTED PRIVATE KEY, PBES2); типы ключей — ECDSA P-256/P-384 и RSA. ГОСТ-ключи файловым
бэкендом не поддерживаются — для ГОСТ остаётся PKCS#11. Другие форматы
конвертируются штатно:
# новый зашифрованный ключ P-256openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 \ | openssl pkcs8 -topk8 -v2 aes-256-cbc -out ca-key.p8chmod 600 ca-key.p8
# конвертация существующего ключа (SEC1/PKCS#1 → PKCS#8)openssl pkcs8 -topk8 -v2 aes-256-cbc -in old-key.pem -out ca-key.p8Правила бэкенда:
- Файл ключа обязан быть недоступен группе и остальным (
chmod 600) — иначе отказ до чтения содержимого. Владение файлом и права каталога бэкенд не проверяет — держите ключ в своём каталоге с правами700. На Windows права файла не проверяются, как и для файлов секретов (см. Источники секретов): ключ принимается, а в stderr печатается предупреждение об этом. - Пароль зашифрованного ключа запрашивается по лестнице источников (см. Источники секретов); в аргументы командной строки и логи пароль не попадает, память затирается.
- Незашифрованный ключ принимается, но с предупреждением при каждом старте; рекомендация — зашифрованный PKCS#8.
- Алгоритм подписи выводится из самого ключа;
--algorithm, не совпадающий с ключом, — ошибка.--keyопционален (по умолчанию — имя файла) и служит идентификатором ключа в журнале выпусков.
Ключ в файле — осознанный компромисс для стендов, CI и малых инсталляций: при компрометации хоста он, в отличие от токена/HSM/Vault, извлекаем. Для прода рекомендованы PKCS#11 или Vault Transit (см. threat-model.md §11).
Журнал выпусков
Заголовок раздела «Журнал выпусков»Каждая операция (выпуск листа, CA, CRL) — запись в NDJSON-журнале, связанная в
hash-chain: монотонный seq, хэш предыдущей записи, фиксированный genesis.
Журнал fail-closed: запись делается до выдачи артефакта, и если журнал
недоступен, операция отклоняется (сертификат без записи не выпускается). Путь
задаётся флагом --journal каждой выпускающей подкоманды.
Голова цепочки периодически подписывается через тот же интерфейс подписи (по
завершении сессии и по команде). issuer verify-journal различает три
состояния:
- цела, хвост полностью подписан — всё в порядке;
- цела, неподписанный хвост с seq N — цепочка не нарушена, но записи с
Nещё не покрыты подписью головы; - нарушена в позиции N — разрыв/подмена/переупорядочивание на записи
N(ненулевой код возврата).
Журнал вторичен: первичная правда — аудит входов на самих устройствах; журнал служит инвентаризации выпуска и разбору инцидентов.
Локализация
Заголовок раздела «Локализация»Операторские поверхности инструмента (сводка операции, построенная из TBS, и вывод CLI) локализованы на русский и английский без i18n-фреймворка (компактная таблица строк). Для CLI локаль разрешается один раз при старте:
- явная настройка — флаг
--lang(ru/en); - переменная
TESSERA_ISSUER_LANG; - переменная
LANG; - fallback — английский.
Совпадение по префиксу языка, регистронезависимо: ru_RU.UTF-8 и RU дают
русский, en_GB — английский; нераспознанное значение просто проваливается к
следующему источнику. Переводятся только подписи полей — технические данные
(субъект RFC 4514, OID, role_id, серийники, crlNumber, таймстемпы)
воспроизводятся байт-в-байт в любой локали.
См. также
Заголовок раздела «См. также»- issuance-workflows.md — пять процессов выпуска, чем они различаются и как выбрать; пошаговые страницы каждого процесса.
- carriers.md — виды носителей, их свойства, пути к модулям PKCS#11 по операционным системам.
- cert-issuance.md — расширения Tessera, их OID и семантика, монотонное сужение рамок делегирования.
- threat-model.md §11 — поверхность атаки инструментов выпуска, ограничение ущерба, остаточные риски.