要点:Sigstore の「キーレス」署名では、署名者が OIDC トークン(GitHub Actions のワークフロー、Google または Microsoft のアカウント)で ID を証明します。Fulcio は一時鍵に対して、その ID を記載した有効期間約 10 分の証明書を発行します。署名は公開の透明性ログ Rekor に記録され、検証に必要なものはすべてバンドル(.sigstore.json)にまとめられます。検証では、署名が有効かどうかだけでなく、誰が署名したかを確認しなければなりません。cosign では --certificate-identity と --certificate-oidc-issuer がこれにあたり、キーレスモードの cosign はこれらを必須としています。npm の来歴と GitHub のアーティファクトアテステーションも同じ仕組みで動いており、npm audit signatures と gh attestation verify がそれらを代わりに確認します。
キーレス署名を使う理由
GPG や有効期間の長いコード署名用の鍵を使う従来のリリース署名には、慢性的な問題が 2 つあります。まず、ユーザーはどの鍵が正しいのかを知っていなければなりません。つまり、鍵の配布こそが信頼にかかわる本当の問題です。次に、鍵は何年もどこかに保管しておく必要があり、たいていは CI のシークレットとして置かれるため、そこから漏洩するおそれがあります。Sigstore は、鍵の代わりに「リポジトリ X のリリースワークフロー」や「alice@example.com」のような、誰にでもわかる ID を使います。さらに、鍵の有効期間に頼る代わりに、署名がいつ作成されたかを証明するログのエントリを使います。
Sigstore は、OpenSSF コミュニティが運営する公開サービス(「公開インスタンス(public-good)」)と、クライアントツールで構成されています。
- Fulcio は、OIDC の ID に対して有効期間の短い X.509 証明書を発行する認証局です。
- Rekor は、署名とアテステーションを記録する透明性ログです。
- TUF メタデータは、信頼の基点(Fulcio の CA、Rekor の鍵、タイムスタンプ局)をクライアントに配布します。
- cosign は、任意のファイル(blob)とコンテナイメージを扱う CLI です。sigstore-python、sigstore-js、sigstore-go、sigstore-java は、同じ作業のためのライブラリです。
署名時の処理の流れ
- クライアントが、メモリ上で一時的な鍵ペアを生成します。
- OIDC トークンを取得します。GitHub Actions ではランナーから(
id-token: write)、ノート PC ではブラウザーでのログインを通じて取得します。 - Fulcio はトークンを確認し、公開鍵を ID に結び付ける証明書を発行します。メールアドレスまたはワークフローの URI はサブジェクト代替名(Subject Alternative Name)に、発行者と 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 のエントリが証明しているからです。このエントリの Rekor の integratedTime は 2026年8月6日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は、ID を含む Fulcio のリーフ証明書です。tlogEntriesには Rekor のエントリ、つまり署名付きチェックポイントに対する包含証明と、署名付きエントリタイムスタンプが入っています。これらがあれば、検証者は Rekor に一切問い合わせる必要がありません。timestampVerificationDataには、タイムスタンプ局が発行した RFC 3161 タイムスタンプが入ります(省略可)。- 本体は、通常のファイル(blob)への署名なら
messageSignature、アテステーション(署名付きの in-toto ステートメント)ならdsseEnvelopeです。
Sigstore バンドル検証ツールは、これらの要素をすべてブラウザー内で確認し、証明書に含まれる ID を表示します。検証が失敗したときに最初に確認すべきなのが、この ID です。
検証で肝心なのは ID
cosign が自身のリリースバイナリを検証した例です(ghcr.io/sigstore/cosign/cosign:v3.1.3、2026年10月20日)。このリリースは Google のインフラストラクチャ上でビルドされているため、ID は Google のサービスアカウントで、発行者は GitHub ではなく Google です。
$ 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
3 番目のケースは意図的な設計です。誰でも自分自身の ID で Fulcio 証明書を取得し、何にでも署名できます。署名に意味が生まれるのは、その ID が想定どおりのものであることを確認したときだけです。cosign は、ID を指定しないとキーレス署名の検証を拒否します(エラーページ)。最後のケースは、改ざんされたときの出力です(エラーページ)。
想定する ID はどこで知ればよいのでしょうか。プロジェクトのドキュメントで確認するか、信頼できるバンドルから一度読み取って、検証スクリプトに固定します。--certificate-identity-regexp はタグに便利です(例:...release.yml@refs/tags/v.*)が、パターンの先頭と末尾の両方をアンカーで固定してください。.*sigstore.* のようなパターンは、誰でも作成できる ID に一致してしまいます。
GitHub Actions での署名
CI では、ワークフローが ID になります。Fulcio はワークフローファイルと ref を SAN に、詳細を拡張に記録します。sigstore@5.0.0 の npm 来歴バンドルを見ると、GitHub Actions の証明書に何が含まれているかがわかります。以下は、その 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 トークンを要求する権限が必要です。GitHub の actions/attest の README には、ファイルのビルドの来歴を生成するための最小構成として次の例が載っています。
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 のラッパーにすぎません。ジョブ内で cosign を使って直接ファイル(blob)に署名するコマンドは、cosign sign-blob --bundle app.sigstore.json app.tar.gz --yes です。cosign の --oidc-provider のヘルプには、何も指定されていない場合に試すトークンの取得元として github-actions が挙げられています。このページのためのキーレス署名は実行していません。公開インスタンスでのキーレス署名は、どれも永続的な公開ログのエントリになるからです。
ワークフローによる署名は、そのワークフロー自体と同じ程度にしか信頼できません。そのブランチにプッシュできる人や、そのワークフローを起動できる人なら誰でも、その ID で有効な署名を作成できます。リリースワークフローは、ブランチとタグの保護、必須のレビュー、環境の承認で保護し、タグからリリースする場合は検証をタグの ref(@refs/tags/...)に固定してください。
npm の来歴
サポート対象の CI でビルドされたパッケージについて、npm は 2 種類の 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
サブジェクトのダイジェストは tarball の SHA-512(849a897e...)なので、上で示したように、ダウンロードした .tgz に対して cosign で来歴バンドルを検証できます。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 CLI のリリース tarball gh_2.102.0_linux_arm64.tar.gz(SHA-256 7862c86c...)には、2026年10月20日の時点で 2 つのアテステーションがありました。cli/cli のデプロイワークフローによる SLSA 来歴と、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 を追加してワークフローファイルを正確に固定し、ビルドを GitHub ホステッドランナーだけで実行している場合は --deny-self-hosted-runners も追加してください。同じバンドルは cosign でも検証できます。SAN に https://github.com/cli/cli/.github/workflows/deployment.yml@refs/heads/trunk を指定して cosign verify-blob-attestation --type slsaprovenance1 を実行すると、Verified OK が出力されました。
2 つ目のアテステーションは性質が異なります。そのプレディケートタイプは https://in-toto.io/attestation/release/v0.2 です。証明書には URI:https://dotcom.releases.github.com と記載されており、発行元は GitHub 独自の Fulcio である O=GitHub, Inc., CN=Fulcio Intermediate l1 です。また、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年10月に一般提供(GA)が始まりました。検証側には cosign 2.6.0 または 3.0.1 以降が必要です。Rekor v2 では検索インデックスが廃止されました。
- 2026年6月、メンテナーは、公開インスタンスが「当面の間、Rekor v1 をデフォルトのログとして使い続ける」と発表しました。Rekor v2 は、オプトインしたエコシステム向けに本番運用が続きます。
- ML-DSA を使う独立したポスト量子 Sigstore は、2026年10月の発表によると「2027年半ばを目標」としています。従来のインスタンスは、「暫定的に 2029年または 2030年を予定」している停止まで運用が続けられます。どちらの場合も、形式はバンドルのままです。
検証する側の作業はほとんど変わりません。クライアントを最新の状態に保ち、バンドルを使って検証し、ログの URL ではなく ID を固定してください。
よくある間違い
- 署名は確認しても ID を確認しない:有効な Sigstore 署名が示すのは、誰かが署名したということだけです。
- 範囲が広すぎる ID パターン:アンカーのない正規表現や、SAN を確認しない発行者だけのチェックがこれにあたります。
- タグからリリースするのにブランチに固定する:
@refs/heads/mainは、誰もリリースするつもりのなかったビルドも含めて、mainからビルドされたものを何でも受け入れます。 - 署名がビルドを証明すると思い込む:blob の署名が証明するのは、誰がそのバイト列に署名したかです。来歴(SLSA、in-toto)は、それがどのように、何からビルドされたかを示します。ポリシーが必要とするほうを検証してください。
- 個人のリリースに個人のメールアドレスで署名する:そのメールアドレスが証明書に含まれ、したがって永続的な公開ログにも残ることに気付かないまま署名してしまうケースです。
- 古いバージョンの cosign が出力する分離形式の
.sigファイルと.pemファイルを配布する:.sigstore.jsonバンドルを公開してください。現在のすべての検証ツールが対応している形式です。
出典
- Sigstore のドキュメントとバンドルの形式
- cosign v3.1.3 とそのリリースバンドル
- Sigstore ブログ:Rekor v2 の GA(2025年10月)、sigstore.dev と Rekor の今後(2026年6月)、Sigstore のポスト量子暗号(2026年10月)
- npm:信頼できる公開とレジストリのアテステーションエンドポイント
- GitHub:
actions/attest、gh attestation verify、gh release verify、in-toto のリリースプレディケート
検証は、2026年10月20日に ghcr.io/sigstore/cosign/cosign:v3.1.3 と、node:24.22.0-trixie 上の npm 11.19.1 および gh 2.102.0(公式リリースの tarball)で実施しました。