From 06ca302ea8b08ea3002bba87433f02a006435175 Mon Sep 17 00:00:00 2001 From: danimtb Date: Fri, 21 Nov 2025 13:21:27 +0100 Subject: [PATCH 01/39] 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 02/39] 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 03/39] 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. From 7038381876fdec2594eaf6f7cc26b77d8828ab37 Mon Sep 17 00:00:00 2001 From: danimtb Date: Thu, 4 Dec 2025 11:41:10 +0100 Subject: [PATCH 04/39] minor update --- examples/extensions/plugins/sign/sign.py | 36 ++++++++++++++---------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/examples/extensions/plugins/sign/sign.py b/examples/extensions/plugins/sign/sign.py index 2f605e57..c579dedf 100644 --- a/examples/extensions/plugins/sign/sign.py +++ b/examples/extensions/plugins/sign/sign.py @@ -26,6 +26,7 @@ def _run_command(command): + ConanOutput().info(f"Running command: {' '.join(command)}") result = subprocess.run( command, stdout=subprocess.PIPE, @@ -77,18 +78,23 @@ def verify(ref, artifacts_folder, signature_folder, files, **kwargs): # 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}") + raise ConanException(f"The provider does not match (conan-client [expected] != {provider} [actual])." + f"Cannot get a public key to verify the package") + + method = summary.get("method") + if method == "openssl-dgst": + # 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}") + else: + raise ConanException(f"Sign method {method} not supported. Cannot verify package") From 03e91864c03537423c099ff0f794f00de1d4b8b1 Mon Sep 17 00:00:00 2001 From: danimtb Date: Tue, 16 Dec 2025 17:34:44 +0100 Subject: [PATCH 05/39] update plugin example --- examples/extensions/plugins/sign/sign.py | 58 ++++++++++--------- .../libcurl/download_image/ci_test_example.py | 2 +- 2 files changed, 33 insertions(+), 27 deletions(-) diff --git a/examples/extensions/plugins/sign/sign.py b/examples/extensions/plugins/sign/sign.py index c579dedf..0e72fe14 100644 --- a/examples/extensions/plugins/sign/sign.py +++ b/examples/extensions/plugins/sign/sign.py @@ -12,7 +12,8 @@ $ 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 +The private_key.pem and public_key.pem files should be placed inside a folder named withe the provider's name +('conan-client' for this example). The conan-client folder should be next to this plugins's file sign.py (inside the CONAN_HOME/extensions/plugins/sing folder). """ @@ -21,8 +22,8 @@ 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) +from conan.tools.pkg_signing.plugin import (get_manifest_filepath, load_manifest, + load_signatures, verify_files_checksums) def _run_command(command): @@ -42,47 +43,52 @@ def _run_command(command): 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" + provider = "conan-client" # This maps to the folder containing the signing keys (for simplicity) + manifest_filepath = get_manifest_filepath(signature_folder) + signature_filename = "pkgsign-manifest.json.sig" + signature_filepath = os.path.join(signature_folder, signature_filename) 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") + + privkey_filepath = os.path.join(os.path.dirname(__file__), provider, "private_key.pem") + # openssl dgst -sha256 -sign private_key.pem -out document.sig document.txt openssl_sign_cmd = [ "openssl", "dgst", "-sha256", "-sign", privkey_filepath, "-out", signature_filepath, - summary_filepath, + manifest_filepath ] try: _run_command(openssl_sign_cmd) except Exception as exc: raise ConanException(f"Error signing artifact {summary_filepath}: {exc}") + return [{"method": "openssl-dgst", + "provider": provider, + "sign_artifacts": {"signature": signature_filename}}] 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") + verify_files_checksums(signature_folder, files) + + signature = load_signatures(signature_folder).get("signatures")[0] + signature_filename = signature.get("sign_artifacts").get("signature") + signature_filepath = os.path.join(signature_folder, signature_filename) 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": - raise ConanException(f"The provider does not match (conan-client [expected] != {provider} [actual])." - f"Cannot get a public key to verify the package") - - method = summary.get("method") - if method == "openssl-dgst": + expected_provider = "conan-client" + signature_provider = signature.get("provider") + if signature_provider != expected_provider: + raise ConanException(f"The provider does not match ({expected_provider} [expected] != {signature_provider} " + "[actual]). Cannot get a public key to verify the package") + pubkey_filepath = os.path.join(os.path.dirname(__file__), "conan-client", "public_key.pem") + + manifest_filepath = get_manifest_filepath(signature_folder) + signature_method = signature.get("method") + if signature_method == "openssl-dgst": # openssl dgst -sha256 -verify public_key.pem -signature document.sig document.txt openssl_verify_cmd = [ "openssl", @@ -90,11 +96,11 @@ def verify(ref, artifacts_folder, signature_folder, files, **kwargs): "-sha256", "-verify", pubkey_filepath, "-signature", signature_filepath, - summary_filepath, + manifest_filepath, ] try: _run_command(openssl_verify_cmd) except Exception as exc: raise ConanException(f"Error verifying signature {signature_filepath}: {exc}") else: - raise ConanException(f"Sign method {method} not supported. Cannot verify package") + raise ConanException(f"Sign method {signature_method} not supported. Cannot verify package") diff --git a/examples/libraries/libcurl/download_image/ci_test_example.py b/examples/libraries/libcurl/download_image/ci_test_example.py index e357638a..cc17ae1d 100644 --- a/examples/libraries/libcurl/download_image/ci_test_example.py +++ b/examples/libraries/libcurl/download_image/ci_test_example.py @@ -3,7 +3,7 @@ print("libcurl and stb example") -# not using a conanfile because that will be created by the CLion plugin, in case someone just wants to +# not using a conanfile because that will be created by the CLion plugins, in case someone just wants to # copy this code to its folder so that the user does not find any conflicting file run("conan install --requires=stb/cci.20240531 --build=missing -g CMakeDeps -g CMakeToolchain --output-folder=build") run("conan install --requires=libcurl/8.12.1 --build=missing -g CMakeDeps -g CMakeToolchain --output-folder=build") From 68d97275d0d407b67d6341f5c0e4e5a7a3be9d4d Mon Sep 17 00:00:00 2001 From: danimtb Date: Wed, 4 Feb 2026 16:49:31 +0100 Subject: [PATCH 06/39] update --- .../plugins/openssl_sign/ci_test_example.py | 18 ++++++++++++++++++ .../plugins/{sign => openssl_sign}/readme.md | 8 ++------ .../plugins/{sign => openssl_sign}/sign.py | 19 +++++++++---------- 3 files changed, 29 insertions(+), 16 deletions(-) create mode 100644 examples/extensions/plugins/openssl_sign/ci_test_example.py rename examples/extensions/plugins/{sign => openssl_sign}/readme.md (59%) rename examples/extensions/plugins/{sign => openssl_sign}/sign.py (84%) diff --git a/examples/extensions/plugins/openssl_sign/ci_test_example.py b/examples/extensions/plugins/openssl_sign/ci_test_example.py new file mode 100644 index 00000000..115cbcd6 --- /dev/null +++ b/examples/extensions/plugins/openssl_sign/ci_test_example.py @@ -0,0 +1,18 @@ +import os +import shutil + +from test.examples_tools import run, tmp_dir + +run("git clone https://github.com/conan-io/conan-extensions.git") +run("openssl genpkey -algorithm RSA -out conan-extensions/plugins/openssl_sign/your-organization/private_key.pem -pkeyopt rsa_keygen_bits:2048") +run("openssl pkey -in conan-extensions/plugins/openssl_sign/your-organization/private_key.pem -pubout -out conan-extensions/plugins/openssl_sign/your-organization/public_key.pem") + +run("conan config install conan-extensions -t dir --source-folder plugins/openssl_sign --target-folder plugins/sign") + +run("conan new cmake_lib -d name=hello -d version=1.0") +run("conan create") + +output = run("conan cache sign hello/1.0") +assert "kk" in output +output = run("conan cache verify hello/1.0") +assert "kk" in output diff --git a/examples/extensions/plugins/sign/readme.md b/examples/extensions/plugins/openssl_sign/readme.md similarity index 59% rename from examples/extensions/plugins/sign/readme.md rename to examples/extensions/plugins/openssl_sign/readme.md index f4debdf5..3a87fb4f 100644 --- a/examples/extensions/plugins/sign/readme.md +++ b/examples/extensions/plugins/openssl_sign/readme.md @@ -1,17 +1,13 @@ ## 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/sign.py```. -- Generate your signing keys (see comment at the top of sign.py) and place them next to the ``sign.py`` file. +- Generate your signing keys (see comment at the top of the ``sign.py`` file) and place them next to the ``sign.py`` file, + inside a folder with the name of your provider (``your-organization`` in the example). - 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. - 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/openssl_sign/sign.py similarity index 84% rename from examples/extensions/plugins/sign/sign.py rename to examples/extensions/plugins/openssl_sign/sign.py index 0e72fe14..628eda21 100644 --- a/examples/extensions/plugins/sign/sign.py +++ b/examples/extensions/plugins/openssl_sign/sign.py @@ -22,8 +22,6 @@ import subprocess from conan.api.output import ConanOutput from conan.errors import ConanException -from conan.tools.pkg_signing.plugin import (get_manifest_filepath, load_manifest, - load_signatures, verify_files_checksums) def _run_command(command): @@ -43,7 +41,7 @@ def _run_command(command): def sign(ref, artifacts_folder, signature_folder, **kwargs): - provider = "conan-client" # This maps to the folder containing the signing keys (for simplicity) + provider = "your-organization" # This maps to the folder containing the signing keys (for simplicity) manifest_filepath = get_manifest_filepath(signature_folder) signature_filename = "pkgsign-manifest.json.sig" signature_filepath = os.path.join(signature_folder, signature_filename) @@ -66,27 +64,28 @@ def sign(ref, artifacts_folder, signature_folder, **kwargs): raise ConanException(f"Error signing artifact {summary_filepath}: {exc}") return [{"method": "openssl-dgst", "provider": provider, - "sign_artifacts": {"signature": signature_filename}}] + "sign_artifacts": { + "manifest": "pkgsign-manifest.json", + "signature": signature_filename}}] def verify(ref, artifacts_folder, signature_folder, files, **kwargs): - verify_files_checksums(signature_folder, files) - - signature = load_signatures(signature_folder).get("signatures")[0] + signatures = os.path.join(signature_folder, "pkgsign-signatures.json") + signature = json.loads(open(signatures).read()).get("signatures")[0] signature_filename = signature.get("sign_artifacts").get("signature") signature_filepath = os.path.join(signature_folder, signature_filename) if not os.path.isfile(signature_filepath): raise ConanException("Signature file does not exist") # The provider is useful to choose the correct public key to verify packages with - expected_provider = "conan-client" + expected_provider = "your-organization" signature_provider = signature.get("provider") if signature_provider != expected_provider: raise ConanException(f"The provider does not match ({expected_provider} [expected] != {signature_provider} " "[actual]). Cannot get a public key to verify the package") - pubkey_filepath = os.path.join(os.path.dirname(__file__), "conan-client", "public_key.pem") + pubkey_filepath = os.path.join(os.path.dirname(__file__), expected_provider, "public_key.pem") - manifest_filepath = get_manifest_filepath(signature_folder) + manifest_filepath =os.path.join(signature_folder, "pkgsign-manifest.json") signature_method = signature.get("method") if signature_method == "openssl-dgst": # openssl dgst -sha256 -verify public_key.pem -signature document.sig document.txt From 0c5eccb0a0509456e1a49b2f6d17cbb0b2670284 Mon Sep 17 00:00:00 2001 From: danimtb Date: Wed, 4 Feb 2026 16:56:16 +0100 Subject: [PATCH 07/39] fix mkdirs --- .../extensions/plugins/openssl_sign/ci_test_example.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/examples/extensions/plugins/openssl_sign/ci_test_example.py b/examples/extensions/plugins/openssl_sign/ci_test_example.py index 115cbcd6..9cc1d7a5 100644 --- a/examples/extensions/plugins/openssl_sign/ci_test_example.py +++ b/examples/extensions/plugins/openssl_sign/ci_test_example.py @@ -1,11 +1,12 @@ import os -import shutil from test.examples_tools import run, tmp_dir run("git clone https://github.com/conan-io/conan-extensions.git") -run("openssl genpkey -algorithm RSA -out conan-extensions/plugins/openssl_sign/your-organization/private_key.pem -pkeyopt rsa_keygen_bits:2048") -run("openssl pkey -in conan-extensions/plugins/openssl_sign/your-organization/private_key.pem -pubout -out conan-extensions/plugins/openssl_sign/your-organization/public_key.pem") +provider_folder = os.path.join("conan-extensions", "plugins", "openssl_sign", "your-organization") +os.makedirs(provider_folder) +run(f"openssl genpkey -algorithm RSA -out {provider_folder}/private_key.pem -pkeyopt rsa_keygen_bits:2048") +run(f"openssl pkey -in {provider_folder}/private_key.pem -pubout -out {provider_folder}/public_key.pem") run("conan config install conan-extensions -t dir --source-folder plugins/openssl_sign --target-folder plugins/sign") From 0dcee4dc47ba883eb75ae900180a7fbb92a6beb8 Mon Sep 17 00:00:00 2001 From: danimtb Date: Wed, 4 Feb 2026 17:11:37 +0100 Subject: [PATCH 08/39] fix folders --- examples/extensions/plugins/openssl_sign/ci_test_example.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/extensions/plugins/openssl_sign/ci_test_example.py b/examples/extensions/plugins/openssl_sign/ci_test_example.py index 9cc1d7a5..8e818168 100644 --- a/examples/extensions/plugins/openssl_sign/ci_test_example.py +++ b/examples/extensions/plugins/openssl_sign/ci_test_example.py @@ -2,13 +2,13 @@ from test.examples_tools import run, tmp_dir -run("git clone https://github.com/conan-io/conan-extensions.git") -provider_folder = os.path.join("conan-extensions", "plugins", "openssl_sign", "your-organization") +run("git clone https://github.com/conan-io/examples2.git") +provider_folder = os.path.join("examples2", "examples", "extensions", "plugins", "openssl_sign", "your-organization") os.makedirs(provider_folder) run(f"openssl genpkey -algorithm RSA -out {provider_folder}/private_key.pem -pkeyopt rsa_keygen_bits:2048") run(f"openssl pkey -in {provider_folder}/private_key.pem -pubout -out {provider_folder}/public_key.pem") -run("conan config install conan-extensions -t dir --source-folder plugins/openssl_sign --target-folder plugins/sign") +run("conan config install examples2/examples -t dir --source-folder extensions/plugins/openssl_sign --target-folder extensions/plugins/sign") run("conan new cmake_lib -d name=hello -d version=1.0") run("conan create") From e8da3fa3c059ad4a28c86150004d0bac0884a766 Mon Sep 17 00:00:00 2001 From: danimtb Date: Wed, 4 Feb 2026 17:29:17 +0100 Subject: [PATCH 09/39] try --- .../extensions/plugins/openssl_sign/ci_test_example.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/examples/extensions/plugins/openssl_sign/ci_test_example.py b/examples/extensions/plugins/openssl_sign/ci_test_example.py index 8e818168..934e47ba 100644 --- a/examples/extensions/plugins/openssl_sign/ci_test_example.py +++ b/examples/extensions/plugins/openssl_sign/ci_test_example.py @@ -1,14 +1,15 @@ import os -from test.examples_tools import run, tmp_dir +from test.examples_tools import run -run("git clone https://github.com/conan-io/examples2.git") -provider_folder = os.path.join("examples2", "examples", "extensions", "plugins", "openssl_sign", "your-organization") + +current_dir = os.path.abspath(os.path.dirname(__file__)) +provider_folder = os.path.join(current_dir, "your-organization") os.makedirs(provider_folder) run(f"openssl genpkey -algorithm RSA -out {provider_folder}/private_key.pem -pkeyopt rsa_keygen_bits:2048") run(f"openssl pkey -in {provider_folder}/private_key.pem -pubout -out {provider_folder}/public_key.pem") -run("conan config install examples2/examples -t dir --source-folder extensions/plugins/openssl_sign --target-folder extensions/plugins/sign") +run(f"conan config install {current_dir} -t dir --target-folder extensions/plugins/sign") run("conan new cmake_lib -d name=hello -d version=1.0") run("conan create") From 343dae2c233f3159540be35d5e8b00e871ad8c71 Mon Sep 17 00:00:00 2001 From: danimtb Date: Wed, 4 Feb 2026 17:39:43 +0100 Subject: [PATCH 10/39] fix path --- examples/extensions/plugins/openssl_sign/sign.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/examples/extensions/plugins/openssl_sign/sign.py b/examples/extensions/plugins/openssl_sign/sign.py index 628eda21..77504a89 100644 --- a/examples/extensions/plugins/openssl_sign/sign.py +++ b/examples/extensions/plugins/openssl_sign/sign.py @@ -13,13 +13,14 @@ $ openssl pkey -in private_key.pem -pubout -out public_key.pem The private_key.pem and public_key.pem files should be placed inside a folder named withe the provider's name -('conan-client' for this example). The conan-client folder should be next to this plugins's file sign.py +('my-organization' for this example). The 'my-organization' folder should be next to this plugins's file sign.py (inside the CONAN_HOME/extensions/plugins/sing folder). """ - import os +import json import subprocess + from conan.api.output import ConanOutput from conan.errors import ConanException @@ -42,7 +43,7 @@ def _run_command(command): def sign(ref, artifacts_folder, signature_folder, **kwargs): provider = "your-organization" # This maps to the folder containing the signing keys (for simplicity) - manifest_filepath = get_manifest_filepath(signature_folder) + manifest_filepath = os.path.join(signature_folder, "pkgsign-manifest.json") signature_filename = "pkgsign-manifest.json.sig" signature_filepath = os.path.join(signature_folder, signature_filename) if os.path.isfile(signature_filepath): From 101b69fb2135ab16b5b39dc316a8507b9580d7d4 Mon Sep 17 00:00:00 2001 From: danimtb Date: Wed, 4 Feb 2026 17:45:48 +0100 Subject: [PATCH 11/39] fix output asserts --- examples/extensions/plugins/openssl_sign/ci_test_example.py | 6 ++++-- examples/extensions/plugins/openssl_sign/sign.py | 2 ++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/examples/extensions/plugins/openssl_sign/ci_test_example.py b/examples/extensions/plugins/openssl_sign/ci_test_example.py index 934e47ba..22581ad5 100644 --- a/examples/extensions/plugins/openssl_sign/ci_test_example.py +++ b/examples/extensions/plugins/openssl_sign/ci_test_example.py @@ -15,6 +15,8 @@ run("conan create") output = run("conan cache sign hello/1.0") -assert "kk" in output +assert "Package signed for reference hello/1.0" in output +assert "[Package sign] Summary: OK=2, FAILED=0" in output output = run("conan cache verify hello/1.0") -assert "kk" in output +assert "Package verified for reference hello/1.0" in output +assert "[Package sign] Summary: OK=2, FAILED=0" in output diff --git a/examples/extensions/plugins/openssl_sign/sign.py b/examples/extensions/plugins/openssl_sign/sign.py index 77504a89..a1bb6cdf 100644 --- a/examples/extensions/plugins/openssl_sign/sign.py +++ b/examples/extensions/plugins/openssl_sign/sign.py @@ -61,6 +61,7 @@ def sign(ref, artifacts_folder, signature_folder, **kwargs): ] try: _run_command(openssl_sign_cmd) + ConanOutput().success(f"Package signed for reference {ref}") except Exception as exc: raise ConanException(f"Error signing artifact {summary_filepath}: {exc}") return [{"method": "openssl-dgst", @@ -100,6 +101,7 @@ def verify(ref, artifacts_folder, signature_folder, files, **kwargs): ] try: _run_command(openssl_verify_cmd) + ConanOutput().success(f"Package verified for reference {ref}") except Exception as exc: raise ConanException(f"Error verifying signature {signature_filepath}: {exc}") else: From f6337991db81b3d7fea49548f23f6d8a199fb9bd Mon Sep 17 00:00:00 2001 From: danimtb Date: Wed, 4 Feb 2026 18:01:16 +0100 Subject: [PATCH 12/39] fix leftovers --- examples/extensions/plugins/openssl_sign/ci_test_example.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/examples/extensions/plugins/openssl_sign/ci_test_example.py b/examples/extensions/plugins/openssl_sign/ci_test_example.py index 22581ad5..5f5e821e 100644 --- a/examples/extensions/plugins/openssl_sign/ci_test_example.py +++ b/examples/extensions/plugins/openssl_sign/ci_test_example.py @@ -20,3 +20,6 @@ output = run("conan cache verify hello/1.0") assert "Package verified for reference hello/1.0" in output assert "[Package sign] Summary: OK=2, FAILED=0" in output + +conan_home = run("conan config home") +os.rmdir(os.path.join(conan_home, "extensions", "plugins", "sign")) From 8985213f763c17d195773a1c20dd7952296446ab Mon Sep 17 00:00:00 2001 From: danimtb Date: Wed, 4 Feb 2026 18:04:06 +0100 Subject: [PATCH 13/39] strip --- examples/extensions/plugins/openssl_sign/ci_test_example.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/extensions/plugins/openssl_sign/ci_test_example.py b/examples/extensions/plugins/openssl_sign/ci_test_example.py index 5f5e821e..99472629 100644 --- a/examples/extensions/plugins/openssl_sign/ci_test_example.py +++ b/examples/extensions/plugins/openssl_sign/ci_test_example.py @@ -21,5 +21,5 @@ assert "Package verified for reference hello/1.0" in output assert "[Package sign] Summary: OK=2, FAILED=0" in output -conan_home = run("conan config home") +conan_home = run("conan config home").strip() os.rmdir(os.path.join(conan_home, "extensions", "plugins", "sign")) From f0df224ccab258978c86e2ca166097522faf378b Mon Sep 17 00:00:00 2001 From: danimtb Date: Wed, 4 Feb 2026 18:08:05 +0100 Subject: [PATCH 14/39] fix --- examples/extensions/plugins/openssl_sign/ci_test_example.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/extensions/plugins/openssl_sign/ci_test_example.py b/examples/extensions/plugins/openssl_sign/ci_test_example.py index 99472629..96e9a5dd 100644 --- a/examples/extensions/plugins/openssl_sign/ci_test_example.py +++ b/examples/extensions/plugins/openssl_sign/ci_test_example.py @@ -1,4 +1,5 @@ import os +import shutil from test.examples_tools import run @@ -22,4 +23,4 @@ assert "[Package sign] Summary: OK=2, FAILED=0" in output conan_home = run("conan config home").strip() -os.rmdir(os.path.join(conan_home, "extensions", "plugins", "sign")) +shutil.rmtree(os.path.join(conan_home, "extensions", "plugins", "sign")) From d552623ccb4eb8794d0b954de62309dae537e2e2 Mon Sep 17 00:00:00 2001 From: Daniel Date: Fri, 6 Feb 2026 10:57:36 +0100 Subject: [PATCH 15/39] Apply suggestion from @danimtb --- examples/libraries/libcurl/download_image/ci_test_example.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/libraries/libcurl/download_image/ci_test_example.py b/examples/libraries/libcurl/download_image/ci_test_example.py index cc17ae1d..c38dc292 100644 --- a/examples/libraries/libcurl/download_image/ci_test_example.py +++ b/examples/libraries/libcurl/download_image/ci_test_example.py @@ -3,7 +3,7 @@ print("libcurl and stb example") -# not using a conanfile because that will be created by the CLion plugins, in case someone just wants to +# not using a conanfile because that will be created by the CLion plugin, in case someone just wants to # copy this code to its folder so that the user does not find any conflicting file run("conan install --requires=stb/cci.20240531 --build=missing -g CMakeDeps -g CMakeToolchain --output-folder=build") run("conan install --requires=libcurl/8.12.1 --build=missing -g CMakeDeps -g CMakeToolchain --output-folder=build") From a3099ee6103f8b16d3ea45aa834992916225ab0a Mon Sep 17 00:00:00 2001 From: Daniel Date: Fri, 6 Feb 2026 10:57:44 +0100 Subject: [PATCH 16/39] Apply suggestion from @danimtb --- examples/extensions/plugins/openssl_sign/readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/extensions/plugins/openssl_sign/readme.md b/examples/extensions/plugins/openssl_sign/readme.md index 3a87fb4f..65292655 100644 --- a/examples/extensions/plugins/openssl_sign/readme.md +++ b/examples/extensions/plugins/openssl_sign/readme.md @@ -5,7 +5,7 @@ Steps to test the example: - 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 the ``sign.py`` file) and place them next to the ``sign.py`` file, - inside a folder with the name of your provider (``your-organization`` in the example). + inside a folder with the name of your provider (``my-organization`` in the example). - 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``. From 61fc931ad4179e343fc7b1aab29385d937bcfbd1 Mon Sep 17 00:00:00 2001 From: Daniel Date: Fri, 6 Feb 2026 10:58:11 +0100 Subject: [PATCH 17/39] Apply suggestion from @danimtb --- examples/extensions/plugins/openssl_sign/ci_test_example.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/extensions/plugins/openssl_sign/ci_test_example.py b/examples/extensions/plugins/openssl_sign/ci_test_example.py index 96e9a5dd..5f34becd 100644 --- a/examples/extensions/plugins/openssl_sign/ci_test_example.py +++ b/examples/extensions/plugins/openssl_sign/ci_test_example.py @@ -5,7 +5,7 @@ current_dir = os.path.abspath(os.path.dirname(__file__)) -provider_folder = os.path.join(current_dir, "your-organization") +provider_folder = os.path.join(current_dir, "my-organization") os.makedirs(provider_folder) run(f"openssl genpkey -algorithm RSA -out {provider_folder}/private_key.pem -pkeyopt rsa_keygen_bits:2048") run(f"openssl pkey -in {provider_folder}/private_key.pem -pubout -out {provider_folder}/public_key.pem") From f1f933af1e97a6a8e01b6a82cc3c2efe8ea8c662 Mon Sep 17 00:00:00 2001 From: Daniel Date: Fri, 6 Feb 2026 10:58:19 +0100 Subject: [PATCH 18/39] Apply suggestion from @danimtb --- examples/extensions/plugins/openssl_sign/readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/extensions/plugins/openssl_sign/readme.md b/examples/extensions/plugins/openssl_sign/readme.md index 65292655..d72602c5 100644 --- a/examples/extensions/plugins/openssl_sign/readme.md +++ b/examples/extensions/plugins/openssl_sign/readme.md @@ -10,4 +10,4 @@ Steps to test the example: - 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 install`` command, and the packages should be verified automatically. +- You can also use the ``conan install`` command, and the packages should be verified automatically when they are downloaded from a remote. From c0a79e9ef86778d89f4ed632e915855c45e8b860 Mon Sep 17 00:00:00 2001 From: Daniel Date: Fri, 6 Feb 2026 10:58:37 +0100 Subject: [PATCH 19/39] Apply suggestion from @danimtb --- examples/extensions/plugins/openssl_sign/sign.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/extensions/plugins/openssl_sign/sign.py b/examples/extensions/plugins/openssl_sign/sign.py index a1bb6cdf..73bd596e 100644 --- a/examples/extensions/plugins/openssl_sign/sign.py +++ b/examples/extensions/plugins/openssl_sign/sign.py @@ -42,7 +42,7 @@ def _run_command(command): def sign(ref, artifacts_folder, signature_folder, **kwargs): - provider = "your-organization" # This maps to the folder containing the signing keys (for simplicity) + provider = "my-organization" # This maps to the folder containing the signing keys (for simplicity) manifest_filepath = os.path.join(signature_folder, "pkgsign-manifest.json") signature_filename = "pkgsign-manifest.json.sig" signature_filepath = os.path.join(signature_folder, signature_filename) From af15f5e3ebc577e833fc9e76fbb9370d1db4688b Mon Sep 17 00:00:00 2001 From: Daniel Date: Fri, 6 Feb 2026 10:58:46 +0100 Subject: [PATCH 20/39] Apply suggestion from @danimtb --- examples/extensions/plugins/openssl_sign/sign.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/extensions/plugins/openssl_sign/sign.py b/examples/extensions/plugins/openssl_sign/sign.py index 73bd596e..9e05aa71 100644 --- a/examples/extensions/plugins/openssl_sign/sign.py +++ b/examples/extensions/plugins/openssl_sign/sign.py @@ -80,7 +80,7 @@ def verify(ref, artifacts_folder, signature_folder, files, **kwargs): raise ConanException("Signature file does not exist") # The provider is useful to choose the correct public key to verify packages with - expected_provider = "your-organization" + expected_provider = "my-organization" signature_provider = signature.get("provider") if signature_provider != expected_provider: raise ConanException(f"The provider does not match ({expected_provider} [expected] != {signature_provider} " From 3b85594a78961e6cd71691f63f212978b19a78fc Mon Sep 17 00:00:00 2001 From: Daniel Date: Fri, 6 Feb 2026 10:59:07 +0100 Subject: [PATCH 21/39] Apply suggestion from @danimtb --- examples/libraries/libcurl/download_image/ci_test_example.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/libraries/libcurl/download_image/ci_test_example.py b/examples/libraries/libcurl/download_image/ci_test_example.py index c38dc292..e357638a 100644 --- a/examples/libraries/libcurl/download_image/ci_test_example.py +++ b/examples/libraries/libcurl/download_image/ci_test_example.py @@ -3,7 +3,7 @@ print("libcurl and stb example") -# not using a conanfile because that will be created by the CLion plugin, in case someone just wants to +# not using a conanfile because that will be created by the CLion plugin, in case someone just wants to # copy this code to its folder so that the user does not find any conflicting file run("conan install --requires=stb/cci.20240531 --build=missing -g CMakeDeps -g CMakeToolchain --output-folder=build") run("conan install --requires=libcurl/8.12.1 --build=missing -g CMakeDeps -g CMakeToolchain --output-folder=build") From da3dfad59851269b684b99028777b40aa261a7c5 Mon Sep 17 00:00:00 2001 From: Daniel Date: Fri, 6 Feb 2026 10:59:33 +0100 Subject: [PATCH 22/39] Apply suggestion from @danimtb --- examples/extensions/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/extensions/README.md b/examples/extensions/README.md index 3515b889..8120cdc1 100644 --- a/examples/extensions/README.md +++ b/examples/extensions/README.md @@ -8,6 +8,6 @@ - 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) +### [Package signing plugin example with OpenSSL](extensions/plugins/openssl_sign) - Learn how to create a package signing plugin in Conan. [Docs](https://docs.conan.io/2/reference/extensions/package_signing.html) From 1977255fcd0dff289b979c459c0718beed0a9e23 Mon Sep 17 00:00:00 2001 From: Daniel Date: Fri, 6 Feb 2026 13:46:34 +0100 Subject: [PATCH 23/39] Apply suggestion from @danimtb --- examples/extensions/plugins/openssl_sign/sign.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/extensions/plugins/openssl_sign/sign.py b/examples/extensions/plugins/openssl_sign/sign.py index 9e05aa71..f8392404 100644 --- a/examples/extensions/plugins/openssl_sign/sign.py +++ b/examples/extensions/plugins/openssl_sign/sign.py @@ -63,7 +63,7 @@ def sign(ref, artifacts_folder, signature_folder, **kwargs): _run_command(openssl_sign_cmd) ConanOutput().success(f"Package signed for reference {ref}") except Exception as exc: - raise ConanException(f"Error signing artifact {summary_filepath}: {exc}") + raise ConanException(f"Error signing artifact") return [{"method": "openssl-dgst", "provider": provider, "sign_artifacts": { From 070c6275ab19452be9f2bb2e05905d9be7548e97 Mon Sep 17 00:00:00 2001 From: danimtb Date: Fri, 6 Feb 2026 15:29:13 +0100 Subject: [PATCH 24/39] conan version check in test --- examples/extensions/plugins/openssl_sign/ci_test_example.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/examples/extensions/plugins/openssl_sign/ci_test_example.py b/examples/extensions/plugins/openssl_sign/ci_test_example.py index 96e9a5dd..84b12221 100644 --- a/examples/extensions/plugins/openssl_sign/ci_test_example.py +++ b/examples/extensions/plugins/openssl_sign/ci_test_example.py @@ -1,8 +1,11 @@ import os import shutil +from conan import conan_version from test.examples_tools import run +if conan_version < "2.26.0-dev": + return current_dir = os.path.abspath(os.path.dirname(__file__)) provider_folder = os.path.join(current_dir, "your-organization") From 7f0aba715d5597234b2c47b2734ede6a5275430a Mon Sep 17 00:00:00 2001 From: Daniel Date: Fri, 6 Feb 2026 15:34:59 +0100 Subject: [PATCH 25/39] Apply suggestion from @danimtb --- examples/extensions/plugins/openssl_sign/readme.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/examples/extensions/plugins/openssl_sign/readme.md b/examples/extensions/plugins/openssl_sign/readme.md index d72602c5..a46622ef 100644 --- a/examples/extensions/plugins/openssl_sign/readme.md +++ b/examples/extensions/plugins/openssl_sign/readme.md @@ -4,8 +4,7 @@ Steps to test the example: - 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 the ``sign.py`` file) and place them next to the ``sign.py`` file, - inside a folder with the name of your provider (``my-organization`` in the example). +- Generate your signing keys (see comment at the top of the ``sign.py`` file) and place them inside a folder with the name of your provider (``my-organization`` in the example) next to the ``sign.py`` file (``CONAN_HOME/extensions/sign/my-organization/``). - 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``. From b0097daa5c4bd8458f93cb993c52e8c4adc100bb Mon Sep 17 00:00:00 2001 From: Daniel Date: Fri, 6 Feb 2026 15:35:07 +0100 Subject: [PATCH 26/39] Apply suggestion from @danimtb --- examples/extensions/plugins/openssl_sign/readme.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/extensions/plugins/openssl_sign/readme.md b/examples/extensions/plugins/openssl_sign/readme.md index a46622ef..c8606bc5 100644 --- a/examples/extensions/plugins/openssl_sign/readme.md +++ b/examples/extensions/plugins/openssl_sign/readme.md @@ -5,8 +5,8 @@ Steps to test the example: - 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 the ``sign.py`` file) and place them inside a folder with the name of your provider (``my-organization`` in the example) next to the ``sign.py`` file (``CONAN_HOME/extensions/sign/my-organization/``). -- 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```. +- 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 install`` command, and the packages should be verified automatically when they are downloaded from a remote. From 21f8998da84e0b3a24f508c5e9d45fc924b7fb7b Mon Sep 17 00:00:00 2001 From: danimtb Date: Fri, 6 Feb 2026 15:42:42 +0100 Subject: [PATCH 27/39] fix test --- .../plugins/openssl_sign/ci_test_example.py | 35 +++++++++---------- 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/examples/extensions/plugins/openssl_sign/ci_test_example.py b/examples/extensions/plugins/openssl_sign/ci_test_example.py index 15665d77..f9f308ad 100644 --- a/examples/extensions/plugins/openssl_sign/ci_test_example.py +++ b/examples/extensions/plugins/openssl_sign/ci_test_example.py @@ -4,26 +4,25 @@ from conan import conan_version from test.examples_tools import run -if conan_version < "2.26.0-dev": - return +if conan_version >= "2.26.0-dev": + current_dir = os.path.abspath(os.path.dirname(__file__)) + provider_folder = os.path.join(current_dir, "my-organization") -current_dir = os.path.abspath(os.path.dirname(__file__)) -provider_folder = os.path.join(current_dir, "my-organization") -os.makedirs(provider_folder) -run(f"openssl genpkey -algorithm RSA -out {provider_folder}/private_key.pem -pkeyopt rsa_keygen_bits:2048") -run(f"openssl pkey -in {provider_folder}/private_key.pem -pubout -out {provider_folder}/public_key.pem") + os.makedirs(provider_folder) + run(f"openssl genpkey -algorithm RSA -out {provider_folder}/private_key.pem -pkeyopt rsa_keygen_bits:2048") + run(f"openssl pkey -in {provider_folder}/private_key.pem -pubout -out {provider_folder}/public_key.pem") -run(f"conan config install {current_dir} -t dir --target-folder extensions/plugins/sign") + run(f"conan config install {current_dir} -t dir --target-folder extensions/plugins/sign") -run("conan new cmake_lib -d name=hello -d version=1.0") -run("conan create") + run("conan new cmake_lib -d name=hello -d version=1.0") + run("conan create") -output = run("conan cache sign hello/1.0") -assert "Package signed for reference hello/1.0" in output -assert "[Package sign] Summary: OK=2, FAILED=0" in output -output = run("conan cache verify hello/1.0") -assert "Package verified for reference hello/1.0" in output -assert "[Package sign] Summary: OK=2, FAILED=0" in output + output = run("conan cache sign hello/1.0") + assert "Package signed for reference hello/1.0" in output + assert "[Package sign] Summary: OK=2, FAILED=0" in output + output = run("conan cache verify hello/1.0") + assert "Package verified for reference hello/1.0" in output + assert "[Package sign] Summary: OK=2, FAILED=0" in output -conan_home = run("conan config home").strip() -shutil.rmtree(os.path.join(conan_home, "extensions", "plugins", "sign")) + conan_home = run("conan config home").strip() + shutil.rmtree(os.path.join(conan_home, "extensions", "plugins", "sign")) From 6cf1083688e87c199f86441dc46507930490f21c Mon Sep 17 00:00:00 2001 From: Daniel Date: Tue, 10 Feb 2026 17:45:43 +0100 Subject: [PATCH 28/39] Update examples/extensions/plugins/openssl_sign/readme.md Co-authored-by: Carlos Zoido --- examples/extensions/plugins/openssl_sign/readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/extensions/plugins/openssl_sign/readme.md b/examples/extensions/plugins/openssl_sign/readme.md index c8606bc5..3fea14e9 100644 --- a/examples/extensions/plugins/openssl_sign/readme.md +++ b/examples/extensions/plugins/openssl_sign/readme.md @@ -4,7 +4,7 @@ Steps to test the example: - 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 the ``sign.py`` file) and place them inside a folder with the name of your provider (``my-organization`` in the example) next to the ``sign.py`` file (``CONAN_HOME/extensions/sign/my-organization/``). +- Generate your signing keys (see comment at the top of the ``sign.py`` file) and place them inside a folder with the name of your provider (``my-organization`` in the example) next to the ``sign.py`` file (``CONAN_HOME/extensions/plugins/sign/my-organization/``). - 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`` From fe98df4f60394cf808435b242f2a486db7e110ca Mon Sep 17 00:00:00 2001 From: Daniel Date: Tue, 10 Feb 2026 17:46:05 +0100 Subject: [PATCH 29/39] Update examples/extensions/plugins/openssl_sign/sign.py Co-authored-by: Carlos Zoido --- examples/extensions/plugins/openssl_sign/sign.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/extensions/plugins/openssl_sign/sign.py b/examples/extensions/plugins/openssl_sign/sign.py index f8392404..c1638c9e 100644 --- a/examples/extensions/plugins/openssl_sign/sign.py +++ b/examples/extensions/plugins/openssl_sign/sign.py @@ -12,7 +12,7 @@ $ openssl pkey -in private_key.pem -pubout -out public_key.pem -The private_key.pem and public_key.pem files should be placed inside a folder named withe the provider's name +The private_key.pem and public_key.pem files should be placed inside a folder named with the the provider's name ('my-organization' for this example). The 'my-organization' folder should be next to this plugins's file sign.py (inside the CONAN_HOME/extensions/plugins/sing folder). """ From 2331950cd9a88f3a2c0fe8b5c8fe1f5a0bbd5419 Mon Sep 17 00:00:00 2001 From: Daniel Date: Tue, 10 Feb 2026 17:46:17 +0100 Subject: [PATCH 30/39] Update examples/extensions/plugins/openssl_sign/sign.py Co-authored-by: Carlos Zoido --- examples/extensions/plugins/openssl_sign/sign.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/extensions/plugins/openssl_sign/sign.py b/examples/extensions/plugins/openssl_sign/sign.py index c1638c9e..15104d87 100644 --- a/examples/extensions/plugins/openssl_sign/sign.py +++ b/examples/extensions/plugins/openssl_sign/sign.py @@ -14,7 +14,7 @@ The private_key.pem and public_key.pem files should be placed inside a folder named with the the provider's name ('my-organization' for this example). The 'my-organization' folder should be next to this plugins's file sign.py -(inside the CONAN_HOME/extensions/plugins/sing folder). +(inside the CONAN_HOME/extensions/plugins/sign folder). """ import os From 566bcfd03fdb1a6e14eae033f49d3e25b9e83f53 Mon Sep 17 00:00:00 2001 From: Daniel Date: Tue, 10 Feb 2026 17:46:44 +0100 Subject: [PATCH 31/39] Update examples/extensions/plugins/openssl_sign/sign.py Co-authored-by: Carlos Zoido --- examples/extensions/plugins/openssl_sign/sign.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/extensions/plugins/openssl_sign/sign.py b/examples/extensions/plugins/openssl_sign/sign.py index 15104d87..530c4aed 100644 --- a/examples/extensions/plugins/openssl_sign/sign.py +++ b/examples/extensions/plugins/openssl_sign/sign.py @@ -63,7 +63,7 @@ def sign(ref, artifacts_folder, signature_folder, **kwargs): _run_command(openssl_sign_cmd) ConanOutput().success(f"Package signed for reference {ref}") except Exception as exc: - raise ConanException(f"Error signing artifact") + raise ConanException(f"Error signing artifact: {exc}") return [{"method": "openssl-dgst", "provider": provider, "sign_artifacts": { From 6a225807fe273d2de353b7da32c5b828a5bb26a1 Mon Sep 17 00:00:00 2001 From: Daniel Date: Tue, 10 Feb 2026 17:47:01 +0100 Subject: [PATCH 32/39] Update examples/extensions/plugins/openssl_sign/sign.py Co-authored-by: Carlos Zoido --- examples/extensions/plugins/openssl_sign/sign.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/extensions/plugins/openssl_sign/sign.py b/examples/extensions/plugins/openssl_sign/sign.py index 530c4aed..8df2c4e6 100644 --- a/examples/extensions/plugins/openssl_sign/sign.py +++ b/examples/extensions/plugins/openssl_sign/sign.py @@ -13,7 +13,7 @@ $ openssl pkey -in private_key.pem -pubout -out public_key.pem The private_key.pem and public_key.pem files should be placed inside a folder named with the the provider's name -('my-organization' for this example). The 'my-organization' folder should be next to this plugins's file sign.py +('my-organization' for this example). The 'my-organization' folder should be next to this plugins' file sign.py (inside the CONAN_HOME/extensions/plugins/sign folder). """ From ae2b1edaa8ccdbf78efe91238657b72e53383366 Mon Sep 17 00:00:00 2001 From: danimtb Date: Wed, 11 Feb 2026 08:05:38 +0100 Subject: [PATCH 33/39] review --- examples/extensions/README.md | 6 +- .../extensions/plugins/openssl_sign/sign.py | 71 ++++++++++--------- 2 files changed, 40 insertions(+), 37 deletions(-) diff --git a/examples/extensions/README.md b/examples/extensions/README.md index 8120cdc1..42e55859 100644 --- a/examples/extensions/README.md +++ b/examples/extensions/README.md @@ -1,13 +1,13 @@ # Conan extensions examples -### [Use custom commands in your Conan CLI](extensions/commands/) +### [Use custom commands in your Conan CLI](commands) - Learn how to create custom commands in Conan. [Docs](https://docs.conan.io/2/reference/commands/custom_commands.html) -### [Use custom deployers](extensions/deployers/) +### [Use custom deployers](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/openssl_sign) +### [Package signing plugin example with OpenSSL](plugins/openssl_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/openssl_sign/sign.py b/examples/extensions/plugins/openssl_sign/sign.py index 8df2c4e6..f14dc300 100644 --- a/examples/extensions/plugins/openssl_sign/sign.py +++ b/examples/extensions/plugins/openssl_sign/sign.py @@ -4,7 +4,7 @@ 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: +To use this plugin, first generate a compatible keypair: $ openssl genpkey -algorithm RSA -out private_key.pem -pkeyopt rsa_keygen_bits:2048 @@ -73,36 +73,39 @@ def sign(ref, artifacts_folder, signature_folder, **kwargs): def verify(ref, artifacts_folder, signature_folder, files, **kwargs): signatures = os.path.join(signature_folder, "pkgsign-signatures.json") - signature = json.loads(open(signatures).read()).get("signatures")[0] - signature_filename = signature.get("sign_artifacts").get("signature") - signature_filepath = os.path.join(signature_folder, signature_filename) - if not os.path.isfile(signature_filepath): - raise ConanException("Signature file does not exist") - - # The provider is useful to choose the correct public key to verify packages with - expected_provider = "my-organization" - signature_provider = signature.get("provider") - if signature_provider != expected_provider: - raise ConanException(f"The provider does not match ({expected_provider} [expected] != {signature_provider} " - "[actual]). Cannot get a public key to verify the package") - pubkey_filepath = os.path.join(os.path.dirname(__file__), expected_provider, "public_key.pem") - - manifest_filepath =os.path.join(signature_folder, "pkgsign-manifest.json") - signature_method = signature.get("method") - if signature_method == "openssl-dgst": - # openssl dgst -sha256 -verify public_key.pem -signature document.sig document.txt - openssl_verify_cmd = [ - "openssl", - "dgst", - "-sha256", - "-verify", pubkey_filepath, - "-signature", signature_filepath, - manifest_filepath, - ] - try: - _run_command(openssl_verify_cmd) - ConanOutput().success(f"Package verified for reference {ref}") - except Exception as exc: - raise ConanException(f"Error verifying signature {signature_filepath}: {exc}") - else: - raise ConanException(f"Sign method {signature_method} not supported. Cannot verify package") + try: + signatures = json.loads(open(signatures).read()).get("signatures") + except Exception: + raise ConanException("Could not verify unsigned package") + + for signature in signatures: + signature_filename = signature.get("sign_artifacts").get("signature") + signature_filepath = os.path.join(signature_folder, signature_filename) + if not os.path.isfile(signature_filepath): + raise ConanException(f"Signature file does not exist at {signature_filepath}") + + # The provider is useful to choose the correct public key to verify packages with + provider = signature.get("provider") + pubkey_filepath = os.path.join(os.path.dirname(__file__), provider, "public_key.pem") + if not os.path.isfile(pubkey_filepath): + raise ConanException(f"Public key not found for provider '{provider}'") + + manifest_filepath =os.path.join(signature_folder, "pkgsign-manifest.json") + signature_method = signature.get("method") + if signature_method == "openssl-dgst": + # openssl dgst -sha256 -verify public_key.pem -signature document.sig document.txt + openssl_verify_cmd = [ + "openssl", + "dgst", + "-sha256", + "-verify", pubkey_filepath, + "-signature", signature_filepath, + manifest_filepath, + ] + try: + _run_command(openssl_verify_cmd) + ConanOutput().success(f"Package verified for reference {ref}") + except Exception as exc: + raise ConanException(f"Error verifying signature {signature_filepath}: {exc}") + else: + raise ConanException(f"Sign method {signature_method} not supported. Cannot verify package") From d201e501189c4ca6f486b474681d977c3988089c Mon Sep 17 00:00:00 2001 From: danimtb Date: Wed, 11 Feb 2026 10:27:55 +0100 Subject: [PATCH 34/39] use warning --- examples/extensions/plugins/openssl_sign/sign.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/extensions/plugins/openssl_sign/sign.py b/examples/extensions/plugins/openssl_sign/sign.py index f14dc300..61220807 100644 --- a/examples/extensions/plugins/openssl_sign/sign.py +++ b/examples/extensions/plugins/openssl_sign/sign.py @@ -76,7 +76,8 @@ def verify(ref, artifacts_folder, signature_folder, files, **kwargs): try: signatures = json.loads(open(signatures).read()).get("signatures") except Exception: - raise ConanException("Could not verify unsigned package") + ConanOutput().warning("Could not verify unsigned package") + return for signature in signatures: signature_filename = signature.get("sign_artifacts").get("signature") From 3c67c3953079f8279d1af684b5e7fb40ea26c1e7 Mon Sep 17 00:00:00 2001 From: danimtb Date: Wed, 11 Feb 2026 12:32:35 +0100 Subject: [PATCH 35/39] fixes --- examples/extensions/plugins/openssl_sign/sign.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/examples/extensions/plugins/openssl_sign/sign.py b/examples/extensions/plugins/openssl_sign/sign.py index 61220807..be5a8323 100644 --- a/examples/extensions/plugins/openssl_sign/sign.py +++ b/examples/extensions/plugins/openssl_sign/sign.py @@ -72,9 +72,10 @@ def sign(ref, artifacts_folder, signature_folder, **kwargs): def verify(ref, artifacts_folder, signature_folder, files, **kwargs): - signatures = os.path.join(signature_folder, "pkgsign-signatures.json") + signatures_path = os.path.join(signature_folder, "pkgsign-signatures.json") try: - signatures = json.loads(open(signatures).read()).get("signatures") + with open(signatures_path, "r", encoding="utf-8") as f: + signatures = json.loads(f.read()).get("signatures") except Exception: ConanOutput().warning("Could not verify unsigned package") return @@ -91,7 +92,7 @@ def verify(ref, artifacts_folder, signature_folder, files, **kwargs): if not os.path.isfile(pubkey_filepath): raise ConanException(f"Public key not found for provider '{provider}'") - manifest_filepath =os.path.join(signature_folder, "pkgsign-manifest.json") + manifest_filepath = os.path.join(signature_folder, "pkgsign-manifest.json") signature_method = signature.get("method") if signature_method == "openssl-dgst": # openssl dgst -sha256 -verify public_key.pem -signature document.sig document.txt From 2eb1bf975a1661570d3708f90ee2776c847f0cec Mon Sep 17 00:00:00 2001 From: Carlos Zoido Date: Wed, 11 Feb 2026 13:11:49 +0100 Subject: [PATCH 36/39] Rename readme.md to README.md --- examples/extensions/plugins/openssl_sign/{readme.md => README.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename examples/extensions/plugins/openssl_sign/{readme.md => README.md} (100%) diff --git a/examples/extensions/plugins/openssl_sign/readme.md b/examples/extensions/plugins/openssl_sign/README.md similarity index 100% rename from examples/extensions/plugins/openssl_sign/readme.md rename to examples/extensions/plugins/openssl_sign/README.md From 61d610fea230c106336d38ffa497118dddbd06af Mon Sep 17 00:00:00 2001 From: danimtb Date: Wed, 11 Feb 2026 14:23:56 +0100 Subject: [PATCH 37/39] add note to code and readme --- examples/extensions/plugins/openssl_sign/README.md | 5 +++++ examples/extensions/plugins/openssl_sign/sign.py | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/examples/extensions/plugins/openssl_sign/README.md b/examples/extensions/plugins/openssl_sign/README.md index 3fea14e9..50bbfc03 100644 --- a/examples/extensions/plugins/openssl_sign/README.md +++ b/examples/extensions/plugins/openssl_sign/README.md @@ -1,6 +1,11 @@ ## Package signing plugin example with Openssl +> **_SECURITY NOTE:_** This example stores a private key next to the plugin for simplicity. **Do not do this in production**. +> Instead, load the signing key from environment variables or a secret manager, or delegate signing to a remote signing service. +> **Always keep the private key out of the Conan cache and out of source control**. + + Steps to test the example: - Copy the ``sign.py`` file to your Conan home at ```CONAN_HOME/extensions/plugins/sign/sign.py```. diff --git a/examples/extensions/plugins/openssl_sign/sign.py b/examples/extensions/plugins/openssl_sign/sign.py index be5a8323..40907aa6 100644 --- a/examples/extensions/plugins/openssl_sign/sign.py +++ b/examples/extensions/plugins/openssl_sign/sign.py @@ -15,6 +15,11 @@ The private_key.pem and public_key.pem files should be placed inside a folder named with the the provider's name ('my-organization' for this example). The 'my-organization' folder should be next to this plugins' file sign.py (inside the CONAN_HOME/extensions/plugins/sign folder). + +SECURITY NOTE: + This example stores a private key next to the plugin for simplicity. **Do not do this in production**. + Instead, load the signing key from environment variables or a secret manager, or delegate signing to a remote signing service. + **Always keep the private key out of the Conan cache and out of source control**. """ import os From 994228b2aeb892f8b4e9686adcc0c7c89412e2b7 Mon Sep 17 00:00:00 2001 From: Daniel Date: Wed, 18 Feb 2026 16:33:14 +0100 Subject: [PATCH 38/39] Update examples/extensions/plugins/openssl_sign/README.md Co-authored-by: Carlos Zoido --- examples/extensions/plugins/openssl_sign/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/extensions/plugins/openssl_sign/README.md b/examples/extensions/plugins/openssl_sign/README.md index 50bbfc03..dc7b0ecb 100644 --- a/examples/extensions/plugins/openssl_sign/README.md +++ b/examples/extensions/plugins/openssl_sign/README.md @@ -1,5 +1,5 @@ -## Package signing plugin example with Openssl +## Package signing plugin example with OpenSSL > **_SECURITY NOTE:_** This example stores a private key next to the plugin for simplicity. **Do not do this in production**. > Instead, load the signing key from environment variables or a secret manager, or delegate signing to a remote signing service. From 9b81ea3967bf0ae0dc541eb4a00aec21516cecae Mon Sep 17 00:00:00 2001 From: Daniel Date: Wed, 18 Feb 2026 17:26:12 +0100 Subject: [PATCH 39/39] Apply suggestion from @czoido Co-authored-by: Carlos Zoido --- examples/extensions/plugins/openssl_sign/sign.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/examples/extensions/plugins/openssl_sign/sign.py b/examples/extensions/plugins/openssl_sign/sign.py index 40907aa6..0f090483 100644 --- a/examples/extensions/plugins/openssl_sign/sign.py +++ b/examples/extensions/plugins/openssl_sign/sign.py @@ -1,8 +1,7 @@ """ Plugin to sign/verify Conan packages with OpenSSL. -Requirements: The following executables should be installed and in the PATH. - - openssl +You will need to have ``openssl`` installed at the system level and available in your ``PATH``. To use this plugin, first generate a compatible keypair: