# DSO Signing — fedem::sign
The `sign/` module provides Ed25519-based signing and verification for
Dynamic Shared Objects (`.so`, `.ffs`, or any ELF shared library).
## Overview
A signed DSO carries two extra ELF sections embedded at signing time:
| Section | Content |
|---|---|
| `.dso_manifest` | UTF-8 JSON metadata (name, version, author, SHA-256, key fingerprint, …) |
| `.dso_sig` | 64-byte raw Ed25519 signature over the manifest JSON |
The SHA-256 in the manifest is computed over the DSO file *before* the
sections are added, so the hash remains stable across re-signing.
---
## CLI tools
### dso-keygen — generate a developer key pair
```bash
dso-keygen --name "Alice" --email "alice@example.com" \
--out-dir keys/ --key-id alice
# Output:
# keys/alice.key private key (chmod 400)
# keys/alice.pub public key
# keys/alice.fingerprint SHA-256 of DER public key
```
### dso-sign — sign a DSO
```bash
dso-sign addon.so --key keys/alice.key
dso-sign addon.so --key keys/alice.key --name my-addon --version 1.2.0
```
The `.so` file is modified in-place. Run after every rebuild.
Re-signing is idempotent: existing sections are stripped before new ones
are added.
### dso-verify — verify a signed DSO
```bash
dso-verify addon.so --keys /etc/myapp/trusted-keys.d/
# Exit codes:
# 0 TRUSTED — valid signature, key in trusted-keys directory
# 1 UNKNOWN — valid signature, key NOT in trusted-keys directory
# 2 UNSIGNED — no manifest/signature sections
# 3 REJECTED — signature invalid or SHA-256 mismatch
# 4 ERROR — I/O or format error
```
---
## C++ API
### Loading a DSO with signature check
```cpp
#include <dso/DSOLoader.hh>
#include <sign/TrustLevel.hh>
// Option 1: Load and get trust level — caller decides what to do
auto result = fedem::dso::DSOLoader::loadSigned(
"addon.so",
{ "/usr/lib/myapp/addons", "/opt/myapp/addons" },
"/etc/myapp/trusted-keys.d" );
if( !result.loaded )
throw std::runtime_error( result.detail );
switch( result.trust ) {
case fedem::sign::TrustLevel::TRUSTED:
log( "addon loaded: TRUSTED" );
break;
case fedem::sign::TrustLevel::UNKNOWN:
log( "addon loaded: UNKNOWN key — warn user" );
break;
case fedem::sign::TrustLevel::UNSIGNED:
log( "addon loaded: no signature" );
break;
case fedem::sign::TrustLevel::REJECTED:
// loadSigned() still loaded it — caller must unload or abort
throw std::runtime_error( "signature rejected: " + result.detail );
}
// Option 2: Load only if TRUSTED — throws SignatureRejected otherwise
fedem::dso::DSOLoader::loadVerified(
"addon.so",
{ "/usr/lib/myapp/addons" },
"/etc/myapp/trusted-keys.d",
fedem::sign::TrustLevel::TRUSTED );
// Option 3: Original API — no signature check at all
fedem::dso::DSOLoader::load( "addon.so", { "/usr/lib/myapp/addons" } );
```
### Verifying without loading
```cpp
#include <sign/Verifier.hh>
auto vr = fedem::sign::Verifier::verify(
"/path/to/addon.so",
"/etc/myapp/trusted-keys.d" );
std::cout << fedem::sign::trustLevelName( vr.trust ) << "\n";
std::cout << vr.detail << "\n";
if( vr.hasManifest )
std::cout << "signed by: " << vr.data.author << "\n";
```
### Per-process cache
`Verifier::verify()` caches results by `soPath`. Because `dlopen()` holds
a reference to the inode, the on-disk file can be replaced without
affecting the running process. The trust level of a loaded DSO cannot
change within one process lifetime — one verification per path is both
correct and efficient.
Use `Verifier::isCached(soPath)` to detect whether a result is already
available (useful for suppressing redundant log messages).
---
## trusted-keys directory layout
```
/etc/myapp/trusted-keys.d/
alice.pub ← PEM Ed25519 public key
bob.pub
```
Any file with extension `.pub` in the directory is scanned.
Files that cannot be parsed as PEM public keys are silently skipped.
---
## Trust model
| Level | Meaning | Recommended action |
|---|---|---|
| `TRUSTED` | Valid signature; signer key in trusted-keys directory | Load |
| `UNKNOWN` | Valid signature; signer key NOT in trusted-keys directory | Warn + load, or reject |
| `UNSIGNED` | No manifest sections | Load (dev mode) or reject (production) |
| `REJECTED` | Signature invalid or SHA-256 mismatch | Always reject |