Skip to content

Commit

Permalink
Merge branch 'jc/push-cert'
Browse files Browse the repository at this point in the history
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
  ...
  • Loading branch information
gitster committed Oct 8, 2014
2 parents 325602c + 6f5ef44 commit fb06b52
Show file tree
Hide file tree
Showing 23 changed files with 932 additions and 159 deletions.
19 changes: 19 additions & 0 deletions Documentation/config.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
9 changes: 8 additions & 1 deletion Documentation/git-push.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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>...]]

Expand Down Expand Up @@ -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
Expand Down
65 changes: 64 additions & 1 deletion Documentation/git-receive-pack.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down Expand Up @@ -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.
Expand All @@ -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
Expand Down
49 changes: 46 additions & 3 deletions Documentation/technical/pack-protocol.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)

Expand All @@ -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,
Expand All @@ -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
-------------

Expand Down
13 changes: 11 additions & 2 deletions Documentation/technical/protocol-capabilities.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
1 change: 1 addition & 0 deletions builtin/push.c
Original file line number Diff line number Diff line change
Expand Up @@ -506,6 +506,7 @@ int cmd_push(int argc, const char **argv, const char *prefix)
OPT_BIT(0, "no-verify", &flags, N_("bypass pre-push hook"), TRANSPORT_PUSH_NO_HOOK),
OPT_BIT(0, "follow-tags", &flags, N_("push missing but relevant tags"),
TRANSPORT_PUSH_FOLLOW_TAGS),
OPT_BIT(0, "signed", &flags, N_("GPG sign the push"), TRANSPORT_PUSH_CERT),
OPT_END()
};

Expand Down
Loading

0 comments on commit fb06b52

Please sign in to comment.