"Load every .so on the path" means anything that can drop a file in that directory can run code in your process. plugin-sign narrows that: an add-on carries an Ed25519 signature, and the host checks it against a set of trusted public keys before — or instead of — trusting the code.

This half of the toolkit needs OpenSSL. Build without it and DSOLoader still has plain load(); the signed overloads and the tools are simply absent.

The workflow

Once, per developer — make a key pair:

dso-keygen --name "Alice" --email "alice@example.com" --key-id alice --out-dir keys/
#   keys/alice.key          private (chmod 400)
#   keys/alice.pub          public
#   keys/alice.fingerprint  SHA-256 of the DER public key

Per build — sign the DSO:

dso-sign libCircle.so --key keys/alice.key --name circle-plugin --version 1.2.0

dso-sign computes the SHA-256 of the file as built, writes a JSON manifest (name, version, author, that hash, the signer's key fingerprint, an ABI version, a timestamp), Ed25519-signs the manifest, and embeds both as ELF sections (.dso_manifest, .dso_sig) with objcopy. Re-signing is idempotent — old sections are stripped first, and the hash is over the pre-section bytes so it stays stable.

Once, per deployment — trust the key on the host:

cp keys/alice.pub /etc/app/trusted-keys.d/

Any *.pub in that directory is a trusted signer.

Checking at load time

Two overloads, both requiring the plugin-sign build:

#include <dso/DSOLoader.hh>
#include <sign/TrustLevel.hh>
using namespace fedem;

// (a) Load, then report — the loader never blocks, the caller decides
auto r = dso::DSOLoader::loadSigned( "libCircle.so",
                                     { "/opt/app/plugins" },
                                     "/etc/app/trusted-keys.d" );
if( !r.loaded ) throw std::runtime_error( r.detail );
switch( r.trust ) {
    case sign::TrustLevel::TRUSTED:  break;                       // ok
    case sign::TrustLevel::UNKNOWN:  warn( "unrecognised signer" ); break;
    case sign::TrustLevel::UNSIGNED: warn( "unsigned add-on" );     break;
    case sign::TrustLevel::REJECTED: unload_and_abort( r.detail );  break;
}

// (b) Load only above a trust floor — throws SignatureRejected otherwise
dso::DSOLoader::loadVerified( "libCircle.so",
                              { "/opt/app/plugins" },
                              "/etc/app/trusted-keys.d",
                              sign::TrustLevel::TRUSTED );

loadSigned is for a host that wants to log or warn but still run. loadVerified is for "no unsigned code, full stop" — pick the minTrust (TRUSTED in production, maybe UNKNOWN in staging) and a failure is an exception, not a branch.

Trust levels

LevelMeaningTypical action
TRUSTEDvalid signature, signer key in the trusted-keys dirload
UNKNOWNvalid signature, signer key not in the dirwarn, or reject in prod
UNSIGNEDno manifest / signature sectionsdev: load; prod: reject
REJECTEDsignature invalid, or file hash ≠ manifest hashalways reject

REJECTED specifically means the file was tampered with after signing, or signed by a key that does not match the manifest — treat it as hostile.

The per-process cache

Verifier::verify() caches its result by path. dlopen pins the inode, so the on-disk file can change without affecting the running process — one verification per path is both correct and cheap. Verifier::isCached(path) lets a host suppress repeat log lines.

String-policy helper

If your host reads a policy from config as a string:

bool ok = fedem::sign::Verifier::shouldLoad( policy, result.trust );
//   "off"    -> always true
//   "warn"   -> true unless REJECTED
//   "strict" -> true only for TRUSTED

Full flag-by-flag detail for the tools is in the Tools section. Next: Packaging Plugins.