From a9f80529b0009fd5c36fb5ee53e2419c564e1583 Mon Sep 17 00:00:00 2001 From: William Woodruff Date: Fri, 10 Jan 2025 17:04:35 -0500 Subject: [PATCH 1/5] deps: bump protobuf-specs Signed-off-by: William Woodruff --- pyproject.toml | 6 ++---- sigstore/_cli.py | 2 +- sigstore/models.py | 19 ++++++++++--------- sigstore/verify/verifier.py | 4 ++-- 4 files changed, 15 insertions(+), 16 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index a1c5ac7e3..d11eaab59 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -38,7 +38,7 @@ dependencies = [ "rfc8785 ~= 0.1.2", "rfc3161-client >= 0.1.2,< 1.1.0", # NOTE(ww): Both under active development, so strictly pinned. - "sigstore-protobuf-specs == 0.3.2", + "sigstore-protobuf-specs == 0.3.5", "sigstore-rekor-types == 0.0.18", "tuf ~= 5.0", "platformdirs ~= 4.2", @@ -60,9 +60,7 @@ lint = [ "bandit", "interrogate >= 1.7.0", "mypy ~= 1.1", - # NOTE(ww): ruff is under active development, so we pin conservatively here - # and let Dependabot periodically perform this update. - "ruff < 0.9.2", + "ruff ~= 0.9", "types-requests", "types-pyOpenSSL", ] diff --git a/sigstore/_cli.py b/sigstore/_cli.py index 2b516a35d..4d5345380 100644 --- a/sigstore/_cli.py +++ b/sigstore/_cli.py @@ -714,7 +714,7 @@ def _sign_common( sig_output = sys.stdout signature = base64.b64encode( - result._inner.message_signature.signature + result._inner.message_signature.signature # type: ignore[union-attr] ).decode() print(signature, file=sig_output) if outputs.signature is not None: diff --git a/sigstore/models.py b/sigstore/models.py index e9693cc9f..d967a97d2 100644 --- a/sigstore/models.py +++ b/sigstore/models.py @@ -19,6 +19,7 @@ from __future__ import annotations import base64 +import json import logging import typing from enum import Enum @@ -466,6 +467,9 @@ def _verify(self) -> None: Bundle.BundleType.BUNDLE_0_3_ALT, ): # For "v3" bundles, the signing certificate is the only one present. + if not self._inner.verification_material.certificate: + raise InvalidBundle("expected certificate in bundle") + leaf_cert = load_der_x509_certificate( self._inner.verification_material.certificate.raw_bytes ) @@ -473,11 +477,8 @@ def _verify(self) -> None: # In older bundles, there is an entire pool (misleadingly called # a chain) of certificates, the first of which is the signing # certificate. - certs = ( - self._inner.verification_material.x509_certificate_chain.certificates - ) - - if len(certs) == 0: + chain = self._inner.verification_material.x509_certificate_chain + if not chain or not chain.certificates: raise InvalidBundle("expected non-empty certificate chain in bundle") # Per client policy in protobuf-specs: the first entry in the chain @@ -489,7 +490,7 @@ def _verify(self) -> None: # and intermediate CAs, so we issue warnings and not hard errors # in those cases. leaf_cert, *chain_certs = [ - load_der_x509_certificate(cert.raw_bytes) for cert in certs + load_der_x509_certificate(cert.raw_bytes) for cert in chain.certificates ] if not cert_is_leaf(leaf_cert): raise InvalidBundle( @@ -589,7 +590,7 @@ def signature(self) -> bytes: return ( self._dsse_envelope.signature if self._dsse_envelope - else self._inner.message_signature.signature + else self._inner.message_signature.signature # type: ignore[union-attr] ) @property @@ -604,7 +605,7 @@ def from_json(cls, raw: bytes | str) -> Bundle: """ Deserialize the given Sigstore bundle. """ - inner = _Bundle().from_json(raw) + inner = _Bundle.from_dict(json.loads(raw)) return cls(inner) def to_json(self) -> str: @@ -626,7 +627,7 @@ def _to_parts( if self._dsse_envelope: content = self._dsse_envelope else: - content = self._inner.message_signature + content = self._inner.message_signature # type: ignore[assignment] return (self.signing_certificate, content, self.log_entry) diff --git a/sigstore/verify/verifier.py b/sigstore/verify/verifier.py index 000e618b8..585081ac5 100644 --- a/sigstore/verify/verifier.py +++ b/sigstore/verify/verifier.py @@ -489,7 +489,7 @@ def verify_artifact( signing_key = bundle.signing_certificate.public_key() signing_key = cast(ec.EllipticCurvePublicKey, signing_key) signing_key.verify( - bundle._inner.message_signature.signature, + bundle._inner.message_signature.signature, # type: ignore[union-attr] hashed_input.digest, ec.ECDSA(hashed_input._as_prehashed()), ) @@ -504,7 +504,7 @@ def verify_artifact( expected_body = _hashedrekord_from_parts( bundle.signing_certificate, - bundle._inner.message_signature.signature, + bundle._inner.message_signature.signature, # type: ignore[union-attr] hashed_input, ) actual_body = rekor_types.Hashedrekord.model_validate_json( From caf2bfeeca24230c18a6c00d2f84106d8474aa52 Mon Sep 17 00:00:00 2001 From: William Woodruff Date: Mon, 13 Jan 2025 14:49:28 -0500 Subject: [PATCH 2/5] more protobuf-specs hackery Signed-off-by: William Woodruff --- sigstore/_cli.py | 2 +- sigstore/hashes.py | 2 +- sigstore/models.py | 41 +++++++++++++++++++++-------------------- 3 files changed, 23 insertions(+), 22 deletions(-) diff --git a/sigstore/_cli.py b/sigstore/_cli.py index 4d5345380..18ee633c6 100644 --- a/sigstore/_cli.py +++ b/sigstore/_cli.py @@ -1205,7 +1205,7 @@ def _fix_bundle(args: argparse.Namespace) -> None: else: rekor = RekorClient.production() - raw_bundle = RawBundle().from_json(args.bundle.read_text()) + raw_bundle = RawBundle.from_dict(json.loads(args.bundle.read_bytes())) if len(raw_bundle.verification_material.tlog_entries) != 1: _fatal("unfixable bundle: must have exactly one log entry") diff --git a/sigstore/hashes.py b/sigstore/hashes.py index 86dd7607d..876f8ea87 100644 --- a/sigstore/hashes.py +++ b/sigstore/hashes.py @@ -60,4 +60,4 @@ def __str__(self) -> str: """ Returns a str representation of this `Hashed`. """ - return f"{self.algorithm.name}:{self.digest.hex()}" + return f"{HashAlgorithm(self.algorithm)}:{self.digest.hex()}" diff --git a/sigstore/models.py b/sigstore/models.py index d967a97d2..79ee6892d 100644 --- a/sigstore/models.py +++ b/sigstore/models.py @@ -577,7 +577,7 @@ def _dsse_envelope(self) -> dsse.Envelope | None: @private """ - if self._inner.dsse_envelope: + if self._inner.is_set("dsse_envelope"): return dsse.Envelope(self._inner.dsse_envelope) return None @@ -654,30 +654,31 @@ def _from_parts( @private """ - inner = _Bundle( - media_type=Bundle.BundleType.BUNDLE_0_3.value, - verification_material=bundle_v1.VerificationMaterial( - certificate=common_v1.X509Certificate(cert.public_bytes(Encoding.DER)), - ), + timestamp_verifcation_data = bundle_v1.TimestampVerificationData( + rfc3161_timestamps=[] ) + if signed_timestamp is not None: + timestamp_verifcation_data.rfc3161_timestamps.extend( + [ + Rfc3161SignedTimestamp(signed_timestamp=response.as_bytes()) + for response in signed_timestamp + ] + ) # Fill in the appropriate variants. if isinstance(content, common_v1.MessageSignature): - inner.message_signature = content + content = {"message_signature": content} else: - inner.dsse_envelope = content._inner + content = {"dsse_envelope": content._inner} - tlog_entry = log_entry._to_rekor() - inner.verification_material.tlog_entries = [tlog_entry] - - if signed_timestamp is not None: - inner.verification_material.timestamp_verification_data = ( - bundle_v1.TimestampVerificationData( - rfc3161_timestamps=[ - Rfc3161SignedTimestamp(signed_timestamp=response.as_bytes()) - for response in signed_timestamp - ] - ) - ) + inner = _Bundle( + media_type=Bundle.BundleType.BUNDLE_0_3.value, + verification_material=bundle_v1.VerificationMaterial( + certificate=common_v1.X509Certificate(cert.public_bytes(Encoding.DER)), + tlog_entries=[log_entry._to_rekor()], + timestamp_verification_data=timestamp_verifcation_data, + ), + **content, + ) return cls(inner) From 9a3bdf245ad5ca6fe7f686426525b29377f79ad5 Mon Sep 17 00:00:00 2001 From: William Woodruff Date: Mon, 13 Jan 2025 14:51:55 -0500 Subject: [PATCH 3/5] ignore another mypy finding Signed-off-by: William Woodruff --- sigstore/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sigstore/models.py b/sigstore/models.py index 79ee6892d..402b8903b 100644 --- a/sigstore/models.py +++ b/sigstore/models.py @@ -578,7 +578,7 @@ def _dsse_envelope(self) -> dsse.Envelope | None: @private """ if self._inner.is_set("dsse_envelope"): - return dsse.Envelope(self._inner.dsse_envelope) + return dsse.Envelope(self._inner.dsse_envelope) # type: ignore[arg-type] return None @property From 27a29c6fb9a6efa7597d586a97efd23a4fee2edb Mon Sep 17 00:00:00 2001 From: William Woodruff Date: Fri, 18 Apr 2025 11:36:30 -0400 Subject: [PATCH 4/5] cherry-pick 702811037ec5cc4f3a6eb43ce4f82ee492ee1ced Signed-off-by: William Woodruff --- sigstore/_internal/trust.py | 4 ++-- sigstore/models.py | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/sigstore/_internal/trust.py b/sigstore/_internal/trust.py index b9f03d15a..18f35f344 100644 --- a/sigstore/_internal/trust.py +++ b/sigstore/_internal/trust.py @@ -93,14 +93,14 @@ class Key: key: PublicKey key_id: KeyID - _RSA_SHA_256_DETAILS: ClassVar[set[_PublicKeyDetails]] = { + _RSA_SHA_256_DETAILS: ClassVar = { _PublicKeyDetails.PKCS1_RSA_PKCS1V5, _PublicKeyDetails.PKIX_RSA_PKCS1V15_2048_SHA256, _PublicKeyDetails.PKIX_RSA_PKCS1V15_3072_SHA256, _PublicKeyDetails.PKIX_RSA_PKCS1V15_4096_SHA256, } - _EC_DETAILS_TO_HASH: ClassVar[dict[_PublicKeyDetails, hashes.HashAlgorithm]] = { + _EC_DETAILS_TO_HASH: ClassVar = { _PublicKeyDetails.PKIX_ECDSA_P256_SHA_256: hashes.SHA256(), _PublicKeyDetails.PKIX_ECDSA_P384_SHA_384: hashes.SHA384(), _PublicKeyDetails.PKIX_ECDSA_P521_SHA_512: hashes.SHA512(), diff --git a/sigstore/models.py b/sigstore/models.py index 4f235dc69..32eda4419 100644 --- a/sigstore/models.py +++ b/sigstore/models.py @@ -667,9 +667,10 @@ def _from_parts( # Fill in the appropriate variants. if isinstance(content, common_v1.MessageSignature): - content = {"message_signature": content} + # mypy will be mystified if types are specified here + content_dict: dict[str, Any] = {"message_signature": content} else: - content = {"dsse_envelope": content._inner} + content_dict = {"dsse_envelope": content._inner} inner = _Bundle( media_type=Bundle.BundleType.BUNDLE_0_3.value, @@ -678,7 +679,7 @@ def _from_parts( tlog_entries=[log_entry._to_rekor()], timestamp_verification_data=timestamp_verifcation_data, ), - **content, + **content_dict, ) return cls(inner) From f8b08a85deadd73884cb00a17c3336f6043afc01 Mon Sep 17 00:00:00 2001 From: William Woodruff Date: Fri, 18 Apr 2025 11:58:06 -0400 Subject: [PATCH 5/5] _cli: fix proto mess Signed-off-by: William Woodruff --- sigstore/_cli.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/sigstore/_cli.py b/sigstore/_cli.py index b8b4e7d06..480512e63 100644 --- a/sigstore/_cli.py +++ b/sigstore/_cli.py @@ -713,9 +713,7 @@ def _sign_common( else: sig_output = sys.stdout - signature = base64.b64encode( - result._inner.message_signature.signature # type: ignore[union-attr] - ).decode() + signature = base64.b64encode(result.signature).decode() print(signature, file=sig_output) if outputs.signature is not None: print(f"Signature written to {outputs.signature}")