-
-
Notifications
You must be signed in to change notification settings - Fork 423
Description
Summary 💡
I would love if GitOxide made it easy to write Git tooling that supports signed pushes.
Background Information
- Transparency log of the kernel, where each commit is just a MIME message that states what is being pushed (not all pushes are signed!): https://git.kernel.org/pub/scm/infra/transparency-logs/gitolite/git/1.git/
git push --signed- Git
pre-receivehook (see environment variables referring to push certificate) (this is likely how we would interface)
This is how a push certificate looks like (taken from transparency log linked above, you can find many of those using git rev-list --all | xargs git grep 'push-certificate'):
certificate version 0.1
pusher Greg Kroah-Hartman <[redacted]> 1757943553 +0200
pushee gitolite.kernel.org:/pub/scm/linux/security/vulns.git
nonce 1757943500-958ee85bd67f4eaea3157d5cd3112662e25a39b4
03898d124628c5431c0ca6b24b480d3f388cb81a 5127821c48ea9f2b324af37e0b89f984932763d6 refs/heads/master
-----BEGIN PGP SIGNATURE-----
iQJPBAABCgA5FiEEZH8oZUiU471FcZm+ONu9yGCSaT4FAmjIFwEbHGdyZWdraEBs
[boring ASCII armored gibberish redacted]
-----END PGP SIGNATURE-----
Integration
GitOxide would ideally help generate/parse/validate push certificates, and provide an interface for servers to receive push certificates, akin to a pre-receive hook or post-receive hook, in order to inspect/validate/archive the push certificate. For example, with a pre-receive hook, that executes env one gets:
$ git push --signed=true --verbose gitd HEAD:refs/heads/main
Pushing to git://127.0.0.1/test.git
Looking up 127.0.0.1 ... done.
Connecting to 127.0.0.1 (port 9418) ... 127.0.0.1 done.
Enumerating objects: 1, done.
Counting objects: 100% (1/1), done.
Writing objects: 100% (1/1), 173 bytes | 173.00 KiB/s, done.
Total 1 (delta 0), reused 0 (delta 0), pack-reused 0 (from 0)
remote: MEMORY_PRESSURE_WRITE=c29tZSAyMDAwMDAgMjAwMDAwMAA=
remote: GIT_PUSH_CERT_NONCE=1771173562-5f9dae1dd09d9f4a7859a3c0902e6f21355d9b23
remote: GIT_DIR=.
remote: PWD=/tmp/gitd/test.git
remote: GIT_QUARANTINE_PATH=/tmp/gitd/test.git/./objects/tmp_objdir-incoming-8MaCHr
remote: SYSTEMD_EXEC_PID=1679293
remote: _=/nix/store/iiishysy5bzkjrawxl4rld1s04qj0k0c-coreutils-9.8/bin/env
remote: GIT_OBJECT_DIRECTORY=/tmp/gitd/test.git/./objects/tmp_objdir-incoming-8MaCHr
remote: GIT_EXEC_PATH=/nix/store/y7kk3dj7wlls924zlvpwmm13gskfw1hk-git-2.51.2/libexec/git-core
remote: LANG=en_US.UTF-8
remote: MEMORY_PRESSURE_WATCH=/sys/fs/cgroup/system.slice/git-daemon.service/memory.pressure
remote: INVOCATION_ID=0e855ccd667147b2ac4f5f08c43664e8
remote: GIT_PUSH_CERT_NONCE_STATUS=OK
remote: GIT_PUSH_CERT_KEY=
remote: GIT_PUSH_CERT=ee38e77233a5a684bd9c7677637d19c87a42b3ab
remote: REMOTE_PORT=42010
remote: GIT_PUSH_OPTION_COUNT=0
remote: USER=root
remote: TZDIR=/nix/store/xh1ff9c9c0yv1wxrwa5gnfp092yagh7v-tzdata-2025b/share/zoneinfo
remote: SHLVL=2
remote: LOCALE_ARCHIVE=/nix/store/iv6mysgipfmq0ygrrlx4ym9dzajky62n-glibc-locales-2.40-66/lib/locale/locale-archive
remote: GIT_PUSH_CERT_STATUS=N
remote: GIT_ALTERNATE_OBJECT_DIRECTORIES=/tmp/gitd/test.git/./objects
remote: JOURNAL_STREAM=8:16713250
remote: REMOTE_ADDR=127.0.0.1
remote: PATH=[redacted]
remote: GIT_PUSH_CERT_SIGNER=
To git://127.0.0.1/test.git
0f773f9..3d103dd HEAD -> main
updating local tracking ref 'refs/remotes/gitd/main'And, on the server side, one can then inspect the certificate as an object:
$ git show ee38e77233a5a684bd9c7677637d19c87a42b3ab
certificate version 0.1
pusher anonymous <anonymous@example.com> 1771173562 +0100
pushee git://127.0.0.1/test.git
nonce 1771173562-5f9dae1dd09d9f4a7859a3c0902e6f21355d9b23
0f773f9e647a667517b2b41f2a39d19ef3a8ec7b 3d103dd4e2aec2e4de4e99f80be481e7cc2b6699 refs/heads/main
-----BEGIN PGP SIGNATURE-----
iIwEABYKADQWIQQNqO2NtzieI0l9QXjYrmhOiYYWDQUCaZH2uhYcYW5vbnltb3Vz
QGV4YW1wbGUuY29tAAoJENiuaE6JhhYNjmwBANEXz98KA64a6O7fMGhQWRP4Fp9A
BU+mBgi/Vmqohso3AQDLJezVdNH9HURr03R6pIYYjvKA0Jude4MSLzikc+y/Aw==
=amDq
-----END PGP SIGNATURE-----Experiments
I set up git daemon on a server.
git daemon command
git daemon \
--reuseaddr \
--base-path=/tmp/gitd \
--listen=127.0.0.1 \
--port=9418 \
--user=git \
--group=git \
--verbose \
--enable=receive-pack \
--verbose \
--export-all /tmp/gitd
On the client I do:
git init .
git remote add gitd git://127.0.0.1/test.git
git config user.email "anonymous@example.com"
git config user.name "anonymous"
git commit --allow-empty --allow-empty-message -m ""
I have prepared a few dumps so you can see how this looks on the wire. (I have *.pcap files, but fail to attach them.)
Success
> 002egit-receive-pack /test.git.host=127.0.0.1.
< 01020000000000000000000000000000000000000000 capabilities^{}.report-status report-status-v2 delete-refs side-band-64k quiet atomic ofs-delta push-cert=1771171633-0c4ddbbe5aec02916c2f5abfc1c2456725228a8e push-options object-format=sha1 agent=git/2.51.2-Linux
< 0000
> 0057push-cert. report-status-v2 side-band-64k object-format=sha1 agent=git/2.51.2-Linux001ccertificate version 0.1
> 003epusher anonymous <anonymous@example.com> 1771171633 +0100
> 0024pushee git://127.0.0.1/test.git
> 003enonce 1771171633-0c4ddbbe5aec02916c2f5abfc1c2456725228a8e
> 0005
> 00660000000000000000000000000000000000000000 0f773f9e647a667517b2b41f2a39d19ef3a8ec7b refs/heads/main
> 0022-----BEGIN PGP SIGNATURE-----
> 0005
> 0045iIwEABYKADQWIQQNqO2NtzieI0l9QXjYrmhOiYYWDQUCaZHvMRYcYW5vbnltb3Vz
> 0045QGV4YW1wbGUuY29tAAoJENiuaE6JhhYNRPEBAOipmlR3jMJJYSnSetm4bln8d6Xk
> 0045gXSdU8EyjaL46ghWAP9BNf2Tv60T9oQN7GtpCSa8m+bsu2CfQSqMFU8ArtT2AA==
> 000a=QmvQ
> 0020-----END PGP SIGNATURE-----
> 0012push-cert-end
> 0000PACK.........
> x....
> .0...{."wA...)..W..C.,2;.....x{.7vU.&iZ:S......:Q[e..D..J.).c.|....1?^p.q.w..C...
> 1..3
> 2.0"..f.1....Z.4> x........cDv.m .m...........
< 002e.000eunpack ok
< 0017ok refs/heads/main
< 00000000
Signed Push Not Supported
> 002egit-receive-pack /test.git.host=127.0.0.1.
< 00b70000000000000000000000000000000000000000 capabilities^{}.report-status report-status-v2 delete-refs side-band-64k quiet atomic ofs-delta object-format=sha1 agent=git/2.51.2-Linux
< 0000
Client fails with
$ git push --signed=true --verbose gitd HEAD:refs/heads/main
fatal: the receiving end does not support --signed pushProblem
Server does not advertise signed push capability, client bails because git push --signed=true demands signing. The capability that git-send-pack is looking for seems to be push-cert=<NONCE>, see https://github.com/git/git/blob/67ad42147a7acc2af6074753ebd03d904476118f/send-pack.c#L567-L581.
Solution
Change the Git configuration of the server side, e.g. by editing/etc/gitconfig, ensuring the following:
[receive]
advertisePushOptions = true
certNonceSeed = "test"
Client Fails to Sign
> 002egit-receive-pack /test.git.host=127.0.0.1.
< 01020000000000000000000000000000000000000000 capabilities^{}.report-status report-status-v2 delete-refs side-band-64k quiet atomic ofs-delta push-cert=1771170392-33ed7d17b5c157eaa6cbc920d3ce7e20ac9bbad9 push-options object-format=sha1 agent=git/2.51.2-Linux
< 0000
Client fails with:
$ git push --signed=true --verbose gitd HEAD:refs/heads/main
error: gpg failed to sign the data:
gpg: skipped "anonymous <anonymous@example.com>": No secret key
[GNUPG:] INV_SGNR 9 anonymous <anonymous@example.com>
[GNUPG:] FAILURE sign 17
gpg: signing failed: No secret key
fatal: failed to sign the push certificateProblem
Server advertises signed push capability, but client fails to sign.
Solution
Generate a new key:
$ gpg --quick-generate-key "anonymous@example.com" ed25519 sign 0
We need to generate a lot of random bytes. It is a good idea to perform
some other action (type on the keyboard, move the mouse, utilize the
disks) during the prime generation; this gives the random number
generator a better chance to gain enough entropy.
Please enter the passphrase to
protect your new key
Passphrase:
Repeat:
You have not entered a passphrase - this is in general a bad idea!
Please confirm that you do not want to have any protection on your key.
Yes, protection is not needed
Enter new passphrase
[ye]? y
gpg: revocation certificate stored as '/home/lorenz/.gnupg/openpgp-revocs.d/0DA8ED8DB7389E23497D4178D8AE684E8986160D.rev'
public and secret key created and signed.
pub ed25519/0xD8AE684E8986160D 2026-02-15 [SC]
Key fingerprint = 0DA8 ED8D B738 9E23 497D 4178 D8AE 684E 8986 160D
uid anonymous <anonymous@example.com>(Note that GitHub does not support signed pushes.)
Motivation 🔦
Refer to https://people.kernel.org/monsieuricon/signed-git-pushes