From 62360bb8c03db615d465b801b887de582f9022bf Mon Sep 17 00:00:00 2001 From: Jussi Kukkonen Date: Thu, 16 May 2024 21:45:03 +0300 Subject: [PATCH 1/4] SigstoreSigner: Update to sigstore 3.0 This makes SigstoreSigner functional again * Many small API changes that mostly make our code look nicer * Unfortunately Sigstore API no longer exposes the actual protobuf object so I go through a json cycle in both verify and sign * sigstore now has a py.typed so I added it to lint dependencies --- mypy.ini | 3 -- pyproject.toml | 2 +- requirements-sigstore.txt | 2 +- securesystemslib/signer/_sigstore_signer.py | 58 +++++++++------------ tox.ini | 1 + 5 files changed, 27 insertions(+), 39 deletions(-) diff --git a/mypy.ini b/mypy.ini index e889fc4f..2b97eb98 100644 --- a/mypy.ini +++ b/mypy.ini @@ -20,9 +20,6 @@ ignore_missing_imports = True [mypy-asn1crypto.*] ignore_missing_imports = True -[mypy-sigstore.*] -ignore_missing_imports = True - [mypy-sigstore_protobuf_specs.*] ignore_missing_imports = True diff --git a/pyproject.toml b/pyproject.toml index afe220e7..d8d7510c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -52,7 +52,7 @@ azurekms = ["azure-identity", "azure-keyvault-keys", "cryptography>=40.0.0"] awskms = ["boto3", "botocore", "cryptography>=40.0.0"] hsm = ["asn1crypto", "cryptography>=40.0.0", "PyKCS11"] PySPX = ["PySPX>=0.5.0"] -sigstore = ["sigstore~=2.0"] +sigstore = ["sigstore~=3.0"] vault = ["hvac", "cryptography>=40.0.0"] [tool.hatch.version] diff --git a/requirements-sigstore.txt b/requirements-sigstore.txt index 759166af..3e5fa176 100644 --- a/requirements-sigstore.txt +++ b/requirements-sigstore.txt @@ -1 +1 @@ -sigstore==2.1.5 +sigstore==3.0.0 diff --git a/securesystemslib/signer/_sigstore_signer.py b/securesystemslib/signer/_sigstore_signer.py index 43b1851f..67549a25 100644 --- a/securesystemslib/signer/_sigstore_signer.py +++ b/securesystemslib/signer/_sigstore_signer.py @@ -1,13 +1,7 @@ """Signer implementation for project sigstore. - -NOTE: SigstoreSigner and -Key are disabled temporarily around -the Securesystemslib 1.0 release as the cyclic dependency -(securesystemslib -> sigstore-python -> tuf -> securesystemslib) -is problematic during API deprecations. -See issue #781. """ -import io +import json import logging from typing import Any, Dict, Optional, Tuple from urllib import parse @@ -66,37 +60,35 @@ def to_dict(self) -> Dict: def verify_signature(self, signature: Signature, data: bytes) -> None: # pylint: disable=import-outside-toplevel,import-error - result = None try: - from sigstore.verify import VerificationMaterials, Verifier + from sigstore.errors import VerificationError as SigstoreVerifyError + from sigstore.models import Bundle + from sigstore.verify import Verifier from sigstore.verify.policy import Identity - from sigstore_protobuf_specs.dev.sigstore.bundle.v1 import Bundle verifier = Verifier.production() identity = Identity( identity=self.keyval["identity"], issuer=self.keyval["issuer"] ) - bundle = Bundle().from_dict(signature.unrecognized_fields["bundle"]) - materials = VerificationMaterials.from_bundle( - input_=io.BytesIO(data), bundle=bundle, offline=True - ) - result = verifier.verify(materials, identity) + bundle_data = signature.unrecognized_fields["bundle"] + bundle = Bundle.from_json(json.dumps(bundle_data)) - except Exception as e: - logger.info("Key %s failed to verify sig: %s", self.keyid, str(e)) - raise VerificationError( - f"Unknown failure to verify signature by {self.keyid}" - ) from e + verifier.verify_artifact(data, bundle, identity) - if not result: + except SigstoreVerifyError as e: logger.info( "Key %s failed to verify sig: %s", self.keyid, - getattr(result, "reason", ""), + e, ) raise UnverifiedSignatureError( f"Failed to verify signature by {self.keyid}" - ) + ) from e + except Exception as e: + logger.info("Key %s failed to verify sig: %s", self.keyid, str(e)) + raise VerificationError( + f"Unknown failure to verify signature by {self.keyid}" + ) from e class SigstoreSigner(Signer): @@ -189,9 +181,9 @@ def from_priv_key_uri( key_identity = public_key.keyval["identity"] key_issuer = public_key.keyval["issuer"] - if key_issuer != token.expected_certificate_subject: + if key_issuer != token.federated_issuer: raise ValueError( - f"Signer identity issuer {token.expected_certificate_subject} " + f"Signer identity issuer {token.federated_issuer} " f"did not match key: {key_issuer}" ) # TODO: should check ambient identity too: unfortunately IdentityToken does @@ -246,9 +238,7 @@ def import_via_auth(cls) -> Tuple[str, SigstoreKey]: # authenticate to get the identity and issuer token = Issuer.production().identity_token() - return cls.import_( - token.identity, token.expected_certificate_subject, False - ) + return cls.import_(token.identity, token.federated_issuer, False) def sign(self, payload: bytes) -> Signature: """Signs payload using the OIDC token on the signer instance. @@ -273,12 +263,12 @@ def sign(self, payload: bytes) -> Signature: context = SigningContext.production() with context.signer(self._token) as sigstore_signer: - result = sigstore_signer.sign(io.BytesIO(payload)) - - bundle = result.to_bundle() - + bundle = sigstore_signer.sign_artifact(payload) + # We want to access the actual signature, see + # https://github.com/sigstore/protobuf-specs/blob/main/protos/sigstore_bundle.proto + bundle_json = json.loads(bundle.to_json()) return Signature( self.public_key.keyid, - bundle.message_signature.signature.hex(), - {"bundle": bundle.to_dict()}, + bundle_json["messageSignature"]["signature"], + {"bundle": bundle_json}, ) diff --git a/tox.ini b/tox.ini index 64649f45..2493fb02 100644 --- a/tox.ini +++ b/tox.ini @@ -63,6 +63,7 @@ commands = deps = -r{toxinidir}/requirements-pinned.txt -r{toxinidir}/requirements-lint.txt + -r{toxinidir}/requirements-sigstore.txt commands = black --check --diff . isort --check --diff . From a75bcf03f7762c485568562f74b27ab84020a485 Mon Sep 17 00:00:00 2001 From: Jussi Kukkonen Date: Thu, 16 May 2024 22:18:55 +0300 Subject: [PATCH 2/4] workflows: Enable sigstore tests These should work again now --- .github/workflows/test-sigstore.yml | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/.github/workflows/test-sigstore.yml b/.github/workflows/test-sigstore.yml index ad8d2f23..87255d37 100644 --- a/.github/workflows/test-sigstore.yml +++ b/.github/workflows/test-sigstore.yml @@ -1,11 +1,10 @@ name: Run Sigstore Signer tests on: - ## Disabled temporarily: #781 - #push: - # branches: - # - main - #pull_request: + push: + branches: + - main + pull_request: workflow_dispatch: permissions: {} From a7f3c09cd5924de81ee35429ba12c15a80f9c799 Mon Sep 17 00:00:00 2001 From: Jussi Kukkonen Date: Fri, 17 May 2024 10:11:02 +0300 Subject: [PATCH 3/4] tests: Add some failures cases to Sigstore test --- tests/check_sigstore_signer.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/check_sigstore_signer.py b/tests/check_sigstore_signer.py index 6e24df7c..166da527 100644 --- a/tests/check_sigstore_signer.py +++ b/tests/check_sigstore_signer.py @@ -20,6 +20,10 @@ from tempfile import TemporaryDirectory from unittest import mock +from securesystemslib.exceptions import ( + UnverifiedSignatureError, + VerificationError, +) from securesystemslib.signer import ( SIGNER_FOR_URI_SCHEME, Signer, @@ -100,8 +104,19 @@ def test_sign(self): signer = Signer.from_priv_key_uri(uri, public_key) sig = signer.sign(b"data") + + # Successful verification public_key.verify_signature(sig, b"data") + # Signature mismatch + with self.assertRaises(UnverifiedSignatureError): + public_key.verify_signature(sig, b"incorrect data") + + # Broken bundle + sig.unrecognized_fields["bundle"]["verificationMaterial"] = None + with self.assertRaises(VerificationError): + public_key.verify_signature(sig, b"data") + if __name__ == "__main__": unittest.main(verbosity=4, buffer=False) From 6fae4c8095b92d6a045d29af9d83f18913041877 Mon Sep 17 00:00:00 2001 From: Jussi Kukkonen Date: Fri, 17 May 2024 10:25:13 +0300 Subject: [PATCH 4/4] SigstoreKey: Handle import errors like Signer Note that this still raises VerificationError just like before (and not UnsupportedLibraryError like Signers do). --- securesystemslib/signer/_sigstore_signer.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/securesystemslib/signer/_sigstore_signer.py b/securesystemslib/signer/_sigstore_signer.py index 67549a25..d14358ff 100644 --- a/securesystemslib/signer/_sigstore_signer.py +++ b/securesystemslib/signer/_sigstore_signer.py @@ -65,7 +65,10 @@ def verify_signature(self, signature: Signature, data: bytes) -> None: from sigstore.models import Bundle from sigstore.verify import Verifier from sigstore.verify.policy import Identity + except ImportError as e: + raise VerificationError(IMPORT_ERROR) from e + try: verifier = Verifier.production() identity = Identity( identity=self.keyval["identity"], issuer=self.keyval["issuer"]