Skip to main content
Cloacina Documentation
Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode Back to homepage

Package Signing

Sign and Verify Workflow Packages

Cloacina supports cryptographic (Ed25519) signing of workflow packages so you can detect tampering and verify authenticity. This guide covers the library-side tasks: enabling verification, generating and trusting keys, signing, verifying, and rotating keys.

No CLI signing path yet
There is currently no end-to-end CLI flow for signing .cloacina workflow packages: cloacinactl package pack --sign (and package publish --sign) fail hard with an error — the flag is tracked under I-0103 and not implemented. What exists today is the library-side signing/verification API documented on this page, plus server-side enforcement (--require-signatures, which will reject uploads you have no CLI means to sign). Plan deployments accordingly.

Enable signature verification

To require verification, configure SecurityConfig:

use cloacina::security::SecurityConfig;

let config = SecurityConfig {
    require_signatures: true,
    // 32-byte master key; only needed when SIGNING with database-stored
    // keys — verification-only loading doesn't require it.
    key_encryption_key: Some(master_key_bytes),
    // The org whose trusted keys uploads are verified against. With
    // require_signatures = true and no org set, verification fails safe
    // (all uploads rejected).
    verification_org_id: Some(org_id),
};

When require_signatures is true, unsigned packages and packages signed by an untrusted or tampered key fail to load.

The key_encryption_key is a 32-byte key used to encrypt private signing keys at rest in the database. Store it in a secrets manager and provide it at runtime.

Generate and trust a signing key

Generate a signing key for your organization, then trust its public key so packages signed with it will verify:

use cloacina::security::{DbKeyManager, KeyManager};

let key_manager = DbKeyManager::new(dal);

// Generate a signing key (private key encrypted under master_key)
let key_info = key_manager
    .create_signing_key(org_id, "release-key-v1", &master_key)
    .await?;

// Export the public key for distribution
let export = key_manager.export_public_key(key_info.id).await?;
println!("{}", export.public_key_pem);

// Trust the public key (from raw bytes or PEM)
key_manager
    .trust_public_key(org_id, &key_info.public_key, Some("Release Key"))
    .await?;

To let a parent organization trust everything a child organization trusts, grant trust between them:

key_manager.grant_trust(parent_org_id, child_org_id).await?;

Sign a package

use cloacina::security::{DbPackageSigner, PackageSigner};

let signer = DbPackageSigner::new(dal);

let signature = signer
    .sign_package_with_db_key(&package_path, key_id, &master_key, true)
    .await?;

To distribute a detached .sig sidecar alongside the package:

use cloacina::security::DetachedSignature;

let detached = DetachedSignature::from_signature_info(&signature);
detached.write_to_file("my-package.so.sig")?;

Verify a package

With the database available, verify against a stored signature (or an adjacent .sig file) using the organization’s trusted keys:

use cloacina::security::{verify_package, SignatureSource};

let result = verify_package(
    &package_path,
    org_id,
    SignatureSource::Auto, // try .sig file, then database
    &package_signer,
    &key_manager,
)
.await?;

When only the public key and a detached signature are available (no database), verify offline:

use cloacina::security::verify_package_offline;

let result = verify_package_offline(&package_path, &signature_path, &public_key_bytes)?;

Rotate signing keys

  1. Generate a new key:

    let new_key = key_manager.create_signing_key(org_id, "release-v2", &master_key).await?;
    
  2. Trust the new key:

    key_manager.trust_public_key(org_id, &new_key.public_key, Some("release-v2")).await?;
    
  3. Update CI to sign new packages with the new key.

  4. During the transition, both old and new signatures verify (both keys are trusted).

  5. After the transition, revoke the old trusted key:

    key_manager.revoke_trusted_key(old_trusted_key_id).await?;
    
  6. Optionally, revoke the old signing key to prevent new signatures:

    key_manager.revoke_signing_key(old_signing_key_id).await?;
    

See Also