Sigstore или GPG — это выбор между двумя способами подписывать артефакты релизов, контейнеры и пакеты. В OpenPGP (GnuPG и совместимые инструменты) мейнтейнер хранит долгоживущий закрытый ключ, публикует его отпечаток и выкладывает рядом с каждым файлом отделённую подпись. В Sigstore подписант проходит аутентификацию через OpenID Connect, центр сертификации Fulcio выпускает сертификат, который на десять минут привязывает новый ключ к удостоверению (identity) подписанта, подпись записывается в журнал прозрачности Rekor, а ключ выбрасывается. Проверяющие стороны проверяют удостоверение, а не ключ.
Коротко о главном
| Sigstore (без ключа) | OpenPGP (GPG) | |
|---|---|---|
| Чему доверяет проверяющая сторона | Удостоверению OIDC (адрес электронной почты или URI рабочего процесса) и издателю через корень Fulcio | Конкретному открытому ключу, полученному и проверенному по независимому каналу |
| Время жизни ключа подписи | Эфемерный ключ; сертификат действует 10 минут | Годы; срок действия задаёт и продлевает владелец |
| Управление ключами | Подписантам не нужно | Генерация, резервное копирование, защита, ротация, отзыв |
| Доказательство времени подписи | Запись в журнале Rekor (и, по желанию, метка времени RFC 3161) | Время создания подписи, указанное самим подписантом |
| Прозрачность | Каждая подпись публично записывается в журнал | Нет |
| Отзыв | Для эфемерных ключей не нужен; скомпрометированные удостоверения учитываются в политике проверки | Сертификат отзыва, который владелец должен опубликовать |
| Подпись офлайн | Нет (нужны Fulcio и Rekor, общедоступные или частные) | Да |
| Формат артефакта | Бандл Sigstore (.sigstore.json): сертификат, подпись или конверт DSSE, доказательство из журнала | Файл .asc или .sig |
| Типичные инструменты | cosign, sigstore-python, sigstore-js, gitsign, attest-build-provenance от GitHub | gpg, Sequoia (sq, sqv), инструменты rpm и apt |
| Где это стандарт | Сведения о происхождении npm (в общем доступе с сентября 2023 года), аттестации PyPI (с ноября 2024 года), аттестации артефактов GitHub (в общем доступе с июня 2024 года), CPython начиная с 3.11 (с 3.14 — единственный вариант), Kubernetes и многие образы контейнеров | Maven Central (обязательно), репозитории Debian и RPM, теги ядра Linux, многие архивы исходного кода |
| Конфиденциальность | Удостоверение подписанта (адрес электронной почты или репозиторий) публично видно в журнале | Ключ и идентификатор пользователя публичны, только если подписант сам их опубликует |
Что содержит подпись без ключа
npm хранит бандлы Sigstore для пакетов, опубликованных со сведениями о происхождении. Скрипт ниже загружает аттестации @noble/curves 2.3.0 через публичный API реестра и декодирует сертификат подписанта (20 октября 2026 года, curl, jq 1.8.1, OpenSSL 3.6.5).
#!/bin/sh
curl -s "https://registry.npmjs.org/-/npm/v1/attestations/@noble%2fcurves@2.3.0" -o att.json
jq -r '.attestations[].predicateType' att.json
bundle='.attestations[] | select(.predicateType == "https://slsa.dev/provenance/v1") | .bundle'
jq -r "$bundle | .mediaType" att.json
jq -r "$bundle | .verificationMaterial.tlogEntries[0] | \"Rekor log index \(.logIndex), integrated \(.integratedTime | tonumber | todate)\"" att.json
jq -r "$bundle | .verificationMaterial.certificate.rawBytes" att.json | openssl base64 -d -A > leaf.der
openssl x509 -inform DER -in leaf.der -noout -issuer -dates -ext subjectAltName
openssl x509 -inform DER -in leaf.der -noout -text | grep -A1 "57264.1.1:" | tail -1 | sed 's/^ */OIDC issuer: /'
rm -f att.json leaf.der
https://github.com/npm/attestation/tree/main/specs/publish/v0.1
https://slsa.dev/provenance/v1
application/vnd.dev.sigstore.bundle.v0.3+json
Rekor log index 2357213216, integrated 2026-08-06T07:37:48Z
issuer=O=sigstore.dev, CN=sigstore-intermediate
notBefore=Aug 6 07:37:48 2026 GMT
notAfter=Aug 6 07:47:48 2026 GMT
X509v3 Subject Alternative Name: critical
URI:https://github.com/paulmillr/jsbt/.github/workflows/release.yml@5320e209ca8cc9b1b0b569675e164bdd2a139d71
OIDC issuer: https://token.actions.githubusercontent.com
У пакета две аттестации: собственная аттестация публикации от npm и утверждение SLSA о происхождении сборки, подписанное в CI. Сертификат действует ровно десять минут и был записан в Rekor в ту же секунду, когда его выпустили. Его субъект — не человек и не ключ, а запущенный рабочий процесс, в данном случае общий рабочий процесс релиза из другого репозитория, закреплённый на коммите; другие расширения Fulcio фиксируют исходный репозиторий, коммит, тег и запуск. Те же данные для любого пакета декодируют инструменты проверки бандлов Sigstore и проверки происхождения npm.
Чем отличается проверка
Проверка подписи GPG — это одна команда, gpg --verify file.asc file, но её смысл зависит от всего, что было до неё: как проверяющая сторона получила ключ, сверили ли отпечаток с надёжным источником и не был ли ключ с тех пор отозван. Большинство результатов «Good signature» в CI-пайплайнах опираются на доверие к любому ключу, который импортировали несколькими строками выше.
Чтобы проверить подпись Sigstore, нужно указать ожидаемое удостоверение, поэтому вопрос политики приходится решать прямо в команде. Скрипт ниже трижды проверяет файл контрольных сумм из релиза v3.1.3 самого cosign с помощью официального образа контейнера cosign v3.1.3: по удостоверению, которое проект Sigstore указывает в документации для своих релизов (сервисный аккаунт Google, издатель https://accounts.google.com), по удостоверению рабочего процесса GitHub Actions, который этот файл не подписывал, и после того, как к файлу дописан один байт.
url=https://github.com/sigstore/cosign/releases/download/v3.1.3
curl -sSLO "$url/cosign_checksums.txt"
curl -sSLO "$url/cosign_checksums.txt.sigstore.json"
cosign() { docker run --rm -v "$PWD":/work:ro -w /work ghcr.io/sigstore/cosign/cosign:v3.1.3 "$@"; }
echo "== expected identity"
cosign verify-blob cosign_checksums.txt \
--bundle cosign_checksums.txt.sigstore.json \
--certificate-identity keyless@projectsigstore.iam.gserviceaccount.com \
--certificate-oidc-issuer https://accounts.google.com
echo "== any other identity"
cosign verify-blob cosign_checksums.txt \
--bundle cosign_checksums.txt.sigstore.json \
--certificate-identity "https://github.com/sigstore/cosign/.github/workflows/release.yml@refs/tags/v3.1.3" \
--certificate-oidc-issuer https://token.actions.githubusercontent.com
echo "== modified file"
printf 'x' >> cosign_checksums.txt
cosign verify-blob cosign_checksums.txt \
--bundle cosign_checksums.txt.sigstore.json \
--certificate-identity keyless@projectsigstore.iam.gserviceaccount.com \
--certificate-oidc-issuer https://accounts.google.com
== expected identity
Verified OK
== any other identity
Error: failed to verify certificate identity: no matching CertificateIdentity found, last error: expected SAN value "https://github.com/sigstore/cosign/.github/workflows/release.yml@refs/tags/v3.1.3", got "keyless@projectsigstore.iam.gserviceaccount.com"
error during command execution: failed to verify certificate identity: no matching CertificateIdentity found, last error: expected SAN value "https://github.com/sigstore/cosign/.github/workflows/release.yml@refs/tags/v3.1.3", got "keyless@projectsigstore.iam.gserviceaccount.com"
== modified file
Error: failed to verify signature: could not verify message: invalid signature when validating ASN.1 encoded signature
error during command execution: failed to verify signature: could not verify message: invalid signature when validating ASN.1 encoded signature
Для первого результата cosign проверил цепочку сертификатов до корня Sigstore (его распространяют через TUF), подпись файла, доказательство включения в Rekor и то, что время записи в журнал попадает в десятиминутное окно действия сертификата. Второй запуск показывает смысл закрепления удостоверения: подпись криптографически верна, но создана она не от имени того удостоверения, которое потребовала проверяющая сторона, поэтому подпись отклонена; третий запуск не проходит уже на самой подписи. Для артефактов, собранных в GitHub Actions, удостоверение — это файл рабочего процесса вместе с его ссылкой (ref), например рабочий процесс release.yml@5320e209… в сертификате из аттестации происхождения npm выше, а издатель — https://token.actions.githubusercontent.com.
Экосистемы пакетов встраивают эти проверки в собственные инструменты. npm проверяет и подписи своего реестра, и аттестации Sigstore для всего установленного:
cd "$(mktemp -d)" && npm init -y > /dev/null
npm install --ignore-scripts --no-fund --no-audit @noble/curves@2.3.0 @noble/hashes@2.3.0
npm audit signatures
added 2 packages in 589ms
audited 2 packages in 0s
2 packages have verified registry signatures
2 packages have verified attestations
(use --json --include-attestations to view attestation details)
gh attestation verify делает то же для аттестаций артефактов GitHub, а PyPI при загрузке аттестаций сверяет их с настройкой доверенной публикации (trusted publisher) проекта.
Компромиссы, о которых нужно знать
- Доверие переходит к поставщикам удостоверений и инфраструктуре Sigstore: злоумышленник, который контролирует аккаунт мейнтейнера на GitHub или рабочий процесс релиза, может получить верные подписи. Прозрачность делает такую атаку обнаружимой (мейнтейнеры могут отслеживать в журнале записи со своим удостоверением), но не невозможной.
- Публичные метаданные: личные адреса электронной почты, служащие удостоверениями, попадают в публичный журнал, а удостоверения рабочих процессов раскрывают имена репозиториев. Частные развёртывания Sigstore избавляют и от того и от другого.
- Доступность: для подписи нужны работающие Fulcio и Rekor. Проверка бандла работает офлайн, когда корень доверия уже закеширован.
- Эксплуатация меняется: Rekor v2, журнал на основе тайлов, находится в общем доступе с октября 2025 года, но общедоступный экземпляр по-прежнему использует по умолчанию Rekor v1, пока мейнтейнеры готовят его к постквантовой криптографии.
- Издержки GPG связаны с людьми: ключи теряются, у них незаметно истекает срок действия, или их вообще никогда не проверяют потребители, и это главная причина, по которой Python, npm и PyPI от него отказались.
Что когда выбирать
- Пакеты, которые собираются в GitHub Actions или GitLab CI для npm, PyPI, crates или контейнеров: Sigstore через доверенную публикацию и сведения о происхождении.
- Образы контейнеров: подписи и аттестации cosign, проверяемые политиками допуска (admission policies).
- Maven Central: OpenPGP обязателен; добавьте ещё и бандл Sigstore — Central проверяет такие бандлы с января 2025 года.
- Пакеты и репозитории дистрибутивов Linux: OpenPGP, как того ожидают инструменты дистрибутивов.
- Подпись в изолированной сети или офлайн: OpenPGP (или частное развёртывание Sigstore с собственным поставщиком удостоверений).
- Коммиты и теги Git: ключи SSH или OpenPGP либо gitsign для коммитов без ключа (см. SSH или GPG для подписи коммитов).
Заметки о миграции
- Подписывайте в CI, а не на ноутбуках: перенесите выпуск релизов в рабочий процесс с токеном OIDC (
id-token: writeв GitHub Actions) и подписывайте там. - На переходный период публикуйте оба варианта: выкладывайте файлы
.ascи.sigstore.jsonи укажите в документации ожидаемые удостоверение и издателя рядом с отпечатком старого ключа. - Используйте встроенные механизмы реестров, если они есть: в npm —
--provenanceи доверенную публикацию, в PyPI — доверенную публикацию (trusted publishers), в GitHub — аттестации артефактов. - Закрепляйте удостоверения точно в политиках проверки: репозиторий, файл рабочего процесса и тег или ветку, а не только организацию.
- Отслеживайте в журнале записи со своими удостоверениями и храните сертификат отзыва ключа OpenPGP, пока не прекратится поддержка последнего релиза, подписанного этим ключом.
Источники
- Документация Sigstore: проверка в cosign и проверка релизов cosign (удостоверение и издатель, использованные выше)
- Релиз cosign v3.1.3
- Sigstore: Rekor v2 в общем доступе и развитие Rekor
- Спецификация SLSA provenance v1
- PEP 740, поддержка цифровых аттестаций в индексе пакетов и PEP 761, отказ от подписей PGP для CPython
- GitHub: аттестации артефактов в общем доступе
- Sonatype: портал публикации в Maven Central (Central Publisher Portal) проверяет подписи Sigstore
- RFC 9580, OpenPGP