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 ...
This commit is contained in:
@ -2044,6 +2044,25 @@ receive.autogc::
|
||||
receiving data from git-push and updating refs. You can stop
|
||||
it by setting this variable to false.
|
||||
|
||||
receive.certnonceseed::
|
||||
By setting this variable to a string, `git receive-pack`
|
||||
will accept a `git push --signed` and verifies it by using
|
||||
a "nonce" protected by HMAC using this string as a secret
|
||||
key.
|
||||
|
||||
receive.certnonceslop::
|
||||
When a `git push --signed` sent a push certificate with a
|
||||
"nonce" that was issued by a receive-pack serving the same
|
||||
repository within this many seconds, export the "nonce"
|
||||
found in the certificate to `GIT_PUSH_CERT_NONCE` to the
|
||||
hooks (instead of what the receive-pack asked the sending
|
||||
side to include). This may allow writing checks in
|
||||
`pre-receive` and `post-receive` a bit easier. Instead of
|
||||
checking `GIT_PUSH_CERT_NONCE_SLOP` environment variable
|
||||
that records by how many seconds the nonce is stale to
|
||||
decide if they want to accept the certificate, they only
|
||||
can check `GIT_PUSH_CERT_NONCE_STATUS` is `OK`.
|
||||
|
||||
receive.fsckObjects::
|
||||
If it is set to true, git-receive-pack will check all received
|
||||
objects. It will abort in the case of a malformed object or a
|
||||
|
@ -10,7 +10,8 @@ SYNOPSIS
|
||||
--------
|
||||
[verse]
|
||||
'git push' [--all | --mirror | --tags] [--follow-tags] [-n | --dry-run] [--receive-pack=<git-receive-pack>]
|
||||
[--repo=<repository>] [-f | --force] [--prune] [-v | --verbose] [-u | --set-upstream]
|
||||
[--repo=<repository>] [-f | --force] [--prune] [-v | --verbose]
|
||||
[-u | --set-upstream] [--signed]
|
||||
[--force-with-lease[=<refname>[:<expect>]]]
|
||||
[--no-verify] [<repository> [<refspec>...]]
|
||||
|
||||
@ -129,6 +130,12 @@ already exists on the remote side.
|
||||
from the remote but are pointing at commit-ish that are
|
||||
reachable from the refs being pushed.
|
||||
|
||||
--signed::
|
||||
GPG-sign the push request to update refs on the receiving
|
||||
side, to allow it to be checked by the hooks and/or be
|
||||
logged. See linkgit:git-receive-pack[1] for the details
|
||||
on the receiving end.
|
||||
|
||||
--receive-pack=<git-receive-pack>::
|
||||
--exec=<git-receive-pack>::
|
||||
Path to the 'git-receive-pack' program on the remote
|
||||
|
@ -53,6 +53,56 @@ the update. Refs to be created will have sha1-old equal to 0\{40},
|
||||
while refs to be deleted will have sha1-new equal to 0\{40}, otherwise
|
||||
sha1-old and sha1-new should be valid objects in the repository.
|
||||
|
||||
When accepting a signed push (see linkgit:git-push[1]), the signed
|
||||
push certificate is stored in a blob and an environment variable
|
||||
`GIT_PUSH_CERT` can be consulted for its object name. See the
|
||||
description of `post-receive` hook for an example. In addition, the
|
||||
certificate is verified using GPG and the result is exported with
|
||||
the following environment variables:
|
||||
|
||||
`GIT_PUSH_CERT_SIGNER`::
|
||||
The name and the e-mail address of the owner of the key that
|
||||
signed the push certificate.
|
||||
|
||||
`GIT_PUSH_CERT_KEY`::
|
||||
The GPG key ID of the key that signed the push certificate.
|
||||
|
||||
`GIT_PUSH_CERT_STATUS`::
|
||||
The status of GPG verification of the push certificate,
|
||||
using the same mnemonic as used in `%G?` format of `git log`
|
||||
family of commands (see linkgit:git-log[1]).
|
||||
|
||||
`GIT_PUSH_CERT_NONCE`::
|
||||
The nonce string the process asked the signer to include
|
||||
in the push certificate. If this does not match the value
|
||||
recorded on the "nonce" header in the push certificate, it
|
||||
may indicate that the certificate is a valid one that is
|
||||
being replayed from a separate "git push" session.
|
||||
|
||||
`GIT_PUSH_CERT_NONCE_STATUS`::
|
||||
`UNSOLICITED`;;
|
||||
"git push --signed" sent a nonce when we did not ask it to
|
||||
send one.
|
||||
`MISSING`;;
|
||||
"git push --signed" did not send any nonce header.
|
||||
`BAD`;;
|
||||
"git push --signed" sent a bogus nonce.
|
||||
`OK`;;
|
||||
"git push --signed" sent the nonce we asked it to send.
|
||||
`SLOP`;;
|
||||
"git push --signed" sent a nonce different from what we
|
||||
asked it to send now, but in a previous session. See
|
||||
`GIT_PUSH_CERT_NONCE_SLOP` environment variable.
|
||||
|
||||
`GIT_PUSH_CERT_NONCE_SLOP`::
|
||||
"git push --signed" sent a nonce different from what we
|
||||
asked it to send now, but in a different session whose
|
||||
starting time is different by this many seconds from the
|
||||
current session. Only meaningful when
|
||||
`GIT_PUSH_CERT_NONCE_STATUS` says `SLOP`.
|
||||
Also read about `receive.certnonceslop` variable in
|
||||
linkgit:git-config[1].
|
||||
|
||||
This hook is called before any refname is updated and before any
|
||||
fast-forward checks are performed.
|
||||
|
||||
@ -101,9 +151,14 @@ the update. Refs that were created will have sha1-old equal to
|
||||
0\{40}, otherwise sha1-old and sha1-new should be valid objects in
|
||||
the repository.
|
||||
|
||||
The `GIT_PUSH_CERT*` environment variables can be inspected, just as
|
||||
in `pre-receive` hook, after accepting a signed push.
|
||||
|
||||
Using this hook, it is easy to generate mails describing the updates
|
||||
to the repository. This example script sends one mail message per
|
||||
ref listing the commits pushed to the repository:
|
||||
ref listing the commits pushed to the repository, and logs the push
|
||||
certificates of signed pushes with good signatures to a logger
|
||||
service:
|
||||
|
||||
#!/bin/sh
|
||||
# mail out commit update information.
|
||||
@ -119,6 +174,14 @@ ref listing the commits pushed to the repository:
|
||||
fi |
|
||||
mail -s "Changes to ref $ref" commit-list@mydomain
|
||||
done
|
||||
# log signed push certificate, if any
|
||||
if test -n "${GIT_PUSH_CERT-}" && test ${GIT_PUSH_CERT_STATUS} = G
|
||||
then
|
||||
(
|
||||
echo expected nonce is ${GIT_PUSH_NONCE}
|
||||
git cat-file blob ${GIT_PUSH_CERT}
|
||||
) | mail -s "push certificate from $GIT_PUSH_CERT_SIGNER" push-log@mydomain
|
||||
fi
|
||||
exit 0
|
||||
|
||||
The exit code from this hook invocation is ignored, however a
|
||||
|
@ -212,9 +212,9 @@ out of what the server said it could do with the first 'want' line.
|
||||
want-list = first-want
|
||||
*additional-want
|
||||
|
||||
shallow-line = PKT_LINE("shallow" SP obj-id)
|
||||
shallow-line = PKT-LINE("shallow" SP obj-id)
|
||||
|
||||
depth-request = PKT_LINE("deepen" SP depth)
|
||||
depth-request = PKT-LINE("deepen" SP depth)
|
||||
|
||||
first-want = PKT-LINE("want" SP obj-id SP capability-list LF)
|
||||
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
|
||||
references.
|
||||
|
||||
----
|
||||
update-request = *shallow command-list [pack-file]
|
||||
update-request = *shallow ( command-list | push-cert ) [pack-file]
|
||||
|
||||
shallow = PKT-LINE("shallow" SP obj-id LF)
|
||||
|
||||
@ -481,12 +481,27 @@ references.
|
||||
old-id = obj-id
|
||||
new-id = obj-id
|
||||
|
||||
push-cert = PKT-LINE("push-cert" NUL capability-list LF)
|
||||
PKT-LINE("certificate version 0.1" LF)
|
||||
PKT-LINE("pusher" SP ident LF)
|
||||
PKT-LINE("pushee" SP url LF)
|
||||
PKT-LINE("nonce" SP nonce LF)
|
||||
PKT-LINE(LF)
|
||||
*PKT-LINE(command LF)
|
||||
*PKT-LINE(gpg-signature-lines LF)
|
||||
PKT-LINE("push-cert-end" LF)
|
||||
|
||||
pack-file = "PACK" 28*(OCTET)
|
||||
----
|
||||
|
||||
If the receiving end does not support delete-refs, the sending end MUST
|
||||
NOT ask for delete command.
|
||||
|
||||
If the receiving end does not support push-cert, the sending end
|
||||
MUST NOT send a push-cert command. When a push-cert command is
|
||||
sent, command-list MUST NOT be sent; the commands recorded in the
|
||||
push certificate is used instead.
|
||||
|
||||
The pack-file MUST NOT be sent if the only command used is 'delete'.
|
||||
|
||||
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
|
||||
it will run any update hooks to make sure that the update is acceptable.
|
||||
If all of that is fine, the server will then update the references.
|
||||
|
||||
Push Certificate
|
||||
----------------
|
||||
|
||||
A push certificate begins with a set of header lines. After the
|
||||
header and an empty line, the protocol commands follow, one per
|
||||
line.
|
||||
|
||||
Currently, the following header fields are defined:
|
||||
|
||||
`pusher` ident::
|
||||
Identify the GPG key in "Human Readable Name <email@address>"
|
||||
format.
|
||||
|
||||
`pushee` url::
|
||||
The repository URL (anonymized, if the URL contains
|
||||
authentication material) the user who ran `git push`
|
||||
intended to push into.
|
||||
|
||||
`nonce` nonce::
|
||||
The 'nonce' string the receiving repository asked the
|
||||
pushing user to include in the certificate, to prevent
|
||||
replay attacks.
|
||||
|
||||
The GPG signature lines are a detached signature for the contents
|
||||
recorded in the push certificate before the signature block begins.
|
||||
The detached signature is used to certify that the commands were
|
||||
given by the pusher, who must be the signer.
|
||||
|
||||
Report Status
|
||||
-------------
|
||||
|
||||
|
@ -18,8 +18,8 @@ was sent. Server MUST NOT ignore capabilities that client requested
|
||||
and server advertised. As a consequence of these rules, server MUST
|
||||
NOT advertise capabilities it does not understand.
|
||||
|
||||
The 'report-status', 'delete-refs', and 'quiet' capabilities are sent and
|
||||
recognized by the receive-pack (push to server) process.
|
||||
The 'report-status', 'delete-refs', 'quiet', and 'push-cert' capabilities
|
||||
are sent and recognized by the receive-pack (push to server) process.
|
||||
|
||||
The 'ofs-delta' and 'side-band-64k' capabilities are sent and recognized
|
||||
by both upload-pack and receive-pack protocols. The 'agent' capability
|
||||
@ -250,3 +250,12 @@ allow-tip-sha1-in-want
|
||||
If the upload-pack server advertises this capability, fetch-pack may
|
||||
send "want" lines with SHA-1s that exist at the server but are not
|
||||
advertised by upload-pack.
|
||||
|
||||
push-cert=<nonce>
|
||||
-----------------
|
||||
|
||||
The receive-pack server that advertises this capability is willing
|
||||
to accept a signed push certificate, and asks the <nonce> to be
|
||||
included in the push certificate. A send-pack client MUST NOT
|
||||
send a push-cert packet unless the receive-pack server advertises
|
||||
this capability.
|
||||
|
Reference in New Issue
Block a user