Artifact ec2c441f0a0fcef29f43416dff422bebc7fb8dc6a6f6a0dbd4c7bd3d40e8bab2:
- File
docs/site/content/docs/concepts/signing.md
— part of check-in
[93ad669695]
at
2026-10-23 20:53:50
on branch main
— docs: reorganise into get-started, concepts, guides, reference
Move the flat docs pages into sections, keep each old URL as an alias, split sparse cones from horizons, and add concept pages for artifacts, repository state, sync, sub-manifests and signing plus a settings reference. Audit the moved pages against the code and fix drift. (user: [email protected], size: 5922) [annotate] [blame] [check-ins using] [more...]
title: Signing
weight: 80
go-libfossil signs two kinds of artifact: check-ins, as fossil and zit sign them, and the seeds an origin mints for structural horizons. Both use the same settings and the same two envelopes, a PGP clear-signed message or the SSH envelope fossil builds around a detached ssh-keygen signature (draft-fossil-artifact-format-00 §4.1.1).
Settings
| Setting | Constant | Meaning |
|---|---|---|
clearsign |
SettingClearsign |
Sign check-ins and horizon seeds. Off by default. |
omitsign |
Fossil's legacy setting. On, it turns signing off again. | |
pgp-command |
SettingPGPCommand |
The signing command. Unset, it is fossil's default, gpg --clearsign -o. |
horizon-seed-key |
SettingHorizonSeedKey |
The public key a clone verifies its origin's seeds with. |
Settings are read from the repository only. go-libfossil reads no global fossil configuration, so values set with fossil settings --global do not apply.
Signing check-ins
With clearsign on, every check-in a commit or a branch creation stores is signed. The command runs as fossil runs it:
- PGP:
COMMAND OUTFILE INFILE, which writes a clear-signed copy of the manifest. - SSH: a command whose name starts with
sshruns asCOMMAND INFILEand writes a detachedINFILE.sig, which is wrapped in fossil's SSH envelope. The namespace must befossilscm.
libfossil -R repo.fossil repo config set clearsign on
libfossil -R repo.fossil repo config set pgp-command \
"ssh-keygen -q -Y sign -n fossilscm -f ~/.ssh/id_ed25519"
The command runs through the shell, so ~ and environment variables expand. A signed check-in looks like this:
-----BEGIN SSH SIGNED MESSAGE-----
C signed
D 2026-10-23T20:45:21.089
F docs/x.md a09fd4d0...
...
Z 0e57ef44fd0eec16824d0a54ab247953
-----BEGIN SSH SIGNATURE-----
...
-----END SSH SIGNATURE-----
The stored bytes are the whole envelope, so the check-in's name is the hash of the signed bytes (§3.1). Readers strip the envelope before parsing, and the Z card covers the manifest inside it (§6.2).
To store one check-in unsigned, as fossil's --nosign does:
libfossil repo ci --nosignandlibfossil repo branch new --nosign.CommitOpts.NoSign(Repo.Commit) andCheckoutCommitOpts.NoSign(Checkout.Checkin).
What fails
- The signer fails or times out (after two minutes). The commit is abandoned and nothing is stored.
- The signer changes what it signs. The envelope must strip back to exactly the manifest that was given, as zit checks it. A dash-escaped or rewritten manifest is refused rather than stored under a name its cards do not match.
- On js/wasm no external command can run, so signing fails there.
go-libfossil does not verify check-in signatures when it reads them. To check one, take the output of libfossil repo cat and run the signer's own verifier: gpg --verify on a PGP envelope, or, for SSH, ssh-keygen -Y verify -n fossilscm -s SIG with the manifest between the header and the signature block on standard input. The conformance test TestSignedCheckin checks that fossil and zit read the signed check-ins, and that a check-in zit signs with the same SSH key verifies here.
Signing horizon seeds
An origin with clearsign on signs each structural horizon seed it mints with the same pgp-command (draft-fossil-horizon-00 §5.2.4). It keeps one signature per seed in its own table, so a seed keeps its name while the tag state it records is unchanged. It signs only for a requester allowed to read, after the request is authorized. A store that cannot write, such as a read-only mirror, signs again for every request.
A clone verifies seeds against its horizon-seed-key setting:
repo, res, err := libfossil.Clone(ctx, "repo.fossil", t, libfossil.CloneOpts{
StoreCone: hc, // a cone with a structural horizon
SeedKey: "ssh-ed25519 AAAA... origin",
})
libfossil repo clone URL repo.fossil --horizon <full hash> --structural \
--seed-key "ssh-ed25519 AAAA... origin"
The key is an SSH public key line, verified in process, or a 40 or 64 digit OpenPGP fingerprint, verified by gpg against the user's keyring. Repo.SetConfig refuses a horizon-seed-key that is neither. The key is the clone's own; the origin never sends it.
The clone verifies every seed it holds: at clone time, on every pull, and when it rebuilds.
| Seed | Key set | Result |
|---|---|---|
| Signed by the key | yes | Held. |
| Unsigned, or signed by another key | yes | Discarded; the store falls back to the content horizon. |
| Any | no | Held. A clone with no key accepts an unsigned seed. |
| Verifier could not run (gpg missing or timed out) | yes | The seed already held is kept and the error is reported. A new seed is refused. Nothing falls back. |
Setting the origin's own horizon-seed-key to its public key makes it check a kept signature before serving it, so replacing the key behind an unchanged pgp-command takes effect at once.
A signature proves where a seed came from, not that its derivation is right (§5.2.4). repo verify --seed (Repo.VerifySeed) checks the seed against the history it summarizes.
Limits
- PGP verification needs gpg installed, with the origin's key in the keyring. On js/wasm only SSH keys verify.
- gpg-agent hangs under a very large open-file limit. A macOS shell can set
ulimit -nto 2^31. Lower it, to 4096 say, in the environment that signs or verifies with gpg.
Where the code is
signing.go: the setting constants.internal/clearsign: running the signer, the SSH envelope, SSH verification in process, gpg verification.internal/manifest/manifest.go: signing a check-in before it is stored.internal/sync/horizon_seedsign.goandinternal/horizon/seed: signing seeds at the origin and verifying them at the clone.