From 8bd8a68cb4932226b05e2a2a7286bbab7bfc61a7 Mon Sep 17 00:00:00 2001 From: Lukas Puehringer Date: Mon, 27 Feb 2023 13:30:13 +0100 Subject: [PATCH 01/10] signer: add basic sigstore signer and verifier Adds SigstoreSigner and SigstoreKey implementations to the securesystemslib signer interface. They can be used to create and verify signatures using an OIDC token and an identity/issuer pair respectively, as described in the TAP: "Ephemeral identity verification using sigstore's Fulcio for TUF developer key management" https://github.com/theupdateframework/taps/blob/3b0d8b108758266e36790b3a782b943d633ce585/candidate-fulcio-tap.md IMPORTANT NOTES: - the created signature does not strictly follow the typing requirements of the interface - currently, only offline verification is supported This patch also adds a GitHub Action workflow that runs a tailored test, using ambient credentials for signing. Signed-off-by: Lukas Puehringer --- .github/workflows/test-sigstore.yml | 38 +++++ pyproject.toml | 1 + requirements-sigstore.txt | 1 + securesystemslib/signer/__init__.py | 1 + securesystemslib/signer/_sigstore_signer.py | 156 ++++++++++++++++++++ tests/check_sigstore_signer.py | 66 +++++++++ tox.ini | 13 ++ 7 files changed, 276 insertions(+) create mode 100644 .github/workflows/test-sigstore.yml create mode 100644 requirements-sigstore.txt create mode 100644 securesystemslib/signer/_sigstore_signer.py create mode 100644 tests/check_sigstore_signer.py diff --git a/.github/workflows/test-sigstore.yml b/.github/workflows/test-sigstore.yml new file mode 100644 index 00000000..012a4cc4 --- /dev/null +++ b/.github/workflows/test-sigstore.yml @@ -0,0 +1,38 @@ +name: Run Sigstore Signer tests + +on: + push: + branches: + - master + pull_request: + workflow_dispatch: + +permissions: {} + +jobs: + test-sigstore: + runs-on: ubuntu-latest + + permissions: + id-token: 'write' # ambient credential is used to sign + + steps: + - name: Checkout securesystemslib + uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c + + - name: Set up Python + uses: actions/setup-python@d27e3f3d7c64b4bbf8e4abfb9b63b83e846e0435 + with: + python-version: '3.x' + cache: 'pip' + cache-dependency-path: 'requirements*.txt' + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install --upgrade tox + + - run: | + export CERT_ID=${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/.github/workflows/test-sigstore.yml@${GITHUB_REF} + export CERT_ISSUER=https://token.actions.githubusercontent.com + tox -e sigstore diff --git a/pyproject.toml b/pyproject.toml index 1b47efca..de2ba369 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -48,6 +48,7 @@ gcpkms = ["google-cloud-kms", "cryptography>=37.0.0"] hsm = ["asn1crypto", "cryptography>=37.0.0", "PyKCS11"] pynacl = ["pynacl>1.2.0"] PySPX = ["PySPX>=0.5.0"] +sigstore = ["sigstore"] [tool.hatch.version] path = "securesystemslib/__init__.py" diff --git a/requirements-sigstore.txt b/requirements-sigstore.txt new file mode 100644 index 00000000..16fad530 --- /dev/null +++ b/requirements-sigstore.txt @@ -0,0 +1 @@ +sigstore==1.1.0 diff --git a/securesystemslib/signer/__init__.py b/securesystemslib/signer/__init__.py index d5c816d1..c83cae7e 100644 --- a/securesystemslib/signer/__init__.py +++ b/securesystemslib/signer/__init__.py @@ -15,6 +15,7 @@ Signer, SSlibSigner, ) +from securesystemslib.signer._sigstore_signer import SigstoreKey, SigstoreSigner # Register supported private key uri schemes and the Signers implementing them SIGNER_FOR_URI_SCHEME.update( diff --git a/securesystemslib/signer/_sigstore_signer.py b/securesystemslib/signer/_sigstore_signer.py new file mode 100644 index 00000000..3a900656 --- /dev/null +++ b/securesystemslib/signer/_sigstore_signer.py @@ -0,0 +1,156 @@ +"""Signer implementation for project sigstore. + +Example: +```python +from sigstore.oidc import Issuer + +from securesystemslib.signer import SigstoreKey, SigstoreSigner + +# Create public key +identity = "luk.puehringer@gmail.com" # change, unless you know my password +issuer = "https://github.com/login/oauth" +public_key = SigstoreKey.from_dict( + "abcdefg", + { + "keytype": "sigstore-oidc", + "scheme": "Fulcio", + "keyval": { + "issuer": issuer, + "identity": identity, + }, + }, +) + +# Create signer +issuer = Issuer.production() +token = issuer.identity_token() # requires sign in with GitHub in a browser +signer = SigstoreSigner(token, public_key) + +# Sign +signature = signer.sign(b"data") + +# Verify +public_key.verify_signature(signature, b"data") + +``` + +""" + +import io +import logging +from typing import Any, Dict, Optional + +from securesystemslib import exceptions +from securesystemslib.signer._signer import ( + Key, + SecretsHandler, + Signature, + Signer, +) + +logger = logging.getLogger(__name__) + + +class SigstoreKey(Key): + """Sigstore verifier.""" + + @classmethod + def from_dict(cls, keyid: str, key_dict: Dict[str, Any]) -> "SigstoreKey": + keytype = key_dict.pop("keytype") + scheme = key_dict.pop("scheme") + keyval = key_dict.pop("keyval") + + for content in ["identity", "issuer"]: + if content not in keyval or not isinstance(keyval[content], str): + raise ValueError( + f"{content} string required for scheme {scheme}" + ) + + return cls(keyid, keytype, scheme, keyval, key_dict) + + def to_dict(self) -> Dict: + return { + "keytype": self.keytype, + "scheme": self.scheme, + "keyval": self.keyval, + **self.unrecognized_fields, + } + + def verify_signature(self, signature: Signature, data: bytes) -> None: + from sigstore.verify import VerificationMaterials, 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"] + ) + + signature_bundle = Bundle().from_dict(signature.signature) + verification_materials = VerificationMaterials.from_bundle( + input_=io.BytesIO(data), bundle=signature_bundle, offline=True + ) + + try: + result = verifier.verify(verification_materials, identity) + if not result: + logger.info( + "Key %s failed to verify sig: %s", self.keyid, result.reason + ) + raise exceptions.UnverifiedSignatureError( + f"Failed to verify signature by {self.keyid}" + ) + except exceptions.UnverifiedSignatureError: + raise + + except Exception as e: + logger.info("Key %s failed to verify sig: %s", self.keyid, str(e)) + raise exceptions.VerificationError( + f"Unknown failure to verify signature by {self.keyid}" + ) from e + + +class SigstoreSigner(Signer): + """Sigstore signer.""" + + def __init__(self, token: str, public_key: Key): + # TODO: Vet public key + # - signer eligible for keytype/scheme? + # - token matches identity/issuer? + self.public_key = public_key + self._token = token + + @classmethod + def from_priv_key_uri( + cls, + priv_key_uri: str, + public_key: Key, + secrets_handler: Optional[SecretsHandler] = None, + ) -> "SigstoreSigner": + raise NotImplementedError() + + def sign(self, payload: bytes) -> Signature: + """Signs payload using the OIDC token on the signer instance. + + Arguments: + payload: bytes to be signed. + + Raises: + Various errors from sigstore-python. + + Returns: + Signature. + + NOTE: The ``signature`` attribute of the returned object + contains the ``dict`` representation of a sigstore ``Bundle`. + This is incompatible with the TUF specification and the + ``Signature` interface, which expect the attribute to be of type `str`. + + """ + from sigstore.sign import Signer as _Signer + + signer = _Signer.production() + result = signer.sign(io.BytesIO(payload), self._token) + # TODO: Ask upstream if they can make this public. + sig = result._to_bundle().to_dict() # pylint: disable=protected-access + return Signature(self.public_key.keyid, sig) diff --git a/tests/check_sigstore_signer.py b/tests/check_sigstore_signer.py new file mode 100644 index 00000000..8fc2c5c7 --- /dev/null +++ b/tests/check_sigstore_signer.py @@ -0,0 +1,66 @@ +""" +Test SigstoreSigner API. + +NOTE: The filename prefix is check_ instead of test_ so that tests are +only run when explicitly invoked in a suited environment. + +""" +import os +import unittest + +from sigstore.oidc import detect_credential + +from securesystemslib.signer import ( + KEY_FOR_TYPE_AND_SCHEME, + Key, + SigstoreKey, + SigstoreSigner, +) + +KEY_FOR_TYPE_AND_SCHEME.update( + { + ("sigstore-oidc", "Fulcio"): SigstoreKey, + } +) + + +class TestSigstoreSigner(unittest.TestCase): + """Test public key parsing, signature creation and verification. + + Requires ambient credentials for signing (e.g. from GitHub Action). + + See sigstore-python docs for more infos about ambient credentials: + https://github.com/sigstore/sigstore-python#signing-with-ambient-credentials + + See securesystemslib SigstoreSigner docs for how to test locally. + """ + + def test_sign(self): + token = detect_credential() + self.assertIsNotNone(token, "ambient credentials required") + + identity = os.getenv("CERT_ID") + self.assertIsNotNone(token, "certificate identity required") + + issuer = os.getenv("CERT_ISSUER") + self.assertIsNotNone(token, "OIDC issuer required") + + public_key = Key.from_dict( + "abcdef", + { + "keytype": "sigstore-oidc", + "scheme": "Fulcio", + "keyval": { + "issuer": issuer, + "identity": identity, + }, + }, + ) + + signer = SigstoreSigner(token, public_key) + sig = signer.sign(b"data") + public_key.verify_signature(sig, b"data") + + +if __name__ == "__main__": + unittest.main(verbosity=4, buffer=False) diff --git a/tox.ini b/tox.ini index 21723355..f428fc0d 100644 --- a/tox.ini +++ b/tox.ini @@ -45,6 +45,19 @@ passenv = commands = python -m tests.check_kms_signers +[testenv:sigstore] +deps = + -r{toxinidir}/requirements-pinned.txt + -r{toxinidir}/requirements-sigstore.txt +passenv = + GITHUB_ACTIONS + ACTIONS_ID_TOKEN_REQUEST_TOKEN + ACTIONS_ID_TOKEN_REQUEST_URL + CERT_ID + CERT_ISSUER +commands = + python -m tests.check_sigstore_signer + # This checks that importing securesystemslib.gpg.constants doesn't shell out on # import. [testenv:py311-test-gpg-fails] From 10ac123b309fd1d22bca49b35c041cca5daac979 Mon Sep 17 00:00:00 2001 From: Lukas Puehringer Date: Tue, 7 Mar 2023 11:29:26 +0100 Subject: [PATCH 02/10] signer: refactor sigstore signer exceptions import Signed-off-by: Lukas Puehringer --- securesystemslib/signer/_sigstore_signer.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/securesystemslib/signer/_sigstore_signer.py b/securesystemslib/signer/_sigstore_signer.py index 3a900656..737fd3d0 100644 --- a/securesystemslib/signer/_sigstore_signer.py +++ b/securesystemslib/signer/_sigstore_signer.py @@ -40,7 +40,10 @@ import logging from typing import Any, Dict, Optional -from securesystemslib import exceptions +from securesystemslib.exceptions import ( + UnverifiedSignatureError, + VerificationError, +) from securesystemslib.signer._signer import ( Key, SecretsHandler, @@ -97,15 +100,15 @@ def verify_signature(self, signature: Signature, data: bytes) -> None: logger.info( "Key %s failed to verify sig: %s", self.keyid, result.reason ) - raise exceptions.UnverifiedSignatureError( + raise UnverifiedSignatureError( f"Failed to verify signature by {self.keyid}" ) - except exceptions.UnverifiedSignatureError: + except UnverifiedSignatureError: raise except Exception as e: logger.info("Key %s failed to verify sig: %s", self.keyid, str(e)) - raise exceptions.VerificationError( + raise VerificationError( f"Unknown failure to verify signature by {self.keyid}" ) from e From ba73dc9a4a480717732c3d0bd8e68e60e2b9bd6b Mon Sep 17 00:00:00 2001 From: Lukas Puehringer Date: Tue, 7 Mar 2023 11:58:51 +0100 Subject: [PATCH 03/10] signer: fix imports in sigstore signer The sigstore library is an optional and circular dependency in securesystemslib. Importing it in the methods, where it is needed, allows importing the module from within sigstore (circular import), or if sigstore is not installed. This commit wraps the local scope imports in try/except to fail gracefully if the methods are called w/o sigstore available, and locally disables the related linter warning. The commit also disables a linter import warning in the related test, which requires sigstore. Alternatively, we could install sigstore in the lint job, which does not seem worth the cycles, just for one method call. Signed-off-by: Lukas Puehringer --- securesystemslib/signer/_sigstore_signer.py | 19 +++++++++++++++---- tests/check_sigstore_signer.py | 2 +- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/securesystemslib/signer/_sigstore_signer.py b/securesystemslib/signer/_sigstore_signer.py index 737fd3d0..5bc3cfd0 100644 --- a/securesystemslib/signer/_sigstore_signer.py +++ b/securesystemslib/signer/_sigstore_signer.py @@ -41,6 +41,7 @@ from typing import Any, Dict, Optional from securesystemslib.exceptions import ( + UnsupportedLibraryError, UnverifiedSignatureError, VerificationError, ) @@ -51,6 +52,8 @@ Signer, ) +IMPORT_ERROR = "sigstore library required to use 'sigstore-oidc' keys" + logger = logging.getLogger(__name__) @@ -80,9 +83,13 @@ def to_dict(self) -> Dict: } def verify_signature(self, signature: Signature, data: bytes) -> None: - from sigstore.verify import VerificationMaterials, Verifier - from sigstore.verify.policy import Identity - from sigstore_protobuf_specs.dev.sigstore.bundle.v1 import Bundle + # pylint: disable=import-outside-toplevel + try: + from sigstore.verify import VerificationMaterials, Verifier + from sigstore.verify.policy import Identity + from sigstore_protobuf_specs.dev.sigstore.bundle.v1 import Bundle + except ImportError as e: + raise UnsupportedLibraryError(IMPORT_ERROR) from e verifier = Verifier.production() identity = Identity( @@ -150,7 +157,11 @@ def sign(self, payload: bytes) -> Signature: ``Signature` interface, which expect the attribute to be of type `str`. """ - from sigstore.sign import Signer as _Signer + # pylint: disable=import-outside-toplevel + try: + from sigstore.sign import Signer as _Signer + except ImportError as e: + raise UnsupportedLibraryError(IMPORT_ERROR) from e signer = _Signer.production() result = signer.sign(io.BytesIO(payload), self._token) diff --git a/tests/check_sigstore_signer.py b/tests/check_sigstore_signer.py index 8fc2c5c7..b2ebae43 100644 --- a/tests/check_sigstore_signer.py +++ b/tests/check_sigstore_signer.py @@ -8,7 +8,7 @@ import os import unittest -from sigstore.oidc import detect_credential +from sigstore.oidc import detect_credential # pylint: disable=import-error from securesystemslib.signer import ( KEY_FOR_TYPE_AND_SCHEME, From 94239fb9b0f854a5996ce30a4c2e35d1f0672b5c Mon Sep 17 00:00:00 2001 From: Lukas Puehringer Date: Tue, 7 Mar 2023 13:24:49 +0100 Subject: [PATCH 04/10] signer: fix SigstoreKey error handling in verify Key.verify_signature implementations should only raise UnverifiedSignatureError or VerificationError, even if an optional dependency is missing. This commit wraps everything in the corresponding try block and removes the previously added UnsupportedLibraryError on ImportError. Plus some minor renames for horizontal density. Signed-off-by: Lukas Puehringer --- securesystemslib/signer/_sigstore_signer.py | 23 ++++++++------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/securesystemslib/signer/_sigstore_signer.py b/securesystemslib/signer/_sigstore_signer.py index 5bc3cfd0..3ddb0b68 100644 --- a/securesystemslib/signer/_sigstore_signer.py +++ b/securesystemslib/signer/_sigstore_signer.py @@ -88,21 +88,16 @@ def verify_signature(self, signature: Signature, data: bytes) -> None: from sigstore.verify import VerificationMaterials, Verifier from sigstore.verify.policy import Identity from sigstore_protobuf_specs.dev.sigstore.bundle.v1 import Bundle - except ImportError as e: - raise UnsupportedLibraryError(IMPORT_ERROR) from e - - verifier = Verifier.production() - identity = Identity( - identity=self.keyval["identity"], issuer=self.keyval["issuer"] - ) - signature_bundle = Bundle().from_dict(signature.signature) - verification_materials = VerificationMaterials.from_bundle( - input_=io.BytesIO(data), bundle=signature_bundle, offline=True - ) - - try: - result = verifier.verify(verification_materials, identity) + verifier = Verifier.production() + identity = Identity( + identity=self.keyval["identity"], issuer=self.keyval["issuer"] + ) + bundle = Bundle().from_dict(signature.signature) + materials = VerificationMaterials.from_bundle( + input_=io.BytesIO(data), bundle=bundle, offline=True + ) + result = verifier.verify(materials, identity) if not result: logger.info( "Key %s failed to verify sig: %s", self.keyid, result.reason From 185947410caec8cf9236b690c5a7ac3b50b51b62 Mon Sep 17 00:00:00 2001 From: Lukas Puehringer Date: Tue, 7 Mar 2023 13:34:37 +0100 Subject: [PATCH 05/10] signer: make sigstore signature API compatible The Signature.signature field is defined as hex string of the signature byte data. But for sigstore we need to store a complex signature bundle, which also contains signature byte data. To stay compatible with the API, we store both, the signature byte data from the bundle in the Signature.signature field, and the whole bundle under unrecognized_fields. Signed-off-by: Lukas Puehringer --- securesystemslib/signer/_sigstore_signer.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/securesystemslib/signer/_sigstore_signer.py b/securesystemslib/signer/_sigstore_signer.py index 3ddb0b68..0fcb2e41 100644 --- a/securesystemslib/signer/_sigstore_signer.py +++ b/securesystemslib/signer/_sigstore_signer.py @@ -93,7 +93,7 @@ def verify_signature(self, signature: Signature, data: bytes) -> None: identity = Identity( identity=self.keyval["identity"], issuer=self.keyval["issuer"] ) - bundle = Bundle().from_dict(signature.signature) + bundle = Bundle().from_dict(signature.unrecognized_fields["bundle"]) materials = VerificationMaterials.from_bundle( input_=io.BytesIO(data), bundle=bundle, offline=True ) @@ -146,10 +146,7 @@ def sign(self, payload: bytes) -> Signature: Returns: Signature. - NOTE: The ``signature`` attribute of the returned object - contains the ``dict`` representation of a sigstore ``Bundle`. - This is incompatible with the TUF specification and the - ``Signature` interface, which expect the attribute to be of type `str`. + NOTE: The relevant data is in `unrecognized_fields["bundle"]`. """ # pylint: disable=import-outside-toplevel @@ -160,6 +157,11 @@ def sign(self, payload: bytes) -> Signature: signer = _Signer.production() result = signer.sign(io.BytesIO(payload), self._token) - # TODO: Ask upstream if they can make this public. - sig = result._to_bundle().to_dict() # pylint: disable=protected-access - return Signature(self.public_key.keyid, sig) + # TODO: Ask upstream if they can make this public + bundle = result._to_bundle() # pylint: disable=protected-access + + return Signature( + self.public_key.keyid, + bundle.message_signature.signature.hex(), + {"bundle": bundle.to_dict()}, + ) From 7aa2123f7d4abdd7efe7b8a1c91973597c56ecab Mon Sep 17 00:00:00 2001 From: Lukas Puehringer Date: Tue, 7 Mar 2023 14:38:43 +0100 Subject: [PATCH 06/10] mypy: ignore missing imports for sigstore Signed-off-by: Lukas Puehringer --- mypy.ini | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/mypy.ini b/mypy.ini index cbe4e0f7..1a25ff52 100644 --- a/mypy.ini +++ b/mypy.ini @@ -20,3 +20,9 @@ ignore_missing_imports = True [mypy-asn1crypto.*] ignore_missing_imports = True + +[mypy-sigstore.*] +ignore_missing_imports = True + +[mypy-sigstore_protobuf_specs.*] +ignore_missing_imports = True From 2b19bae7427dcc8af6ed76e2e84524db013b6cc4 Mon Sep 17 00:00:00 2001 From: Lukas Puehringer Date: Tue, 7 Mar 2023 14:58:05 +0100 Subject: [PATCH 07/10] signer: bump sigstore dependency Signed-off-by: Lukas Puehringer --- requirements-sigstore.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-sigstore.txt b/requirements-sigstore.txt index 16fad530..4f8ef56f 100644 --- a/requirements-sigstore.txt +++ b/requirements-sigstore.txt @@ -1 +1 @@ -sigstore==1.1.0 +sigstore==1.1.1 From b7b4191a7abe9364130fb61dc4aa2d0c205a499c Mon Sep 17 00:00:00 2001 From: Lukas Puehringer Date: Tue, 7 Mar 2023 15:23:57 +0100 Subject: [PATCH 08/10] lint: disable sigstore verify import warning Appease linter w/o installing optional requirement in linter run. Signed-off-by: Lukas Puehringer --- securesystemslib/signer/_sigstore_signer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/securesystemslib/signer/_sigstore_signer.py b/securesystemslib/signer/_sigstore_signer.py index 0fcb2e41..98f6f381 100644 --- a/securesystemslib/signer/_sigstore_signer.py +++ b/securesystemslib/signer/_sigstore_signer.py @@ -83,7 +83,7 @@ def to_dict(self) -> Dict: } def verify_signature(self, signature: Signature, data: bytes) -> None: - # pylint: disable=import-outside-toplevel + # pylint: disable=import-outside-toplevel,import-error try: from sigstore.verify import VerificationMaterials, Verifier from sigstore.verify.policy import Identity From 717f73ae66674ffc20d6f95b7eaba7e7772f75bb Mon Sep 17 00:00:00 2001 From: Lukas Puehringer Date: Tue, 7 Mar 2023 15:29:56 +0100 Subject: [PATCH 09/10] ci: adopt recent branch rename Signed-off-by: Lukas Puehringer --- .github/workflows/test-sigstore.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-sigstore.yml b/.github/workflows/test-sigstore.yml index 012a4cc4..84e1c35e 100644 --- a/.github/workflows/test-sigstore.yml +++ b/.github/workflows/test-sigstore.yml @@ -3,7 +3,7 @@ name: Run Sigstore Signer tests on: push: branches: - - master + - main pull_request: workflow_dispatch: From 345d4280e616a93167ec251e06c79e907b48b695 Mon Sep 17 00:00:00 2001 From: Lukas Puehringer Date: Thu, 9 Mar 2023 10:51:31 +0100 Subject: [PATCH 10/10] signer: update sigstore docs + minor refactor As per Jussi's CR: - Add comments to explain env vars - Add comments to mark API as unstable - Refactor raise-except-raise pattern Signed-off-by: Lukas Puehringer --- securesystemslib/signer/_sigstore_signer.py | 30 +++++++++++++-------- tox.ini | 1 + 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/securesystemslib/signer/_sigstore_signer.py b/securesystemslib/signer/_sigstore_signer.py index 98f6f381..cf4c5568 100644 --- a/securesystemslib/signer/_sigstore_signer.py +++ b/securesystemslib/signer/_sigstore_signer.py @@ -58,7 +58,10 @@ class SigstoreKey(Key): - """Sigstore verifier.""" + """Sigstore verifier. + + NOTE: unstable API - routines and metadata formats may change! + """ @classmethod def from_dict(cls, keyid: str, key_dict: Dict[str, Any]) -> "SigstoreKey": @@ -84,6 +87,7 @@ 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.verify.policy import Identity @@ -98,15 +102,6 @@ def verify_signature(self, signature: Signature, data: bytes) -> None: input_=io.BytesIO(data), bundle=bundle, offline=True ) result = verifier.verify(materials, identity) - if not result: - logger.info( - "Key %s failed to verify sig: %s", self.keyid, result.reason - ) - raise UnverifiedSignatureError( - f"Failed to verify signature by {self.keyid}" - ) - except UnverifiedSignatureError: - raise except Exception as e: logger.info("Key %s failed to verify sig: %s", self.keyid, str(e)) @@ -114,9 +109,22 @@ def verify_signature(self, signature: Signature, data: bytes) -> None: f"Unknown failure to verify signature by {self.keyid}" ) from e + if not result: + logger.info( + "Key %s failed to verify sig: %s", + self.keyid, + getattr(result, "reason", ""), + ) + raise UnverifiedSignatureError( + f"Failed to verify signature by {self.keyid}" + ) + class SigstoreSigner(Signer): - """Sigstore signer.""" + """Sigstore signer. + + NOTE: unstable API - routines and metadata formats may change! + """ def __init__(self, token: str, public_key: Key): # TODO: Vet public key diff --git a/tox.ini b/tox.ini index f428fc0d..e6e52b70 100644 --- a/tox.ini +++ b/tox.ini @@ -50,6 +50,7 @@ deps = -r{toxinidir}/requirements-pinned.txt -r{toxinidir}/requirements-sigstore.txt passenv = + # These are required to detect ambient credentials on GitHub GITHUB_ACTIONS ACTIONS_ID_TOKEN_REQUEST_TOKEN ACTIONS_ID_TOKEN_REQUEST_URL