Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
06ca302
Add package signing plugin example
danimtb Nov 21, 2025
e7313ee
Update examples/extensions/plugins/sign/readme.md
danimtb Nov 21, 2025
83b4499
update
danimtb Nov 21, 2025
47dfd0b
Merge branch 'danimtb/extension-plugin-pkgsign' of github.com:conan-i…
danimtb Nov 21, 2025
7038381
minor update
danimtb Dec 4, 2025
03e9186
update plugin example
danimtb Dec 16, 2025
68d9727
update
danimtb Feb 4, 2026
0c5eccb
fix mkdirs
danimtb Feb 4, 2026
0dcee4d
fix folders
danimtb Feb 4, 2026
e8da3fa
try
danimtb Feb 4, 2026
343dae2
fix path
danimtb Feb 4, 2026
101b69f
fix output asserts
danimtb Feb 4, 2026
f633799
fix leftovers
danimtb Feb 4, 2026
8985213
strip
danimtb Feb 4, 2026
f0df224
fix
danimtb Feb 4, 2026
d552623
Apply suggestion from @danimtb
danimtb Feb 6, 2026
a3099ee
Apply suggestion from @danimtb
danimtb Feb 6, 2026
61fc931
Apply suggestion from @danimtb
danimtb Feb 6, 2026
f1f933a
Apply suggestion from @danimtb
danimtb Feb 6, 2026
c0a79e9
Apply suggestion from @danimtb
danimtb Feb 6, 2026
af15f5e
Apply suggestion from @danimtb
danimtb Feb 6, 2026
3b85594
Apply suggestion from @danimtb
danimtb Feb 6, 2026
da3dfad
Apply suggestion from @danimtb
danimtb Feb 6, 2026
1977255
Apply suggestion from @danimtb
danimtb Feb 6, 2026
070c627
conan version check in test
danimtb Feb 6, 2026
cd3e0c8
Merge branch 'main' of github.com:conan-io/examples2 into danimtb/ext…
danimtb Feb 6, 2026
0825c06
Merge branch 'danimtb/extension-plugin-pkgsign' of github.com:conan-i…
danimtb Feb 6, 2026
7f0aba7
Apply suggestion from @danimtb
danimtb Feb 6, 2026
b0097da
Apply suggestion from @danimtb
danimtb Feb 6, 2026
21f8998
fix test
danimtb Feb 6, 2026
6cf1083
Update examples/extensions/plugins/openssl_sign/readme.md
danimtb Feb 10, 2026
fe98df4
Update examples/extensions/plugins/openssl_sign/sign.py
danimtb Feb 10, 2026
2331950
Update examples/extensions/plugins/openssl_sign/sign.py
danimtb Feb 10, 2026
566bcfd
Update examples/extensions/plugins/openssl_sign/sign.py
danimtb Feb 10, 2026
6a22580
Update examples/extensions/plugins/openssl_sign/sign.py
danimtb Feb 10, 2026
ae2b1ed
review
danimtb Feb 11, 2026
d201e50
use warning
danimtb Feb 11, 2026
3c67c39
fixes
danimtb Feb 11, 2026
2eb1bf9
Rename readme.md to README.md
czoido Feb 11, 2026
61d610f
add note to code and readme
danimtb Feb 11, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions examples/extensions/README.md
Original file line number Diff line number Diff line change
@@ -1,9 +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](plugins/openssl_sign)

- Learn how to create a package signing plugin in Conan. [Docs](https://docs.conan.io/2/reference/extensions/package_signing.html)
17 changes: 17 additions & 0 deletions examples/extensions/plugins/openssl_sign/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@

## Package signing plugin example with Openssl
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
## 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.
> **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```.
- 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/<keys>``).
- 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.
28 changes: 28 additions & 0 deletions examples/extensions/plugins/openssl_sign/ci_test_example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import os
import shutil

from conan import conan_version
from test.examples_tools import run

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")

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("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

conan_home = run("conan config home").strip()
shutil.rmtree(os.path.join(conan_home, "extensions", "plugins", "sign"))
118 changes: 118 additions & 0 deletions examples/extensions/plugins/openssl_sign/sign.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
"""
Plugin to sign/verify Conan packages with OpenSSL.

Requirements: The following executables should be installed and in the PATH.
- openssl
Comment on lines +4 to +5
Copy link
Contributor

@czoido czoido Feb 18, 2026

Choose a reason for hiding this comment

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

Suggested change
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:

$ 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 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
import json
import subprocess

from conan.api.output import ConanOutput
from conan.errors import ConanException


def _run_command(command):
ConanOutput().info(f"Running command: {' '.join(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):
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)
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__), 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,
manifest_filepath
]
try:
_run_command(openssl_sign_cmd)
ConanOutput().success(f"Package signed for reference {ref}")
except Exception as exc:
raise ConanException(f"Error signing artifact: {exc}")
return [{"method": "openssl-dgst",
"provider": provider,
"sign_artifacts": {
"manifest": "pkgsign-manifest.json",
"signature": signature_filename}}]


def verify(ref, artifacts_folder, signature_folder, files, **kwargs):
signatures_path = os.path.join(signature_folder, "pkgsign-signatures.json")
try:
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

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")