Skip to content

gpg: sign kernel patches with a key recipients can actually verify - #27

Open
jlhe97 wants to merge 1 commit into
masterfrom
pr27
Open

gpg: sign kernel patches with a key recipients can actually verify#27
jlhe97 wants to merge 1 commit into
masterfrom
pr27

Conversation

@jlhe97

@jlhe97 jlhe97 commented Aug 17, 2026

Copy link
Copy Markdown
Owner

Patch signing was deliberately off. The reasoning was sound as far as it went:
the public key was unpublished, so a signature nobody could verify was pure
cost, and gpg --send-keys could not publish it -- failing with "Invalid
argument" over both hkp:// and hkps://, which looked like the end of it.

It was not. The block is on gpg's own keyserver transport, not on the host.
Plain HTTPS to keys.openpgp.org goes straight through, and that server accepts
uploads over its VKS API at POST /vks/v1/upload. The key is published and
email-verified now, so vks/v1/by-email resolves it and a maintainer can find it
from the From: header alone. That removes the only thing the earlier decision
rested on, so this reverses it.

Signing is gated on the secret key being present locally -- not on a hostname,
not on a flag. signing_key_fingerprint() looks for a secret key matching the
identity, and configure_patch_signing() writes nothing at all when there is
none. That single condition is what keeps the installer safe to run in a
container, on a restricted devserver, or on a laptop the key has not reached
yet. The e2e images assert the negative case: sendemail configured, and
patatt.signingkey empty, because no container holds the key. An explicitly set
user.signingKey or patatt.signingkey is never clobbered -- a work key or a
hardware token means what it says. commit.gpgsign is left alone, since signing
mailing-list patches is a different decision from signing every commit in every
repo on the machine.

The cross-platform seam turned out narrower than expected: only the pinentry
differs. gpg.conf is committed and identical everywhere; gpg-agent.conf is
generated per host and gitignored, because it carries an absolute path --
pinentry-mac on macOS, a graphical prompt where there is a display, and
pinentry-curses over ssh and on headless boxes, which is the case that actually
matters. .zshrc exports GPG_TTY, without which that curses prompt fails with
"Inappropriate ioctl for device" instead of prompting. ~/.gnupg is forced to
700 in two places: configure_gnupg makes it, and the FILES loop's mkdir -p
would otherwise have created it with the umask default, which gpg refuses to
use.

bin/gpg-setup covers what the installer cannot derive: --check, --publish over
the HTTPS API rather than the blocked transport, --export/--import to move the
secret key between machines as a symmetrically encrypted transfer file,
--enable-signing, and --test. --test drives patatt rather than gpg alone,
because gpg working does not prove b4 will sign. It deliberately does not look
for a patatt binary: b4 imports patatt as a library and Homebrew keeps it in a
private venv, so command -v patatt finds nothing on a perfectly healthy
setup, and an earlier version of this check reported "skipped" at exactly the
moment the answer mattered. It reads b4's interpreter out of its shebang
instead, and degrades to a diagnosis rather than a traceback if patatt's
internals move.

This also folds in the pending mail-pass work it depends on, since git
send-email resolves the SMTP password through the same accessor: --store and
--check subcommands, a warning when the input is not 16 characters (a
10-character account password got stored by mistake once, and IMAP and SMTP
both reject it), and libsecret for apt and dnf so secret-tool exists on Linux
at all. sendemail.smtppass is actively unset if found, because b4 only falls
back to git credential fill when it is empty.

Verified: shellcheck clean and 100/100 bats, both run via npx since brew is not
writable on this machine. Signing proven end to end -- patatt signs a message
and self-validates it as openpgp against the local keyring. Not verified: the
--export/--import path has never been exercised against a real second machine,
only the logic around it.

Patch signing was deliberately off. The reasoning was sound as far as it went:
the public key was unpublished, so a signature nobody could verify was pure
cost, and `gpg --send-keys` could not publish it -- failing with "Invalid
argument" over both hkp:// and hkps://, which looked like the end of it.

It was not. The block is on gpg's own keyserver transport, not on the host.
Plain HTTPS to keys.openpgp.org goes straight through, and that server accepts
uploads over its VKS API at POST /vks/v1/upload. The key is published and
email-verified now, so vks/v1/by-email resolves it and a maintainer can find it
from the From: header alone. That removes the only thing the earlier decision
rested on, so this reverses it.

Signing is gated on the secret key being present locally -- not on a hostname,
not on a flag. signing_key_fingerprint() looks for a secret key matching the
identity, and configure_patch_signing() writes nothing at all when there is
none. That single condition is what keeps the installer safe to run in a
container, on a restricted devserver, or on a laptop the key has not reached
yet. The e2e images assert the negative case: sendemail configured, and
patatt.signingkey empty, because no container holds the key. An explicitly set
user.signingKey or patatt.signingkey is never clobbered -- a work key or a
hardware token means what it says. commit.gpgsign is left alone, since signing
mailing-list patches is a different decision from signing every commit in every
repo on the machine.

The cross-platform seam turned out narrower than expected: only the pinentry
differs. gpg.conf is committed and identical everywhere; gpg-agent.conf is
generated per host and gitignored, because it carries an absolute path --
pinentry-mac on macOS, a graphical prompt where there is a display, and
pinentry-curses over ssh and on headless boxes, which is the case that actually
matters. .zshrc exports GPG_TTY, without which that curses prompt fails with
"Inappropriate ioctl for device" instead of prompting. ~/.gnupg is forced to
700 in two places: configure_gnupg makes it, and the FILES loop's mkdir -p
would otherwise have created it with the umask default, which gpg refuses to
use.

bin/gpg-setup covers what the installer cannot derive: --check, --publish over
the HTTPS API rather than the blocked transport, --export/--import to move the
secret key between machines as a symmetrically encrypted transfer file,
--enable-signing, and --test. --test drives patatt rather than gpg alone,
because gpg working does not prove b4 will sign. It deliberately does not look
for a patatt binary: b4 imports patatt as a library and Homebrew keeps it in a
private venv, so `command -v patatt` finds nothing on a perfectly healthy
setup, and an earlier version of this check reported "skipped" at exactly the
moment the answer mattered. It reads b4's interpreter out of its shebang
instead, and degrades to a diagnosis rather than a traceback if patatt's
internals move.

This also folds in the pending mail-pass work it depends on, since git
send-email resolves the SMTP password through the same accessor: --store and
--check subcommands, a warning when the input is not 16 characters (a
10-character account password got stored by mistake once, and IMAP and SMTP
both reject it), and libsecret for apt and dnf so secret-tool exists on Linux
at all. sendemail.smtppass is actively unset if found, because b4 only falls
back to `git credential fill` when it is empty.

Verified: shellcheck clean and 100/100 bats, both run via npx since brew is not
writable on this machine. Signing proven end to end -- patatt signs a message
and self-validates it as openpgp against the local keyring. Not verified: the
--export/--import path has never been exercised against a real second machine,
only the logic around it.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant