push: the beginning of "git push --signed"
While signed tags and commits assert that the objects thusly signed
came from you, who signed these objects, there is not a good way to
assert that you wanted to have a particular object at the tip of a
particular branch. My signing v2.0.1 tag only means I want to call
the version v2.0.1, and it does not mean I want to push it out to my
'master' branch---it is likely that I only want it in 'maint', so
the signature on the object alone is insufficient.
The only assurance to you that 'maint' points at what I wanted to
place there comes from your trust on the hosting site and my
authentication with it, which cannot easily audited later.
Introduce a mechanism that allows you to sign a "push certificate"
(for the lack of better name) every time you push, asserting that
what object you are pushing to update which ref that used to point
at what other object. Think of it as a cryptographic protection for
ref updates, similar to signed tags/commits but working on an
orthogonal axis.
The basic flow based on this mechanism goes like this:
1. You push out your work with "git push --signed".
2. The sending side learns where the remote refs are as usual,
together with what protocol extension the receiving end
supports. If the receiving end does not advertise the protocol
extension "push-cert", an attempt to "git push --signed" fails.
Otherwise, a text file, that looks like the following, is
prepared in core:
certificate version 0.1
pusher Junio C Hamano <gitster@pobox.com> 1315427886 -0700
7339ca65... 21580ecb... refs/heads/master
3793ac56... 12850bec... refs/heads/next
The file begins with a few header lines, which may grow as we
gain more experience. The 'pusher' header records the name of
the signer (the value of user.signingkey configuration variable,
falling back to GIT_COMMITTER_{NAME|EMAIL}) and the time of the
certificate generation. After the header, a blank line follows,
followed by a copy of the protocol message lines.
Each line shows the old and the new object name at the tip of
the ref this push tries to update, in the way identical to how
the underlying "git push" protocol exchange tells the ref
updates to the receiving end (by recording the "old" object
name, the push certificate also protects against replaying). It
is expected that new command packet types other than the
old-new-refname kind will be included in push certificate in the
same way as would appear in the plain vanilla command packets in
unsigned pushes.
The user then is asked to sign this push certificate using GPG,
formatted in a way similar to how signed tag objects are signed,
and the result is sent to the other side (i.e. receive-pack).
In the protocol exchange, this step comes immediately before the
sender tells what the result of the push should be, which in
turn comes before it sends the pack data.
3. When the receiving end sees a push certificate, the certificate
is written out as a blob. The pre-receive hook can learn about
the certificate by checking GIT_PUSH_CERT environment variable,
which, if present, tells the object name of this blob, and make
the decision to allow or reject this push. Additionally, the
post-receive hook can also look at the certificate, which may be
a good place to log all the received certificates for later
audits.
Because a push certificate carry the same information as the usual
command packets in the protocol exchange, we can omit the latter
when a push certificate is in use and reduce the protocol overhead.
This however is not included in this patch to make it easier to
review (in other words, the series at this step should never be
released without the remainder of the series, as it implements an
interim protocol that will be incompatible with the final one).
As such, the documentation update for the protocol is left out of
this step.
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2014-09-12 20:17:07 +02:00
|
|
|
#!/bin/sh
|
|
|
|
|
|
|
|
test_description='signed push'
|
|
|
|
|
|
|
|
. ./test-lib.sh
|
|
|
|
. "$TEST_DIRECTORY"/lib-gpg.sh
|
|
|
|
|
|
|
|
prepare_dst () {
|
|
|
|
rm -fr dst &&
|
|
|
|
test_create_repo dst &&
|
|
|
|
|
|
|
|
git push dst master:noop master:ff master:noff
|
|
|
|
}
|
|
|
|
|
|
|
|
test_expect_success setup '
|
|
|
|
# master, ff and noff branches pointing at the same commit
|
|
|
|
test_tick &&
|
|
|
|
git commit --allow-empty -m initial &&
|
|
|
|
|
|
|
|
git checkout -b noop &&
|
|
|
|
git checkout -b ff &&
|
|
|
|
git checkout -b noff &&
|
|
|
|
|
|
|
|
# noop stays the same, ff advances, noff rewrites
|
|
|
|
test_tick &&
|
|
|
|
git commit --allow-empty --amend -m rewritten &&
|
|
|
|
git checkout ff &&
|
|
|
|
|
|
|
|
test_tick &&
|
|
|
|
git commit --allow-empty -m second
|
|
|
|
'
|
|
|
|
|
|
|
|
test_expect_success 'unsigned push does not send push certificate' '
|
|
|
|
prepare_dst &&
|
|
|
|
mkdir -p dst/.git/hooks &&
|
|
|
|
write_script dst/.git/hooks/post-receive <<-\EOF &&
|
|
|
|
# discard the update list
|
|
|
|
cat >/dev/null
|
|
|
|
# record the push certificate
|
|
|
|
if test -n "${GIT_PUSH_CERT-}"
|
|
|
|
then
|
|
|
|
git cat-file blob $GIT_PUSH_CERT >../push-cert
|
|
|
|
fi
|
|
|
|
EOF
|
|
|
|
|
|
|
|
git push dst noop ff +noff &&
|
|
|
|
! test -f dst/push-cert
|
|
|
|
'
|
|
|
|
|
|
|
|
test_expect_success 'talking with a receiver without push certificate support' '
|
|
|
|
prepare_dst &&
|
|
|
|
mkdir -p dst/.git/hooks &&
|
|
|
|
write_script dst/.git/hooks/post-receive <<-\EOF &&
|
|
|
|
# discard the update list
|
|
|
|
cat >/dev/null
|
|
|
|
# record the push certificate
|
|
|
|
if test -n "${GIT_PUSH_CERT-}"
|
|
|
|
then
|
|
|
|
git cat-file blob $GIT_PUSH_CERT >../push-cert
|
|
|
|
fi
|
|
|
|
EOF
|
|
|
|
|
|
|
|
git push dst noop ff +noff &&
|
|
|
|
! test -f dst/push-cert
|
|
|
|
'
|
|
|
|
|
|
|
|
test_expect_success 'push --signed fails with a receiver without push certificate support' '
|
|
|
|
prepare_dst &&
|
|
|
|
mkdir -p dst/.git/hooks &&
|
|
|
|
test_must_fail git push --signed dst noop ff +noff 2>err &&
|
|
|
|
test_i18ngrep "the receiving end does not support" err
|
|
|
|
'
|
|
|
|
|
2017-08-07 20:20:48 +02:00
|
|
|
test_expect_success 'push --signed=1 is accepted' '
|
2017-08-07 20:20:46 +02:00
|
|
|
prepare_dst &&
|
|
|
|
mkdir -p dst/.git/hooks &&
|
|
|
|
test_must_fail git push --signed=1 dst noop ff +noff 2>err &&
|
|
|
|
test_i18ngrep "the receiving end does not support" err
|
|
|
|
'
|
|
|
|
|
2014-08-18 22:46:58 +02:00
|
|
|
test_expect_success GPG 'no certificate for a signed push with no update' '
|
|
|
|
prepare_dst &&
|
|
|
|
mkdir -p dst/.git/hooks &&
|
|
|
|
write_script dst/.git/hooks/post-receive <<-\EOF &&
|
|
|
|
if test -n "${GIT_PUSH_CERT-}"
|
|
|
|
then
|
|
|
|
git cat-file blob $GIT_PUSH_CERT >../push-cert
|
|
|
|
fi
|
|
|
|
EOF
|
|
|
|
git push dst noop &&
|
|
|
|
! test -f dst/push-cert
|
|
|
|
'
|
|
|
|
|
push: the beginning of "git push --signed"
While signed tags and commits assert that the objects thusly signed
came from you, who signed these objects, there is not a good way to
assert that you wanted to have a particular object at the tip of a
particular branch. My signing v2.0.1 tag only means I want to call
the version v2.0.1, and it does not mean I want to push it out to my
'master' branch---it is likely that I only want it in 'maint', so
the signature on the object alone is insufficient.
The only assurance to you that 'maint' points at what I wanted to
place there comes from your trust on the hosting site and my
authentication with it, which cannot easily audited later.
Introduce a mechanism that allows you to sign a "push certificate"
(for the lack of better name) every time you push, asserting that
what object you are pushing to update which ref that used to point
at what other object. Think of it as a cryptographic protection for
ref updates, similar to signed tags/commits but working on an
orthogonal axis.
The basic flow based on this mechanism goes like this:
1. You push out your work with "git push --signed".
2. The sending side learns where the remote refs are as usual,
together with what protocol extension the receiving end
supports. If the receiving end does not advertise the protocol
extension "push-cert", an attempt to "git push --signed" fails.
Otherwise, a text file, that looks like the following, is
prepared in core:
certificate version 0.1
pusher Junio C Hamano <gitster@pobox.com> 1315427886 -0700
7339ca65... 21580ecb... refs/heads/master
3793ac56... 12850bec... refs/heads/next
The file begins with a few header lines, which may grow as we
gain more experience. The 'pusher' header records the name of
the signer (the value of user.signingkey configuration variable,
falling back to GIT_COMMITTER_{NAME|EMAIL}) and the time of the
certificate generation. After the header, a blank line follows,
followed by a copy of the protocol message lines.
Each line shows the old and the new object name at the tip of
the ref this push tries to update, in the way identical to how
the underlying "git push" protocol exchange tells the ref
updates to the receiving end (by recording the "old" object
name, the push certificate also protects against replaying). It
is expected that new command packet types other than the
old-new-refname kind will be included in push certificate in the
same way as would appear in the plain vanilla command packets in
unsigned pushes.
The user then is asked to sign this push certificate using GPG,
formatted in a way similar to how signed tag objects are signed,
and the result is sent to the other side (i.e. receive-pack).
In the protocol exchange, this step comes immediately before the
sender tells what the result of the push should be, which in
turn comes before it sends the pack data.
3. When the receiving end sees a push certificate, the certificate
is written out as a blob. The pre-receive hook can learn about
the certificate by checking GIT_PUSH_CERT environment variable,
which, if present, tells the object name of this blob, and make
the decision to allow or reject this push. Additionally, the
post-receive hook can also look at the certificate, which may be
a good place to log all the received certificates for later
audits.
Because a push certificate carry the same information as the usual
command packets in the protocol exchange, we can omit the latter
when a push certificate is in use and reduce the protocol overhead.
This however is not included in this patch to make it easier to
review (in other words, the series at this step should never be
released without the remainder of the series, as it implements an
interim protocol that will be incompatible with the final one).
As such, the documentation update for the protocol is left out of
this step.
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2014-09-12 20:17:07 +02:00
|
|
|
test_expect_success GPG 'signed push sends push certificate' '
|
|
|
|
prepare_dst &&
|
|
|
|
mkdir -p dst/.git/hooks &&
|
2014-08-22 01:45:30 +02:00
|
|
|
git -C dst config receive.certnonceseed sekrit &&
|
push: the beginning of "git push --signed"
While signed tags and commits assert that the objects thusly signed
came from you, who signed these objects, there is not a good way to
assert that you wanted to have a particular object at the tip of a
particular branch. My signing v2.0.1 tag only means I want to call
the version v2.0.1, and it does not mean I want to push it out to my
'master' branch---it is likely that I only want it in 'maint', so
the signature on the object alone is insufficient.
The only assurance to you that 'maint' points at what I wanted to
place there comes from your trust on the hosting site and my
authentication with it, which cannot easily audited later.
Introduce a mechanism that allows you to sign a "push certificate"
(for the lack of better name) every time you push, asserting that
what object you are pushing to update which ref that used to point
at what other object. Think of it as a cryptographic protection for
ref updates, similar to signed tags/commits but working on an
orthogonal axis.
The basic flow based on this mechanism goes like this:
1. You push out your work with "git push --signed".
2. The sending side learns where the remote refs are as usual,
together with what protocol extension the receiving end
supports. If the receiving end does not advertise the protocol
extension "push-cert", an attempt to "git push --signed" fails.
Otherwise, a text file, that looks like the following, is
prepared in core:
certificate version 0.1
pusher Junio C Hamano <gitster@pobox.com> 1315427886 -0700
7339ca65... 21580ecb... refs/heads/master
3793ac56... 12850bec... refs/heads/next
The file begins with a few header lines, which may grow as we
gain more experience. The 'pusher' header records the name of
the signer (the value of user.signingkey configuration variable,
falling back to GIT_COMMITTER_{NAME|EMAIL}) and the time of the
certificate generation. After the header, a blank line follows,
followed by a copy of the protocol message lines.
Each line shows the old and the new object name at the tip of
the ref this push tries to update, in the way identical to how
the underlying "git push" protocol exchange tells the ref
updates to the receiving end (by recording the "old" object
name, the push certificate also protects against replaying). It
is expected that new command packet types other than the
old-new-refname kind will be included in push certificate in the
same way as would appear in the plain vanilla command packets in
unsigned pushes.
The user then is asked to sign this push certificate using GPG,
formatted in a way similar to how signed tag objects are signed,
and the result is sent to the other side (i.e. receive-pack).
In the protocol exchange, this step comes immediately before the
sender tells what the result of the push should be, which in
turn comes before it sends the pack data.
3. When the receiving end sees a push certificate, the certificate
is written out as a blob. The pre-receive hook can learn about
the certificate by checking GIT_PUSH_CERT environment variable,
which, if present, tells the object name of this blob, and make
the decision to allow or reject this push. Additionally, the
post-receive hook can also look at the certificate, which may be
a good place to log all the received certificates for later
audits.
Because a push certificate carry the same information as the usual
command packets in the protocol exchange, we can omit the latter
when a push certificate is in use and reduce the protocol overhead.
This however is not included in this patch to make it easier to
review (in other words, the series at this step should never be
released without the remainder of the series, as it implements an
interim protocol that will be incompatible with the final one).
As such, the documentation update for the protocol is left out of
this step.
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2014-09-12 20:17:07 +02:00
|
|
|
write_script dst/.git/hooks/post-receive <<-\EOF &&
|
|
|
|
# discard the update list
|
|
|
|
cat >/dev/null
|
|
|
|
# record the push certificate
|
|
|
|
if test -n "${GIT_PUSH_CERT-}"
|
|
|
|
then
|
|
|
|
git cat-file blob $GIT_PUSH_CERT >../push-cert
|
2014-08-15 00:59:21 +02:00
|
|
|
fi &&
|
|
|
|
|
|
|
|
cat >../push-cert-status <<E_O_F
|
|
|
|
SIGNER=${GIT_PUSH_CERT_SIGNER-nobody}
|
|
|
|
KEY=${GIT_PUSH_CERT_KEY-nokey}
|
|
|
|
STATUS=${GIT_PUSH_CERT_STATUS-nostatus}
|
2014-08-22 01:45:30 +02:00
|
|
|
NONCE_STATUS=${GIT_PUSH_CERT_NONCE_STATUS-nononcestatus}
|
|
|
|
NONCE=${GIT_PUSH_CERT_NONCE-nononce}
|
2014-08-15 00:59:21 +02:00
|
|
|
E_O_F
|
|
|
|
|
|
|
|
EOF
|
|
|
|
|
push: the beginning of "git push --signed"
While signed tags and commits assert that the objects thusly signed
came from you, who signed these objects, there is not a good way to
assert that you wanted to have a particular object at the tip of a
particular branch. My signing v2.0.1 tag only means I want to call
the version v2.0.1, and it does not mean I want to push it out to my
'master' branch---it is likely that I only want it in 'maint', so
the signature on the object alone is insufficient.
The only assurance to you that 'maint' points at what I wanted to
place there comes from your trust on the hosting site and my
authentication with it, which cannot easily audited later.
Introduce a mechanism that allows you to sign a "push certificate"
(for the lack of better name) every time you push, asserting that
what object you are pushing to update which ref that used to point
at what other object. Think of it as a cryptographic protection for
ref updates, similar to signed tags/commits but working on an
orthogonal axis.
The basic flow based on this mechanism goes like this:
1. You push out your work with "git push --signed".
2. The sending side learns where the remote refs are as usual,
together with what protocol extension the receiving end
supports. If the receiving end does not advertise the protocol
extension "push-cert", an attempt to "git push --signed" fails.
Otherwise, a text file, that looks like the following, is
prepared in core:
certificate version 0.1
pusher Junio C Hamano <gitster@pobox.com> 1315427886 -0700
7339ca65... 21580ecb... refs/heads/master
3793ac56... 12850bec... refs/heads/next
The file begins with a few header lines, which may grow as we
gain more experience. The 'pusher' header records the name of
the signer (the value of user.signingkey configuration variable,
falling back to GIT_COMMITTER_{NAME|EMAIL}) and the time of the
certificate generation. After the header, a blank line follows,
followed by a copy of the protocol message lines.
Each line shows the old and the new object name at the tip of
the ref this push tries to update, in the way identical to how
the underlying "git push" protocol exchange tells the ref
updates to the receiving end (by recording the "old" object
name, the push certificate also protects against replaying). It
is expected that new command packet types other than the
old-new-refname kind will be included in push certificate in the
same way as would appear in the plain vanilla command packets in
unsigned pushes.
The user then is asked to sign this push certificate using GPG,
formatted in a way similar to how signed tag objects are signed,
and the result is sent to the other side (i.e. receive-pack).
In the protocol exchange, this step comes immediately before the
sender tells what the result of the push should be, which in
turn comes before it sends the pack data.
3. When the receiving end sees a push certificate, the certificate
is written out as a blob. The pre-receive hook can learn about
the certificate by checking GIT_PUSH_CERT environment variable,
which, if present, tells the object name of this blob, and make
the decision to allow or reject this push. Additionally, the
post-receive hook can also look at the certificate, which may be
a good place to log all the received certificates for later
audits.
Because a push certificate carry the same information as the usual
command packets in the protocol exchange, we can omit the latter
when a push certificate is in use and reduce the protocol overhead.
This however is not included in this patch to make it easier to
review (in other words, the series at this step should never be
released without the remainder of the series, as it implements an
interim protocol that will be incompatible with the final one).
As such, the documentation update for the protocol is left out of
this step.
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2014-09-12 20:17:07 +02:00
|
|
|
git push --signed dst noop ff +noff &&
|
2014-08-22 01:45:30 +02:00
|
|
|
|
|
|
|
(
|
|
|
|
cat <<-\EOF &&
|
|
|
|
SIGNER=C O Mitter <committer@example.com>
|
|
|
|
KEY=13B6F51ECDDE430D
|
|
|
|
STATUS=G
|
|
|
|
NONCE_STATUS=OK
|
|
|
|
EOF
|
|
|
|
sed -n -e "s/^nonce /NONCE=/p" -e "/^$/q" dst/push-cert
|
|
|
|
) >expect &&
|
|
|
|
|
t5534: fix misleading grep invocation
It seems to be a little-known feature of `grep` (and it certainly came
as a surprise to this here developer who believed to know the Unix tools
pretty well) that multiple patterns can be passed in the same
command-line argument simply by separating them by newlines. Watch, and
learn:
$ printf '1\n2\n3\n' | grep "$(printf '1\n3\n')"
1
3
That behavior also extends to patterns passed via `-e`, and it is not
modified by passing the option `-E` (but trying this with -P issues the
error "grep: the -P option only supports a single pattern").
It seems that there are more old Unix hands who are surprised by this
behavior, as grep invocations of the form
grep "$(git rev-parse A B) C" file
were introduced in a85b377d041 (push: the beginning of "git push
--signed", 2014-09-12), and later faithfully copy-edited in b9459019bbb
(push: heed user.signingkey for signed pushes, 2014-10-22).
Please note that the output of `git rev-parse A B` separates the object
IDs via *newlines*, not via spaces, and those newlines are preserved
because the interpolation is enclosed in double quotes.
As a consequence, these tests try to validate that the file contains
either A's object ID, or B's object ID followed by C, or both. Clearly,
however, what the test wanted to see is that there is a line that
contains all of them.
This is clearly unintended, and the grep invocations in question really
match too many lines.
Fix the test by avoiding the newlines in the patterns.
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2017-07-05 13:37:49 +02:00
|
|
|
noop=$(git rev-parse noop) &&
|
|
|
|
ff=$(git rev-parse ff) &&
|
|
|
|
noff=$(git rev-parse noff) &&
|
|
|
|
grep "$noop $ff refs/heads/ff" dst/push-cert &&
|
|
|
|
grep "$noop $noff refs/heads/noff" dst/push-cert &&
|
2014-08-15 00:59:21 +02:00
|
|
|
test_cmp expect dst/push-cert-status
|
push: the beginning of "git push --signed"
While signed tags and commits assert that the objects thusly signed
came from you, who signed these objects, there is not a good way to
assert that you wanted to have a particular object at the tip of a
particular branch. My signing v2.0.1 tag only means I want to call
the version v2.0.1, and it does not mean I want to push it out to my
'master' branch---it is likely that I only want it in 'maint', so
the signature on the object alone is insufficient.
The only assurance to you that 'maint' points at what I wanted to
place there comes from your trust on the hosting site and my
authentication with it, which cannot easily audited later.
Introduce a mechanism that allows you to sign a "push certificate"
(for the lack of better name) every time you push, asserting that
what object you are pushing to update which ref that used to point
at what other object. Think of it as a cryptographic protection for
ref updates, similar to signed tags/commits but working on an
orthogonal axis.
The basic flow based on this mechanism goes like this:
1. You push out your work with "git push --signed".
2. The sending side learns where the remote refs are as usual,
together with what protocol extension the receiving end
supports. If the receiving end does not advertise the protocol
extension "push-cert", an attempt to "git push --signed" fails.
Otherwise, a text file, that looks like the following, is
prepared in core:
certificate version 0.1
pusher Junio C Hamano <gitster@pobox.com> 1315427886 -0700
7339ca65... 21580ecb... refs/heads/master
3793ac56... 12850bec... refs/heads/next
The file begins with a few header lines, which may grow as we
gain more experience. The 'pusher' header records the name of
the signer (the value of user.signingkey configuration variable,
falling back to GIT_COMMITTER_{NAME|EMAIL}) and the time of the
certificate generation. After the header, a blank line follows,
followed by a copy of the protocol message lines.
Each line shows the old and the new object name at the tip of
the ref this push tries to update, in the way identical to how
the underlying "git push" protocol exchange tells the ref
updates to the receiving end (by recording the "old" object
name, the push certificate also protects against replaying). It
is expected that new command packet types other than the
old-new-refname kind will be included in push certificate in the
same way as would appear in the plain vanilla command packets in
unsigned pushes.
The user then is asked to sign this push certificate using GPG,
formatted in a way similar to how signed tag objects are signed,
and the result is sent to the other side (i.e. receive-pack).
In the protocol exchange, this step comes immediately before the
sender tells what the result of the push should be, which in
turn comes before it sends the pack data.
3. When the receiving end sees a push certificate, the certificate
is written out as a blob. The pre-receive hook can learn about
the certificate by checking GIT_PUSH_CERT environment variable,
which, if present, tells the object name of this blob, and make
the decision to allow or reject this push. Additionally, the
post-receive hook can also look at the certificate, which may be
a good place to log all the received certificates for later
audits.
Because a push certificate carry the same information as the usual
command packets in the protocol exchange, we can omit the latter
when a push certificate is in use and reduce the protocol overhead.
This however is not included in this patch to make it easier to
review (in other words, the series at this step should never be
released without the remainder of the series, as it implements an
interim protocol that will be incompatible with the final one).
As such, the documentation update for the protocol is left out of
this step.
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2014-09-12 20:17:07 +02:00
|
|
|
'
|
|
|
|
|
2017-05-09 21:23:53 +02:00
|
|
|
test_expect_success GPG 'inconsistent push options in signed push not allowed' '
|
|
|
|
# First, invoke receive-pack with dummy input to obtain its preamble.
|
|
|
|
prepare_dst &&
|
|
|
|
git -C dst config receive.certnonceseed sekrit &&
|
|
|
|
git -C dst config receive.advertisepushoptions 1 &&
|
|
|
|
printf xxxx | test_might_fail git receive-pack dst >preamble &&
|
|
|
|
|
|
|
|
# Then, invoke push. Simulate a receive-pack that sends the preamble we
|
|
|
|
# obtained, followed by a dummy packet.
|
|
|
|
write_script myscript <<-\EOF &&
|
|
|
|
cat preamble &&
|
|
|
|
printf xxxx &&
|
|
|
|
cat >push
|
|
|
|
EOF
|
|
|
|
test_might_fail git push --push-option="foo" --push-option="bar" \
|
|
|
|
--receive-pack="\"$(pwd)/myscript\"" --signed dst --delete ff &&
|
|
|
|
|
|
|
|
# Replay the push output on a fresh dst, checking that ff is truly
|
|
|
|
# deleted.
|
|
|
|
prepare_dst &&
|
|
|
|
git -C dst config receive.certnonceseed sekrit &&
|
|
|
|
git -C dst config receive.advertisepushoptions 1 &&
|
|
|
|
git receive-pack dst <push &&
|
|
|
|
test_must_fail git -C dst rev-parse ff &&
|
|
|
|
|
|
|
|
# Tweak the push output to make the push option outside the cert
|
|
|
|
# different, then replay it on a fresh dst, checking that ff is not
|
|
|
|
# deleted.
|
|
|
|
perl -pe "s/([^ ])bar/\$1baz/" push >push.tweak &&
|
|
|
|
prepare_dst &&
|
|
|
|
git -C dst config receive.certnonceseed sekrit &&
|
|
|
|
git -C dst config receive.advertisepushoptions 1 &&
|
|
|
|
git receive-pack dst <push.tweak >out &&
|
|
|
|
git -C dst rev-parse ff &&
|
|
|
|
grep "inconsistent push options" out
|
|
|
|
'
|
|
|
|
|
2014-10-22 16:57:49 +02:00
|
|
|
test_expect_success GPG 'fail without key and heed user.signingkey' '
|
|
|
|
prepare_dst &&
|
|
|
|
mkdir -p dst/.git/hooks &&
|
|
|
|
git -C dst config receive.certnonceseed sekrit &&
|
|
|
|
write_script dst/.git/hooks/post-receive <<-\EOF &&
|
|
|
|
# discard the update list
|
|
|
|
cat >/dev/null
|
|
|
|
# record the push certificate
|
|
|
|
if test -n "${GIT_PUSH_CERT-}"
|
|
|
|
then
|
|
|
|
git cat-file blob $GIT_PUSH_CERT >../push-cert
|
|
|
|
fi &&
|
|
|
|
|
|
|
|
cat >../push-cert-status <<E_O_F
|
|
|
|
SIGNER=${GIT_PUSH_CERT_SIGNER-nobody}
|
|
|
|
KEY=${GIT_PUSH_CERT_KEY-nokey}
|
|
|
|
STATUS=${GIT_PUSH_CERT_STATUS-nostatus}
|
|
|
|
NONCE_STATUS=${GIT_PUSH_CERT_NONCE_STATUS-nononcestatus}
|
|
|
|
NONCE=${GIT_PUSH_CERT_NONCE-nononce}
|
|
|
|
E_O_F
|
|
|
|
|
|
|
|
EOF
|
|
|
|
|
2018-07-20 10:28:07 +02:00
|
|
|
test_config user.email hasnokey@nowhere.com &&
|
|
|
|
(
|
|
|
|
sane_unset GIT_COMMITTER_EMAIL &&
|
|
|
|
test_must_fail git push --signed dst noop ff +noff
|
|
|
|
) &&
|
|
|
|
test_config user.signingkey $GIT_COMMITTER_EMAIL &&
|
2014-10-22 16:57:49 +02:00
|
|
|
git push --signed dst noop ff +noff &&
|
|
|
|
|
|
|
|
(
|
|
|
|
cat <<-\EOF &&
|
|
|
|
SIGNER=C O Mitter <committer@example.com>
|
|
|
|
KEY=13B6F51ECDDE430D
|
|
|
|
STATUS=G
|
|
|
|
NONCE_STATUS=OK
|
|
|
|
EOF
|
|
|
|
sed -n -e "s/^nonce /NONCE=/p" -e "/^$/q" dst/push-cert
|
|
|
|
) >expect &&
|
|
|
|
|
t5534: fix misleading grep invocation
It seems to be a little-known feature of `grep` (and it certainly came
as a surprise to this here developer who believed to know the Unix tools
pretty well) that multiple patterns can be passed in the same
command-line argument simply by separating them by newlines. Watch, and
learn:
$ printf '1\n2\n3\n' | grep "$(printf '1\n3\n')"
1
3
That behavior also extends to patterns passed via `-e`, and it is not
modified by passing the option `-E` (but trying this with -P issues the
error "grep: the -P option only supports a single pattern").
It seems that there are more old Unix hands who are surprised by this
behavior, as grep invocations of the form
grep "$(git rev-parse A B) C" file
were introduced in a85b377d041 (push: the beginning of "git push
--signed", 2014-09-12), and later faithfully copy-edited in b9459019bbb
(push: heed user.signingkey for signed pushes, 2014-10-22).
Please note that the output of `git rev-parse A B` separates the object
IDs via *newlines*, not via spaces, and those newlines are preserved
because the interpolation is enclosed in double quotes.
As a consequence, these tests try to validate that the file contains
either A's object ID, or B's object ID followed by C, or both. Clearly,
however, what the test wanted to see is that there is a line that
contains all of them.
This is clearly unintended, and the grep invocations in question really
match too many lines.
Fix the test by avoiding the newlines in the patterns.
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2017-07-05 13:37:49 +02:00
|
|
|
noop=$(git rev-parse noop) &&
|
|
|
|
ff=$(git rev-parse ff) &&
|
|
|
|
noff=$(git rev-parse noff) &&
|
|
|
|
grep "$noop $ff refs/heads/ff" dst/push-cert &&
|
|
|
|
grep "$noop $noff refs/heads/noff" dst/push-cert &&
|
2014-10-22 16:57:49 +02:00
|
|
|
test_cmp expect dst/push-cert-status
|
|
|
|
'
|
|
|
|
|
2018-07-20 10:28:07 +02:00
|
|
|
test_expect_success GPGSM 'fail without key and heed user.signingkey x509' '
|
|
|
|
test_config gpg.format x509 &&
|
|
|
|
prepare_dst &&
|
|
|
|
mkdir -p dst/.git/hooks &&
|
|
|
|
git -C dst config receive.certnonceseed sekrit &&
|
|
|
|
write_script dst/.git/hooks/post-receive <<-\EOF &&
|
|
|
|
# discard the update list
|
|
|
|
cat >/dev/null
|
|
|
|
# record the push certificate
|
|
|
|
if test -n "${GIT_PUSH_CERT-}"
|
|
|
|
then
|
|
|
|
git cat-file blob $GIT_PUSH_CERT >../push-cert
|
|
|
|
fi &&
|
|
|
|
|
|
|
|
cat >../push-cert-status <<E_O_F
|
|
|
|
SIGNER=${GIT_PUSH_CERT_SIGNER-nobody}
|
|
|
|
KEY=${GIT_PUSH_CERT_KEY-nokey}
|
|
|
|
STATUS=${GIT_PUSH_CERT_STATUS-nostatus}
|
|
|
|
NONCE_STATUS=${GIT_PUSH_CERT_NONCE_STATUS-nononcestatus}
|
|
|
|
NONCE=${GIT_PUSH_CERT_NONCE-nononce}
|
|
|
|
E_O_F
|
|
|
|
|
|
|
|
EOF
|
|
|
|
|
|
|
|
test_config user.email hasnokey@nowhere.com &&
|
|
|
|
test_config user.signingkey "" &&
|
|
|
|
(
|
|
|
|
sane_unset GIT_COMMITTER_EMAIL &&
|
|
|
|
test_must_fail git push --signed dst noop ff +noff
|
|
|
|
) &&
|
|
|
|
test_config user.signingkey $GIT_COMMITTER_EMAIL &&
|
|
|
|
git push --signed dst noop ff +noff &&
|
|
|
|
|
|
|
|
(
|
|
|
|
cat <<-\EOF &&
|
|
|
|
SIGNER=/CN=C O Mitter/O=Example/SN=C O/GN=Mitter
|
|
|
|
KEY=
|
|
|
|
STATUS=G
|
|
|
|
NONCE_STATUS=OK
|
|
|
|
EOF
|
|
|
|
sed -n -e "s/^nonce /NONCE=/p" -e "/^$/q" dst/push-cert
|
|
|
|
) >expect.in &&
|
|
|
|
key=$(cat "${GNUPGHOME}/trustlist.txt" | cut -d" " -f1 | tr -d ":") &&
|
|
|
|
sed -e "s/^KEY=/KEY=${key}/" expect.in >expect &&
|
|
|
|
|
|
|
|
noop=$(git rev-parse noop) &&
|
|
|
|
ff=$(git rev-parse ff) &&
|
|
|
|
noff=$(git rev-parse noff) &&
|
|
|
|
grep "$noop $ff refs/heads/ff" dst/push-cert &&
|
|
|
|
grep "$noop $noff refs/heads/noff" dst/push-cert &&
|
|
|
|
test_cmp expect dst/push-cert-status
|
|
|
|
'
|
|
|
|
|
push: the beginning of "git push --signed"
While signed tags and commits assert that the objects thusly signed
came from you, who signed these objects, there is not a good way to
assert that you wanted to have a particular object at the tip of a
particular branch. My signing v2.0.1 tag only means I want to call
the version v2.0.1, and it does not mean I want to push it out to my
'master' branch---it is likely that I only want it in 'maint', so
the signature on the object alone is insufficient.
The only assurance to you that 'maint' points at what I wanted to
place there comes from your trust on the hosting site and my
authentication with it, which cannot easily audited later.
Introduce a mechanism that allows you to sign a "push certificate"
(for the lack of better name) every time you push, asserting that
what object you are pushing to update which ref that used to point
at what other object. Think of it as a cryptographic protection for
ref updates, similar to signed tags/commits but working on an
orthogonal axis.
The basic flow based on this mechanism goes like this:
1. You push out your work with "git push --signed".
2. The sending side learns where the remote refs are as usual,
together with what protocol extension the receiving end
supports. If the receiving end does not advertise the protocol
extension "push-cert", an attempt to "git push --signed" fails.
Otherwise, a text file, that looks like the following, is
prepared in core:
certificate version 0.1
pusher Junio C Hamano <gitster@pobox.com> 1315427886 -0700
7339ca65... 21580ecb... refs/heads/master
3793ac56... 12850bec... refs/heads/next
The file begins with a few header lines, which may grow as we
gain more experience. The 'pusher' header records the name of
the signer (the value of user.signingkey configuration variable,
falling back to GIT_COMMITTER_{NAME|EMAIL}) and the time of the
certificate generation. After the header, a blank line follows,
followed by a copy of the protocol message lines.
Each line shows the old and the new object name at the tip of
the ref this push tries to update, in the way identical to how
the underlying "git push" protocol exchange tells the ref
updates to the receiving end (by recording the "old" object
name, the push certificate also protects against replaying). It
is expected that new command packet types other than the
old-new-refname kind will be included in push certificate in the
same way as would appear in the plain vanilla command packets in
unsigned pushes.
The user then is asked to sign this push certificate using GPG,
formatted in a way similar to how signed tag objects are signed,
and the result is sent to the other side (i.e. receive-pack).
In the protocol exchange, this step comes immediately before the
sender tells what the result of the push should be, which in
turn comes before it sends the pack data.
3. When the receiving end sees a push certificate, the certificate
is written out as a blob. The pre-receive hook can learn about
the certificate by checking GIT_PUSH_CERT environment variable,
which, if present, tells the object name of this blob, and make
the decision to allow or reject this push. Additionally, the
post-receive hook can also look at the certificate, which may be
a good place to log all the received certificates for later
audits.
Because a push certificate carry the same information as the usual
command packets in the protocol exchange, we can omit the latter
when a push certificate is in use and reduce the protocol overhead.
This however is not included in this patch to make it easier to
review (in other words, the series at this step should never be
released without the remainder of the series, as it implements an
interim protocol that will be incompatible with the final one).
As such, the documentation update for the protocol is left out of
this step.
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2014-09-12 20:17:07 +02:00
|
|
|
test_done
|