diff --git a/pyproject.toml b/pyproject.toml index 4f58e1135..c25531b8e 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 ~= 6.0", "platformdirs ~= 4.2", diff --git a/sigstore/_cli.py b/sigstore/_cli.py index e2b1c752a..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 - ).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}") @@ -1202,7 +1200,7 @@ def _fix_bundle(args: argparse.Namespace) -> None: # for custom Rekor instances. rekor = RekorClient.staging() if args.staging else 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/_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/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 674949cd7..32eda4419 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( @@ -576,8 +577,8 @@ def _dsse_envelope(self) -> dsse.Envelope | None: @private """ - if self._inner.dsse_envelope: - return dsse.Envelope(self._inner.dsse_envelope) + if self._inner.is_set("dsse_envelope"): + return dsse.Envelope(self._inner.dsse_envelope) # type: ignore[arg-type] return None @property @@ -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: @@ -623,7 +624,10 @@ def _to_parts( """ content: common_v1.MessageSignature | dsse.Envelope - content = self._dsse_envelope or self._inner.message_signature + if self._dsse_envelope: + content = self._dsse_envelope + else: + content = self._inner.message_signature # type: ignore[assignment] return (self.signing_certificate, content, self.log_entry) @@ -650,30 +654,32 @@ 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 + # mypy will be mystified if types are specified here + content_dict: dict[str, Any] = {"message_signature": content} else: - inner.dsse_envelope = content._inner + content_dict = {"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_dict, + ) return cls(inner) diff --git a/sigstore/verify/verifier.py b/sigstore/verify/verifier.py index b782f969c..9a3f7718b 100644 --- a/sigstore/verify/verifier.py +++ b/sigstore/verify/verifier.py @@ -500,7 +500,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()), ) @@ -515,7 +515,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(