Skip to content

Commit fb06b52

Browse files
committed
Merge branch 'jc/push-cert'
Allow "git push" request to be signed, so that it can be verified and audited, using the GPG signature of the person who pushed, that the tips of branches at a public repository really point the commits the pusher wanted to, without having to "trust" the server. * jc/push-cert: (24 commits) receive-pack::hmac_sha1(): copy the entire SHA-1 hash out signed push: allow stale nonce in stateless mode signed push: teach smart-HTTP to pass "git push --signed" around signed push: fortify against replay attacks signed push: add "pushee" header to push certificate signed push: remove duplicated protocol info send-pack: send feature request on push-cert packet receive-pack: GPG-validate push certificates push: the beginning of "git push --signed" pack-protocol doc: typofix for PKT-LINE gpg-interface: move parse_signature() to where it should be gpg-interface: move parse_gpg_output() to where it should be send-pack: clarify that cmds_sent is a boolean send-pack: refactor inspecting and resetting status and sending commands send-pack: rename "new_refs" to "need_pack_data" receive-pack: factor out capability string generation send-pack: factor out capability string generation send-pack: always send capabilities send-pack: refactor decision to send update per ref send-pack: move REF_STATUS_REJECT_NODELETE logic a bit higher ...
2 parents 325602c + 6f5ef44 commit fb06b52

23 files changed

+932
-159
lines changed

Documentation/config.txt

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2044,6 +2044,25 @@ receive.autogc::
20442044
receiving data from git-push and updating refs. You can stop
20452045
it by setting this variable to false.
20462046

2047+
receive.certnonceseed::
2048+
By setting this variable to a string, `git receive-pack`
2049+
will accept a `git push --signed` and verifies it by using
2050+
a "nonce" protected by HMAC using this string as a secret
2051+
key.
2052+
2053+
receive.certnonceslop::
2054+
When a `git push --signed` sent a push certificate with a
2055+
"nonce" that was issued by a receive-pack serving the same
2056+
repository within this many seconds, export the "nonce"
2057+
found in the certificate to `GIT_PUSH_CERT_NONCE` to the
2058+
hooks (instead of what the receive-pack asked the sending
2059+
side to include). This may allow writing checks in
2060+
`pre-receive` and `post-receive` a bit easier. Instead of
2061+
checking `GIT_PUSH_CERT_NONCE_SLOP` environment variable
2062+
that records by how many seconds the nonce is stale to
2063+
decide if they want to accept the certificate, they only
2064+
can check `GIT_PUSH_CERT_NONCE_STATUS` is `OK`.
2065+
20472066
receive.fsckObjects::
20482067
If it is set to true, git-receive-pack will check all received
20492068
objects. It will abort in the case of a malformed object or a

Documentation/git-push.txt

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ SYNOPSIS
1010
--------
1111
[verse]
1212
'git push' [--all | --mirror | --tags] [--follow-tags] [-n | --dry-run] [--receive-pack=<git-receive-pack>]
13-
[--repo=<repository>] [-f | --force] [--prune] [-v | --verbose] [-u | --set-upstream]
13+
[--repo=<repository>] [-f | --force] [--prune] [-v | --verbose]
14+
[-u | --set-upstream] [--signed]
1415
[--force-with-lease[=<refname>[:<expect>]]]
1516
[--no-verify] [<repository> [<refspec>...]]
1617

@@ -129,6 +130,12 @@ already exists on the remote side.
129130
from the remote but are pointing at commit-ish that are
130131
reachable from the refs being pushed.
131132

133+
--signed::
134+
GPG-sign the push request to update refs on the receiving
135+
side, to allow it to be checked by the hooks and/or be
136+
logged. See linkgit:git-receive-pack[1] for the details
137+
on the receiving end.
138+
132139
--receive-pack=<git-receive-pack>::
133140
--exec=<git-receive-pack>::
134141
Path to the 'git-receive-pack' program on the remote

Documentation/git-receive-pack.txt

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,56 @@ the update. Refs to be created will have sha1-old equal to 0\{40},
5353
while refs to be deleted will have sha1-new equal to 0\{40}, otherwise
5454
sha1-old and sha1-new should be valid objects in the repository.
5555

