sigstoreのKeyless Signingでは何を検証しているのか

最近、個人的にsigstoreのKeyless Signingがアツいです。以下のブログを読みました。

これらの記事は主にコンテナイメージの署名について解説しているのですが、sigstoreはバイナリの署名にも使えます。以前から、複数メンテナ体制で秘密鍵をどう共有しよう?とか、公開鍵の安全な配布方法って無くない?と考えていたので、渡りに船です。

早速、採用に向けて色々調べていたのですが、署名した人をどう確認しているのか、改ざんされていないことをどう保証しているのかがわからなかったので、Keyless Signingでは何を検証する必要があるのかを調べました。

全然わからない状態から調べているので、間違ったことを書いてるかもしれません。何か間違いを見つけたらTwitterとかで教えて下さい。

CosignによるKeyless Signing

実際に動かしながら確認したいので、まずは署名と検証を試してみます。CosignというCLIを使います。

この記事を書いている時点の最新版であるv1.7.2では、Keyless Signingは実験的機能であり、環境変数COSIGN_EXPERIMENTAL=1を設定する必要があります。署名する対象は適当なUUIDのテキストファイルです。

$ uuidgen > artifact.txt
$ cat artifact.txt
D4A671E9-BC95-47B9-840B-D92517348960

$ COSIGN_EXPERIMENTAL=1 cosign sign-blob --output-signature artifact.txt.sig artifact.txt
Using payload from: artifact.txt
Generating ephemeral keys...
Retrieving signed certificate...
Your browser will now be opened to:
https://oauth2.sigstore.dev/auth/auth?access_type=online&client_id=sigstore&code_challenge=...

Successfully verified SCT...
using ephemeral certificate:
-----BEGIN CERTIFICATE-----
MIICEDCCAZegAwIBAgITE9tB5vLrfUDhWfcvIlAoPmURGjAKBggqhkjOPQQDAzAq
MRUwEwYDVQQKEwxzaWdzdG9yZS5kZXYxETAPBgNVBAMTCHNpZ3N0b3JlMB4XDTIy
MDQyMzEzMjEwM1oXDTIyMDQyMzEzMzEwMlowADBZMBMGByqGSM49AgEGCCqGSM49
AwEHA0IABJwVpj2x/WjymidiXnY0H0ONkFQApzd/DmxnaHVpxqQYWlfLCr4L6n9g
musqV8t4SMvIgh5us/x62RU6iNrr1YOjgcUwgcIwDgYDVR0PAQH/BAQDAgeAMBMG
A1UdJQQMMAoGCCsGAQUFBwMDMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFB2/WTE9
LwhxEvPFeXMJT6afTHl3MB8GA1UdIwQYMBaAFFjAHl+RRaVmqXrMkKGTItAqxcX6
MCIGA1UdEQEB/wQYMBaBFHdhdGFzc2Jhc3NAZ21haWwuY29tMCkGCisGAQQBg78w
AQEEG2h0dHBzOi8vYWNjb3VudHMuZ29vZ2xlLmNvbTAKBggqhkjOPQQDAwNnADBk
AjAPme8wCmrTiWFBChw7km5hMSpkgtbEP6m86iyjOQty6xdOB/tScXsJ3hcQ8f2r
zrECMAjn4c/7uByMdtXGN7TLhlw7suMhuRlbCqP/rXj3Zdwx+x+393WJPqridQfI
b6Xjcw==
-----END CERTIFICATE-----

tlog entry created with index: 2093484
Signature wrote in the file artifact.txt.sig

cosign sign-blobを実行すると、ブラウザが立ち上がり、GitHubGoogleMicrosoftアカウントのいずれかでログインが求められます。ログインに成功すると、手元にartifact.txt.sigファイルが保存されます。

検証はcosign verify-blobで行います。

$ COSIGN_EXPERIMENTAL=1 cosign verify-blob --signature artifact.txt.sig artifact.txt
tlog entry verified with uuid: 94d9cb793c8e7439f300755070fb04af35b821191f0ab3a70e0c6f3dd84dbc47 index: 2093484
Verified OK

別のファイルに対して検証するとちゃんと失敗します。

$ COSIGN_EXPERIMENTAL=1 cosign verify-blob --signature artifact.txt.sig artifact_malformed.txt
Error: verifying blob [artifact_malformed.txt]: could not find a tlog entry for provided blob
main.go:46: error during command execution: verifying blob [artifact_malformed.txt]: could not find a tlog entry for provided blob

