diff --git a/sigstore/_internal/trust.py b/sigstore/_internal/trust.py index abd0462b..d335d87e 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/sigstore/verify/verifier.py b/sigstore/verify/verifier.py index 8ea416fd..6f1845b7 100644 --- a/sigstore/verify/verifier.py +++ b/sigstore/verify/verifier.py @@ -25,8 +25,9 @@ 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.x509 import Certificate, ExtendedKeyUsage, KeyUsage from cryptography.x509.oid import ExtendedKeyUsageOID from OpenSSL.crypto import ( X509, @@ -38,6 +39,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 @@ -417,34 +420,18 @@ 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) + if entry._kind_version.kind != "dsse": + raise VerificationError( + f"Expected entry type dsse, got {entry._kind_version.kind}" ) - 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 - ): - 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), + if entry._kind_version.version == "0.0.2": + _validate_dsse_v002_entry_body(bundle) + elif entry._kind_version.version == "0.0.1": + _validate_dsse_v001_entry_body(bundle) + else: + raise VerificationError( + f"Unsupported dsse version {entry._kind_version.version}" ) - 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) @@ -489,16 +476,175 @@ def verify_artifact( # (8): verify the consistency of the log entry's body against # the other bundle materials (and input being verified). entry = bundle.log_entry + if entry._kind_version.kind != "hashedrekord": + raise VerificationError( + f"Expected entry type hashedrekord, got {entry._kind_version.kind}" + ) + + if entry._kind_version.version == "0.0.2": + _validate_hashedrekord_v002_entry_body(bundle) + elif entry._kind_version.version == "0.0.1": + _validate_hashedrekord_v001_entry_body(bundle, hashed_input) + else: + raise VerificationError( + f"Unsupported hashedrekord version {entry._kind_version.version}" + ) - expected_body = _hashedrekord_from_parts( - bundle.signing_certificate, - bundle._inner.message_signature.signature, # type: ignore[union-attr] - hashed_input, + +def _validate_dsse_v001_entry_body(bundle: Bundle) -> None: + """ + Validate the Entry body for dsse v001. + """ + entry = bundle.log_entry + envelope = bundle._dsse_envelope + if envelope is None: + raise VerificationError( + "cannot perform DSSE verification on a bundle without a DSSE envelope" ) - actual_body = rekor_types.Hashedrekord.model_validate_json( - base64.b64decode(entry.body) + 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 + ): + 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), ) - if expected_body != actual_body: - raise VerificationError( - "transparency log entry is inconsistent with other materials" + for signature in envelope._inner.signatures + ] + if signatures != entry_body.spec.root.signatures: + raise VerificationError("log entry signatures do not match bundle") + + +def _validate_dsse_v002_entry_body(bundle: Bundle) -> None: + """ + Validate Entry body for dsse v002. + """ + entry = bundle.log_entry + envelope = bundle._dsse_envelope + if envelope is None: + raise VerificationError( + "cannot perform DSSE verification on a bundle without a DSSE envelope" + ) + try: + 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 v2_body.spec.dsse_v002.payload_hash.algorithm != v1.HashAlgorithm.SHA2_256: + raise VerificationError("expected SHA256 hash in DSSE entry") + + 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") + + v2_signatures = [ + v2.Signature( + content=signature.sig, + verifier=_v2_verifier_from_certificate(bundle.signing_certificate), + ) + for signature in envelope._inner.signatures + ] + if v2_signatures != v2_body.spec.dsse_v002.signatures: + raise VerificationError("log entry signatures do not match bundle") + + +def _validate_hashedrekord_v001_entry_body( + bundle: Bundle, hashed_input: Hashed +) -> None: + """ + Validate the Entry body for hashedrekord v001. + """ + 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 expected_body != actual_body: + raise VerificationError( + "transparency log entry is inconsistent with other materials" + ) + + +def _validate_hashedrekord_v002_entry_body(bundle: Bundle) -> None: + """ + Validate Entry body for hashedrekord v002. + """ + entry = bundle.log_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( + 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_from_certificate(bundle.signing_certificate), + ), ) + ), + ) + 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" + ) + + +def _v2_verifier_from_certificate(certificate: Certificate) -> v2.Verifier: + """ + Return a Rekor v2 protobuf Verifier for the signing certificate. + + This method decides which signature algorithms are supported for verification + (in a rekor v2 entry), see + https://github.com/sigstore/architecture-docs/blob/main/algorithm-registry.md. + Note that actual signature verification happens in verify_artifact() and + verify_dsse(): New keytypes need to be added here and in those methods. + """ + public_key = certificate.public_key() + + if isinstance(public_key, ec.EllipticCurvePublicKey): + if isinstance(public_key.curve, ec.SECP256R1): + key_details = v1.PublicKeyDetails.PKIX_ECDSA_P256_SHA_256 + elif isinstance(public_key.curve, ec.SECP384R1): + key_details = v1.PublicKeyDetails.PKIX_ECDSA_P384_SHA_384 + elif isinstance(public_key.curve, ec.SECP521R1): + key_details = v1.PublicKeyDetails.PKIX_ECDSA_P521_SHA_512 + else: + raise ValueError(f"Unsupported EC curve: {public_key.curve.name}") + else: + raise ValueError(f"Unsupported public key type: {type(public_key)}") + + return v2.Verifier( + x509_certificate=v1.X509Certificate( + certificate.public_bytes(encoding=serialization.Encoding.DER) + ), + key_details=cast(v1.PublicKeyDetails, key_details), + ) diff --git a/test/assets/a.dsse.staging-rekor-v2.txt b/test/assets/a.dsse.staging-rekor-v2.txt new file mode 100644 index 00000000..8d0585ac --- /dev/null +++ b/test/assets/a.dsse.staging-rekor-v2.txt @@ -0,0 +1,5 @@ +DO NOT MODIFY ME! + +this is "a.txt", a sample input for sigstore-python's unit tests. + +DO NOT MODIFY ME! diff --git a/test/assets/a.dsse.staging-rekor-v2.txt.sigstore.json b/test/assets/a.dsse.staging-rekor-v2.txt.sigstore.json new file mode 100644 index 00000000..af2fe26f --- /dev/null +++ b/test/assets/a.dsse.staging-rekor-v2.txt.sigstore.json @@ -0,0 +1 @@ +{"mediaType": "application/vnd.dev.sigstore.bundle.v0.3+json", "verificationMaterial": {"certificate": {"rawBytes": "MIIDBDCCAoqgAwIBAgIUYlZafqye+P/bWSMSdvxrr7y+NUEwCgYIKoZIzj0EAwMwNzEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRlcm1lZGlhdGUwHhcNMjUwNjA5MjEwNjI1WhcNMjUwNjA5MjExNjI1WjAAMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEwDj9XB2rrkUTaCgPE3OGPJ+176EZM3u2SK2XLKoMUQn79zywhocahVPybzn/6nMkWkew8SFaDhkL4PCAENNzcqOCAakwggGlMA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQUQ/OiAAk5AAqjN5apYfVwt/M4S5UwHwYDVR0jBBgwFoAUcYYwphR8Ym/599b0BRp/X//rb6wwWQYDVR0RAQH/BE8wTYFLaW5zZWN1cmUtY2xvdWR0b3Atc2hhcmVkLXVzZXJAY2xvdWR0b3AtcHJvZC11cy1lYXN0LmlhbS5nc2VydmljZWFjY291bnQuY29tMCkGCisGAQQBg78wAQEEG2h0dHBzOi8vYWNjb3VudHMuZ29vZ2xlLmNvbTArBgorBgEEAYO/MAEIBB0MG2h0dHBzOi8vYWNjb3VudHMuZ29vZ2xlLmNvbTCBigYKKwYBBAHWeQIEAgR8BHoAeAB2ACswvNxoiMni4dgmKV50H0g5MZYC8pwzy15DQP6yrIZ6AAABl1aEEo4AAAQDAEcwRQIhAJzFA8xqE8owuQqk9ao7NLQy/YoTsy23A+ZU3cdL+MM1AiAZyN3FSWf13Fl3oL+P5jAvv0xRyqGrWEyZJw4KO7XhnDAKBggqhkjOPQQDAwNoADBlAjA9OgkRsqwLbt59TB0Jb15NBBQiaNBRRqUdo2FuSrvEWWDnnynmqo0GygnbCmz2CJwCMQDFCWJExAUGX7v5UQUzDz1pc1b0WvX1wAP2fhbgir2yZZRcsr4OdWz31arOo6USvVI="}, "tlogEntries": [{"logIndex": "689", "logId": {"keyId": "8w1amZ2S5mJIQkQmPxdMuOrL/oJkvFg9MnQXmeOCXck="}, "kindVersion": {"kind": "dsse", "version": "0.0.2"}, "inclusionProof": {"logIndex": "689", "rootHash": "VLopDAB81ENEy7SM2Oe4gxf026TulneLw22pUPlt0qE=", "treeSize": "690", "hashes": ["7G2mWiDIVCMp4cUCF9+qqADG/ICLRt3I2I9nqIWaKnA=", "/Fm4+swicRuu0gv27PWsZ2C1hw3IbCcatPnSV6oTbOw=", "9AF3UpKoSTEa5MS8BHGJxKHH9zVkJgn29s03k14ZtdI=", "QMesRTEZdIgthOEinYE/9J7wGv+VmArDZTICj9POmhY=", "UNUMG62rMwoqCqFKknh4R5Ubkf5Z6dj+Pk0m/1xu8uo="], "checkpoint": {"envelope": "log2025-alpha1.rekor.sigstage.dev\n690\nVLopDAB81ENEy7SM2Oe4gxf026TulneLw22pUPlt0qE=\n\n\u2014 log2025-alpha1.rekor.sigstage.dev 8w1amfdsl47Li2mk9esQ1K+vF9tg8WCLlNKBcoVTzrHr4howD6z2171ij8XW6d48AUEoV4PK1DDz5jHUlCQ98okwLQw=\n"}}, "canonicalizedBody": "eyJhcGlWZXJzaW9uIjoiMC4wLjIiLCJraW5kIjoiZHNzZSIsInNwZWMiOnsiZHNzZVYwMDIiOnsicGF5bG9hZEhhc2giOnsiYWxnb3JpdGhtIjoiU0hBMl8yNTYiLCJkaWdlc3QiOiI0a2QxR3VyKzFmZE1wMHVBZFJyQnBQYTZONXB3OWx0b25pZXdlekg4MmhvPSJ9LCJzaWduYXR1cmVzIjpbeyJjb250ZW50IjoiTUVZQ0lRQ3F6dEJCTXpiYmU3alN6NXFQOE93U3hKWDBFb0VTSGg5d21uRXljUzd3S3dJaEFMd1BIaWt0b2dRY3greFZMWEhsSU56dTI1clRTNW5YRkJ3OEtxcXp5OGZkIiwidmVyaWZpZXIiOnsia2V5RGV0YWlscyI6IlBLSVhfRUNEU0FfUDI1Nl9TSEFfMjU2IiwieDUwOUNlcnRpZmljYXRlIjp7InJhd0J5dGVzIjoiTUlJREJEQ0NBb3FnQXdJQkFnSVVZbFphZnF5ZStQL2JXU01TZHZ4cnI3eStOVUV3Q2dZSUtvWkl6ajBFQXdNd056RVZNQk1HQTFVRUNoTU1jMmxuYzNSdmNtVXVaR1YyTVI0d0hBWURWUVFERXhWemFXZHpkRzl5WlMxcGJuUmxjbTFsWkdsaGRHVXdIaGNOTWpVd05qQTVNakV3TmpJMVdoY05NalV3TmpBNU1qRXhOakkxV2pBQU1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRXdEajlYQjJycmtVVGFDZ1BFM09HUEorMTc2RVpNM3UyU0syWExLb01VUW43OXp5d2hvY2FoVlB5YnpuLzZuTWtXa2V3OFNGYURoa0w0UENBRU5OemNxT0NBYWt3Z2dHbE1BNEdBMVVkRHdFQi93UUVBd0lIZ0RBVEJnTlZIU1VFRERBS0JnZ3JCZ0VGQlFjREF6QWRCZ05WSFE0RUZnUVVRL09pQUFrNUFBcWpONWFwWWZWd3QvTTRTNVV3SHdZRFZSMGpCQmd3Rm9BVWNZWXdwaFI4WW0vNTk5YjBCUnAvWC8vcmI2d3dXUVlEVlIwUkFRSC9CRTh3VFlGTGFXNXpaV04xY21VdFkyeHZkV1IwYjNBdGMyaGhjbVZrTFhWelpYSkFZMnh2ZFdSMGIzQXRjSEp2WkMxMWN5MWxZWE4wTG1saGJTNW5jMlZ5ZG1salpXRmpZMjkxYm5RdVkyOXRNQ2tHQ2lzR0FRUUJnNzh3QVFFRUcyaDBkSEJ6T2k4dllXTmpiM1Z1ZEhNdVoyOXZaMnhsTG1OdmJUQXJCZ29yQmdFRUFZTy9NQUVJQkIwTUcyaDBkSEJ6T2k4dllXTmpiM1Z1ZEhNdVoyOXZaMnhsTG1OdmJUQ0JpZ1lLS3dZQkJBSFdlUUlFQWdSOEJIb0FlQUIyQUNzd3ZOeG9pTW5pNGRnbUtWNTBIMGc1TVpZQzhwd3p5MTVEUVA2eXJJWjZBQUFCbDFhRUVvNEFBQVFEQUVjd1JRSWhBSnpGQTh4cUU4b3d1UXFrOWFvN05MUXkvWW9Uc3kyM0ErWlUzY2RMK01NMUFpQVp5TjNGU1dmMTNGbDNvTCtQNWpBdnYweFJ5cUdyV0V5Wkp3NEtPN1hobkRBS0JnZ3Foa2pPUFFRREF3Tm9BREJsQWpBOU9na1JzcXdMYnQ1OVRCMEpiMTVOQkJRaWFOQlJScVVkbzJGdVNydkVXV0RubnlubXFvMEd5Z25iQ216MkNKd0NNUURGQ1dKRXhBVUdYN3Y1VVFVekR6MXBjMWIwV3ZYMXdBUDJmaGJnaXIyeVpaUmNzcjRPZFd6MzFhck9vNlVTdlZJPSJ9fX1dfX19"}], "timestampVerificationData": {"rfc3161Timestamps": [{"signedTimestamp": "MIIE5zADAgEAMIIE3gYJKoZIhvcNAQcCoIIEzzCCBMsCAQMxDTALBglghkgBZQMEAgEwgcEGCyqGSIb3DQEJEAEEoIGxBIGuMIGrAgEBBgkrBgEEAYO/MAIwMTANBglghkgBZQMEAgEFAAQg7mKrZuedCow8ht74HmPFNT7ZP18+JAF/WDRwwOFuzn8CFBKaF0PyLXni4RkH6K+ZuzF9x2JcGA8yMDI1MDYwOTIxMDYyOFowAwIBAQIIWJ9Fv2Y6K7CgMqQwMC4xFTATBgNVBAoTDHNpZ3N0b3JlLmRldjEVMBMGA1UEAxMMc2lnc3RvcmUtdHNhoIICEzCCAg8wggGWoAMCAQICFAo1oQZh1eJBc8aJlqfyffJ+A3ynMAoGCCqGSM49BAMDMDkxFTATBgNVBAoTDHNpZ3N0b3JlLmRldjEgMB4GA1UEAxMXc2lnc3RvcmUtdHNhLXNlbGZzaWduZWQwHhcNMjUwMzI4MDkxNDA2WhcNMzUwMzI2MDgxNDA2WjAuMRUwEwYDVQQKEwxzaWdzdG9yZS5kZXYxFTATBgNVBAMTDHNpZ3N0b3JlLXRzYTB2MBAGByqGSM49AgEGBSuBBAAiA2IABMdb+Rdx6Q/XoB7pJ6QRZUc+0AUQybuGnlc7fcyS0WNJb5sdZRe1gTNnPQDfGRj0LJg6h5STdkf+/kcS5L5S85HNfSDsd/Le5hhhHAe2oFA3Qhfyst0Uy0itF6P9AIB0HaNqMGgwDgYDVR0PAQH/BAQDAgeAMB0GA1UdDgQWBBSo/GT2KN4u5jtzT1SMUsThnN1TpTAfBgNVHSMEGDAWgBQ7IEZZXrUyTUcwzm5j7nN0R/IEfTAWBgNVHSUBAf8EDDAKBggrBgEFBQcDCDAKBggqhkjOPQQDAwNnADBkAjBEr1UuhhrRd9/idfU38BDViV40b+ItPx0BcC1EpF+k31e4NJxvFZ6jRyS7xKQLTo0CMFA97ssE16K0D9Q4G1dPaxfWHp/ghKrP4hKYniVj7LdvNEkjmeTWvncj1ZPf/EhZOjGCAdowggHWAgEBMFEwOTEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MSAwHgYDVQQDExdzaWdzdG9yZS10c2Etc2VsZnNpZ25lZAIUCjWhBmHV4kFzxomWp/J98n4DfKcwCwYJYIZIAWUDBAIBoIH8MBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAcBgkqhkiG9w0BCQUxDxcNMjUwNjA5MjEwNjI4WjAvBgkqhkiG9w0BCQQxIgQgm3w3T24hj0XJHfurAzfPAUM+UpN9mOfHY9jwsQe6eYkwgY4GCyqGSIb3DQEJEAIvMX8wfTB7MHkEIAb0/+BH/rNZmbczsNejI1Ac/BjkwDNmqEXXdTbnSydEMFUwPaQ7MDkxFTATBgNVBAoTDHNpZ3N0b3JlLmRldjEgMB4GA1UEAxMXc2lnc3RvcmUtdHNhLXNlbGZzaWduZWQCFAo1oQZh1eJBc8aJlqfyffJ+A3ynMAoGCCqGSM49BAMCBGYwZAIwJQ/ArYnYtKS38pLXrZ1A/CT1VGgDRUoSkslIGKlHU98qwoWUjjgmmdbeYakSqfENAjABbYaUoMwznhyQd8CKMo7f092Z3Plwa/enOQqgmyu1dAPpmD8rYr2VEjVEGKcvVoY="}]}}, "dsseEnvelope": {"payload": "eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjEiLCJzdWJqZWN0IjpbeyJuYW1lIjoiYS50eHQiLCJkaWdlc3QiOnsic2hhMjU2IjoiZTI0OGE1ZGI0OTMzZGJhNjU3ODIwMDIzOGM5MWE1N2Y1ZTY1YjkyNWI3MzA1MGFlNzg2OTMzNDY4YjdhYzEwMSJ9fV0sInByZWRpY2F0ZVR5cGUiOiJodHRwczovL3Nsc2EuZGV2L3Byb3ZlbmFuY2UvdjEiLCJwcmVkaWNhdGUiOnsiYnVpbGREZWZpbml0aW9uIjp7ImJ1aWxkVHlwZSI6Imh0dHBzOi8vYWN0aW9ucy5naXRodWIuaW8vYnVpbGR0eXBlcy93b3JrZmxvdy92MSIsImV4dGVybmFsUGFyYW1ldGVycyI6eyJ3b3JrZmxvdyI6eyJyZWYiOiJyZWZzL3RhZ3MvMS4yMS4wIiwicmVwb3NpdG9yeSI6Imh0dHBzOi8vZ2l0aHViLmNvbS9vY3RvLW9yZy9vY3RvLXJlcG8iLCJwYXRoIjoiLmdpdGh1Yi93b3JrZmxvd3MvY2kueWFtbCJ9fSwiaW50ZXJuYWxQYXJhbWV0ZXJzIjp7ImdpdGh1YiI6eyJldmVudF9uYW1lIjoicHVzaCIsInJlcG9zaXRvcnlfaWQiOiIwMDAwMDAwMDAiLCJyZXBvc2l0b3J5X293bmVyX2lkIjoiMDAwMDAwMCIsInJ1bm5lcl9lbnZpcm9ubWVudCI6ImdpdGh1Yi1ob3N0ZWQifX0sInJlc29sdmVkRGVwZW5kZW5jaWVzIjpbeyJ1cmkiOiJnaXQraHR0cHM6Ly9naXRodWIuY29tL29jdG8tb3JnL29jdG8tcmVwb0ByZWZzL3RhZ3MvMS4yMS4wIiwiZGlnZXN0Ijp7ImdpdENvbW1pdCI6IjFhYzkzY2UyMWVlNTI2YjM2ZmQxNTRiOTA1OGQ5N2RmYWE0MjRjNTAifX1dfSwicnVuRGV0YWlscyI6eyJidWlsZGVyIjp7ImlkIjoiaHR0cHM6Ly9naXRodWIuY29tL29jdG8tb3JnL29jdG8tcmVwby8uZ2l0aHViL3dvcmtmbG93cy9kb2NrZXIueWFtbEByZWZzL2hlYWRzL2RldmVsb3BtZW50In0sIm1ldGFkYXRhIjp7Imludm9jYXRpb25JZCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9vY3RvLW9yZy9vY3RvLXJlcG8vYWN0aW9ucy9ydW5zLzEwMzEzOTgzMjE4L2F0dGVtcHRzLzIifX19fQ==", "payloadType": "application/vnd.in-toto+json", "signatures": [{"sig": "MEYCIQCqztBBMzbbe7jSz5qP8OwSxJX0EoESHh9wmnEycS7wKwIhALwPHiktogQcx+xVLXHlINzu25rTS5nXFBw8Kqqzy8fd"}]}} 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 00000000..1a430525 --- /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/assets/staging-rekor-v2.txt b/test/assets/staging-rekor-v2.txt new file mode 100644 index 00000000..1895b222 --- /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 00000000..80c32553 --- /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/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 00000000..3c1dd3fa --- /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 diff --git a/test/unit/conftest.py b/test/unit/conftest.py index d40d8d7f..da3ca157 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/internal/rekor/test_client_v2.py b/test/unit/internal/rekor/test_client_v2.py index e8058223..41c0e52a 100644 --- a/test/unit/internal/rekor/test_client_v2.py +++ b/test/unit/internal/rekor/test_client_v2.py @@ -19,23 +19,19 @@ from sigstore import dsse from sigstore._internal.rekor.client_v2 import ( LogEntry, - RekorV2Client, ) from sigstore.models import rekor_v1 -ALPHA_REKOR_V2_URL = "https://log2025-alpha1.rekor.sigstage.dev" - @pytest.mark.staging @pytest.mark.ambient_oidc -def test_rekor_v2_create_entry_dsse(staging): +def test_rekor_v2_create_entry_dsse(staging_with_rekorv2): # This is not a real unit test: it requires not only staging rekor but also TUF # fulcio and oidc -- maybe useful only until we have real integration tests in place - sign_ctx_cls, _, identity = staging + sign_ctx_cls, _, identity = staging_with_rekorv2 # Hack to run Signer.sign() with staging rekor v2 sign_ctx = sign_ctx_cls() - sign_ctx._rekor = RekorV2Client(ALPHA_REKOR_V2_URL) stmt = ( dsse.StatementBuilder() @@ -64,14 +60,13 @@ def test_rekor_v2_create_entry_dsse(staging): @pytest.mark.staging @pytest.mark.ambient_oidc -def test_rekor_v2_create_entry_hashed_rekord(staging): +def test_rekor_v2_create_entry_hashed_rekord(staging_with_rekorv2): # This is not a real unit test: it requires not only staging rekor but also TUF # fulcio and oidc -- maybe useful only until we have real integration tests in place - sign_ctx_cls, _, identity = staging + sign_ctx_cls, _, identity = staging_with_rekorv2 # Hack to run Signer.sign() with staging rekor v2 sign_ctx = sign_ctx_cls() - sign_ctx._rekor = RekorV2Client(ALPHA_REKOR_V2_URL) with sign_ctx.signer(identity) as signer: bundle = signer.sign_artifact(b"") diff --git a/test/unit/internal/test_trust.py b/test/unit/internal/test_trust.py index 14b00624..4eef7f68 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", [ diff --git a/test/unit/test_sign.py b/test/unit/test_sign.py index 244cfc8e..b7f05637 100644 --- a/test/unit/test_sign.py +++ b/test/unit/test_sign.py @@ -51,6 +51,22 @@ def test_sign_rekor_entry_consistent(sign_ctx_and_ident_for_env): assert expected_entry.log_index == actual_entry.log_index +@pytest.mark.staging +@pytest.mark.ambient_oidc +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 adf6a1d6..9381d074 100644 --- a/test/unit/verify/test_verifier.py +++ b/test/unit/verify/test_verifier.py @@ -76,15 +76,28 @@ 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): +def test_verifier_bundle_artifact(signing_bundle, null_policy, filename): (file, bundle) = signing_bundle(filename) verifier = Verifier.staging() verifier.verify_artifact(file.read_bytes(), bundle, null_policy) +@pytest.mark.online +@pytest.mark.parametrize( + "filename", + ("a.dsse.staging-rekor-v2.txt",), +) +def test_verifier_bundle_dsse(signing_bundle, null_policy, filename): + (file, bundle) = signing_bundle(filename) + + verifier = Verifier.staging() + verifier.verify_dsse(bundle, null_policy) + + @pytest.mark.parametrize( "filename", ("bundle.txt", "bundle_v3.txt", "bundle_v3_alt.txt") )