cosign実行時のやりとりを覗いてみた
cosign のコマンドを実行した時に、sigstore関連のサーバにどういったリクエストが飛ぶのかが気になり調べてみました。
$ cat test-result.json {"passed": true} $ COSIGN_EXPERIMENTAL=1 cosign attest --type 'https://example.com/TestResult/v1' --predicate ./test-result.json otms61/test-custom-attest
cosignをある程度知っている前提で記事を書いているので、あまり馴染みがない場合には以下のブログなどを参考にしてみてください。
扱うリクエスト
- oauth2
GET https://oauth2.sigstore.dev/auth/.well-known/openid-configuration
GET https://oauth2.sigstore.dev/auth/auth?access_type=online&client_id=sigstore&code_challenge=AVDpMwBa9ulXDqR3M45vmV9jOd1dzAjMYGviS0KJJII&code_challenge_method=S256&nonce=2Ev62T7F7w5FJ1oDf6fdpm13LoI&redirect_uri=http%3A%2F%2Flocalhost%3A51217%2Fauth%2Fcallback&response_type=code&scope=openid+email&state=2Ev62TAtN2xv5qOnmoU6jIvrV1r
- redirect_uri:
http://localhost:51217/auth/callback
- scope:
openid+email
- redirect_uri:
GET http://localhost:51217/auth/callback?code=ek2umzn4x5ox4twmr4mu5x6y5&state=2Ev62TAtN2xv5qOnmoU6jIvrV1r
GET https://oauth2.sigstore.dev/auth/keys
- fulcio
POST https://fulcio.sigstore.dev/api/v1/signingCert
- rekor
POST https://rekor.sigstore.dev/api/v1/log/entries
docker hubとのやりとりは対象外としています。
リクエスト内に含まれている署名に関しては検証コードを書いてみたりしました。Goだとほぼコピペで終わりそうだったので、あえてpythonで書いています。
記事の中のTokenは一部変更していたり、期限も過ぎているので使用はできなくはなっているはずですが、何か問題ありそうなところがあったら教えていただけると助かります。
oauth2.sigstore.devとのやりとり
OpenIDプロバイダーの情報
まずはOpenIDプロバイダーの情報を取得します。
> GET /auth/.well-known/openid-configuration HTTP/1.1 > Host: oauth2.sigstore.dev < HTTP/2.0 200 OK < Content-Length: 1140 < Content-Type: application/json < Date: Sun, 18 Sep 2022 01:11:43 GMT < Strict-Transport-Security: max-age=15724800; includeSubDomains { "issuer": "https://oauth2.sigstore.dev/auth", "authorization_endpoint": "https://oauth2.sigstore.dev/auth/auth", "token_endpoint": "https://oauth2.sigstore.dev/auth/token", "jwks_uri": "https://oauth2.sigstore.dev/auth/keys", "userinfo_endpoint": "https://oauth2.sigstore.dev/auth/userinfo", "device_authorization_endpoint": "https://oauth2.sigstore.dev/auth/device/code", "grant_types_supported": ["authorization_code", "refresh_token", "urn:ietf:params:oauth:grant-type:device_code"], "response_types_supported": ["code"], "subject_types_supported": ["public"], "id_token_signing_alg_values_supported": ["RS256"], "code_challenge_methods_supported": ["S256", "plain"], "scopes_supported": ["openid", "email", "groups", "profile", "offline_access"], "token_endpoint_auth_methods_supported": ["client_secret_basic", "client_secret_post"], "claims_supported": ["iss", "sub", "aud", "iat", "exp", "email", "email_verified", "locale", "name", "preferred_username", "at_hash"] }
.well-known/openid-configuration
ではOpenIDプロバイダーの情報を取得しています。ここで Authorization Endpointを取得しています。
.well-known/openid-configuration
は、OpenID Connect Discovery 1.0 という仕様で定義されていて、以下のサイトの情報なども参考にしてください。
qiita.com
Authorization Endpointへの問い合わせ
ブラウザが開き以下のURLにアクセスします。
https://oauth2.sigstore.dev/auth/auth?access_type=online&client_id=sigstore&code_challenge=Sb0BAM0SKuusKmwGOUKD1jzyM4X5Q_Oba7j8V6bKvjI&code_challenge_method=S256&nonce=2EzYGdY34ebABazSvPKBSf9gKa3&redirect_uri=http://localhost:57543/auth/callback&response_type=code&scope=openid+email&state=2EzYGkmktp31kSIeTrRyPr3FFpp
このURLのパスは、.well-known/openid-configuration
の authorization_endpoint
です。
"authorization_endpoint": "https://oauth2.sigstore.dev/auth/auth",
パラメータから、code grant flowで、openid と emailのスコープを要求していることがわかります。
- response_type: code
- redirect_uri: http://localhost:51217/auth/callback
- scope: openid+email
最終的にredirect_uriに指定されたアドレスに以下のようにcodeをつけてリダイレクトされます。
http://localhost:57543/auth/callback?code=jjbbyyoo5kff6ohrzlkciet6e&state=2EzYGkmktp31kSIeTrRyPr3FFpp
ブラウザで開かれてから、localhostにリダイレクトされるまで他にもいくつかリクエストが飛んでいます。
- ブラウザで開かれるURL
https://oauth2.sigstore.dev/auth/auth?access_type=online&client_id=sigstore&code_challenge=Sb0BAM0SKuusKmwGOUKD1jzyM4X5Q_Oba7j8V6bKvjI&code_challenge_method=S256&nonce=2EzYGdY34ebABazSvPKBSf9gKa3&redirect_uri=http://localhost:57543/auth/callback&response_type=code&scope=openid+email&state=2EzYGkmktp31kSIeTrRyPr3FFpp
- Googleを選択したときにリダイレクトされるURL
https://accounts.google.com/o/oauth2/v2/auth?client_id=237800849078-hri2ndt7gdafpf34kq8crd5sik9pe3so.apps.googleusercontent.com&redirect_uri=https://oauth2.sigstore.dev/auth/callback&response_type=code&scope=openid+email&state=aabb112233vxe2wr72yb2a22o
- Googleから受け取ったcode(
4/00AAbbJrqZwRWjh0IBC19_8DIab-a2r6EHZXIYNpIcHWEGwpYCgly9NSbLcw1eK7XG0JFmg
)を渡しているhttps://oauth2.sigstore.dev/auth/callback?state=aabb112233vxe2wr72yb2a22o&code=4/00AAbbJrqZwRWjh0IBC19_8DIab-a2r6EHZXIYNpIcHWEGwpYCgly9NSbLcw1eK7XG0JFmg&scope=email+https://www.googleapis.com/auth/userinfo.email+openid&authuser=0&prompt=none
- 承認完了画面の表示
https://oauth2.sigstore.dev/auth/approval?req=aabb112233vxe2wr72yb2a22o&hmac=rZaSah1VGH47Dd8jZFdCUqCh-95J4jhk8jYTC72iVoM
- localhostへ
oauth2.sigstore.dev
のcode(jjbbyyoo5kff6ohrzlkciet6e
)を渡すリダイレクトhttp://localhost:57543/auth/callback?code=jjbbyyoo5kff6ohrzlkciet6e&state=2EzYGkmktp31kSIeTrRyPr3FFpp
code
が2度出てきて紛らわしいですが、最初の方がGoogleが発行し oauth2.sigstore.dev
に渡すcodeで、oauth2.sigstore.dev
のサーバ側でGoogleからトークンを取得するのに使っているものと思われます。最後の locahostへ伝えられるcodeは oauth2.sigstore.dev
で発行されたcodeで、oauth2.sigstore.dev
の token_endpoint
でトークンを取得するのに使われます。
Id Tokenの取得
callbackで受け取ったcodeを oauth2.sigstore.dev
に伝え、 oauth2.sigstore.dev
のId Tokenを取得しています。
> POST /auth/token HTTP/1.1 > Host: oauth2.sigstore.dev > Authorization: Basic c2lnc3RvcmU6 > Content-Length: 225 > Content-Type: application/x-www-form-urlencoded code=jjbbyyoo5kff6ohrzlkciet6e&code_verifier=2Ev62YX6tFyMfI1sesgYp6ZRX4o2Ev62XJyVdyusyd32WyA2qbqGMF&grant_type=authorization_code&nonce=2EzYGdY34ebABazSvPKBSf9gKa3&redirect_uri=http%3A%2F%2Flocalhost%3A51217%2Fauth%2Fcallback < HTTP/2.0 200 OK < Cache-Control: no-store < Content-Length: 2060 < Content-Type: application/json < Date: Sun, 18 Sep 2022 01:11:49 GMT < Pragma: no-cache < Strict-Transport-Security: max-age=15724800; includeSubDomains { "access_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6ImZhYjMwMThlZThmZTM1NjdlY2JhODMwN2RlOWI0OWM5YWYzYTE5NmIifQ.eyJpc3MiOiJodHRwczovL29hdXRoMi5zaWdzdG9yZS5kZXYvYXV0aCIsInN1YiI6IkNoVXhNREU0TnprMk1qYzFORE13TnpVM056ZzRNVGdTSDJoMGRIQnpPaVV5UmlVeVJtRmpZMjkxYm5SekxtZHZiMmRzWlM1amIyMCIsImF1ZCI6InNpZ3N0b3JlIiwiZXhwIjoxNjYzNDYzNTY4LCJpYXQiOjE2NjM0NjM1MDgsIm5vbmNlIjoiMkV2NjJUN0Y3dzVGSjFvRGY2ZmRwbTEzTG9JIiwiYXRfaGFzaCI6IlVRajBIc3RrR2dhWkZRSWNVRGFYZVEiLCJlbWFpbCI6InNhc29ha2lyYTYxMTRAZ21haWwuY29tIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsImZlZGVyYXRlZF9jbGFpbXMiOnsiY29ubmVjdG9yX2lkIjoiaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tIiwidXNlcl9pZCI6IjEwMTg3OTYyNzU0MzA3NTc3ODgxOCJ9fQ.hlqM-fgzkaEjqN1EpOLS2ui5BcrD-f3ljm6R5Us_stwP1FoLERNoh38-oM79F_FKXJLbCeeBQV2vYrianeKo7ZeRFlkBj604nUyhWxrnTyaRyEVb3t09egEPe8wjrIR15y8to2I1emrn3GOZLTmLH71hrRN4H-2yse_1QQgtdoqwFJ6iANSyoq2XiCKsGFpEdgguQYUG_o2nE1Ur7WQ5Z6yzo_NFsrcfKqA19gOJl1nKOpeudxjGwVP0Q6rePMAnFgLN04cNFE2jeDD4_apcb2eLCO7FeQOeuF-85FoXsF9gNUIkUgyTEM0dAWQFqHH6vL4dtCVQ1jS0MveU4VTS00", "token_type": "bearer", "expires_in": 59, "id_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6ImZhYjMwMThlZThmZTM1NjdlY2JhODMwN2RlOWI0OWM5YWYzYTE5NmIifQ.eyJpc3MiOiJodHRwczovL29hdXRoMi5zaWdzdG9yZS5kZXYvYXV0aCIsInN1YiI6IkNoVXhNREU0TnprMk1qYzFORE13TnpVM056ZzRNVGdTSDJoMGRIQnpPaVV5UmlVeVJtRmpZMjkxYm5SekxtZHZiMmRzWlM1amIyMCIsImF1ZCI6InNpZ3N0b3JlIiwiZXhwIjoxNjYzNDYzNTY4LCJpYXQiOjE2NjM0NjM1MDgsIm5vbmNlIjoiMkV2NjJUN0Y3dzVGSjFvRGY2ZmRwbTEzTG9JIiwiYXRfaGFzaCI6InIzWlVid3d5a2RHTVlwT0tUUEVjQ0EiLCJjX2hhc2giOiJlMVJLVXhHWlZPdnZacS1VbERQRFpBIiwiZW1haWwiOiJzYXNvYWtpcmE2MTE0QGdtYWlsLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJmZWRlcmF0ZWRfY2xhaW1zIjp7ImNvbm5lY3Rvcl9pZCI6Imh0dHBzOi8vYWNjb3VudHMuZ29vZ2xlLmNvbSIsInVzZXJfaWQiOiIxMDE4Nzk2Mjc1NDMwNzU3Nzg4MTgifX0.ir6acPSd58j9TSVEDTpc1zuIq9YSJvCb-S314uFFEtaJ01Iv8AdhRjRVjuO6EJPx75OwxqDvQOb6SgeCw9y5H0Emy75mjWPj4sCQXIuSRhLrJhJ13CaJ9g-Pdy1xAsk3OOd4U5VrSRqh6tvmRenrD6rMY5ee_m5NTFaFOpj9D133xND87o_HIxDI7EP-9rau9S_E6uv_zUAn4LEQRasXj-F9s1Yn33BuHkNlQS5Kln62qp9Rj8leAR6GW9xJNyMNN_VdQeIva7lHOCaK68d-GVPqKVjyzyL1PLJiNBIUKc4EsbNtI6phpAGj3OOGWn1UFN7MnDYLhESDrDntvoGF00" }
このURLのパスは、.well-known/openid-configuration
の token_endpoint
です。
"token_endpoint": "https://oauth2.sigstore.dev/auth/token",
Id Tokenの中身は、
Header: { "alg": "RS256", "kid": "fab3018ee8fe3567ecba8307de9b49c9af3a196b" } Claims: { "at_hash": "r3ZUbwwykdGMYpOKTPEcCA", "aud": "sigstore", "c_hash": "e1RKUxGZVOvvZq-UlDPDZA", "email": "sasoakira6114@gmail.com", "email_verified": true, "exp": 1663463568, "federated_claims": { "connector_id": "https://accounts.google.com", "user_id": "101879627543075778818" }, "iat": 1663463508, "iss": "https://oauth2.sigstore.dev/auth", "nonce": "2Ev62T7F7w5FJ1oDf6fdpm13LoI", "sub": "ChUxMDE4Nzk2Mjc1NDMwNzU3Nzg4MTgSH2h0dHBzOiUyRiUyRmFjY291bnRzLmdvb2dsZS5jb20" }
有効期限は59秒と短い値が採用されています。
"expires_in": 59
レスポンスヘッダーの時間より、Id Tokenが発行された時刻は 01:11:49
。
< Date: Sun, 18 Sep 2022 01:11:49 GMT
Id Token の clameの exp
が 1663463568
なので、失効するのは 01:12:48
となっています。
> date -u -r "1663463568" Sun Sep 18 01:12:48 UTC 2022
有効期限は59秒、発行時刻が 01:11:49
で、IdTokenの失効時刻が 01:12:48
となっており、整合性が取れていることがわかります。
Id Token検証のための鍵の取得
Id Tokenを検証するために鍵を取得します。
> GET /auth/keys HTTP/1.1 > Host: oauth2.sigstore.dev < HTTP/2.0 200 OK < Cache-Control: max-age=8930, must-revalidate < Content-Length: 1032 < Content-Type: application/json < Date: Sun, 18 Sep 2022 01:11:49 GMT < Strict-Transport-Security: max-age=15724800; includeSubDomains { "keys": [ { "use": "sig", "kty": "RSA", "kid": "fab3018ee8fe3567ecba8307de9b49c9af3a196b", "alg": "RS256", "n": "sgYrM7Fmkez4ruXBTmwNovlo0pDpCRmWuHvLBlStsXpZoiypNdUQ-9foCkA2JSDJ2ZH6YRuGZlQkHesLVVumOMITxz53-zmBuHdRtLBe4RG8hKDwEXTND0JS43-Ew9nzqpZy5xcZlXQGngGptDA_dCgNLRdiX6QTnLCyHQna4qUzum3CExyOCpyldIFBXL7XvNECWarrFAsLZzeWCRPpeMqd5jQ27DDEgzsQfd6Z3F-fyDcp64KYNGIcj64wYnKErM2p7IsfpjqURD2hhEtXcxgd7_b31GUbDP1r24XXsAgSeGNRSw5XNhbzVn80x_EyY7VHAGcFWsnYjFfLJ4gaJQ", "e": "AQAB" }, { "use": "sig", "kty": "RSA", "kid": "7f5cc041e09c5ca3df4f88eff9f75149f973250a", "alg": "RS256", "n": "li7QSTuaFhmtFU2Td12ju7dD9Q-_igOhSd5yXQxHXLpZInbcyL8R7MdiYIHOG6t2mLpD8QKwEL5qwnEjCGqx-REUARyRT6pWqHDtlGBvekabYNZIc4bCWMxkpcvDNpLvIhr-6C8LC7ocpZiZtcKuAtYWW62lzEtAhQEC2WdFeBONBwHthh-M0edRfMlxciKEH89q75AGG-4JeSWPUW6eYEoOw4NxupKGEBu4C7rSBJb1jJxDZq2vfMBHyCmlADYGaw_hbD-HN6Yny2-CXrJpv1FkKI4bea_NnWG85SEE9COQzhsiKvFJDWW2ZPoxlVif4OcVBz9K74IrKxXtfBxVuQ", "e": "AQAB" } ] }
このURLのパスは、.well-known/openid-configuration
の jwks_uri
です。
"jwks_uri": "https://oauth2.sigstore.dev/auth/keys",
Id Tokenの検証については以下のブログが参考になります。
fulcioとのやりとり
oauth2.sigstore.dev
で発行したIdTokenをもとに証明書を発行します。
> POST /api/v1/signingCert HTTP/1.1 > Host: fulcio.sigstore.dev > Authorization: Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6ImZhYjMwMThlZThmZTM1NjdlY2JhODMwN2RlOWI0OWM5YWYzYTE5NmIifQ.eyJpc3MiOiJodHRwczovL29hdXRoMi5zaWdzdG9yZS5kZXYvYXV0aCIsInN1YiI6IkNoVXhNREU0TnprMk1qYzFORE13TnpVM056ZzRNVGdTSDJoMGRIQnpPaVV5UmlVeVJtRmpZMjkxYm5SekxtZHZiMmRzWlM1amIyMCIsImF1ZCI6InNpZ3N0b3JlIiwiZXhwIjoxNjYzNDYzNTY4LCJpYXQiOjE2NjM0NjM1MDgsIm5vbmNlIjoiMkV2NjJUN0Y3dzVGSjFvRGY2ZmRwbTEzTG9JIiwiYXRfaGFzaCI6InIzWlVid3d5a2RHTVlwT0tUUEVjQ0EiLCJjX2hhc2giOiJlMVJLVXhHWlZPdnZacS1VbERQRFpBIiwiZW1haWwiOiJzYXNvYWtpcmE2MTE0QGdtYWlsLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJmZWRlcmF0ZWRfY2xhaW1zIjp7ImNvbm5lY3Rvcl9pZCI6Imh0dHBzOi8vYWNjb3VudHMuZ29vZ2xlLmNvbSIsInVzZXJfaWQiOiIxMDE4Nzk2Mjc1NDMwNzU3Nzg4MTgifX0.ir6acPSd58j9TSVEDTpc1zuIq9YSJvCb-S314uFFEtaJ01Iv8AdhRjRVjuO6EJPx75OwxqDvQOb6SgeCw9y5H0Emy75mjWPj4sCQXIuSRhLrJhJ13CaJ9g-Pdy1xAsk3OOd4U5VrSRqh6tvmRenrD6rMY5ee_m5NTFaFOpj9D133xND87o_HIxDI7EP-9rau9S_E6uv_zUAn4LEQRasXj-F9s1Yn33BuHkNlQS5Kln62qp9Rj8leAR6GW9xJNyMNN_VdQeIva7lHOCaK68d-GVPqKVjyzyL1PLJiNBIUKc4EsbNtI6phpAGj3OOGWn1UFN7MnDYLhESDrDntvoGF00 > Content-Length: 325 > Content-Type: application/json { "publicKey": { "content": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEi2pKkJdJxrDZrCuDT+JDN/63RS9Hcz1yr4YBYVYLnI4hnFqnAMvx68xbjWXW3O8jEWIsgkMGcGa+giYIDV0bFQ==", "algorithm": "ecdsa" }, "signedEmailAddress": "MEQCIEme0ouHErG7JnhFx94DtQuUGOJ38N+Uj1cVp8P/fM2MAiBv95ZfvLi1U9taWbA1p/T9FTrKZFmkqQF7XhZq3h+e2g==", "certificateSigningRequest": null } < HTTP/2.0 201 Created < Content-Type: application/pem-certificate-chain < Date: Sun, 18 Sep 2022 01:11:49 GMT < Strict-Transport-Security: max-age=15724800; includeSubDomains < Vary: Origin -----BEGIN CERTIFICATE----- MIICpDCCAimgAwIBAgIUOrq8rexC31ayBokWjaOX28PUdEYwCgYIKoZIzj0EAwMw NzEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRl cm1lZGlhdGUwHhcNMjIwOTE4MDExMTQ5WhcNMjIwOTE4MDEyMTQ5WjAAMFkwEwYH KoZIzj0CAQYIKoZIzj0DAQcDQgAEi2pKkJdJxrDZrCuDT+JDN/63RS9Hcz1yr4YB YVYLnI4hnFqnAMvx68xbjWXW3O8jEWIsgkMGcGa+giYIDV0bFaOCAUgwggFEMA4G A1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQUOIuA oH2TmkY/ULd1symjM3KMmVswHwYDVR0jBBgwFoAU39Ppz1YkEZb5qNjpKFWixi4Y ZD8wJQYDVR0RAQH/BBswGYEXc2Fzb2FraXJhNjExNEBnbWFpbC5jb20wKQYKKwYB BAGDvzABAQQbaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tMIGKBgorBgEEAdZ5 AgQCBHwEegB4AHYACGCS8ChS/2hF0dFrJ4ScRWcYrBY9wzjSbea8IgY2b3IAAAGD TibPoAAABAMARzBFAiBBYUxFpEdyvAakCjn+oeSVjXHACO5cy3wbdnG1yla+8wIh AP/M0EOBYQeM0UC/PBbgkhH2qY3VZbRPoV/ZSLDre7uVMAoGCCqGSM49BAMDA2kA MGYCMQDpGJ4wxnLlNKT1lzpbwCdUAmcftAoZohq62z7NkmTgXJqET8VdiMCTagO4 4Jkb4GICMQDz0lHSw5SYgc1JF2RP0yScTIlkQ2Io4SG8efZwtJw77vlNhAYxBMx5 RX2TKy4AeO0= -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIICGjCCAaGgAwIBAgIUALnViVfnU0brJasmRkHrn/UnfaQwCgYIKoZIzj0EAwMw KjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTAeFw0y MjA0MTMyMDA2MTVaFw0zMTEwMDUxMzU2NThaMDcxFTATBgNVBAoTDHNpZ3N0b3Jl LmRldjEeMBwGA1UEAxMVc2lnc3RvcmUtaW50ZXJtZWRpYXRlMHYwEAYHKoZIzj0C AQYFK4EEACIDYgAE8RVS/ysH+NOvuDZyPIZtilgUF9NlarYpAd9HP1vBBH1U5CV7 7LSS7s0ZiH4nE7Hv7ptS6LvvR/STk798LVgMzLlJ4HeIfF3tHSaexLcYpSASr1kS 0N/RgBJz/9jWCiXno3sweTAOBgNVHQ8BAf8EBAMCAQYwEwYDVR0lBAwwCgYIKwYB BQUHAwMwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQU39Ppz1YkEZb5qNjp KFWixi4YZD8wHwYDVR0jBBgwFoAUWMAeX5FFpWapesyQoZMi0CrFxfowCgYIKoZI zj0EAwMDZwAwZAIwPCsQK4DYiZYDPIaDi5HFKnfxXx6ASSVmERfsynYBiX2X6SJR nZU84/9DZdnFvvxmAjBOt6QpBlc4J/0DxvkTCqpclvziL6BCCPnjdlIB3Pu3BxsP mygUY7Ii2zbdCdliiow= -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIB9zCCAXygAwIBAgIUALZNAPFdxHPwjeDloDwyYChAO/4wCgYIKoZIzj0EAwMw KjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTAeFw0y MTEwMDcxMzU2NTlaFw0zMTEwMDUxMzU2NThaMCoxFTATBgNVBAoTDHNpZ3N0b3Jl LmRldjERMA8GA1UEAxMIc2lnc3RvcmUwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAT7 XeFT4rb3PQGwS4IajtLk3/OlnpgangaBclYpsYBr5i+4ynB07ceb3LP0OIOZdxex X69c5iVuyJRQ+Hz05yi+UF3uBWAlHpiS5sh0+H2GHE7SXrk1EC5m1Tr19L9gg92j YzBhMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRY wB5fkUWlZql6zJChkyLQKsXF+jAfBgNVHSMEGDAWgBRYwB5fkUWlZql6zJChkyLQ KsXF+jAKBggqhkjOPQQDAwNpADBmAjEAj1nHeXZp+13NWBNa+EDsDP8G1WWg1tCM WP/WHPqpaVo0jhsweNFZgSs0eE7wYI4qAjEA2WB9ot98sIkoF3vZYdd3/VtWB5b9 TNMea7Ix/stJ5TfcLLeABLE4BNJOsQ4vnBHJ -----END CERTIFICATE-----
リクエスト
keyless sigingの時のcosign attest
コマンド内では、コマンド実行中に秘密鍵を生成します。この秘密鍵に対応する公開鍵と、この秘密鍵で署名したメールアドレスがリクエストには含まれています。
cosign/fulcio.go at 7ba521444f9fcfdf2e1e5936c05834597674e6c9 · sigstore/cosign · GitHub
リクエストに含まれるpublicKeyは、ASN.1 DER 形式にエンコードされています。
cosign/fulcio.go at 7ba521444f9fcfdf2e1e5936c05834597674e6c9 · sigstore/cosign · GitHub
signedEmailAddress は、Id Tokenに含まれているメールアドレス( sasoakira6114@gmail.com
)を秘密鍵で署名したシグネチャをDER形式にしたものです。
import base64 import hashlib import ecdsa from ecdsa.util import sigdecode_der c = { "publicKey": { "content": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEi2pKkJdJxrDZrCuDT+JDN/63RS9Hcz1yr4YBYVYLnI4hnFqnAMvx68xbjWXW3O8jEWIsgkMGcGa+giYIDV0bFQ==", "algorithm": "ecdsa", }, "signedEmailAddress": "MEQCIEme0ouHErG7JnhFx94DtQuUGOJ38N+Uj1cVp8P/fM2MAiBv95ZfvLi1U9taWbA1p/T9FTrKZFmkqQF7XhZq3h+e2g==", "certificateSigningRequest": None, } v = ecdsa.VerifyingKey.from_der(base64.b64decode(c["publicKey"]["content"])) # verifyに失敗するとFalseがかえる print( v.verify( signature=base64.b64decode(c["signedEmailAddress"]), data=b"sasoakira6114@gmail.com", hashfunc=hashlib.sha256, sigdecode=sigdecode_der, ) ) """ $ python x.py True """
元のメールアドレスは、リクエストボディには含まず、AuthorizationヘッダーにId Tokenが含まれているためサーバサイドで検証することが可能です。
レスポンス
レスポンスの証明書に含まれる情報を見ていきます。 証明書の仕様 や、fulcioが証明書発行のリクエストを受けた時の挙動を説明したhow-certificate-issuing-works.md を知ると分かりやすいです。
how-certificate-issuing-works.md
の 2 | Authentication
は、Id Token検証のための鍵の取得
でクライアント側でもId Tokenの検証をしたのと同様のことをfulcioでも行います。
3 | Verifying the challenge
は fulcioとのやりとり
の リクエスト
の検証で確かめたことと同様のことをしています。challengeと呼んでいるものは、signedEmailAddressのことです。
以降は発行された証明書を見ながら確認していきます。
レスポンスには3つの証明書チェインで返ってきますが、3番目がfulcioのルート証明書で、2番目がfulcioの中間証明書で、利用者のリクエストをもとに発行されたのは1番目の証明書です。
fulcioの証明書チェーン
まずは、中間証明書とfulcioのルート証明書をみていきます。
中間証明書の中身
$ openssl x509 -text -in intermediate.crt Certificate: Data: Version: 3 (0x2) Serial Number: b9:d5:89:57:e7:53:46:eb:25:ab:26:46:41:eb:9f:f5:27:7d:a4 Signature Algorithm: ecdsa-with-SHA384 Issuer: O=sigstore.dev, CN=sigstore Validity Not Before: Apr 13 20:06:15 2022 GMT Not After : Oct 5 13:56:58 2031 GMT Subject: O=sigstore.dev, CN=sigstore-intermediate Subject Public Key Info: Public Key Algorithm: id-ecPublicKey Public-Key: (384 bit) pub: 04:f1:15:52:ff:2b:07:f8:d3:af:b8:36:72:3c:86: 6d:8a:58:14:17:d3:65:6a:b6:29:01:df:47:3f:5b: c1:04:7d:54:e4:25:7b:ec:b4:92:ee:cd:19:88:7e: 27:13:b1:ef:ee:9b:52:e8:bb:ef:47:f4:93:93:bf: 7c:2d:58:0c:cc:b9:49:e0:77:88:7c:5d:ed:1d:26: 9e:c4:b7:18:a5:20:12:af:59:12:d0:df:d1:80:12: 73:ff:d8:d6:0a:25:e7 ASN1 OID: secp384r1 NIST CURVE: P-384 X509v3 extensions: X509v3 Key Usage: critical Certificate Sign, CRL Sign X509v3 Extended Key Usage: Code Signing X509v3 Basic Constraints: critical CA:TRUE, pathlen:0 X509v3 Subject Key Identifier: DF:D3:E9:CF:56:24:11:96:F9:A8:D8:E9:28:55:A2:C6:2E:18:64:3F 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 Signature Algorithm: ecdsa-with-SHA384 30:64:02:30:3c:2b:10:2b:80:d8:89:96:03:3c:86:83:8b:91: c5:2a:77:f1:5f:1e:80:49:25:66:11:17:ec:ca:76:01:89:7d: 97:e9:22:51:9d:95:3c:e3:ff:43:65:d9:c5:be:fc:66:02:30: 4e:b7:a4:29:06:57:38:27:fd:03:c6:f9:13:0a:aa:5c:96:fc: e2:2f:a0:42:08:f9:e3:76:52:01:dc:fb:b7:07:1b:0f:9b:28: 14:63:b2:22:db:36:dd:09:d9:62:8a:8c
ルート証明書の中身
openssl x509 -text -in root.crt Certificate: Data: Version: 3 (0x2) Serial Number: b6:4d:00:f1:5d:c4:73:f0:8d:e0:e5:a0:3c:32:60:28:40:3b:fe Signature Algorithm: ecdsa-with-SHA384 Issuer: O=sigstore.dev, CN=sigstore Validity Not Before: Oct 7 13:56:59 2021 GMT Not After : Oct 5 13:56:58 2031 GMT Subject: O=sigstore.dev, CN=sigstore Subject Public Key Info: Public Key Algorithm: id-ecPublicKey Public-Key: (384 bit) pub: 04:fb:5d:e1:53:e2:b6:f7:3d:01:b0:4b:82:1a:8e: d2:e4:df:f3:a5:9e:98:1a:9e:06:81:72:56:29:b1: 80:6b:e6:2f:b8:ca:70:74:ed:c7:9b:dc:b3:f4:38: 83:99:77:17:b1:5f:af:5c:e6:25:6e:c8:94:50:f8: 7c:f4:e7:28:be:50:5d:ee:05:60:25:1e:98:92:e6: c8:74:f8:7d:86:1c:4e:d2:5e:b9:35:10:2e:66:d5: 3a:f5:f4:bf:60:83:dd ASN1 OID: secp384r1 NIST CURVE: P-384 X509v3 extensions: X509v3 Key Usage: critical Certificate Sign, CRL Sign X509v3 Basic Constraints: critical CA:TRUE X509v3 Subject Key Identifier: 58:C0:1E:5F:91:45:A5:66:A9:7A:CC:90:A1:93:22:D0:2A:C5:C5:FA 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 Signature Algorithm: ecdsa-with-SHA384 30:66:02:31:00:8f:59:c7:79:76:69:fb:5d:cd:58:13:5a:f8: 40:ec:0c:ff:06:d5:65:a0:d6:d0:8c:58:ff:d6:1c:fa:a9:69: 5a:34:8e:1b:30:78:d1:59:81:2b:34:78:4e:f0:60:8e:2a:02: 31:00:d9:60:7d:a2:df:7c:b0:89:28:17:7b:d9:61:d7:77:fd: 5b:56:07:96:fd:4c:d3:1e:6b:b2:31:fe:cb:49:e5:37:dc:2c: b7:80:04:b1:38:04:d2:4e:b1:0e:2f:9c:11:c9
証明書チェーンになっていることも確認しておきます。
$ openssl verify -verbose -CAfile root.crt.pem intermediate.crt.pem intermediate.crt: OK $ cat intermediate.crt.pem root.crt.pem > p.crt.pem $ openssl verify -verbose -CAfile p.crt.pem user.crt.pem user.crt: error 10 at 0 depth lookup:certificate has expired OK
証明書の有効期限が10分なので、検証した時には期限切れになってしまっていますが、証明書のチェーンはできていることがわかります。
TUFによる証明書の管理
このfulcioのルート証明書は sigstore/sigstore
という、sigstoreのライブラリがまとまっているレポジトリ内にも置かれています。一方で、中間証明書はこのレポジトリには置かれていないようでした。
コードを見るとTUF(The Update Framework) という仕組みを使って証明書を管理し、アップデートの対応をおこなっているようです。
DefaultRemoteRootにアクセスするとファイルのリストを閲覧可能です。
const ( // DefaultRemoteRoot is the default remote TUF root location. DefaultRemoteRoot = "https://sigstore-tuf-root.storage.googleapis.com" )
sigstore/client.go at 181eb26ad2d04c11a387c35efdecef307c941541 · sigstore/sigstore · GitHub
このファイルのリスト内に、先ほど確認した証明書と同様のルート証明書( targets/fulcio_v1.crt.pem
)と週刊証明書( targets/fulcio_intermediate_v1.crt.pem
)というファイルが存在します。また、後ほど出てくるSCTの署名検証に使う公開鍵( targets/ctfe.pub
)も含まれています。
それぞれ、RootのURLにパスを追加するとファイルをダウロードすることが可能です。中身も先ほどと同様であることがわかります。
> curl https://sigstore-tuf-root.storage.googleapis.com/targets/fulcio_v1.crt.pem -----BEGIN CERTIFICATE----- MIIB9zCCAXygAwIBAgIUALZNAPFdxHPwjeDloDwyYChAO/4wCgYIKoZIzj0EAwMw KjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTAeFw0y MTEwMDcxMzU2NTlaFw0zMTEwMDUxMzU2NThaMCoxFTATBgNVBAoTDHNpZ3N0b3Jl LmRldjERMA8GA1UEAxMIc2lnc3RvcmUwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAT7 XeFT4rb3PQGwS4IajtLk3/OlnpgangaBclYpsYBr5i+4ynB07ceb3LP0OIOZdxex X69c5iVuyJRQ+Hz05yi+UF3uBWAlHpiS5sh0+H2GHE7SXrk1EC5m1Tr19L9gg92j YzBhMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRY wB5fkUWlZql6zJChkyLQKsXF+jAfBgNVHSMEGDAWgBRYwB5fkUWlZql6zJChkyLQ KsXF+jAKBggqhkjOPQQDAwNpADBmAjEAj1nHeXZp+13NWBNa+EDsDP8G1WWg1tCM WP/WHPqpaVo0jhsweNFZgSs0eE7wYI4qAjEA2WB9ot98sIkoF3vZYdd3/VtWB5b9 TNMea7Ix/stJ5TfcLLeABLE4BNJOsQ4vnBHJ -----END CERTIFICATE----- > curl https://sigstore-tuf-root.storage.googleapis.com/targets/fulcio_intermediate_v1.crt.pem -----BEGIN CERTIFICATE----- MIICGjCCAaGgAwIBAgIUALnViVfnU0brJasmRkHrn/UnfaQwCgYIKoZIzj0EAwMw KjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTAeFw0y MjA0MTMyMDA2MTVaFw0zMTEwMDUxMzU2NThaMDcxFTATBgNVBAoTDHNpZ3N0b3Jl LmRldjEeMBwGA1UEAxMVc2lnc3RvcmUtaW50ZXJtZWRpYXRlMHYwEAYHKoZIzj0C AQYFK4EEACIDYgAE8RVS/ysH+NOvuDZyPIZtilgUF9NlarYpAd9HP1vBBH1U5CV7 7LSS7s0ZiH4nE7Hv7ptS6LvvR/STk798LVgMzLlJ4HeIfF3tHSaexLcYpSASr1kS 0N/RgBJz/9jWCiXno3sweTAOBgNVHQ8BAf8EBAMCAQYwEwYDVR0lBAwwCgYIKwYB BQUHAwMwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQU39Ppz1YkEZb5qNjp KFWixi4YZD8wHwYDVR0jBBgwFoAUWMAeX5FFpWapesyQoZMi0CrFxfowCgYIKoZI zj0EAwMDZwAwZAIwPCsQK4DYiZYDPIaDi5HFKnfxXx6ASSVmERfsynYBiX2X6SJR nZU84/9DZdnFvvxmAjBOt6QpBlc4J/0DxvkTCqpclvziL6BCCPnjdlIB3Pu3BxsP mygUY7Ii2zbdCdliiow= -----END CERTIFICATE-----
TUFについて詳しいことは分かっていませんが、コード実行時にremoteのファイルリストを確認し、証明書を取得していると思われます。
ユーザの証明書
Certificate: Data: Version: 3 (0x2) Serial Number: 3a:ba:bc:ad:ec:42:df:56:b2:06:89:16:8d:a3:97:db:c3:d4:74:46 Signature Algorithm: ecdsa-with-SHA384 Issuer: O = sigstore.dev, CN = sigstore-intermediate Validity Not Before: Sep 18 01:11:49 2022 GMT Not After : Sep 18 01:21:49 2022 GMT Subject: Subject Public Key Info: Public Key Algorithm: id-ecPublicKey Public-Key: (256 bit) pub: 04:8b:6a:4a:90:97:49:c6:b0:d9:ac:2b:83:4f:e2: 43:37:fe:b7:45:2f:47:73:3d:72:af:86:01:61:56: 0b:9c:8e:21:9c:5a:a7:00:cb:f1:eb:cc:5b:8d:65: d6:dc:ef:23:11:62:2c:82:43:06:70:66:be:82:26: 08:0d:5d:1b:15 ASN1 OID: prime256v1 NIST CURVE: P-256 X509v3 extensions: X509v3 Key Usage: critical Digital Signature X509v3 Extended Key Usage: Code Signing X509v3 Subject Key Identifier: 38:8B:80:A0:7D:93:9A:46:3F:50:B7:75:B3:29:A3:33:72:8C:99:5B X509v3 Authority Key Identifier: DF:D3:E9:CF:56:24:11:96:F9:A8:D8:E9:28:55:A2:C6:2E:18:64:3F X509v3 Subject Alternative Name: critical email:sasoakira6114@gmail.com 1.3.6.1.4.1.57264.1.1: https://accounts.google.com CT Precertificate SCTs: Signed Certificate Timestamp: Version : v1 (0x0) Log ID : 08:60:92:F0:28:52:FF:68:45:D1:D1:6B:27:84:9C:45: 67:18:AC:16:3D:C3:38:D2:6D:E6:BC:22:06:36:6F:72 Timestamp : Sep 18 01:11:49.920 2022 GMT Extensions: none Signature : ecdsa-with-SHA256 30:45:02:20:41:61:4C:45:A4:47:72:BC:06:A4:0A:39: FE:A1:E4:95:8D:71:C0:08:EE:5C:CB:7C:1B:76:71:B5: CA:56:BE:F3:02:21:00:FF:CC:D0:43:81:61:07:8C:D1: 40:BF:3C:16:E0:92:11:F6:A9:8D:D5:65:B4:4F:A1:5F: D9:48:B0:EB:7B:BB:95 Signature Algorithm: ecdsa-with-SHA384 Signature Value: 30:66:02:31:00:e9:18:9e:30:c6:72:e5:34:a4:f5:97:3a:5b: c0:27:54:02:67:1f:b4:0a:19:a2:1a:ba:db:3e:cd:92:64:e0: 5c:9a:84:4f:c5:5d:88:c0:93:6a:03:b8:e0:99:1b:e0:62:02: 31:00:f3:d2:51:d2:c3:94:98:81:cd:49:17:64:4f:d3:24:9c: 4c:89:64:43:62:28:e1:21:bc:79:f6:70:b4:9c:3b:ee:f9:4d: 84:06:31:04:cc:79:45:7d:93:2b:2e:00:78:ed
この証明書はリクエストで送った公開鍵に対する証明書といくつかの拡張フィールドに値が含まれています。 how-certificate-issuing-works.md
の 4 | Constructing a certificate
の画像からもわかるかと思います。
Validity Not Before: Sep 18 01:11:49 2022 GMT Not After : Sep 18 01:21:49 2022 GMT
この証明書の有効期限は10分間となっています。
この証明書に含まれている公開鍵を使って、先ほどのsignedEmailAddressできることを以下のようなコードで検証することができます。
import base64 from cryptography.x509 import load_pem_x509_certificate from cryptography.hazmat.primitives.asymmetric import ec from cryptography.hazmat.primitives import hashes cert = """-----BEGIN CERTIFICATE----- MIICpDCCAimgAwIBAgIUOrq8rexC31ayBokWjaOX28PUdEYwCgYIKoZIzj0EAwMw NzEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRl cm1lZGlhdGUwHhcNMjIwOTE4MDExMTQ5WhcNMjIwOTE4MDEyMTQ5WjAAMFkwEwYH KoZIzj0CAQYIKoZIzj0DAQcDQgAEi2pKkJdJxrDZrCuDT+JDN/63RS9Hcz1yr4YB YVYLnI4hnFqnAMvx68xbjWXW3O8jEWIsgkMGcGa+giYIDV0bFaOCAUgwggFEMA4G A1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQUOIuA oH2TmkY/ULd1symjM3KMmVswHwYDVR0jBBgwFoAU39Ppz1YkEZb5qNjpKFWixi4Y ZD8wJQYDVR0RAQH/BBswGYEXc2Fzb2FraXJhNjExNEBnbWFpbC5jb20wKQYKKwYB BAGDvzABAQQbaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tMIGKBgorBgEEAdZ5 AgQCBHwEegB4AHYACGCS8ChS/2hF0dFrJ4ScRWcYrBY9wzjSbea8IgY2b3IAAAGD TibPoAAABAMARzBFAiBBYUxFpEdyvAakCjn+oeSVjXHACO5cy3wbdnG1yla+8wIh AP/M0EOBYQeM0UC/PBbgkhH2qY3VZbRPoV/ZSLDre7uVMAoGCCqGSM49BAMDA2kA MGYCMQDpGJ4wxnLlNKT1lzpbwCdUAmcftAoZohq62z7NkmTgXJqET8VdiMCTagO4 4Jkb4GICMQDz0lHSw5SYgc1JF2RP0yScTIlkQ2Io4SG8efZwtJw77vlNhAYxBMx5 RX2TKy4AeO0= -----END CERTIFICATE-----""" c = { "signedEmailAddress": "MEQCIEme0ouHErG7JnhFx94DtQuUGOJ38N+Uj1cVp8P/fM2MAiBv95ZfvLi1U9taWbA1p/T9FTrKZFmkqQF7XhZq3h+e2g==", "certificateSigningRequest": None, } z = load_pem_x509_certificate(cert.encode("utf-8")) # verifyに失敗すると例外があがる print( z.public_key().verify( signature=base64.b64decode(c["signedEmailAddress"]), data=b"sasoakira6114@gmail.com", signature_algorithm=ec.ECDSA(hashes.SHA256()), ) ) """ $ python x.py None """
X509v3 extensionsについて
X509v3 Subject Alternative Name: critical email:sasoakira6114@gmail.com
Subject Alternative Name にはメールアドレスが含まれています。
/docs/man1.0.2/man5/x509v3_config.html
1.3.6.1.4.1.57264.1.1: https://accounts.google.com
Object Identifier(OID)が 1.3.6.1.4.1.57264
は、sigstoreによって定義されているフィールドのようです。
fulcio/oid-info.md at main · sigstore/fulcio · GitHub
1.3.6.1.4.1.57264.1.1
は、 Issuerの情報らしく、Googleアカウントと紐づけた場合には https://accounts.google.com
になりました。
また、Github アカウントと紐づけた場合には、 https://github.com/login/oauth
となりました。
1.3.6.1.4.1.57264
には、他にもGithub ActionのWorkflowで作られた場合にはworkflowの情報が含まれるようです。
X509v3 extensionsのSCTについて
CT Precertificate SCTs: Signed Certificate Timestamp: Version : v1 (0x0) Log ID : 08:60:92:F0:28:52:FF:68:45:D1:D1:6B:27:84:9C:45: 67:18:AC:16:3D:C3:38:D2:6D:E6:BC:22:06:36:6F:72 Timestamp : Sep 18 01:11:49.920 2022 GMT Extensions: none Signature : ecdsa-with-SHA256 30:45:02:20:41:61:4C:45:A4:47:72:BC:06:A4:0A:39: FE:A1:E4:95:8D:71:C0:08:EE:5C:CB:7C:1B:76:71:B5: CA:56:BE:F3:02:21:00:FF:CC:D0:43:81:61:07:8C:D1: 40:BF:3C:16:E0:92:11:F6:A9:8D:D5:65:B4:4F:A1:5F: D9:48:B0:EB:7B:BB:95
CT Precertificate SCTsのOIDは 1.3.6.1.4.1.11129.2.4.2
で、説明は以下となっています。
One or more RFC 6962 Signed Certificate Timestamps
RFC 6962 とはCertificate Transparencyに関するものです。 6 | Certificate Transparency log inclusion
にも Certificate Transparency に証明書発行のログをアップロードして、その情報を埋め込んだ証明書を作っていることが分かります。
SCTの仕組みについては、Googleのサイト や、以下のような資料が詳しいです。 https://www.jnsa.org/seminar/pki-day/2016/data/1-2_oosumi.pdf
Log IDは、署名に使われた公開鍵のDER形式のsha256の値です。公開鍵はTUFのファイルリストから取得可能です。 計算方法は以下のブログが参考になります。
Log ID : 08:60:92:F0:28:52:FF:68:45:D1:D1:6B:27:84:9C:45:67:18:AC:16:3D:C3:38:D2:6D:E6:BC:22:06:36:6F:72
LogIDを公開鍵から求めてみます。
$ curl -sO https://sigstore-tuf-root.storage.googleapis.com/targets/ctfe.pub $ cat ctfe.pub -----BEGIN PUBLIC KEY----- MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEbfwR+RJudXscgRBRpKX1XFDy3Pyu dDxz/SfnRi1fT8ekpfBd2O1uoz7jr3Z8nKzxA69EUQ+eFCFI3zeubPWU7w== -----END PUBLIC KEY-----⏎ $ openssl ec -outform der -pubin -in ctfe.pub | shasum -a 256 read EC key writing EC key 086092f02852ff6845d1d16b27849c456718ac163dc338d26de6bc2206366f72 -
フォーマットの違いはありますが、LogIDが一致することが確認できました。
SCTを検証しているコードはこの関数。
cosign/verify.go at 31e665415f2da47356e4657e751443fcf5f394ed · sigstore/cosign · GitHub
rekorとのやりとり
rekorに、attestationと証明書を登録します。
> POST /api/v1/log/entries HTTP/1.1 > Host: rekor.sigstore.dev > Accept: application/json > Content-Length: 2080 > Content-Type: application/json { "apiVersion": "0.0.1", "spec": { "content": { "envelope": "{\"payloadType\":\"application/vnd.in-toto+json\",\"payload\":\"eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInByZWRpY2F0ZVR5cGUiOiJodHRwczovL2V4YW1wbGUuY29tL1Rlc3RSZXN1bHQvdjEiLCJzdWJqZWN0IjpbeyJuYW1lIjoiaW5kZXguZG9ja2VyLmlvL290bXM2MS90ZXN0LWN1c3RvbS1hdHRlc3QiLCJkaWdlc3QiOnsic2hhMjU2IjoiOTIyNTE0NTgwODhjNjM4MDYxY2RhOGZkOGI0MDNiNzZkNjYxYTRkYzZiN2VlNzFiNmFmZmNmMTg3MjU1N2IyYiJ9fV0sInByZWRpY2F0ZSI6eyJwYXNzZWQiOnRydWV9fQ==\",\"signatures\":[{\"keyid\":\"\",\"sig\":\"MEUCIAs21tx59kbSkqA9kRE/b3De51SN62cbJSCWG6x9PDO7AiEAquGbnjchwI4D0P4m4njMnH2AsnR/zpzosRogQugDkJ8=\"}]}", "hash": { "algorithm": "sha256", "value": "505eaf34d3fd4b40b3d60734b1f14d1792d4dc91b0b2677993fd37b8d493a466" } }, "publicKey": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNwRENDQWltZ0F3SUJBZ0lVT3JxOHJleEMzMWF5Qm9rV2phT1gyOFBVZEVZd0NnWUlLb1pJemowRUF3TXcKTnpFVk1CTUdBMVVFQ2hNTWMybG5jM1J2Y21VdVpHVjJNUjR3SEFZRFZRUURFeFZ6YVdkemRHOXlaUzFwYm5SbApjbTFsWkdsaGRHVXdIaGNOTWpJd09URTRNREV4TVRRNVdoY05Nakl3T1RFNE1ERXlNVFE1V2pBQU1Ga3dFd1lICktvWkl6ajBDQVFZSUtvWkl6ajBEQVFjRFFnQUVpMnBLa0pkSnhyRFpyQ3VEVCtKRE4vNjNSUzlIY3oxeXI0WUIKWVZZTG5JNGhuRnFuQU12eDY4eGJqV1hXM084akVXSXNna01HY0dhK2dpWUlEVjBiRmFPQ0FVZ3dnZ0ZFTUE0RwpBMVVkRHdFQi93UUVBd0lIZ0RBVEJnTlZIU1VFRERBS0JnZ3JCZ0VGQlFjREF6QWRCZ05WSFE0RUZnUVVPSXVBCm9IMlRta1kvVUxkMXN5bWpNM0tNbVZzd0h3WURWUjBqQkJnd0ZvQVUzOVBwejFZa0VaYjVxTmpwS0ZXaXhpNFkKWkQ4d0pRWURWUjBSQVFIL0JCc3dHWUVYYzJGemIyRnJhWEpoTmpFeE5FQm5iV0ZwYkM1amIyMHdLUVlLS3dZQgpCQUdEdnpBQkFRUWJhSFIwY0hNNkx5OWhZMk52ZFc1MGN5NW5iMjluYkdVdVkyOXRNSUdLQmdvckJnRUVBZFo1CkFnUUNCSHdFZWdCNEFIWUFDR0NTOENoUy8yaEYwZEZySjRTY1JXY1lyQlk5d3pqU2JlYThJZ1kyYjNJQUFBR0QKVGliUG9BQUFCQU1BUnpCRkFpQkJZVXhGcEVkeXZBYWtDam4rb2VTVmpYSEFDTzVjeTN3YmRuRzF5bGErOHdJaApBUC9NMEVPQllRZU0wVUMvUEJiZ2toSDJxWTNWWmJSUG9WL1pTTERyZTd1Vk1Bb0dDQ3FHU000OUJBTURBMmtBCk1HWUNNUURwR0o0d3huTGxOS1QxbHpwYndDZFVBbWNmdEFvWm9ocTYyejdOa21UZ1hKcUVUOFZkaU1DVGFnTzQKNEprYjRHSUNNUUR6MGxIU3c1U1lnYzFKRjJSUDB5U2NUSWxrUTJJbzRTRzhlZlp3dEp3Nzd2bE5oQVl4Qk14NQpSWDJUS3k0QWVPMD0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=" }, "kind": "intoto" } < HTTP/2.0 201 Created < Content-Type: application/json < Date: Sun, 18 Sep 2022 01:11:53 GMT < Location: /api/v1/log/entries/4c43de72686ceb257f6641ca1d62069f7f891822bca772d166dd88941e31c5f1 < Strict-Transport-Security: max-age=15724800; includeSubDomains < Vary: Origin { "4c43de72686ceb257f6641ca1d62069f7f891822bca772d166dd88941e31c5f1": { "body": "eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiaW50b3RvIiwic3BlYyI6eyJjb250ZW50Ijp7Imhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiI1MDVlYWYzNGQzZmQ0YjQwYjNkNjA3MzRiMWYxNGQxNzkyZDRkYzkxYjBiMjY3Nzk5M2ZkMzdiOGQ0OTNhNDY2In0sInBheWxvYWRIYXNoIjp7ImFsZ29yaXRobSI6InNoYTI1NiIsInZhbHVlIjoiZDRjZjI2YWUwZmQ3OWRhMTU2ZjAzYzgwNjY2ZTFjZWFmZTljNjQ4ZWU2MGQwYjM2ODBlNGU4ZTQzZDNiZGNiMCJ9fSwicHVibGljS2V5IjoiTFMwdExTMUNSVWRKVGlCRFJWSlVTVVpKUTBGVVJTMHRMUzB0Q2sxSlNVTndSRU5EUVdsdFowRjNTVUpCWjBsVlQzSnhPSEpsZUVNek1XRjVRbTlyVjJwaFQxZ3lPRkJWWkVWWmQwTm5XVWxMYjFwSmVtb3dSVUYzVFhjS1RucEZWazFDVFVkQk1WVkZRMmhOVFdNeWJHNWpNMUoyWTIxVmRWcEhWakpOVWpSM1NFRlpSRlpSVVVSRmVGWjZZVmRrZW1SSE9YbGFVekZ3WW01U2JBcGpiVEZzV2tkc2FHUkhWWGRJYUdOT1RXcEpkMDlVUlRSTlJFVjRUVlJSTlZkb1kwNU5ha2wzVDFSRk5FMUVSWGxOVkZFMVYycEJRVTFHYTNkRmQxbElDa3R2V2tsNmFqQkRRVkZaU1V0dldrbDZhakJFUVZGalJGRm5RVVZwTW5CTGEwcGtTbmh5UkZweVEzVkVWQ3RLUkU0dk5qTlNVemxJWTNveGVYSTBXVUlLV1ZaWlRHNUpOR2h1Um5GdVFVMTJlRFk0ZUdKcVYxaFhNMDg0YWtWWFNYTm5hMDFIWTBkaEsyZHBXVWxFVmpCaVJtRlBRMEZWWjNkblowWkZUVUUwUndwQk1WVmtSSGRGUWk5M1VVVkJkMGxJWjBSQlZFSm5UbFpJVTFWRlJFUkJTMEpuWjNKQ1owVkdRbEZqUkVGNlFXUkNaMDVXU0ZFMFJVWm5VVlZQU1hWQkNtOUlNbFJ0YTFrdlZVeGtNWE41YldwTk0wdE5iVlp6ZDBoM1dVUldVakJxUWtKbmQwWnZRVlV6T1ZCd2VqRlphMFZhWWpWeFRtcHdTMFpYYVhocE5Ga0tXa1E0ZDBwUldVUldVakJTUVZGSUwwSkNjM2RIV1VWWVl6SkdlbUl5Um5KaFdFcG9UbXBGZUU1RlFtNWlWMFp3WWtNMWFtSXlNSGRMVVZsTFMzZFpRZ3BDUVVkRWRucEJRa0ZSVVdKaFNGSXdZMGhOTmt4NU9XaFpNazUyWkZjMU1HTjVOVzVpTWpsdVlrZFZkVmt5T1hSTlNVZExRbWR2Y2tKblJVVkJaRm8xQ2tGblVVTkNTSGRGWldkQ05FRklXVUZEUjBOVE9FTm9VeTh5YUVZd1pFWnlTalJUWTFKWFkxbHlRbGs1ZDNwcVUySmxZVGhKWjFreVlqTkpRVUZCUjBRS1ZHbGlVRzlCUVVGQ1FVMUJVbnBDUmtGcFFrSlpWWGhHY0VWa2VYWkJZV3REYW00cmIyVlRWbXBZU0VGRFR6VmplVE4zWW1SdVJ6RjViR0VyT0hkSmFBcEJVQzlOTUVWUFFsbFJaVTB3VlVNdlVFSmlaMnRvU0RKeFdUTldXbUpTVUc5V0wxcFRURVJ5WlRkMVZrMUJiMGREUTNGSFUwMDBPVUpCVFVSQk1tdEJDazFIV1VOTlVVUndSMG8wZDNodVRHeE9TMVF4Ykhwd1luZERaRlZCYldObWRFRnZXbTlvY1RZeWVqZE9hMjFVWjFoS2NVVlVPRlprYVUxRFZHRm5UelFLTkVwcllqUkhTVU5OVVVSNk1HeElVM2MxVTFsbll6RktSakpTVURCNVUyTlVTV3hyVVRKSmJ6UlRSemhsWmxwM2RFcDNOemQyYkU1b1FWbDRRazE0TlFwU1dESlVTM2swUVdWUE1EMEtMUzB0TFMxRlRrUWdRMFZTVkVsR1NVTkJWRVV0TFMwdExRbz0ifX0=", "integratedTime": 1663463513, "logID": "c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d", "logIndex": 3527741, "verification": { "signedEntryTimestamp": "MEUCIQDDPOjpA4pKVf0go1NclpptEBALyqqmtJlhKi13w5CyxgIgB4lxM6RktSfMtoaeO8DffoN51ymoAZWAmJjvL6UrNz0=" } } }
リクエスト
まず、spec.publicKey
を見ていきます。ここには、fulcioから取得したユーザの証明書が入っています。
import base64 r = { "apiVersion": "0.0.1", "spec": { "content": { "envelope": '{"payloadType":"application/vnd.in-toto+json","payload":"eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInByZWRpY2F0ZVR5cGUiOiJodHRwczovL2V4YW1wbGUuY29tL1Rlc3RSZXN1bHQvdjEiLCJzdWJqZWN0IjpbeyJuYW1lIjoiaW5kZXguZG9ja2VyLmlvL290bXM2MS90ZXN0LWN1c3RvbS1hdHRlc3QiLCJkaWdlc3QiOnsic2hhMjU2IjoiOTIyNTE0NTgwODhjNjM4MDYxY2RhOGZkOGI0MDNiNzZkNjYxYTRkYzZiN2VlNzFiNmFmZmNmMTg3MjU1N2IyYiJ9fV0sInByZWRpY2F0ZSI6eyJwYXNzZWQiOnRydWV9fQ==","signatures":[{"keyid":"","sig":"MEUCIAs21tx59kbSkqA9kRE/b3De51SN62cbJSCWG6x9PDO7AiEAquGbnjchwI4D0P4m4njMnH2AsnR/zpzosRogQugDkJ8="}]}', "hash": { "algorithm": "sha256", "value": "505eaf34d3fd4b40b3d60734b1f14d1792d4dc91b0b2677993fd37b8d493a466", }, }, "publicKey": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNwRENDQWltZ0F3SUJBZ0lVT3JxOHJleEMzMWF5Qm9rV2phT1gyOFBVZEVZd0NnWUlLb1pJemowRUF3TXcKTnpFVk1CTUdBMVVFQ2hNTWMybG5jM1J2Y21VdVpHVjJNUjR3SEFZRFZRUURFeFZ6YVdkemRHOXlaUzFwYm5SbApjbTFsWkdsaGRHVXdIaGNOTWpJd09URTRNREV4TVRRNVdoY05Nakl3T1RFNE1ERXlNVFE1V2pBQU1Ga3dFd1lICktvWkl6ajBDQVFZSUtvWkl6ajBEQVFjRFFnQUVpMnBLa0pkSnhyRFpyQ3VEVCtKRE4vNjNSUzlIY3oxeXI0WUIKWVZZTG5JNGhuRnFuQU12eDY4eGJqV1hXM084akVXSXNna01HY0dhK2dpWUlEVjBiRmFPQ0FVZ3dnZ0ZFTUE0RwpBMVVkRHdFQi93UUVBd0lIZ0RBVEJnTlZIU1VFRERBS0JnZ3JCZ0VGQlFjREF6QWRCZ05WSFE0RUZnUVVPSXVBCm9IMlRta1kvVUxkMXN5bWpNM0tNbVZzd0h3WURWUjBqQkJnd0ZvQVUzOVBwejFZa0VaYjVxTmpwS0ZXaXhpNFkKWkQ4d0pRWURWUjBSQVFIL0JCc3dHWUVYYzJGemIyRnJhWEpoTmpFeE5FQm5iV0ZwYkM1amIyMHdLUVlLS3dZQgpCQUdEdnpBQkFRUWJhSFIwY0hNNkx5OWhZMk52ZFc1MGN5NW5iMjluYkdVdVkyOXRNSUdLQmdvckJnRUVBZFo1CkFnUUNCSHdFZWdCNEFIWUFDR0NTOENoUy8yaEYwZEZySjRTY1JXY1lyQlk5d3pqU2JlYThJZ1kyYjNJQUFBR0QKVGliUG9BQUFCQU1BUnpCRkFpQkJZVXhGcEVkeXZBYWtDam4rb2VTVmpYSEFDTzVjeTN3YmRuRzF5bGErOHdJaApBUC9NMEVPQllRZU0wVUMvUEJiZ2toSDJxWTNWWmJSUG9WL1pTTERyZTd1Vk1Bb0dDQ3FHU000OUJBTURBMmtBCk1HWUNNUURwR0o0d3huTGxOS1QxbHpwYndDZFVBbWNmdEFvWm9ocTYyejdOa21UZ1hKcUVUOFZkaU1DVGFnTzQKNEprYjRHSUNNUUR6MGxIU3c1U1lnYzFKRjJSUDB5U2NUSWxrUTJJbzRTRzhlZlp3dEp3Nzd2bE5oQVl4Qk14NQpSWDJUS3k0QWVPMD0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=", }, "kind": "intoto", } print(base64.b64decode(r["spec"]["publicKey"]).decode("utf-8")) """ > python b.py -----BEGIN CERTIFICATE----- MIICpDCCAimgAwIBAgIUOrq8rexC31ayBokWjaOX28PUdEYwCgYIKoZIzj0EAwMw NzEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRl cm1lZGlhdGUwHhcNMjIwOTE4MDExMTQ5WhcNMjIwOTE4MDEyMTQ5WjAAMFkwEwYH KoZIzj0CAQYIKoZIzj0DAQcDQgAEi2pKkJdJxrDZrCuDT+JDN/63RS9Hcz1yr4YB YVYLnI4hnFqnAMvx68xbjWXW3O8jEWIsgkMGcGa+giYIDV0bFaOCAUgwggFEMA4G A1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQUOIuA oH2TmkY/ULd1symjM3KMmVswHwYDVR0jBBgwFoAU39Ppz1YkEZb5qNjpKFWixi4Y ZD8wJQYDVR0RAQH/BBswGYEXc2Fzb2FraXJhNjExNEBnbWFpbC5jb20wKQYKKwYB BAGDvzABAQQbaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tMIGKBgorBgEEAdZ5 AgQCBHwEegB4AHYACGCS8ChS/2hF0dFrJ4ScRWcYrBY9wzjSbea8IgY2b3IAAAGD TibPoAAABAMARzBFAiBBYUxFpEdyvAakCjn+oeSVjXHACO5cy3wbdnG1yla+8wIh AP/M0EOBYQeM0UC/PBbgkhH2qY3VZbRPoV/ZSLDre7uVMAoGCCqGSM49BAMDA2kA MGYCMQDpGJ4wxnLlNKT1lzpbwCdUAmcftAoZohq62z7NkmTgXJqET8VdiMCTagO4 4Jkb4GICMQDz0lHSw5SYgc1JF2RP0yScTIlkQ2Io4SG8efZwtJw77vlNhAYxBMx5 RX2TKy4AeO0= -----END CERTIFICATE----- """
リクエストのin-toto attestationを検証してみる
次に、spec.content.envelope
を見ていきます。
おさらいとしてcosiginのコマンドを貼っておきます。
$ cat test-result.json {"passed": true} $ COSIGN_EXPERIMENTAL=1 ./cosign attest --type 'https://example.com/TestResult/v1' --predicate ./test-result.json otms61/test-custom-attest
spec.content.envelope
にはin-toto attestationの形式で、指定した test-result.json
の値がpredicateとして、対象のレポジトリ( otms61/test-custom-attest
) がsubjectとして入っています。
in-toto attestationについては以前に記事を書いたので参考にしてみてください。 otameshi61.hatenablog.com
$ cat envelop.intoto.jsonl | jq . { "payloadType": "application/vnd.in-toto+json", "payload": "eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInByZWRpY2F0ZVR5cGUiOiJodHRwczovL2V4YW1wbGUuY29tL1Rlc3RSZXN1bHQvdjEiLCJzdWJqZWN0IjpbeyJuYW1lIjoiaW5kZXguZG9ja2VyLmlvL290bXM2MS90ZXN0LWN1c3RvbS1hdHRlc3QiLCJkaWdlc3QiOnsic2hhMjU2IjoiOTIyNTE0NTgwODhjNjM4MDYxY2RhOGZkOGI0MDNiNzZkNjYxYTRkYzZiN2VlNzFiNmFmZmNmMTg3MjU1N2IyYiJ9fV0sInByZWRpY2F0ZSI6eyJwYXNzZWQiOnRydWV9fQ==", "signatures": [ { "keyid": "", "sig": "MEUCIAs21tx59kbSkqA9kRE/b3De51SN62cbJSCWG6x9PDO7AiEAquGbnjchwI4D0P4m4njMnH2AsnR/zpzosRogQugDkJ8=" } ] }
sigにある署名を検証してみます。 in-toto attestaionはDSSE Envelopeという形式をとっていて、payloadTypeとpayloadをもとにPAEという文字列を生成し、この文字列に対してsigを生成しています。
公開鍵は証明書から取り出したものを使用しています。
import base64 from cryptography.x509 import load_pem_x509_certificate from cryptography.hazmat.primitives.asymmetric import ec from cryptography.hazmat.primitives import hashes cert = """-----BEGIN CERTIFICATE----- MIICpDCCAimgAwIBAgIUOrq8rexC31ayBokWjaOX28PUdEYwCgYIKoZIzj0EAwMw NzEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRl cm1lZGlhdGUwHhcNMjIwOTE4MDExMTQ5WhcNMjIwOTE4MDEyMTQ5WjAAMFkwEwYH KoZIzj0CAQYIKoZIzj0DAQcDQgAEi2pKkJdJxrDZrCuDT+JDN/63RS9Hcz1yr4YB YVYLnI4hnFqnAMvx68xbjWXW3O8jEWIsgkMGcGa+giYIDV0bFaOCAUgwggFEMA4G A1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQUOIuA oH2TmkY/ULd1symjM3KMmVswHwYDVR0jBBgwFoAU39Ppz1YkEZb5qNjpKFWixi4Y ZD8wJQYDVR0RAQH/BBswGYEXc2Fzb2FraXJhNjExNEBnbWFpbC5jb20wKQYKKwYB BAGDvzABAQQbaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tMIGKBgorBgEEAdZ5 AgQCBHwEegB4AHYACGCS8ChS/2hF0dFrJ4ScRWcYrBY9wzjSbea8IgY2b3IAAAGD TibPoAAABAMARzBFAiBBYUxFpEdyvAakCjn+oeSVjXHACO5cy3wbdnG1yla+8wIh AP/M0EOBYQeM0UC/PBbgkhH2qY3VZbRPoV/ZSLDre7uVMAoGCCqGSM49BAMDA2kA MGYCMQDpGJ4wxnLlNKT1lzpbwCdUAmcftAoZohq62z7NkmTgXJqET8VdiMCTagO4 4Jkb4GICMQDz0lHSw5SYgc1JF2RP0yScTIlkQ2Io4SG8efZwtJw77vlNhAYxBMx5 RX2TKy4AeO0= -----END CERTIFICATE-----""" p = { "payloadType": "application/vnd.in-toto+json", "payload": "eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInByZWRpY2F0ZVR5cGUiOiJodHRwczovL2V4YW1wbGUuY29tL1Rlc3RSZXN1bHQvdjEiLCJzdWJqZWN0IjpbeyJuYW1lIjoiaW5kZXguZG9ja2VyLmlvL290bXM2MS90ZXN0LWN1c3RvbS1hdHRlc3QiLCJkaWdlc3QiOnsic2hhMjU2IjoiOTIyNTE0NTgwODhjNjM4MDYxY2RhOGZkOGI0MDNiNzZkNjYxYTRkYzZiN2VlNzFiNmFmZmNmMTg3MjU1N2IyYiJ9fV0sInByZWRpY2F0ZSI6eyJwYXNzZWQiOnRydWV9fQ==", "signatures": [ { "keyid": "", "sig": "MEUCIAs21tx59kbSkqA9kRE/b3De51SN62cbJSCWG6x9PDO7AiEAquGbnjchwI4D0P4m4njMnH2AsnR/zpzosRogQugDkJ8=", } ], } decoded = base64.b64decode(p["payload"]).decode("utf-8") pae = f"DSSEv1 {len(p['payloadType'])} {p['payloadType']} {len(decoded)} {decoded}".encode( "utf-8" ) z = load_pem_x509_certificate(cert.encode("utf-8")) # 検証に失敗すると例外があがる print( z.public_key().verify( signature=base64.b64decode(p["signatures"][0]["sig"]), data=pae, signature_algorithm=ec.ECDSA(hashes.SHA256()), ) ) """ $ python z.py None """
in-toto attestationのstatementのsubjectについて
payloadをデコードすると以下の情報が入っています。
$ cat envelop.intoto.jsonl | jq ".payload | @base64d | fromjson" { "_type": "https://in-toto.io/Statement/v0.1", "predicateType": "https://example.com/TestResult/v1", "subject": [ { "name": "index.docker.io/otms61/test-custom-attest", "digest": { "sha256": "92251458088c638061cda8fd8b403b76d661a4dc6b7ee71b6affcf1872557b2b" } } ], "predicate": { "passed": true } }
predicate には --predicate
で指定した test-result.json
の内容が入っており、predicateTypeには
--type
で指定した https://example.com/TestResult/v1
が入っていることがわかります。
$ cat test-result.json {"passed": true} $ COSIGN_EXPERIMENTAL=1 ./cosign attest --type 'https://example.com/TestResult/v1' --predicate ./test-result.json otms61/test-custom-attest
digest の sha256は、対象のレポジトリのマニフェストのsha256の値が入っています。
$ crane manifest otms61/test-custom-attest | shasum -a 256 92251458088c638061cda8fd8b403b76d661a4dc6b7ee71b6affcf1872557b2b -
sigstoreがなぜsubjectに対照イメージのマニフェストのハッシュ値を使っているかは以下のブログの 署名フォーマット
に詳しいです。
リクエスト内のハッシュ値
残りのリクエストの値も見ていきます。
spec.content.hash
の値は、 spec.content.envelope
のハッシュ値です。
from hashlib import sha256 r = { "apiVersion": "0.0.1", "spec": { "content": { "envelope": '{"payloadType":"application/vnd.in-toto+json","payload":"eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInByZWRpY2F0ZVR5cGUiOiJodHRwczovL2V4YW1wbGUuY29tL1Rlc3RSZXN1bHQvdjEiLCJzdWJqZWN0IjpbeyJuYW1lIjoiaW5kZXguZG9ja2VyLmlvL290bXM2MS90ZXN0LWN1c3RvbS1hdHRlc3QiLCJkaWdlc3QiOnsic2hhMjU2IjoiOTIyNTE0NTgwODhjNjM4MDYxY2RhOGZkOGI0MDNiNzZkNjYxYTRkYzZiN2VlNzFiNmFmZmNmMTg3MjU1N2IyYiJ9fV0sInByZWRpY2F0ZSI6eyJwYXNzZWQiOnRydWV9fQ==","signatures":[{"keyid":"","sig":"MEUCIAs21tx59kbSkqA9kRE/b3De51SN62cbJSCWG6x9PDO7AiEAquGbnjchwI4D0P4m4njMnH2AsnR/zpzosRogQugDkJ8="}]}', "hash": { "algorithm": "sha256", "value": "505eaf34d3fd4b40b3d60734b1f14d1792d4dc91b0b2677993fd37b8d493a466", }, }, "publicKey": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNwRENDQWltZ0F3SUJBZ0lVT3JxOHJleEMzMWF5Qm9rV2phT1gyOFBVZEVZd0NnWUlLb1pJemowRUF3TXcKTnpFVk1CTUdBMVVFQ2hNTWMybG5jM1J2Y21VdVpHVjJNUjR3SEFZRFZRUURFeFZ6YVdkemRHOXlaUzFwYm5SbApjbTFsWkdsaGRHVXdIaGNOTWpJd09URTRNREV4TVRRNVdoY05Nakl3T1RFNE1ERXlNVFE1V2pBQU1Ga3dFd1lICktvWkl6ajBDQVFZSUtvWkl6ajBEQVFjRFFnQUVpMnBLa0pkSnhyRFpyQ3VEVCtKRE4vNjNSUzlIY3oxeXI0WUIKWVZZTG5JNGhuRnFuQU12eDY4eGJqV1hXM084akVXSXNna01HY0dhK2dpWUlEVjBiRmFPQ0FVZ3dnZ0ZFTUE0RwpBMVVkRHdFQi93UUVBd0lIZ0RBVEJnTlZIU1VFRERBS0JnZ3JCZ0VGQlFjREF6QWRCZ05WSFE0RUZnUVVPSXVBCm9IMlRta1kvVUxkMXN5bWpNM0tNbVZzd0h3WURWUjBqQkJnd0ZvQVUzOVBwejFZa0VaYjVxTmpwS0ZXaXhpNFkKWkQ4d0pRWURWUjBSQVFIL0JCc3dHWUVYYzJGemIyRnJhWEpoTmpFeE5FQm5iV0ZwYkM1amIyMHdLUVlLS3dZQgpCQUdEdnpBQkFRUWJhSFIwY0hNNkx5OWhZMk52ZFc1MGN5NW5iMjluYkdVdVkyOXRNSUdLQmdvckJnRUVBZFo1CkFnUUNCSHdFZWdCNEFIWUFDR0NTOENoUy8yaEYwZEZySjRTY1JXY1lyQlk5d3pqU2JlYThJZ1kyYjNJQUFBR0QKVGliUG9BQUFCQU1BUnpCRkFpQkJZVXhGcEVkeXZBYWtDam4rb2VTVmpYSEFDTzVjeTN3YmRuRzF5bGErOHdJaApBUC9NMEVPQllRZU0wVUMvUEJiZ2toSDJxWTNWWmJSUG9WL1pTTERyZTd1Vk1Bb0dDQ3FHU000OUJBTURBMmtBCk1HWUNNUURwR0o0d3huTGxOS1QxbHpwYndDZFVBbWNmdEFvWm9ocTYyejdOa21UZ1hKcUVUOFZkaU1DVGFnTzQKNEprYjRHSUNNUUR6MGxIU3c1U1lnYzFKRjJSUDB5U2NUSWxrUTJJbzRTRzhlZlp3dEp3Nzd2bE5oQVl4Qk14NQpSWDJUS3k0QWVPMD0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=", }, "kind": "intoto", } hash = sha256(r["spec"]["content"]["envelope"].encode("utf-8")) print(hash.hexdigest() == r["spec"]["content"]["hash"]["value"]) """ > python a.py True """
レスポンス
< HTTP/2.0 201 Created < Content-Type: application/json < Date: Sun, 18 Sep 2022 01:11:53 GMT < Location: /api/v1/log/entries/4c43de72686ceb257f6641ca1d62069f7f891822bca772d166dd88941e31c5f1 < Strict-Transport-Security: max-age=15724800; includeSubDomains < Vary: Origin { "4c43de72686ceb257f6641ca1d62069f7f891822bca772d166dd88941e31c5f1": { "body": "eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiaW50b3RvIiwic3BlYyI6eyJjb250ZW50Ijp7Imhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiI1MDVlYWYzNGQzZmQ0YjQwYjNkNjA3MzRiMWYxNGQxNzkyZDRkYzkxYjBiMjY3Nzk5M2ZkMzdiOGQ0OTNhNDY2In0sInBheWxvYWRIYXNoIjp7ImFsZ29yaXRobSI6InNoYTI1NiIsInZhbHVlIjoiZDRjZjI2YWUwZmQ3OWRhMTU2ZjAzYzgwNjY2ZTFjZWFmZTljNjQ4ZWU2MGQwYjM2ODBlNGU4ZTQzZDNiZGNiMCJ9fSwicHVibGljS2V5IjoiTFMwdExTMUNSVWRKVGlCRFJWSlVTVVpKUTBGVVJTMHRMUzB0Q2sxSlNVTndSRU5EUVdsdFowRjNTVUpCWjBsVlQzSnhPSEpsZUVNek1XRjVRbTlyVjJwaFQxZ3lPRkJWWkVWWmQwTm5XVWxMYjFwSmVtb3dSVUYzVFhjS1RucEZWazFDVFVkQk1WVkZRMmhOVFdNeWJHNWpNMUoyWTIxVmRWcEhWakpOVWpSM1NFRlpSRlpSVVVSRmVGWjZZVmRrZW1SSE9YbGFVekZ3WW01U2JBcGpiVEZzV2tkc2FHUkhWWGRJYUdOT1RXcEpkMDlVUlRSTlJFVjRUVlJSTlZkb1kwNU5ha2wzVDFSRk5FMUVSWGxOVkZFMVYycEJRVTFHYTNkRmQxbElDa3R2V2tsNmFqQkRRVkZaU1V0dldrbDZhakJFUVZGalJGRm5RVVZwTW5CTGEwcGtTbmh5UkZweVEzVkVWQ3RLUkU0dk5qTlNVemxJWTNveGVYSTBXVUlLV1ZaWlRHNUpOR2h1Um5GdVFVMTJlRFk0ZUdKcVYxaFhNMDg0YWtWWFNYTm5hMDFIWTBkaEsyZHBXVWxFVmpCaVJtRlBRMEZWWjNkblowWkZUVUUwUndwQk1WVmtSSGRGUWk5M1VVVkJkMGxJWjBSQlZFSm5UbFpJVTFWRlJFUkJTMEpuWjNKQ1owVkdRbEZqUkVGNlFXUkNaMDVXU0ZFMFJVWm5VVlZQU1hWQkNtOUlNbFJ0YTFrdlZVeGtNWE41YldwTk0wdE5iVlp6ZDBoM1dVUldVakJxUWtKbmQwWnZRVlV6T1ZCd2VqRlphMFZhWWpWeFRtcHdTMFpYYVhocE5Ga0tXa1E0ZDBwUldVUldVakJTUVZGSUwwSkNjM2RIV1VWWVl6SkdlbUl5Um5KaFdFcG9UbXBGZUU1RlFtNWlWMFp3WWtNMWFtSXlNSGRMVVZsTFMzZFpRZ3BDUVVkRWRucEJRa0ZSVVdKaFNGSXdZMGhOTmt4NU9XaFpNazUyWkZjMU1HTjVOVzVpTWpsdVlrZFZkVmt5T1hSTlNVZExRbWR2Y2tKblJVVkJaRm8xQ2tGblVVTkNTSGRGWldkQ05FRklXVUZEUjBOVE9FTm9VeTh5YUVZd1pFWnlTalJUWTFKWFkxbHlRbGs1ZDNwcVUySmxZVGhKWjFreVlqTkpRVUZCUjBRS1ZHbGlVRzlCUVVGQ1FVMUJVbnBDUmtGcFFrSlpWWGhHY0VWa2VYWkJZV3REYW00cmIyVlRWbXBZU0VGRFR6VmplVE4zWW1SdVJ6RjViR0VyT0hkSmFBcEJVQzlOTUVWUFFsbFJaVTB3VlVNdlVFSmlaMnRvU0RKeFdUTldXbUpTVUc5V0wxcFRURVJ5WlRkMVZrMUJiMGREUTNGSFUwMDBPVUpCVFVSQk1tdEJDazFIV1VOTlVVUndSMG8wZDNodVRHeE9TMVF4Ykhwd1luZERaRlZCYldObWRFRnZXbTlvY1RZeWVqZE9hMjFVWjFoS2NVVlVPRlprYVUxRFZHRm5UelFLTkVwcllqUkhTVU5OVVVSNk1HeElVM2MxVTFsbll6RktSakpTVURCNVUyTlVTV3hyVVRKSmJ6UlRSemhsWmxwM2RFcDNOemQyYkU1b1FWbDRRazE0TlFwU1dESlVTM2swUVdWUE1EMEtMUzB0TFMxRlRrUWdRMFZTVkVsR1NVTkJWRVV0TFMwdExRbz0ifX0=", "integratedTime": 1663463513, "logID": "c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d", "logIndex": 3527741, "verification": { "signedEntryTimestamp": "MEUCIQDDPOjpA4pKVf0go1NclpptEBALyqqmtJlhKi13w5CyxgIgB4lxM6RktSfMtoaeO8DffoN51ymoAZWAmJjvL6UrNz0=" } } }
ステータスコードは 201 Created
で、Locationに作成されたリソースのパスが記載されています。
GETで取得した値の方が情報が多いため、こちらの値を見ていきます。
$ curl -s --request GET --url https://rekor.sigstore.dev/api/v1/log/entries/4c43de72686ceb257f6641ca1d62069f7f891822bca772d166dd88941e31c5f1 | jq . { "4c43de72686ceb257f6641ca1d62069f7f891822bca772d166dd88941e31c5f1": { "attestation": { "data": "eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInByZWRpY2F0ZVR5cGUiOiJodHRwczovL2V4YW1wbGUuY29tL1Rlc3RSZXN1bHQvdjEiLCJzdWJqZWN0IjpbeyJuYW1lIjoiaW5kZXguZG9ja2VyLmlvL290bXM2MS90ZXN0LWN1c3RvbS1hdHRlc3QiLCJkaWdlc3QiOnsic2hhMjU2IjoiOTIyNTE0NTgwODhjNjM4MDYxY2RhOGZkOGI0MDNiNzZkNjYxYTRkYzZiN2VlNzFiNmFmZmNmMTg3MjU1N2IyYiJ9fV0sInByZWRpY2F0ZSI6eyJwYXNzZWQiOnRydWV9fQ==" }, "body": "eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiaW50b3RvIiwic3BlYyI6eyJjb250ZW50Ijp7Imhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiI1MDVlYWYzNGQzZmQ0YjQwYjNkNjA3MzRiMWYxNGQxNzkyZDRkYzkxYjBiMjY3Nzk5M2ZkMzdiOGQ0OTNhNDY2In0sInBheWxvYWRIYXNoIjp7ImFsZ29yaXRobSI6InNoYTI1NiIsInZhbHVlIjoiZDRjZjI2YWUwZmQ3OWRhMTU2ZjAzYzgwNjY2ZTFjZWFmZTljNjQ4ZWU2MGQwYjM2ODBlNGU4ZTQzZDNiZGNiMCJ9fSwicHVibGljS2V5IjoiTFMwdExTMUNSVWRKVGlCRFJWSlVTVVpKUTBGVVJTMHRMUzB0Q2sxSlNVTndSRU5EUVdsdFowRjNTVUpCWjBsVlQzSnhPSEpsZUVNek1XRjVRbTlyVjJwaFQxZ3lPRkJWWkVWWmQwTm5XVWxMYjFwSmVtb3dSVUYzVFhjS1RucEZWazFDVFVkQk1WVkZRMmhOVFdNeWJHNWpNMUoyWTIxVmRWcEhWakpOVWpSM1NFRlpSRlpSVVVSRmVGWjZZVmRrZW1SSE9YbGFVekZ3WW01U2JBcGpiVEZzV2tkc2FHUkhWWGRJYUdOT1RXcEpkMDlVUlRSTlJFVjRUVlJSTlZkb1kwNU5ha2wzVDFSRk5FMUVSWGxOVkZFMVYycEJRVTFHYTNkRmQxbElDa3R2V2tsNmFqQkRRVkZaU1V0dldrbDZhakJFUVZGalJGRm5RVVZwTW5CTGEwcGtTbmh5UkZweVEzVkVWQ3RLUkU0dk5qTlNVemxJWTNveGVYSTBXVUlLV1ZaWlRHNUpOR2h1Um5GdVFVMTJlRFk0ZUdKcVYxaFhNMDg0YWtWWFNYTm5hMDFIWTBkaEsyZHBXVWxFVmpCaVJtRlBRMEZWWjNkblowWkZUVUUwUndwQk1WVmtSSGRGUWk5M1VVVkJkMGxJWjBSQlZFSm5UbFpJVTFWRlJFUkJTMEpuWjNKQ1owVkdRbEZqUkVGNlFXUkNaMDVXU0ZFMFJVWm5VVlZQU1hWQkNtOUlNbFJ0YTFrdlZVeGtNWE41YldwTk0wdE5iVlp6ZDBoM1dVUldVakJxUWtKbmQwWnZRVlV6T1ZCd2VqRlphMFZhWWpWeFRtcHdTMFpYYVhocE5Ga0tXa1E0ZDBwUldVUldVakJTUVZGSUwwSkNjM2RIV1VWWVl6SkdlbUl5Um5KaFdFcG9UbXBGZUU1RlFtNWlWMFp3WWtNMWFtSXlNSGRMVVZsTFMzZFpRZ3BDUVVkRWRucEJRa0ZSVVdKaFNGSXdZMGhOTmt4NU9XaFpNazUyWkZjMU1HTjVOVzVpTWpsdVlrZFZkVmt5T1hSTlNVZExRbWR2Y2tKblJVVkJaRm8xQ2tGblVVTkNTSGRGWldkQ05FRklXVUZEUjBOVE9FTm9VeTh5YUVZd1pFWnlTalJUWTFKWFkxbHlRbGs1ZDNwcVUySmxZVGhKWjFreVlqTkpRVUZCUjBRS1ZHbGlVRzlCUVVGQ1FVMUJVbnBDUmtGcFFrSlpWWGhHY0VWa2VYWkJZV3REYW00cmIyVlRWbXBZU0VGRFR6VmplVE4zWW1SdVJ6RjViR0VyT0hkSmFBcEJVQzlOTUVWUFFsbFJaVTB3VlVNdlVFSmlaMnRvU0RKeFdUTldXbUpTVUc5V0wxcFRURVJ5WlRkMVZrMUJiMGREUTNGSFUwMDBPVUpCVFVSQk1tdEJDazFIV1VOTlVVUndSMG8wZDNodVRHeE9TMVF4Ykhwd1luZERaRlZCYldObWRFRnZXbTlvY1RZeWVqZE9hMjFVWjFoS2NVVlVPRlprYVUxRFZHRm5UelFLTkVwcllqUkhTVU5OVVVSNk1HeElVM2MxVTFsbll6RktSakpTVURCNVUyTlVTV3hyVVRKSmJ6UlRSemhsWmxwM2RFcDNOemQyYkU1b1FWbDRRazE0TlFwU1dESlVTM2swUVdWUE1EMEtMUzB0TFMxRlRrUWdRMFZTVkVsR1NVTkJWRVV0TFMwdExRbz0ifX0=", "integratedTime": 1663463513, "logID": "c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d", "logIndex": 3527741, "verification": { "inclusionProof": { "hashes": [ "c9758c506f78253208cfb417acf808db1c7f845bc7d84426041be88da141de78", "f1f4310c743e29c84c5d0668eb710d1b6647a11d3b4ecc080105eac8941ed858", "7ad323f32d80ac4800c4d305e97e4082ada7545c7ce18e7e760f2a254fc37545", "7900dfe673792ae932e9003fc6219f7608413ae36e2ea8cd82522324dae23d7d", "f97d5b3cec12ff3607a3b25fa51e36c6fbb13553ec93d5cdc1ff3cdf509e17a6", "19cdf066e43cdde3b8782e2177b02b47891bae7b523238064c12a3e576f359bd", "ae95a5a30dc3f4f4aaa324f7f7d028a61b4c752efc2c5e680e4cb2b4bc2c616d", "5de43cdc0fc444eff6cdec0f8e36ff3dc02cd672fc03e4a06b51cf2022d2e575", "c9509918f560d8d4a7c688a236af36fde5e15e21e1548cf130c98695810970b8", "cdf24d1833eeef52de4c31b84a2f481930e6c2167ca1c9820e5311dc01287fd5", "81e8c50dd54ea567e80fc4427d61d26a9ad16e29cdaea6ac87e69aaed32a54f4", "fe79312498d289bcb6bc0d4226b21cabb71bb260f987bb711b548a5fe1f83b6e", "b06b1025bd35f834f3c39786ef4e554bf06c76f61e497fd1765d0689480d3107", "bca0b68bd01bcbedb77d5ef7760d79854383eb1359263884cee78eb7955766f9", "7ae973cb7963f4c8609ee238ba06d9d84f1f73be8aa145279e2110e59c7862a3", "f8b4461eb78dadbd7cffd9a281185b7b1f2dfee61c17688016dc75c40b728eb2", "4e659e217261878d89a86f48b5a2f1a8bf2e47aad22d077ad45d0bcec969c383", "72ffc75e39e3042b31f3ee5e6f6315726d1263c7ccc6465bfca688cc2f612247", "6d494b237648126525b08f975c736a55d1f7a64472fcc2782bbc16733c608d7b", "efb36cfc54705d8cd921a621a9389ffa03956b15d68bfabadac2b4853852079b" ], "logIndex": 3527741, "rootHash": "5f1fbe1b5cb5b9daff35778d78ef02a2f11584539a59c62835afac3ef3198da1", "treeSize": 3531728 }, "signedEntryTimestamp": "MEYCIQCLfobPJEVNQ7//arswv62/R1PgQMAelCl5zHa+tr0dFwIhAMGNKi77UKqKrFeTeFlaLQzPs5pTwFf6+uHCj56fn1TB" } } }
まず、1番上の 4c43de72686ceb257f6641ca1d62069f7f891822bca772d166dd88941e31c5f1
はrekorの中でUUIDと呼ばれるIDで、エントリに一意に紐づくID。また、logIndex の3527741も同様にエントリに紐づく値になっています。
logIDは、rekorの公開鍵のハッシュ値となっています。logIDという名前はSCTの公開鍵のハッシュ値の時に使われていた名称から持ってきていると思われますが、rekorのレスポンスに並べられると、エントリのindexのlogIndexなどがありかなり分かりにくいですね。。
# TUFから取得 > curl -s https://sigstore-tuf-root.storage.googleapis.com/targets/rekor.pub | openssl ec -outform der -pubin | shasum -a 256 read EC key writing EC key c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d - # rekorから取得 > curl -s --request GET --url https://rekor.sigstore.dev/api/v1/log/publicKey | openssl ec -outform der -pubin | shasum -a 256 read EC key writing EC key c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d -
attestation.data
には、アップロードしたattestaionのpayloadが入っています。
bodyの中身
body
は デコードすると以下の情報が入っています。
{ "apiVersion": "0.0.1", "kind": "intoto", "spec": { "content": { "hash": { "algorithm": "sha256", "value": "505eaf34d3fd4b40b3d60734b1f14d1792d4dc91b0b2677993fd37b8d493a466" }, "payloadHash": { "algorithm": "sha256", "value": "d4cf26ae0fd79da156f03c80666e1ceafe9c648ee60d0b3680e4e8e43d3bdcb0" } }, "publicKey": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNwRENDQWltZ0F3SUJBZ0lVT3JxOHJleEMzMWF5Qm9rV2phT1gyOFBVZEVZd0NnWUlLb1pJemowRUF3TXcKTnpFVk1CTUdBMVVFQ2hNTWMybG5jM1J2Y21VdVpHVjJNUjR3SEFZRFZRUURFeFZ6YVdkemRHOXlaUzFwYm5SbApjbTFsWkdsaGRHVXdIaGNOTWpJd09URTRNREV4TVRRNVdoY05Nakl3T1RFNE1ERXlNVFE1V2pBQU1Ga3dFd1lICktvWkl6ajBDQVFZSUtvWkl6ajBEQVFjRFFnQUVpMnBLa0pkSnhyRFpyQ3VEVCtKRE4vNjNSUzlIY3oxeXI0WUIKWVZZTG5JNGhuRnFuQU12eDY4eGJqV1hXM084akVXSXNna01HY0dhK2dpWUlEVjBiRmFPQ0FVZ3dnZ0ZFTUE0RwpBMVVkRHdFQi93UUVBd0lIZ0RBVEJnTlZIU1VFRERBS0JnZ3JCZ0VGQlFjREF6QWRCZ05WSFE0RUZnUVVPSXVBCm9IMlRta1kvVUxkMXN5bWpNM0tNbVZzd0h3WURWUjBqQkJnd0ZvQVUzOVBwejFZa0VaYjVxTmpwS0ZXaXhpNFkKWkQ4d0pRWURWUjBSQVFIL0JCc3dHWUVYYzJGemIyRnJhWEpoTmpFeE5FQm5iV0ZwYkM1amIyMHdLUVlLS3dZQgpCQUdEdnpBQkFRUWJhSFIwY0hNNkx5OWhZMk52ZFc1MGN5NW5iMjluYkdVdVkyOXRNSUdLQmdvckJnRUVBZFo1CkFnUUNCSHdFZWdCNEFIWUFDR0NTOENoUy8yaEYwZEZySjRTY1JXY1lyQlk5d3pqU2JlYThJZ1kyYjNJQUFBR0QKVGliUG9BQUFCQU1BUnpCRkFpQkJZVXhGcEVkeXZBYWtDam4rb2VTVmpYSEFDTzVjeTN3YmRuRzF5bGErOHdJaApBUC9NMEVPQllRZU0wVUMvUEJiZ2toSDJxWTNWWmJSUG9WL1pTTERyZTd1Vk1Bb0dDQ3FHU000OUJBTURBMmtBCk1HWUNNUURwR0o0d3huTGxOS1QxbHpwYndDZFVBbWNmdEFvWm9ocTYyejdOa21UZ1hKcUVUOFZkaU1DVGFnTzQKNEprYjRHSUNNUUR6MGxIU3c1U1lnYzFKRjJSUDB5U2NUSWxrUTJJbzRTRzhlZlp3dEp3Nzd2bE5oQVl4Qk14NQpSWDJUS3k0QWVPMD0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=" } }
spec.content.hash
には、リクエストでも送った in-toto attestationのハッシュ値が入っています。
spec.publicKey
には、リクエストでも送った fulcioから受け取ったユーザの証明書が含まれています。
spec.content.payloadHash
には、attestation.data
をデコードした値のsha256が入っています。
import base64 import json import hashlib r = { "4c43de72686ceb257f6641ca1d62069f7f891822bca772d166dd88941e31c5f1": { "attestation": { "data": "eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInByZWRpY2F0ZVR5cGUiOiJodHRwczovL2V4YW1wbGUuY29tL1Rlc3RSZXN1bHQvdjEiLCJzdWJqZWN0IjpbeyJuYW1lIjoiaW5kZXguZG9ja2VyLmlvL290bXM2MS90ZXN0LWN1c3RvbS1hdHRlc3QiLCJkaWdlc3QiOnsic2hhMjU2IjoiOTIyNTE0NTgwODhjNjM4MDYxY2RhOGZkOGI0MDNiNzZkNjYxYTRkYzZiN2VlNzFiNmFmZmNmMTg3MjU1N2IyYiJ9fV0sInByZWRpY2F0ZSI6eyJwYXNzZWQiOnRydWV9fQ==" }, "body": "eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiaW50b3RvIiwic3BlYyI6eyJjb250ZW50Ijp7Imhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiI1MDVlYWYzNGQzZmQ0YjQwYjNkNjA3MzRiMWYxNGQxNzkyZDRkYzkxYjBiMjY3Nzk5M2ZkMzdiOGQ0OTNhNDY2In0sInBheWxvYWRIYXNoIjp7ImFsZ29yaXRobSI6InNoYTI1NiIsInZhbHVlIjoiZDRjZjI2YWUwZmQ3OWRhMTU2ZjAzYzgwNjY2ZTFjZWFmZTljNjQ4ZWU2MGQwYjM2ODBlNGU4ZTQzZDNiZGNiMCJ9fSwicHVibGljS2V5IjoiTFMwdExTMUNSVWRKVGlCRFJWSlVTVVpKUTBGVVJTMHRMUzB0Q2sxSlNVTndSRU5EUVdsdFowRjNTVUpCWjBsVlQzSnhPSEpsZUVNek1XRjVRbTlyVjJwaFQxZ3lPRkJWWkVWWmQwTm5XVWxMYjFwSmVtb3dSVUYzVFhjS1RucEZWazFDVFVkQk1WVkZRMmhOVFdNeWJHNWpNMUoyWTIxVmRWcEhWakpOVWpSM1NFRlpSRlpSVVVSRmVGWjZZVmRrZW1SSE9YbGFVekZ3WW01U2JBcGpiVEZzV2tkc2FHUkhWWGRJYUdOT1RXcEpkMDlVUlRSTlJFVjRUVlJSTlZkb1kwNU5ha2wzVDFSRk5FMUVSWGxOVkZFMVYycEJRVTFHYTNkRmQxbElDa3R2V2tsNmFqQkRRVkZaU1V0dldrbDZhakJFUVZGalJGRm5RVVZwTW5CTGEwcGtTbmh5UkZweVEzVkVWQ3RLUkU0dk5qTlNVemxJWTNveGVYSTBXVUlLV1ZaWlRHNUpOR2h1Um5GdVFVMTJlRFk0ZUdKcVYxaFhNMDg0YWtWWFNYTm5hMDFIWTBkaEsyZHBXVWxFVmpCaVJtRlBRMEZWWjNkblowWkZUVUUwUndwQk1WVmtSSGRGUWk5M1VVVkJkMGxJWjBSQlZFSm5UbFpJVTFWRlJFUkJTMEpuWjNKQ1owVkdRbEZqUkVGNlFXUkNaMDVXU0ZFMFJVWm5VVlZQU1hWQkNtOUlNbFJ0YTFrdlZVeGtNWE41YldwTk0wdE5iVlp6ZDBoM1dVUldVakJxUWtKbmQwWnZRVlV6T1ZCd2VqRlphMFZhWWpWeFRtcHdTMFpYYVhocE5Ga0tXa1E0ZDBwUldVUldVakJTUVZGSUwwSkNjM2RIV1VWWVl6SkdlbUl5Um5KaFdFcG9UbXBGZUU1RlFtNWlWMFp3WWtNMWFtSXlNSGRMVVZsTFMzZFpRZ3BDUVVkRWRucEJRa0ZSVVdKaFNGSXdZMGhOTmt4NU9XaFpNazUyWkZjMU1HTjVOVzVpTWpsdVlrZFZkVmt5T1hSTlNVZExRbWR2Y2tKblJVVkJaRm8xQ2tGblVVTkNTSGRGWldkQ05FRklXVUZEUjBOVE9FTm9VeTh5YUVZd1pFWnlTalJUWTFKWFkxbHlRbGs1ZDNwcVUySmxZVGhKWjFreVlqTkpRVUZCUjBRS1ZHbGlVRzlCUVVGQ1FVMUJVbnBDUmtGcFFrSlpWWGhHY0VWa2VYWkJZV3REYW00cmIyVlRWbXBZU0VGRFR6VmplVE4zWW1SdVJ6RjViR0VyT0hkSmFBcEJVQzlOTUVWUFFsbFJaVTB3VlVNdlVFSmlaMnRvU0RKeFdUTldXbUpTVUc5V0wxcFRURVJ5WlRkMVZrMUJiMGREUTNGSFUwMDBPVUpCVFVSQk1tdEJDazFIV1VOTlVVUndSMG8wZDNodVRHeE9TMVF4Ykhwd1luZERaRlZCYldObWRFRnZXbTlvY1RZeWVqZE9hMjFVWjFoS2NVVlVPRlprYVUxRFZHRm5UelFLTkVwcllqUkhTVU5OVVVSNk1HeElVM2MxVTFsbll6RktSakpTVURCNVUyTlVTV3hyVVRKSmJ6UlRSemhsWmxwM2RFcDNOemQyYkU1b1FWbDRRazE0TlFwU1dESlVTM2swUVdWUE1EMEtMUzB0TFMxRlRrUWdRMFZTVkVsR1NVTkJWRVV0TFMwdExRbz0ifX0=", "integratedTime": 1663463513, "logID": "c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d", "logIndex": 3527741, "verification": { "inclusionProof": { "hashes": [ "c9758c506f78253208cfb417acf808db1c7f845bc7d84426041be88da141de78", "f1f4310c743e29c84c5d0668eb710d1b6647a11d3b4ecc080105eac8941ed858", "7ad323f32d80ac4800c4d305e97e4082ada7545c7ce18e7e760f2a254fc37545", "7900dfe673792ae932e9003fc6219f7608413ae36e2ea8cd82522324dae23d7d", "f97d5b3cec12ff3607a3b25fa51e36c6fbb13553ec93d5cdc1ff3cdf509e17a6", "19cdf066e43cdde3b8782e2177b02b47891bae7b523238064c12a3e576f359bd", "ae95a5a30dc3f4f4aaa324f7f7d028a61b4c752efc2c5e680e4cb2b4bc2c616d", "5de43cdc0fc444eff6cdec0f8e36ff3dc02cd672fc03e4a06b51cf2022d2e575", "c9509918f560d8d4a7c688a236af36fde5e15e21e1548cf130c98695810970b8", "cdf24d1833eeef52de4c31b84a2f481930e6c2167ca1c9820e5311dc01287fd5", "81e8c50dd54ea567e80fc4427d61d26a9ad16e29cdaea6ac87e69aaed32a54f4", "fe79312498d289bcb6bc0d4226b21cabb71bb260f987bb711b548a5fe1f83b6e", "b06b1025bd35f834f3c39786ef4e554bf06c76f61e497fd1765d0689480d3107", "bca0b68bd01bcbedb77d5ef7760d79854383eb1359263884cee78eb7955766f9", "7ae973cb7963f4c8609ee238ba06d9d84f1f73be8aa145279e2110e59c7862a3", "f8b4461eb78dadbd7cffd9a281185b7b1f2dfee61c17688016dc75c40b728eb2", "4e659e217261878d89a86f48b5a2f1a8bf2e47aad22d077ad45d0bcec969c383", "72ffc75e39e3042b31f3ee5e6f6315726d1263c7ccc6465bfca688cc2f612247", "6d494b237648126525b08f975c736a55d1f7a64472fcc2782bbc16733c608d7b", "efb36cfc54705d8cd921a621a9389ffa03956b15d68bfabadac2b4853852079b", ], "logIndex": 3527741, "rootHash": "5f1fbe1b5cb5b9daff35778d78ef02a2f11584539a59c62835afac3ef3198da1", "treeSize": 3531728, }, "signedEntryTimestamp": "MEYCIQCLfobPJEVNQ7//arswv62/R1PgQMAelCl5zHa+tr0dFwIhAMGNKi77UKqKrFeTeFlaLQzPs5pTwFf6+uHCj56fn1TB", }, } } hashed = hashlib.sha256( base64.b64decode( r["4c43de72686ceb257f6641ca1d62069f7f891822bca772d166dd88941e31c5f1"][ "attestation" ]["data"].encode("utf-8") ) ).hexdigest() body = json.loads( base64.b64decode( r["4c43de72686ceb257f6641ca1d62069f7f891822bca772d166dd88941e31c5f1"]["body"] ).decode("utf-8") ) print(hashed == body["spec"]["content"]["payloadHash"]["value"]) """ > python c.py True """
verification について - inclusionProof
。verification.inclusionProof
は、trillian のデータ構造の値が埋め込んであるようですが、rekor のクライアントの検証にも使われていないため、どう活用するかはまだ確認中です
2022/10/3 追記 @knqyf263 さんに VerifyTLogEntryの中でinclusionProofが使われていることを教えていただきました。ありがとうございます!
cosign/tlog.go at b3b6ae25362dc2c92c78abf2370ba0342ee86b2f · sigstore/cosign · GitHub
inclusionProof は、trillianが使っているデータ構造のMerkle Treeの検証に必要な情報が含まれています。 検証の流れを理解するには以下のブログを読んでもらうのが1番良いと思います。
簡単に流れを説明すると、rekorのレスポンスに含まれるbodyから得られる値(下図の②)からleaf(B)を計算し、BとAからCを計算します。その後にCとDからrootのEまで計算します。
https://transparency.dev/images/merkle-trees/merkle-tree-a-b-d.svg
ここで、AとDについては今まで全く出てこない値となります。rekorでは、これらの値はサーバのレスポンスから受け取ります。
したがって、rekorのレスポンスに含まれるbodyと、inclusionProofのhashesの値からrootのハッシュまで計算していきます。
rootのハッシュは別途inclusionProofにrootHashとして与えられます。また、このrootのハッシュ値はrekorに署名された形で取得も可能のようです。
以下のようなコードでrekorからのレスポンスからrootハッシュを計算することが可能です。
import base64 import hashlib res = { "362f8ecba72f43264c43de72686ceb257f6641ca1d62069f7f891822bca772d166dd88941e31c5f1": { "attestation": { "data": "eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInByZWRpY2F0ZVR5cGUiOiJodHRwczovL2V4YW1wbGUuY29tL1Rlc3RSZXN1bHQvdjEiLCJzdWJqZWN0IjpbeyJuYW1lIjoiaW5kZXguZG9ja2VyLmlvL290bXM2MS90ZXN0LWN1c3RvbS1hdHRlc3QiLCJkaWdlc3QiOnsic2hhMjU2IjoiOTIyNTE0NTgwODhjNjM4MDYxY2RhOGZkOGI0MDNiNzZkNjYxYTRkYzZiN2VlNzFiNmFmZmNmMTg3MjU1N2IyYiJ9fV0sInByZWRpY2F0ZSI6eyJwYXNzZWQiOnRydWV9fQ==" }, "body": "eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiaW50b3RvIiwic3BlYyI6eyJjb250ZW50Ijp7Imhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiI1MDVlYWYzNGQzZmQ0YjQwYjNkNjA3MzRiMWYxNGQxNzkyZDRkYzkxYjBiMjY3Nzk5M2ZkMzdiOGQ0OTNhNDY2In0sInBheWxvYWRIYXNoIjp7ImFsZ29yaXRobSI6InNoYTI1NiIsInZhbHVlIjoiZDRjZjI2YWUwZmQ3OWRhMTU2ZjAzYzgwNjY2ZTFjZWFmZTljNjQ4ZWU2MGQwYjM2ODBlNGU4ZTQzZDNiZGNiMCJ9fSwicHVibGljS2V5IjoiTFMwdExTMUNSVWRKVGlCRFJWSlVTVVpKUTBGVVJTMHRMUzB0Q2sxSlNVTndSRU5EUVdsdFowRjNTVUpCWjBsVlQzSnhPSEpsZUVNek1XRjVRbTlyVjJwaFQxZ3lPRkJWWkVWWmQwTm5XVWxMYjFwSmVtb3dSVUYzVFhjS1RucEZWazFDVFVkQk1WVkZRMmhOVFdNeWJHNWpNMUoyWTIxVmRWcEhWakpOVWpSM1NFRlpSRlpSVVVSRmVGWjZZVmRrZW1SSE9YbGFVekZ3WW01U2JBcGpiVEZzV2tkc2FHUkhWWGRJYUdOT1RXcEpkMDlVUlRSTlJFVjRUVlJSTlZkb1kwNU5ha2wzVDFSRk5FMUVSWGxOVkZFMVYycEJRVTFHYTNkRmQxbElDa3R2V2tsNmFqQkRRVkZaU1V0dldrbDZhakJFUVZGalJGRm5RVVZwTW5CTGEwcGtTbmh5UkZweVEzVkVWQ3RLUkU0dk5qTlNVemxJWTNveGVYSTBXVUlLV1ZaWlRHNUpOR2h1Um5GdVFVMTJlRFk0ZUdKcVYxaFhNMDg0YWtWWFNYTm5hMDFIWTBkaEsyZHBXVWxFVmpCaVJtRlBRMEZWWjNkblowWkZUVUUwUndwQk1WVmtSSGRGUWk5M1VVVkJkMGxJWjBSQlZFSm5UbFpJVTFWRlJFUkJTMEpuWjNKQ1owVkdRbEZqUkVGNlFXUkNaMDVXU0ZFMFJVWm5VVlZQU1hWQkNtOUlNbFJ0YTFrdlZVeGtNWE41YldwTk0wdE5iVlp6ZDBoM1dVUldVakJxUWtKbmQwWnZRVlV6T1ZCd2VqRlphMFZhWWpWeFRtcHdTMFpYYVhocE5Ga0tXa1E0ZDBwUldVUldVakJTUVZGSUwwSkNjM2RIV1VWWVl6SkdlbUl5Um5KaFdFcG9UbXBGZUU1RlFtNWlWMFp3WWtNMWFtSXlNSGRMVVZsTFMzZFpRZ3BDUVVkRWRucEJRa0ZSVVdKaFNGSXdZMGhOTmt4NU9XaFpNazUyWkZjMU1HTjVOVzVpTWpsdVlrZFZkVmt5T1hSTlNVZExRbWR2Y2tKblJVVkJaRm8xQ2tGblVVTkNTSGRGWldkQ05FRklXVUZEUjBOVE9FTm9VeTh5YUVZd1pFWnlTalJUWTFKWFkxbHlRbGs1ZDNwcVUySmxZVGhKWjFreVlqTkpRVUZCUjBRS1ZHbGlVRzlCUVVGQ1FVMUJVbnBDUmtGcFFrSlpWWGhHY0VWa2VYWkJZV3REYW00cmIyVlRWbXBZU0VGRFR6VmplVE4zWW1SdVJ6RjViR0VyT0hkSmFBcEJVQzlOTUVWUFFsbFJaVTB3VlVNdlVFSmlaMnRvU0RKeFdUTldXbUpTVUc5V0wxcFRURVJ5WlRkMVZrMUJiMGREUTNGSFUwMDBPVUpCVFVSQk1tdEJDazFIV1VOTlVVUndSMG8wZDNodVRHeE9TMVF4Ykhwd1luZERaRlZCYldObWRFRnZXbTlvY1RZeWVqZE9hMjFVWjFoS2NVVlVPRlprYVUxRFZHRm5UelFLTkVwcllqUkhTVU5OVVVSNk1HeElVM2MxVTFsbll6RktSakpTVURCNVUyTlVTV3hyVVRKSmJ6UlRSemhsWmxwM2RFcDNOemQyYkU1b1FWbDRRazE0TlFwU1dESlVTM2swUVdWUE1EMEtMUzB0TFMxRlRrUWdRMFZTVkVsR1NVTkJWRVV0TFMwdExRbz0ifX0=", "integratedTime": 1663463513, "logID": "c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d", "logIndex": 3527741, "verification": { "inclusionProof": { "checkpoint": "rekor.sigstore.dev - 3904496407287907110\n4163431\nTQBqpG78tgfdUdkAsSE3VMUMySUcNAXGwlYdnWovMjk=\nTimestamp: 1664726649849955098\n\n— rekor.sigstore.dev wNI9ajBFAiEA2fSWL7E94qWhPUcYBULuLtRWe7gFy0yyIConYkLkp6wCIHQ6ObwfZPcn28sCLe9DoDb7mdVXAo/a5qtX5q5N1JIR\n", "hashes": [ "c9758c506f78253208cfb417acf808db1c7f845bc7d84426041be88da141de78", "f1f4310c743e29c84c5d0668eb710d1b6647a11d3b4ecc080105eac8941ed858", "7ad323f32d80ac4800c4d305e97e4082ada7545c7ce18e7e760f2a254fc37545", "7900dfe673792ae932e9003fc6219f7608413ae36e2ea8cd82522324dae23d7d", "f97d5b3cec12ff3607a3b25fa51e36c6fbb13553ec93d5cdc1ff3cdf509e17a6", "19cdf066e43cdde3b8782e2177b02b47891bae7b523238064c12a3e576f359bd", "ae95a5a30dc3f4f4aaa324f7f7d028a61b4c752efc2c5e680e4cb2b4bc2c616d", "5de43cdc0fc444eff6cdec0f8e36ff3dc02cd672fc03e4a06b51cf2022d2e575", "c9509918f560d8d4a7c688a236af36fde5e15e21e1548cf130c98695810970b8", "cdf24d1833eeef52de4c31b84a2f481930e6c2167ca1c9820e5311dc01287fd5", "81e8c50dd54ea567e80fc4427d61d26a9ad16e29cdaea6ac87e69aaed32a54f4", "fe79312498d289bcb6bc0d4226b21cabb71bb260f987bb711b548a5fe1f83b6e", "b06b1025bd35f834f3c39786ef4e554bf06c76f61e497fd1765d0689480d3107", "628304d1503a60c71421b080a8e63f9f412e5434084dbfb66dfdef1a785e2034", "7ae973cb7963f4c8609ee238ba06d9d84f1f73be8aa145279e2110e59c7862a3", "f8b4461eb78dadbd7cffd9a281185b7b1f2dfee61c17688016dc75c40b728eb2", "4e659e217261878d89a86f48b5a2f1a8bf2e47aad22d077ad45d0bcec969c383", "3e7b39f35274294419372997dc29033a0cdb7c1f628a44df62eadcaa9a487e35", "72ffc75e39e3042b31f3ee5e6f6315726d1263c7ccc6465bfca688cc2f612247", "b387d94cf186d8be69e18e15422d3c85a6146d6527a8653c0fa07423d45d2dcb", "6d494b237648126525b08f975c736a55d1f7a64472fcc2782bbc16733c608d7b", "efb36cfc54705d8cd921a621a9389ffa03956b15d68bfabadac2b4853852079b", ], "logIndex": 3527741, "rootHash": "4d006aa46efcb607dd51d900b1213754c50cc9251c3405c6c2561d9d6a2f3239", "treeSize": 4163431, }, "signedEntryTimestamp": "MEUCICqfXqsxO8GiismNP4xykravxTrmGBF550HELnzDBumcAiEAl8FeAWT3Hk7BBx6Dgol/Uzy+GaLP347Djf3jM7dxQSs=", }, } } r = res[ "362f8ecba72f43264c43de72686ceb257f6641ca1d62069f7f891822bca772d166dd88941e31c5f1" ] leaf = hashlib.sha256(b"\x00" + base64.b64decode(r["body"])).hexdigest() proof = r["verification"]["inclusionProof"] index = proof["logIndex"] size = proof["treeSize"] inner = len(bin(index ^ (size - 1)).lstrip("0b")) border = len(bin(index >> inner).lstrip("0b")) def hash_children(l, r): return hashlib.sha256(b"\x01" + bytes.fromhex(l) + bytes.fromhex(r)).hexdigest() seed = leaf for i, h in enumerate(proof["hashes"][:inner]): if (index >> i) & 1 == 0: seed = hash_children(seed, h) else: seed = hash_children(h, seed) for i, h in enumerate(proof["hashes"][inner:]): seed = hash_children(h, seed) print(seed == proof["rootHash"]) """ > python x.py True """
verification について - signedEntryTimestamp
verification.signedEntryTimestamp
を見ていきます。
"signedEntryTimestamp": "MEYCIQCLfobPJEVNQ7//arswv62/R1PgQMAelCl5zHa+tr0dFwIhAMGNKi77UKqKrFeTeFlaLQzPs5pTwFf6+uHCj56fn1TB",
これはレスポンスに含まれる integratedTime
, logIndex
, body
, logID
をrekorが署名した値になっています。
検証に使うための鍵はAPI経由で取得することができます。
> curl --request GET --url https://rekor.sigstore.dev/api/v1/log/publicKey -----BEGIN PUBLIC KEY----- MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE2G2Y+2tabdTV5BcGiBIx0a9fAFwr kBbmLSGtks4L3qX6yYY0zufBnhC8Ur/iy55GhWP/9A/bY2LhC30M9+RYtw== -----END PUBLIC KEY-----
データは JSON Canonicalization Scheme という、jsonの形式が一意に定まるようにフォーマットを使っています。 datatracker.ietf.org
import base64 import hashlib import ecdsa from ecdsa.util import sigdecode_der import canonicaljson res = { "4c43de72686ceb257f6641ca1d62069f7f891822bca772d166dd88941e31c5f1": { "attestation": { "data": "eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInByZWRpY2F0ZVR5cGUiOiJodHRwczovL2V4YW1wbGUuY29tL1Rlc3RSZXN1bHQvdjEiLCJzdWJqZWN0IjpbeyJuYW1lIjoiaW5kZXguZG9ja2VyLmlvL290bXM2MS90ZXN0LWN1c3RvbS1hdHRlc3QiLCJkaWdlc3QiOnsic2hhMjU2IjoiOTIyNTE0NTgwODhjNjM4MDYxY2RhOGZkOGI0MDNiNzZkNjYxYTRkYzZiN2VlNzFiNmFmZmNmMTg3MjU1N2IyYiJ9fV0sInByZWRpY2F0ZSI6eyJwYXNzZWQiOnRydWV9fQ==" }, "body": "eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiaW50b3RvIiwic3BlYyI6eyJjb250ZW50Ijp7Imhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiI1MDVlYWYzNGQzZmQ0YjQwYjNkNjA3MzRiMWYxNGQxNzkyZDRkYzkxYjBiMjY3Nzk5M2ZkMzdiOGQ0OTNhNDY2In0sInBheWxvYWRIYXNoIjp7ImFsZ29yaXRobSI6InNoYTI1NiIsInZhbHVlIjoiZDRjZjI2YWUwZmQ3OWRhMTU2ZjAzYzgwNjY2ZTFjZWFmZTljNjQ4ZWU2MGQwYjM2ODBlNGU4ZTQzZDNiZGNiMCJ9fSwicHVibGljS2V5IjoiTFMwdExTMUNSVWRKVGlCRFJWSlVTVVpKUTBGVVJTMHRMUzB0Q2sxSlNVTndSRU5EUVdsdFowRjNTVUpCWjBsVlQzSnhPSEpsZUVNek1XRjVRbTlyVjJwaFQxZ3lPRkJWWkVWWmQwTm5XVWxMYjFwSmVtb3dSVUYzVFhjS1RucEZWazFDVFVkQk1WVkZRMmhOVFdNeWJHNWpNMUoyWTIxVmRWcEhWakpOVWpSM1NFRlpSRlpSVVVSRmVGWjZZVmRrZW1SSE9YbGFVekZ3WW01U2JBcGpiVEZzV2tkc2FHUkhWWGRJYUdOT1RXcEpkMDlVUlRSTlJFVjRUVlJSTlZkb1kwNU5ha2wzVDFSRk5FMUVSWGxOVkZFMVYycEJRVTFHYTNkRmQxbElDa3R2V2tsNmFqQkRRVkZaU1V0dldrbDZhakJFUVZGalJGRm5RVVZwTW5CTGEwcGtTbmh5UkZweVEzVkVWQ3RLUkU0dk5qTlNVemxJWTNveGVYSTBXVUlLV1ZaWlRHNUpOR2h1Um5GdVFVMTJlRFk0ZUdKcVYxaFhNMDg0YWtWWFNYTm5hMDFIWTBkaEsyZHBXVWxFVmpCaVJtRlBRMEZWWjNkblowWkZUVUUwUndwQk1WVmtSSGRGUWk5M1VVVkJkMGxJWjBSQlZFSm5UbFpJVTFWRlJFUkJTMEpuWjNKQ1owVkdRbEZqUkVGNlFXUkNaMDVXU0ZFMFJVWm5VVlZQU1hWQkNtOUlNbFJ0YTFrdlZVeGtNWE41YldwTk0wdE5iVlp6ZDBoM1dVUldVakJxUWtKbmQwWnZRVlV6T1ZCd2VqRlphMFZhWWpWeFRtcHdTMFpYYVhocE5Ga0tXa1E0ZDBwUldVUldVakJTUVZGSUwwSkNjM2RIV1VWWVl6SkdlbUl5Um5KaFdFcG9UbXBGZUU1RlFtNWlWMFp3WWtNMWFtSXlNSGRMVVZsTFMzZFpRZ3BDUVVkRWRucEJRa0ZSVVdKaFNGSXdZMGhOTmt4NU9XaFpNazUyWkZjMU1HTjVOVzVpTWpsdVlrZFZkVmt5T1hSTlNVZExRbWR2Y2tKblJVVkJaRm8xQ2tGblVVTkNTSGRGWldkQ05FRklXVUZEUjBOVE9FTm9VeTh5YUVZd1pFWnlTalJUWTFKWFkxbHlRbGs1ZDNwcVUySmxZVGhKWjFreVlqTkpRVUZCUjBRS1ZHbGlVRzlCUVVGQ1FVMUJVbnBDUmtGcFFrSlpWWGhHY0VWa2VYWkJZV3REYW00cmIyVlRWbXBZU0VGRFR6VmplVE4zWW1SdVJ6RjViR0VyT0hkSmFBcEJVQzlOTUVWUFFsbFJaVTB3VlVNdlVFSmlaMnRvU0RKeFdUTldXbUpTVUc5V0wxcFRURVJ5WlRkMVZrMUJiMGREUTNGSFUwMDBPVUpCVFVSQk1tdEJDazFIV1VOTlVVUndSMG8wZDNodVRHeE9TMVF4Ykhwd1luZERaRlZCYldObWRFRnZXbTlvY1RZeWVqZE9hMjFVWjFoS2NVVlVPRlprYVUxRFZHRm5UelFLTkVwcllqUkhTVU5OVVVSNk1HeElVM2MxVTFsbll6RktSakpTVURCNVUyTlVTV3hyVVRKSmJ6UlRSemhsWmxwM2RFcDNOemQyYkU1b1FWbDRRazE0TlFwU1dESlVTM2swUVdWUE1EMEtMUzB0TFMxRlRrUWdRMFZTVkVsR1NVTkJWRVV0TFMwdExRbz0ifX0=", "integratedTime": 1663463513, "logID": "c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d", "logIndex": 3527741, "verification": { "inclusionProof": { "hashes": [ "c9758c506f78253208cfb417acf808db1c7f845bc7d84426041be88da141de78", "f1f4310c743e29c84c5d0668eb710d1b6647a11d3b4ecc080105eac8941ed858", "7ad323f32d80ac4800c4d305e97e4082ada7545c7ce18e7e760f2a254fc37545", "7900dfe673792ae932e9003fc6219f7608413ae36e2ea8cd82522324dae23d7d", "f97d5b3cec12ff3607a3b25fa51e36c6fbb13553ec93d5cdc1ff3cdf509e17a6", "19cdf066e43cdde3b8782e2177b02b47891bae7b523238064c12a3e576f359bd", "ae95a5a30dc3f4f4aaa324f7f7d028a61b4c752efc2c5e680e4cb2b4bc2c616d", "5de43cdc0fc444eff6cdec0f8e36ff3dc02cd672fc03e4a06b51cf2022d2e575", "c9509918f560d8d4a7c688a236af36fde5e15e21e1548cf130c98695810970b8", "cdf24d1833eeef52de4c31b84a2f481930e6c2167ca1c9820e5311dc01287fd5", "81e8c50dd54ea567e80fc4427d61d26a9ad16e29cdaea6ac87e69aaed32a54f4", "fe79312498d289bcb6bc0d4226b21cabb71bb260f987bb711b548a5fe1f83b6e", "b06b1025bd35f834f3c39786ef4e554bf06c76f61e497fd1765d0689480d3107", "bca0b68bd01bcbedb77d5ef7760d79854383eb1359263884cee78eb7955766f9", "7ae973cb7963f4c8609ee238ba06d9d84f1f73be8aa145279e2110e59c7862a3", "f8b4461eb78dadbd7cffd9a281185b7b1f2dfee61c17688016dc75c40b728eb2", "4e659e217261878d89a86f48b5a2f1a8bf2e47aad22d077ad45d0bcec969c383", "72ffc75e39e3042b31f3ee5e6f6315726d1263c7ccc6465bfca688cc2f612247", "6d494b237648126525b08f975c736a55d1f7a64472fcc2782bbc16733c608d7b", "efb36cfc54705d8cd921a621a9389ffa03956b15d68bfabadac2b4853852079b", ], "logIndex": 3527741, "rootHash": "5f1fbe1b5cb5b9daff35778d78ef02a2f11584539a59c62835afac3ef3198da1", "treeSize": 3531728, }, "signedEntryTimestamp": "MEYCIQCLfobPJEVNQ7//arswv62/R1PgQMAelCl5zHa+tr0dFwIhAMGNKi77UKqKrFeTeFlaLQzPs5pTwFf6+uHCj56fn1TB", }, } } r = res["4c43de72686ceb257f6641ca1d62069f7f891822bca772d166dd88941e31c5f1"] pub = """-----BEGIN PUBLIC KEY----- MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE2G2Y+2tabdTV5BcGiBIx0a9fAFwr kBbmLSGtks4L3qX6yYY0zufBnhC8Ur/iy55GhWP/9A/bY2LhC30M9+RYtw== -----END PUBLIC KEY-----""" data = canonicaljson.encode_canonical_json( { "integratedTime": r["integratedTime"], "logIndex": r["logIndex"], "body": r["body"], "logID": r["logID"], } ) v = ecdsa.VerifyingKey.from_pem(pub) print( v.verify( signature=base64.b64decode(r["verification"]["signedEntryTimestamp"]), data=data, hashfunc=hashlib.sha256, sigdecode=sigdecode_der, ) ) """ > python d.py True """
verification.signedEntryTimestamp
の検証には integratedTime
, logIndex
, body
, logID
を使っているため、attestationのデータの attestation.data
は直接含まれていません。ただし、spec.content.payloadHash
には attestation.data
をデコードした値のsha256が入っています。そのため、verification.signedEntryTimestamp
の検証後に、spec.content.payloadHash
を確認することで、attestation.data
が改ざんされていないことがわかります。
最後に
ひとつのコマンドでやり取りされる内容をまとめていたらだいぶ長くなってしまった。リクエスト内でhashやsignatureなどのキーで値がやり取りされているが、一体どこの値のハッシュを取ったのかや、どこの値をどこの鍵でsignatureを作っているのかなど曖昧な点が多かった。今回記事を書きながら手を動かせたことで、だいぶ理解が深まったと思う。
sigstore関連の話題は、関連する技術も同時に知ることができて結構楽しい。SCTの検証とinclusionProofは調べきれなかったのは心残りではある。