ここまで、署名側、検証側のどちらも秘密鍵、公開鍵を意識しませんでした。なぜ正規のファイルの検証だけを成功させることができるのでしょうか?

攻撃側から考える検証

今回のartifact.txtに対する検証を攻撃側から考えてみます。

攻撃1: ファイルの差し替え

まず、artifact.txtを検証なしで受け入れる場合、どのような攻撃ができるでしょうか?

単純にartifact.txtを悪意のあるファイルに差し替えることできます。GitHubのリリースに添付されているバイナリであれば、メンテナのアクセストークンを手に入れられれば、簡単にファイルを差し替えられます。

アクセストークンを手に入れることは、残念ながらそれほど難しいことではありません。GitHub連携したサービスからの流出や、GitHub Actionsでのコマンドインジェクションなど、実例は多くあります。

これを防ぐためには、artifact.txtが正規のファイルであることを検証できる必要があります。メンテナは秘密鍵と公開鍵を生成し、秘密鍵artifact.txtを署名し、それをシグネチャファイルartifact.txt.sigとして公開します。利用者は公開鍵を使って、artifact.txt.sigを検証することで、正規のファイルであることを確認します。

この署名があれば、攻撃者はメンテナの秘密鍵を手を入れない限り、差し替えたartifact_malformed.txtに対応するシグネチャファイルを生成できないため、攻撃は失敗します。

CosignではVerifySignatureシグネチャを検証しています。

// verify the signature
if err := verifier.VerifySignature(bytes.NewReader([]byte(sig)), bytes.NewReader(blobBytes)); err != nil {
    continue
}

https://github.com/sigstore/cosign/blob/v1.7.2/cmd/cosign/cli/verify/verify_blob.go#L214-L217

攻撃2: 公開鍵の差し替え

しかし、ここには重大な問題があります。利用者はどうやって公開鍵を取得すれば良いのでしょうか。artifact.txtと同じように公開鍵を取得する場合、公開鍵だけ差し替えられていない保証はありません。攻撃者がartifact_malformed.txtを自身の秘密鍵で署名し、シグネチャファイルと公開鍵を差し替えると、攻撃は成立します。

ここからがKeyless Signingの解説です。CosignはRekorというTransparency Log(以下、Tログ)を提供するサービスから公開鍵を取得します。Tログは署名時に必ず作成されます。

tlogEntry, err := cosign.GetTlogEntry(ctx, rClient, u)
if err != nil {
    continue
}

certs, err := extractCerts(tlogEntry)
if err != nil {
    continue
}

https://github.com/sigstore/cosign/blob/v1.7.2/cmd/cosign/cli/verify/verify_blob.go#L190-L198

Tログには証明書が埋め込まれており、そこから公開鍵を取得できます。rekor-cliを使うと、実際のTログを確認できます。UUIDはartifact.txtのsha256ハッシュから検索できます。

$ rekor-cli search --artifact artifact.txt
Found matching entries (listed by UUID):
94d9cb793c8e7439f300755070fb04af35b821191f0ab3a70e0c6f3dd84dbc47

$ rekor-cli get --uuid 94d9cb793c8e7439f300755070fb04af35b821191f0ab3a70e0c6f3dd84dbc47
LogID: c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d
Index: 2093484
IntegratedTime: 2022-04-23T13:21:09Z
UUID: 94d9cb793c8e7439f300755070fb04af35b821191f0ab3a70e0c6f3dd84dbc47
Body: {
  "HashedRekordObj": {
    "data": {
      "hash": {
        "algorithm": "sha256",
        "value": "3aece84d7d32f68ac07e5d5d4fbba6e16085693e871d31a304ff55048c89e60d"
      }
    },
    "signature": {
      "content": "MEQCIE0D6m4Lew8Umdz7vUQsMZwVZpEBIc3Oz6lVKhxbsbeZAiBs2KLR7sWwUxXrIQFVHe4Lfsjtu8naaiL+39GEZLWe8w==",
      "publicKey": {
        "content": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNFRENDQVplZ0F3SUJBZ0lURTl0QjV2THJmVURoV2ZjdklsQW9QbVVSR2pBS0JnZ3Foa2pPUFFRREF6QXEKTVJVd0V3WURWUVFLRXd4emFXZHpkRzl5WlM1a1pYWXhFVEFQQmdOVkJBTVRDSE5wWjNOMGIzSmxNQjRYRFRJeQpNRFF5TXpFek1qRXdNMW9YRFRJeU1EUXlNekV6TXpFd01sb3dBREJaTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5CkF3RUhBMElBQkp3VnBqMngvV2p5bWlkaVhuWTBIME9Oa0ZRQXB6ZC9EbXhuYUhWcHhxUVlXbGZMQ3I0TDZuOWcKbXVzcVY4dDRTTXZJZ2g1dXMveDYyUlU2aU5ycjFZT2pnY1V3Z2NJd0RnWURWUjBQQVFIL0JBUURBZ2VBTUJNRwpBMVVkSlFRTU1Bb0dDQ3NHQVFVRkJ3TURNQXdHQTFVZEV3RUIvd1FDTUFBd0hRWURWUjBPQkJZRUZCMi9XVEU5Ckx3aHhFdlBGZVhNSlQ2YWZUSGwzTUI4R0ExVWRJd1FZTUJhQUZGakFIbCtSUmFWbXFYck1rS0dUSXRBcXhjWDYKTUNJR0ExVWRFUUVCL3dRWU1CYUJGSGRoZEdGemMySmhjM05BWjIxaGFXd3VZMjl0TUNrR0Npc0dBUVFCZzc4dwpBUUVFRzJoMGRIQnpPaTh2WVdOamIzVnVkSE11WjI5dloyeGxMbU52YlRBS0JnZ3Foa2pPUFFRREF3Tm5BREJrCkFqQVBtZTh3Q21yVGlXRkJDaHc3a201aE1TcGtndGJFUDZtODZpeWpPUXR5NnhkT0IvdFNjWHNKM2hjUThmMnIKenJFQ01Bam40Yy83dUJ5TWR0WEdON1RMaGx3N3N1TWh1UmxiQ3FQL3JYajNaZHd4K3grMzkzV0pQcXJpZFFmSQpiNlhqY3c9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg=="
      }
    }
  }
}

publicKeyBase64エンコードされており、デコードすると証明書が手に入ります。

$ rekor-cli get --uuid 94d9cb793c8e7439f300755070fb04af35b821191f0ab3a70e0c6f3dd84dbc47 --format json | jq -r '.Body.HashedRekordObj.signature.publicKey.content' > publicKey
$ cat publicKey | base64 --decode
-----BEGIN CERTIFICATE-----
MIICEDCCAZegAwIBAgITE9tB5vLrfUDhWfcvIlAoPmURGjAKBggqhkjOPQQDAzAq
MRUwEwYDVQQKEwxzaWdzdG9yZS5kZXYxETAPBgNVBAMTCHNpZ3N0b3JlMB4XDTIy
MDQyMzEzMjEwM1oXDTIyMDQyMzEzMzEwMlowADBZMBMGByqGSM49AgEGCCqGSM49
AwEHA0IABJwVpj2x/WjymidiXnY0H0ONkFQApzd/DmxnaHVpxqQYWlfLCr4L6n9g
musqV8t4SMvIgh5us/x62RU6iNrr1YOjgcUwgcIwDgYDVR0PAQH/BAQDAgeAMBMG
A1UdJQQMMAoGCCsGAQUFBwMDMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFB2/WTE9
LwhxEvPFeXMJT6afTHl3MB8GA1UdIwQYMBaAFFjAHl+RRaVmqXrMkKGTItAqxcX6
MCIGA1UdEQEB/wQYMBaBFHdhdGFzc2Jhc3NAZ21haWwuY29tMCkGCisGAQQBg78w
AQEEG2h0dHBzOi8vYWNjb3VudHMuZ29vZ2xlLmNvbTAKBggqhkjOPQQDAwNnADBk
AjAPme8wCmrTiWFBChw7km5hMSpkgtbEP6m86iyjOQty6xdOB/tScXsJ3hcQ8f2r
zrECMAjn4c/7uByMdtXGN7TLhlw7suMhuRlbCqP/rXj3Zdwx+x+393WJPqridQfI
b6Xjcw==
-----END CERTIFICATE-----

ここから公開鍵を抽出します。

cat publicKey | base64 --decode | openssl x509 -pubkey -noout
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEnBWmPbH9aPKaJ2JedjQfQ42QVACn
N38ObGdodWnGpBhaV8sKvgvqf2Ca6ypXy3hIy8iCHm6z/HrZFTqI2uvVgw==
-----END PUBLIC KEY-----

