From efd4119054aaa003ffca8f6e33579fb4a4b1b441 Mon Sep 17 00:00:00 2001 From: William Woodruff Date: Fri, 10 May 2024 14:18:10 -0400 Subject: [PATCH 01/18] sigstore: refactor trust state management Signed-off-by: William Woodruff --- sigstore/_cli.py | 114 +++--------------- sigstore/_internal/{trustroot.py => trust.py} | 0 2 files changed, 16 insertions(+), 98 deletions(-) rename sigstore/_internal/{trustroot.py => trust.py} (100%) diff --git a/sigstore/_cli.py b/sigstore/_cli.py index 1835bc299..da473ea8c 100644 --- a/sigstore/_cli.py +++ b/sigstore/_cli.py @@ -27,17 +27,8 @@ from rich.logging import RichHandler from sigstore import __version__ -from sigstore._internal.fulcio.client import ( - DEFAULT_FULCIO_URL, - ExpiredCertificate, - FulcioClient, -) +from sigstore._internal.fulcio.client import ExpiredCertificate from sigstore._internal.rekor import _hashedrekord_from_parts -from sigstore._internal.rekor.client import ( - DEFAULT_REKOR_URL, - RekorClient, -) -from sigstore._internal.trustroot import KeyringPurpose, TrustedRoot from sigstore._utils import sha256_digest from sigstore.errors import Error, VerificationError from sigstore.hashes import Hashed @@ -95,35 +86,6 @@ def _boolify_env(envvar: str) -> bool: raise ValueError(f"can't coerce '{val}' to a boolean") -def _add_shared_instance_options(group: argparse._ArgumentGroup) -> None: - """ - Common Sigstore instance options, shared between all `sigstore` subcommands. - """ - group.add_argument( - "--staging", - dest="__deprecated_staging", - action="store_true", - default=False, - help=( - "Use sigstore's staging instances, instead of the default production instances. " - "This option will be deprecated in favor of the global `--staging` option " - "in a future release." - ), - ) - group.add_argument( - "--rekor-url", - dest="__deprecated_rekor_url", - metavar="URL", - type=str, - default=None, - help=( - "The Rekor instance to use (conflicts with --staging). " - "This option will be deprecated in favor of the global `--rekor-url` option " - "in a future release." - ), - ) - - def _add_shared_verify_input_options(group: argparse._ArgumentGroup) -> None: """ Common input options, shared between all `sigstore verify` subcommands. @@ -238,7 +200,7 @@ def _parser() -> argparse.ArgumentParser: "-V", "--version", action="version", version=f"sigstore {__version__}" ) - global_instance_options = parser.add_argument_group("Sigstore instance options") + global_instance_options = parser.add_mutually_exclusive_group() global_instance_options.add_argument( "--staging", action="store_true", @@ -246,13 +208,11 @@ def _parser() -> argparse.ArgumentParser: help="Use sigstore's staging instances, instead of the default production instances", ) global_instance_options.add_argument( - "--rekor-url", - metavar="URL", + "--trust-config", + metavar="FILE", type=str, - default=os.getenv("SIGSTORE_REKOR_URL", DEFAULT_REKOR_URL), - help="The Rekor instance to use (conflicts with --staging)", + help="The client trust configuration to use", ) - subcommands = parser.add_subparsers( required=True, dest="subcommand", @@ -332,16 +292,6 @@ def _parser() -> argparse.ArgumentParser: help="Overwrite preexisting signature and certificate outputs, if present", ) - instance_options = sign.add_argument_group("Sigstore instance options") - _add_shared_instance_options(instance_options) - instance_options.add_argument( - "--fulcio-url", - metavar="URL", - type=str, - default=os.getenv("SIGSTORE_FULCIO_URL", DEFAULT_FULCIO_URL), - help="The Fulcio instance to use (conflicts with --staging)", - ) - sign.add_argument( "files", metavar="FILE", @@ -385,9 +335,6 @@ def _parser() -> argparse.ArgumentParser: required=True, ) - instance_options = verify_identity.add_argument_group("Sigstore instance options") - _add_shared_instance_options(instance_options) - # `sigstore verify github` verify_github = verify_subcommand.add_parser( "github", @@ -442,9 +389,6 @@ def _parser() -> argparse.ArgumentParser: help="The `git` ref that the workflow was invoked with", ) - instance_options = verify_github.add_argument_group("Sigstore instance options") - _add_shared_instance_options(instance_options) - # `sigstore get-identity-token` get_identity_token = subcommands.add_parser( "get-identity-token", @@ -469,22 +413,6 @@ def main() -> None: _logger.debug(f"parsed arguments {args}") - # A few instance flags (like `--staging` and `--rekor-url`) are supported at both the - # top-level `sigstore` level and the subcommand level (e.g. `sigstore verify --staging`), - # but the former is preferred. - if getattr(args, "__deprecated_staging", False): - _logger.warning( - "`--staging` should be used as a global option, rather than a subcommand option. " - "Passing `--staging` as a subcommand option will be deprecated in a future release." - ) - args.staging = args.__deprecated_staging - if getattr(args, "__deprecated_rekor_url", None): - _logger.warning( - "`--rekor-url` should be used as a global option, rather than a subcommand option. " - "Passing `--rekor-url` as a subcommand option will be deprecated in a future release." - ) - args.rekor_url = args.__deprecated_rekor_url - # Stuff the parser back into our namespace, so that we can use it for # error handling later. args._parser = parser @@ -588,17 +516,13 @@ def _sign(args: argparse.Namespace) -> None: _logger.debug("sign: staging instances requested") signing_ctx = SigningContext.staging() args.oidc_issuer = STAGING_OAUTH_ISSUER_URL - elif args.fulcio_url == DEFAULT_FULCIO_URL and args.rekor_url == DEFAULT_REKOR_URL: - signing_ctx = SigningContext.production() + elif args.trust_config: + raise ValueError("fuck") else: - # Assume "production" trust root if no keys are given as arguments - trusted_root = TrustedRoot.production(purpose=KeyringPurpose.SIGN) - - signing_ctx = SigningContext( - fulcio=FulcioClient(args.fulcio_url), - rekor=RekorClient(args.rekor_url), - trusted_root=trusted_root, - ) + # If the user didn't request the staging instance or pass in an + # explicit client trust config, we're using the public good (i.e. + # production) instance. + signing_ctx = SigningContext.production() # The order of precedence for identities is as follows: # @@ -738,8 +662,8 @@ def _collect_verification_state( missing.append(str(cert)) input_map[file] = {"cert": cert, "sig": sig} else: - # If a user hasn't explicitly supplied `--signature`, `--certificate` or - # `--rekor-bundle`, we expect a bundle either supplied via `--bundle` or with the + # If a user hasn't explicitly supplied `--signature` or `--certificate`, + # we expect a bundle either supplied via `--bundle` or with the # default `{input}.sigstore(.json)?` name. if not bundle.is_file(): missing.append(str(bundle)) @@ -754,16 +678,10 @@ def _collect_verification_state( if args.staging: _logger.debug("verify: staging instances requested") verifier = Verifier.staging() - elif args.rekor_url == DEFAULT_REKOR_URL: - verifier = Verifier.production() + elif args.trust_config: + raise ValueError("fuck") else: - trusted_root = TrustedRoot.production(purpose=KeyringPurpose.VERIFY) - verifier = Verifier( - rekor=RekorClient( - url=args.rekor_url, - ), - trusted_root=trusted_root, - ) + verifier = Verifier.production() all_materials = [] for file, inputs in input_map.items(): diff --git a/sigstore/_internal/trustroot.py b/sigstore/_internal/trust.py similarity index 100% rename from sigstore/_internal/trustroot.py rename to sigstore/_internal/trust.py From b94888b46eb1c33d3ee522f88a4a551d7687d7be Mon Sep 17 00:00:00 2001 From: William Woodruff Date: Fri, 10 May 2024 14:18:49 -0400 Subject: [PATCH 02/18] test: rename Signed-off-by: William Woodruff --- test/unit/internal/{test_trust_root.py => test_trust.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename test/unit/internal/{test_trust_root.py => test_trust.py} (100%) diff --git a/test/unit/internal/test_trust_root.py b/test/unit/internal/test_trust.py similarity index 100% rename from test/unit/internal/test_trust_root.py rename to test/unit/internal/test_trust.py From e5d24b05d9de7895f76109a1b95189c4221181b6 Mon Sep 17 00:00:00 2001 From: William Woodruff Date: Fri, 10 May 2024 14:19:13 -0400 Subject: [PATCH 03/18] docstring Signed-off-by: William Woodruff --- sigstore/_internal/trust.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sigstore/_internal/trust.py b/sigstore/_internal/trust.py index 8b60b263b..6dca56b56 100644 --- a/sigstore/_internal/trust.py +++ b/sigstore/_internal/trust.py @@ -13,7 +13,7 @@ # limitations under the License. """ -Trust root management for sigstore-python. +Client trust configuration and trust root management for sigstore-python. """ from __future__ import annotations From 0be13aae77aaa9696e8c9afe572527a5b17401eb Mon Sep 17 00:00:00 2001 From: William Woodruff Date: Fri, 10 May 2024 14:25:58 -0400 Subject: [PATCH 04/18] sigstore, test: propagate rename Signed-off-by: William Woodruff --- sigstore/_internal/rekor/checkpoint.py | 2 +- sigstore/_internal/sct.py | 2 +- sigstore/models.py | 2 +- sigstore/sign.py | 2 +- sigstore/verify/verifier.py | 2 +- test/unit/internal/test_trust.py | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/sigstore/_internal/rekor/checkpoint.py b/sigstore/_internal/rekor/checkpoint.py index c49ae67b9..a02c3cbb2 100644 --- a/sigstore/_internal/rekor/checkpoint.py +++ b/sigstore/_internal/rekor/checkpoint.py @@ -27,7 +27,7 @@ from pydantic import BaseModel, Field, StrictStr -from sigstore._internal.trustroot import RekorKeyring +from sigstore._internal.trust import RekorKeyring from sigstore._utils import KeyID from sigstore.errors import VerificationError diff --git a/sigstore/_internal/sct.py b/sigstore/_internal/sct.py index 8a6a943f7..e9ad5a45f 100644 --- a/sigstore/_internal/sct.py +++ b/sigstore/_internal/sct.py @@ -35,7 +35,7 @@ ) from cryptography.x509.oid import ExtendedKeyUsageOID -from sigstore._internal.trustroot import CTKeyring +from sigstore._internal.trust import CTKeyring from sigstore._utils import ( DERCert, KeyID, diff --git a/sigstore/models.py b/sigstore/models.py index cb7f60257..7b03cc537 100644 --- a/sigstore/models.py +++ b/sigstore/models.py @@ -65,7 +65,7 @@ from sigstore.errors import Error, VerificationError if typing.TYPE_CHECKING: - from sigstore._internal.trustroot import RekorKeyring + from sigstore._internal.trust import RekorKeyring _logger = logging.getLogger(__name__) diff --git a/sigstore/sign.py b/sigstore/sign.py index d7d3a21f6..e95981e58 100644 --- a/sigstore/sign.py +++ b/sigstore/sign.py @@ -62,7 +62,7 @@ ) from sigstore._internal.rekor.client import RekorClient from sigstore._internal.sct import verify_sct -from sigstore._internal.trustroot import KeyringPurpose, TrustedRoot +from sigstore._internal.trust import KeyringPurpose, TrustedRoot from sigstore._utils import sha256_digest from sigstore.models import Bundle from sigstore.oidc import ExpiredIdentity, IdentityToken diff --git a/sigstore/verify/verifier.py b/sigstore/verify/verifier.py index bdf48b387..d7944acff 100644 --- a/sigstore/verify/verifier.py +++ b/sigstore/verify/verifier.py @@ -44,7 +44,7 @@ _get_precertificate_signed_certificate_timestamps, verify_sct, ) -from sigstore._internal.trustroot import KeyringPurpose, TrustedRoot +from sigstore._internal.trust import KeyringPurpose, TrustedRoot from sigstore._utils import base64_encode_pem_cert, sha256_digest from sigstore.errors import VerificationError from sigstore.hashes import Hashed diff --git a/test/unit/internal/test_trust.py b/test/unit/internal/test_trust.py index 0cc4cab64..a69cbdd0b 100644 --- a/test/unit/internal/test_trust.py +++ b/test/unit/internal/test_trust.py @@ -21,7 +21,7 @@ from cryptography.x509 import load_pem_x509_certificate from sigstore_protobuf_specs.dev.sigstore.common.v1 import TimeRange -from sigstore._internal.trustroot import ( +from sigstore._internal.trust import ( KeyringPurpose, TrustedRoot, _is_timerange_valid, From 172fc0e3b8be468629838233d9b1752fa418212e Mon Sep 17 00:00:00 2001 From: William Woodruff Date: Fri, 10 May 2024 15:55:20 -0400 Subject: [PATCH 05/18] _internal: hackety hack Signed-off-by: William Woodruff --- sigstore/_internal/trust.py | 37 ++++++++++++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/sigstore/_internal/trust.py b/sigstore/_internal/trust.py index 6dca56b56..3e029bccf 100644 --- a/sigstore/_internal/trust.py +++ b/sigstore/_internal/trust.py @@ -41,6 +41,9 @@ CertificateAuthority, TransparencyLogInstance, ) +from sigstore_protobuf_specs.dev.sigstore.trustroot.v1 import ( + ClientTrustConfig as _ClientTrustConfig, +) from sigstore_protobuf_specs.dev.sigstore.trustroot.v1 import ( TrustedRoot as _TrustedRoot, ) @@ -266,15 +269,14 @@ def staging( """ return cls.from_tuf(STAGING_TUF_URL, offline, purpose) - @staticmethod def _get_tlog_keys( - tlogs: list[TransparencyLogInstance], purpose: KeyringPurpose + self, tlogs: list[TransparencyLogInstance] ) -> Iterable[_PublicKey]: """ Yields an iterator of public keys for transparency log instances that are suitable for `purpose`. """ - allow_expired = purpose is KeyringPurpose.VERIFY + allow_expired = self.purpose is KeyringPurpose.VERIFY for tlog in tlogs: if not _is_timerange_valid( tlog.public_key.valid_for, allow_expired=allow_expired @@ -298,14 +300,14 @@ def _get_ca_keys( def rekor_keyring(self) -> RekorKeyring: """Return keyring with keys for Rekor.""" - keys: list[_PublicKey] = list(self._get_tlog_keys(self.tlogs, self.purpose)) + keys: list[_PublicKey] = list(self._get_tlog_keys(self.tlogs)) if len(keys) != 1: raise MetadataError("Did not find one Rekor key in trusted root") return RekorKeyring(Keyring(keys)) def ct_keyring(self) -> CTKeyring: """Return keyring with key for CTFE.""" - ctfes: list[_PublicKey] = list(self._get_tlog_keys(self.ctlogs, self.purpose)) + ctfes: list[_PublicKey] = list(self._get_tlog_keys(self.ctlogs)) if not ctfes: raise MetadataError("CTFE keys not found in trusted root") return CTKeyring(Keyring(ctfes)) @@ -324,3 +326,28 @@ def get_fulcio_certs(self) -> list[Certificate]: if not certs: raise MetadataError("Fulcio certificates not found in trusted root") return certs + + +class ClientTrustConfig: + """ + Represents a Sigstore client's trust configuration, including a root of trust. + """ + + @classmethod + def from_json(cls, raw: str) -> None: + """ + Deserialize the given client trust config. + """ + inner = _ClientTrustConfig().from_json(raw) + cls(inner) + + def __init__(self, inner: _ClientTrustConfig) -> None: + """ + @api private + """ + self._inner = inner + # self._ + + # @property + # def trusted_root(self) -> TrustedRoot: + # pass From 89990947b234668110c4ce90dd59bc7b39dc4429 Mon Sep 17 00:00:00 2001 From: William Woodruff Date: Tue, 14 May 2024 16:17:12 -0400 Subject: [PATCH 06/18] sigstore: refactor purpose handling Signed-off-by: William Woodruff --- sigstore/_internal/trust.py | 40 ++++++++++++++++++------------------- sigstore/sign.py | 19 +++++++++++------- sigstore/verify/verifier.py | 10 ++++------ 3 files changed, 36 insertions(+), 33 deletions(-) diff --git a/sigstore/_internal/trust.py b/sigstore/_internal/trust.py index ad18de3ad..c879c03eb 100644 --- a/sigstore/_internal/trust.py +++ b/sigstore/_internal/trust.py @@ -217,28 +217,28 @@ def __str__(self) -> str: return self.value -class TrustedRoot(_TrustedRoot): - """Complete set of trusted entities for a Sigstore client""" +class TrustedRoot: + """ + The cryptographic root(s) of trust for a Sigstore instance. + """ - purpose: KeyringPurpose + def __init__(self, inner: _TrustedRoot): + self._inner = inner @classmethod def from_file( cls, path: str, - purpose: KeyringPurpose = KeyringPurpose.VERIFY, ) -> TrustedRoot: """Create a new trust root from file""" - trusted_root: TrustedRoot = cls().from_json(Path(path).read_bytes()) - trusted_root.purpose = purpose - return trusted_root + inner = _TrustedRoot().from_json(Path(path).read_bytes()) + return cls(inner) @classmethod def from_tuf( cls, url: str, offline: bool = False, - purpose: KeyringPurpose = KeyringPurpose.VERIFY, ) -> TrustedRoot: """Create a new trust root from a TUF repository. @@ -246,42 +246,40 @@ def from_tuf( update the trust root from remote TUF repository. """ path = TrustUpdater(url, offline).get_trusted_root_path() - return cls.from_file(path, purpose) + return cls.from_file(path) @classmethod def production( cls, offline: bool = False, - purpose: KeyringPurpose = KeyringPurpose.VERIFY, ) -> TrustedRoot: """Create new trust root from Sigstore production TUF repository. If `offline`, will use trust root in local TUF cache. Otherwise will update the trust root from remote TUF repository. """ - return cls.from_tuf(DEFAULT_TUF_URL, offline, purpose) + return cls.from_tuf(DEFAULT_TUF_URL, offline) @classmethod def staging( cls, offline: bool = False, - purpose: KeyringPurpose = KeyringPurpose.VERIFY, ) -> TrustedRoot: """Create new trust root from Sigstore staging TUF repository. If `offline`, will use trust root in local TUF cache. Otherwise will update the trust root from remote TUF repository. """ - return cls.from_tuf(STAGING_TUF_URL, offline, purpose) + return cls.from_tuf(STAGING_TUF_URL, offline) def _get_tlog_keys( - self, tlogs: list[TransparencyLogInstance] + self, tlogs: list[TransparencyLogInstance], purpose: KeyringPurpose ) -> Iterable[_PublicKey]: """ Yields an iterator of public keys for transparency log instances that are suitable for `purpose`. """ - allow_expired = self.purpose is KeyringPurpose.VERIFY + allow_expired = purpose is KeyringPurpose.VERIFY for tlog in tlogs: if not _is_timerange_valid( tlog.public_key.valid_for, allow_expired=allow_expired @@ -302,17 +300,17 @@ def _get_ca_keys( for cert in ca.cert_chain.certificates: yield cert.raw_bytes - def rekor_keyring(self) -> RekorKeyring: + def rekor_keyring(self, purpose: KeyringPurpose) -> RekorKeyring: """Return keyring with keys for Rekor.""" - keys: list[_PublicKey] = list(self._get_tlog_keys(self.tlogs)) + keys: list[_PublicKey] = list(self._get_tlog_keys(self._inner.tlogs, purpose)) if len(keys) != 1: raise MetadataError("Did not find one Rekor key in trusted root") return RekorKeyring(Keyring(keys)) - def ct_keyring(self) -> CTKeyring: + def ct_keyring(self, purpose: KeyringPurpose) -> CTKeyring: """Return keyring with key for CTFE.""" - ctfes: list[_PublicKey] = list(self._get_tlog_keys(self.ctlogs)) + ctfes: list[_PublicKey] = list(self._get_tlog_keys(self._inner.ctlogs, purpose)) if not ctfes: raise MetadataError("CTFE keys not found in trusted root") return CTKeyring(Keyring(ctfes)) @@ -326,7 +324,9 @@ def get_fulcio_certs(self) -> list[Certificate]: # been active when the certificate was used to sign. certs = [ load_der_x509_certificate(c) - for c in self._get_ca_keys(self.certificate_authorities, allow_expired=True) + for c in self._get_ca_keys( + self._inner.certificate_authorities, allow_expired=True + ) ] if not certs: raise MetadataError("Fulcio certificates not found in trusted root") diff --git a/sigstore/sign.py b/sigstore/sign.py index e95981e58..1c59d345b 100644 --- a/sigstore/sign.py +++ b/sigstore/sign.py @@ -165,7 +165,12 @@ def _signing_cert( cert = certificate_response.cert chain = certificate_response.chain - verify_sct(sct, cert, chain, self._signing_ctx._trusted_root.ct_keyring()) + verify_sct( + sct, + cert, + chain, + self._signing_ctx._trusted_root.ct_keyring(KeyringPurpose.SIGN), + ) _logger.debug("Successfully verified SCT...") @@ -311,10 +316,10 @@ def production(cls) -> SigningContext: """ Return a `SigningContext` instance configured against Sigstore's production-level services. """ - trusted_root = TrustedRoot.production(purpose=KeyringPurpose.SIGN) - rekor = RekorClient.production() return cls( - fulcio=FulcioClient.production(), rekor=rekor, trusted_root=trusted_root + fulcio=FulcioClient.production(), + rekor=RekorClient.production(), + trusted_root=TrustedRoot.production(), ) @classmethod @@ -322,10 +327,10 @@ def staging(cls) -> SigningContext: """ Return a `SignerContext` instance configured against Sigstore's staging-level services. """ - trusted_root = TrustedRoot.staging(purpose=KeyringPurpose.SIGN) - rekor = RekorClient.staging() return cls( - fulcio=FulcioClient.staging(), rekor=rekor, trusted_root=trusted_root + fulcio=FulcioClient.staging(), + rekor=RekorClient.staging(), + trusted_root=TrustedRoot.staging(), ) @contextmanager diff --git a/sigstore/verify/verifier.py b/sigstore/verify/verifier.py index d7944acff..278a57a19 100644 --- a/sigstore/verify/verifier.py +++ b/sigstore/verify/verifier.py @@ -81,10 +81,9 @@ def production(cls) -> Verifier: """ Return a `Verifier` instance configured against Sigstore's production-level services. """ - trusted_root = TrustedRoot.production(purpose=KeyringPurpose.VERIFY) return cls( rekor=RekorClient.production(), - trusted_root=trusted_root, + trusted_root=TrustedRoot.production(), ) @classmethod @@ -92,10 +91,9 @@ def staging(cls) -> Verifier: """ Return a `Verifier` instance configured against Sigstore's staging-level services. """ - trusted_root = TrustedRoot.staging(purpose=KeyringPurpose.VERIFY) return cls( rekor=RekorClient.staging(), - trusted_root=trusted_root, + trusted_root=TrustedRoot.staging(), ) def _verify_common_signing_cert( @@ -166,7 +164,7 @@ def _verify_common_signing_cert( sct, cert, [parent_cert.to_cryptography() for parent_cert in chain], - self._trusted_root.ct_keyring(), + self._trusted_root.ct_keyring(KeyringPurpose.VERIFY), ) except VerificationError as e: raise VerificationError(f"failed to verify SCT on signing certificate: {e}") @@ -190,7 +188,7 @@ def _verify_common_signing_cert( # (5): verify the inclusion promise for the log entry, if present. entry = bundle.log_entry try: - entry._verify(self._trusted_root.rekor_keyring()) + entry._verify(self._trusted_root.rekor_keyring(KeyringPurpose.VERIFY)) except VerificationError as exc: raise VerificationError(f"invalid log entry: {exc}") From fa05d2708e0cb9c8161615811e293da68bf64351 Mon Sep 17 00:00:00 2001 From: William Woodruff Date: Tue, 14 May 2024 16:27:51 -0400 Subject: [PATCH 07/18] trust: docstring Signed-off-by: William Woodruff --- sigstore/_internal/trust.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/sigstore/_internal/trust.py b/sigstore/_internal/trust.py index c879c03eb..77801cf90 100644 --- a/sigstore/_internal/trust.py +++ b/sigstore/_internal/trust.py @@ -223,6 +223,11 @@ class TrustedRoot: """ def __init__(self, inner: _TrustedRoot): + """ + Construct a new `TrustedRoot`. + + @api private + """ self._inner = inner @classmethod From a1c0784aa9f38699c6cbcff38eb27e6de60d711e Mon Sep 17 00:00:00 2001 From: William Woodruff Date: Tue, 14 May 2024 16:36:08 -0400 Subject: [PATCH 08/18] fixup trust tests Signed-off-by: William Woodruff --- test/unit/internal/test_trust.py | 78 +++++++++++++++++++++++--------- 1 file changed, 57 insertions(+), 21 deletions(-) diff --git a/test/unit/internal/test_trust.py b/test/unit/internal/test_trust.py index a69cbdd0b..cc55c8fcf 100644 --- a/test/unit/internal/test_trust.py +++ b/test/unit/internal/test_trust.py @@ -37,7 +37,7 @@ def test_trust_root_tuf_caches_and_requests(mock_staging_tuf, tuf_dirs): # keep track of requests the TrustUpdater invoked by TrustedRoot makes reqs, fail_reqs = mock_staging_tuf - trust_root = TrustedRoot.staging(purpose=KeyringPurpose.VERIFY) + trust_root = TrustedRoot.staging() # metadata was "downloaded" from staging expected = ["root.json", "snapshot.json", "targets.json", "timestamp.json"] assert sorted(os.listdir(data_dir)) == expected @@ -53,15 +53,15 @@ def test_trust_root_tuf_caches_and_requests(mock_staging_tuf, tuf_dirs): assert reqs == expected_requests assert fail_reqs == expected_fail_reqs - trust_root.ct_keyring() - trust_root.rekor_keyring() + trust_root.ct_keyring(KeyringPurpose.VERIFY) + trust_root.rekor_keyring(KeyringPurpose.VERIFY) # no new requests assert reqs == expected_requests assert fail_reqs == expected_fail_reqs # New trust root (and TrustUpdater instance), same cache dirs - trust_root = TrustedRoot.staging(purpose=KeyringPurpose.VERIFY) + trust_root = TrustedRoot.staging() # Expect new timestamp and root requests expected_requests["timestamp.json"] += 1 @@ -69,8 +69,8 @@ def test_trust_root_tuf_caches_and_requests(mock_staging_tuf, tuf_dirs): assert reqs == expected_requests assert fail_reqs == expected_fail_reqs - trust_root.ct_keyring() - trust_root.rekor_keyring() + trust_root.ct_keyring(purpose=KeyringPurpose.VERIFY) + trust_root.rekor_keyring(purpose=KeyringPurpose.VERIFY) # Expect no requests assert reqs == expected_requests assert fail_reqs == expected_fail_reqs @@ -83,7 +83,7 @@ def test_trust_root_tuf_offline(mock_staging_tuf, tuf_dirs): # keep track of requests the TrustUpdater invoked by TrustedRoot makes reqs, fail_reqs = mock_staging_tuf - trust_root = TrustedRoot.staging(offline=True, purpose=KeyringPurpose.VERIFY) + trust_root = TrustedRoot.staging(offline=True) # Only the embedded root is in local TUF metadata, nothing is downloaded expected = ["root.json"] @@ -91,8 +91,8 @@ def test_trust_root_tuf_offline(mock_staging_tuf, tuf_dirs): assert reqs == {} assert fail_reqs == {} - trust_root.ct_keyring() - trust_root.rekor_keyring() + trust_root.ct_keyring(purpose=KeyringPurpose.VERIFY) + trust_root.rekor_keyring(purpose=KeyringPurpose.VERIFY) # Still no requests assert reqs == {} @@ -153,35 +153,71 @@ def _pem_keys(keys): ] # Assert that trust root from TUF contains the expected keys/certs - trust_root = TrustedRoot.staging(purpose=KeyringPurpose.VERIFY) + trust_root = TrustedRoot.staging() assert ctfe_keys[0] in get_public_bytes( - [k.key for k in trust_root.ct_keyring()._keyring.values()] + [ + k.key + for k in trust_root.ct_keyring( + purpose=KeyringPurpose.VERIFY + )._keyring.values() + ] ) assert ( - get_public_bytes([k.key for k in trust_root.rekor_keyring()._keyring.values()]) + get_public_bytes( + [ + k.key + for k in trust_root.rekor_keyring( + purpose=KeyringPurpose.VERIFY + )._keyring.values() + ] + ) == rekor_keys ) assert trust_root.get_fulcio_certs() == fulcio_certs # Assert that trust root from offline TUF contains the expected keys/certs - trust_root = TrustedRoot.staging(offline=True, purpose=KeyringPurpose.VERIFY) + trust_root = TrustedRoot.staging(offline=True) assert ctfe_keys[0] in get_public_bytes( - [k.key for k in trust_root.ct_keyring()._keyring.values()] + [ + k.key + for k in trust_root.ct_keyring( + purpose=KeyringPurpose.VERIFY + )._keyring.values() + ] ) assert ( - get_public_bytes([k.key for k in trust_root.rekor_keyring()._keyring.values()]) + get_public_bytes( + [ + k.key + for k in trust_root.rekor_keyring( + purpose=KeyringPurpose.VERIFY + )._keyring.values() + ] + ) == rekor_keys ) assert trust_root.get_fulcio_certs() == fulcio_certs # Assert that trust root from file contains the expected keys/certs path = tuf_asset.target_path("trusted_root.json") - trust_root = TrustedRoot.from_file(path, purpose=KeyringPurpose.VERIFY) + trust_root = TrustedRoot.from_file(path) assert ctfe_keys[0] in get_public_bytes( - [k.key for k in trust_root.ct_keyring()._keyring.values()] + [ + k.key + for k in trust_root.ct_keyring( + purpose=KeyringPurpose.VERIFY + )._keyring.values() + ] ) assert ( - get_public_bytes([k.key for k in trust_root.rekor_keyring()._keyring.values()]) + get_public_bytes( + [ + k.key + for k in trust_root.rekor_keyring( + purpose=KeyringPurpose.VERIFY + )._keyring.values() + ] + ) == rekor_keys ) assert trust_root.get_fulcio_certs() == fulcio_certs @@ -194,14 +230,14 @@ def test_trust_root_tuf_instance_error(): def test_trust_root_tuf_ctfe_keys_error(monkeypatch): trust_root = TrustedRoot.staging(offline=True) - monkeypatch.setattr(trust_root, "ctlogs", []) + monkeypatch.setattr(trust_root._inner, "ctlogs", []) with pytest.raises(Exception, match="CTFE keys not found in trusted root"): - trust_root.ct_keyring() + trust_root.ct_keyring(purpose=KeyringPurpose.VERIFY) def test_trust_root_fulcio_certs_error(tuf_asset, monkeypatch): trust_root = TrustedRoot.staging(offline=True) - monkeypatch.setattr(trust_root, "certificate_authorities", []) + monkeypatch.setattr(trust_root._inner, "certificate_authorities", []) with pytest.raises( Exception, match="Fulcio certificates not found in trusted root" ): From 239397a6068dac0645305b92ea0f4b37d219e4a9 Mon Sep 17 00:00:00 2001 From: William Woodruff Date: Tue, 14 May 2024 16:42:49 -0400 Subject: [PATCH 09/18] test_sign: fixup Signed-off-by: William Woodruff --- test/unit/test_sign.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/test/unit/test_sign.py b/test/unit/test_sign.py index 233340eaf..5c2e2a496 100644 --- a/test/unit/test_sign.py +++ b/test/unit/test_sign.py @@ -66,7 +66,7 @@ def test_sct_verify_keyring_lookup_error(sign_ctx_and_ident_for_env, monkeypatch # a signer whose keyring always fails to lookup a given key. ctx: SigningContext = ctx() mock = pretend.stub( - ct_keyring=lambda: pretend.stub(verify=pretend.raiser(VerificationError)) + ct_keyring=lambda *a: pretend.stub(verify=pretend.raiser(VerificationError)) ) ctx._trusted_root = mock assert identity is not None @@ -85,10 +85,9 @@ def test_sct_verify_keyring_error(sign_ctx_and_ident_for_env, monkeypatch): # a signer whose keyring throws an internal error. ctx: SigningContext = ctx() mock = pretend.stub( - ct_keyring=lambda: pretend.stub(verify=pretend.raiser(VerificationError)) + ct_keyring=lambda *a: pretend.stub(verify=pretend.raiser(VerificationError)) ) ctx._trusted_root = mock - ctx._rekor._ct_keyring = pretend.stub(verify=pretend.raiser(VerificationError)) assert identity is not None payload = secrets.token_bytes(32) From 5c6b4f19ce6c1ec6fa865290f031ee6121d6f0f3 Mon Sep 17 00:00:00 2001 From: William Woodruff Date: Tue, 14 May 2024 16:59:53 -0400 Subject: [PATCH 10/18] hook up client trust config Signed-off-by: William Woodruff --- sigstore/_cli.py | 11 ++++++----- sigstore/_internal/trust.py | 14 ++++++++------ sigstore/sign.py | 15 ++++++++++++++- sigstore/verify/verifier.py | 14 +++++++++++++- 4 files changed, 41 insertions(+), 13 deletions(-) diff --git a/sigstore/_cli.py b/sigstore/_cli.py index c9cb8b684..9b9e8fa1e 100644 --- a/sigstore/_cli.py +++ b/sigstore/_cli.py @@ -29,13 +29,13 @@ from sigstore import __version__ from sigstore._internal.fulcio.client import ExpiredCertificate from sigstore._internal.rekor import _hashedrekord_from_parts +from sigstore._internal.trust import ClientTrustConfig from sigstore._utils import sha256_digest from sigstore.errors import Error, VerificationError from sigstore.hashes import Hashed from sigstore.models import Bundle from sigstore.oidc import ( DEFAULT_OAUTH_ISSUER_URL, - STAGING_OAUTH_ISSUER_URL, ExpiredIdentity, IdentityToken, Issuer, @@ -210,7 +210,7 @@ def _parser() -> argparse.ArgumentParser: global_instance_options.add_argument( "--trust-config", metavar="FILE", - type=str, + type=Path, help="The client trust configuration to use", ) subcommands = parser.add_subparsers( @@ -515,9 +515,9 @@ def _sign(args: argparse.Namespace) -> None: if args.staging: _logger.debug("sign: staging instances requested") signing_ctx = SigningContext.staging() - args.oidc_issuer = STAGING_OAUTH_ISSUER_URL elif args.trust_config: - raise ValueError("fuck") + trust_config = ClientTrustConfig.from_json(args.trust_config.read_text()) + signing_ctx = SigningContext._from_trust_config(trust_config) else: # If the user didn't request the staging instance or pass in an # explicit client trust config, we're using the public good (i.e. @@ -679,7 +679,8 @@ def _collect_verification_state( _logger.debug("verify: staging instances requested") verifier = Verifier.staging() elif args.trust_config: - raise ValueError("fuck") + trust_config = ClientTrustConfig.from_json(args.trust_config.read_text()) + verifier = Verifier._from_trust_config(trust_config) else: verifier = Verifier.production() diff --git a/sigstore/_internal/trust.py b/sigstore/_internal/trust.py index 77801cf90..6b863c596 100644 --- a/sigstore/_internal/trust.py +++ b/sigstore/_internal/trust.py @@ -344,20 +344,22 @@ class ClientTrustConfig: """ @classmethod - def from_json(cls, raw: str) -> None: + def from_json(cls, raw: str) -> ClientTrustConfig: """ Deserialize the given client trust config. """ inner = _ClientTrustConfig().from_json(raw) - cls(inner) + return cls(inner) def __init__(self, inner: _ClientTrustConfig) -> None: """ @api private """ self._inner = inner - # self._ - # @property - # def trusted_root(self) -> TrustedRoot: - # pass + @property + def trusted_root(self) -> TrustedRoot: + """ + Return the interior root of trust, as a `TrustedRoot`. + """ + return TrustedRoot(self._inner.trusted_root) diff --git a/sigstore/sign.py b/sigstore/sign.py index 1c59d345b..5392772c1 100644 --- a/sigstore/sign.py +++ b/sigstore/sign.py @@ -62,7 +62,7 @@ ) from sigstore._internal.rekor.client import RekorClient from sigstore._internal.sct import verify_sct -from sigstore._internal.trust import KeyringPurpose, TrustedRoot +from sigstore._internal.trust import ClientTrustConfig, KeyringPurpose, TrustedRoot from sigstore._utils import sha256_digest from sigstore.models import Bundle from sigstore.oidc import ExpiredIdentity, IdentityToken @@ -333,6 +333,19 @@ def staging(cls) -> SigningContext: trusted_root=TrustedRoot.staging(), ) + @classmethod + def _from_trust_config(cls, trust_config: ClientTrustConfig) -> SigningContext: + """ + Create a `SigningContext` from the given `ClientTrustConfig`. + + @api private + """ + return cls( + fulcio=FulcioClient(trust_config._inner.signing_config.ca_url), + rekor=RekorClient(trust_config._inner.signing_config.tlog_urls[0]), + trusted_root=trust_config.trusted_root, + ) + @contextmanager def signer( self, identity_token: IdentityToken, *, cache: bool = True diff --git a/sigstore/verify/verifier.py b/sigstore/verify/verifier.py index 278a57a19..8f46df117 100644 --- a/sigstore/verify/verifier.py +++ b/sigstore/verify/verifier.py @@ -44,7 +44,7 @@ _get_precertificate_signed_certificate_timestamps, verify_sct, ) -from sigstore._internal.trust import KeyringPurpose, TrustedRoot +from sigstore._internal.trust import ClientTrustConfig, KeyringPurpose, TrustedRoot from sigstore._utils import base64_encode_pem_cert, sha256_digest from sigstore.errors import VerificationError from sigstore.hashes import Hashed @@ -96,6 +96,18 @@ def staging(cls) -> Verifier: trusted_root=TrustedRoot.staging(), ) + @classmethod + def _from_trust_config(cls, trust_config: ClientTrustConfig) -> Verifier: + """ + Create a `Verifier` from the given `ClientTrustConfig`. + + @api private + """ + return cls( + rekor=RekorClient(trust_config._inner.signing_config.tlog_urls[0]), + trusted_root=trust_config.trusted_root, + ) + def _verify_common_signing_cert( self, bundle: Bundle, policy: VerificationPolicy ) -> None: From 02d121bae79b2c20c78bd10bc8a02c1e24007b61 Mon Sep 17 00:00:00 2001 From: William Woodruff Date: Tue, 14 May 2024 18:29:16 -0400 Subject: [PATCH 11/18] README: update `--help` Signed-off-by: William Woodruff --- README.md | 70 +++++++++++++------------------------------------------ 1 file changed, 16 insertions(+), 54 deletions(-) diff --git a/README.md b/README.md index 8f115fda5..c86fa64e0 100644 --- a/README.md +++ b/README.md @@ -96,29 +96,26 @@ Top-level: ``` -usage: sigstore [-h] [-v] [-V] [--staging] [--rekor-url URL] COMMAND ... +usage: sigstore [-h] [-v] [-V] [--staging | --trust-config FILE] COMMAND ... a tool for signing and verifying Python package distributions positional arguments: - COMMAND the operation to perform - sign sign one or more inputs - verify verify one or more inputs + COMMAND the operation to perform + sign sign one or more inputs + verify verify one or more inputs get-identity-token - retrieve and return a Sigstore-compatible OpenID Connect - token + retrieve and return a Sigstore-compatible OpenID + Connect token optional arguments: - -h, --help show this help message and exit - -v, --verbose run with additional debug logging; supply multiple times - to increase verbosity (default: 0) - -V, --version show program's version number and exit - -Sigstore instance options: - --staging Use sigstore's staging instances, instead of the default - production instances (default: False) - --rekor-url URL The Rekor instance to use (conflicts with --staging) - (default: https://rekor.sigstore.dev) + -h, --help show this help message and exit + -v, --verbose run with additional debug logging; supply multiple + times to increase verbosity (default: 0) + -V, --version show program's version number and exit + --staging Use sigstore's staging instances, instead of the + default production instances (default: False) + --trust-config FILE The client trust configuration to use (default: None) ``` @@ -132,8 +129,7 @@ usage: sigstore sign [-h] [-v] [--identity-token TOKEN] [--oidc-client-id ID] [--oidc-disable-ambient-providers] [--oidc-issuer URL] [--oauth-force-oob] [--no-default-files] [--signature FILE] [--certificate FILE] [--bundle FILE] - [--output-directory DIR] [--overwrite] [--staging] - [--rekor-url URL] [--fulcio-url URL] + [--output-directory DIR] [--overwrite] FILE [FILE ...] positional arguments: @@ -178,18 +174,6 @@ Output options: (default: None) --overwrite Overwrite preexisting signature and certificate outputs, if present (default: False) - -Sigstore instance options: - --staging Use sigstore's staging instances, instead of the - default production instances. This option will be - deprecated in favor of the global `--staging` option - in a future release. (default: False) - --rekor-url URL The Rekor instance to use (conflicts with --staging). - This option will be deprecated in favor of the global - `--rekor-url` option in a future release. (default: - None) - --fulcio-url URL The Fulcio instance to use (conflicts with --staging) - (default: https://fulcio.sigstore.dev) ``` @@ -207,8 +191,7 @@ to by a particular OIDC provider (like `https://github.com/login/oauth`). usage: sigstore verify identity [-h] [-v] [--certificate FILE] [--signature FILE] [--bundle FILE] --cert-identity IDENTITY [--offline] - --cert-oidc-issuer URL [--staging] - [--rekor-url URL] + --cert-oidc-issuer URL FILE [FILE ...] optional arguments: @@ -235,16 +218,6 @@ Verification options: --cert-oidc-issuer URL The OIDC issuer URL to check for in the certificate's OIDC issuer extension (default: None) - -Sigstore instance options: - --staging Use sigstore's staging instances, instead of the - default production instances. This option will be - deprecated in favor of the global `--staging` option - in a future release. (default: False) - --rekor-url URL The Rekor instance to use (conflicts with --staging). - This option will be deprecated in favor of the global - `--rekor-url` option in a future release. (default: - None) ``` @@ -261,8 +234,7 @@ usage: sigstore verify github [-h] [-v] [--certificate FILE] [--signature FILE] [--bundle FILE] --cert-identity IDENTITY [--offline] [--trigger EVENT] [--sha SHA] [--name NAME] - [--repository REPO] [--ref REF] [--staging] - [--rekor-url URL] + [--repository REPO] [--ref REF] FILE [FILE ...] optional arguments: @@ -296,16 +268,6 @@ Verification options: under (default: None) --ref REF The `git` ref that the workflow was invoked with (default: None) - -Sigstore instance options: - --staging Use sigstore's staging instances, instead of the - default production instances. This option will be - deprecated in favor of the global `--staging` option - in a future release. (default: False) - --rekor-url URL The Rekor instance to use (conflicts with --staging). - This option will be deprecated in favor of the global - `--rekor-url` option in a future release. (default: - None) ``` From e283cf70c058de40bd2944b0377bd588967d70cb Mon Sep 17 00:00:00 2001 From: William Woodruff Date: Wed, 15 May 2024 11:31:10 -0400 Subject: [PATCH 12/18] README: document BYO PKI Signed-off-by: William Woodruff --- README.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/README.md b/README.md index c86fa64e0..cecc7c163 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,7 @@ else! * [Verifying](#verifying) * [Generic identities](#generic-identities) * [Signatures from GitHub Actions](#signatures-from-github-actions) + * [Advanced usage](#advanced-usage) * [Example uses](#example-uses) * [Signing with ambient credentials](#signing-with-ambient-credentials) * [Signing with an email identity](#signing-with-an-email-identity) @@ -271,6 +272,29 @@ Verification options: ``` +## Advanced usage + +### Configuring a custom root of trust ("BYO PKI") + +Apart from the default and "staging" Sigstore instances, `sigstore` also +supports "BYO PKI" setups, where a user maintains their own Sigstore +instance services. + +These are supported via the `--trust-config` flag, which accepts a +JSON-formatted file conforming to the `ClientTrustConfig` message +in the [Sigstore protobuf specs](https://github.com/sigstore/protobuf-specs). +This file configures the entire Sigstore instance state, *including* the URIs +used to access the CA and artifact transparency services as well as the +cryptographic root of trust itself. + +To use a custom client config, prepend `--trust-config` to any `sigstore` +command: + +```console +$ sigstore --trust-config custom.trustconfig.json sign foo.txt +$ sigstore --trust-config custom.trustconfig.json verify identity foo.txt ... +``` + ## Example uses `sigstore` supports a wide variety of workflows and usages. Some common ones are From 670a3cfc35d1b6ad9ada0920c1826df47bb612c2 Mon Sep 17 00:00:00 2001 From: William Woodruff Date: Wed, 15 May 2024 11:40:37 -0400 Subject: [PATCH 13/18] sigstore: enforce client trust config media type Signed-off-by: William Woodruff --- sigstore/_internal/trust.py | 28 +++++++++++++++++++++++++++- sigstore/models.py | 4 ++-- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/sigstore/_internal/trust.py b/sigstore/_internal/trust.py index 6b863c596..aac5addab 100644 --- a/sigstore/_internal/trust.py +++ b/sigstore/_internal/trust.py @@ -55,7 +55,7 @@ key_id, load_der_public_key, ) -from sigstore.errors import MetadataError, VerificationError +from sigstore.errors import Error, MetadataError, VerificationError def _is_timerange_valid(period: TimeRange | None, *, allow_expired: bool) -> bool: @@ -343,6 +343,17 @@ class ClientTrustConfig: Represents a Sigstore client's trust configuration, including a root of trust. """ + class ClientTrustConfigType(str, Enum): + """ + Known Sigstore client trust config media types. + """ + + CONFIG_0_1 = "application/vnd.dev.sigstore.clienttrustconfig.v0.1+json" + + def __str__(self) -> str: + """Returns the variant's string value.""" + return self.value + @classmethod def from_json(cls, raw: str) -> ClientTrustConfig: """ @@ -356,6 +367,21 @@ def __init__(self, inner: _ClientTrustConfig) -> None: @api private """ self._inner = inner + self._verify() + + def _verify(self) -> None: + """ + Performs various feats of heroism to ensure that the client trust config + is well-formed. + """ + + # The client trust config must have a recognized media type. + try: + ClientTrustConfig(self._inner.media_type) + except ValueError: + raise Error( + f"unsupported client trust config format: {self._inner.media_type}" + ) @property def trusted_root(self) -> TrustedRoot: diff --git a/sigstore/models.py b/sigstore/models.py index 375ff6458..dea5c7314 100644 --- a/sigstore/models.py +++ b/sigstore/models.py @@ -358,9 +358,9 @@ def __init__(self, inner: _Bundle) -> None: @private """ self._inner = inner - self._verify_bundle() + self._verify() - def _verify_bundle(self) -> None: + def _verify(self) -> None: """ Performs various feats of heroism to ensure the bundle is well-formed and upholds invariants, including: From 4bb731c912a744fcabeca5289d46cf73ce97c5d2 Mon Sep 17 00:00:00 2001 From: William Woodruff Date: Wed, 15 May 2024 11:41:22 -0400 Subject: [PATCH 14/18] fix type Signed-off-by: William Woodruff --- sigstore/_internal/trust.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sigstore/_internal/trust.py b/sigstore/_internal/trust.py index aac5addab..ed8c88b35 100644 --- a/sigstore/_internal/trust.py +++ b/sigstore/_internal/trust.py @@ -377,7 +377,7 @@ def _verify(self) -> None: # The client trust config must have a recognized media type. try: - ClientTrustConfig(self._inner.media_type) + ClientTrustConfig.ClientTrustConfigType(self._inner.media_type) except ValueError: raise Error( f"unsupported client trust config format: {self._inner.media_type}" From 8f6e42f179b7e3da49d9060478be83e73c1c0519 Mon Sep 17 00:00:00 2001 From: William Woodruff Date: Wed, 15 May 2024 11:54:57 -0400 Subject: [PATCH 15/18] enforce media types, unit tests Signed-off-by: William Woodruff --- sigstore/_internal/trust.py | 24 ++++ .../assets/trust_config/config.badtype.json | 127 ++++++++++++++++++ test/unit/assets/trust_config/config.v1.json | 127 ++++++++++++++++++ test/unit/internal/test_trust.py | 23 +++- 4 files changed, 300 insertions(+), 1 deletion(-) create mode 100644 test/unit/assets/trust_config/config.badtype.json create mode 100644 test/unit/assets/trust_config/config.v1.json diff --git a/sigstore/_internal/trust.py b/sigstore/_internal/trust.py index ed8c88b35..d660bee56 100644 --- a/sigstore/_internal/trust.py +++ b/sigstore/_internal/trust.py @@ -222,6 +222,17 @@ class TrustedRoot: The cryptographic root(s) of trust for a Sigstore instance. """ + class TrustedRootType(str, Enum): + """ + Known Sigstore trusted root media types. + """ + + TRUSTED_ROOT_0_1 = "application/vnd.dev.sigstore.trustedroot+json;version=0.1" + + def __str__(self) -> str: + """Returns the variant's string value.""" + return self.value + def __init__(self, inner: _TrustedRoot): """ Construct a new `TrustedRoot`. @@ -229,6 +240,19 @@ def __init__(self, inner: _TrustedRoot): @api private """ self._inner = inner + self._verify() + + def _verify(self) -> None: + """ + Performs various feats of heroism to ensure that the trusted root + is well-formed. + """ + + # The trusted root must have a recognized media type. + try: + TrustedRoot.TrustedRootType(self._inner.media_type) + except ValueError: + raise Error(f"unsupported trusted root format: {self._inner.media_type}") @classmethod def from_file( diff --git a/test/unit/assets/trust_config/config.badtype.json b/test/unit/assets/trust_config/config.badtype.json new file mode 100644 index 000000000..3f0ef4812 --- /dev/null +++ b/test/unit/assets/trust_config/config.badtype.json @@ -0,0 +1,127 @@ +{ + "mediaType": "bad-media-type", + "trustedRoot": { + "mediaType": "application/vnd.dev.sigstore.trustedroot+json;version=0.1", + "tlogs": [ + { + "baseUrl": "https://rekor.sigstore.dev", + "hashAlgorithm": "SHA2_256", + "publicKey": { + "rawBytes": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE2G2Y+2tabdTV5BcGiBIx0a9fAFwrkBbmLSGtks4L3qX6yYY0zufBnhC8Ur/iy55GhWP/9A/bY2LhC30M9+RYtw==", + "keyDetails": "PKIX_ECDSA_P256_SHA_256", + "validFor": { + "start": "2021-01-12T11:53:27.000Z" + } + }, + "logId": { + "keyId": "wNI9atQGlz+VWfO6LRygH4QUfY/8W4RFwiT5i5WRgB0=" + } + } + ], + "certificateAuthorities": [ + { + "subject": { + "organization": "sigstore.dev", + "commonName": "sigstore" + }, + "uri": "https://fulcio.sigstore.dev", + "certChain": { + "certificates": [ + { + "rawBytes": "MIIB+DCCAX6gAwIBAgITNVkDZoCiofPDsy7dfm6geLbuhzAKBggqhkjOPQQDAzAqMRUwEwYDVQQKEwxzaWdzdG9yZS5kZXYxETAPBgNVBAMTCHNpZ3N0b3JlMB4XDTIxMDMwNzAzMjAyOVoXDTMxMDIyMzAzMjAyOVowKjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTB2MBAGByqGSM49AgEGBSuBBAAiA2IABLSyA7Ii5k+pNO8ZEWY0ylemWDowOkNa3kL+GZE5Z5GWehL9/A9bRNA3RbrsZ5i0JcastaRL7Sp5fp/jD5dxqc/UdTVnlvS16an+2Yfswe/QuLolRUCrcOE2+2iA5+tzd6NmMGQwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQEwHQYDVR0OBBYEFMjFHQBBmiQpMlEk6w2uSu1KBtPsMB8GA1UdIwQYMBaAFMjFHQBBmiQpMlEk6w2uSu1KBtPsMAoGCCqGSM49BAMDA2gAMGUCMH8liWJfMui6vXXBhjDgY4MwslmN/TJxVe/83WrFomwmNf056y1X48F9c4m3a3ozXAIxAKjRay5/aj/jsKKGIkmQatjI8uupHr/+CxFvaJWmpYqNkLDGRU+9orzh5hI2RrcuaQ==" + } + ] + }, + "validFor": { + "start": "2021-03-07T03:20:29.000Z", + "end": "2022-12-31T23:59:59.999Z" + } + }, + { + "subject": { + "organization": "sigstore.dev", + "commonName": "sigstore" + }, + "uri": "https://fulcio.sigstore.dev", + "certChain": { + "certificates": [ + { + "rawBytes": "MIICGjCCAaGgAwIBAgIUALnViVfnU0brJasmRkHrn/UnfaQwCgYIKoZIzj0EAwMwKjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTAeFw0yMjA0MTMyMDA2MTVaFw0zMTEwMDUxMzU2NThaMDcxFTATBgNVBAoTDHNpZ3N0b3JlLmRldjEeMBwGA1UEAxMVc2lnc3RvcmUtaW50ZXJtZWRpYXRlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE8RVS/ysH+NOvuDZyPIZtilgUF9NlarYpAd9HP1vBBH1U5CV77LSS7s0ZiH4nE7Hv7ptS6LvvR/STk798LVgMzLlJ4HeIfF3tHSaexLcYpSASr1kS0N/RgBJz/9jWCiXno3sweTAOBgNVHQ8BAf8EBAMCAQYwEwYDVR0lBAwwCgYIKwYBBQUHAwMwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQU39Ppz1YkEZb5qNjpKFWixi4YZD8wHwYDVR0jBBgwFoAUWMAeX5FFpWapesyQoZMi0CrFxfowCgYIKoZIzj0EAwMDZwAwZAIwPCsQK4DYiZYDPIaDi5HFKnfxXx6ASSVmERfsynYBiX2X6SJRnZU84/9DZdnFvvxmAjBOt6QpBlc4J/0DxvkTCqpclvziL6BCCPnjdlIB3Pu3BxsPmygUY7Ii2zbdCdliiow=" + }, + { + "rawBytes": "MIIB9zCCAXygAwIBAgIUALZNAPFdxHPwjeDloDwyYChAO/4wCgYIKoZIzj0EAwMwKjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTAeFw0yMTEwMDcxMzU2NTlaFw0zMTEwMDUxMzU2NThaMCoxFTATBgNVBAoTDHNpZ3N0b3JlLmRldjERMA8GA1UEAxMIc2lnc3RvcmUwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAT7XeFT4rb3PQGwS4IajtLk3/OlnpgangaBclYpsYBr5i+4ynB07ceb3LP0OIOZdxexX69c5iVuyJRQ+Hz05yi+UF3uBWAlHpiS5sh0+H2GHE7SXrk1EC5m1Tr19L9gg92jYzBhMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRYwB5fkUWlZql6zJChkyLQKsXF+jAfBgNVHSMEGDAWgBRYwB5fkUWlZql6zJChkyLQKsXF+jAKBggqhkjOPQQDAwNpADBmAjEAj1nHeXZp+13NWBNa+EDsDP8G1WWg1tCMWP/WHPqpaVo0jhsweNFZgSs0eE7wYI4qAjEA2WB9ot98sIkoF3vZYdd3/VtWB5b9TNMea7Ix/stJ5TfcLLeABLE4BNJOsQ4vnBHJ" + } + ] + }, + "validFor": { + "start": "2022-04-13T20:06:15.000Z" + } + } + ], + "ctlogs": [ + { + "baseUrl": "https://ctfe.sigstore.dev/test", + "hashAlgorithm": "SHA2_256", + "publicKey": { + "rawBytes": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEbfwR+RJudXscgRBRpKX1XFDy3PyudDxz/SfnRi1fT8ekpfBd2O1uoz7jr3Z8nKzxA69EUQ+eFCFI3zeubPWU7w==", + "keyDetails": "PKIX_ECDSA_P256_SHA_256", + "validFor": { + "start": "2021-03-14T00:00:00.000Z", + "end": "2022-10-31T23:59:59.999Z" + } + }, + "logId": { + "keyId": "CGCS8ChS/2hF0dFrJ4ScRWcYrBY9wzjSbea8IgY2b3I=" + } + }, + { + "baseUrl": "https://ctfe.sigstore.dev/2022", + "hashAlgorithm": "SHA2_256", + "publicKey": { + "rawBytes": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEiPSlFi0CmFTfEjCUqF9HuCEcYXNKAaYalIJmBZ8yyezPjTqhxrKBpMnaocVtLJBI1eM3uXnQzQGAJdJ4gs9Fyw==", + "keyDetails": "PKIX_ECDSA_P256_SHA_256", + "validFor": { + "start": "2022-10-20T00:00:00.000Z" + } + }, + "logId": { + "keyId": "3T0wasbHETJjGR4cmWc3AqJKXrjePK3/h4pygC8p7o4=" + } + } + ], + "timestampAuthorities": [ + { + "subject": { + "organization": "GitHub, Inc.", + "commonName": "Internal Services Root" + }, + "certChain": { + "certificates": [ + { + "rawBytes": "MIIB3DCCAWKgAwIBAgIUchkNsH36Xa04b1LqIc+qr9DVecMwCgYIKoZIzj0EAwMwMjEVMBMGA1UEChMMR2l0SHViLCBJbmMuMRkwFwYDVQQDExBUU0EgaW50ZXJtZWRpYXRlMB4XDTIzMDQxNDAwMDAwMFoXDTI0MDQxMzAwMDAwMFowMjEVMBMGA1UEChMMR2l0SHViLCBJbmMuMRkwFwYDVQQDExBUU0EgVGltZXN0YW1waW5nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEUD5ZNbSqYMd6r8qpOOEX9ibGnZT9GsuXOhr/f8U9FJugBGExKYp40OULS0erjZW7xV9xV52NnJf5OeDq4e5ZKqNWMFQwDgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMIMAwGA1UdEwEB/wQCMAAwHwYDVR0jBBgwFoAUaW1RudOgVt0leqY0WKYbuPr47wAwCgYIKoZIzj0EAwMDaAAwZQIwbUH9HvD4ejCZJOWQnqAlkqURllvu9M8+VqLbiRK+zSfZCZwsiljRn8MQQRSkXEE5AjEAg+VxqtojfVfu8DhzzhCx9GKETbJHb19iV72mMKUbDAFmzZ6bQ8b54Zb8tidy5aWe" + }, + { + "rawBytes": "MIICEDCCAZWgAwIBAgIUX8ZO5QXP7vN4dMQ5e9sU3nub8OgwCgYIKoZIzj0EAwMwODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MB4XDTIzMDQxNDAwMDAwMFoXDTI4MDQxMjAwMDAwMFowMjEVMBMGA1UEChMMR2l0SHViLCBJbmMuMRkwFwYDVQQDExBUU0EgaW50ZXJtZWRpYXRlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEvMLY/dTVbvIJYANAuszEwJnQE1llftynyMKIMhh48HmqbVr5ygybzsLRLVKbBWOdZ21aeJz+gZiytZetqcyF9WlER5NEMf6JV7ZNojQpxHq4RHGoGSceQv/qvTiZxEDKo2YwZDAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUaW1RudOgVt0leqY0WKYbuPr47wAwHwYDVR0jBBgwFoAU9NYYlobnAG4c0/qjxyH/lq/wz+QwCgYIKoZIzj0EAwMDaQAwZgIxAK1B185ygCrIYFlIs3GjswjnwSMG6LY8woLVdakKDZxVa8f8cqMs1DhcxJ0+09w95QIxAO+tBzZk7vjUJ9iJgD4R6ZWTxQWKqNm74jO99o+o9sv4FI/SZTZTFyMn0IJEHdNmyA==" + }, + { + "rawBytes": "MIIB9DCCAXqgAwIBAgIUa/JAkdUjK4JUwsqtaiRJGWhqLSowCgYIKoZIzj0EAwMwODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MB4XDTIzMDQxNDAwMDAwMFoXDTMzMDQxMTAwMDAwMFowODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEf9jFAXxz4kx68AHRMOkFBhflDcMTvzaXz4x/FCcXjJ/1qEKon/qPIGnaURskDtyNbNDOpeJTDDFqt48iMPrnzpx6IZwqemfUJN4xBEZfza+pYt/iyod+9tZr20RRWSv/o0UwQzAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBAjAdBgNVHQ4EFgQU9NYYlobnAG4c0/qjxyH/lq/wz+QwCgYIKoZIzj0EAwMDaAAwZQIxALZLZ8BgRXzKxLMMN9VIlO+e4hrBnNBgF7tz7Hnrowv2NetZErIACKFymBlvWDvtMAIwZO+ki6ssQ1bsZo98O8mEAf2NZ7iiCgDDU0Vwjeco6zyeh0zBTs9/7gV6AHNQ53xD" + } + ] + }, + "validFor": { + "start": "2023-04-14T00:00:00.000Z" + } + } + ] + }, + "signingConfig": { + "caUrl": "https://fakeca.example.com", + "oidcUrl": "https://fakeoidc.example.com", + "tlogUrls": [ + "https://fakelog.example.com" + ], + "tsaUrls": [ + "https://faketsa.example.com" + ] + } +} diff --git a/test/unit/assets/trust_config/config.v1.json b/test/unit/assets/trust_config/config.v1.json new file mode 100644 index 000000000..d3fae7ac6 --- /dev/null +++ b/test/unit/assets/trust_config/config.v1.json @@ -0,0 +1,127 @@ +{ + "mediaType": "application/vnd.dev.sigstore.clienttrustconfig.v0.1+json", + "trustedRoot": { + "mediaType": "application/vnd.dev.sigstore.trustedroot+json;version=0.1", + "tlogs": [ + { + "baseUrl": "https://rekor.sigstore.dev", + "hashAlgorithm": "SHA2_256", + "publicKey": { + "rawBytes": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE2G2Y+2tabdTV5BcGiBIx0a9fAFwrkBbmLSGtks4L3qX6yYY0zufBnhC8Ur/iy55GhWP/9A/bY2LhC30M9+RYtw==", + "keyDetails": "PKIX_ECDSA_P256_SHA_256", + "validFor": { + "start": "2021-01-12T11:53:27.000Z" + } + }, + "logId": { + "keyId": "wNI9atQGlz+VWfO6LRygH4QUfY/8W4RFwiT5i5WRgB0=" + } + } + ], + "certificateAuthorities": [ + { + "subject": { + "organization": "sigstore.dev", + "commonName": "sigstore" + }, + "uri": "https://fulcio.sigstore.dev", + "certChain": { + "certificates": [ + { + "rawBytes": "MIIB+DCCAX6gAwIBAgITNVkDZoCiofPDsy7dfm6geLbuhzAKBggqhkjOPQQDAzAqMRUwEwYDVQQKEwxzaWdzdG9yZS5kZXYxETAPBgNVBAMTCHNpZ3N0b3JlMB4XDTIxMDMwNzAzMjAyOVoXDTMxMDIyMzAzMjAyOVowKjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTB2MBAGByqGSM49AgEGBSuBBAAiA2IABLSyA7Ii5k+pNO8ZEWY0ylemWDowOkNa3kL+GZE5Z5GWehL9/A9bRNA3RbrsZ5i0JcastaRL7Sp5fp/jD5dxqc/UdTVnlvS16an+2Yfswe/QuLolRUCrcOE2+2iA5+tzd6NmMGQwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQEwHQYDVR0OBBYEFMjFHQBBmiQpMlEk6w2uSu1KBtPsMB8GA1UdIwQYMBaAFMjFHQBBmiQpMlEk6w2uSu1KBtPsMAoGCCqGSM49BAMDA2gAMGUCMH8liWJfMui6vXXBhjDgY4MwslmN/TJxVe/83WrFomwmNf056y1X48F9c4m3a3ozXAIxAKjRay5/aj/jsKKGIkmQatjI8uupHr/+CxFvaJWmpYqNkLDGRU+9orzh5hI2RrcuaQ==" + } + ] + }, + "validFor": { + "start": "2021-03-07T03:20:29.000Z", + "end": "2022-12-31T23:59:59.999Z" + } + }, + { + "subject": { + "organization": "sigstore.dev", + "commonName": "sigstore" + }, + "uri": "https://fulcio.sigstore.dev", + "certChain": { + "certificates": [ + { + "rawBytes": "MIICGjCCAaGgAwIBAgIUALnViVfnU0brJasmRkHrn/UnfaQwCgYIKoZIzj0EAwMwKjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTAeFw0yMjA0MTMyMDA2MTVaFw0zMTEwMDUxMzU2NThaMDcxFTATBgNVBAoTDHNpZ3N0b3JlLmRldjEeMBwGA1UEAxMVc2lnc3RvcmUtaW50ZXJtZWRpYXRlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE8RVS/ysH+NOvuDZyPIZtilgUF9NlarYpAd9HP1vBBH1U5CV77LSS7s0ZiH4nE7Hv7ptS6LvvR/STk798LVgMzLlJ4HeIfF3tHSaexLcYpSASr1kS0N/RgBJz/9jWCiXno3sweTAOBgNVHQ8BAf8EBAMCAQYwEwYDVR0lBAwwCgYIKwYBBQUHAwMwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQU39Ppz1YkEZb5qNjpKFWixi4YZD8wHwYDVR0jBBgwFoAUWMAeX5FFpWapesyQoZMi0CrFxfowCgYIKoZIzj0EAwMDZwAwZAIwPCsQK4DYiZYDPIaDi5HFKnfxXx6ASSVmERfsynYBiX2X6SJRnZU84/9DZdnFvvxmAjBOt6QpBlc4J/0DxvkTCqpclvziL6BCCPnjdlIB3Pu3BxsPmygUY7Ii2zbdCdliiow=" + }, + { + "rawBytes": "MIIB9zCCAXygAwIBAgIUALZNAPFdxHPwjeDloDwyYChAO/4wCgYIKoZIzj0EAwMwKjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTAeFw0yMTEwMDcxMzU2NTlaFw0zMTEwMDUxMzU2NThaMCoxFTATBgNVBAoTDHNpZ3N0b3JlLmRldjERMA8GA1UEAxMIc2lnc3RvcmUwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAT7XeFT4rb3PQGwS4IajtLk3/OlnpgangaBclYpsYBr5i+4ynB07ceb3LP0OIOZdxexX69c5iVuyJRQ+Hz05yi+UF3uBWAlHpiS5sh0+H2GHE7SXrk1EC5m1Tr19L9gg92jYzBhMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRYwB5fkUWlZql6zJChkyLQKsXF+jAfBgNVHSMEGDAWgBRYwB5fkUWlZql6zJChkyLQKsXF+jAKBggqhkjOPQQDAwNpADBmAjEAj1nHeXZp+13NWBNa+EDsDP8G1WWg1tCMWP/WHPqpaVo0jhsweNFZgSs0eE7wYI4qAjEA2WB9ot98sIkoF3vZYdd3/VtWB5b9TNMea7Ix/stJ5TfcLLeABLE4BNJOsQ4vnBHJ" + } + ] + }, + "validFor": { + "start": "2022-04-13T20:06:15.000Z" + } + } + ], + "ctlogs": [ + { + "baseUrl": "https://ctfe.sigstore.dev/test", + "hashAlgorithm": "SHA2_256", + "publicKey": { + "rawBytes": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEbfwR+RJudXscgRBRpKX1XFDy3PyudDxz/SfnRi1fT8ekpfBd2O1uoz7jr3Z8nKzxA69EUQ+eFCFI3zeubPWU7w==", + "keyDetails": "PKIX_ECDSA_P256_SHA_256", + "validFor": { + "start": "2021-03-14T00:00:00.000Z", + "end": "2022-10-31T23:59:59.999Z" + } + }, + "logId": { + "keyId": "CGCS8ChS/2hF0dFrJ4ScRWcYrBY9wzjSbea8IgY2b3I=" + } + }, + { + "baseUrl": "https://ctfe.sigstore.dev/2022", + "hashAlgorithm": "SHA2_256", + "publicKey": { + "rawBytes": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEiPSlFi0CmFTfEjCUqF9HuCEcYXNKAaYalIJmBZ8yyezPjTqhxrKBpMnaocVtLJBI1eM3uXnQzQGAJdJ4gs9Fyw==", + "keyDetails": "PKIX_ECDSA_P256_SHA_256", + "validFor": { + "start": "2022-10-20T00:00:00.000Z" + } + }, + "logId": { + "keyId": "3T0wasbHETJjGR4cmWc3AqJKXrjePK3/h4pygC8p7o4=" + } + } + ], + "timestampAuthorities": [ + { + "subject": { + "organization": "GitHub, Inc.", + "commonName": "Internal Services Root" + }, + "certChain": { + "certificates": [ + { + "rawBytes": "MIIB3DCCAWKgAwIBAgIUchkNsH36Xa04b1LqIc+qr9DVecMwCgYIKoZIzj0EAwMwMjEVMBMGA1UEChMMR2l0SHViLCBJbmMuMRkwFwYDVQQDExBUU0EgaW50ZXJtZWRpYXRlMB4XDTIzMDQxNDAwMDAwMFoXDTI0MDQxMzAwMDAwMFowMjEVMBMGA1UEChMMR2l0SHViLCBJbmMuMRkwFwYDVQQDExBUU0EgVGltZXN0YW1waW5nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEUD5ZNbSqYMd6r8qpOOEX9ibGnZT9GsuXOhr/f8U9FJugBGExKYp40OULS0erjZW7xV9xV52NnJf5OeDq4e5ZKqNWMFQwDgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMIMAwGA1UdEwEB/wQCMAAwHwYDVR0jBBgwFoAUaW1RudOgVt0leqY0WKYbuPr47wAwCgYIKoZIzj0EAwMDaAAwZQIwbUH9HvD4ejCZJOWQnqAlkqURllvu9M8+VqLbiRK+zSfZCZwsiljRn8MQQRSkXEE5AjEAg+VxqtojfVfu8DhzzhCx9GKETbJHb19iV72mMKUbDAFmzZ6bQ8b54Zb8tidy5aWe" + }, + { + "rawBytes": "MIICEDCCAZWgAwIBAgIUX8ZO5QXP7vN4dMQ5e9sU3nub8OgwCgYIKoZIzj0EAwMwODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MB4XDTIzMDQxNDAwMDAwMFoXDTI4MDQxMjAwMDAwMFowMjEVMBMGA1UEChMMR2l0SHViLCBJbmMuMRkwFwYDVQQDExBUU0EgaW50ZXJtZWRpYXRlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEvMLY/dTVbvIJYANAuszEwJnQE1llftynyMKIMhh48HmqbVr5ygybzsLRLVKbBWOdZ21aeJz+gZiytZetqcyF9WlER5NEMf6JV7ZNojQpxHq4RHGoGSceQv/qvTiZxEDKo2YwZDAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUaW1RudOgVt0leqY0WKYbuPr47wAwHwYDVR0jBBgwFoAU9NYYlobnAG4c0/qjxyH/lq/wz+QwCgYIKoZIzj0EAwMDaQAwZgIxAK1B185ygCrIYFlIs3GjswjnwSMG6LY8woLVdakKDZxVa8f8cqMs1DhcxJ0+09w95QIxAO+tBzZk7vjUJ9iJgD4R6ZWTxQWKqNm74jO99o+o9sv4FI/SZTZTFyMn0IJEHdNmyA==" + }, + { + "rawBytes": "MIIB9DCCAXqgAwIBAgIUa/JAkdUjK4JUwsqtaiRJGWhqLSowCgYIKoZIzj0EAwMwODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MB4XDTIzMDQxNDAwMDAwMFoXDTMzMDQxMTAwMDAwMFowODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEf9jFAXxz4kx68AHRMOkFBhflDcMTvzaXz4x/FCcXjJ/1qEKon/qPIGnaURskDtyNbNDOpeJTDDFqt48iMPrnzpx6IZwqemfUJN4xBEZfza+pYt/iyod+9tZr20RRWSv/o0UwQzAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBAjAdBgNVHQ4EFgQU9NYYlobnAG4c0/qjxyH/lq/wz+QwCgYIKoZIzj0EAwMDaAAwZQIxALZLZ8BgRXzKxLMMN9VIlO+e4hrBnNBgF7tz7Hnrowv2NetZErIACKFymBlvWDvtMAIwZO+ki6ssQ1bsZo98O8mEAf2NZ7iiCgDDU0Vwjeco6zyeh0zBTs9/7gV6AHNQ53xD" + } + ] + }, + "validFor": { + "start": "2023-04-14T00:00:00.000Z" + } + } + ] + }, + "signingConfig": { + "caUrl": "https://fakeca.example.com", + "oidcUrl": "https://fakeoidc.example.com", + "tlogUrls": [ + "https://fakelog.example.com" + ], + "tsaUrls": [ + "https://faketsa.example.com" + ] + } +} diff --git a/test/unit/internal/test_trust.py b/test/unit/internal/test_trust.py index cc55c8fcf..60de5de48 100644 --- a/test/unit/internal/test_trust.py +++ b/test/unit/internal/test_trust.py @@ -22,12 +22,13 @@ from sigstore_protobuf_specs.dev.sigstore.common.v1 import TimeRange from sigstore._internal.trust import ( + ClientTrustConfig, KeyringPurpose, TrustedRoot, _is_timerange_valid, ) from sigstore._utils import load_pem_public_key -from sigstore.errors import RootError +from sigstore.errors import Error, RootError def test_trust_root_tuf_caches_and_requests(mock_staging_tuf, tuf_dirs): @@ -242,3 +243,23 @@ def test_trust_root_fulcio_certs_error(tuf_asset, monkeypatch): Exception, match="Fulcio certificates not found in trusted root" ): trust_root.get_fulcio_certs() + + +class TestClientTrustConfig: + def test_good(self, asset): + path = asset("trust_config/config.v1.json") + config = ClientTrustConfig.from_json(path.read_text()) + + assert config._inner.signing_config.ca_url == "https://fakeca.example.com" + assert config._inner.signing_config.oidc_url == "https://fakeoidc.example.com" + assert config._inner.signing_config.tlog_urls == ["https://fakelog.example.com"] + assert config._inner.signing_config.tsa_urls == ["https://faketsa.example.com"] + assert isinstance(config.trusted_root, TrustedRoot) + + def test_bad_media_type(self, asset): + path = asset("trust_config/config.badtype.json") + + with pytest.raises( + Error, match="unsupported client trust config format: bad-media-type" + ): + ClientTrustConfig.from_json(path.read_text()) From 979ac6d9bc6409962c791dce1247275ebb272a51 Mon Sep 17 00:00:00 2001 From: William Woodruff Date: Wed, 15 May 2024 12:02:41 -0400 Subject: [PATCH 16/18] test: more trust tests Signed-off-by: William Woodruff --- .../trusted_root/trustedroot.badtype.json | 114 ++++++++++++++++++ .../assets/trusted_root/trustedroot.v1.json | 114 ++++++++++++++++++ test/unit/internal/test_trust.py | 25 ++++ 3 files changed, 253 insertions(+) create mode 100644 test/unit/assets/trusted_root/trustedroot.badtype.json create mode 100644 test/unit/assets/trusted_root/trustedroot.v1.json diff --git a/test/unit/assets/trusted_root/trustedroot.badtype.json b/test/unit/assets/trusted_root/trustedroot.badtype.json new file mode 100644 index 000000000..539ce3227 --- /dev/null +++ b/test/unit/assets/trusted_root/trustedroot.badtype.json @@ -0,0 +1,114 @@ +{ + "mediaType": "bad-media-type", + "tlogs": [ + { + "baseUrl": "https://rekor.sigstore.dev", + "hashAlgorithm": "SHA2_256", + "publicKey": { + "rawBytes": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE2G2Y+2tabdTV5BcGiBIx0a9fAFwrkBbmLSGtks4L3qX6yYY0zufBnhC8Ur/iy55GhWP/9A/bY2LhC30M9+RYtw==", + "keyDetails": "PKIX_ECDSA_P256_SHA_256", + "validFor": { + "start": "2021-01-12T11:53:27.000Z" + } + }, + "logId": { + "keyId": "wNI9atQGlz+VWfO6LRygH4QUfY/8W4RFwiT5i5WRgB0=" + } + } + ], + "certificateAuthorities": [ + { + "subject": { + "organization": "sigstore.dev", + "commonName": "sigstore" + }, + "uri": "https://fulcio.sigstore.dev", + "certChain": { + "certificates": [ + { + "rawBytes": "MIIB+DCCAX6gAwIBAgITNVkDZoCiofPDsy7dfm6geLbuhzAKBggqhkjOPQQDAzAqMRUwEwYDVQQKEwxzaWdzdG9yZS5kZXYxETAPBgNVBAMTCHNpZ3N0b3JlMB4XDTIxMDMwNzAzMjAyOVoXDTMxMDIyMzAzMjAyOVowKjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTB2MBAGByqGSM49AgEGBSuBBAAiA2IABLSyA7Ii5k+pNO8ZEWY0ylemWDowOkNa3kL+GZE5Z5GWehL9/A9bRNA3RbrsZ5i0JcastaRL7Sp5fp/jD5dxqc/UdTVnlvS16an+2Yfswe/QuLolRUCrcOE2+2iA5+tzd6NmMGQwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQEwHQYDVR0OBBYEFMjFHQBBmiQpMlEk6w2uSu1KBtPsMB8GA1UdIwQYMBaAFMjFHQBBmiQpMlEk6w2uSu1KBtPsMAoGCCqGSM49BAMDA2gAMGUCMH8liWJfMui6vXXBhjDgY4MwslmN/TJxVe/83WrFomwmNf056y1X48F9c4m3a3ozXAIxAKjRay5/aj/jsKKGIkmQatjI8uupHr/+CxFvaJWmpYqNkLDGRU+9orzh5hI2RrcuaQ==" + } + ] + }, + "validFor": { + "start": "2021-03-07T03:20:29.000Z", + "end": "2022-12-31T23:59:59.999Z" + } + }, + { + "subject": { + "organization": "sigstore.dev", + "commonName": "sigstore" + }, + "uri": "https://fulcio.sigstore.dev", + "certChain": { + "certificates": [ + { + "rawBytes": "MIICGjCCAaGgAwIBAgIUALnViVfnU0brJasmRkHrn/UnfaQwCgYIKoZIzj0EAwMwKjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTAeFw0yMjA0MTMyMDA2MTVaFw0zMTEwMDUxMzU2NThaMDcxFTATBgNVBAoTDHNpZ3N0b3JlLmRldjEeMBwGA1UEAxMVc2lnc3RvcmUtaW50ZXJtZWRpYXRlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE8RVS/ysH+NOvuDZyPIZtilgUF9NlarYpAd9HP1vBBH1U5CV77LSS7s0ZiH4nE7Hv7ptS6LvvR/STk798LVgMzLlJ4HeIfF3tHSaexLcYpSASr1kS0N/RgBJz/9jWCiXno3sweTAOBgNVHQ8BAf8EBAMCAQYwEwYDVR0lBAwwCgYIKwYBBQUHAwMwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQU39Ppz1YkEZb5qNjpKFWixi4YZD8wHwYDVR0jBBgwFoAUWMAeX5FFpWapesyQoZMi0CrFxfowCgYIKoZIzj0EAwMDZwAwZAIwPCsQK4DYiZYDPIaDi5HFKnfxXx6ASSVmERfsynYBiX2X6SJRnZU84/9DZdnFvvxmAjBOt6QpBlc4J/0DxvkTCqpclvziL6BCCPnjdlIB3Pu3BxsPmygUY7Ii2zbdCdliiow=" + }, + { + "rawBytes": "MIIB9zCCAXygAwIBAgIUALZNAPFdxHPwjeDloDwyYChAO/4wCgYIKoZIzj0EAwMwKjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTAeFw0yMTEwMDcxMzU2NTlaFw0zMTEwMDUxMzU2NThaMCoxFTATBgNVBAoTDHNpZ3N0b3JlLmRldjERMA8GA1UEAxMIc2lnc3RvcmUwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAT7XeFT4rb3PQGwS4IajtLk3/OlnpgangaBclYpsYBr5i+4ynB07ceb3LP0OIOZdxexX69c5iVuyJRQ+Hz05yi+UF3uBWAlHpiS5sh0+H2GHE7SXrk1EC5m1Tr19L9gg92jYzBhMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRYwB5fkUWlZql6zJChkyLQKsXF+jAfBgNVHSMEGDAWgBRYwB5fkUWlZql6zJChkyLQKsXF+jAKBggqhkjOPQQDAwNpADBmAjEAj1nHeXZp+13NWBNa+EDsDP8G1WWg1tCMWP/WHPqpaVo0jhsweNFZgSs0eE7wYI4qAjEA2WB9ot98sIkoF3vZYdd3/VtWB5b9TNMea7Ix/stJ5TfcLLeABLE4BNJOsQ4vnBHJ" + } + ] + }, + "validFor": { + "start": "2022-04-13T20:06:15.000Z" + } + } + ], + "ctlogs": [ + { + "baseUrl": "https://ctfe.sigstore.dev/test", + "hashAlgorithm": "SHA2_256", + "publicKey": { + "rawBytes": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEbfwR+RJudXscgRBRpKX1XFDy3PyudDxz/SfnRi1fT8ekpfBd2O1uoz7jr3Z8nKzxA69EUQ+eFCFI3zeubPWU7w==", + "keyDetails": "PKIX_ECDSA_P256_SHA_256", + "validFor": { + "start": "2021-03-14T00:00:00.000Z", + "end": "2022-10-31T23:59:59.999Z" + } + }, + "logId": { + "keyId": "CGCS8ChS/2hF0dFrJ4ScRWcYrBY9wzjSbea8IgY2b3I=" + } + }, + { + "baseUrl": "https://ctfe.sigstore.dev/2022", + "hashAlgorithm": "SHA2_256", + "publicKey": { + "rawBytes": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEiPSlFi0CmFTfEjCUqF9HuCEcYXNKAaYalIJmBZ8yyezPjTqhxrKBpMnaocVtLJBI1eM3uXnQzQGAJdJ4gs9Fyw==", + "keyDetails": "PKIX_ECDSA_P256_SHA_256", + "validFor": { + "start": "2022-10-20T00:00:00.000Z" + } + }, + "logId": { + "keyId": "3T0wasbHETJjGR4cmWc3AqJKXrjePK3/h4pygC8p7o4=" + } + } + ], + "timestampAuthorities": [ + { + "subject": { + "organization": "GitHub, Inc.", + "commonName": "Internal Services Root" + }, + "certChain": { + "certificates": [ + { + "rawBytes": "MIIB3DCCAWKgAwIBAgIUchkNsH36Xa04b1LqIc+qr9DVecMwCgYIKoZIzj0EAwMwMjEVMBMGA1UEChMMR2l0SHViLCBJbmMuMRkwFwYDVQQDExBUU0EgaW50ZXJtZWRpYXRlMB4XDTIzMDQxNDAwMDAwMFoXDTI0MDQxMzAwMDAwMFowMjEVMBMGA1UEChMMR2l0SHViLCBJbmMuMRkwFwYDVQQDExBUU0EgVGltZXN0YW1waW5nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEUD5ZNbSqYMd6r8qpOOEX9ibGnZT9GsuXOhr/f8U9FJugBGExKYp40OULS0erjZW7xV9xV52NnJf5OeDq4e5ZKqNWMFQwDgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMIMAwGA1UdEwEB/wQCMAAwHwYDVR0jBBgwFoAUaW1RudOgVt0leqY0WKYbuPr47wAwCgYIKoZIzj0EAwMDaAAwZQIwbUH9HvD4ejCZJOWQnqAlkqURllvu9M8+VqLbiRK+zSfZCZwsiljRn8MQQRSkXEE5AjEAg+VxqtojfVfu8DhzzhCx9GKETbJHb19iV72mMKUbDAFmzZ6bQ8b54Zb8tidy5aWe" + }, + { + "rawBytes": "MIICEDCCAZWgAwIBAgIUX8ZO5QXP7vN4dMQ5e9sU3nub8OgwCgYIKoZIzj0EAwMwODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MB4XDTIzMDQxNDAwMDAwMFoXDTI4MDQxMjAwMDAwMFowMjEVMBMGA1UEChMMR2l0SHViLCBJbmMuMRkwFwYDVQQDExBUU0EgaW50ZXJtZWRpYXRlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEvMLY/dTVbvIJYANAuszEwJnQE1llftynyMKIMhh48HmqbVr5ygybzsLRLVKbBWOdZ21aeJz+gZiytZetqcyF9WlER5NEMf6JV7ZNojQpxHq4RHGoGSceQv/qvTiZxEDKo2YwZDAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUaW1RudOgVt0leqY0WKYbuPr47wAwHwYDVR0jBBgwFoAU9NYYlobnAG4c0/qjxyH/lq/wz+QwCgYIKoZIzj0EAwMDaQAwZgIxAK1B185ygCrIYFlIs3GjswjnwSMG6LY8woLVdakKDZxVa8f8cqMs1DhcxJ0+09w95QIxAO+tBzZk7vjUJ9iJgD4R6ZWTxQWKqNm74jO99o+o9sv4FI/SZTZTFyMn0IJEHdNmyA==" + }, + { + "rawBytes": "MIIB9DCCAXqgAwIBAgIUa/JAkdUjK4JUwsqtaiRJGWhqLSowCgYIKoZIzj0EAwMwODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MB4XDTIzMDQxNDAwMDAwMFoXDTMzMDQxMTAwMDAwMFowODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEf9jFAXxz4kx68AHRMOkFBhflDcMTvzaXz4x/FCcXjJ/1qEKon/qPIGnaURskDtyNbNDOpeJTDDFqt48iMPrnzpx6IZwqemfUJN4xBEZfza+pYt/iyod+9tZr20RRWSv/o0UwQzAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBAjAdBgNVHQ4EFgQU9NYYlobnAG4c0/qjxyH/lq/wz+QwCgYIKoZIzj0EAwMDaAAwZQIxALZLZ8BgRXzKxLMMN9VIlO+e4hrBnNBgF7tz7Hnrowv2NetZErIACKFymBlvWDvtMAIwZO+ki6ssQ1bsZo98O8mEAf2NZ7iiCgDDU0Vwjeco6zyeh0zBTs9/7gV6AHNQ53xD" + } + ] + }, + "validFor": { + "start": "2023-04-14T00:00:00.000Z" + } + } + ] +} diff --git a/test/unit/assets/trusted_root/trustedroot.v1.json b/test/unit/assets/trusted_root/trustedroot.v1.json new file mode 100644 index 000000000..190c76a65 --- /dev/null +++ b/test/unit/assets/trusted_root/trustedroot.v1.json @@ -0,0 +1,114 @@ +{ + "mediaType": "application/vnd.dev.sigstore.trustedroot+json;version=0.1", + "tlogs": [ + { + "baseUrl": "https://rekor.sigstore.dev", + "hashAlgorithm": "SHA2_256", + "publicKey": { + "rawBytes": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE2G2Y+2tabdTV5BcGiBIx0a9fAFwrkBbmLSGtks4L3qX6yYY0zufBnhC8Ur/iy55GhWP/9A/bY2LhC30M9+RYtw==", + "keyDetails": "PKIX_ECDSA_P256_SHA_256", + "validFor": { + "start": "2021-01-12T11:53:27.000Z" + } + }, + "logId": { + "keyId": "wNI9atQGlz+VWfO6LRygH4QUfY/8W4RFwiT5i5WRgB0=" + } + } + ], + "certificateAuthorities": [ + { + "subject": { + "organization": "sigstore.dev", + "commonName": "sigstore" + }, + "uri": "https://fulcio.sigstore.dev", + "certChain": { + "certificates": [ + { + "rawBytes": "MIIB+DCCAX6gAwIBAgITNVkDZoCiofPDsy7dfm6geLbuhzAKBggqhkjOPQQDAzAqMRUwEwYDVQQKEwxzaWdzdG9yZS5kZXYxETAPBgNVBAMTCHNpZ3N0b3JlMB4XDTIxMDMwNzAzMjAyOVoXDTMxMDIyMzAzMjAyOVowKjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTB2MBAGByqGSM49AgEGBSuBBAAiA2IABLSyA7Ii5k+pNO8ZEWY0ylemWDowOkNa3kL+GZE5Z5GWehL9/A9bRNA3RbrsZ5i0JcastaRL7Sp5fp/jD5dxqc/UdTVnlvS16an+2Yfswe/QuLolRUCrcOE2+2iA5+tzd6NmMGQwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQEwHQYDVR0OBBYEFMjFHQBBmiQpMlEk6w2uSu1KBtPsMB8GA1UdIwQYMBaAFMjFHQBBmiQpMlEk6w2uSu1KBtPsMAoGCCqGSM49BAMDA2gAMGUCMH8liWJfMui6vXXBhjDgY4MwslmN/TJxVe/83WrFomwmNf056y1X48F9c4m3a3ozXAIxAKjRay5/aj/jsKKGIkmQatjI8uupHr/+CxFvaJWmpYqNkLDGRU+9orzh5hI2RrcuaQ==" + } + ] + }, + "validFor": { + "start": "2021-03-07T03:20:29.000Z", + "end": "2022-12-31T23:59:59.999Z" + } + }, + { + "subject": { + "organization": "sigstore.dev", + "commonName": "sigstore" + }, + "uri": "https://fulcio.sigstore.dev", + "certChain": { + "certificates": [ + { + "rawBytes": "MIICGjCCAaGgAwIBAgIUALnViVfnU0brJasmRkHrn/UnfaQwCgYIKoZIzj0EAwMwKjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTAeFw0yMjA0MTMyMDA2MTVaFw0zMTEwMDUxMzU2NThaMDcxFTATBgNVBAoTDHNpZ3N0b3JlLmRldjEeMBwGA1UEAxMVc2lnc3RvcmUtaW50ZXJtZWRpYXRlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE8RVS/ysH+NOvuDZyPIZtilgUF9NlarYpAd9HP1vBBH1U5CV77LSS7s0ZiH4nE7Hv7ptS6LvvR/STk798LVgMzLlJ4HeIfF3tHSaexLcYpSASr1kS0N/RgBJz/9jWCiXno3sweTAOBgNVHQ8BAf8EBAMCAQYwEwYDVR0lBAwwCgYIKwYBBQUHAwMwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQU39Ppz1YkEZb5qNjpKFWixi4YZD8wHwYDVR0jBBgwFoAUWMAeX5FFpWapesyQoZMi0CrFxfowCgYIKoZIzj0EAwMDZwAwZAIwPCsQK4DYiZYDPIaDi5HFKnfxXx6ASSVmERfsynYBiX2X6SJRnZU84/9DZdnFvvxmAjBOt6QpBlc4J/0DxvkTCqpclvziL6BCCPnjdlIB3Pu3BxsPmygUY7Ii2zbdCdliiow=" + }, + { + "rawBytes": "MIIB9zCCAXygAwIBAgIUALZNAPFdxHPwjeDloDwyYChAO/4wCgYIKoZIzj0EAwMwKjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTAeFw0yMTEwMDcxMzU2NTlaFw0zMTEwMDUxMzU2NThaMCoxFTATBgNVBAoTDHNpZ3N0b3JlLmRldjERMA8GA1UEAxMIc2lnc3RvcmUwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAT7XeFT4rb3PQGwS4IajtLk3/OlnpgangaBclYpsYBr5i+4ynB07ceb3LP0OIOZdxexX69c5iVuyJRQ+Hz05yi+UF3uBWAlHpiS5sh0+H2GHE7SXrk1EC5m1Tr19L9gg92jYzBhMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRYwB5fkUWlZql6zJChkyLQKsXF+jAfBgNVHSMEGDAWgBRYwB5fkUWlZql6zJChkyLQKsXF+jAKBggqhkjOPQQDAwNpADBmAjEAj1nHeXZp+13NWBNa+EDsDP8G1WWg1tCMWP/WHPqpaVo0jhsweNFZgSs0eE7wYI4qAjEA2WB9ot98sIkoF3vZYdd3/VtWB5b9TNMea7Ix/stJ5TfcLLeABLE4BNJOsQ4vnBHJ" + } + ] + }, + "validFor": { + "start": "2022-04-13T20:06:15.000Z" + } + } + ], + "ctlogs": [ + { + "baseUrl": "https://ctfe.sigstore.dev/test", + "hashAlgorithm": "SHA2_256", + "publicKey": { + "rawBytes": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEbfwR+RJudXscgRBRpKX1XFDy3PyudDxz/SfnRi1fT8ekpfBd2O1uoz7jr3Z8nKzxA69EUQ+eFCFI3zeubPWU7w==", + "keyDetails": "PKIX_ECDSA_P256_SHA_256", + "validFor": { + "start": "2021-03-14T00:00:00.000Z", + "end": "2022-10-31T23:59:59.999Z" + } + }, + "logId": { + "keyId": "CGCS8ChS/2hF0dFrJ4ScRWcYrBY9wzjSbea8IgY2b3I=" + } + }, + { + "baseUrl": "https://ctfe.sigstore.dev/2022", + "hashAlgorithm": "SHA2_256", + "publicKey": { + "rawBytes": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEiPSlFi0CmFTfEjCUqF9HuCEcYXNKAaYalIJmBZ8yyezPjTqhxrKBpMnaocVtLJBI1eM3uXnQzQGAJdJ4gs9Fyw==", + "keyDetails": "PKIX_ECDSA_P256_SHA_256", + "validFor": { + "start": "2022-10-20T00:00:00.000Z" + } + }, + "logId": { + "keyId": "3T0wasbHETJjGR4cmWc3AqJKXrjePK3/h4pygC8p7o4=" + } + } + ], + "timestampAuthorities": [ + { + "subject": { + "organization": "GitHub, Inc.", + "commonName": "Internal Services Root" + }, + "certChain": { + "certificates": [ + { + "rawBytes": "MIIB3DCCAWKgAwIBAgIUchkNsH36Xa04b1LqIc+qr9DVecMwCgYIKoZIzj0EAwMwMjEVMBMGA1UEChMMR2l0SHViLCBJbmMuMRkwFwYDVQQDExBUU0EgaW50ZXJtZWRpYXRlMB4XDTIzMDQxNDAwMDAwMFoXDTI0MDQxMzAwMDAwMFowMjEVMBMGA1UEChMMR2l0SHViLCBJbmMuMRkwFwYDVQQDExBUU0EgVGltZXN0YW1waW5nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEUD5ZNbSqYMd6r8qpOOEX9ibGnZT9GsuXOhr/f8U9FJugBGExKYp40OULS0erjZW7xV9xV52NnJf5OeDq4e5ZKqNWMFQwDgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMIMAwGA1UdEwEB/wQCMAAwHwYDVR0jBBgwFoAUaW1RudOgVt0leqY0WKYbuPr47wAwCgYIKoZIzj0EAwMDaAAwZQIwbUH9HvD4ejCZJOWQnqAlkqURllvu9M8+VqLbiRK+zSfZCZwsiljRn8MQQRSkXEE5AjEAg+VxqtojfVfu8DhzzhCx9GKETbJHb19iV72mMKUbDAFmzZ6bQ8b54Zb8tidy5aWe" + }, + { + "rawBytes": "MIICEDCCAZWgAwIBAgIUX8ZO5QXP7vN4dMQ5e9sU3nub8OgwCgYIKoZIzj0EAwMwODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MB4XDTIzMDQxNDAwMDAwMFoXDTI4MDQxMjAwMDAwMFowMjEVMBMGA1UEChMMR2l0SHViLCBJbmMuMRkwFwYDVQQDExBUU0EgaW50ZXJtZWRpYXRlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEvMLY/dTVbvIJYANAuszEwJnQE1llftynyMKIMhh48HmqbVr5ygybzsLRLVKbBWOdZ21aeJz+gZiytZetqcyF9WlER5NEMf6JV7ZNojQpxHq4RHGoGSceQv/qvTiZxEDKo2YwZDAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUaW1RudOgVt0leqY0WKYbuPr47wAwHwYDVR0jBBgwFoAU9NYYlobnAG4c0/qjxyH/lq/wz+QwCgYIKoZIzj0EAwMDaQAwZgIxAK1B185ygCrIYFlIs3GjswjnwSMG6LY8woLVdakKDZxVa8f8cqMs1DhcxJ0+09w95QIxAO+tBzZk7vjUJ9iJgD4R6ZWTxQWKqNm74jO99o+o9sv4FI/SZTZTFyMn0IJEHdNmyA==" + }, + { + "rawBytes": "MIIB9DCCAXqgAwIBAgIUa/JAkdUjK4JUwsqtaiRJGWhqLSowCgYIKoZIzj0EAwMwODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MB4XDTIzMDQxNDAwMDAwMFoXDTMzMDQxMTAwMDAwMFowODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEf9jFAXxz4kx68AHRMOkFBhflDcMTvzaXz4x/FCcXjJ/1qEKon/qPIGnaURskDtyNbNDOpeJTDDFqt48iMPrnzpx6IZwqemfUJN4xBEZfza+pYt/iyod+9tZr20RRWSv/o0UwQzAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBAjAdBgNVHQ4EFgQU9NYYlobnAG4c0/qjxyH/lq/wz+QwCgYIKoZIzj0EAwMDaAAwZQIxALZLZ8BgRXzKxLMMN9VIlO+e4hrBnNBgF7tz7Hnrowv2NetZErIACKFymBlvWDvtMAIwZO+ki6ssQ1bsZo98O8mEAf2NZ7iiCgDDU0Vwjeco6zyeh0zBTs9/7gV6AHNQ53xD" + } + ] + }, + "validFor": { + "start": "2023-04-14T00:00:00.000Z" + } + } + ] +} diff --git a/test/unit/internal/test_trust.py b/test/unit/internal/test_trust.py index 60de5de48..d74052e52 100644 --- a/test/unit/internal/test_trust.py +++ b/test/unit/internal/test_trust.py @@ -31,6 +31,31 @@ from sigstore.errors import Error, RootError +class TestTrustedRoot: + def test_good(self, asset): + path = asset("trusted_root/trustedroot.v1.json") + root = TrustedRoot.from_file(path) + + assert ( + root._inner.media_type == TrustedRoot.TrustedRootType.TRUSTED_ROOT_0_1.value + ) + assert len(root._inner.tlogs) == 1 + assert len(root._inner.certificate_authorities) == 2 + assert len(root._inner.ctlogs) == 2 + assert len(root._inner.timestamp_authorities) == 1 + + def test_bad_media_type(self, asset): + path = asset("trusted_root/trustedroot.badtype.json") + + with pytest.raises( + Error, match="unsupported trusted root format: bad-media-type" + ): + TrustedRoot.from_file(path) + + +# TODO(ww): Move these into appropriate class-scoped tests. + + def test_trust_root_tuf_caches_and_requests(mock_staging_tuf, tuf_dirs): # start with empty target cache, empty local metadata dir data_dir, cache_dir = tuf_dirs From f7f33076ddda487c920743e3f3f0b970df2b17fb Mon Sep 17 00:00:00 2001 From: William Woodruff Date: Wed, 15 May 2024 12:09:24 -0400 Subject: [PATCH 17/18] CHANGELOG: record changes Signed-off-by: William Woodruff --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e2c610803..8280ef06f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,10 @@ All versions prior to 0.9.0 are untracked. for representing in-toto statements and DSSE envelopes ([#930](https://github.com/sigstore/sigstore-python/pull/930)) +* CLI: The `--trust-config` flag has been added as a global option, + enabling consistent "BYO PKI" uses of `sigstore` with a single flag + ([#1010](https://github.com/sigstore/sigstore-python/pull/1010)) + ### Removed * **BREAKING API CHANGE**: `SigningResult` has been removed. @@ -44,6 +48,11 @@ All versions prior to 0.9.0 are untracked. The public verification and policy APIs now raise `sigstore.errors.VerificationError` on failure. +* **BREAKING CLI CHANGE**: The `--rekor-url` and `--fulcio-url` + flags have been entirely removed. To configure a custom PKI, use + `--trust-config` + ([#1010](https://github.com/sigstore/sigstore-python/pull/1010)) + ### Changed * **BREAKING API CHANGE**: `Verifier.verify(...)` now takes a `bytes | Hashed` From 5c49a6dd829144e402a39dde72dcefaa8f318dae Mon Sep 17 00:00:00 2001 From: William Woodruff Date: Thu, 16 May 2024 10:39:25 -0400 Subject: [PATCH 18/18] README: fix `--help` Signed-off-by: William Woodruff --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8a61276b9..a2c6a30ed 100644 --- a/README.md +++ b/README.md @@ -238,7 +238,7 @@ usage: sigstore verify github [-h] [-v] [--certificate FILE] [--ref REF] FILE [FILE ...] -options: +optional arguments: -h, --help show this help message and exit -v, --verbose run with additional debug logging; supply multiple times to increase verbosity (default: 0)