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.

  1. Copy sign.py and signing-config.json from the examples2 folder into your Conan home:

    CONAN_HOME/extensions/plugins/sign/sign.py

    CONAN_HOME/extensions/plugins/sign/signing-config.json

  2. Place the generated keys in a folder named after the provider used by the plugin. This example uses my-organization (the name is hardcoded in sign.py):

    CONAN_HOME/extensions/plugins/sign/my-organization/signing.key

    CONAN_HOME/extensions/plugins/sign/my-organization/signing.pub

  3. Set the COSIGN_PASSWORD environment 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, set COSIGN_PASSWORD to 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.