From: Nick Alcock <nick.alcock@oracle.com>
To: binutils@sourceware.org, gcc@gcc.gnu.org
Subject: FYI: easy-ish hardware-backed git commit and tag signing with Git 2.34+ & OpenSSH 8.2+
Date: Thu, 19 Sep 2024 14:54:40 +0100 [thread overview]
Message-ID: <87ttebeokf.fsf@esperi.org.uk> (raw)
(I was asked to send this at Cauldron.
This mail does not solve the public key distribution problem, though it
does discuss it. I suspect that for our restricted use case, solving it
is probably going to be easy. This is not quite ready to go onto the
wiki because I think we should probably figure this out first, or we can
sign commits but not verify the signatures!)
So git has long had the ability to sign commits, tags and pushes with
GPG. But for me this has always been less than useful: the only thing I
use gpg for is this sort of signing, which means it's rarely done and
it's almost always broken when the time comes to actually do it. Worse
yet, because the web of trust is so rusted, almost nobody can actually
verify that my commits are signed by me because almost nobody is in the
web of trust other than a few hundred Debian developers. Even actual
cryptographers recommend against using GPG, but until now we had no
choice for git commits.
Git 2.34 and recent enough SSH change that: recent SSH releases can sign
things with your SSH keys, and Git 2.34 gains the ability to use that to
sign commits, tags and pushes: and because OpenSSH 8.2+ added the
ability to use FIDO2 security keys as hardware tokens, you can now sign
commits and tags with a key that is present nowhere on your computer but
only on a hardware key in your personal possession, Spectre-proof.
This is still fairly new stuff: there are still a few rough edges and
broken bits, and it needs both Git 2.34+ and OpenSSH 8.2p1+ (but
specifically *not* 8.7p1, which accidentally broke signing with SSH
keys. Git 2.34+ checks for this and will refuse to try to sign things
with the bad OpenSSH release). Plus when verifying signatures you have
to get keys and stuff them into a separate file, because there is
nothing like a keyserver for SSH keys. (But that's easier than you'd
think for the very common case of a key tied to some github or gitlab
user, but none of this is specific to any sort of proprietary service.
We might want a page somewhere giving public keys used to sign commits,
or a file in the repo listing them, or a separate repo with a pile of
public keys in, or something like that.)
With all that in mind, here's what I did back in 2021 to get a new
YubiKey 5 set up to do git commit and tag signing (though this should
work with literally any FIDO2-capable key, which are common as dirt now:
I mention YubiKeys below simply because that's all I've tested this with
so far). Most of this is stuff you need to do only once per machine, or
only once per hardware key.
First, setup for signing, which you need to do only once per (machine,
yubikey) pair. You have to
1) plug in the yubikey. obvious really.
2) create an SK-backed SSH key based on that yubikey. On the local
machine:
ssh-keygen -f .ssh/sw-signing-yk2021 -t ed25519-sk
You'll have to touch the key during this opration, to prove it's in
your physical possession.
The private half of the key that gets generated is just a wrapper
which points at the Yubikey. The public half is a real public key
matching the secret half on the Yubikey, as usual.
(You can, if you like, use the same key you use for local SSH
authentication, but I've chosen to separate them and use a separate
one for signing alone so that I can revoke either of them without
affecting the other one. Even though both are just wrappers, they are
not interchangeable: they have distinct public keys and you can
revoke them independently.)
(Unfortunately, it looks like you cannot yet use the SSH key you use
to connect to sourceware itself -- sourceware seems to be running
OpenSSH 8.0, which is too old to handle hardware-backed sk keys.
Maybe an upgrade is in order?)
Passphrase the key if you like, though all this stops people doing is
using the key if they steal your yubikey, in which case you probably
have bigger problems. Certainly I do since it would mean they've
got my front door keys too.
3) load the key into your SSH agent, if you're using one.
4) Tell ~/.gitconfig you're signing with an SSH key, not a GPG key:
[gpg]
format = ssh
[user]
signingKey = "/home/your/.ssh/key.pub"
Now you can sign tags and commits (and do signed pushes if you find
anywhere that treats signed pushes differently from unsigned ones) via
the usual
git tag --sign -a TAG
or
git commit -S
You can turn this on by default via
[commit]
gpgSign = true
or
[tag]
gpgSign = true
in .git/config or ~/.gitconfig.
The private key used to do the signature never leaves the yubikey, so
you can only do this if your yubikey is plugged in and SSH can see it.
(If you're not running an SSH agent, you can also only do this on the
machine your key is plugged in to: with an SSH agent and agent
forwarding set up, the signing request is passed back to your machine:
you can make sure this is all working right via an ssh-add -L before you
try a tag).
You'll need to touch the button on your yubikey to prove physical
presence every time you commit or tag anything, which of course means it
has to be plugged in. (I believe all FIDO tokens are required to have a
button like this.)
To verify a tag or commit signed with an SSH key, there is also some
setup work that needs to be done:
1) If you like, you can verify not only that a signature is valid but
that it belongs to the person you think it does. To do that, first of
all you need to get the public keys for the user doing the signing.
If github or gitlab know about those keys, this is easy:
curl -LO https://github.com/$USERNAME.keys
curl -LO https://gitlab.com/$USERNAME.keys
(It's possible we might simply want to stick a bunch of public keys
used for sourceware signing somewhere everyone can easily get at
them: I only mention github and gitlab here as an example of a way
an existing service is translating SSH keys into a URL you can use
to get their public half. There are many other approaches we could
use instead -- obviously relying on either github or gitlab for
key acquisition for verification would be less than ideal!)
You put this key into a new line in the 'allowed signers' file, which
is documented in the git-config and ssh-keygen manpages: you can put
it anywhere you like, but usually it goes under ~/.ssh. This is easy
to set up, with each line looking like this (and of course I can post
this freely because, well, it's a public key. Feel free to use it,
all you can do with it is give me access to your machines!)
nix@esperi.org.uk,nick.alcock@oracle.com,oranix@esperi.org.uk namespaces="git,sign",valid-after="20211115" sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAIPct0hXtOYE6kIF6x9I7LGkm4k04UgC8/geO40TtSKD+AAAABHNzaDo= nix-yk2021@sourceware
(not, as it happens, actually my sourceware key)
Everything from the sk-ssh onwards is just pasted in from the public
key file downloaded above. The bit before must contain at least one
email address (actually, key principal): if a commit is found signed
with this key, one of these addresses will be reported as the signing
user (usually the first). Next comes a bunch of comma-separated
options: for git usage you need at least namespaces="git", meaning
that this key can be used for signing Git commits. ("sign" means the
key can be seen for random ad-hoc signatures done via ssh-keygen).
valid-after (and valid-before) let you put age bounds on a key if you
know when it was created.
So a minimal line for this usage would look like
nix@esperi.org.uk namespaces="git" sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAIPct0hXtOYE6kIF6x9I7LGkm4k04UgC8/geO40TtSKD+AAAABHNzaDo=
2) Stick the allowed signers filename in .gitconfig:
[gpg.ssh]
allowedSignersFile = "/home/your/.ssh/allowed_signers"
This file needs to be there if you want to verify any signatures at
all. Right now it also needs at least one signature in it, due to an
ssh bug, even if you're not planning to do anything more than check
if sigs are valid (oops).
3) With all that done, verification using SSH keys is just done via 'git
tag --verify' as usual. If the
signature comes from a user in the allowed-signers file, you see
something like this:
% git tag --verify 1.0
object fa48619a4207beb6477d03f8cc4bf5e60ea93bff
type commit
tag 1.0
tagger Nix <nix@esperi.org.uk> 1637241873 +0000
This is release 1.0.
It feels like time for a release, since this has been in active
use for years with no bugs reported.
Good "git" signature for nix@esperi.org.uk with ED25519-SK key SHA256:0iXFd+5rii2SgUyd812DWvnUnDBjdn2Zm/galoTpOFM
... which lets you see that not only was it signed but the signature
really came from the user you'd expect: the one who tagged the
commit. (The SHA256 stuff after the end is a key fingerprint: if you
get this message, you know it must match once of the keys in your
allowed-signers file, and you can if you like prove that via
ssh-keygen -l.)
If the key is not listed in your allowed-signers file, git (well,
actually ssh) says instead:
Good "git" signature with ED25519-SK key SHA256:0iXFd+5rii2SgUyd812DWvnUnDBjdn2Zm/galoTpOFM
No principal matched.
If the signature is bad, you see something like:
Could not verify signature.
(and then why, e.g. "key is not yet valid" or "bad signature")
git log --show-signature also works, following which each signed commit
is preceded by something like this:
commit 52196b27b4e022b1b458de029ea4e8f0a5a855b7 (HEAD -> nix/queue)
Good "git" signature for nix@esperi.org.uk with ED25519-SK key SHA256:0iXFd+5rii2SgUyd812DWvnUnDBjdn2Zm/galoTpOFM
Author: Nick Alcock <nick.alcock@oracle.com>
Date: Tue Nov 23 16:57:10 2021 +0000
[...]
... anyway, I hope this is useful to some people. I've been signing my
binutils commits this way for some time now (using a key from
https://github.com/nickalcock.keys as the key, but again none of this
is GitHub-specific, I'm just using it as a key distribution method until
we figure out a better one.).
reply other threads:[~2024-09-19 13:55 UTC|newest]
Thread overview: [no followups] expand[flat|nested] mbox.gz Atom feed
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=87ttebeokf.fsf@esperi.org.uk \
--to=nick.alcock@oracle.com \
--cc=binutils@sourceware.org \
--cc=gcc@gcc.gnu.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).