-
Notifications
You must be signed in to change notification settings - Fork 64
Add package signing plugin example using OpenSSL #207
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
danimtb
wants to merge
40
commits into
main
Choose a base branch
from
danimtb/extension-plugin-pkgsign
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+169
−2
Open
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 e7313ee
Update examples/extensions/plugins/sign/readme.md
danimtb 83b4499
update
danimtb 47dfd0b
Merge branch 'danimtb/extension-plugin-pkgsign' of github.com:conan-i…
danimtb 7038381
minor update
danimtb 03e9186
update plugin example
danimtb 68d9727
update
danimtb 0c5eccb
fix mkdirs
danimtb 0dcee4d
fix folders
danimtb e8da3fa
try
danimtb 343dae2
fix path
danimtb 101b69f
fix output asserts
danimtb f633799
fix leftovers
danimtb 8985213
strip
danimtb f0df224
fix
danimtb d552623
Apply suggestion from @danimtb
danimtb a3099ee
Apply suggestion from @danimtb
danimtb 61fc931
Apply suggestion from @danimtb
danimtb f1f933a
Apply suggestion from @danimtb
danimtb c0a79e9
Apply suggestion from @danimtb
danimtb af15f5e
Apply suggestion from @danimtb
danimtb 3b85594
Apply suggestion from @danimtb
danimtb da3dfad
Apply suggestion from @danimtb
danimtb 1977255
Apply suggestion from @danimtb
danimtb 070c627
conan version check in test
danimtb cd3e0c8
Merge branch 'main' of github.com:conan-io/examples2 into danimtb/ext…
danimtb 0825c06
Merge branch 'danimtb/extension-plugin-pkgsign' of github.com:conan-i…
danimtb 7f0aba7
Apply suggestion from @danimtb
danimtb b0097da
Apply suggestion from @danimtb
danimtb 21f8998
fix test
danimtb 6cf1083
Update examples/extensions/plugins/openssl_sign/readme.md
danimtb fe98df4
Update examples/extensions/plugins/openssl_sign/sign.py
danimtb 2331950
Update examples/extensions/plugins/openssl_sign/sign.py
danimtb 566bcfd
Update examples/extensions/plugins/openssl_sign/sign.py
danimtb 6a22580
Update examples/extensions/plugins/openssl_sign/sign.py
danimtb ae2b1ed
review
danimtb d201e50
use warning
danimtb 3c67c39
fixes
danimtb 2eb1bf9
Rename readme.md to README.md
czoido 61d610f
add note to code and readme
danimtb File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,17 @@ | ||||||
|
|
||||||
| ## Package signing plugin example with Openssl | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
|
||||||
| > **_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
28
examples/extensions/plugins/openssl_sign/ci_test_example.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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")) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||
|
|
||||||||
| 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") | ||||||||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.