56+
When accepting a signed push (see linkgit:git-push[1]), the signed
57+
push certificate is stored in a blob and an environment variable
58+
`GIT_PUSH_CERT` can be consulted for its object name. See the
59+
description of `post-receive` hook for an example. In addition, the
60+
certificate is verified using GPG and the result is exported with
61+
the following environment variables:
62+
63+
`GIT_PUSH_CERT_SIGNER`::
64+
The name and the e-mail address of the owner of the key that
65+
signed the push certificate.
66+
67+
`GIT_PUSH_CERT_KEY`::
68+
The GPG key ID of the key that signed the push certificate.
69+
70+
`GIT_PUSH_CERT_STATUS`::
71+
The status of GPG verification of the push certificate,
72+
using the same mnemonic as used in `%G?` format of `git log`
73+
family of commands (see linkgit:git-log[1]).
74+
75+
`GIT_PUSH_CERT_NONCE`::
76+
The nonce string the process asked the signer to include
77+
in the push certificate. If this does not match the value
78+
recorded on the "nonce" header in the push certificate, it
79+
may indicate that the certificate is a valid one that is
80+
being replayed from a separate "git push" session.
81+
82+
`GIT_PUSH_CERT_NONCE_STATUS`::
83+
`UNSOLICITED`;;
84+
"git push --signed" sent a nonce when we did not ask it to
85+
send one.
86+
`MISSING`;;
87+
"git push --signed" did not send any nonce header.
88+
`BAD`;;
89+
"git push --signed" sent a bogus nonce.
90+
`OK`;;
91+
"git push --signed" sent the nonce we asked it to send.
92+
`SLOP`;;
93+
"git push --signed" sent a nonce different from what we
94+
asked it to send now, but in a previous session. See
95+
`GIT_PUSH_CERT_NONCE_SLOP` environment variable.
96+
97+
`GIT_PUSH_CERT_NONCE_SLOP`::
98+
"git push --signed" sent a nonce different from what we
99+
asked it to send now, but in a different session whose
100+
starting time is different by this many seconds from the
101+
current session. Only meaningful when
102+
`GIT_PUSH_CERT_NONCE_STATUS` says `SLOP`.
103+
Also read about `receive.certnonceslop` variable in
104+
linkgit:git-config[1].
105+
56106
This hook is called before any refname is updated and before any
57107
fast-forward checks are performed.
58108

@@ -101,9 +151,14 @@ the update. Refs that were created will have sha1-old equal to
101151
0\{40}, otherwise sha1-old and sha1-new should be valid objects in
102152
the repository.
103153

154+
The `GIT_PUSH_CERT*` environment variables can be inspected, just as
155+
in `pre-receive` hook, after accepting a signed push.
156+
104157
Using this hook, it is easy to generate mails describing the updates
105158
to the repository. This example script sends one mail message per
106-
ref listing the commits pushed to the repository:
159+
ref listing the commits pushed to the repository, and logs the push
160+
certificates of signed pushes with good signatures to a logger
161+
service:
107162

108163
#!/bin/sh
109164
# mail out commit update information.
@@ -119,6 +174,14 @@ ref listing the commits pushed to the repository:
119174
fi |
120175
mail -s "Changes to ref $ref" commit-list@mydomain
121176
done
177+
# log signed push certificate, if any
178+
if test -n "${GIT_PUSH_CERT-}" && test ${GIT_PUSH_CERT_STATUS} = G
179+
then
180+
(
181+
echo expected nonce is ${GIT_PUSH_NONCE}
182+
git cat-file blob ${GIT_PUSH_CERT}
183+
) | mail -s "push certificate from $GIT_PUSH_CERT_SIGNER" push-log@mydomain
184+
fi
122185
exit 0
123186

124187
The exit code from this hook invocation is ignored, however a

Documentation/technical/pack-protocol.txt

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -212,9 +212,9 @@ out of what the server said it could do with the first 'want' line.
212212
want-list = first-want
213213
*additional-want
214214

215-
shallow-line = PKT_LINE("shallow" SP obj-id)
215+
shallow-line = PKT-LINE("shallow" SP obj-id)
216216

217-
depth-request = PKT_LINE("deepen" SP depth)
217+
depth-request = PKT-LINE("deepen" SP depth)
218218

219219
first-want = PKT-LINE("want" SP obj-id SP capability-list LF)
220220
additional-want = PKT-LINE("want" SP obj-id LF)
@@ -465,7 +465,7 @@ contain all the objects that the server will need to complete the new
465465
references.
466466

467467
----
468-
update-request = *shallow command-list [pack-file]
468+
update-request = *shallow ( command-list | push-cert ) [pack-file]
469469