この公開鍵を使い、シグネチャファイルを検証します。しかし、これでは公開鍵が攻撃者によって差し替えられていないことを保証できていません。

もっと言えば、Tログの登録は誰でもできてしまうので、攻撃者は差し替える必要すらありません。上記の手順と同じように、artifact_malformed.txtcosign sign-blobすれば良いのです。

これを防ぐためには、証明書をちゃんと確認する必要があります。opensslで詳しく見てみましょう。

$ cat publicKey | base64 --decode | openssl x509 -text -noout
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            13:db:41:e6:f2:eb:7d:40:e1:59:f7:2f:22:50:28:3e:65:11:1a
    Signature Algorithm: ecdsa-with-SHA384
        Issuer: O=sigstore.dev, CN=sigstore
        Validity
            Not Before: Apr 23 13:21:03 2022 GMT
            Not After : Apr 23 13:31:02 2022 GMT
        Subject:
        Subject Public Key Info:
            Public Key Algorithm: id-ecPublicKey
                Public-Key: (256 bit)
                pub:
                    04:9c:15:a6:3d:b1:fd:68:f2:9a:27:62:5e:76:34:
                    1f:43:8d:90:54:00:a7:37:7f:0e:6c:67:68:75:69:
                    c6:a4:18:5a:57:cb:0a:be:0b:ea:7f:60:9a:eb:2a:
                    57:cb:78:48:cb:c8:82:1e:6e:b3:fc:7a:d9:15:3a:
                    88:da:eb:d5:83
                ASN1 OID: prime256v1
                NIST CURVE: P-256
        X509v3 extensions:
            X509v3 Key Usage: critical
                Digital Signature
            X509v3 Extended Key Usage:
                Code Signing
            X509v3 Basic Constraints: critical
                CA:FALSE
            X509v3 Subject Key Identifier:
                1D:BF:59:31:3D:2F:08:71:12:F3:C5:79:73:09:4F:A6:9F:4C:79:77
            X509v3 Authority Key Identifier:
                keyid:58:C0:1E:5F:91:45:A5:66:A9:7A:CC:90:A1:93:22:D0:2A:C5:C5:FA

            X509v3 Subject Alternative Name: critical
                email:watassbass@gmail.com
            1.3.6.1.4.1.57264.1.1:
                https://accounts.google.com
    Signature Algorithm: ecdsa-with-SHA384
         30:64:02:30:0f:99:ef:30:0a:6a:d3:89:61:41:0a:1c:3b:92:
         6e:61:31:2a:64:82:d6:c4:3f:a9:bc:ea:2c:a3:39:0b:72:eb:
         17:4e:07:fb:52:71:7b:09:de:17:10:f1:fd:ab:ce:b1:02:30:
         08:e7:e1:cf:fb:b8:1c:8c:76:d5:c6:37:b4:cb:86:5c:3b:b2:
         e3:21:b9:19:5b:0a:a3:ff:ad:78:f7:65:dc:31:fb:1f:b7:f7:
         75:89:3e:aa:e2:75:07:c8:6f:a5:e3:73

注目すべきは、X509v3 extensionsの欄です。ここには署名する際に利用したサービスがGoogleであること、ログインしたアカウントのメールアドレスが記録されています。これによって、署名を行った人物を特定でき、攻撃者が勝手に登録した証明書との区別ができます。

しかし、このX509v3 extensionsは改ざんされていないと言えるでしょうか?これを検証するためには、証明書チェーンを確認する必要があります。X.509証明書は発行元のFulcioというサービスによって署名されており、攻撃者はFulcioの秘密鍵を手に入れない限り、公開鍵はもちろん、X509v3 extensionsの中身を改ざんできません。

opensslを使えば、以下のように検証できます。

$ cat publicKey | base64 --decode | openssl verify -CAfile fulcio_v1.crt.pem
stdin:
error 10 at 0 depth lookup:certificate has expired
OK

証明書の有効期限は10分しか無いため、certificate has expiredエラーが発生します。しかし、後ほど説明しますが、Tログは後から改ざんできないため、証明書が有効な間にTログが登録されたことさえ確認できれば、検証時点で証明書が有効かどうかは重要ではありません。よって無視しても大丈夫です。

この証明書の検証は、cosign.ValidateAndUnpackCertで行っています。以下は証明書のメールアドレスを検証するコードです。

