Signing Plugins¶
This tutorial walks through the full plugin signing workflow: generating an Ed25519 (a public-key signature algorithm) keypair, signing a compiled plugin dylib, verifying the signature from the command line, and configuring the host to require valid signatures at load time.
Signing ensures that only plugins produced by a trusted party can be loaded. If a plugin file is tampered with after signing, or if it was signed with an untrusted key, the host will reject it.
Prerequisites¶
- Completed Your First Plugin
- The
fidiusCLI installed (cargo install fidius-cli) - A compiled plugin dylib (e.g.
libcalculator_plugin.dylibfrom the previous tutorials)
What you will learn¶
- Generate an Ed25519 signing keypair
- Sign a plugin dylib
- Verify the signature from the CLI
- Configure the host to require signatures
- Understand what happens with wrong keys, missing signatures, and tampered files
Step 1: Generate a keypair¶
The fidius keygen command generates an Ed25519 keypair and writes two files:
Output:
mykey.secret-- 32-byte Ed25519 secret key. You need it to sign plugins.mykey.public-- 32-byte Ed25519 public key. Distribute this to hosts that need to verify your plugins.
Security note: Treat
mykey.secretlike a password. Store it outside version control, restrict file permissions (chmod 600 mykey.secret), and consider using a secrets manager in CI/CD pipelines. Anyone with access to the secret key can sign plugins that your host will trust.
Step 2: Build the plugin¶
If you haven't already, build the plugin from the Your First Plugin tutorial:
The dylib will be at a path like:
| Platform | Path |
|---|---|
| macOS | target/debug/libcalculator_plugin.dylib |
| Linux | target/debug/libcalculator_plugin.so |
| Windows | target/debug/calculator_plugin.dll |
The examples below use the macOS path. Substitute your platform's extension as needed.
Step 3: Sign the plugin¶
Output:
The signature is written to a detached .sig file next to the dylib. The
naming convention appends .sig to the full filename including extension
(e.g. libcalculator_plugin.dylib.sig).
The signature covers the entire contents of the dylib file. If even a single byte changes after signing, verification will fail.
Step 4: Verify the signature from the CLI¶
Output on success:
The verify command reads the dylib, reads the .sig file from the expected
location, and checks the Ed25519 signature against the provided public key. If
verification fails, it prints Signature INVALID and exits with code 1.
Step 5: Configure the host to require signatures¶
First, add ed25519-dalek to the host's Cargo.toml:
[dependencies]
calculator-interface = { path = "../calculator-interface", features = ["host"] }
fidius-host = { version = "0.1" }
ed25519-dalek = "2"
Then update calculator-host/src/main.rs to load the public key and require
signature verification:
use calculator_interface::{AddInput, CalculatorClient};
use fidius_host::{PluginHandle, PluginHost};
fn main() {
let plugin_dir = std::env::args()
.nth(1)
.expect("usage: calculator-host <plugin-dir>");
// Load the public key (32 bytes).
let key_bytes: [u8; 32] = std::fs::read("mykey.public")
.expect("could not read mykey.public")
.try_into()
.expect("public key must be exactly 32 bytes");
let verifying_key = ed25519_dalek::VerifyingKey::from_bytes(&key_bytes)
.expect("invalid public key");
let host = PluginHost::builder()
.search_path(&plugin_dir)
.require_signature(true)
.trusted_keys(&[verifying_key])
.build()
.expect("failed to build plugin host");
let loaded = host
.load("BasicCalculator")
.expect("failed to load BasicCalculator");
let handle = PluginHandle::from_loaded(loaded);
let client = CalculatorClient::from_handle(handle);
let output = client
.add(&AddInput { a: 3, b: 7 })
.expect("add() failed");
println!("add(3, 7) = {}", output.result);
}
Make sure the host's Cargo.toml enables the interface crate's host
feature so the CalculatorClient type is generated (see
Your First Plugin).
The two builder methods that control signing:
.require_signature(true)-- the host will callverify_signature()on every dylib before loading it. If the.sigfile is missing, loading fails withLoadError::SignatureRequired..trusted_keys(&[verifying_key])-- provides the Ed25519 public keys that the host trusts. The signature must verify against at least one of these keys; otherwise loading fails withLoadError::SignatureInvalid.
Step 6: Build and run¶
Expected output:
Step 7: See what happens when things go wrong¶
Wrong key¶
Generate a second keypair and try to load a plugin signed with the first key while the host trusts only the second key:
Update the host to load otherkey.public instead of mykey.public, rebuild,
and run:
The host returns LoadError::SignatureInvalid. The signature itself is valid
(it was produced with a real key), but it does not match any key in the
trusted_keys list.
Missing signature file¶
Delete the .sig file and try to load:
The host returns LoadError::SignatureRequired. When require_signature(true)
is set, every dylib must have a corresponding .sig file.
Tampered file¶
Sign the plugin, then modify the dylib after signing:
fidius sign --key mykey.secret target/debug/libcalculator_plugin.dylib
# Append a byte to simulate tampering
echo -n "x" >> target/debug/libcalculator_plugin.dylib
cargo run --bin calculator-host -- target/debug/
The host returns LoadError::SignatureInvalid. The signature was valid for the
original file contents, but the dylib has changed since signing.
Re-sign after any legitimate rebuild:
cargo build -p calculator-plugin
fidius sign --key mykey.secret target/debug/libcalculator_plugin.dylib
No signature requirement¶
If you do not call .require_signature(true) on the builder, unsigned plugins
load normally. The default is require_signature: false:
This loads any valid plugin regardless of whether a .sig file exists.
Multiple trusted keys¶
You can trust multiple keys by passing a slice:
let host = PluginHost::builder()
.search_path(&plugin_dir)
.require_signature(true)
.trusted_keys(&[key_a, key_b, key_c])
.build()
.unwrap();
A plugin signed with any one of these keys will load successfully. This is useful for key rotation: add the new key to the trusted set before retiring the old one.
For a complete list of signing-related error cases, see Errors reference.
Next steps¶
- Your First Plugin -- review the basics
- Optional Methods -- evolve interfaces with backward-compatible optional methods