Cosignによる署名検証とSigstoreの全体像

Shun Kobayashi

2025.10.1

Sigstore

Sigstore は、コンテナイメージ、バイナリ、SBOMなどのソフトウェアアーティファクトに対して、安全な署名と検証を実現するOSSプロジェクトです。これを使うことで、ソフトウェアサプライチェーンのセキュリティを向上させることができます。

OpenSSFのプロジェクト群の中でも、2025年9月時点で唯一のGraduatedプロジェクトに認定されています。OpenSSF(Open Source Security Foundation) は、OSSのセキュリティ強化を目的としてLinux Foundationの傘下に設立された組織で、AWS, Apple, Google を含む118のメンバー(2025年9月時点)によって構成されています。

ソフトウェアサプライチェーンにおける代表的な攻撃ベクトルには、次のようなものがあります。

  • 類似したパッケージ名によるタイポスクワッティング
  • パッケージの保管先レジストリやサーバの乗っ取り
  • ソフトウェア公開後の改ざん

従来のGPGを用いた署名方式では、開発者が秘密鍵で署名し、利用者は公開鍵で検証することで、「その署名が正規の開発者によるものであること」を確認していました。

しかし、この方法にはいくつか弱点があります。

  • 署名者が本当にその開発者本人であるかを確認する手段がない
  • 秘密鍵の紛失や盗難に対する安全な対処が難しい
  • 鍵が漏洩した場合に、新しい鍵を配布して信頼を再構築するのが困難

これらの課題を解決するために、Sigstoreは「IDベースの署名アプローチ」を採用しています。OpenID Connect(OIDC)を活用した キーレス署名(Keyless Signing)により、開発者はGPGのような永続的な鍵の管理から解放され、安全に署名を行うことができます。

ただし、開発者が署名するだけでは、安全なサプライチェーンは実現できません。署名は「検証」されて初めて意味を持ちます。利用者は、Rekorに記録された情報やOIDCトークンを通じて「誰が署名したのか」を確認できますが、これはあらかじめその署名者(開発者)のIDを信頼していることが前提となります。そのため、各プロジェクトにおいては署名者のID(例:OIDC上のメールアドレスやGitHubアカウントなど)を明示的に公開し、READMEなどに記載して利用者が確認できるようにしておくことが重要です。一例として、CPythonのドキュメントには、署名者のIDとOIDC Issuerが記載されています。

利用状況

Sigstoreのブログによると、2022年10月にGAになって以降、2023年11月時点で、4,600万件の署名が記録され、GitHubにある22,000件のプロジェクトがSigstoreを使用して署名しています。

また、Sigstore導入への取り組みとして、特に、プログラミング言語やOSのパッケージマネージャに導入を進めています。

npm (2023/10/03のSigstoreブログ) npmパッケージのReadme下部にあるProvenanceに、パッケージがどこで作成され、誰が公開したのかが表示されます。 GitHub ActionsとGitLab CI/CDでビルドした場合に利用可能です。 詳しくはドキュメントを参照してください。

例) sigstore-jsのProvenance

CPython Python 3.11 から、GPG署名に加え、Sigstoreでも署名されるようになりました。また、次のPython 3.14からは、GPG署名が廃止され、Sigstoreの署名のみになります。 ドキュメントはこちらです。

Homebrew (2024/05/14のSigstoreブログ) β機能であり、GitHubとHomebrewで現在も開発を行っています。 GitHub Artifact Attestations(sigstoreを使用)により、GitHub Actionsでビルド時に署名ができるようになりました。これは、Actions→Attestationsから確認することができます。 それを活用することで、Homebrewの公式formulae(Homebrew/homebrew-core)にあるパッケージは全て署名されています。homebrew-coreにあるパッケージのAttestationsはこちらにあり、ghコマンドで検証できます。 brewコマンドでもtrailofbits/homebrew-brew-verifyを使うことで、brew verify で検証できますが、2025年9月時点でもアップストリームには組み込まれていません。また、環境変数に HOMEBREW_VERIFY_ATTESTATIONS=1 をセットすることで、インストール時に検証することもできます。 詳細は、ブログを見てください。また、GitHub Artifact Attestationsによる署名・検証はGitHubのドキュメントが参考になります。

