Signing packages with Sigstore (Cosign)ΒΆ
This is an example of a package signing plugin implementation using Sigstore via
Cosign.
You need Cosign (version 3.0.0 or newer) on your PATH.
See the Cosign releases page for binaries.
Warning
This feature is experimental and subject to breaking changes. See the Conan stability section for more information.
This example is available in the examples2 repository: examples/extensions/plugins/sigstore_sign.
Note
Cosign is used here for demonstration only. The package signing plugin mechanism is backend-agnostic; you could implement
a similar plugin with other tools (for example OpenSSL or GPG) by changing the commands invoked from sign() and verify(),
as described in Package signing.
Generating the signing key pairΒΆ
Generate a Cosign key pair (Cosign prompts for a passphrase to protect the private key):
$ cosign generate-key-pair --output-key-prefix signing
This creates signing.key (private) and signing.pub (public).
Use the passphrase later to set the COSIGN_PASSWORD environment variable when configuring the plugin (see below).
Configuring the pluginΒΆ
Caution
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.
Copy
sign.pyandsigning-config.jsonfrom the examples2 folder into your Conan home:CONAN_HOME/extensions/plugins/sign/sign.pyCONAN_HOME/extensions/plugins/sign/signing-config.jsonPlace the generated keys in a folder named after the provider used by the plugin. This example uses
my-organization(the name is hardcoded insign.py):CONAN_HOME/extensions/plugins/sign/my-organization/signing.keyCONAN_HOME/extensions/plugins/sign/my-organization/signing.pubSet the
COSIGN_PASSWORDenvironment variable. The plugin requires this variable to be present when signing: Cosign reads it instead of prompting on the terminal. Set it to the private key passphrase you chose when generating the key pair. If the key has no passphrase, setCOSIGN_PASSWORDto an empty value.
Your layout should look like this:
CONAN_HOME/
βββ extensions/
βββ plugins/
βββ sign/
βββ sign.py
βββ signing-config.json
βββ my-organization/
βββ signing.key
βββ signing.pub
Tip
The package signing plugin lives under the Conan configuration directory, so you can distribute it with conan config install (for example from a fork or internal repo that contains the same files).
ImplementationΒΆ
Note
Method name convention: Use the literal string sigstore (lowercase) in the method field when your plugin uses this
Cosign/Sigstore tools. This is a convenient way to identify the signing method used to sign the package and so the verifier
can pick the right backend.
For signing, sign() invokes cosign sign-blob on Conanβs pkgsign-manifest.json, writes a Sigstore bundle
(artifact.sigstore.json) next to the manifest, and returns metadata for pkgsign-signatures.json:
def sign(ref, artifacts_folder, signature_folder, **kwargs):
...
cosign_sign_cmd = [
"cosign",
"sign-blob",
"--key",
privkey_filepath,
"--bundle",
bundle_filepath,
"-y",
f"--signing-config={_signing_config_path()}",
manifest_filepath,
]
try:
_run_command(cosign_sign_cmd)
ConanOutput().success(f"Package signed for reference {ref}")
except Exception as exc:
raise ConanException(f"Error signing artifact: {exc}") from exc
return [
{
"method": "sigstore",
"provider": provider,
"sign_artifacts": {
"manifest": "pkgsign-manifest.json",
"bundle": "artifact.sigstore.json",
},
}
]
For verification, verify() reads pkgsign-signatures.json, resolves the manifest and bundle paths, loads the public key for
the recorded provider, and runs cosign verify-blob (without Rekor support):
def verify(ref, artifacts_folder, signature_folder, files, **kwargs):
...
cosign_verify_cmd = [
"cosign",
"verify-blob",
"--key",
pubkey_filepath,
"--bundle",
bundle_filepath,
"--private-infrastructure=true",
manifest_filepath,
]
try:
_run_command(cosign_verify_cmd)
ConanOutput().success(f"Package verified for reference {ref}")
except Exception as exc:
raise ConanException(f"Error verifying signature {bundle_filepath}: {exc}") from exc
If verification fails, the plugin raises ConanException. On success it does not return a value.
You can read more about pkgsign-manifest.json at Package signing.
Signing packagesΒΆ
Create a package and sign it:
$ conan new cmake_lib -d name=hello -d version=1.0
$ conan create
$ conan cache sign hello/1.0
hello/1.0: Compressing conan_sources.tgz
hello/1.0:dee9f7f985eb1c20e3c41afaa8c35e2a34b5ae0b: Compressing conan_package.tgz
Running command: cosign sign-blob --key .../sign/my-organization/signing.key --bundle .../metadata/sign/artifact.sigstore.json -y --signing-config=.../sign/signing-config.json .../metadata/sign/pkgsign-manifest.json
Package signed for reference hello/1.0
...
[Package sign] Summary: OK=2, FAILED=0
Note
Starting with Conan 2.26.0, conan upload does not sign packages automatically. Use conan cache sign before upload when remotes should store signatures. See Package signing.
Verifying packagesΒΆ
Verify recipe and package binaries in the cache:
$ conan cache verify hello/1.0
[Package sign] Checksum verified for file conan_sources.tgz (...)
...
Running command: cosign verify-blob --key .../sign/my-organization/signing.pub --bundle .../metadata/sign/artifact.sigstore.json --private-infrastructure=true .../metadata/sign/pkgsign-manifest.json
Package verified for reference hello/1.0
...
[Package sign] Summary: OK=2, FAILED=0
Packages downloaded from a remote are verified on install (for example conan install).
See also
Plugin API and manifest details: Package signing.