diff --git a/.gitattributes b/.gitattributes index 9dbb6a9ec..a901a4f18 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,5 +1,5 @@ # These directories contain TUF and other assets that are either digested # or sized-checked so CRLF normalization breaks them. sigstore/_store/** binary diff=text -test/unit/assets/** binary diff=text -test/unit/assets/x509/** -binary +test/assets/** binary diff=text +test/assets/x509/** -binary diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 014edab33..486d6ec03 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -48,7 +48,9 @@ jobs: # This in turn effectively exercises the correctness of our # "online-only" test markers, since any test that's online # but not marked as such will fail. - unshare --map-root-user --net make test TEST_ARGS="--skip-online -vv --showlocals" + # We also explicitly exclude the intergration tests, since these are + # always online. + unshare --map-root-user --net make test T="test/unit" TEST_ARGS="--skip-online -vv --showlocals" - name: test run: make test TEST_ARGS="-vv --showlocals" diff --git a/.gitignore b/.gitignore index fa3baff73..5bf6416f6 100644 --- a/.gitignore +++ b/.gitignore @@ -23,6 +23,5 @@ build !sigstore/_store/*.crt !sigstore/_store/*.pem !sigstore/_store/*.pub -!test/unit/assets/* -!test/unit/assets/x509/* -!test/unit/assets/staging-tuf/* +!test/assets/** +!test/assets/staging-tuf/** diff --git a/CHANGELOG.md b/CHANGELOG.md index e2d964f82..46822663d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,22 @@ All versions prior to 0.9.0 are untracked. ## [Unreleased] +### Added + +* API: `models.Bundle.BundleType` is now a public API + ([#1089](https://github.com/sigstore/sigstore-python/pull/1089)) + +* CLI: The `sigstore plumbing` subcommand hierarchy has been added. This + hierarchy is for *developer-only* interactions, such as fixing malformed + Sigstore bundles. These subcommands are **not considered stable until + explicitly documented as such**. + ([#1089](https://github.com/sigstore/sigstore-python/pull/1089)) + +### Changed + +* CLI: The default console logger now emits to `stderr`, rather than `stdout` + ([#1089](https://github.com/sigstore/sigstore-python/pull/1089)) + ## [3.1.0] ### Added diff --git a/Makefile b/Makefile index e18a279b3..9b89be7d0 100644 --- a/Makefile +++ b/Makefile @@ -40,7 +40,7 @@ endif ifneq ($(T),) T := $(T) else - T := test/unit + T := test/unit test/integration endif .PHONY: all @@ -91,7 +91,7 @@ test-interactive: test gen-x509-testcases: $(VENV)/pyvenv.cfg . $(VENV_BIN)/activate && \ export TESTCASE_OVERWRITE=1 && \ - python test/unit/assets/x509/build-testcases.py && \ + python test/assets/x509/build-testcases.py && \ git diff --exit-code .PHONY: doc diff --git a/README.md b/README.md index 406d54c05..0f64732b3 100644 --- a/README.md +++ b/README.md @@ -108,6 +108,7 @@ positional arguments: get-identity-token retrieve and return a Sigstore-compatible OpenID Connect token + plumbing developer-only plumbing operations optional arguments: -h, --help show this help message and exit diff --git a/sigstore/_cli.py b/sigstore/_cli.py index eee57553c..73b19b7d8 100644 --- a/sigstore/_cli.py +++ b/sigstore/_cli.py @@ -24,16 +24,21 @@ from cryptography.hazmat.primitives.serialization import Encoding from cryptography.x509 import load_pem_x509_certificate +from rich.console import Console from rich.logging import RichHandler +from sigstore_protobuf_specs.dev.sigstore.bundle.v1 import ( + Bundle as RawBundle, +) from sigstore import __version__, dsse from sigstore._internal.fulcio.client import ExpiredCertificate from sigstore._internal.rekor import _hashedrekord_from_parts +from sigstore._internal.rekor.client import RekorClient 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.models import Bundle, InvalidBundle from sigstore.oidc import ( DEFAULT_OAUTH_ISSUER_URL, ExpiredIdentity, @@ -47,7 +52,10 @@ policy, ) -logging.basicConfig(format="%(message)s", datefmt="[%X]", handlers=[RichHandler()]) +_console = Console(file=sys.stderr) +logging.basicConfig( + format="%(message)s", datefmt="[%X]", handlers=[RichHandler(console=_console)] +) _logger = logging.getLogger(__name__) # NOTE: We configure the top package logger, rather than the root logger, @@ -56,7 +64,15 @@ _package_logger.setLevel(os.environ.get("SIGSTORE_LOGLEVEL", "INFO").upper()) -def _die(args: argparse.Namespace, message: str) -> NoReturn: +def _fatal(message: str) -> NoReturn: + """ + Logs a fatal condition and exits. + """ + _logger.fatal(message) + sys.exit(1) + + +def _invalid_arguments(args: argparse.Namespace, message: str) -> NoReturn: """ An `argparse` helper that fixes up the type hints on our use of `ArgumentParser.error`. @@ -405,12 +421,54 @@ def _parser() -> argparse.ArgumentParser: ) _add_shared_oidc_options(get_identity_token) + # `sigstore plumbing` + plumbing = subcommands.add_parser( + "plumbing", + help="developer-only plumbing operations", + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + parents=[parent_parser], + ) + plumbing_subcommands = plumbing.add_subparsers( + required=True, + dest="plumbing_subcommand", + metavar="COMMAND", + help="the operation to perform", + ) + + # `sigstore plumbing fix-bundle` + fix_bundle = plumbing_subcommands.add_parser( + "fix-bundle", + help="fix (and optionally upgrade) older bundle formats", + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + parents=[parent_parser], + ) + fix_bundle.add_argument( + "--bundle", + metavar="FILE", + type=Path, + required=True, + help=("The bundle to fix and/or upgrade"), + ) + fix_bundle.add_argument( + "--upgrade-version", + action="store_true", + help="Upgrade the bundle to the latest bundle spec version", + ) + fix_bundle.add_argument( + "--in-place", + action="store_true", + help="Overwrite the input bundle with its fix instead of emitting to stdout", + ) + return parser -def main() -> None: +def main(args: list[str] | None = None) -> None: + if not args: + args = sys.argv[1:] + parser = _parser() - args = parser.parse_args() + args = parser.parse_args(args) # Configure logging upfront, so that we don't miss anything. if args.verbose >= 1: @@ -437,10 +495,12 @@ def main() -> None: if identity: print(identity) else: - _die(args, "No identity token supplied or detected!") - + _invalid_arguments(args, "No identity token supplied or detected!") + elif args.subcommand == "plumbing": + if args.plumbing_subcommand == "fix-bundle": + _fix_bundle(args) else: - _die(args, f"Unknown subcommand: {args.subcommand}") + _invalid_arguments(args, f"Unknown subcommand: {args.subcommand}") except Error as e: e.log_and_exit(_logger, args.verbose >= 1) @@ -453,19 +513,21 @@ def _sign(args: argparse.Namespace) -> None: # `--no-default-files` has no effect on `--bundle`, but we forbid it because # it indicates user confusion. if args.no_default_files and has_bundle: - _die(args, "--no-default-files may not be combined with --bundle.") + _invalid_arguments( + args, "--no-default-files may not be combined with --bundle." + ) # Fail if `--signature` or `--certificate` is specified *and* we have more # than one input. if (has_sig or has_crt or has_bundle) and len(args.files) > 1: - _die( + _invalid_arguments( args, "Error: --signature, --certificate, and --bundle can't be used with " "explicit outputs for multiple inputs.", ) if args.output_directory and (has_sig or has_crt or has_bundle): - _die( + _invalid_arguments( args, "Error: --signature, --certificate, and --bundle can't be used with " "an explicit output directory.", @@ -473,14 +535,16 @@ def _sign(args: argparse.Namespace) -> None: # Fail if either `--signature` or `--certificate` is specified, but not both. if has_sig ^ has_crt: - _die(args, "Error: --signature and --certificate must be used together.") + _invalid_arguments( + args, "Error: --signature and --certificate must be used together." + ) # Build up the map of inputs -> outputs ahead of any signing operations, # so that we can fail early if overwriting without `--overwrite`. output_map: dict[Path, dict[str, Path | None]] = {} for file in args.files: if not file.is_file(): - _die(args, f"Input must be a file: {file}") + _invalid_arguments(args, f"Input must be a file: {file}") sig, cert, bundle = ( args.signature, @@ -490,7 +554,9 @@ def _sign(args: argparse.Namespace) -> None: output_dir = args.output_directory if args.output_directory else file.parent if output_dir.exists() and not output_dir.is_dir(): - _die(args, f"Output directory exists and is not a directory: {output_dir}") + _invalid_arguments( + args, f"Output directory exists and is not a directory: {output_dir}" + ) output_dir.mkdir(parents=True, exist_ok=True) if not bundle and not args.no_default_files: @@ -506,7 +572,7 @@ def _sign(args: argparse.Namespace) -> None: extants.append(str(bundle)) if extants: - _die( + _invalid_arguments( args, "Refusing to overwrite outputs without --overwrite: " f"{', '.join(extants)}", @@ -543,7 +609,7 @@ def _sign(args: argparse.Namespace) -> None: identity = _get_identity(args) if not identity: - _die(args, "No identity token supplied or detected!") + _invalid_arguments(args, "No identity token supplied or detected!") with signing_ctx.signer(identity) as signer: for file, outputs in output_map.items(): @@ -609,7 +675,7 @@ def _collect_verification_state( # Fail if --certificate, --signature, or --bundle is specified and we # have more than one input. if (args.certificate or args.signature or args.bundle) and len(args.files) > 1: - _die( + _invalid_arguments( args, "--certificate, --signature, or --bundle can only be used " "with a single input file", @@ -617,18 +683,22 @@ def _collect_verification_state( # Fail if `--certificate` or `--signature` is used with `--bundle`. if args.bundle and (args.certificate or args.signature): - _die(args, "--bundle cannot be used with --certificate or --signature") + _invalid_arguments( + args, "--bundle cannot be used with --certificate or --signature" + ) # Fail if `--certificate` or `--signature` is used with `--offline`. if args.offline and (args.certificate or args.signature): - _die(args, "--offline cannot be used with --certificate or --signature") + _invalid_arguments( + args, "--offline cannot be used with --certificate or --signature" + ) # The converse of `sign`: we build up an expected input map and check # that we have everything so that we can fail early. input_map = {} for file in args.files: if not file.is_file(): - _die(args, f"Input must be a file: {file}") + _invalid_arguments(args, f"Input must be a file: {file}") sig, cert, bundle = ( args.signature, @@ -656,7 +726,7 @@ def _collect_verification_state( elif bundle.is_file() and legacy_default_bundle.is_file(): # Don't allow the user to implicitly verify `{input}.sigstore.json` if # `{input}.sigstore` is also present, since this implies user confusion. - _die( + _invalid_arguments( args, f"Conflicting inputs: {bundle} and {legacy_default_bundle}", ) @@ -678,7 +748,7 @@ def _collect_verification_state( input_map[file] = {"bundle": bundle} if missing: - _die( + _invalid_arguments( args, f"Missing verification materials for {(file)}: {', '.join(missing)}", ) @@ -719,7 +789,9 @@ def _collect_verification_state( _hashedrekord_from_parts(cert, signature, hashed) ) if log_entry is None: - _die(args, f"No matching log entry for {file}'s verification materials") + _invalid_arguments( + args, f"No matching log entry for {file}'s verification materials" + ) bundle = Bundle.from_parts(cert, signature, log_entry) _logger.debug(f"Verifying contents from: {file}") @@ -752,7 +824,7 @@ def _verify_github(args: argparse.Namespace) -> None: # We require at least one of `--cert-identity` or `--repository`, # to minimize the risk of user confusion about what's being verified. if not (args.cert_identity or args.workflow_repository): - _die(args, "--cert-identity or --repository is required") + _invalid_arguments(args, "--cert-identity or --repository is required") # No matter what the user configures above, we require the OIDC issuer to # be GitHub Actions. @@ -852,3 +924,44 @@ def _get_identity(args: argparse.Namespace) -> Optional[IdentityToken]: ) return token + + +def _fix_bundle(args: argparse.Namespace) -> None: + # NOTE: We could support `--trusted-root` here in the future, + # for custom Rekor instances. + if args.staging: + rekor = RekorClient.staging() + else: + rekor = RekorClient.production() + + raw_bundle = RawBundle().from_json(args.bundle.read_text()) + + if len(raw_bundle.verification_material.tlog_entries) != 1: + _fatal("unfixable bundle: must have exactly one log entry") + + # Some old versions of sigstore-python (1.x) produce malformed + # bundles where the inclusion proof is present but without + # its checkpoint. We fix these by retrieving the complete entry + # from Rekor and replacing the incomplete entry. + tlog_entry = raw_bundle.verification_material.tlog_entries[0] + inclusion_proof = tlog_entry.inclusion_proof + if not inclusion_proof.checkpoint: + _logger.info("fixable: bundle's log entry is missing a checkpoint") + new_entry = rekor.log.entries.get(log_index=tlog_entry.log_index)._to_rekor() + raw_bundle.verification_material.tlog_entries = [new_entry] + + # Try to create our invariant-preserving Bundle from the any changes above. + try: + bundle = Bundle(raw_bundle) + except InvalidBundle as e: + e.log_and_exit(_logger) + + # Round-trip through the bundle's parts to induce a version upgrade, + # if requested. + if args.upgrade_version: + bundle = Bundle._from_parts(*bundle._to_parts()) + + if args.in_place: + args.bundle.write_text(bundle.to_json()) + else: + print(bundle.to_json()) diff --git a/sigstore/_internal/rekor/client.py b/sigstore/_internal/rekor/client.py index d90f96968..86fc0421e 100644 --- a/sigstore/_internal/rekor/client.py +++ b/sigstore/_internal/rekor/client.py @@ -246,8 +246,6 @@ def __del__(self) -> None: def production(cls) -> RekorClient: """ Returns a `RekorClient` populated with the default Rekor production instance. - - trust_root must be a `TrustedRoot` for the production TUF repository. """ return cls( DEFAULT_REKOR_URL, @@ -257,8 +255,6 @@ def production(cls) -> RekorClient: def staging(cls) -> RekorClient: """ Returns a `RekorClient` populated with the default Rekor staging instance. - - trust_root must be a `TrustedRoot` for the staging TUF repository. """ return cls(STAGING_REKOR_URL) diff --git a/sigstore/_utils.py b/sigstore/_utils.py index 970d77d81..86b1ccdc5 100644 --- a/sigstore/_utils.py +++ b/sigstore/_utils.py @@ -21,7 +21,6 @@ import base64 import hashlib import sys -from enum import Enum from typing import IO, NewType, Type, Union from cryptography.hazmat.primitives import serialization @@ -62,21 +61,6 @@ """ -class BundleType(str, Enum): - """ - Known Sigstore bundle media types. - """ - - BUNDLE_0_1 = "application/vnd.dev.sigstore.bundle+json;version=0.1" - BUNDLE_0_2 = "application/vnd.dev.sigstore.bundle+json;version=0.2" - BUNDLE_0_3_ALT = "application/vnd.dev.sigstore.bundle+json;version=0.3" - BUNDLE_0_3 = "application/vnd.dev.sigstore.bundle.v0.3+json" - - def __str__(self) -> str: - """Returns the variant's string value.""" - return self.value - - def load_pem_public_key( key_pem: bytes, *, diff --git a/sigstore/models.py b/sigstore/models.py index 31b2468fc..fd323b415 100644 --- a/sigstore/models.py +++ b/sigstore/models.py @@ -21,6 +21,7 @@ import base64 import logging import typing +from enum import Enum from textwrap import dedent from typing import Any, List, Optional @@ -57,7 +58,6 @@ from sigstore._internal.rekor.checkpoint import verify_checkpoint from sigstore._utils import ( B64Str, - BundleType, KeyID, cert_is_leaf, cert_is_root_ca, @@ -199,8 +199,8 @@ def _from_dict_rekor(cls, dict_: dict[str, Any]) -> LogEntry: inclusion_proof: InclusionProof | None = tlog_entry.inclusion_proof # This check is required by us as the client, not the # protobuf-specs themselves. - if inclusion_proof is None or inclusion_proof.checkpoint.envelope is None: - raise InvalidBundle("entry must contain inclusion proof") + if not inclusion_proof or not inclusion_proof.checkpoint.envelope: + raise InvalidBundle("entry must contain inclusion proof, with checkpoint") parsed_inclusion_proof = LogInclusionProof( checkpoint=inclusion_proof.checkpoint.envelope, @@ -224,7 +224,12 @@ def _from_dict_rekor(cls, dict_: dict[str, Any]) -> LogEntry: ), ) - def _to_dict_rekor(self) -> dict[str, Any]: + def _to_rekor(self) -> rekor_v1.TransparencyLogEntry: + """ + Create a new protobuf-level `TransparencyLogEntry` from this `LogEntry`. + + @private + """ inclusion_promise: rekor_v1.InclusionPromise | None = None if self.inclusion_promise: inclusion_promise = rekor_v1.InclusionPromise( @@ -259,8 +264,7 @@ def _to_dict_rekor(self) -> dict[str, Any]: kind=body_entry.kind, version=body_entry.api_version ) - tlog_entry_dict: dict[str, Any] = tlog_entry.to_dict() - return tlog_entry_dict + return tlog_entry def encode_canonical(self) -> bytes: """ @@ -350,6 +354,20 @@ class Bundle: Represents a Sigstore bundle. """ + class BundleType(str, Enum): + """ + Known Sigstore bundle media types. + """ + + BUNDLE_0_1 = "application/vnd.dev.sigstore.bundle+json;version=0.1" + BUNDLE_0_2 = "application/vnd.dev.sigstore.bundle+json;version=0.2" + BUNDLE_0_3_ALT = "application/vnd.dev.sigstore.bundle+json;version=0.3" + BUNDLE_0_3 = "application/vnd.dev.sigstore.bundle.v0.3+json" + + def __str__(self) -> str: + """Returns the variant's string value.""" + return self.value + def __init__(self, inner: _Bundle) -> None: """ Creates a new bundle. This is not a public API; use @@ -372,12 +390,15 @@ def _verify(self) -> None: # The bundle must have a recognized media type. try: - media_type = BundleType(self._inner.media_type) + media_type = Bundle.BundleType(self._inner.media_type) except ValueError: raise InvalidBundle(f"unsupported bundle format: {self._inner.media_type}") # Extract the signing certificate. - if media_type in (BundleType.BUNDLE_0_3, BundleType.BUNDLE_0_3_ALT): + if media_type in ( + Bundle.BundleType.BUNDLE_0_3, + Bundle.BundleType.BUNDLE_0_3_ALT, + ): # For "v3" bundles, the signing certificate is the only one present. leaf_cert = load_der_x509_certificate( self._inner.verification_material.certificate.raw_bytes @@ -445,7 +466,7 @@ def _verify(self) -> None: # (when constructing the LogEntry). log_entry = LogEntry._from_dict_rekor(tlog_entry.to_dict()) - if media_type == BundleType.BUNDLE_0_1: + if media_type == Bundle.BundleType.BUNDLE_0_1: if not log_entry.inclusion_promise: raise InvalidBundle("bundle must contain an inclusion promise") if not log_entry.inclusion_proof.checkpoint: @@ -496,6 +517,23 @@ def to_json(self) -> str: """ return self._inner.to_json() + def _to_parts( + self, + ) -> tuple[Certificate, common_v1.MessageSignature | dsse.Envelope, LogEntry]: + """ + Decompose the `Bundle` into its core constituent parts. + + @private + """ + + content: common_v1.MessageSignature | dsse.Envelope + if self._dsse_envelope: + content = self._dsse_envelope + else: + content = self._inner.message_signature + + return (self.signing_certificate, content, self.log_entry) + @classmethod def from_parts(cls, cert: Certificate, sig: bytes, log_entry: LogEntry) -> Bundle: """ @@ -519,7 +557,7 @@ def _from_parts( """ inner = _Bundle( - media_type=BundleType.BUNDLE_0_3.value, + media_type=Bundle.BundleType.BUNDLE_0_3.value, verification_material=bundle_v1.VerificationMaterial( certificate=common_v1.X509Certificate(cert.public_bytes(Encoding.DER)), ), @@ -531,8 +569,7 @@ def _from_parts( else: inner.dsse_envelope = content._inner - tlog_entry = rekor_v1.TransparencyLogEntry() - tlog_entry.from_dict(log_entry._to_dict_rekor()) + tlog_entry = log_entry._to_rekor() inner.verification_material.tlog_entries = [tlog_entry] return cls(inner) diff --git a/test/unit/assets/a.txt b/test/assets/a.txt similarity index 100% rename from test/unit/assets/a.txt rename to test/assets/a.txt diff --git a/test/unit/assets/a.txt.crt b/test/assets/a.txt.crt similarity index 100% rename from test/unit/assets/a.txt.crt rename to test/assets/a.txt.crt diff --git a/test/unit/assets/a.txt.sig b/test/assets/a.txt.sig similarity index 100% rename from test/unit/assets/a.txt.sig rename to test/assets/a.txt.sig diff --git a/test/unit/assets/b.txt b/test/assets/b.txt similarity index 100% rename from test/unit/assets/b.txt rename to test/assets/b.txt diff --git a/test/unit/assets/b.txt.crt b/test/assets/b.txt.crt similarity index 100% rename from test/unit/assets/b.txt.crt rename to test/assets/b.txt.crt diff --git a/test/unit/assets/b.txt.sig b/test/assets/b.txt.sig similarity index 100% rename from test/unit/assets/b.txt.sig rename to test/assets/b.txt.sig diff --git a/test/unit/assets/bad.txt b/test/assets/bad.txt similarity index 100% rename from test/unit/assets/bad.txt rename to test/assets/bad.txt diff --git a/test/unit/assets/bad.txt.crt b/test/assets/bad.txt.crt similarity index 100% rename from test/unit/assets/bad.txt.crt rename to test/assets/bad.txt.crt diff --git a/test/unit/assets/bad.txt.sig b/test/assets/bad.txt.sig similarity index 100% rename from test/unit/assets/bad.txt.sig rename to test/assets/bad.txt.sig diff --git a/test/unit/assets/bundle.txt b/test/assets/bundle.txt similarity index 100% rename from test/unit/assets/bundle.txt rename to test/assets/bundle.txt diff --git a/test/unit/assets/bundle.txt.crt b/test/assets/bundle.txt.crt similarity index 100% rename from test/unit/assets/bundle.txt.crt rename to test/assets/bundle.txt.crt diff --git a/test/unit/assets/bundle.txt.sig b/test/assets/bundle.txt.sig similarity index 100% rename from test/unit/assets/bundle.txt.sig rename to test/assets/bundle.txt.sig diff --git a/test/unit/assets/bundle.txt.sigstore b/test/assets/bundle.txt.sigstore similarity index 100% rename from test/unit/assets/bundle.txt.sigstore rename to test/assets/bundle.txt.sigstore diff --git a/test/unit/assets/bundle_cve_2022_36056.txt b/test/assets/bundle_cve_2022_36056.txt similarity index 100% rename from test/unit/assets/bundle_cve_2022_36056.txt rename to test/assets/bundle_cve_2022_36056.txt diff --git a/test/unit/assets/bundle_cve_2022_36056.txt.sigstore b/test/assets/bundle_cve_2022_36056.txt.sigstore similarity index 100% rename from test/unit/assets/bundle_cve_2022_36056.txt.sigstore rename to test/assets/bundle_cve_2022_36056.txt.sigstore diff --git a/test/unit/assets/bundle_invalid_version.txt b/test/assets/bundle_invalid_version.txt similarity index 100% rename from test/unit/assets/bundle_invalid_version.txt rename to test/assets/bundle_invalid_version.txt diff --git a/test/unit/assets/bundle_invalid_version.txt.sigstore b/test/assets/bundle_invalid_version.txt.sigstore similarity index 100% rename from test/unit/assets/bundle_invalid_version.txt.sigstore rename to test/assets/bundle_invalid_version.txt.sigstore diff --git a/test/unit/assets/bundle_no_cert_v1.txt b/test/assets/bundle_no_cert_v1.txt similarity index 100% rename from test/unit/assets/bundle_no_cert_v1.txt rename to test/assets/bundle_no_cert_v1.txt diff --git a/test/unit/assets/bundle_no_cert_v1.txt.sigstore b/test/assets/bundle_no_cert_v1.txt.sigstore similarity index 100% rename from test/unit/assets/bundle_no_cert_v1.txt.sigstore rename to test/assets/bundle_no_cert_v1.txt.sigstore diff --git a/test/unit/assets/bundle_no_checkpoint.txt b/test/assets/bundle_no_checkpoint.txt similarity index 100% rename from test/unit/assets/bundle_no_checkpoint.txt rename to test/assets/bundle_no_checkpoint.txt diff --git a/test/unit/assets/bundle_no_checkpoint.txt.bundle b/test/assets/bundle_no_checkpoint.txt.bundle similarity index 100% rename from test/unit/assets/bundle_no_checkpoint.txt.bundle rename to test/assets/bundle_no_checkpoint.txt.bundle diff --git a/test/unit/assets/bundle_no_checkpoint.txt.crt b/test/assets/bundle_no_checkpoint.txt.crt similarity index 100% rename from test/unit/assets/bundle_no_checkpoint.txt.crt rename to test/assets/bundle_no_checkpoint.txt.crt diff --git a/test/unit/assets/bundle_no_checkpoint.txt.sigstore b/test/assets/bundle_no_checkpoint.txt.sigstore similarity index 100% rename from test/unit/assets/bundle_no_checkpoint.txt.sigstore rename to test/assets/bundle_no_checkpoint.txt.sigstore diff --git a/test/unit/assets/bundle_no_log_entry.txt b/test/assets/bundle_no_log_entry.txt similarity index 100% rename from test/unit/assets/bundle_no_log_entry.txt rename to test/assets/bundle_no_log_entry.txt diff --git a/test/unit/assets/bundle_no_log_entry.txt.sigstore b/test/assets/bundle_no_log_entry.txt.sigstore similarity index 100% rename from test/unit/assets/bundle_no_log_entry.txt.sigstore rename to test/assets/bundle_no_log_entry.txt.sigstore diff --git a/test/unit/assets/bundle_v3.txt b/test/assets/bundle_v3.txt similarity index 100% rename from test/unit/assets/bundle_v3.txt rename to test/assets/bundle_v3.txt diff --git a/test/unit/assets/bundle_v3.txt.sigstore b/test/assets/bundle_v3.txt.sigstore similarity index 100% rename from test/unit/assets/bundle_v3.txt.sigstore rename to test/assets/bundle_v3.txt.sigstore diff --git a/test/unit/assets/bundle_v3_alt.txt b/test/assets/bundle_v3_alt.txt similarity index 100% rename from test/unit/assets/bundle_v3_alt.txt rename to test/assets/bundle_v3_alt.txt diff --git a/test/unit/assets/bundle_v3_alt.txt.sigstore b/test/assets/bundle_v3_alt.txt.sigstore similarity index 100% rename from test/unit/assets/bundle_v3_alt.txt.sigstore rename to test/assets/bundle_v3_alt.txt.sigstore diff --git a/test/unit/assets/bundle_v3_github.whl b/test/assets/bundle_v3_github.whl similarity index 100% rename from test/unit/assets/bundle_v3_github.whl rename to test/assets/bundle_v3_github.whl diff --git a/test/unit/assets/bundle_v3_github.whl.sigstore b/test/assets/bundle_v3_github.whl.sigstore similarity index 100% rename from test/unit/assets/bundle_v3_github.whl.sigstore rename to test/assets/bundle_v3_github.whl.sigstore diff --git a/test/unit/assets/c.txt b/test/assets/c.txt similarity index 100% rename from test/unit/assets/c.txt rename to test/assets/c.txt diff --git a/test/unit/assets/c.txt.crt b/test/assets/c.txt.crt similarity index 100% rename from test/unit/assets/c.txt.crt rename to test/assets/c.txt.crt diff --git a/test/unit/assets/c.txt.sig b/test/assets/c.txt.sig similarity index 100% rename from test/unit/assets/c.txt.sig rename to test/assets/c.txt.sig diff --git a/test/assets/integration/Python-3.12.5.tgz.sigstore b/test/assets/integration/Python-3.12.5.tgz.sigstore new file mode 100644 index 000000000..548303b3f --- /dev/null +++ b/test/assets/integration/Python-3.12.5.tgz.sigstore @@ -0,0 +1 @@ +{"mediaType": "application/vnd.dev.sigstore.bundle+json;version=0.1", "verificationMaterial": {"x509CertificateChain": {"certificates": [{"rawBytes": "MIIC5zCCAm2gAwIBAgIUJlhDDqj05f6TwIEKO4YUQ+JeMUgwCgYIKoZIzj0EAwMwNzEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRlcm1lZGlhdGUwHhcNMjQwODA2MjAzMjQ3WhcNMjQwODA2MjA0MjQ3WjAAMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEyfxCuMuSwrq27CDuXVog75EfL9WfcuY9Z2NmxikgeF8oMEG4mMN+ULqfNR/uM9+XzT5ideXYPYp+I9Sj/hDFv4G7dk1YYgvySUqrY7uxeUYvVSk+Y3ZiPgk9ADu6wPAzo4IBbzCCAWswDgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMDMB0GA1UdDgQWBBRXq80OR1/j1OhcQlF00SLIgjjKgDAfBgNVHSMEGDAWgBTf0+nPViQRlvmo2OkoVaLGLhhkPzAfBgNVHREBAf8EFTATgRF0aG9tYXNAcHl0aG9uLm9yZzApBgorBgEEAYO/MAEBBBtodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20wKwYKKwYBBAGDvzABCAQdDBtodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20wgYoGCisGAQQB1nkCBAIEfAR6AHgAdgDdPTBqxscRMmMZHhyZZzcCokpeuN48rf+HinKALynujgAAAZEpZPIRAAAEAwBHMEUCIQDyXwfd7XnVIidGsF1oawebvXpVrlKE5xaGoywy7KU+XQIgWiFoQP4yq0cZmuY3BWBSvjXC2LFHOt75Bgda6wN40mwwCgYIKoZIzj0EAwMDaAAwZQIwbUsZO2Go1XXJx31LtqG2wA6W8yQUMzoieEy6aSF5h9Ka3G80vJnlGIu1Gv1BgGSuAjEA8I8O6Nb7pGpejOSHEb+eKFBjHJzsAYhRc4+QaVSi2poc9UMvg01qfTtXyE/HsNgw"}]}, "tlogEntries": [{"logIndex": "118981923", "logId": {"keyId": "wNI9atQGlz+VWfO6LRygH4QUfY/8W4RFwiT5i5WRgB0="}, "kindVersion": {"kind": "hashedrekord", "version": "0.0.1"}, "integratedTime": "1722976367", "inclusionPromise": {"signedEntryTimestamp": "MEUCIFHZzeCjijPmhyFe2nM04kIAJ7MUxBZUE5/dDN2az/YYAiEApLjBB/nZJJHYoMXhg+VfKOPmRNymdDQevt390XU6xoo="}, "inclusionProof": {"logIndex": "114818492", "rootHash": "IqAkWiiNkCTFxyYb94s81eNqaapA73SgxBxd06iPI04=", "treeSize": "114818493", "hashes": ["PMN+wGyFObrmIvP3UuG8F/K3r+S5gnVUNjTG9KRxSQI=", "IbBdNH70ZqaY+VA0Gox1yc/e7rTLDAr00GFLtAS1mM0=", "d9hP0b+P5gvyMADKIkgpYQfvzecgmGRsUAAfRXSkCvQ=", "0mWfN8v15Z2C5/2mwHGp1Tns3g82mm+8tcRMCmSlTkQ=", "N/jfjW9aFr+UzHBai8+y+VBVG5BztJO/AZcC+BxllRg=", "aVnjeQ4AARM1lia/y4Z6qLrK9b7yLU9GvzYjrhVNIGQ=", "/oczRbnX0wVoMcxf3FonUslk7JCszDsgFwdWN3hQ/PI=", "bJQEErUPH5I1mbnua8mOhyl0xwcbcK3SE1ktgx9zIZc=", "mJjriUsaYb3cYi8BAKBoYkXOb60BV9QLvVl4JkCof8s=", "FuqiuF+HGbxEPfTq5V1LEOD2xEkbOhSTHhh9OgesRec=", "gdYky8OkC3TR65e8i+N+u+FW8WwVOWv3ReiEdspNMoU=", "8QWire253mh3dyplsqOeYFI2Ar7vM6tDRPFjeMYLxck=", "uQRyyLzWiHmeVVM6L4XonE+3Lh8nQrzaUFXwRnObrjE=", "lvYqunhigwQrJ1cNg7lMmilqxS8D8HoDJPLndmoaKoM=", "1uSClB8CJleRshjxptJIRvzgY8fg8XITEtJZiU2Exwo=", "v7N3pwo5/dDC9hrWE31X4X+pIwTlvQXBlFvUC/xjjdc=", "yPZFEKyq0Jj5sObbCwB/LMHlcgQl8ux2d2IkRYWLIt8=", "ndmjFxe89oJp4z+fXcLQM1BmC+7Sp8m8VMkNIafNhYk=", "a6kLnwN4nPldqWq4OoO6Mz25ZQx1TaLMF0IbMSMVduQ=", "98enzMaC+x5oCMvIZQA5z8vu2apDMCFvE/935NfuPw8="]}, "canonicalizedBody": "eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiaGFzaGVkcmVrb3JkIiwic3BlYyI6eyJkYXRhIjp7Imhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiIzOGRjNGUyYzI2MWQ0OWM2NjExOTYwNjZlZGJmYjcwZmRiMTZiZTRhNzljYzgyMjBjMjI0ZGZlYjU2MzZkNDA1In19LCJzaWduYXR1cmUiOnsiY29udGVudCI6Ik1HVUNNQ1YrbnlnYlJ3RUpkRENJNk9vbCs1R0dzL2RidUdOTzdQU3dBMjl4aHBPSjArQUJRdmwxMnBHekszdXp1bEl6aGdJeEFLbWVDSFYvbUs1cGxlTi9zTHFGaWRobGE5VGFVbXNZaFp5SUJJaCs4NmVydy9GTHBQWGI1bloxOEFXTHJGUWZNQT09IiwicHVibGljS2V5Ijp7ImNvbnRlbnQiOiJMUzB0TFMxQ1JVZEpUaUJEUlZKVVNVWkpRMEZVUlMwdExTMHRDazFKU1VNMWVrTkRRVzB5WjBGM1NVSkJaMGxWU214b1JFUnhhakExWmpaVWQwbEZTMDgwV1ZWUkswcGxUVlZuZDBObldVbExiMXBKZW1vd1JVRjNUWGNLVG5wRlZrMUNUVWRCTVZWRlEyaE5UV015Ykc1ak0xSjJZMjFWZFZwSFZqSk5ValIzU0VGWlJGWlJVVVJGZUZaNllWZGtlbVJIT1hsYVV6RndZbTVTYkFwamJURnNXa2RzYUdSSFZYZElhR05PVFdwUmQwOUVRVEpOYWtGNlRXcFJNMWRvWTA1TmFsRjNUMFJCTWsxcVFUQk5hbEV6VjJwQlFVMUlXWGRGUVZsSUNrdHZXa2w2YWpCRFFWRlpSa3MwUlVWQlEwbEVXV2RCUlhsbWVFTjFUWFZUZDNKeE1qZERSSFZZVm05bk56VkZaa3c1VjJaamRWazVXakpPYlhocGEyY0taVVk0YjAxRlJ6UnRUVTRyVlV4eFprNVNMM1ZOT1N0WWVsUTFhV1JsV0ZsUVdYQXJTVGxUYWk5b1JFWjJORWMzWkdzeFdWbG5kbmxUVlhGeVdUZDFlQXBsVlZsMlZsTnJLMWt6V21sUVoyczVRVVIxTm5kUVFYcHZORWxDWW5wRFEwRlhjM2RFWjFsRVZsSXdVRUZSU0M5Q1FWRkVRV2RsUVUxQ1RVZEJNVlZrQ2twUlVVMU5RVzlIUTBOelIwRlJWVVpDZDAxRVRVSXdSMEV4VldSRVoxRlhRa0pTV0hFNE1FOVNNUzlxTVU5b1kxRnNSakF3VTB4SloycHFTMmRFUVdZS1FtZE9Wa2hUVFVWSFJFRlhaMEpVWmpBcmJsQldhVkZTYkhadGJ6SlBhMjlXWVV4SFRHaG9hMUI2UVdaQ1owNVdTRkpGUWtGbU9FVkdWRUZVWjFKR01BcGhSemwwV1ZoT1FXTkliREJoUnpsMVRHMDVlVnA2UVhCQ1oyOXlRbWRGUlVGWlR5OU5RVVZDUWtKMGIyUklVbmRqZW05MlRESkdhbGt5T1RGaWJsSjZDa3h0WkhaaU1tUnpXbE0xYW1JeU1IZExkMWxMUzNkWlFrSkJSMFIyZWtGQ1EwRlJaRVJDZEc5a1NGSjNZM3B2ZGt3eVJtcFpNamt4WW01U2VreHRaSFlLWWpKa2MxcFROV3BpTWpCM1oxbHZSME5wYzBkQlVWRkNNVzVyUTBKQlNVVm1RVkkyUVVoblFXUm5SR1JRVkVKeGVITmpVazF0VFZwSWFIbGFXbnBqUXdwdmEzQmxkVTQwT0hKbUswaHBia3RCVEhsdWRXcG5RVUZCV2tWd1dsQkpVa0ZCUVVWQmQwSklUVVZWUTBsUlJIbFlkMlprTjFodVZrbHBaRWR6UmpGdkNtRjNaV0oyV0hCV2NteExSVFY0WVVkdmVYZDVOMHRWSzFoUlNXZFhhVVp2VVZBMGVYRXdZMXB0ZFZrelFsZENVM1pxV0VNeVRFWklUM1EzTlVKblpHRUtObmRPTkRCdGQzZERaMWxKUzI5YVNYcHFNRVZCZDAxRVlVRkJkMXBSU1hkaVZYTmFUekpIYnpGWVdFcDRNekZNZEhGSE1uZEJObGM0ZVZGVlRYcHZhUXBsUlhrMllWTkdOV2c1UzJFelJ6Z3dka3B1YkVkSmRURkhkakZDWjBkVGRVRnFSVUU0U1RoUE5rNWlOM0JIY0dWcVQxTklSV0lyWlV0R1FtcElTbnB6Q2tGWmFGSmpOQ3RSWVZaVGFUSndiMk01VlUxMlp6QXhjV1pVZEZoNVJTOUljMDVuZHdvdExTMHRMVVZPUkNCRFJWSlVTVVpKUTBGVVJTMHRMUzB0Q2c9PSJ9fX19"}]}, "messageSignature": {"messageDigest": {"algorithm": "SHA2_256", "digest": "ONxOLCYdScZhGWBm7b+3D9sWvkp5zIIgwiTf61Y21AU="}, "signature": "MGUCMCV+nygbRwEJdDCI6Ool+5GGs/dbuGNO7PSwA29xhpOJ0+ABQvl12pGzK3uzulIzhgIxAKmeCHV/mK5pleN/sLqFidhla9TaUmsYhZyIBIh+86erw/FLpPXb5nZ18AWLrFQfMA=="}} diff --git a/test/unit/assets/offline-rekor.txt b/test/assets/offline-rekor.txt similarity index 100% rename from test/unit/assets/offline-rekor.txt rename to test/assets/offline-rekor.txt diff --git a/test/unit/assets/offline-rekor.txt.crt b/test/assets/offline-rekor.txt.crt similarity index 100% rename from test/unit/assets/offline-rekor.txt.crt rename to test/assets/offline-rekor.txt.crt diff --git a/test/unit/assets/offline-rekor.txt.sig b/test/assets/offline-rekor.txt.sig similarity index 100% rename from test/unit/assets/offline-rekor.txt.sig rename to test/assets/offline-rekor.txt.sig diff --git a/test/unit/assets/staging-tuf/2.registry.npmjs.org.json b/test/assets/staging-tuf/2.registry.npmjs.org.json similarity index 100% rename from test/unit/assets/staging-tuf/2.registry.npmjs.org.json rename to test/assets/staging-tuf/2.registry.npmjs.org.json diff --git a/test/unit/assets/staging-tuf/4.root.json b/test/assets/staging-tuf/4.root.json similarity index 100% rename from test/unit/assets/staging-tuf/4.root.json rename to test/assets/staging-tuf/4.root.json diff --git a/test/unit/assets/staging-tuf/4.snapshot.json b/test/assets/staging-tuf/4.snapshot.json similarity index 100% rename from test/unit/assets/staging-tuf/4.snapshot.json rename to test/assets/staging-tuf/4.snapshot.json diff --git a/test/unit/assets/staging-tuf/4.targets.json b/test/assets/staging-tuf/4.targets.json similarity index 100% rename from test/unit/assets/staging-tuf/4.targets.json rename to test/assets/staging-tuf/4.targets.json diff --git a/test/unit/assets/staging-tuf/targets/09ab08698a67354a95d3b8897d9ce7eaef05f06f5ed5f0202d79c228579858ecc5816b7e1b7cc6786abe7d6aaa758e1fcb05900cb749235186c3bf9522d6d7ce.rekor.pub b/test/assets/staging-tuf/targets/09ab08698a67354a95d3b8897d9ce7eaef05f06f5ed5f0202d79c228579858ecc5816b7e1b7cc6786abe7d6aaa758e1fcb05900cb749235186c3bf9522d6d7ce.rekor.pub similarity index 100% rename from test/unit/assets/staging-tuf/targets/09ab08698a67354a95d3b8897d9ce7eaef05f06f5ed5f0202d79c228579858ecc5816b7e1b7cc6786abe7d6aaa758e1fcb05900cb749235186c3bf9522d6d7ce.rekor.pub rename to test/assets/staging-tuf/targets/09ab08698a67354a95d3b8897d9ce7eaef05f06f5ed5f0202d79c228579858ecc5816b7e1b7cc6786abe7d6aaa758e1fcb05900cb749235186c3bf9522d6d7ce.rekor.pub diff --git a/test/unit/assets/staging-tuf/targets/0e6b0442485ad552bea5f62f11c29e2acfda35307d7538430b4cc1dbef49bff1.fulcio.crt.pem b/test/assets/staging-tuf/targets/0e6b0442485ad552bea5f62f11c29e2acfda35307d7538430b4cc1dbef49bff1.fulcio.crt.pem similarity index 100% rename from test/unit/assets/staging-tuf/targets/0e6b0442485ad552bea5f62f11c29e2acfda35307d7538430b4cc1dbef49bff1.fulcio.crt.pem rename to test/assets/staging-tuf/targets/0e6b0442485ad552bea5f62f11c29e2acfda35307d7538430b4cc1dbef49bff1.fulcio.crt.pem diff --git a/test/unit/assets/staging-tuf/targets/1d80b8f72505a43e65e6e125247cd508f61b459dc457c1d1bcb78d96e1760959.rekor.pub b/test/assets/staging-tuf/targets/1d80b8f72505a43e65e6e125247cd508f61b459dc457c1d1bcb78d96e1760959.rekor.pub similarity index 100% rename from test/unit/assets/staging-tuf/targets/1d80b8f72505a43e65e6e125247cd508f61b459dc457c1d1bcb78d96e1760959.rekor.pub rename to test/assets/staging-tuf/targets/1d80b8f72505a43e65e6e125247cd508f61b459dc457c1d1bcb78d96e1760959.rekor.pub diff --git a/test/unit/assets/staging-tuf/targets/3d035f94e1b14ac84627a28afdbed9a34861fb84239f76d73aa1a99f52262bfd95c4fa0ee71f1fd7e3bfb998d89cd5e0f0eafcff9fa7fa87c6e23484fc1e0cec.ctfe_2022_2.pub b/test/assets/staging-tuf/targets/3d035f94e1b14ac84627a28afdbed9a34861fb84239f76d73aa1a99f52262bfd95c4fa0ee71f1fd7e3bfb998d89cd5e0f0eafcff9fa7fa87c6e23484fc1e0cec.ctfe_2022_2.pub similarity index 100% rename from test/unit/assets/staging-tuf/targets/3d035f94e1b14ac84627a28afdbed9a34861fb84239f76d73aa1a99f52262bfd95c4fa0ee71f1fd7e3bfb998d89cd5e0f0eafcff9fa7fa87c6e23484fc1e0cec.ctfe_2022_2.pub rename to test/assets/staging-tuf/targets/3d035f94e1b14ac84627a28afdbed9a34861fb84239f76d73aa1a99f52262bfd95c4fa0ee71f1fd7e3bfb998d89cd5e0f0eafcff9fa7fa87c6e23484fc1e0cec.ctfe_2022_2.pub diff --git a/test/unit/assets/staging-tuf/targets/7054b4f15f969daca1c242bb9e77527abaf0b9acf9818a2a35144e4b32b20dc6.ctfe_2022_2.pub b/test/assets/staging-tuf/targets/7054b4f15f969daca1c242bb9e77527abaf0b9acf9818a2a35144e4b32b20dc6.ctfe_2022_2.pub similarity index 100% rename from test/unit/assets/staging-tuf/targets/7054b4f15f969daca1c242bb9e77527abaf0b9acf9818a2a35144e4b32b20dc6.ctfe_2022_2.pub rename to test/assets/staging-tuf/targets/7054b4f15f969daca1c242bb9e77527abaf0b9acf9818a2a35144e4b32b20dc6.ctfe_2022_2.pub diff --git a/test/unit/assets/staging-tuf/targets/782868913fe13c385105ddf33e827191386f58da40a931f2075a7e27b1b6ac7b.fulcio_intermediate.crt.pem b/test/assets/staging-tuf/targets/782868913fe13c385105ddf33e827191386f58da40a931f2075a7e27b1b6ac7b.fulcio_intermediate.crt.pem similarity index 100% rename from test/unit/assets/staging-tuf/targets/782868913fe13c385105ddf33e827191386f58da40a931f2075a7e27b1b6ac7b.fulcio_intermediate.crt.pem rename to test/assets/staging-tuf/targets/782868913fe13c385105ddf33e827191386f58da40a931f2075a7e27b1b6ac7b.fulcio_intermediate.crt.pem diff --git a/test/unit/assets/staging-tuf/targets/90659875a02f73d1026055427c6d857c556e410e23748ff88aeb493227610fd2f5fbdd95ef2a21565f91438dfb3e073f50c4c9dd06f9a601b5d9b064d5cb60b4.fulcio_intermediate.crt.pem b/test/assets/staging-tuf/targets/90659875a02f73d1026055427c6d857c556e410e23748ff88aeb493227610fd2f5fbdd95ef2a21565f91438dfb3e073f50c4c9dd06f9a601b5d9b064d5cb60b4.fulcio_intermediate.crt.pem similarity index 100% rename from test/unit/assets/staging-tuf/targets/90659875a02f73d1026055427c6d857c556e410e23748ff88aeb493227610fd2f5fbdd95ef2a21565f91438dfb3e073f50c4c9dd06f9a601b5d9b064d5cb60b4.fulcio_intermediate.crt.pem rename to test/assets/staging-tuf/targets/90659875a02f73d1026055427c6d857c556e410e23748ff88aeb493227610fd2f5fbdd95ef2a21565f91438dfb3e073f50c4c9dd06f9a601b5d9b064d5cb60b4.fulcio_intermediate.crt.pem diff --git a/test/unit/assets/staging-tuf/targets/910d899c7763563095a0fe684c8477573fedc19a78586de6ecfbfd8f289f5423.ctfe_2022.pub b/test/assets/staging-tuf/targets/910d899c7763563095a0fe684c8477573fedc19a78586de6ecfbfd8f289f5423.ctfe_2022.pub similarity index 100% rename from test/unit/assets/staging-tuf/targets/910d899c7763563095a0fe684c8477573fedc19a78586de6ecfbfd8f289f5423.ctfe_2022.pub rename to test/assets/staging-tuf/targets/910d899c7763563095a0fe684c8477573fedc19a78586de6ecfbfd8f289f5423.ctfe_2022.pub diff --git a/test/unit/assets/staging-tuf/targets/99f4f7728a889fa7db2fec893c387714a64aaf032fbe3035909fc8445effb857.trusted_root.json b/test/assets/staging-tuf/targets/99f4f7728a889fa7db2fec893c387714a64aaf032fbe3035909fc8445effb857.trusted_root.json similarity index 100% rename from test/unit/assets/staging-tuf/targets/99f4f7728a889fa7db2fec893c387714a64aaf032fbe3035909fc8445effb857.trusted_root.json rename to test/assets/staging-tuf/targets/99f4f7728a889fa7db2fec893c387714a64aaf032fbe3035909fc8445effb857.trusted_root.json diff --git a/test/unit/assets/staging-tuf/targets/ab975a75600fc366a837536d0dcba841b755552d21bb114498ff8ac9d2403f76643f5b91269bce5d124a365514719a3edee9dcc2b046cb173f51af659911fcd3.ctfe_2022.pub b/test/assets/staging-tuf/targets/ab975a75600fc366a837536d0dcba841b755552d21bb114498ff8ac9d2403f76643f5b91269bce5d124a365514719a3edee9dcc2b046cb173f51af659911fcd3.ctfe_2022.pub similarity index 100% rename from test/unit/assets/staging-tuf/targets/ab975a75600fc366a837536d0dcba841b755552d21bb114498ff8ac9d2403f76643f5b91269bce5d124a365514719a3edee9dcc2b046cb173f51af659911fcd3.ctfe_2022.pub rename to test/assets/staging-tuf/targets/ab975a75600fc366a837536d0dcba841b755552d21bb114498ff8ac9d2403f76643f5b91269bce5d124a365514719a3edee9dcc2b046cb173f51af659911fcd3.ctfe_2022.pub diff --git a/test/unit/assets/staging-tuf/targets/acf0438a71de70bbf1813c908545281e0c4a1e3aafa2ce36b82c1cc24a9cce5169e9dcfe85c31bb4f662e94fdd9a686fa54fbbfccff1888e51ebd73924c12495.trusted_root.json b/test/assets/staging-tuf/targets/acf0438a71de70bbf1813c908545281e0c4a1e3aafa2ce36b82c1cc24a9cce5169e9dcfe85c31bb4f662e94fdd9a686fa54fbbfccff1888e51ebd73924c12495.trusted_root.json similarity index 100% rename from test/unit/assets/staging-tuf/targets/acf0438a71de70bbf1813c908545281e0c4a1e3aafa2ce36b82c1cc24a9cce5169e9dcfe85c31bb4f662e94fdd9a686fa54fbbfccff1888e51ebd73924c12495.trusted_root.json rename to test/assets/staging-tuf/targets/acf0438a71de70bbf1813c908545281e0c4a1e3aafa2ce36b82c1cc24a9cce5169e9dcfe85c31bb4f662e94fdd9a686fa54fbbfccff1888e51ebd73924c12495.trusted_root.json diff --git a/test/unit/assets/staging-tuf/targets/b861189e48df51186a39612230fba6b02af951f7b35ad9375e8ca182d0e085d470e26d69f7cd4d7450a0f223991e8e5a4ddf8f1968caa15255de8e37035af43a.ctfe.pub b/test/assets/staging-tuf/targets/b861189e48df51186a39612230fba6b02af951f7b35ad9375e8ca182d0e085d470e26d69f7cd4d7450a0f223991e8e5a4ddf8f1968caa15255de8e37035af43a.ctfe.pub similarity index 100% rename from test/unit/assets/staging-tuf/targets/b861189e48df51186a39612230fba6b02af951f7b35ad9375e8ca182d0e085d470e26d69f7cd4d7450a0f223991e8e5a4ddf8f1968caa15255de8e37035af43a.ctfe.pub rename to test/assets/staging-tuf/targets/b861189e48df51186a39612230fba6b02af951f7b35ad9375e8ca182d0e085d470e26d69f7cd4d7450a0f223991e8e5a4ddf8f1968caa15255de8e37035af43a.ctfe.pub diff --git a/test/unit/assets/staging-tuf/targets/bd7a6812a1f239dfddbbb19d36c7423d21510da56d466ba5018401959cd66037.ctfe.pub b/test/assets/staging-tuf/targets/bd7a6812a1f239dfddbbb19d36c7423d21510da56d466ba5018401959cd66037.ctfe.pub similarity index 100% rename from test/unit/assets/staging-tuf/targets/bd7a6812a1f239dfddbbb19d36c7423d21510da56d466ba5018401959cd66037.ctfe.pub rename to test/assets/staging-tuf/targets/bd7a6812a1f239dfddbbb19d36c7423d21510da56d466ba5018401959cd66037.ctfe.pub diff --git a/test/unit/assets/staging-tuf/targets/c69ae618883a0c89c282c0943a1ad0c16b0a7788f74e47a1adefc631dac48a0c4449d8c3de7455ae7d772e43c4a87e341f180b0614a46a86006969f8a7b84532.fulcio.crt.pem b/test/assets/staging-tuf/targets/c69ae618883a0c89c282c0943a1ad0c16b0a7788f74e47a1adefc631dac48a0c4449d8c3de7455ae7d772e43c4a87e341f180b0614a46a86006969f8a7b84532.fulcio.crt.pem similarity index 100% rename from test/unit/assets/staging-tuf/targets/c69ae618883a0c89c282c0943a1ad0c16b0a7788f74e47a1adefc631dac48a0c4449d8c3de7455ae7d772e43c4a87e341f180b0614a46a86006969f8a7b84532.fulcio.crt.pem rename to test/assets/staging-tuf/targets/c69ae618883a0c89c282c0943a1ad0c16b0a7788f74e47a1adefc631dac48a0c4449d8c3de7455ae7d772e43c4a87e341f180b0614a46a86006969f8a7b84532.fulcio.crt.pem diff --git a/test/unit/assets/staging-tuf/targets/registry.npmjs.org/7a8ec9678ad824cdccaa7a6dc0961caf8f8df61bc7274189122c123446248426.keys.json b/test/assets/staging-tuf/targets/registry.npmjs.org/7a8ec9678ad824cdccaa7a6dc0961caf8f8df61bc7274189122c123446248426.keys.json similarity index 100% rename from test/unit/assets/staging-tuf/targets/registry.npmjs.org/7a8ec9678ad824cdccaa7a6dc0961caf8f8df61bc7274189122c123446248426.keys.json rename to test/assets/staging-tuf/targets/registry.npmjs.org/7a8ec9678ad824cdccaa7a6dc0961caf8f8df61bc7274189122c123446248426.keys.json diff --git a/test/unit/assets/staging-tuf/targets/registry.npmjs.org/881a853ee92d8cf513b07c164fea36b22a7305c256125bdfffdc5c65a4205c4c3fc2b5bcc98964349167ea68d40b8cd02551fcaa870a30d4601ba1caf6f63699.keys.json b/test/assets/staging-tuf/targets/registry.npmjs.org/881a853ee92d8cf513b07c164fea36b22a7305c256125bdfffdc5c65a4205c4c3fc2b5bcc98964349167ea68d40b8cd02551fcaa870a30d4601ba1caf6f63699.keys.json similarity index 100% rename from test/unit/assets/staging-tuf/targets/registry.npmjs.org/881a853ee92d8cf513b07c164fea36b22a7305c256125bdfffdc5c65a4205c4c3fc2b5bcc98964349167ea68d40b8cd02551fcaa870a30d4601ba1caf6f63699.keys.json rename to test/assets/staging-tuf/targets/registry.npmjs.org/881a853ee92d8cf513b07c164fea36b22a7305c256125bdfffdc5c65a4205c4c3fc2b5bcc98964349167ea68d40b8cd02551fcaa870a30d4601ba1caf6f63699.keys.json diff --git a/test/unit/assets/staging-tuf/timestamp.json b/test/assets/staging-tuf/timestamp.json similarity index 100% rename from test/unit/assets/staging-tuf/timestamp.json rename to test/assets/staging-tuf/timestamp.json diff --git a/test/unit/assets/trust_config/config.badtype.json b/test/assets/trust_config/config.badtype.json similarity index 100% rename from test/unit/assets/trust_config/config.badtype.json rename to test/assets/trust_config/config.badtype.json diff --git a/test/unit/assets/trust_config/config.v1.json b/test/assets/trust_config/config.v1.json similarity index 100% rename from test/unit/assets/trust_config/config.v1.json rename to test/assets/trust_config/config.v1.json diff --git a/test/unit/assets/trusted_root/trustedroot.badtype.json b/test/assets/trusted_root/trustedroot.badtype.json similarity index 100% rename from test/unit/assets/trusted_root/trustedroot.badtype.json rename to test/assets/trusted_root/trustedroot.badtype.json diff --git a/test/unit/assets/trusted_root/trustedroot.v1.json b/test/assets/trusted_root/trustedroot.v1.json similarity index 100% rename from test/unit/assets/trusted_root/trustedroot.v1.json rename to test/assets/trusted_root/trustedroot.v1.json diff --git a/test/unit/assets/x509/bogus-intermediate-with-eku.pem b/test/assets/x509/bogus-intermediate-with-eku.pem similarity index 100% rename from test/unit/assets/x509/bogus-intermediate-with-eku.pem rename to test/assets/x509/bogus-intermediate-with-eku.pem diff --git a/test/unit/assets/x509/bogus-intermediate.pem b/test/assets/x509/bogus-intermediate.pem similarity index 100% rename from test/unit/assets/x509/bogus-intermediate.pem rename to test/assets/x509/bogus-intermediate.pem diff --git a/test/unit/assets/x509/bogus-leaf-invalid-eku.pem b/test/assets/x509/bogus-leaf-invalid-eku.pem similarity index 100% rename from test/unit/assets/x509/bogus-leaf-invalid-eku.pem rename to test/assets/x509/bogus-leaf-invalid-eku.pem diff --git a/test/unit/assets/x509/bogus-leaf-invalid-ku.pem b/test/assets/x509/bogus-leaf-invalid-ku.pem similarity index 100% rename from test/unit/assets/x509/bogus-leaf-invalid-ku.pem rename to test/assets/x509/bogus-leaf-invalid-ku.pem diff --git a/test/unit/assets/x509/bogus-leaf-missing-eku.pem b/test/assets/x509/bogus-leaf-missing-eku.pem similarity index 100% rename from test/unit/assets/x509/bogus-leaf-missing-eku.pem rename to test/assets/x509/bogus-leaf-missing-eku.pem diff --git a/test/unit/assets/x509/bogus-leaf.pem b/test/assets/x509/bogus-leaf.pem similarity index 100% rename from test/unit/assets/x509/bogus-leaf.pem rename to test/assets/x509/bogus-leaf.pem diff --git a/test/unit/assets/x509/bogus-root-invalid-ku.pem b/test/assets/x509/bogus-root-invalid-ku.pem similarity index 100% rename from test/unit/assets/x509/bogus-root-invalid-ku.pem rename to test/assets/x509/bogus-root-invalid-ku.pem diff --git a/test/unit/assets/x509/bogus-root-missing-ku.pem b/test/assets/x509/bogus-root-missing-ku.pem similarity index 100% rename from test/unit/assets/x509/bogus-root-missing-ku.pem rename to test/assets/x509/bogus-root-missing-ku.pem diff --git a/test/unit/assets/x509/bogus-root-noncritical-bc.pem b/test/assets/x509/bogus-root-noncritical-bc.pem similarity index 100% rename from test/unit/assets/x509/bogus-root-noncritical-bc.pem rename to test/assets/x509/bogus-root-noncritical-bc.pem diff --git a/test/unit/assets/x509/bogus-root.pem b/test/assets/x509/bogus-root.pem similarity index 100% rename from test/unit/assets/x509/bogus-root.pem rename to test/assets/x509/bogus-root.pem diff --git a/test/unit/assets/x509/build-testcases.py b/test/assets/x509/build-testcases.py similarity index 100% rename from test/unit/assets/x509/build-testcases.py rename to test/assets/x509/build-testcases.py diff --git a/test/unit/assets/x509/nonroot-privkey.pem b/test/assets/x509/nonroot-privkey.pem similarity index 100% rename from test/unit/assets/x509/nonroot-privkey.pem rename to test/assets/x509/nonroot-privkey.pem diff --git a/test/unit/assets/x509/root-privkey.pem b/test/assets/x509/root-privkey.pem similarity index 100% rename from test/unit/assets/x509/root-privkey.pem rename to test/assets/x509/root-privkey.pem diff --git a/test/integration/cli/__init__.py b/test/integration/cli/__init__.py new file mode 100644 index 000000000..88cb71fa9 --- /dev/null +++ b/test/integration/cli/__init__.py @@ -0,0 +1,13 @@ +# Copyright 2022 The Sigstore Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. diff --git a/test/integration/cli/conftest.py b/test/integration/cli/conftest.py new file mode 100644 index 000000000..1e689d09a --- /dev/null +++ b/test/integration/cli/conftest.py @@ -0,0 +1,39 @@ +# Copyright 2022 The Sigstore Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from pathlib import Path +from typing import Callable + +import pytest + +from sigstore._cli import main + +_ASSETS = (Path(__file__).parent.parent.parent / "assets/integration").resolve() +assert _ASSETS.is_dir() + + +@pytest.fixture +def asset(): + def _asset(name: str) -> Path: + return _ASSETS / name + + return _asset + + +@pytest.fixture(scope="function") +def sigstore() -> Callable: + def _sigstore(*args: str): + main(list(args)) + + return _sigstore diff --git a/test/integration/cli/test_plumbing.py b/test/integration/cli/test_plumbing.py new file mode 100644 index 000000000..ebcf8e7fc --- /dev/null +++ b/test/integration/cli/test_plumbing.py @@ -0,0 +1,103 @@ +# Copyright 2022 The Sigstore Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +import pytest +from sigstore_protobuf_specs.dev.sigstore.common.v1 import HashAlgorithm + +from sigstore.hashes import Hashed +from sigstore.models import Bundle, InvalidBundle +from sigstore.verify import policy +from sigstore.verify.verifier import Verifier + + +def test_fix_bundle_fixes_missing_checkpoint(capsys, sigstore, asset): + invalid_bundle = asset("Python-3.12.5.tgz.sigstore") + + # The bundle is invalid, because it's missing a checkpoint + # for its inclusion proof. + with pytest.raises( + InvalidBundle, match="entry must contain inclusion proof, with checkpoint" + ): + Bundle.from_json(invalid_bundle.read_text()) + + # Running `sigstore plumbing fix-bundle` emits a fixed bundle. + sigstore("plumbing", "fix-bundle", "--bundle", str(invalid_bundle)) + + captures = capsys.readouterr() + + # The bundle now loads correctly. + bundle = Bundle.from_json(captures.out) + + # We didn't pass `--upgrade-version` so the version is still v0.1. + assert bundle._inner.media_type == Bundle.BundleType.BUNDLE_0_1 + + # ...and the fixed bundle can now be used to verify the `Python-3.12.5.tgz` + # release. + verifier = Verifier.production() + verifier.verify_artifact( + Hashed( + algorithm=HashAlgorithm.SHA2_256, + digest=bytes.fromhex( + "38dc4e2c261d49c661196066edbfb70fdb16be4a79cc8220c224dfeb5636d405" + ), + ), + bundle, + policy.AllOf( + [ + policy.Identity( + identity="thomas@python.org", issuer="https://accounts.google.com" + ) + ] + ), + ) + + +def test_fix_bundle_upgrades_bundle(capsys, sigstore, asset): + invalid_bundle = asset("Python-3.12.5.tgz.sigstore") + + # Running `sigstore plumbing fix-bundle --upgrade-version` + # emits a fixed bundle. + sigstore( + "plumbing", "fix-bundle", "--upgrade-version", "--bundle", str(invalid_bundle) + ) + + captures = capsys.readouterr() + + # The bundle now loads correctly. + bundle = Bundle.from_json(captures.out) + + # The bundle is now the latest version (v0.3). + assert bundle._inner.media_type == Bundle.BundleType.BUNDLE_0_3 + + # ...and the upgraded (and fixed) bundle can still verify + # the release. + # ...and the fixed can now be used to verify the `Python-3.12.5.tgz` release. + verifier = Verifier.production() + verifier.verify_artifact( + Hashed( + algorithm=HashAlgorithm.SHA2_256, + digest=bytes.fromhex( + "38dc4e2c261d49c661196066edbfb70fdb16be4a79cc8220c224dfeb5636d405" + ), + ), + bundle, + policy.AllOf( + [ + policy.Identity( + identity="thomas@python.org", issuer="https://accounts.google.com" + ) + ] + ), + ) diff --git a/test/unit/conftest.py b/test/unit/conftest.py index c112e8768..2c55bf6a7 100644 --- a/test/unit/conftest.py +++ b/test/unit/conftest.py @@ -44,7 +44,7 @@ from sigstore.sign import SigningContext from sigstore.verify.verifier import Verifier -_ASSETS = (Path(__file__).parent / "assets").resolve() +_ASSETS = (Path(__file__).parent.parent / "assets").resolve() assert _ASSETS.is_dir() _TUF_ASSETS = (_ASSETS / "staging-tuf").resolve() diff --git a/test/unit/test_models.py b/test/unit/test_models.py index 93b0d0f12..40d82c7bb 100644 --- a/test/unit/test_models.py +++ b/test/unit/test_models.py @@ -38,7 +38,7 @@ def test_logentry_roundtrip(self, signing_bundle): _, bundle = signing_bundle("bundle.txt") assert ( - LogEntry._from_dict_rekor(bundle.log_entry._to_dict_rekor()) + LogEntry._from_dict_rekor(bundle.log_entry._to_rekor().to_dict()) == bundle.log_entry ) @@ -111,7 +111,7 @@ def test_invalid_no_log_entry(self, signing_bundle): def test_verification_materials_offline_no_checkpoint(self, signing_bundle): with pytest.raises( - InvalidBundle, match="expected checkpoint in inclusion proof" + InvalidBundle, match="entry must contain inclusion proof, with checkpoint" ): signing_bundle("bundle_no_checkpoint.txt") @@ -123,3 +123,10 @@ def test_bundle_roundtrip(self, signing_bundle): assert json.loads(Bundle.from_json(bundle.to_json()).to_json()) == json.loads( bundle.to_json() ) + + +class TestKnownBundleTypes: + def test_str(self): + for type_ in Bundle.BundleType: + assert str(type_) == type_.value + assert type_ in Bundle.BundleType diff --git a/test/unit/test_utils.py b/test/unit/test_utils.py index fffa8d8d9..c5cd862c6 100644 --- a/test/unit/test_utils.py +++ b/test/unit/test_utils.py @@ -182,10 +182,3 @@ def test_cert_is_leaf_invalid_version(helper): with pytest.raises(VerificationError, match="invalid X.509 version"): helper(cert) - - -class TestKnownBundleTypes: - def test_str(self): - for type_ in utils.BundleType: - assert str(type_) == type_.value - assert type_ in utils.BundleType