例) brewコマンドを用いてsqliteの署名検証

PyPI (2024/11/14のSitstoreブログ) PyPIに登録したPythonパッケージのDownload Files→Distributionの”view details”をクリックすると確認することができ、npmと同じく、Provenanceにパッケージがどこで作成され、誰が公開したのかが表示されます。 PyPIのTrusted Publishing設定が有効で標準的なGitHub Actions(pypa/gh-action-pypi-publish)を使っている場合、自動で有効化されます。

例) sigstore-pythonのProvenance

Are we PEP 740 yet?” というサイトでPyPIで最もダウンロードされている上位360のパッケージが対応しているか確認できます。

Cosign

Sigstoreプロジェクトの一部であり、ソフトウェアの署名と検証を行うためのツールです。

Sigstore関連のツールには、Rakorに保存したログを確認するための rakor-cli も存在しますが、ソフトウェアの開発者や利用者が意識して使うのは cosign であるため、ここでは取り上げません。

cosign はGoでインストールできるほか、バイナリや各種パッケージマネージャでもインストール可能です。詳しくは、公式ドキュメントをご覧ください。

$ go install github.com/sigstore/cosign/v2/cmd/cosign@latest

ファイルの署名検証

署名

ファイルには cosign sign-blob で署名します。

$ cosign sign-blob <file> --bundle <bundle-file>

コマンドを実行すると、ブラウザが起動して Google, GitHub, Microsoft などのIdP(Identity Provider)による認証が行われます。その後、一時的に生成された秘密鍵を使ってファイルが署名され、署名はRekorに記録されます。

キーレス署名の場合には、検証のために証明書と署名を用意する必要がありますが、これらを1つのファイルにまとめて出力するbundleファイルが推奨されています。ただし、 --output-certificate および --output-signature を使用して別々のファイルに保存することも可能です。

bundleファイルは、base64SignaturecertrekorBundle の3つのフィールドを持つJSONオブジェクトです。bundleファイルのフォーマットについては、“Sigstore bundle format” ブログ が参考になります。

{
  "base64Signature": "{署名をbase64でエンコードした値}",
  "cert": "{X.509証明書}",
  "rekorBundle": {
    "SignedEntryTimestamp": "署名したタイムスタンプをbase64でエンコードした値",
    "Payload": {
      "body": "Rekorに格納されたエントリをBase64でエンコードした値",
      "integratedTime": {エントリがRekorに追加されたUNIX時刻},
      "logIndex": {Rekorログ内でのインデックス番号},
      "logID": "{RekorインスタンスのID}"
    }
  }
}

署名は、cosign-installer GitHub Action により、CI上で自動化もできます。

検証

署名の検証には、3つの情報が必要になります。

  1. 署名 署名時に生成された .sig ファイルであり、署名そのものを含みます。
  2. 証明書 署名に使われた公開鍵を含み、それが特定のIDに紐づいていることを保証する証明書です。
  3. 署名者のID 署名者を識別するための情報で、メールアドレスとOIDCプロバイダ(Issuer)を指します。

署名時にbundleファイルを作成した場合、署名と証明書は含まれています。別々のファイルで保存した場合、--cert で証明書を指定し、--signature で署名を指定する必要があります。

署名者のIDは --certificate-identity で、Issuerは --certificate-oidc-issuer で指定します。

OIDCプロバイダーのURLは次のようになっています。

$ cosign verify-blob <file> \
  --bundle <bundle-file> \
  --certificate-identity=<Email> \
  --certificate-oidc-issuer=<IdP>

使用例

test.txtを作成して、署名・検証します。

署名対象は、テキストではなく設定ファイルやバイナリでも同じように署名できます。

$ touch test.txt

# 署名(bundleファイルを生成・IdPはGitHubを使用)
$ cosign sign-blob test.txt --bundle test.txt.bundle

