Two sections

A signed DSO carries two extra ELF sections, both noload,readonly:

SectionBytesContent
.dso_manifestvariablecanonical UTF-8 JSON (schema below)
.dso_sig64raw Ed25519 signature over the .dso_manifest bytes

dso-sign adds them with objcopy; the host reads them with an mmap and a manual section-table walk (fedem::sign::Manifest::read) — no libelf dependency.

Manifest schema

{
  "name":        "circle-plugin",
  "version":     "1.2.0",
  "author":      "Alice <alice@example.com>",
  "sha256":      "<hex — SHA-256 of the DSO BEFORE the sections were added>",
  "publicKeyId": "<hex — SHA-256 of the signer's DER-encoded public key>",
  "abiMajor":    1,
  "abiMinor":    0,
  "timestamp":   "2026-09-08T12:00:00Z"
}

Key order is fixed (Manifest::toJson) so the signed bytes are reproducible.

The stable-hash trick

sha256 is computed over the DSO before the two sections exist. To verify, Verifier:

  1. strips .dso_manifest + .dso_sig to a temp file,
  2. hashes that temp file,
  3. compares with manifest.sha256.

So re-signing (which strips and re-adds the sections) does not change the hash, and a tampered code section does — mismatch is REJECTED.

Trusted-keys directory

A plain directory of PEM Ed25519 public keys:

/etc/app/trusted-keys.d/
    alice.pub
    build-server.pub

Any *.pub is a trusted signer; the file name is irrelevant (the match is by fingerprint). Unparseable files are skipped silently. Add a key = copy a file; revoke = delete it.

Trust levels

TRUSTED (valid sig + key known) · UNKNOWN (valid sig, key not in the dir) · UNSIGNED (no sections) · REJECTED (bad sig or hash mismatch). The library enforces nothing — see TrustLevel for the policy table.

What this model addresses

  • Tampering after signing — any change to the code section fails the hash check (REJECTED).
  • Unauthorised add-ons — an add-on not signed by a key you placed in the directory is UNKNOWN (or UNSIGNED), which a strict host refuses.
  • Provenance — the manifest records who signed it and when.

What it does not address

  • Revocation freshness — it is a directory, not a CRL/OCSP. Removing a key only affects verifications after that point (and cached results persist for the process lifetime).
  • Supply-chain before the signature — it attests that this signer vouched for these bytes, not that the bytes are safe.
  • Downgrade / rollback — nothing stops loading an older signed version; the timestamp and version are informational.
  • Non-ELF platforms — the section embedding is ELF-only.
  • Confidentiality — nothing is encrypted; the manifest is readable with readelf -p .dso_manifest.

It is a pragmatic "did someone I trust build this, and has it changed since" check, sized for an application loading its own plugins — not a general code-signing PKI.