From 179b53c4a090f24fe22829493a7165194ded981f Mon Sep 17 00:00:00 2001 From: Jussi Kukkonen Date: Mon, 9 Jun 2025 12:10:49 +0300 Subject: [PATCH 1/5] trust: Start returning RekorV2Client from signingconfig If signingconfig contains rekor v2, let's start preferring it Make sure we test the status quo (no rekor v2 in signing config) and the case where there is a rekor v2 in signing config. Signed-off-by: Jussi Kukkonen --- sigstore/_internal/trust.py | 16 ++++-- .../signingconfig-only-v1-rekor.v2.json | 53 +++++++++++++++++++ test/unit/internal/test_trust.py | 16 +++++- 3 files changed, 80 insertions(+), 5 deletions(-) create mode 100644 test/assets/signing_config/signingconfig-only-v1-rekor.v2.json diff --git a/sigstore/_internal/trust.py b/sigstore/_internal/trust.py index 8179e9770..6e8298d64 100644 --- a/sigstore/_internal/trust.py +++ b/sigstore/_internal/trust.py @@ -60,7 +60,9 @@ ) from sigstore._internal.fulcio.client import FulcioClient +from sigstore._internal.rekor import RekorLogSubmitter from sigstore._internal.rekor.client import RekorClient +from sigstore._internal.rekor.client_v2 import RekorV2Client from sigstore._internal.timestamp import TimestampAuthorityClient from sigstore._internal.tuf import DEFAULT_TUF_URL, STAGING_TUF_URL, TrustUpdater from sigstore._utils import ( @@ -73,7 +75,7 @@ from sigstore.errors import Error, MetadataError, TUFError, VerificationError # Versions supported by this client -REKOR_VERSIONS = [1] +REKOR_VERSIONS = [1, 2] TSA_VERSIONS = [1] FULCIO_VERSIONS = [1] OIDC_VERSIONS = [1] @@ -420,11 +422,19 @@ def _get_valid_services( return result[:count] - def get_tlogs(self) -> list[RekorClient]: + def get_tlogs(self) -> list[RekorLogSubmitter]: """ Returns the rekor transparency log clients to sign with. """ - return [RekorClient(tlog.url) for tlog in self._tlogs] + result: list[RekorLogSubmitter] = [] + for tlog in self._tlogs: + if tlog.major_api_version == 1: + result.append(RekorClient(tlog.url)) + elif tlog.major_api_version == 2: + result.append(RekorV2Client(tlog.url)) + else: + raise AssertionError(f"Unexpected Rekor v{tlog.major_api_version}") + return result def get_fulcio(self) -> FulcioClient: """ diff --git a/test/assets/signing_config/signingconfig-only-v1-rekor.v2.json b/test/assets/signing_config/signingconfig-only-v1-rekor.v2.json new file mode 100644 index 000000000..1a4305259 --- /dev/null +++ b/test/assets/signing_config/signingconfig-only-v1-rekor.v2.json @@ -0,0 +1,53 @@ +{ + "mediaType": "application/vnd.dev.sigstore.signingconfig.v0.2+json", + "caUrls": [ + { + "url": "https://fulcio.example.com", + "majorApiVersion": 1, + "validFor": { + "start": "2023-04-14T21:38:40Z" + } + }, + { + "url": "https://fulcio-old.example.com", + "majorApiVersion": 1, + "validFor": { + "start": "2022-04-14T21:38:40Z", + "end": "2023-04-14T21:38:40Z" + } + } + ], + "oidcUrls": [ + { + "url": "https://oauth2.example.com/auth", + "majorApiVersion": 1, + "validFor": { + "start": "2025-04-16T00:00:00Z" + } + } + ], + "rekorTlogUrls": [ + { + "url": "https://rekor.example.com", + "majorApiVersion": 1, + "validFor": { + "start": "2021-01-12T11:53:27Z" + } + } + ], + "tsaUrls": [ + { + "url": "https://timestamp.example.com/api/v1/timestamp", + "majorApiVersion": 1, + "validFor": { + "start": "2025-04-09T00:00:00Z" + } + } + ], + "rekorTlogConfig": { + "selector": "ANY" + }, + "tsaConfig": { + "selector": "ANY" + } +} diff --git a/test/unit/internal/test_trust.py b/test/unit/internal/test_trust.py index 14b00624f..4eef7f68c 100644 --- a/test/unit/internal/test_trust.py +++ b/test/unit/internal/test_trust.py @@ -28,6 +28,7 @@ from sigstore._internal.fulcio.client import FulcioClient from sigstore._internal.rekor.client import RekorClient +from sigstore._internal.rekor.client_v2 import RekorV2Client from sigstore._internal.timestamp import TimestampAuthorityClient from sigstore._internal.trust import ( CertificateAuthority, @@ -83,16 +84,27 @@ def test_good(self, asset): assert fulcio.url == "https://fulcio.example.com" assert signing_config.get_oidc_url() == "https://oauth2.example.com/auth" + # signing config contains v1 and v2, we pick v2 tlogs = signing_config.get_tlogs() assert len(tlogs) == 1 - assert isinstance(tlogs[0], RekorClient) - assert tlogs[0].url == "https://rekor.example.com/api/v1" + assert isinstance(tlogs[0], RekorV2Client) + assert tlogs[0].url == "https://rekor-v2.example.com/api/v2" tsas = signing_config.get_tsas() assert len(tsas) == 1 assert isinstance(tsas[0], TimestampAuthorityClient) assert tsas[0].url == "https://timestamp.example.com/api/v1/timestamp" + def test_good_only_v1_rekor(self, asset): + """Test case where a rekor 2 instance is not available""" + path = asset("signing_config/signingconfig-only-v1-rekor.v2.json") + signing_config = SigningConfig.from_file(path) + + tlogs = signing_config.get_tlogs() + assert len(tlogs) == 1 + assert isinstance(tlogs[0], RekorClient) + assert tlogs[0].url == "https://rekor.example.com/api/v1" + @pytest.mark.parametrize( "services, versions, config, expected_result", [ From 2c8e5a5b3eada379b8937ff19919aa24512e3f44 Mon Sep 17 00:00:00 2001 From: Jussi Kukkonen Date: Mon, 9 Jun 2025 12:23:00 +0300 Subject: [PATCH 2/5] test: Add trust config for signing with staging rekor v2 This is current staging trust root and signing config, with just the rekor v2 instance added to signing config $ TRUSTCONFIG=test/assets/trust_config/staging-but-sign-with-rekor-v2.json $ sigstore --trust-config $TRUSTCONFIG sign README.md Signed-off-by: Jussi Kukkonen --- .../staging-but-sign-with-rekor-v2.json | 183 ++++++++++++++++++ 1 file changed, 183 insertions(+) create mode 100644 test/assets/trust_config/staging-but-sign-with-rekor-v2.json diff --git a/test/assets/trust_config/staging-but-sign-with-rekor-v2.json b/test/assets/trust_config/staging-but-sign-with-rekor-v2.json new file mode 100644 index 000000000..3c1dd3fa0 --- /dev/null +++ b/test/assets/trust_config/staging-but-sign-with-rekor-v2.json @@ -0,0 +1,183 @@ +{ + "mediaType": "application/vnd.dev.sigstore.clienttrustconfig.v0.1+json", + "trustedRoot": { + "mediaType": "application/vnd.dev.sigstore.trustedroot+json;version=0.1", + "tlogs": [ + { + "baseUrl": "https://rekor.sigstage.dev", + "hashAlgorithm": "SHA2_256", + "publicKey": { + "rawBytes": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEDODRU688UYGuy54mNUlaEBiQdTE9nYLr0lg6RXowI/QV/RE1azBn4Eg5/2uTOMbhB1/gfcHzijzFi9Tk+g1Prg==", + "keyDetails": "PKIX_ECDSA_P256_SHA_256", + "validFor": { + "start": "2021-01-12T11:53:27Z" + } + }, + "logId": { + "keyId": "0y8wo8MtY5wrdiIFohx7sHeI5oKDpK5vQhGHI6G+pJY=" + } + }, + { + "baseUrl": "https://log2025-alpha1.rekor.sigstage.dev", + "hashAlgorithm": "SHA2_256", + "publicKey": { + "rawBytes": "MCowBQYDK2VwAyEAPn+AREHoBaZ7wgS1zBqpxmLSGnyhxXj4lFxSdWVB8o8=", + "keyDetails": "PKIX_ED25519", + "validFor": { + "start": "2025-04-16T00:00:00Z" + } + }, + "logId": { + "keyId": "RycrnT/11WQ15JtgBXeYVLlFYMtbAka7+JnxUQaOX5E=" + } + } + ], + "certificateAuthorities": [ + { + "subject": { + "organization": "sigstore.dev", + "commonName": "sigstore" + }, + "uri": "https://fulcio.sigstage.dev", + "certChain": { + "certificates": [ + { + "rawBytes": "MIICGTCCAaCgAwIBAgITJta/okfgHvjabGm1BOzuhrwA1TAKBggqhkjOPQQDAzAqMRUwEwYDVQQKEwxzaWdzdG9yZS5kZXYxETAPBgNVBAMTCHNpZ3N0b3JlMB4XDTIyMDQxNDIxMzg0MFoXDTMyMDMyMjE2NTA0NVowNzEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRlcm1lZGlhdGUwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAASosAySWJQ/tK5r8T5aHqavk0oI+BKQbnLLdmOMRXHQF/4Hx9KtNfpcdjH9hNKQSBxSlLFFN3tvFCco0qFBzWYwZtsYsBe1l91qYn/9VHFTaEVwYQWIJEEvrs0fvPuAqjajezB5MA4GA1UdDwEB/wQEAwIBBjATBgNVHSUEDDAKBggrBgEFBQcDAzASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQWBBRxhjCmFHxib/n31vQFGn9f/+tvrDAfBgNVHSMEGDAWgBT/QjK6aH2rOnCv3AzUGuI+h49mZTAKBggqhkjOPQQDAwNnADBkAjAM1lbKkcqQlE/UspMTbWNo1y2TaJ44tx3l/FJFceTSdDZ+0W1OHHeU4twie/lq8XgCMHQxgEv26xNNiAGyPXbkYgrDPvbOqp0UeWX4mJnLSrBr3aN/KX1SBrKQu220FmVL0Q==" + }, + { + "rawBytes": "MIIB9jCCAXugAwIBAgITDdEJvluliE0AzYaIE4jTMdnFTzAKBggqhkjOPQQDAzAqMRUwEwYDVQQKEwxzaWdzdG9yZS5kZXYxETAPBgNVBAMTCHNpZ3N0b3JlMB4XDTIyMDMyNTE2NTA0NloXDTMyMDMyMjE2NTA0NVowKjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTB2MBAGByqGSM49AgEGBSuBBAAiA2IABMo9BUNk9QIYisYysC24+2OytoV72YiLonYcqR3yeVnYziPt7Xv++CYE8yoCTiwedUECCWKOcvQKRCJZb9ht4Hzy+VvBx36hK+C6sECCSR0x6pPSiz+cTk1f788ZjBlUZaNjMGEwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFP9CMrpofas6cK/cDNQa4j6Hj2ZlMB8GA1UdIwQYMBaAFP9CMrpofas6cK/cDNQa4j6Hj2ZlMAoGCCqGSM49BAMDA2kAMGYCMQD+kojuzMwztNay9Ibzjuk//ZL5m6T2OCsm45l1lY004pcb984L926BowodoirFMcMCMQDIJtFHhP/1D3a+M3dAGomOb6O4CmTry3TTPbPsAFnv22YA0Y+P21NVoxKDjdu0tkw=" + } + ] + }, + "validFor": { + "start": "2022-04-14T21:38:40Z" + } + } + ], + "ctlogs": [ + { + "baseUrl": "https://ctfe.sigstage.dev/test", + "hashAlgorithm": "SHA2_256", + "publicKey": { + "rawBytes": "MIICCgKCAgEA27A2MPQXm0I0v7/Ly5BIauDjRZF5Jor9vU+QheoE2UIIsZHcyYq3slHzSSHy2lLj1ZD2d91CtJ492ZXqnBmsr4TwZ9jQ05tW2mGIRI8u2DqN8LpuNYZGz/f9SZrjhQQmUttqWmtu3UoLfKz6NbNXUnoo+NhZFcFRLXJ8VporVhuiAmL7zqT53cXR3yQfFPCUDeGnRksnlhVIAJc3AHZZSHQJ8DEXMhh35TVv2nYhTI3rID7GwjXXw4ocz7RGDD37ky6p39Tl5NB71gT1eSqhZhGHEYHIPXraEBd5+3w9qIuLWlp5Ej/K6Mu4ELioXKCUimCbwy+Cs8UhHFlqcyg4AysOHJwIadXIa8LsY51jnVSGrGOEBZevopmQPNPtyfFY3dmXSS+6Z3RD2Gd6oDnNGJzpSyEk410Ag5uvNDfYzJLCWX9tU8lIxNwdFYmIwpd89HijyRyoGnoJ3entd63cvKfuuix5r+GHyKp1Xm1L5j5AWM6P+z0xigwkiXnt+adexAl1J9wdDxv/pUFEESRF4DG8DFGVtbdH6aR1A5/vD4krO4tC1QYUSeyL5Mvsw8WRqIFHcXtgybtxylljvNcGMV1KXQC8UFDmpGZVDSHx6v3e/BHMrZ7gjoCCfVMZ/cFcQi0W2AIHPYEMH/C95J2r4XbHMRdYXpovpOoT5Ca78gsCAwEAAQ==", + "keyDetails": "PKCS1_RSA_PKCS1V5", + "validFor": { + "start": "2021-03-14T00:00:00Z", + "end": "2022-07-31T00:00:00Z" + } + }, + "logId": { + "keyId": "G3wUKk6ZK6ffHh/FdCRUE2wVekyzHEEIpSG4savnv0w=" + } + }, + { + "baseUrl": "https://ctfe.sigstage.dev/2022", + "hashAlgorithm": "SHA2_256", + "publicKey": { + "rawBytes": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEh99xuRi6slBFd8VUJoK/rLigy4bYeSYWO/fE6Br7r0D8NpMI94+A63LR/WvLxpUUGBpY8IJA3iU2telag5CRpA==", + "keyDetails": "PKIX_ECDSA_P256_SHA_256", + "validFor": { + "start": "2022-07-01T00:00:00Z", + "end": "2022-07-31T00:00:00Z" + } + }, + "logId": { + "keyId": "++JKOMQt7SJ3ynUHnCfnDhcKP8/58J4TueMqXuk3HmA=" + } + }, + { + "baseUrl": "https://ctfe.sigstage.dev/2022-2", + "hashAlgorithm": "SHA2_256", + "publicKey": { + "rawBytes": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE8gEDKNme8AnXuPBgHjrtXdS6miHqc24CRblNEOFpiJRngeq8Ko73Y+K18yRYVf1DXD4AVLwvKyzdNdl5n0jUSQ==", + "keyDetails": "PKIX_ECDSA_P256_SHA_256", + "validFor": { + "start": "2022-07-01T00:00:00Z" + } + }, + "logId": { + "keyId": "KzC83GiIyeLh2CYpXnQfSDkxlgLynDPLXkNA/rKshno=" + } + } + ], + "timestampAuthorities": [ + { + "subject": { + "organization": "sigstore.dev", + "commonName": "sigstore-tsa-selfsigned" + }, + "uri": "https://timestamp.sigstage.dev/api/v1/timestamp", + "certChain": { + "certificates": [ + { + "rawBytes": "MIICDzCCAZagAwIBAgIUCjWhBmHV4kFzxomWp/J98n4DfKcwCgYIKoZIzj0EAwMwOTEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MSAwHgYDVQQDExdzaWdzdG9yZS10c2Etc2VsZnNpZ25lZDAeFw0yNTAzMjgwOTE0MDZaFw0zNTAzMjYwODE0MDZaMC4xFTATBgNVBAoTDHNpZ3N0b3JlLmRldjEVMBMGA1UEAxMMc2lnc3RvcmUtdHNhMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEx1v5F3HpD9egHuknpBFlRz7QBRDJu4aeVzt9zJLRY0lvmx1lF7WBM2c9AN8ZGPQsmDqHlJN2R/7+RxLkvlLzkc19IOx38t7mGGEcB7agUDdCF/Ky3RTLSK0Xo/0AgHQdo2owaDAOBgNVHQ8BAf8EBAMCB4AwHQYDVR0OBBYEFKj8ZPYo3i7mO3NPVIxSxOGc3VOlMB8GA1UdIwQYMBaAFDsgRlletTJNRzDObmPuc3RH8gR9MBYGA1UdJQEB/wQMMAoGCCsGAQUFBwMIMAoGCCqGSM49BAMDA2cAMGQCMESvVS6GGtF33+J19TfwENWJXjRv4i0/HQFwLUSkX6TfV7g0nG8VnqNHJLvEpAtOjQIwUD3uywTXorQP1DgbV09rF9Yen+CEqs/iEpieJWPst280SSOZ5Na+dyPVk9/8SFk6" + }, + { + "rawBytes": "MIIB9zCCAXygAwIBAgIUCPExEFKiQh0dP4sp5ltmSYSSkFUwCgYIKoZIzj0EAwMwOTEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MSAwHgYDVQQDExdzaWdzdG9yZS10c2Etc2VsZnNpZ25lZDAeFw0yNTAzMjgwOTE0MDZaFw0zNTAzMjYwODE0MDZaMDkxFTATBgNVBAoTDHNpZ3N0b3JlLmRldjEgMB4GA1UEAxMXc2lnc3RvcmUtdHNhLXNlbGZzaWduZWQwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAATt0tIDWyo4ARfL9BaSo0W5bJQEbKJTU/u7llvdjSI5aTkOAJa8tixn2+LEfPG4dMFdsMPtsIuU1qn2OqFiuMk6vHv/c+az25RQVY1oo50iMb0jIL3N4FgwhPFpZnCbQPOjRTBDMA4GA1UdDwEB/wQEAwIBBjASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQWBBQ7IEZZXrUyTUcwzm5j7nN0R/IEfTAKBggqhkjOPQQDAwNpADBmAjEA2MI1VXgbf3dUOSc95hSRypBKOab18eh2xzQtxUsHvWeY+1iFgyMluUuNR6taoSmFAjEA31m2czguZhKYX+4JSKu5pRYhBTXAd8KKQ3xdPRX/qCaLvT2qJAEQ1YQM3EJRrtI7" + } + ] + }, + "validFor": { + "start": "2025-04-09T00:00:00Z" + } + } + ] + }, + "signing_config": { + "mediaType": "application/vnd.dev.sigstore.signingconfig.v0.2+json", + "caUrls": [ + { + "url": "https://fulcio.sigstage.dev", + "majorApiVersion": 1, + "validFor": { + "start": "2022-04-14T21:38:40Z" + }, + "operator": "sigstore.dev" + } + ], + "oidcUrls": [ + { + "url": "https://oauth2.sigstage.dev/auth", + "majorApiVersion": 1, + "validFor": { + "start": "2025-04-16T00:00:00Z" + }, + "operator": "sigstore.dev" + } + ], + "rekorTlogUrls": [ + { + "url": "https://log2025-alpha1.rekor.sigstage.dev", + "majorApiVersion": 2, + "validFor": { + "start": "2025-06-09T00:00:00Z" + }, + "operator": "sigstore.dev" + }, + { + "url": "https://rekor.sigstage.dev", + "majorApiVersion": 1, + "validFor": { + "start": "2021-01-12T11:53:27Z" + }, + "operator": "sigstore.dev" + } + ], + "tsaUrls": [ + { + "url": "https://timestamp.sigstage.dev/api/v1/timestamp", + "majorApiVersion": 1, + "validFor": { + "start": "2025-04-09T00:00:00Z" + }, + "operator": "sigstore.dev" + } + ], + "rekorTlogConfig": { + "selector": "ANY" + }, + "tsaConfig": { + "selector": "ANY" + } + } +} \ No newline at end of file From c5a2b15e8e8b48ff19964a8d327a055ed305f000 Mon Sep 17 00:00:00 2001 From: Jussi Kukkonen Date: Mon, 9 Jun 2025 13:23:43 +0300 Subject: [PATCH 3/5] verify: Initial support for rekor v2 verify This code is originally from Ramon, updated by Jussi $ TRUSTCONFIG=test/assets/trust_config/staging-but-sign-with-rekor-v2.json $ sigstore --trust-config $TRUSTCONFIG sign README.md $ sigstore --staging verify identity \ --cert-identity jku@goto.fi \ --cert-oidc-issuer https://github.com/login/oauth README.md OK: README.md Co-authored-by: Ramon Petgrave Signed-off-by: Jussi Kukkonen --- sigstore/verify/verifier.py | 161 ++++++++++++++++++++++++++++-------- 1 file changed, 126 insertions(+), 35 deletions(-) diff --git a/sigstore/verify/verifier.py b/sigstore/verify/verifier.py index 56d132de2..25500a265 100644 --- a/sigstore/verify/verifier.py +++ b/sigstore/verify/verifier.py @@ -25,8 +25,10 @@ import rekor_types from cryptography.exceptions import InvalidSignature +from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives.asymmetric import ec -from cryptography.x509 import ExtendedKeyUsage, KeyUsage +from cryptography.hazmat.primitives.asymmetric.ec import EllipticCurvePublicKey +from cryptography.x509 import Certificate, ExtendedKeyUsage, KeyUsage from cryptography.x509.oid import ExtendedKeyUsageOID from OpenSSL.crypto import ( X509, @@ -38,6 +40,8 @@ from pydantic import ValidationError from rfc3161_client import TimeStampResponse, VerifierBuilder from rfc3161_client import VerificationError as Rfc3161VerificationError +from sigstore_protobuf_specs.dev.sigstore.common import v1 +from sigstore_protobuf_specs.dev.sigstore.rekor import v2 from sigstore import dsse from sigstore._internal.rekor import _hashedrekord_from_parts @@ -371,6 +375,20 @@ def _verify_common_signing_cert( f"invalid signing cert: expired at time of signing, time via {vts}" ) + @staticmethod + def _get_key_details(certificate: Certificate) -> v1.PublicKeyDetails: + """Determine PublicKeyDetails from a certificate""" + public_key = certificate.public_key() + if isinstance(public_key, EllipticCurvePublicKey): + if public_key.curve.name == "secp256r1": + return cast( + v1.PublicKeyDetails, + v1.PublicKeyDetails.PKIX_ECDSA_P256_SHA_256, + ) + # TODO support other keys + raise ValueError(f"Unsupported EC curve: {public_key.curve.name}") + raise ValueError(f"Unsupported public key type: {type(public_key)}") + def verify_dsse( self, bundle: Bundle, policy: VerificationPolicy ) -> tuple[str, bytes]: @@ -418,34 +436,74 @@ def verify_dsse( # Instead, we manually pick apart the entry body below and verify # the parts we can (namely the payload hash and signature list). entry = bundle.log_entry - try: - entry_body = rekor_types.Dsse.model_validate_json( - base64.b64decode(entry.body) - ) - except ValidationError as exc: - raise VerificationError(f"invalid DSSE log entry: {exc}") - - payload_hash = sha256_digest(envelope._inner.payload).digest.hex() if ( - entry_body.spec.root.payload_hash.algorithm # type: ignore[union-attr] - != rekor_types.dsse.Algorithm.SHA256 + entry._kind_version.kind == "dsse" + and entry._kind_version.version == "0.0.2" ): - raise VerificationError("expected SHA256 payload hash in DSSE log entry") - if payload_hash != entry_body.spec.root.payload_hash.value: # type: ignore[union-attr] - raise VerificationError("log entry payload hash does not match bundle") - - # NOTE: Like `dsse._verify`: multiple signatures would be frivolous here, - # but we handle them just in case the signer has somehow produced multiple - # signatures for their envelope with the same signing key. - signatures = [ - rekor_types.dsse.Signature( - signature=base64.b64encode(signature.sig).decode(), - verifier=base64_encode_pem_cert(bundle.signing_certificate), - ) - for signature in envelope._inner.signatures - ] - if signatures != entry_body.spec.root.signatures: - raise VerificationError("log entry signatures do not match bundle") + try: + entry_body = v2.Entry().from_json(base64.b64decode(entry.body)) + except ValidationError as exc: + raise VerificationError(f"invalid DSSE log entry: {exc}") + + if ( + entry_body.spec.dsse_v002.payload_hash.algorithm + != v1.HashAlgorithm.SHA2_256 + ): + raise VerificationError("expected SHA256 hash in DSSE entry") + + payload_hash = sha256_digest(envelope._inner.payload).digest + if entry_body.spec.dsse_v002.payload_hash.digest != payload_hash: + raise VerificationError("DSSE entry payload hash does not match bundle") + + signatures = [ + v2.Signature( + content=signature.sig, + verifier=v2.Verifier( + x509_certificate=v1.X509Certificate( + bundle.signing_certificate.public_bytes( + encoding=serialization.Encoding.DER + ) + ), + key_details=self._get_key_details(bundle.signing_certificate), + ), + ) + for signature in envelope._inner.signatures + ] + if signatures != entry_body.spec.dsse_v002.signatures: + raise VerificationError("log entry signatures do not match bundle") + else: + try: + entry_body = rekor_types.Dsse.model_validate_json( + base64.b64decode(entry.body) + ) + except ValidationError as exc: + raise VerificationError(f"invalid DSSE log entry: {exc}") + + payload_hash = sha256_digest(envelope._inner.payload).digest.hex() + if ( + # type: ignore[union-attr] + entry_body.spec.root.payload_hash.algorithm + != rekor_types.dsse.Algorithm.SHA256 + ): + raise VerificationError( + "expected SHA256 payload hash in DSSE log entry" + ) + # type: ignore[union-attr] + if payload_hash != entry_body.spec.root.payload_hash.value: + raise VerificationError("log entry payload hash does not match bundle") + + # NOTE: Like `dsse._verify`: multiple signatures would be frivolous here, + # but we handle them just in case the signer has somehow produced multiple + # signatures for their envelope with the same signing key. + signatures = [ + rekor_types.dsse.Signature( + signature=base64.b64encode(signature.sig).decode(), + verifier=base64_encode_pem_cert(bundle.signing_certificate), + ) + for signature in envelope._inner.signatures + ] + if signatures != entry_body.spec.root.signatures: + raise VerificationError("log entry signatures do not match bundle") return (envelope._inner.payload_type, envelope._inner.payload) @@ -491,14 +549,47 @@ def verify_artifact( # the other bundle materials (and input being verified). entry = bundle.log_entry - expected_body = _hashedrekord_from_parts( - bundle.signing_certificate, - bundle._inner.message_signature.signature, # type: ignore[union-attr] - hashed_input, - ) - actual_body = rekor_types.Hashedrekord.model_validate_json( - base64.b64decode(entry.body) - ) + if ( + entry._kind_version.kind == "hashedrekord" + and entry._kind_version.version == "0.0.2" + ): + expected_body = v2.Entry( + kind=entry._kind_version.kind, + api_version=entry._kind_version.version, + spec=v2.Spec( + hashed_rekord_v002=v2.HashedRekordLogEntryV002( + data=v1.HashOutput( + algorithm=bundle._inner.message_signature.message_digest.algorithm, + digest=bundle._inner.message_signature.message_digest.digest, + ), + signature=v2.Signature( + content=bundle._inner.message_signature.signature, + verifier=v2.Verifier( + x509_certificate=v1.X509Certificate( + bundle.signing_certificate.public_bytes( + encoding=serialization.Encoding.DER + ) + ), + key_details=self._get_key_details( + bundle.signing_certificate + ), + ), + ), + ) + ), + ) + actual_body = v2.Entry().from_json(base64.b64decode(entry.body)) + else: + expected_body = _hashedrekord_from_parts( + bundle.signing_certificate, + # type: ignore[union-attr] + bundle._inner.message_signature.signature, + hashed_input, + ) + actual_body = rekor_types.Hashedrekord.model_validate_json( + base64.b64decode(entry.body) + ) + if expected_body != actual_body: raise VerificationError( "transparency log entry is inconsistent with other materials" From 0287e0a9cea5844a1ea3dc037e9c2e1f00c29100 Mon Sep 17 00:00:00 2001 From: Jussi Kukkonen Date: Mon, 9 Jun 2025 13:44:25 +0300 Subject: [PATCH 4/5] verifier: Fix lint issues This makes the code quite a bit uglier: we will likely want to refactor... Signed-off-by: Jussi Kukkonen --- sigstore/verify/verifier.py | 48 +++++++++++++++++++++++-------------- 1 file changed, 30 insertions(+), 18 deletions(-) diff --git a/sigstore/verify/verifier.py b/sigstore/verify/verifier.py index 25500a265..d885fde8a 100644 --- a/sigstore/verify/verifier.py +++ b/sigstore/verify/verifier.py @@ -441,21 +441,26 @@ def verify_dsse( and entry._kind_version.version == "0.0.2" ): try: - entry_body = v2.Entry().from_json(base64.b64decode(entry.body)) + v2_body = v2.Entry().from_json(base64.b64decode(entry.body)) except ValidationError as exc: raise VerificationError(f"invalid DSSE log entry: {exc}") + if v2_body.spec.dsse_v002 is None: + raise VerificationError( + "invalid DSSE log entry: missing dsse_v002 field" + ) + if ( - entry_body.spec.dsse_v002.payload_hash.algorithm + v2_body.spec.dsse_v002.payload_hash.algorithm != v1.HashAlgorithm.SHA2_256 ): raise VerificationError("expected SHA256 hash in DSSE entry") - payload_hash = sha256_digest(envelope._inner.payload).digest - if entry_body.spec.dsse_v002.payload_hash.digest != payload_hash: + digest = sha256_digest(envelope._inner.payload).digest + if v2_body.spec.dsse_v002.payload_hash.digest != digest: raise VerificationError("DSSE entry payload hash does not match bundle") - signatures = [ + v2_signatures = [ v2.Signature( content=signature.sig, verifier=v2.Verifier( @@ -469,7 +474,7 @@ def verify_dsse( ) for signature in envelope._inner.signatures ] - if signatures != entry_body.spec.dsse_v002.signatures: + if v2_signatures != v2_body.spec.dsse_v002.signatures: raise VerificationError("log entry signatures do not match bundle") else: try: @@ -481,15 +486,13 @@ def verify_dsse( payload_hash = sha256_digest(envelope._inner.payload).digest.hex() if ( - # type: ignore[union-attr] - entry_body.spec.root.payload_hash.algorithm + entry_body.spec.root.payload_hash.algorithm # type: ignore[union-attr] != rekor_types.dsse.Algorithm.SHA256 ): raise VerificationError( "expected SHA256 payload hash in DSSE log entry" ) - # type: ignore[union-attr] - if payload_hash != entry_body.spec.root.payload_hash.value: + if payload_hash != entry_body.spec.root.payload_hash.value: # type: ignore[union-attr] raise VerificationError("log entry payload hash does not match bundle") # NOTE: Like `dsse._verify`: multiple signatures would be frivolous here, @@ -553,7 +556,12 @@ def verify_artifact( entry._kind_version.kind == "hashedrekord" and entry._kind_version.version == "0.0.2" ): - expected_body = v2.Entry( + if bundle._inner.message_signature is None: + raise VerificationError( + "invalid hashedrekord log entry: missing message signature" + ) + + v2_expected_body = v2.Entry( kind=entry._kind_version.kind, api_version=entry._kind_version.version, spec=v2.Spec( @@ -578,19 +586,23 @@ def verify_artifact( ) ), ) - actual_body = v2.Entry().from_json(base64.b64decode(entry.body)) + v2_actual_body = v2.Entry().from_json(base64.b64decode(entry.body)) + if v2_expected_body != v2_actual_body: + raise VerificationError( + "transparency log entry is inconsistent with other materials" + ) + else: expected_body = _hashedrekord_from_parts( bundle.signing_certificate, - # type: ignore[union-attr] - bundle._inner.message_signature.signature, + bundle._inner.message_signature.signature, # type: ignore[union-attr] hashed_input, ) actual_body = rekor_types.Hashedrekord.model_validate_json( base64.b64decode(entry.body) ) - if expected_body != actual_body: - raise VerificationError( - "transparency log entry is inconsistent with other materials" - ) + if expected_body != actual_body: + raise VerificationError( + "transparency log entry is inconsistent with other materials" + ) From 08c89bb1ee8298d721cbddd26e8fa40113e96c46 Mon Sep 17 00:00:00 2001 From: Jussi Kukkonen Date: Mon, 9 Jun 2025 15:14:50 +0300 Subject: [PATCH 5/5] tests: Add tests for signing and verifying rekorv2 bundles These are fairly basic for now. Signed-off-by: Jussi Kukkonen --- test/assets/staging-rekor-v2.txt | 5 ++++ .../assets/staging-rekor-v2.txt.sigstore.json | 1 + test/unit/conftest.py | 29 +++++++++++++++++++ test/unit/test_sign.py | 15 ++++++++++ test/unit/verify/test_verifier.py | 3 +- 5 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 test/assets/staging-rekor-v2.txt create mode 100644 test/assets/staging-rekor-v2.txt.sigstore.json diff --git a/test/assets/staging-rekor-v2.txt b/test/assets/staging-rekor-v2.txt new file mode 100644 index 000000000..1895b2228 --- /dev/null +++ b/test/assets/staging-rekor-v2.txt @@ -0,0 +1,5 @@ +DO NOT MODIFY ME! + +this is "staging-rekor-v2.txt", a sample input for sigstore-python's unit tests. + +DO NOT MODIFY ME! diff --git a/test/assets/staging-rekor-v2.txt.sigstore.json b/test/assets/staging-rekor-v2.txt.sigstore.json new file mode 100644 index 000000000..80c325534 --- /dev/null +++ b/test/assets/staging-rekor-v2.txt.sigstore.json @@ -0,0 +1 @@ +{"mediaType": "application/vnd.dev.sigstore.bundle.v0.3+json", "verificationMaterial": {"certificate": {"rawBytes": "MIICyzCCAlCgAwIBAgIUJc/6ox+xb+Cmb5UVrFhdu5jiMzIwCgYIKoZIzj0EAwMwNzEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRlcm1lZGlhdGUwHhcNMjUwNjA5MTE1NzM1WhcNMjUwNjA5MTIwNzM1WjAAMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEvoYb1h6sjlOR276rCjnPc/PgZtTahLzmf32f08PZ/2eWr4q979itVw1PG8IhcK3E2ZiihegXEgh4mPkkMn78BKOCAW8wggFrMA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQUsoZlvpIKgR6WlgezvkD6xzHypcMwHwYDVR0jBBgwFoAUcYYwphR8Ym/599b0BRp/X//rb6wwGQYDVR0RAQH/BA8wDYELamt1QGdvdG8uZmkwLAYKKwYBBAGDvzABAQQeaHR0cHM6Ly9naXRodWIuY29tL2xvZ2luL29hdXRoMC4GCisGAQQBg78wAQgEIAweaHR0cHM6Ly9naXRodWIuY29tL2xvZ2luL29hdXRoMIGKBgorBgEEAdZ5AgQCBHwEegB4AHYAKzC83GiIyeLh2CYpXnQfSDkxlgLynDPLXkNA/rKshnoAAAGXVI2aFgAABAMARzBFAiBDHRpKGTpiU3Nx28XgewlvzbMt/ug6ipN8Xj9tryWbwQIhAP/3Cngo4St1nAggkflowySL0fPYg/QDcJKE6XceON3WMAoGCCqGSM49BAMDA2kAMGYCMQCfyQmcNbg2g5PD9Jrb9yOS+vEwwThoY2YDoptDzhJvOxNYLek6DRwCAjZ4SqeTwmQCMQDD3lXotLGsn/CJxGlEiVaF2+z3SKb+bLGGKQATHPkZ/XHvLI2cAdVhcTYeEn36shE="}, "tlogEntries": [{"logIndex": "645", "logId": {"keyId": "8w1amZ2S5mJIQkQmPxdMuOrL/oJkvFg9MnQXmeOCXck="}, "kindVersion": {"kind": "hashedrekord", "version": "0.0.2"}, "inclusionProof": {"logIndex": "645", "rootHash": "kNum4JmdViJPfZLMRB3xPi6flATj2JzJSiF+1pQDzNQ=", "treeSize": "646", "hashes": ["eTqr8nE8VGEREKQ2MDQeD+zKHTJERE6iNw0tG1G+WbQ=", "wzbEsO0X3AWHadlgJZx7yhJdRVEZ2dEY21okXQ6UIi4=", "QMesRTEZdIgthOEinYE/9J7wGv+VmArDZTICj9POmhY=", "UNUMG62rMwoqCqFKknh4R5Ubkf5Z6dj+Pk0m/1xu8uo="], "checkpoint": {"envelope": "log2025-alpha1.rekor.sigstage.dev\n646\nkNum4JmdViJPfZLMRB3xPi6flATj2JzJSiF+1pQDzNQ=\n\n\u2014 log2025-alpha1.rekor.sigstage.dev 8w1amQA0XB55lIjvC/rvbpawQn9lp2R5TSkvqoNJuxcH9Ii05Ddi66xN8z5ZE6GsK2MkvgNZuqnZ5RtHbq2kpt/B8AE=\n"}}, "canonicalizedBody": "eyJhcGlWZXJzaW9uIjoiMC4wLjIiLCJraW5kIjoiaGFzaGVkcmVrb3JkIiwic3BlYyI6eyJoYXNoZWRSZWtvcmRWMDAyIjp7ImRhdGEiOnsiYWxnb3JpdGhtIjoiU0hBMl8yNTYiLCJkaWdlc3QiOiJGZlp5UmhGWklidDhIZURuNmVrblhJQVczQ1ZLREFDWWlKUkxmdE5rU3FvPSJ9LCJzaWduYXR1cmUiOnsiY29udGVudCI6Ik1FVUNJQVo2VDhBVVpTQ0JaYUtKa3NMbFNpbE5xRUVPdDRaeUdNR2VwVXBLcDdWR0FpRUFzL1gwa01KVG5FT3V6L0RMV3hUTDR3QlZOa2lXSVVERjM2RUVENzAzOTZBPSIsInZlcmlmaWVyIjp7ImtleURldGFpbHMiOiJQS0lYX0VDRFNBX1AyNTZfU0hBXzI1NiIsIng1MDlDZXJ0aWZpY2F0ZSI6eyJyYXdCeXRlcyI6Ik1JSUN5ekNDQWxDZ0F3SUJBZ0lVSmMvNm94K3hiK0NtYjVVVnJGaGR1NWppTXpJd0NnWUlLb1pJemowRUF3TXdOekVWTUJNR0ExVUVDaE1NYzJsbmMzUnZjbVV1WkdWMk1SNHdIQVlEVlFRREV4VnphV2R6ZEc5eVpTMXBiblJsY20xbFpHbGhkR1V3SGhjTk1qVXdOakE1TVRFMU56TTFXaGNOTWpVd05qQTVNVEl3TnpNMVdqQUFNRmt3RXdZSEtvWkl6ajBDQVFZSUtvWkl6ajBEQVFjRFFnQUV2b1liMWg2c2psT1IyNzZyQ2puUGMvUGdadFRhaEx6bWYzMmYwOFBaLzJlV3I0cTk3OWl0VncxUEc4SWhjSzNFMlppaWhlZ1hFZ2g0bVBra01uNzhCS09DQVc4d2dnRnJNQTRHQTFVZER3RUIvd1FFQXdJSGdEQVRCZ05WSFNVRUREQUtCZ2dyQmdFRkJRY0RBekFkQmdOVkhRNEVGZ1FVc29abHZwSUtnUjZXbGdlenZrRDZ4ekh5cGNNd0h3WURWUjBqQkJnd0ZvQVVjWVl3cGhSOFltLzU5OWIwQlJwL1gvL3JiNnd3R1FZRFZSMFJBUUgvQkE4d0RZRUxhbXQxUUdkdmRHOHVabWt3TEFZS0t3WUJCQUdEdnpBQkFRUWVhSFIwY0hNNkx5OW5hWFJvZFdJdVkyOXRMMnh2WjJsdUwyOWhkWFJvTUM0R0Npc0dBUVFCZzc4d0FRZ0VJQXdlYUhSMGNITTZMeTluYVhSb2RXSXVZMjl0TDJ4dloybHVMMjloZFhSb01JR0tCZ29yQmdFRUFkWjVBZ1FDQkh3RWVnQjRBSFlBS3pDODNHaUl5ZUxoMkNZcFhuUWZTRGt4bGdMeW5EUExYa05BL3JLc2hub0FBQUdYVkkyYUZnQUFCQU1BUnpCRkFpQkRIUnBLR1RwaVUzTngyOFhnZXdsdnpiTXQvdWc2aXBOOFhqOXRyeVdid1FJaEFQLzNDbmdvNFN0MW5BZ2drZmxvd3lTTDBmUFlnL1FEY0pLRTZYY2VPTjNXTUFvR0NDcUdTTTQ5QkFNREEya0FNR1lDTVFDZnlRbWNOYmcyZzVQRDlKcmI5eU9TK3ZFd3dUaG9ZMllEb3B0RHpoSnZPeE5ZTGVrNkRSd0NBalo0U3FlVHdtUUNNUUREM2xYb3RMR3NuL0NKeEdsRWlWYUYyK3ozU0tiK2JMR0dLUUFUSFBrWi9YSHZMSTJjQWRWaGNUWWVFbjM2c2hFPSJ9fX19fX0="}], "timestampVerificationData": {"rfc3161Timestamps": [{"signedTimestamp": "MIIE6zADAgEAMIIE4gYJKoZIhvcNAQcCoIIE0zCCBM8CAQMxDTALBglghkgBZQMEAgEwgcMGCyqGSIb3DQEJEAEEoIGzBIGwMIGtAgEBBgkrBgEEAYO/MAIwMTANBglghkgBZQMEAgEFAAQgOjYPmS6Qixa9OQqdXWQMPN66194GUnV3liEVd7cbW8oCFQDuYcF6Hx3Wi2sgxpmG+IG2KlvUKRgPMjAyNTA2MDkxMTU3MzhaMAMCAQECCQCbf5cNt4JRDqAypDAwLjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MRUwEwYDVQQDEwxzaWdzdG9yZS10c2GgggITMIICDzCCAZagAwIBAgIUCjWhBmHV4kFzxomWp/J98n4DfKcwCgYIKoZIzj0EAwMwOTEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MSAwHgYDVQQDExdzaWdzdG9yZS10c2Etc2VsZnNpZ25lZDAeFw0yNTAzMjgwOTE0MDZaFw0zNTAzMjYwODE0MDZaMC4xFTATBgNVBAoTDHNpZ3N0b3JlLmRldjEVMBMGA1UEAxMMc2lnc3RvcmUtdHNhMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEx1v5F3HpD9egHuknpBFlRz7QBRDJu4aeVzt9zJLRY0lvmx1lF7WBM2c9AN8ZGPQsmDqHlJN2R/7+RxLkvlLzkc19IOx38t7mGGEcB7agUDdCF/Ky3RTLSK0Xo/0AgHQdo2owaDAOBgNVHQ8BAf8EBAMCB4AwHQYDVR0OBBYEFKj8ZPYo3i7mO3NPVIxSxOGc3VOlMB8GA1UdIwQYMBaAFDsgRlletTJNRzDObmPuc3RH8gR9MBYGA1UdJQEB/wQMMAoGCCsGAQUFBwMIMAoGCCqGSM49BAMDA2cAMGQCMESvVS6GGtF33+J19TfwENWJXjRv4i0/HQFwLUSkX6TfV7g0nG8VnqNHJLvEpAtOjQIwUD3uywTXorQP1DgbV09rF9Yen+CEqs/iEpieJWPst280SSOZ5Na+dyPVk9/8SFk6MYIB3DCCAdgCAQEwUTA5MRUwEwYDVQQKEwxzaWdzdG9yZS5kZXYxIDAeBgNVBAMTF3NpZ3N0b3JlLXRzYS1zZWxmc2lnbmVkAhQKNaEGYdXiQXPGiZan8n3yfgN8pzALBglghkgBZQMEAgGggfwwGgYJKoZIhvcNAQkDMQ0GCyqGSIb3DQEJEAEEMBwGCSqGSIb3DQEJBTEPFw0yNTA2MDkxMTU3MzhaMC8GCSqGSIb3DQEJBDEiBCA6qJ7IlNaN4uuHegN2O+NsWY5kB6sw8E/Q3H3arU8jmDCBjgYLKoZIhvcNAQkQAi8xfzB9MHsweQQgBvT/4Ef+s1mZtzOw16MjUBz8GOTAM2aoRdd1NudLJ0QwVTA9pDswOTEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MSAwHgYDVQQDExdzaWdzdG9yZS10c2Etc2VsZnNpZ25lZAIUCjWhBmHV4kFzxomWp/J98n4DfKcwCgYIKoZIzj0EAwIEaDBmAjEA9vHFXY/Ia5L2g8F7ipZpiJOgDoAau7L+UkE5c1cCM2FYDZN1QQzWjXGj1CwQMOcuAjEAtBIxQiiecOzOkFo1Bj0n9xkIjyErSBT+P3P6OWgwdivDosxQCTMF7iNeI7wgFQxw"}]}}, "messageSignature": {"messageDigest": {"algorithm": "SHA2_256", "digest": "FfZyRhFZIbt8HeDn6eknXIAW3CVKDACYiJRLftNkSqo="}, "signature": "MEUCIAZ6T8AUZSCBZaKJksLlSilNqEEOt4ZyGMGepUpKp7VGAiEAs/X0kMJTnEOuz/DLWxTL4wBVNkiWIUDF36EED70396A="}} diff --git a/test/unit/conftest.py b/test/unit/conftest.py index d40d8d7fc..da3ca1573 100644 --- a/test/unit/conftest.py +++ b/test/unit/conftest.py @@ -31,6 +31,7 @@ from id import ( detect_credential, ) +from sigstore_protobuf_specs.dev.sigstore.trustroot.v1 import Service from tuf.api.exceptions import DownloadHTTPError from tuf.ngclient import FetcherInterface, updater @@ -237,6 +238,34 @@ def signer(): return signer, verifier, IdentityToken(token) +@pytest.fixture +def staging_with_rekorv2() -> tuple[ + type[SigningContext], type[Verifier], IdentityToken +]: + """ + Returns a SigningContext, Verifier, and IdentityToken for the staging environment. + The signingContext will use the Rekor V2 instance even if it is not yet enabled in + staging signing config. + """ + + def signer(): + trust_config = ClientTrustConfig.staging() + trust_config.signing_config._tlogs.append( + Service("https://log2025-alpha1.rekor.sigstage.dev", 2) + ) + return SigningContext.from_trust_config(trust_config) + + verifier = Verifier.staging + + # Detect env variable for local interactive tests. + token = os.getenv("SIGSTORE_IDENTITY_TOKEN_staging") + if not token: + # If the variable is not defined, try getting an ambient token. + token = detect_credential(TEST_CLIENT_ID) + + return signer, verifier, IdentityToken(token) + + @pytest.fixture def dummy_jwt(): def _dummy_jwt(claims: dict): diff --git a/test/unit/test_sign.py b/test/unit/test_sign.py index 244cfc8e7..46a26fb99 100644 --- a/test/unit/test_sign.py +++ b/test/unit/test_sign.py @@ -51,6 +51,21 @@ def test_sign_rekor_entry_consistent(sign_ctx_and_ident_for_env): assert expected_entry.log_index == actual_entry.log_index +@pytest.mark.staging +def test_sign_with_staging_rekor_v2(staging_with_rekorv2, null_policy): + ctx_cls, verifier_cls, identity = staging_with_rekorv2 + + ctx: SigningContext = ctx_cls() + verifier = verifier_cls() + assert identity is not None + + payload = secrets.token_bytes(32) + with ctx.signer(identity) as signer: + bundle = signer.sign_artifact(payload) + + verifier.verify_artifact(payload, bundle, null_policy) + + @pytest.mark.parametrize("env", ["staging", "production"]) @pytest.mark.ambient_oidc def test_sct_verify_keyring_lookup_error(sign_ctx_and_ident_for_env, monkeypatch): diff --git a/test/unit/verify/test_verifier.py b/test/unit/verify/test_verifier.py index 057b35e50..dcbcb6181 100644 --- a/test/unit/verify/test_verifier.py +++ b/test/unit/verify/test_verifier.py @@ -76,7 +76,8 @@ def test_verifier_multiple_verifications(signing_materials, null_policy): @pytest.mark.online @pytest.mark.parametrize( - "filename", ("bundle.txt", "bundle_v3.txt", "bundle_v3_alt.txt") + "filename", + ("bundle.txt", "bundle_v3.txt", "bundle_v3_alt.txt", "staging-rekor-v2.txt"), ) def test_verifier_bundle(signing_bundle, null_policy, filename): (file, bundle) = signing_bundle(filename)