470470
shallow = PKT-LINE("shallow" SP obj-id LF)
471471

@@ -481,12 +481,27 @@ references.
481481
old-id = obj-id
482482
new-id = obj-id
483483

484+
push-cert = PKT-LINE("push-cert" NUL capability-list LF)
485+
PKT-LINE("certificate version 0.1" LF)
486+
PKT-LINE("pusher" SP ident LF)
487+
PKT-LINE("pushee" SP url LF)
488+
PKT-LINE("nonce" SP nonce LF)
489+
PKT-LINE(LF)
490+
*PKT-LINE(command LF)
491+
*PKT-LINE(gpg-signature-lines LF)
492+
PKT-LINE("push-cert-end" LF)
493+
484494
pack-file = "PACK" 28*(OCTET)
485495
----
486496

487497
If the receiving end does not support delete-refs, the sending end MUST
488498
NOT ask for delete command.
489499

500+
If the receiving end does not support push-cert, the sending end
501+
MUST NOT send a push-cert command. When a push-cert command is
502+
sent, command-list MUST NOT be sent; the commands recorded in the
503+
push certificate is used instead.
504+
490505
The pack-file MUST NOT be sent if the only command used is 'delete'.
491506

492507
A pack-file MUST be sent if either create or update command is used,
@@ -501,6 +516,34 @@ was being processed (the obj-id is still the same as the old-id), and
501516
it will run any update hooks to make sure that the update is acceptable.
502517
If all of that is fine, the server will then update the references.
503518

519+
Push Certificate
520+
----------------
521+
522+
A push certificate begins with a set of header lines. After the
523+
header and an empty line, the protocol commands follow, one per
524+
line.
525+
526+
Currently, the following header fields are defined:
527+
528+
`pusher` ident::
529+
Identify the GPG key in "Human Readable Name <email@address>"
530+
format.
531+
532+
`pushee` url::
533+
The repository URL (anonymized, if the URL contains
534+
authentication material) the user who ran `git push`
535+
intended to push into.
536+
537+
`nonce` nonce::
538+
The 'nonce' string the receiving repository asked the
539+
pushing user to include in the certificate, to prevent
540+
replay attacks.
541+
542+
The GPG signature lines are a detached signature for the contents
543+
recorded in the push certificate before the signature block begins.
544+
The detached signature is used to certify that the commands were
545+
given by the pusher, who must be the signer.
546+
504547
Report Status
505548
-------------
506549

Documentation/technical/protocol-capabilities.txt

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ was sent. Server MUST NOT ignore capabilities that client requested
1818
and server advertised. As a consequence of these rules, server MUST
1919
NOT advertise capabilities it does not understand.
2020

21-
The 'report-status', 'delete-refs', and 'quiet' capabilities are sent and
22-
recognized by the receive-pack (push to server) process.
21+
The 'report-status', 'delete-refs', 'quiet', and 'push-cert' capabilities
22+
are sent and recognized by the receive-pack (push to server) process.
2323

2424
The 'ofs-delta' and 'side-band-64k' capabilities are sent and recognized
2525
by both upload-pack and receive-pack protocols. The 'agent' capability
@@ -250,3 +250,12 @@ allow-tip-sha1-in-want
250250
If the upload-pack server advertises this capability, fetch-pack may
251251
send "want" lines with SHA-1s that exist at the server but are not
252252
advertised by upload-pack.
253+
254+
push-cert=<nonce>
255+
-----------------
256+
257+
The receive-pack server that advertises this capability is willing
258+
to accept a signed push certificate, and asks the <nonce> to be
259+
included in the push certificate. A send-pack client MUST NOT
260+
send a push-cert packet unless the receive-pack server advertises
261+
this capability.

builtin/push.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -506,6 +506,7 @@ int cmd_push(int argc, const char **argv, const char *prefix)
506506
OPT_BIT(0, "no-verify", &flags, N_("bypass pre-push hook"), TRANSPORT_PUSH_NO_HOOK),
507507
OPT_BIT(0, "follow-tags", &flags, N_("push missing but relevant tags"),
508508
TRANSPORT_PUSH_FOLLOW_TAGS),
509+
OPT_BIT(0, "signed", &flags, N_("GPG sign the push"), TRANSPORT_PUSH_CERT),
509510
OPT_END()
510511
};
511512

0 commit comments

Comments
 (0)