# 検証(bundleファイルを利用・IdPはGitHubを指定)
$ cosign verify-blob test.txt \
  --bundle test.txt.bundle \
  --certificate-identity name@example.com \
  --certificate-oidc-issuer https://github.com/login/oauth

コンテナイメージの署名検証

署名

コンテナイメージには、cosign sign で署名します。

この署名情報は、新しくファイルを作成してそこに書き込まれるのではなく、OCIアーティファクトとして、コンテナレジストリに保存されます

そのため、署名を行う前に、署名対象のコンテナイメージをレジストリにpushしておく必要があります

$ cosign sign <container-image>

検証

cosign verify で検証を行います。

署名や証明書はコンテナレジストリに一緒に格納されているため、ファイル署名の検証のように、bundleファイルもしくは、署名、証明書を指定する必要はありません。

$ cosign verify <container-image> \
  --certificate-identity=<Email> \
  --certificate-oidc-issuer=<IdP>

使用例

”Hello, World!”を出力するシンプルなバイナリを含むコンテナイメージmozsec/helloを署名し、その署名を検証します。事前にコンテナイメージをpushしておく必要があります。

$ docker build -t mozsec/hello ./
$ docker push mozsec/hello

# 署名(IdPはGitHubを使用)
$ cosign sign mozsec/hello

Docker Hubであれば、latestのダイジェスト(714afd8d6916~ )に.sigというサフィックスのついたタグとしてpushされます。

例) Docker Hub上に保存したmozsec/helloのWebUI

署名の保存先はcosign triangulate で確認できます。

$ cosign triangulate mozsec/hello
index.docker.io/mozsec/hello:sha256-714afd8d69161a880ca9695dfa9843784e0ffe550d56e3c9c66af4db04ad8bae.sig

cosign verify で検証します。

ただし、あくまでコンテナレジストリにあるコンテナイメージの署名を検証をしているだけです。pullしてきたローカルのコンテナイメージが正しいかどうか確認するには、digestを比較してください。

# 検証
$ cosign verify mozsec/hello \
--certificate-identity=<email> \
--certificate-oidc-issuer=https://github.com/login/oauth

Verification for index.docker.io/mozsec/hello:latest --
The following checks were performed on each of these signatures:
  - The cosign claims were validated
  - Existence of the claims in the transparency log was verified offline
  - The code-signing certificate was verified using trusted certificate authority certificates

[{"critical":{"identity":{"docker-reference":"index.docker.io/mozsec/hello"},"image":{"docker-manifest-digest":"…"},"type":"cosign container image signature"},"optional":{"…":"h…","Bundle":{"SignedEntryTimestamp":"…","Payload":{"body":"…","integratedTime":…,"logIndex":…,"logID":"…"}},"Issuer":"…","Subject":"…"}}]

署名や証明書が 714afd8d6916~.sig に保存されることはわかりましたが、この .sig の実体は何なのでしょうか?

crane を使うと、コンテナレジストリに保存した署名データの確認ができます。

$ crane manifest mozsec/hello:sha256-714afd8d69161a880ca9695dfa9843784e0ffe550d56e3c9c66af4db04ad8bae.sig | jq .
{
  "schemaVersion": 2,
  "mediaType": "application/vnd.oci.image.manifest.v1+json",
  "config": {
    "mediaType": "application/vnd.oci.image.config.v1+json",
    "size": 233,
    "digest": "sha256:a7385b404261255be8a221cdecab331c30919e35731be3f882a652333ff7d287"
  },
  "layers": [
    {
      "mediaType": "application/vnd.dev.cosign.simplesigning.v1+json",
      "size": 244,
      "digest": "sha256:2d0a13adb49c418f81dc48e34c2802a9e09c82b4ff597aa1e7d9fd5e1289da83",
      "annotations": {
        "dev.cosignproject.cosign/signature": "{署名値}",
        "dev.sigstore.cosign/bundle": "{Rekor transparency log entry}",
        "dev.sigstore.cosign/certificate": "{X.509 証明書}",
        "dev.sigstore.cosign/chain": "{root CAまでの証明書チェイン}"
      }
    }
  ]
}

