Code View

plugin / plugin-2.2.0.0 / docs / signing.md
Preview
# 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 |