Skip to content

Conversation

@danimtb
Copy link
Member

@danimtb danimtb commented Aug 15, 2025

Changelog: Feature: Improve package signing plugin integration.
Changelog: Feature: Add conan cache sign and conan cache verify commands for package signing.
Changelog Feature: Standardize package summary format with files, checksums, signing method and signing provider.
Docs: missing

  • Added new commands for sign and verify.
  • Added text and json format output.
  • Manage errors and messages (warnings too) for the report.
  • Added context parameter to decide the behavior of the plugin when uploading, installing or just managing the signatures from the cache.
  • Added tools to create a package summary json file wtih files, checkusms, signing methof and signing provider.

@danimtb danimtb marked this pull request as ready for review November 11, 2025 15:48
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:
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assuming here that if the pkg signing plugin of the user only uses return, this means that it was correctly signed or verified

if sign_tools.is_pkg_signed():
summary = sign_tools.load_summary()
if summary.get("provider") != "conan-client":
return "Warn: Package already signed by another provider"
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be a warn or an error depending on the context or maybe there is some functionality still missing like conan cache sign --force to resign packages (user's plugin implementation). Also a conan cache sign --clear could be useful to "unsign" packages. WDYT?

Comment on lines +43 to +48
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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is doing a checksum of all artifacts in the package?
What exactly for?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We agreed that this should be the way the plugin should work. Get the cheksums of all the contetns of the package, create a summary file with filenames and checkesums and use that file to sign the package

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But maybe, this is the kind of thing that shouldn't belong then to user space? This is something that Conan should kind of enforce in the plugin? Or do we still envision other possible signing approaches that do not sign this file?

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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why instantiating here PkgSignaturesTools with those arguments, then passing it to self._plugin_sign_function() as an argument if the function can create the tools inside?

I am not convinced by this API, passing an object with data this way is not that common, in general Conan defines helpers/tools that users can instantiate and use them themselves, or provide an input-output interface, in which Conan would automatically manage the resource, in this case sign-summary.json. If this file must exist for any signing plugin, then why not mandating the return interface of sign() to return the necessary data so Conan can enforce the correct generation of sign-summary.json?

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"}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why returning the "action"? It is clear that you called the verify() method, in the same way you are not returning the package_list as it is an input, both the context and action seem redundant, they are already define by the action of calling verify(pkg_list).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I might sound redundant, but I think that having the information saved as a "log" might be useful if the json report is saved for later consumption or just for auditing

@czoido
Copy link
Contributor

czoido commented Nov 13, 2025

@danimtb what about something like this for the output? I think it may be more readable?
In the first one I also added the # for the rrev and the : for the package id but I think that they are not adding much? The others don't have that notation.

$ conan cache sign *

Found 3 pkg/version recipes matching * in local cache

[Package signing] Signing packages in local cache...

lib1ok/0.1
  #a5e2af5522a1edcab963447eec649700
  recipe: OK
  packages
    :da39a3ee5e6b4b0d3255bfef95601890: OK

lib2fail/0.1
  #70a185be5a95af3dde25b74ae800b2f2
  recipe: WARN (already signed by another provider)
  packages
    :da39a3ee5e6b4b0d3255bfef95601890: WARN (already signed by another provider)

lib3fail/0.1
  #09ccc766ddd11c96aa78307b3f166fd6
  recipe: FAIL (sign failed)
  packages
    :da39a3ee5e6b4b0d3255bfef95601890: FAIL (sign failed)

[Package signing] Summary: OK=3, WARN=2, FAILED=2

$ conan cache verify *

Found 3 pkg/version recipes matching * in local cache

[Package signing] Verifying signatures in local cache...

lib1ok/0.1
  a5e2af5522a1edcab963447eec649700
  recipe: OK
  packages
    da39a3ee5e6b4b0d3255bfef95601890: OK

lib2fail/0.1
  70a185be5a95af3dde25b74ae800b2f2
  recipe: FAIL (wrong provider)
  packages
    da39a3ee5e6b4b0d3255bfef95601890: FAIL (wrong provider)
    da39agfffg34234ff22424f4490fef95: OK
    4b0d3255bfef9560189da39a3ee5e6b0: FAIL (wrong provider)

lib3fail/0.1
  09ccc766ddd11c96aa78307b3f166fd6
  recipe: FAIL (Package is not signed)
  packages
    da39a3ee5e6b4b0d3255bfef95601890: FAIL (Package is not signed)

[Package signing] Summary: OK=3, WARN=0, FAILED=5
$ conan upload * -c -r default

Found 3 pkg/version recipes matching * in local cache

======== Uploading to remote default ========

-------- Checking server for existing packages --------
lib1ok/0.1: Checking which revisions exist in the remote server
lib2fail/0.1: Checking which revisions exist in the remote server
lib3fail/0.1: Checking which revisions exist in the remote server

-------- Preparing artifacts for upload --------
lib1ok/0.1:da39a3ee5e6b4b0d3255bfef95601890: Compressing conan_package.tgz
lib2fail/0.1:da39a3ee5e6b4b0d3255bfef95601890: Compressing conan_package.tgz
lib3fail/0.1:da39a3ee5e6b4b0d3255bfef95601890: Compressing conan_package.tgz

[Package signing] Signing packages before upload...

lib1ok/0.1
  a5e2af5522a1edcab963447eec649700
  recipe: OK (already signed by the same provider)
  packages
    da39a3ee5e6b4b0d3255bfef95601890: OK (already signed by the same provider)

lib2fail/0.1
  70a185be5a95af3dde25b74ae800b2f2
  recipe: WARN (already signed by another provider)
  packages
    da39a3ee5e6b4b0d3255bfef95601890: WARN (already signed by another provider)

lib3fail/0.1
  09ccc766ddd11c96aa78307b3f166fd6
  recipe: FAIL (sign failed)
  packages
    da39a3ee5e6b4b0d3255bfef95601890: FAIL (sign failed)

[Package signing] Summary: OK=3, WARN=2, FAILED=2

ERROR: Upload aborted: package signing failed
  lib3fail/0.1#09ccc766ddd11c96aa78307b3f166fd6
    reason: sign failed

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants