Перейти к содержимому
ecdsa.com

Sigstore или GPG: подпись с долгоживущим ключом или без

Подпись GPG говорит, что подписал владелец определённого ключа. Подпись Sigstore говорит, что подписали от имени определённого удостоверения (часто это рабочий процесс CI) в зафиксированный в журнале момент и ключом, который существовал считанные минуты.
Что сравниваетсяSigstoreOpenPGP

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 от GitHubgpg, 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).

Разбор бандла Sigstore, на котором основана аттестация происхождения npm
#!/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, который этот файл не подписывал, и после того, как к файлу дописан один байт.

Проверка подписи без ключа для общедоступного файла релиза в cosign v3.1.3
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
Результат (20 октября 2026 года)
== 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 для всего установленного:

Проверка подписей реестра и происхождения установленных пакетов npm
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 для подписи коммитов).

Заметки о миграции

  1. Подписывайте в CI, а не на ноутбуках: перенесите выпуск релизов в рабочий процесс с токеном OIDC (id-token: write в GitHub Actions) и подписывайте там.
  2. На переходный период публикуйте оба варианта: выкладывайте файлы .asc и .sigstore.json и укажите в документации ожидаемые удостоверение и издателя рядом с отпечатком старого ключа.
  3. Используйте встроенные механизмы реестров, если они есть: в npm — --provenance и доверенную публикацию, в PyPI — доверенную публикацию (trusted publishers), в GitHub — аттестации артефактов.
  4. Закрепляйте удостоверения точно в политиках проверки: репозиторий, файл рабочего процесса и тег или ветку, а не только организацию.
  5. Отслеживайте в журнале записи со своими удостоверениями и храните сертификат отзыва ключа OpenPGP, пока не прекратится поддержка последнего релиза, подписанного этим ключом.

Источники

Все сравнения