これは OCI Image Manifest 形式で構成された JSON ドキュメントであり、署名情報はlayers[0].annotationsに格納されています。

layersだけでなくconfigフィールドも存在しますが、これは仕様で必須になっているため形式的に存在しているだけです。先ほど取得したマニフェストの .config.digest をもとにblobを取得すると、”diff_ids”に署名のdigest(.layers.digest)が含まれているだけで、他は適当な値や空になっていることが確認できます。

$ crane blob mozsec/hello@sha256:a7385b404261255be8a221cdecab331c30919e35731be3f882a652333ff7d287 | jq .
{
  "architecture": "",
  "created": "0001-01-01T00:00:00Z",
  "history": [
    {
      "created": "0001-01-01T00:00:00Z"
    }
  ],
  "os": "",
  "rootfs": {
    "type": "layers",
    "diff_ids": [
      "sha256:2d0a13adb49c418f81dc48e34c2802a9e09c82b4ff597aa1e7d9fd5e1289da83"
    ]
  },
  "config": {}
}

署名の流れ

Sigstore では、従来どおり開発者が保持する秘密鍵(GPG鍵)を使った署名も可能ですが、キーレス署名が大きな特徴であることは前述しました。キーレス署名では、従来のように長期的に秘密鍵(GPG鍵)を管理するのではなく、OIDC(OpenID Connect)による認証を利用して「本人であることの証明」を行います。

署名時には、まず Cosign のプロセス内で一時的に鍵ペア(公開鍵と秘密鍵)がメモリ上に生成されます。次に、OIDC 認証で発行されたトークンを用いて、有効期限10分の短期証明書が Fulcio により発行されます。この証明書と一時的な秘密鍵を使って署名を行います。署名が完了すると、プロセスが終了し、秘密鍵はメモリ上から破棄されるため、ディスクに保存されることがありません。そのため、従来のGPGのように秘密鍵を保持しておく必要がなく、秘密鍵の漏洩リスクを考慮する必要がなくなります。

キーレス署名と言われますが、開発者が鍵の存在を意識する必要がないだけであり、実際には鍵ペアを使用しています。

署名処理の流れは次のようになります。

検証の流れ

検証においても、公開鍵(GPG鍵)を使いません。代わりに、署名時に Fulcio が発行した証明書に含まれる署名者のメールアドレスやOIDC Issuer(IdPのURL)を検証ポリシーとして指定して、「誰の署名を信頼するか」をチェックします。

blobの場合は、—-bundle で指定されたbundleファイルに含まれるRekorのログを使用します。コンテナの場合は、コンテナレジストリにある.sigサフィックスの署名オブジェクトの .layers[0].annotations にRekorのログが含まれるため、これを使用します。

検証の流れは次のようになります。

トラストモデル

Sigstoreの安全性は、複数のコンポーネントによって構成される信頼の枠組みによって実現されています。

Trust Root

Trust Root とは、Sigstore全体の「信頼の起点(root of trust)」を表す概念であり、Fulcio、RekorといったSigstoreのコンポーネントが最終的に依拠する基盤です。「Sigstore の検証を行うときに、最初に信頼を置く対象」が Trust Root になります。

Trust Rootは、公開鍵署名を通じて設定され、複数の企業や学術機関から選出された5人のキーホルダーによって管理されています。また、TUF(The Update Framework)に基づいており、次のような特性によって高い安全性が担保されています。TUFについては、Sigstoreのブログも参考になります。

  • 鍵のローテーションおよび失効への対応
  • 攻撃耐性
  • オフラインでの検証や復旧の仕組み

Trust Rootによって、次のような検証を安全に行うことができます。

  1. Fulcioによって発行された証明書の正当性 攻撃者が独自にFulcioサーバを立てて証明書を発行したとしても、そのFulcioのRoot CA証明書はTrust Rootによって署名されていないため、信頼できない証明書として弾くことができます。
  2. Rekorに記録されたログの整合性 攻撃者が独自にRekorサーバを立ち上げてログを公開したとしても、Rekorの公開鍵はTrust Rootによって配布されていないため、検証時に不正なログとして検知することができます。

