Коротко. При подписи Sigstore «без ключа» (keyless) подписант подтверждает своё удостоверение OIDC-токеном (рабочий процесс GitHub Actions, аккаунт Google или Microsoft). Fulcio выпускает для эфемерного ключа сертификат сроком примерно на 10 минут, в который записано это удостоверение. Подпись попадает в публичный журнал прозрачности Rekor, а всё, что нужно для проверки, собирается в бандл (.sigstore.json). Проверка должна выяснить, кто подписал, а не только то, что подпись верна. В cosign для этого служат флаги --certificate-identity и --certificate-oidc-issuer, которые в режиме без ключа обязательны. Сведения о происхождении npm (npm provenance) и аттестации артефактов GitHub основаны на том же механизме: npm audit signatures и gh attestation verify проверяют их за вас.
Зачем подпись без ключа
У классической подписи релизов с помощью GPG или долгоживущего ключа подписи кода две хронические проблемы. Пользователи должны знать, какой ключ правильный, так что распространение ключей и есть настоящая проблема доверия. Кроме того, ключ годами приходится где-то хранить, обычно в секрете CI, откуда он может утечь. Sigstore заменяет ключ удостоверением, которое людям и так понятно, например «рабочий процесс релиза в репозитории X» или «alice@example.com». А срок жизни ключа он заменяет записью в журнале, которая доказывает, когда создана подпись.
Sigstore состоит из публичных сервисов, которые поддерживает сообщество OpenSSF («общедоступный экземпляр», public-good instance), и клиентских инструментов:
- Fulcio — центр сертификации (CA), который выпускает краткосрочные сертификаты X.509 для удостоверений OIDC.
- Rekor — журнал прозрачности для подписей и аттестаций.
- Метаданные TUF доставляют клиентам корни доверия (CA Fulcio, ключ Rekor, службы меток времени).
- cosign — CLI для произвольных файлов (blob) и образов контейнеров. sigstore-python, sigstore-js, sigstore-go и sigstore-java — библиотеки для тех же задач.
Что происходит при подписи
- Клиент создаёт в памяти эфемерную пару ключей.
- Он получает OIDC-токен: в GitHub Actions — от раннера (
id-token: write), на ноутбуке — через вход в браузере. - Fulcio проверяет токен и выпускает сертификат, который привязывает открытый ключ к удостоверению: адрес электронной почты или URI рабочего процесса попадает в альтернативное имя субъекта (Subject Alternative Name, SAN), а издатель и детали CI — в расширения.
- Клиент подписывает дайджест артефакта и отправляет подпись и сертификат в Rekor, а Rekor возвращает запись журнала с доказательством включения и подписанной меткой времени.
- Эфемерный закрытый ключ удаляется. В бандле остаются сертификат, подпись и доказательства из журнала.
Насколько недолго живёт такой сертификат, видно по бандлу, который cosign прикладывает к собственному релизу v3.1.3:
issuer=O=sigstore.dev, CN=sigstore-intermediate
notBefore=Aug 6 01:03:25 2026 GMT
notAfter=Aug 6 01:13:25 2026 GMT
X509v3 Extended Key Usage:
Code Signing
X509v3 Subject Alternative Name: critical
email:keyless@projectsigstore.iam.gserviceaccount.com
1.3.6.1.4.1.57264.1.1:
https://accounts.google.com
Срок действия — 10 минут. Проверить подпись можно и после этого срока: запись в Rekor доказывает, что подпись создана внутри этого окна. integratedTime этой записи в Rekor — 2026-08-06 01:03:25 UTC.
Бандл
Бандл — документ JSON, в котором есть всё, что нужно проверяющей стороне, кроме самого артефакта и корней доверия. Вот бандл из релиза cosign в сокращённом виде:
{
"mediaType": "application/vnd.dev.sigstore.bundle.v0.3+json",
"verificationMaterial": {
"certificate": { "rawBytes": "MIIDDzCCApWgAwIBAgIUavXJ..." },
"tlogEntries": [
{
"logIndex": "2352242192",
"kindVersion": { "kind": "hashedrekord", "version": "0.0.1" },
"integratedTime": "1785978205",
"inclusionPromise": { "signedEntryTimestamp": "MEYCIQCvddr55BoX..." },
"inclusionProof": { "logIndex": "2230337930", "treeSize": "2230337938", "rootHash": "62m7TRzwvcKyu+r6...", "hashes": ["..."], "checkpoint": { "envelope": "..." } },
"canonicalizedBody": "eyJhcGlWZXJzaW9u..."
}
],
"timestampVerificationData": { "rfc3161Timestamps": ["..."] }
},
"messageSignature": {
"messageDigest": { "algorithm": "SHA2_256", "digest": "xdMk4JGCaw16eOsW..." },
"signature": "MEUCIECQp5FyO00l..."
}
}
certificate— конечный сертификат (leaf) от Fulcio, в котором записано удостоверение.tlogEntriesсодержит запись Rekor: доказательство включения относительно подписанной контрольной точки (checkpoint) и подписанную метку времени записи. С ними проверяющей стороне вообще не нужно обращаться к Rekor.timestampVerificationDataсодержит необязательные метки времени RFC 3161 от службы меток времени.- Содержимое — либо
messageSignatureдля обычной подписи файла (blob), либоdsseEnvelopeдля аттестации, то есть подписанного заявления in-toto.
Инструмент проверки бандлов Sigstore проверяет все эти части прямо в браузере и показывает удостоверение из сертификата — на него нужно смотреть первым делом, когда подпись не проходит проверку.
Проверка: главное — удостоверение
Вот как cosign проверяет бинарный файл собственного релиза (ghcr.io/sigstore/cosign/cosign:v3.1.3, 20 октября 2026 года). Релиз собирается на инфраструктуре Google, поэтому удостоверение здесь — сервисный аккаунт Google, и издатель тоже Google, а не GitHub:
$ cosign verify-blob cosign-linux-arm64 --bundle cosign-linux-arm64.sigstore.json \
--certificate-identity keyless@projectsigstore.iam.gserviceaccount.com \
--certificate-oidc-issuer https://accounts.google.com
Verified OK
Теперь случаи с ошибками. Сообщение в каждом из них точно указывает, какая проверка не прошла:
--- right email, wrong issuer
error during command execution: failed to verify certificate identity: no matching CertificateIdentity found, last error: expected issuer value "https://token.actions.githubusercontent.com", got "https://accounts.google.com"
--- a different identity
error during command execution: failed to verify certificate identity: no matching CertificateIdentity found, last error: expected SAN value "release@example.com", got "keyless@projectsigstore.iam.gserviceaccount.com"
--- no identity at all
error during command execution: --certificate-identity or --certificate-identity-regexp is required for verification in keyless mode
--- a modified binary
error during command execution: failed to verify signature: could not verify message: invalid signature when validating ASN.1 encoded signature
Третий случай — сознательное проектное решение. Кто угодно может получить сертификат Fulcio на собственное удостоверение и подписать что угодно. Подпись имеет смысл только тогда, когда вы проверили, что удостоверение именно то, которое вы ожидаете. cosign отказывается проверять подписи без ключа, если удостоверение не указано (страница ошибки). Последний случай показывает, как выглядит подмена файла (страница ошибки).
Откуда взять ожидаемое удостоверение? Его можно найти в документации проекта или один раз прочитать из бандла, которому вы доверяете, и закрепить в скрипте проверки. --certificate-identity-regexp удобен для тегов (например, ...release.yml@refs/tags/v.*), но привязывайте выражение и к началу, и к концу строки. Шаблону вроде .*sigstore.* соответствуют удостоверения, которые может создать кто угодно.
Подпись в GitHub Actions
В CI удостоверение — это рабочий процесс. Fulcio записывает файл рабочего процесса и ссылку (ref) в SAN, а детали — в расширения. Что содержит сертификат из GitHub Actions, показывает бандл сведений о происхождении npm для sigstore@5.0.0. Ниже — расширения X.509 его сертификата, декодированные небольшим скриптом:
1.3.6.1.4.1.57264.1.8 Issuer (V2) https://token.actions.githubusercontent.com
1.3.6.1.4.1.57264.1.9 Build Signer URI https://github.com/sigstore/sigstore-js/.github/workflows/release.yml@refs/heads/main
1.3.6.1.4.1.57264.1.12 Source Repository URI https://github.com/sigstore/sigstore-js
1.3.6.1.4.1.57264.1.13 Source Repository Digest 7d2900eca1c22b3f87c13987c8d4b7c9a29b733a
1.3.6.1.4.1.57264.1.14 Source Repository Ref refs/heads/main
1.3.6.1.4.1.57264.1.18 Build Config URI https://github.com/sigstore/sigstore-js/.github/workflows/release.yml@refs/heads/main
1.3.6.1.4.1.57264.1.20 Build Trigger push
1.3.6.1.4.1.57264.1.21 Run Invocation URI https://github.com/sigstore/sigstore-js/actions/runs/26781866618/attempts/1
cosign умеет проверять и эти утверждения, а не только SAN:
$ cosign verify-blob-attestation --bundle provenance.sigstore.json --type slsaprovenance1 \
--certificate-identity-regexp '^https://github\.com/sigstore/sigstore-js/\.github/workflows/release\.yml@' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
--certificate-github-workflow-repository sigstore/sigstore-js \
--certificate-github-workflow-trigger push \
sigstore-5.0.0.tgz
Verified OK
Чтобы подписывать собственные релизы в GitHub Actions, задаче нужно разрешение на запрос OIDC-токена. В README actions/attest от GitHub приведена такая минимальная настройка сведений о происхождении сборки для файла:
permissions:
id-token: write
attestations: write
artifact-metadata: write
steps:
# ... build steps that produce dist/app.tar.gz ...
- uses: actions/attest@v4
with:
subject-path: 'dist/app.tar.gz'
Публичные репозитории используют общедоступный экземпляр Sigstore. Частные репозитории используют собственный экземпляр GitHub, для которого нужен GitHub Enterprise Cloud. actions/attest-build-provenance по-прежнему работает, но начиная с v4 это лишь обёртка над actions/attest. Подписать файл (blob) с помощью cosign прямо в задаче можно командой cosign sign-blob --bundle app.sigstore.json app.tar.gz --yes. Справка cosign по флагу --oidc-provider называет github-actions среди источников токена, которые cosign пробует, если источник не задан. Для этой страницы мы не выполняли подпись без ключа, потому что каждая такая подпись в общедоступном экземпляре становится постоянной публичной записью в журнале.
Подпись из рабочего процесса заслуживает ровно столько доверия, сколько сам рабочий процесс. Любой, кто может выполнить push в ветку или запустить рабочий процесс, может получить верную подпись с этим удостоверением. Защищайте рабочие процессы релизов правилами защиты веток и тегов, обязательными ревью и одобрениями для окружений (environments), а если выпускаете релизы из тегов, закрепляйте проверку на ссылке тега (@refs/tags/...).
Сведения о происхождении npm
Для пакетов, собранных в поддерживаемой системе CI, npm публикует два вида доказательств Sigstore: аттестацию публикации, подписанную реестром, и аттестацию сведений о происхождении SLSA, подписанную при сборке. npm audit signatures проверяет обе для всего установленного:
$ npm audit signatures
audited 52 packages in 2s
52 packages have verified registry signatures
26 packages have verified attestations
(use --json --include-attestations to view attestation details)
Аттестации конкретной версии публично доступны по адресу https://registry.npmjs.org/-/npm/v1/attestations/<name>@<version>. Для sigstore@5.0.0:
https://github.com/npm/attestation/tree/main/specs/publish/v0.1 | bundle application/vnd.dev.sigstore.bundle+json;version=0.2
subject: pkg:npm/sigstore@5.0.0 849a897e81bf7b8a8541a6ae...
https://slsa.dev/provenance/v1 | bundle application/vnd.dev.sigstore.bundle.v0.3+json
subject: pkg:npm/sigstore@5.0.0 849a897e81bf7b8a8541a6ae...
workflow: https://github.com/sigstore/sigstore-js .github/workflows/release.yml refs/heads/main
builder: https://github.com/actions/runner/github-hosted
commit: 7d2900eca1c22b3f87c13987c8d4b7c9a29b733a
Дайджест субъекта — SHA-512 архива пакета (849a897e...), поэтому бандл сведений о происхождении можно проверить с помощью cosign по скачанному файлу .tgz, как показано выше. С доверенной публикацией npm (trusted publishing, npm CLI 11.5.1 и новее) пакеты, которые публикуются из GitHub Actions или GitLab CI через OIDC, получают сведения о происхождении автоматически, без флага --provenance и без долгоживущего токена npm. CircleCI поддерживается для доверенной публикации, но пока не для сведений о происхождении. Вне поддерживаемой системы CI команда npm publish --provenance завершается ошибкой Automatic provenance generation not supported for provider: null. Проверка происхождения npm показывает репозиторий, рабочий процесс и коммит для любой версии пакета.
Аттестации артефактов GitHub
GitHub хранит аттестации файлов релизов и отдаёт их через свой API. У архива gh_2.102.0_linux_arm64.tar.gz (SHA-256 7862c86c...) из релиза CLI gh на 20 октября 2026 года было две аттестации: сведения о происхождении SLSA от рабочего процесса развёртывания cli/cli и аттестация релиза, подписанная самим GitHub. Если бандл сведений о происхождении сохранён локально, gh attestation verify работает без входа в аккаунт и выводит применённую политику:
$ gh attestation verify gh_2.102.0_linux_arm64.tar.gz --repo cli/cli --bundle gh-1.sigstore.json
Loaded digest sha256:7862c86c72f43df3a2d93ddde6f473285b4e2af61b494849846827e513ef6484 for file:///d/gh_2.102.0_linux_arm64.tar.gz
Loaded 1 attestations from /d/gh-1.sigstore.json
The following policy criteria will be enforced:
- Predicate type must match:................ https://slsa.dev/provenance/v1
- Source Repository Owner URI must match:... https://github.com/cli
- Source Repository URI must match:......... https://github.com/cli/cli
- Subject Alternative Name must match regex: (?i)^https://github\.com/cli/cli/
- OIDC Issuer must match:................... https://token.actions.githubusercontent.com
✓ Verification succeeded!
The following 1 attestation matched the policy criteria
- Attestation #1
- Build repo:..... cli/cli
- Build workflow:. .github/workflows/deployment.yml@refs/heads/trunk
- Signer repo:.... cli/cli
- Signer workflow: .github/workflows/deployment.yml@refs/heads/trunk
С --repo example/cli та же проверка заканчивается сообщением ✗ Sigstore verification failed. Политика по умолчанию (владелец, репозиторий, префикс SAN) — хорошее начало. Для автоматизации релизов добавьте --signer-workflow, чтобы закрепить точный файл рабочего процесса, и --deny-self-hosted-runners, если ваши сборки выполняются только на раннерах, размещённых GitHub. Тот же бандл проходит и проверку в cosign: команда cosign verify-blob-attestation --type slsaprovenance1 с SAN https://github.com/cli/cli/.github/workflows/deployment.yml@refs/heads/trunk вывела Verified OK.
Вторая аттестация устроена иначе. Её тип предиката — https://in-toto.io/attestation/release/v0.2. В сертификате указан URI:https://dotcom.releases.github.com, а выпустил его O=GitHub, Inc., CN=Fulcio Intermediate l1, собственный Fulcio GitHub; вместо записи в Rekor у этой аттестации метка времени RFC 3161. GitHub создаёт такую аттестацию для репозиториев, в которых включены неизменяемые релизы (immutable releases). Она доказывает, что файл относится к релизу, но не то, как он собран. Её проверяют gh release verify и gh release verify-asset. В нашем тесте команде gh release verify-asset понадобился gh с выполненным входом: без токена gh 2.102.0 остановился с сообщением To get started with GitHub CLI, please run: gh auth login.
Когда подпись без ключа не подходит
Сборки в изолированной сети, непубличные артефакты, которые не должны попадать в публичный журнал, или политики, требующие держать ключ в HSM: для таких случаев cosign умеет подписывать и ключами, а отправку в журнал можно отключить. Проверено с cosign v3.1.3:
$ cosign generate-key-pair
Private key written to cosign.key
Public key written to cosign.pub
$ cosign sign-blob --key cosign.key --tlog-upload=false --use-signing-config=false --bundle app.sigstore.json app-1.0.0.tar.gz --yes
Using payload from: app-1.0.0.tar.gz
Signing artifact...
Wrote bundle to file app.sigstore.json
$ cosign verify-blob --key cosign.pub --bundle app.sigstore.json --insecure-ignore-tlog=true app-1.0.0.tar.gz
WARNING: Skipping tlog verification is an insecure practice that lacks transparency and auditability verification for the blob.
Verified OK
$ cosign verify-blob --key cosign.pub --bundle app.sigstore.json app-1.0.0.tar.gz
error during command execution: failed to verify log inclusion: not enough verified log entries from transparency log: 0 < 1
В этом случае бандл содержит только подсказку publicKey, и вы снова возвращаетесь к распространению и защите долгоживущего ключа — к той самой проблеме, которую устраняет подпись без ключа. Если нужна непубличная, но по-прежнему доступная для аудита схема, разверните собственный экземпляр Sigstore (Fulcio, Rekor, TUF) или воспользуйтесь экземпляром стороннего поставщика. Так GitHub поступает для частных репозиториев.
Состояние инфраструктуры в 2026 году
- Rekor v2, журнал на основе тайлов, вышел в общий доступ в октябре 2025 года. Проверяющим сторонам нужен cosign 2.6.0 или 3.0.1 и новее. В Rekor v2 больше нет поискового индекса.
- В июне 2026 года мейнтейнеры объявили, что общедоступный экземпляр «в обозримом будущем продолжит использовать Rekor v1 как журнал по умолчанию». Rekor v2 остаётся в эксплуатации для экосистем, которые сами решили на него перейти.
- Отдельный постквантовый Sigstore с ML-DSA, согласно объявлению от октября 2026 года, «намечен на середину 2027 года». Классический экземпляр будет работать до отключения, которое «предварительно запланировано на 2029 или 2030 год». Форматом в обоих случаях остаются бандлы.
Со стороны проверки почти ничего не меняется: обновляйте клиенты, проверяйте по бандлам и закрепляйте удостоверения, а не URL журналов.
Частые ошибки
- Проверка подписи без проверки удостоверения. Верная подпись Sigstore говорит лишь о том, что подписал кто-то.
- Слишком широкие шаблоны удостоверений. Регулярные выражения без привязки к началу и концу строки или проверка издателя без проверки SAN.
- Закрепление на ветке, когда релизы выпускаются из тегов.
@refs/heads/mainпринимает всё, что собрано изmain, включая сборку, которую никто не собирался выпускать. - Предположение, что подпись доказывает сборку. Подпись файла (blob) доказывает, кто подписал байты. Сведения о происхождении (SLSA, in-toto) говорят, как и из чего эти байты собраны. Проверяйте то, что требует ваша политика.
- Подпись личных релизов личным адресом электронной почты без понимания того, что этот адрес записывается в сертификат, а значит, и в публичный журнал, где он останется навсегда.
- Публикация отдельных файлов
.sigи.pemиз старых версий cosign. Публикуйте бандл.sigstore.json. Этот формат понимают все современные средства проверки.
Источники
- Документация Sigstore и формат бандла.
- cosign v3.1.3 и бандлы его релизов.
- Блог Sigstore: Rekor v2 в общем доступе (октябрь 2025 года), sigstore.dev и развитие Rekor (июнь 2026 года), Постквантовая криптография для Sigstore (октябрь 2026 года).
- npm: доверенная публикация и эндпоинт аттестаций реестра.
- GitHub:
actions/attest,gh attestation verify,gh release verifyи предикат релиза in-toto.
Проверки выполнены 20 октября 2026 года в ghcr.io/sigstore/cosign/cosign:v3.1.3, а также с npm 11.19.1 и gh 2.102.0 (официальный архив релиза) в node:24.22.0-trixie.