diff --git a/conan/api/subapi/cache.py b/conan/api/subapi/cache.py index 862530b324a..581f2c0d647 100644 --- a/conan/api/subapi/cache.py +++ b/conan/api/subapi/cache.py @@ -16,6 +16,7 @@ from conan.errors import ConanException from conan.api.model import PkgReference from conan.api.model import RecipeReference +from conan.internal.rest.pkg_sign import PkgSignaturesPlugin from conan.internal.util.dates import revision_timestamp_now from conan.internal.util.files import rmdir, mkdir, remove, save @@ -76,6 +77,20 @@ def check_integrity(self, package_list): checker = IntegrityChecker(cache) checker.check(package_list) + def sign(self, package_list): + """Sign packages with the signing plugin""" + cache = PkgCache(self._conan_api.cache_folder, self._api_helpers.global_conf) + pkg_signer = PkgSignaturesPlugin(cache, self._conan_api.home_folder) + results = pkg_signer.sign(package_list, context="cache") + return {"results": results, "context": "cache", "action": "sign"} + + def verify(self, package_list): + """Verify packages with the signing plugin""" + cache = PkgCache(self._conan_api.cache_folder, self._api_helpers.global_conf) + pkg_signer = PkgSignaturesPlugin(cache, self._conan_api.home_folder) + results = pkg_signer.verify_pkglist(package_list, context="cache") + return {"results": results, "context": "cache", "action": "verify"} + def clean(self, package_list, source=True, build=True, download=True, temp=True, backup_sources=False): """ diff --git a/conan/cli/commands/cache.py b/conan/cli/commands/cache.py index 975ff02b3c3..10ffd4517bc 100644 --- a/conan/cli/commands/cache.py +++ b/conan/cli/commands/cache.py @@ -2,7 +2,7 @@ from conan.api.conan_api import ConanAPI from conan.api.model import ListPattern, MultiPackagesList -from conan.api.output import cli_out_write, ConanOutput +from conan.api.output import cli_out_write, ConanOutput, Color from conan.cli import make_abs_path from conan.cli.command import conan_command, conan_subcommand, OnceArgument from conan.cli.commands.list import print_list_text, print_list_json @@ -15,6 +15,25 @@ def json_export(data): cli_out_write(json.dumps({"cache_path": data})) +def print_cache_sign_verify_text(data): + elements = data.get("results") + if elements: + title = "Verification" if data.get("action") == "verify" else "Signing" + cli_out_write(f"[Package signing plugin] {title} results:", fg=Color.BRIGHT_BLUE) + for ref, result in elements.items(): + cli_out_write(f"- {ref}", fg=Color.BRIGHT_BLUE) + if result is None: + result = "Signed" if data.get("action") == "sign" else "Signature verified" + color = Color.BRIGHT_YELLOW if "warn" in result else Color.BRIGHT_WHITE + color = Color.BRIGHT_RED if "fail" in result else color + cli_out_write(f" {result}", fg=color) + + +def print_cache_sign_verify_json(data): + myjson = json.dumps(data, indent=4) + cli_out_write(myjson) + + @conan_command(group="Consumer") def cache(conan_api: ConanAPI, parser, *args): """ @@ -150,6 +169,66 @@ def cache_check_integrity(conan_api: ConanAPI, parser, subparser, *args): ConanOutput().success("Integrity check: ok") +@conan_subcommand(formatters={"text": print_cache_sign_verify_text, + "json": print_cache_sign_verify_json}) +def cache_sign(conan_api: ConanAPI, parser, subparser, *args): + """ + Sign packages with the Package Singing Plugin + """ + subparser.add_argument("pattern", nargs="?", + help="Selection pattern for references to check integrity for") + subparser.add_argument("-l", "--list", action=OnceArgument, + help="Package list of packages to check integrity for") + subparser.add_argument('-p', '--package-query', action=OnceArgument, + help="Only the packages matching a specific query, e.g., " + "os=Windows AND (arch=x86 OR compiler=gcc)") + args = parser.parse_args(*args) + + if args.pattern is None and args.list is None: + raise ConanException("Missing pattern or package list file") + if args.pattern and args.list: + raise ConanException("Cannot specify both pattern and list") + + if args.list: + listfile = make_abs_path(args.list) + multi_package_list = MultiPackagesList.load(listfile) + package_list = multi_package_list["Local Cache"] + else: + ref_pattern = ListPattern(args.pattern, rrev="*", package_id="*", prev="*") + package_list = conan_api.list.select(ref_pattern, package_query=args.package_query) + return conan_api.cache.sign(package_list) + + +@conan_subcommand(formatters={"text": print_cache_sign_verify_text, + "json": print_cache_sign_verify_json}) +def cache_verify(conan_api: ConanAPI, parser, subparser, *args): + """ + Check the signature of packages with the Package Singing Plugin + """ + subparser.add_argument("pattern", nargs="?", + help="Selection pattern for references to check integrity for") + subparser.add_argument("-l", "--list", action=OnceArgument, + help="Package list of packages to check integrity for") + subparser.add_argument('-p', '--package-query', action=OnceArgument, + help="Only the packages matching a specific query, e.g., " + "os=Windows AND (arch=x86 OR compiler=gcc)") + args = parser.parse_args(*args) + + if args.pattern is None and args.list is None: + raise ConanException("Missing pattern or package list file") + if args.pattern and args.list: + raise ConanException("Cannot specify both pattern and list") + + if args.list: + listfile = make_abs_path(args.list) + multi_package_list = MultiPackagesList.load(listfile) + package_list = multi_package_list["Local Cache"] + else: + ref_pattern = ListPattern(args.pattern, rrev="*", package_id="*", prev="*") + package_list = conan_api.list.select(ref_pattern, package_query=args.package_query) + return conan_api.cache.verify(package_list) + + @conan_subcommand(formatters={"text": print_list_text, "json": print_list_json}) def cache_save(conan_api: ConanAPI, parser, subparser, *args): diff --git a/conan/internal/rest/pkg_sign.py b/conan/internal/rest/pkg_sign.py index baa8e8c72cc..319ede28a44 100644 --- a/conan/internal/rest/pkg_sign.py +++ b/conan/internal/rest/pkg_sign.py @@ -1,44 +1,176 @@ +import copy +import json import os +from conan.api.output import ConanOutput +from conan.errors import ConanException from conan.internal.cache.conan_reference_layout import METADATA from conan.internal.cache.home_paths import HomePaths from conan.internal.loader import load_python_file -from conan.internal.util.files import mkdir +from conan.internal.util.files import load, mkdir, save, sha256sum + + +class PkgSignaturesTools: + + SIGN_SUMMARY_CONTENT = { + "provider": None, + "method": None, + "files": {} + } + SIGN_SUMMARY_FILENAME = "sign-summary.json" + + def __init__(self, artifacts_folder, signature_folder): + self._artifacts_folder = artifacts_folder + self._signature_folder = signature_folder + + def get_summary_file_path(self): + return os.path.join(self._signature_folder, self.SIGN_SUMMARY_FILENAME) + + def is_pkg_signed(self): + try: + c = self.load_summary() + except FileNotFoundError: + return False + return bool(c.get("provider") and c.get("method")) + + def create_summary_content(self): + """ + Creates the summary content as a dictionary for manipulation + @return: Dictionary with the summary content + """ + checksums = {} + for fname in os.listdir(self._artifacts_folder): + file_path = os.path.join(self._artifacts_folder, fname) + if os.path.isfile(file_path): + sha256 = sha256sum(file_path) + checksums[fname] = sha256 + sorted_checksums = dict(sorted(checksums.items())) + content = copy.deepcopy(self.SIGN_SUMMARY_CONTENT) + content["files"] = sorted_checksums + return content + + def load_summary(self): + """" + Loads the summary file from the signature folder + """ + return json.loads(load(self.get_summary_file_path())) + + def save_summary(self, content): + """ + Saves the content of the summary to the signature folder using SIGN_SUMMARY_FILENAME as the + file name + @param content: Content of the summary file + @return: + """ + assert content.get("provider") + assert content.get("method") + save(self.get_summary_file_path(), json.dumps(content)) class PkgSignaturesPlugin: def __init__(self, cache, home_folder): self._cache = cache - signer = HomePaths(home_folder).sign_plugin_path - if os.path.isfile(signer): - mod, _ = load_python_file(signer) - # TODO: At the moment it requires both methods sign and verify, but that might be relaxed - self._plugin_sign_function = mod.sign - self._plugin_verify_function = mod.verify - else: - self._plugin_sign_function = self._plugin_verify_function = None + self.sign_plugin_path = HomePaths(home_folder).sign_plugin_path + self._plugin_sign_function = self._plugin_verify_function = None + if os.path.isfile(self.sign_plugin_path): + mod, _ = load_python_file(self.sign_plugin_path) + try: + self._plugin_sign_function = mod.sign + except AttributeError: + pass + try: + self._plugin_verify_function = mod.verify + except AttributeError: + pass - def sign(self, upload_data): + def sign(self, upload_data, context="upload"): # cache, upload, + results = {} if self._plugin_sign_function is None: - return + ConanOutput().error(f"[Package signing plugin] sign() function not found in " + f"{self.sign_plugin_path}") + return results - def _sign(ref, files, folder): + def _sign(ref, files, folder, context="upload"): + output = ConanOutput(scope=f"{ref.repr_notime()}") metadata_sign = os.path.join(folder, METADATA, "sign") mkdir(metadata_sign) - self._plugin_sign_function(ref, artifacts_folder=folder, signature_folder=metadata_sign) + sign_tools = PkgSignaturesTools(folder, metadata_sign) + try: + result = self._plugin_sign_function(ref, artifacts_folder=folder, + signature_folder=metadata_sign, output=output, + sign_tools=sign_tools) + except (ConanException, AssertionError) as e: + result = _handle_failure(e, context, ref) + # Add files to the pkglist/bundle for f in os.listdir(metadata_sign): files[f"{METADATA}/sign/{f}"] = os.path.join(metadata_sign, f) + return {ref.repr_notime(): result} - for rref, recipe_bundle in upload_data.refs().items(): - if recipe_bundle["upload"]: - _sign(rref, recipe_bundle["files"], self._cache.recipe_layout(rref).download_export()) - for pref, pkg_bundle in upload_data.prefs(rref, recipe_bundle).items(): - if pkg_bundle["upload"]: - _sign(pref, pkg_bundle["files"], self._cache.pkg_layout(pref).download_package()) + if context == "upload": + for rref, recipe_bundle in upload_data.refs().items(): + if recipe_bundle["upload"]: + result = _sign(rref, recipe_bundle["files"], + self._cache.recipe_layout(rref).download_export()) + results.update(result) + for pref, pkg_bundle in upload_data.prefs(rref, recipe_bundle).items(): + if pkg_bundle["upload"]: + result = _sign(pref, pkg_bundle["files"], + self._cache.pkg_layout(pref).download_package()) + results.update(result) + else: + for rref, recipe_bundle in upload_data.refs().items(): + if recipe_bundle: + result = _sign(rref, {}, self._cache.recipe_layout(rref).download_export(), + context) + results.update(result) + for pref, pkg_bundle in upload_data.prefs(rref, recipe_bundle).items(): + if pkg_bundle: + result = _sign(pref, {}, self._cache.pkg_layout(pref).download_package(), + context) + results.update(result) + return results - def verify(self, ref, folder, files): + def verify(self, ref, folder, files, context="install"): if self._plugin_verify_function is None: - return + ConanOutput().error(f"[Package signing plugin] verify() function not found in " + f"{self.sign_plugin_path}") + return {} + output = ConanOutput(scope=f"{ref.repr_notime()}") metadata_sign = os.path.join(folder, METADATA, "sign") - self._plugin_verify_function(ref, artifacts_folder=folder, signature_folder=metadata_sign, - files=files) + sign_tools = PkgSignaturesTools(folder, metadata_sign) + try: + result = self._plugin_verify_function(ref, artifacts_folder=folder, + signature_folder=metadata_sign, files=files, + output=output, sign_tools=sign_tools) + except (ConanException, AssertionError) as e: + result = _handle_failure(e, context, ref) + return {ref.repr_notime(): result} + + def verify_pkglist(self, pkg_list, context="cache"): # cache, install, upload + results = {} + if self._plugin_verify_function is None: + ConanOutput().error(f"[Package signing plugin] verify() function not found in " + f"{self.sign_plugin_path}") + return results + + for rref, recipe_bundle in pkg_list.refs().items(): + if recipe_bundle: + rref_folder = self._cache.recipe_layout(rref).download_export() + result = self.verify(rref, rref_folder, os.listdir(rref_folder), context) + results.update(result) + for pref, pkg_bundle in pkg_list.prefs(rref, recipe_bundle).items(): + if pkg_bundle: + pref_folder = self._cache.pkg_layout(pref).download_package() + result = self.verify(pref, pref_folder, os.listdir(pref_folder), context) + results.update(result) + return results + + +def _handle_failure(exception, action, ref): + exception_msg = str(exception) + if action in ["upload", "install"]: + # TODO: Mark folder with set_dirty(artifacts_folder) + raise ConanException(f"{ref.repr_notime()}: {exception_msg}") + else: + error_msg = f"Failed: {exception_msg}" if exception_msg else "Failed" + return error_msg diff --git a/test/integration/command/cache/test_cache_sign.py b/test/integration/command/cache/test_cache_sign.py new file mode 100644 index 00000000000..dea08cfeedb --- /dev/null +++ b/test/integration/command/cache/test_cache_sign.py @@ -0,0 +1,115 @@ +import json +import os +import textwrap + +import pytest + +from conan.test.assets.genconanfile import GenConanfile +from conan.test.utils.tools import TestClient + + +def test_pkg_sign_no_plugin(): + c = TestClient() + c.save({"conanfile.py": GenConanfile("pkg", "0.1")}) + c.run("create .") + c.run("cache sign *") + assert "ERROR: [Package signing plugin] sign() function not found" in c.out + c.run("cache verify *") + assert "ERROR: [Package signing plugin] verify() function not found" in c.out + + +def test_pkg_sign_basic(): + c = TestClient() + c.save({"conanfile.py": GenConanfile("pkg", "0.1")}) + signer = textwrap.dedent(r""" + def sign(ref, artifacts_folder, signature_folder, output, sign_tools): + output.info(f"Signing package {ref.repr_notime()}") + """) + c.save_home({"extensions/plugins/sign/sign.py": signer}) + c.run("create .") + c.run("cache sign *") + assert "Signing package pkg/0.1#485dad6cb11e2fa99d9afbe44a57a164" in c.out + assert "Signing package pkg/0.1#485dad6cb11e2fa99d9afbe44a57a164" \ + ":da39a3ee5e6b4b0d3255bfef95601890afd80709#0ba8627bd47edc3a501e8f0eb9a79e5e" in c.out + + +def test_pkg_verify_basic(): + c = TestClient() + c.save({"conanfile.py": GenConanfile("pkg", "0.1")}) + signer = textwrap.dedent(r""" + def verify(ref, artifacts_folder, signature_folder, files, output, sign_tools): + output.info(f"Verifying package {ref.repr_notime()}") + return "success" + """) + c.save_home({"extensions/plugins/sign/sign.py": signer}) + c.run("create .") + c.run("cache verify *") + assert "Verifying package pkg/0.1#485dad6cb11e2fa99d9afbe44a57a164" in c.out + assert "Verifying package pkg/0.1#485dad6cb11e2fa99d9afbe44a57a164" \ + ":da39a3ee5e6b4b0d3255bfef95601890afd80709#0ba8627bd47edc3a501e8f0eb9a79e5e" in c.out + + +def test_pkg_sign_exception(): + c = TestClient() + signer = textwrap.dedent(r""" + from conan.errors import ConanException + + def sign(ref, artifacts_folder, signature_folder, output, sign_tools): + output.info(f"Signing package {ref.repr_notime()}") + if "lib" in ref.repr_notime(): + raise ConanException("error signing package") + return "success" + """) + c.save_home({"extensions/plugins/sign/sign.py": signer}) + c.save({"conanfile.py": GenConanfile("pkg", "0.1")}) + c.run("create .") + c.save({"conanfile.py": GenConanfile("lib", "0.1")}) + c.run("create .") + c.run("cache sign *") + assert "Signing package lib/0.1#dbe307e08b1a344fef76f60c85c0c4e8" in c.out + assert "Signing package pkg/0.1#485dad6cb11e2fa99d9afbe44a57a164" in c.out + assert "[Package signing plugin] Signing results:" in c.out + assert "- lib/0.1#dbe307e08b1a344fef76f60c85c0c4e8\n" \ + " Failed: error signing package" in c.out + assert "- pkg/0.1#485dad6cb11e2fa99d9afbe44a57a164\n" \ + " success" in c.out + # test json output + c.run("cache sign * -f json") + data = json.loads(c.stdout) + assert data["action"] == "sign" + assert data["results"]["lib/0.1#dbe307e08b1a344fef76f60c85c0c4e8"] == \ + "Failed: error signing package" + assert data["results"]["pkg/0.1#485dad6cb11e2fa99d9afbe44a57a164"] == "success" + + +def test_pkg_verify_exception(): + c = TestClient() + signer = textwrap.dedent(r""" + from conan.errors import ConanException + + def verify(ref, artifacts_folder, signature_folder, files, output, sign_tools): + output.info(f"Verifying package {ref.repr_notime()}") + if "lib" in ref.repr_notime(): + raise ConanException("bad signature verification") + return "success" + """) + c.save_home({"extensions/plugins/sign/sign.py": signer}) + c.save({"conanfile.py": GenConanfile("pkg", "0.1")}) + c.run("create .") + c.save({"conanfile.py": GenConanfile("lib", "0.1")}) + c.run("create .") + c.run("cache verify *") + assert "Verifying package lib/0.1#dbe307e08b1a344fef76f60c85c0c4e8" in c.out + assert "Verifying package pkg/0.1#485dad6cb11e2fa99d9afbe44a57a164" in c.out + assert "[Package signing plugin] Verification results:" in c.out + assert "- lib/0.1#dbe307e08b1a344fef76f60c85c0c4e8\n" \ + " Failed: bad signature verification" in c.out + assert "- pkg/0.1#485dad6cb11e2fa99d9afbe44a57a164\n" \ + " success" in c.out + # test json output + c.run("cache verify * -f json") + data = json.loads(c.stdout) + assert data["action"] == "verify" + assert data["results"]["lib/0.1#dbe307e08b1a344fef76f60c85c0c4e8"] == \ + "Failed: bad signature verification" + assert data["results"]["pkg/0.1#485dad6cb11e2fa99d9afbe44a57a164"] == "success" diff --git a/test/integration/test_pkg_signing.py b/test/integration/test_pkg_signing.py index d29d21ef80d..c0841d8931e 100644 --- a/test/integration/test_pkg_signing.py +++ b/test/integration/test_pkg_signing.py @@ -13,7 +13,7 @@ def test_pkg_sign(): signer = textwrap.dedent(r""" import os - def sign(ref, artifacts_folder, signature_folder): + def sign(ref, artifacts_folder, signature_folder, **kwargs): print("Signing ref: ", ref) print("Signing folder: ", artifacts_folder) files = [] @@ -24,7 +24,7 @@ def sign(ref, artifacts_folder, signature_folder): signature = os.path.join(signature_folder, "signature.asc") open(signature, "w").write("\n".join(files)) - def verify(ref, artifacts_folder, signature_folder, files): + def verify(ref, artifacts_folder, signature_folder, files, **kwargs): print("Verifying ref: ", ref) print("Verifying folder: ", artifacts_folder) signature = os.path.join(signature_folder, "signature.asc") @@ -54,3 +54,91 @@ def verify(ref, artifacts_folder, signature_folder, files): assert "Verifying ref: pkg/0.1" in c.out assert "VERIFYING conanfile.py" not in c.out # It doesn't re-verify previous contents assert "VERIFYING conan_sources.tgz" in c.out + + +def test_pkg_sign_canonical(): + c = TestClient(default_server_user=True) + c.save({"conanfile1.py": GenConanfile("lib1ok", "0.1"), + "conanfile2.py": GenConanfile("lib2fail", "0.1"), # This pkg fails when installed + "conanfile3.py": GenConanfile("lib3fail", "0.1")}) # This pkg should always fail + c.run("create conanfile1.py") + c.run("create conanfile2.py") + c.run("create conanfile3.py") + signer = textwrap.dedent(r""" + import os + from conan.errors import ConanException + + def sign(ref, artifacts_folder, signature_folder, output, sign_tools): + output.info("Signing reference") + output.info(f"Signing folder: {artifacts_folder}") + if sign_tools.is_pkg_signed(): + summary = sign_tools.load_summary() + if summary.get("provider") != "conan-client": + output.warning("Package already signed by another provider") + return "Warn: Package already signed by another provider" + output.info("Package already signed by the same provider") + return "Package already signed by the same provider" + + c = sign_tools.create_summary_content() + c["method"] = "sigstore" + + if "lib3fail" in str(ref): + raise ConanException("sign failed") + elif "lib2fail" in str(ref): + c["provider"] = "this will fail to verify" + else: + c["provider"] = "conan-client" + sign_tools.save_summary(c) + output.info("Signature ok") + + def verify(ref, artifacts_folder, signature_folder, files, output, sign_tools): + output.info("Verifying reference") + if not sign_tools.is_pkg_signed(): + raise ConanException("Package is not signed") + + if "lib3fail" in str(ref): + raise ConanException("verify failed") + summary = sign_tools.load_summary() + assert summary.get("provider") == "conan-client", "wrong provider" + output.info("Verification ok") + """) + c.save_home({"extensions/plugins/sign/sign.py": signer}) + + # Cache verify command does not fail if package is not signed + c.run("cache verify *") + assert "lib1ok/0.1#a5e2af5522a1edcab963447eec649700\n Failed: Package is not signed" in c.out + assert "lib2fail/0.1#70a185be5a95af3dde25b74ae800b2f2\n Failed: Package is not signed" in c.out + assert "lib3fail/0.1#09ccc766ddd11c96aa78307b3f166fd6\n Failed: Package is not signed" in c.out + + # Cache sign command does not fail if a package fails to sign, but it reports it + c.run("cache sign *") + assert "lib1ok/0.1#a5e2af5522a1edcab963447eec649700\n Signed" in c.out + assert "lib2fail/0.1#70a185be5a95af3dde25b74ae800b2f2\n Signed" in c.out + assert "lib3fail/0.1#09ccc766ddd11c96aa78307b3f166fd6\n Failed: sign failed" in c.out + + # Upload sign fails if package signing fails + c.run("upload * -c -r default", assert_error=True) + assert "lib1ok/0.1#a5e2af5522a1edcab963447eec649700: Package already signed by the same provider" in c.out + assert "lib2fail/0.1#70a185be5a95af3dde25b74ae800b2f2: WARN: Package already signed by another provider" in c.out + assert "ERROR: lib3fail/0.1#09ccc766ddd11c96aa78307b3f166fd6: sign failed" in c.out + + # If upload sign failed, no packages should be uploaded + c.run("list * -r default") + assert "WARN: There are no matching recipe references" in c.out + + # Upload packages individually to avoid previous failure + c.run("upload lib1ok* -c -r default") + c.run("upload lib2fail* -c -r default") + c.run("remove * -c") + + # Install verify command should fail if package is signed by another provider + c.run("install --requires lib1ok/0.1 --requires lib2fail/0.1 -r default", assert_error=True) + assert "lib1ok/0.1#a5e2af5522a1edcab963447eec649700: Verification ok" in c.out + assert "lib2fail/0.1#70a185be5a95af3dde25b74ae800b2f2: wrong provider" in c.out + + # Packages that failed in install verification should not appear as installed + c.run("list *") + assert "lib1ok" in c.out + assert "lib2fail" not in c.out + c.run("cache verify *") + assert "lib1ok/0.1#a5e2af5522a1edcab963447eec649700\n Signature verified" in c.out diff --git a/test/unittests/tools/files/test_sign_tools.py b/test/unittests/tools/files/test_sign_tools.py new file mode 100644 index 00000000000..311ba3897b5 --- /dev/null +++ b/test/unittests/tools/files/test_sign_tools.py @@ -0,0 +1,51 @@ +import os + +import pytest + +from conan.internal.rest.pkg_sign import PkgSignaturesTools +from conan.test.utils.tools import temp_folder, save_files + + +@pytest.fixture +def pkg_sign_tools(): + main_folder = temp_folder() + artifacts_folder = os.path.join(main_folder, "af") + os.mkdir(artifacts_folder) + signature_folder = os.path.join(main_folder, "sf") + os.mkdir(signature_folder) + save_files(artifacts_folder, {"conan_package.tgz": "", "conanmanifest.txt": ""}) + return PkgSignaturesTools(artifacts_folder, signature_folder) + + +def test_get_summary_file_path(pkg_sign_tools): + sfp = pkg_sign_tools.get_summary_file_path() + assert f"sf{os.path.sep}sign-summary.json" in sfp + + +def test_create_summary_content(pkg_sign_tools): + c = pkg_sign_tools.create_summary_content() + assert c.get("method") is None + assert c.get("provider") is None + assert c.get("files").get("conan_package.tgz") + assert c.get("files").get("conanmanifest.txt") + + +def test_save_load_summary(pkg_sign_tools): + c = pkg_sign_tools.create_summary_content() + c["provider"] = "conan" + c["method"] = "sigstore" + pkg_sign_tools.save_summary(c) + assert os.path.exists(os.path.join(pkg_sign_tools._signature_folder, "sign-summary.json")) + summary = pkg_sign_tools.load_summary() + assert summary.get("provider") == "conan" + assert summary.get("method") == "sigstore" + assert list(summary.get("files").keys()) == ["conan_package.tgz", "conanmanifest.txt"] + + +def test_is_pkg_signed(pkg_sign_tools): + assert not pkg_sign_tools.is_pkg_signed() + c = pkg_sign_tools.create_summary_content() + c["provider"] = "the provider" + c["method"] = "the method" + pkg_sign_tools.save_summary(c) + assert pkg_sign_tools.is_pkg_signed()