// Now verify the cert, then the signature.
if err := TrustedCert(cert, co.RootCerts, co.IntermediateCerts); err != nil {
    return nil, err
}
if co.CertEmail != "" {
    emailVerified := false
    for _, em := range cert.EmailAddresses {
        if co.CertEmail == em {
            emailVerified = true
            break
        }
    }
    if !emailVerified {
        return nil, errors.New("expected email not found in certificate")
    }
}

https://github.com/sigstore/cosign/blob/v1.7.2/pkg/cosign/verify.go#L153-L168

さて、勘の鋭い人はここで「Fulcioの公開鍵はどうやって安全に取得するの?」と思うかもしれません。これはThe Update Framework(以下、TUF)に則って、Google Cloud Storageからダウンロードしているようです。

co := &cosign.CheckOpts{
    RootCerts:      fulcio.GetRoots(),
    CertEmail:      certEmail,
    CertOidcIssuer: certOidcIssuer,
}

https://github.com/sigstore/cosign/blob/v1.7.2/cmd/cosign/cli/verify/verify_blob.go#L200-L204

TUFはリポジトリや署名キーの侵害から保護する、安全なソフトウェアアップデートを実現するためのフレームワークとのことです(あんまり詳しく調べていません)

「じゃあ最初からTUFでバイナリの公開鍵を配布すればいいじゃん」とも思うかもしれません。確かにそれができればsigstoreを使う理由は無いわけですが、メンテナによる秘密鍵の管理が面倒な問題は解決しません。代わりにFulcioが面倒な秘密鍵の管理を行ってくれているおかげで、我々は秘密鍵の管理から解放されているわけですね。

攻撃3: X509v3 extensionsに埋め込まれる本人情報のなりすまし

さて、Tログの公開鍵の信頼性はFulcioの証明書発行に懸かっていることがわかりました。既に発行された証明書を偽装するのは困難なので、発行時に使われる本人情報を偽装して、なりすました結果を元にFulcioに証明書を発行させられないか考えてみます。

しかし、これはOpenID Connect(以下、OIDC)によって成立しません。X509v3 extensionsに埋め込まれる本人情報はOIDCによって生成されたIDトークンの属性値であり、これを発行できるのはFulcioが許可しているOIDCプロバイダしかありません。つまり、GitHubGoogleMicrosoftアカウントで認証できない限り、なりすますことはできません。

ところで、GitHubで認証できることをメンテナの確認に使っているのであれば、前述のアクセストークンの流出と同じことが起きるのではないか?と思うかもしれません。しかし、アクセストークンでは認証はできないですし、二段階認証を有効化していれば、検証をしないよりは安全になると思います。たぶん...

攻撃4: Fulcioを侵害し、不正な証明書を発行させる

次の攻撃は、Fulcio自体を侵害し、不正に証明書を発行させる方法です。何らかの脆弱性を突き、IDトークン以外の値を使って証明書を発行できたとします。

これを防ぐ仕組みがCertificate Transparency Log(以下、CTログ)です。ざっと説明すると、認証局が発行した証明書の記録を、誰でも閲覧できるログに残すことで、証明書の不正な発行をいち早く検知できるようにする、というものです。以下のブログが参考になりました。

CTログは不正な証明書の発行を防ぐものではありませんが、CTログへの登録を不正に回避していないかどうかは、Signed Certificate Timestamp(以下、SCT)を使って確認できます。Cosignでは、ValidateAndUnpackCertの中で確認しています(この実装はv1.7.2には含まれていません。Fulcioが発行する証明書にもSCTは含まれていないようですし、FulcioはまだCTログを登録してない?)

contains, err := ctl.ContainsSCT(cert.Raw)
if err != nil {
    return nil, err
}
if co.EnforceSCT && !contains {
    return nil, errors.New("certificate does not include required embedded SCT")
}
if contains {
    // handle if chains has more than one chain - grab first and print message
    if len(chains) > 1 {
        fmt.Fprintf(os.Stderr, "**Info** Multiple valid certificate chains found. Selecting the first to verify the SCT.\n")
    }
    if err := ctl.VerifyEmbeddedSCT(context.Background(), chains[0]); err != nil {
        return nil, err
    }
}

https://github.com/sigstore/cosign/blob/e1469ba182426c9cd5c4e119bb1c949818ce385f/pkg/cosign/verify.go#L234-L249

攻撃5: RekorのTログを改ざんする

