本文へスキップ
ecdsa.com
← すべての記事

Sigstore によるリリース署名:cosign、バンドル、署名者の ID

Sigstore は、有効期間の長い署名鍵を、CI のワークフローまたはメールアドレスに結び付いた有効期間 10 分の証明書と、公開ログのエントリに置き換えます。署名そのものは簡単で、間違えやすいのは検証のほうです。この記事では、2026年10月20日に実際のリリースで検証した一連の流れを、最初から最後まで紹介します。

要点: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 は、同じ作業のためのライブラリです。

署名時の処理の流れ

  1. クライアントが、メモリ上で一時的な鍵ペアを生成します。
  2. OIDC トークンを取得します。GitHub Actions ではランナーから(id-token: write)、ノート PC ではブラウザーでのログインを通じて取得します。
  3. Fulcio はトークンを確認し、公開鍵を ID に結び付ける証明書を発行します。メールアドレスまたはワークフローの URI はサブジェクト代替名(Subject Alternative Name)に、発行者と CI の詳細は拡張に記録されます。
  4. クライアントはアーティファクトのダイジェストに署名し、署名と証明書を Rekor に送信します。Rekor は、包含証明と署名付きタイムスタンプを含むログエントリを返します。
  5. 一時的な秘密鍵は破棄されます。バンドルには、証明書、署名、ログの証跡が格納されます。

cosign が自身の v3.1.3 リリースに添付しているバンドル内の証明書を見ると、有効期間がいかに短いかがわかります。

cosign-linux-arm64.sigstore.json 内の証明書を openssl x509 で表示
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 のリリースバンドルを抜粋したものです。

cosign-linux-arm64.sigstore.json(v3.1.3 のリリースアセット、値は短縮)
{
  "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:想定どおりの ID
$ 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

次は失敗するケースです。どのケースでも、どのチェックが失敗したのかがメッセージから正確にわかります。

cosign verify-blob:失敗したときの出力
--- 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 拡張を小さなスクリプトでデコードしたものです。

sigstore@5.0.0 の npm 来歴証明書に含まれる Fulcio の拡張
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 に加えてこれらのクレームも確認できます。

GitHub のクレームを指定した cosign verify-blob-attestation(cosign v3.1.3)
$ 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 には、ファイルのビルドの来歴を生成するための最小構成として次の例が載っています。

GitHub Actions:リリースファイルのビルドの来歴(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 11.19.1:npm audit signatures(sigstore@5.0.0 と semver@7.7.2、依存関係を含む)
$ 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 の場合は次のとおりです。

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 2.102.0:ローカルのバンドルを使った 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
$ 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 バンドルを公開してください。現在のすべての検証ツールが対応している形式です。

出典

検証は、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)で実施しました。