Rekor

Rekorは、全ての署名と証明書を記録するappend-onlyな透明性ログを提供します。これはGoogleが開発したログ基盤である Trillian をベースにして構築されており、一度記録されたエントリは改ざん・削除できません。

Trillianの内部では、Merkle Tree(ハッシュ木)が使われています。例えば、4つのレコードがある場合、それぞれのハッシュ値を計算して、A、B、D、F を得ます。次に、A と B を組み合わせて C、D と F を組み合わせて G を計算します。最後に、C と G を組み合わせて H を計算します。この最上位の H を Tree Head Hash(Merkle Tree) といい、ログ内の全てのレコードのスナップショットとして機能します。攻撃者がログを改ざんした場合には、Tree Head Hash の値が必ず変わり、過去のスナップショットと比較することで、改ざんを検出することができます。

さらに、Rekor(Trillian) は秘密鍵を使用して Tree Head Hash に署名し Signed Tree Head(STH) として公開します。クライアントは Trust Root から配布される Rekor の公開鍵で STH を検証することで、Tree Head Hash が正規の Rekor によって生成され、通信経路で改ざんされていないことを確認できます。

Rekor により、次のことが可能になります。

  • 開発者は、いつ誰がソフトウェアをビルド・署名したかを証明
  • 利用者は、信頼できるプロセスでビルドされているかを検証

Rekor は sigstore/rekor で公開されており、自前で運用することも可能です。ただし、基本的にはパブリックインスタンスである rekor.sigsotre.dev を使用します。このサービスは99.5%のSLOで運用されているとのことです。

Fulcio

Fulcioは、OIDC トークンを使って認証されたユーザに対して、短期間(10分間)有効な X.509 証明書を発行する認証局(CA)です。この証明書は Cosign が一時的に生成する鍵と組み合わせて署名に使用され、そのアーティファクトのダイジェストを Rekor に記録します。これにより、証明書の有効期限が切れた後でも、Rekor を参照することで、署名時点で証明書が確かに有効だったことを将来にわたって検証可能です。

Fulcio が発行する証明書は、すべて Rekor に記録されます。そのため、Rekor に登録されていない証明書は不正に発行されたものとして無効とされます。

この仕組みにより、Fulcio は CA の不正発行や IdP の侵害に対して高い耐性を備えています。利用者は Rekor に存在しない証明書を信頼しないようにすることで、CA の悪意ある行為や IdP の侵害により誤って発行された証明書を検出できるようになります。

Fulcio も sigstore/fulcio で公開されており、自身で運用することも可能です。ただし、基本的にはパブリックインスタンスである fulcio.sigstore.dev を使用します。このサービスは99.5%のSLOで運用されているとのことです。

Cosign

Cosign は、コンテナイメージやファイルに対して署名や検証を行うCLIツールです。

署名時に生成する一時的な鍵ペアはメモリ内にのみ存在し、秘密鍵はディスクに保存されません。また、検証する際には、Rekor から署名、証明書、タイムスタンプを取得することで、署名が正規のものであるかを確認できます。

これにより、秘密鍵の漏洩リスクが大幅に低減され、従来の鍵管理に起因する問題を回避できます。

まとめ

本記事では、Sigstoreによって提供されるキーレス署名の仕組みと、Cosignを用いた署名・検証の方法について紹介しました。

また、FulcioやRekorといったコンポーネントの役割やソフトウェアサプライチェーンにおけるトラストモデルについても取り上げました。これらを利用することで、従来のGPG署名に頼ることなく、OIDCベースで安全かつ効率的な署名が可能になります。

Kubernetes での具体的な使い方は、KubernetesのSigstore活用 もあわせてご覧ください。

参考

ブログ一覧へ戻る

お気軽にお問い合わせください

SREの設計・技術支援から、
SRE運用内で使用する
ツールの導入など、
SRE全般についてご支援しています。

資料請求・お問い合わせ