さて、これでFulcioへの攻撃も難しいことがわかりました。次に攻撃者が狙うとしたら、Rekorでしょうか。Tログを改ざんしても、証明書を偽装できないので攻撃はかなり難しいですが、仮にFulcioの秘密鍵を手に入れたとして、それで署名した不正な証明書での既存のTログの改ざんを考えます。

これを防ぐのが、Merkle TreeとSigned Entry Timestamp(以下、SET)です。Merkle TreeはTログの各エントリのハッシュを葉とする木構造であり、親ノードは子ノードのハッシュから計算され...というのを繰り返して、最終的に根を計算します。根はRekorの秘密鍵によって署名されており、Tログが改ざんされると、根のハッシュ値が変わってしまうため、改ざんを検知できます。

Cosignはこのツリーの検証をcosign.VerifyTLogEntryで行っています。

hashes := [][]byte{}
for _, h := range e.Verification.InclusionProof.Hashes {
    hb, _ := hex.DecodeString(h)
    hashes = append(hashes, hb)
}

rootHash, _ := hex.DecodeString(*e.Verification.InclusionProof.RootHash)
entryBytes, err := base64.StdEncoding.DecodeString(e.Body.(string))
if err != nil {
    return err
}
leafHash := rfc6962.DefaultHasher.HashLeaf(entryBytes)

// Verify the inclusion proof.
v := logverifier.New(rfc6962.DefaultHasher)
if err := v.VerifyInclusionProof(*e.Verification.InclusionProof.LogIndex, *e.Verification.InclusionProof.TreeSize, hashes, rootHash, leafHash); err != nil {
    return errors.Wrap(err, "verifying inclusion proof")
}

https://github.com/sigstore/cosign/blob/v1.7.2/pkg/cosign/tlog.go#L314-L331

SETはSCTと似ていますが、証明書ではなくTログに対するタイムスタンプです。RekorでもFulcioのCTログ同様に、署名のログはすべて記録されています。TUFでRekorの公開鍵を取得し、SETに対して検証することで、不正なTログを登録できないようにします。

Cosignでは同じくcosign.VerifyTLogEntryで確認しています。

var entryVerError error
for _, pubKey := range rekorPubKeys {
    entryVerError = VerifySET(payload, []byte(e.Verification.SignedEntryTimestamp), pubKey.PubKey)
    // Return once the SET is verified successfully.
    if entryVerError == nil {
        if pubKey.Status != tuf.Active {
            fmt.Fprintf(os.Stderr, "**Info** Successfully verified Rekor entry using an expired verification key\n")
        }
        return nil
    }
}
return errors.Wrap(entryVerError, "verifying signedEntryTimestamp")

https://github.com/sigstore/cosign/blob/v1.7.2/pkg/cosign/tlog.go#L359-L370

結論

CosignがKeyless Signingで検証していることをまとめます。

  1. ファイルのハッシュから対応するRekorのTログを取得
  2. Tログに埋め込まれた証明書の証明書チェーンを検証して、Fulcioから発行されていることを確認
  3. 証明書のX509v3 extensionsに埋め込まれたOIDC IDトークンの属性値を確認
  4. 証明書に埋め込まれたSCTで、FulcioがCTログに記録した証明書であることを確認
  5. 証明書の公開鍵で署名を検証
  6. RekorのTログのInclusion proofを検証し、Tログが改ざんされていないことを確認
  7. RekorのTログに埋め込まれたSETで、Rekorが正規に登録したログであることを確認
  8. 証明書が有効な期間中にRekorのTログが登録されたことを確認

検証側に求められることは膨大ですが、多くの技術が組み合わさって、特定の秘密鍵を必要としない署名ができるのは面白いです。

まだ開発段階ということで、Tログは予告なしにリセットされる可能性があったり、RekorやFulcioの運用はベストエフォートだったりと、完全にPGP署名を置き換えられるような状態では無いと思います。しかし、既にPGP署名を提供している場合には、とりあえず試してみる価値はあるのではないでしょうか。私のメンテしているTFLintでは、早速採用しました。GoReleaser + GitHub Actionsだと、数行程度で簡単に対応できるのでおすすめです。

GitHub ActionsをOIDCプロバイダとして利用する場合、利用者は証明書に埋め込まれたリポジトリ名を検証する必要があるのですが、Cosign v1.7.2ではまだこの属性の確認には対応していないようです。議論は行われているので、対応を待ちましょう(何か貢献できると良いのですが...)

参考