From 06ca302ea8b08ea3002bba87433f02a006435175 Mon Sep 17 00:00:00 2001 From: danimtb Date: Fri, 21 Nov 2025 13:21:27 +0100 Subject: [PATCH 1/3] Add package signing plugin example --- examples/extensions/README.md | 4 + examples/extensions/plugins/sign/readme.md | 16 ++++ examples/extensions/plugins/sign/sign.py | 94 ++++++++++++++++++++++ 3 files changed, 114 insertions(+) create mode 100644 examples/extensions/plugins/sign/readme.md create mode 100644 examples/extensions/plugins/sign/sign.py diff --git a/examples/extensions/README.md b/examples/extensions/README.md index 42c7fe29..3515b889 100644 --- a/examples/extensions/README.md +++ b/examples/extensions/README.md @@ -7,3 +7,7 @@ ### [Use custom deployers](extensions/deployers/) - Learn how to create a custom deployer in Conan. [Docs](https://docs.conan.io/2/reference/extensions/deployers.html) + +### [Package signing plugin example with OpenSSL](extensions/plugins/sign) + +- Learn how to create a package signing plugin in Conan. [Docs](https://docs.conan.io/2/reference/extensions/package_signing.html) diff --git a/examples/extensions/plugins/sign/readme.md b/examples/extensions/plugins/sign/readme.md new file mode 100644 index 00000000..9124e846 --- /dev/null +++ b/examples/extensions/plugins/sign/readme.md @@ -0,0 +1,16 @@ + +## Package signing plugin example with Openssl + +To run the package signing example, make sure you are using Conan with the changes in this branch: + + - https://github.com/danimtb/conan/tree/feature/improve_pkg-sign + +Steps to test the example: + +- Copy the ``sign.py`` file to your Conan home at ```CONAN_HOME/extensions/plugins/sign/sing.py```. +- Generate your signing keys (see comment at the top of sign.py) and place them next to the ``sign.py`` file. +- Create a new package to test the sign and verify commands: ``conan new cmake_lib -d name=hello -d version=1.0``. +- Sign the package: ``conan cache sign hello/1.0``. +- Verify the package signature: ```conan cache verify hello/1.0```. +- You can also use the ``conan upload`` command, and the packages should be signed automatically. +- You can also use the ``conan install`` command, and the packages should be verified automatically. diff --git a/examples/extensions/plugins/sign/sign.py b/examples/extensions/plugins/sign/sign.py new file mode 100644 index 00000000..2f605e57 --- /dev/null +++ b/examples/extensions/plugins/sign/sign.py @@ -0,0 +1,94 @@ +""" +Plugin to sign/verify Conan packages with OpenSSL. + +Requirements: The following executables should be installed and in the PATH. + - openssl + +To use this sigstore plugins, first generate a compatible keypair and define the environment variables for the keys: + + $ openssl genpkey -algorithm RSA -out private_key.pem -pkeyopt rsa_keygen_bits:2048 + +And extract the public key: + + $ openssl pkey -in private_key.pem -pubout -out public_key.pem + +The private_key.pem and public_key.pem files should be placed next to this plugins's file sign.py +(inside the CONAN_HOME/extensions/plugins/sing folder). +""" + + +import os +import subprocess +from conan.api.output import ConanOutput +from conan.errors import ConanException +from conan.tools.pkg_signing.plugin import (create_summary_content, get_summary_file_path, + load_summary, save_summary) + + +def _run_command(command): + result = subprocess.run( + command, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, # returns strings instead of bytes + check=False # we'll manually handle error checking + ) + + if result.returncode != 0: + raise subprocess.CalledProcessError( + result.returncode, result.args, output=result.stdout, stderr=result.stderr + ) + + +def sign(ref, artifacts_folder, signature_folder, **kwargs): + c = create_summary_content(artifacts_folder) + c["method"] = "openssl-dgst" + c["provider"] = "conan-client" + save_summary(signature_folder, c) + + # openssl dgst -sha256 -sign private_key.pem -out document.sig document.txt + summary_filepath = get_summary_file_path(signature_folder) + signature_filepath = f"{summary_filepath}.sig" + if os.path.isfile(signature_filepath): + ConanOutput().warning(f"Package {ref.repr_notime()} was already signed") + privkey_filepath = os.path.join(os.path.dirname(__file__), "private_key.pem") + openssl_sign_cmd = [ + "openssl", + "dgst", + "-sha256", + "-sign", privkey_filepath, + "-out", signature_filepath, + summary_filepath, + ] + try: + _run_command(openssl_sign_cmd) + except Exception as exc: + raise ConanException(f"Error signing artifact {summary_filepath}: {exc}") + + +def verify(ref, artifacts_folder, signature_folder, files, **kwargs): + summary_filepath = get_summary_file_path(signature_folder) + signature_filepath = f"{summary_filepath}.sig" + pubkey_filepath = os.path.join(os.path.dirname(__file__), "public_key.pem") + if not os.path.isfile(signature_filepath): + raise ConanException("Signature file does not exist") + + summary = load_summary(signature_folder) + # The provider is useful to choose the correct public key to verify packages with + provider = summary.get("provider") + if provider != "conan-client": + return f"Warn: The provider does not match (conan-client [expected] != {provider} [actual])" + + # openssl dgst -sha256 -verify public_key.pem -signature document.sig document.txt + openssl_verify_cmd = [ + "openssl", + "dgst", + "-sha256", + "-verify", pubkey_filepath, + "-signature", signature_filepath, + summary_filepath, + ] + try: + _run_command(openssl_verify_cmd) + except Exception as exc: + raise ConanException(f"Error verifying signature {signature_filepath}: {exc}") From e7313ee620fd0ff5b548a668d67eb202feb5f7f7 Mon Sep 17 00:00:00 2001 From: Daniel Date: Fri, 21 Nov 2025 13:29:21 +0100 Subject: [PATCH 2/3] Update examples/extensions/plugins/sign/readme.md Co-authored-by: Carlos Zoido --- examples/extensions/plugins/sign/readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/extensions/plugins/sign/readme.md b/examples/extensions/plugins/sign/readme.md index 9124e846..7aa052a7 100644 --- a/examples/extensions/plugins/sign/readme.md +++ b/examples/extensions/plugins/sign/readme.md @@ -7,7 +7,7 @@ To run the package signing example, make sure you are using Conan with the chang Steps to test the example: -- Copy the ``sign.py`` file to your Conan home at ```CONAN_HOME/extensions/plugins/sign/sing.py```. +- Copy the ``sign.py`` file to your Conan home at ```CONAN_HOME/extensions/plugins/sign/sign.py```. - Generate your signing keys (see comment at the top of sign.py) and place them next to the ``sign.py`` file. - Create a new package to test the sign and verify commands: ``conan new cmake_lib -d name=hello -d version=1.0``. - Sign the package: ``conan cache sign hello/1.0``. From 83b4499ed66599839b00418c0745238d48385eb0 Mon Sep 17 00:00:00 2001 From: danimtb Date: Fri, 21 Nov 2025 13:42:01 +0100 Subject: [PATCH 3/3] update --- examples/extensions/plugins/sign/readme.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/extensions/plugins/sign/readme.md b/examples/extensions/plugins/sign/readme.md index 9124e846..049452db 100644 --- a/examples/extensions/plugins/sign/readme.md +++ b/examples/extensions/plugins/sign/readme.md @@ -9,7 +9,8 @@ Steps to test the example: - Copy the ``sign.py`` file to your Conan home at ```CONAN_HOME/extensions/plugins/sign/sing.py```. - Generate your signing keys (see comment at the top of sign.py) and place them next to the ``sign.py`` file. -- Create a new package to test the sign and verify commands: ``conan new cmake_lib -d name=hello -d version=1.0``. +- Generate a new project to test the sign and verify commands: ``conan new cmake_lib -d name=hello -d version=1.0``. +- Create the package: ``conan create``. - Sign the package: ``conan cache sign hello/1.0``. - Verify the package signature: ```conan cache verify hello/1.0```. - You can also use the ``conan upload`` command, and the packages should be signed automatically.