Signing your commits
This page is for anyone who commits to a KTL repository, and above all for a curator.
A signature ties a commit to you cryptographically, so the forge can show it Verified. In the KTL repositories it is required for one thing: a pull request that adds or removes a by: human: verification event in .lokf/knowledge/.
The provenance job in each repository’s knowledge-registrar.yaml checks those. It accepts either an approving review from the confirming account or a verified commit attributed to it. A sole maintainer cannot approve their own pull request, so for them the signature is the only way. The job exists because a claim that a named person checked a concept has to be tied back to that person: threat-model.md says what such a confirmation proves.
You are encouraged to sign everything. It costs one setup and nothing afterwards.
Choose one format: GPG or SSH. Mixing them is the usual failure. Git reads user.signingkey in whichever format gpg.format names, so a file in the wrong format fails with could not load public key. If you have no preference, GPG is the one to pick: it carries an identity and an expiry of its own, which is what the sections below manage. Whichever format you choose, the address in git config user.email must be a verified email on your GitHub account, or every commit stays Unverified.
GPG, step by step
Section titled “GPG, step by step”The key is kept in ~/.gnupg/. You never save it to a file of your own: exporting prints it to the screen for pasting.
-
Create it. Choose
ECC (sign only)withCurve 25519, and an expiry of 1-2 years rather than0(never expires). An expiry limits the damage if the key is ever lost, and extending it later is one command.Terminal window gpg --full-generate-key -
Set a passphrase, and record it in your password manager as you type it. There is no recovery: a forgotten passphrase means the key is unusable and you start again.
-
Find the fingerprint, the 40-character string on the line under
sec:Terminal window gpg --list-secret-keys --keyid-format=long -
Point git at it:
Terminal window git config --global gpg.format openpgpgit config --global user.signingkey <fingerprint>git config --global commit.gpgsign true -
Give the public half to the forge. Copy the whole block,
BEGIN/ENDlines included, and paste it at github.com/settings/keys → New GPG key, or under your profile’s GPG keys on GitLab, Codeberg or Gitea:Terminal window gpg --armor --export <fingerprint> -
Test it. Make any commit, then:
Terminal window git log --show-signature -1You want
gpg: Good signature from .... GitHub’s own verdict is theVerifiedbadge next to the commit once pushed. -
Optional, but it removes most of the prompts: cache the passphrase for the working day instead of being asked every commit. Put this in
~/.gnupg/gpg-agent.conf, then rungpgconf --reload gpg-agent:default-cache-ttl 28800max-cache-ttl 86400
SSH, step by step
Section titled “SSH, step by step”SSH is simpler, and reuses a key you may already have. The file must be an SSH public key, one line starting ssh-ed25519 AAAA..., and not a GPG export saved under an SSH-looking name.
ssh-keygen -t ed25519 -C "you@example.com" # skip if ~/.ssh/id_ed25519 existsgit config --global gpg.format sshgit config --global user.signingkey ~/.ssh/id_ed25519.pubgit config --global commit.gpgsign truecat ~/.ssh/id_ed25519.pub # paste this into the forgeAdd it at github.com/settings/keys under SSH keys → New SSH key → Key type: Signing Key. GitHub keeps authentication and signing keys in separate lists. A key registered only for authentication leaves every commit Unverified, even though it is the same key. GitLab asks the same as the key’s usage type.
Renew a GPG key before it expires
Section titled “Renew a GPG key before it expires”Set a calendar reminder for a month before the expiry date gpg --list-secret-keys shows. Renewing takes a minute:
gpg --quick-set-expire <fingerprint> 2ygpg --armor --export <fingerprint>Then, on github.com/settings/keys, delete the old entry and add the exported key again. GitHub stores the expiry from the copy you uploaded and does not re-read it, so this step is what renews it there.
Two things to know:
- Extending keeps the same key and fingerprint, so past commits keep verifying and nothing else needs reconfiguring. Prefer it to making a new key.
- An expiry is not a compromise. GitHub still shows commits signed before the key expired as
Verified. What an expired key stops is new signatures. So renew whenever you notice. But if the key is ever stolen, revoke it instead, and do not extend it.
Put your key on file for the forge-free gate
Section titled “Put your key on file for the forge-free gate”A repository that runs knowledge-provenance.sh (ktl-sidecar Step 5) checks each confirmation’s signature against a public key it carries under .lokf/curators/. The file is named after your login on the forge (GitHub, GitLab, Codeberg or another). Export the key you sign with and open a pull request holding only that file. The gate, the CI job that checks a pull request, refuses a change that adds a key and a confirmation by its holder together:
gpg --armor --export YOUR_KEY_ID > .lokf/curators/YOUR_LOGIN.asc # GPG (the export includes the subkeys)cp ~/.ssh/id_ed25519.pub .lokf/curators/YOUR_LOGIN.pub # SSHKeep one kind of key per login. When the key changes, replace the file in its own pull request the same way. A maintainer reviewing that change is what makes the key on file worth anything.
If signing fails
Section titled “If signing fails”could not load public key:gpg.formatdoes not match the keyuser.signingkeypoints at. Check the file is what its name implies: a GPG export begins-----BEGIN PGP PUBLIC KEY BLOCK-----, an SSH key beginsssh-ed25519.- No prompt appears and the commit fails: GPG has nowhere to ask for your passphrase, common over SSH or in a terminal-less session. Add
export GPG_TTY=$(tty)to your shell profile and open a new shell. - Signed locally but GitHub says
Unverified: the commit email is not a verified address on your account, or the key was added to the wrong list (authentication instead of signing).