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'
|
|
|
|
|
2020-11-19 00:44:33 +01:00
|
|
|
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
|
tests: mark tests relying on the current default for `init.defaultBranch`
In addition to the manual adjustment to let the `linux-gcc` CI job run
the test suite with `master` and then with `main`, this patch makes sure
that GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME is set in all test scripts
that currently rely on the initial branch name being `master by default.
To determine which test scripts to mark up, the first step was to
force-set the default branch name to `master` in
- all test scripts that contain the keyword `master`,
- t4211, which expects `t/t4211/history.export` with a hard-coded ref to
initialize the default branch,
- t5560 because it sources `t/t556x_common` which uses `master`,
- t8002 and t8012 because both source `t/annotate-tests.sh` which also
uses `master`)
This trick was performed by this command:
$ sed -i '/^ *\. \.\/\(test-lib\|lib-\(bash\|cvs\|git-svn\)\|gitweb-lib\)\.sh$/i\
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=master\
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\
' $(git grep -l master t/t[0-9]*.sh) \
t/t4211*.sh t/t5560*.sh t/t8002*.sh t/t8012*.sh
After that, careful, manual inspection revealed that some of the test
scripts containing the needle `master` do not actually rely on a
specific default branch name: either they mention `master` only in a
comment, or they initialize that branch specificially, or they do not
actually refer to the current default branch. Therefore, the
aforementioned modification was undone in those test scripts thusly:
$ git checkout HEAD -- \
t/t0027-auto-crlf.sh t/t0060-path-utils.sh \
t/t1011-read-tree-sparse-checkout.sh \
t/t1305-config-include.sh t/t1309-early-config.sh \
t/t1402-check-ref-format.sh t/t1450-fsck.sh \
t/t2024-checkout-dwim.sh \
t/t2106-update-index-assume-unchanged.sh \
t/t3040-subprojects-basic.sh t/t3301-notes.sh \
t/t3308-notes-merge.sh t/t3423-rebase-reword.sh \
t/t3436-rebase-more-options.sh \
t/t4015-diff-whitespace.sh t/t4257-am-interactive.sh \
t/t5323-pack-redundant.sh t/t5401-update-hooks.sh \
t/t5511-refspec.sh t/t5526-fetch-submodules.sh \
t/t5529-push-errors.sh t/t5530-upload-pack-error.sh \
t/t5548-push-porcelain.sh \
t/t5552-skipping-fetch-negotiator.sh \
t/t5572-pull-submodule.sh t/t5608-clone-2gb.sh \
t/t5614-clone-submodules-shallow.sh \
t/t7508-status.sh t/t7606-merge-custom.sh \
t/t9302-fast-import-unpack-limit.sh
We excluded one set of test scripts in these commands, though: the range
of `git p4` tests. The reason? `git p4` stores the (foreign) remote
branch in the branch called `p4/master`, which is obviously not the
default branch. Manual analysis revealed that only five of these tests
actually require a specific default branch name to pass; They were
modified thusly:
$ sed -i '/^ *\. \.\/lib-git-p4\.sh$/i\
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=master\
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\
' t/t980[0167]*.sh t/t9811*.sh
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-11-19 00:44:19 +01:00
|
|
|
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
|
|
|
|
|
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-lib.sh
|
|
|
|
. "$TEST_DIRECTORY"/lib-gpg.sh
|
|
|
|
|
|
|
|
prepare_dst () {
|
|
|
|
rm -fr dst &&
|
|
|
|
test_create_repo dst &&
|
|
|
|
|
2020-11-19 00:44:33 +01:00
|
|
|
git push dst main:noop main:ff main:noff
|
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 setup '
|
2020-11-19 00:44:33 +01:00
|
|
|
# main, ff and noff branches pointing at the same commit
|
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_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 &&
|
2022-03-17 11:13:14 +01:00
|
|
|
test_hook -C dst post-receive <<-\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
|
|
|
# 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 &&
|
2022-03-17 11:13:14 +01:00
|
|
|
test_hook -C dst post-receive <<-\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
|
|
|
# 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 &&
|
|
|
|
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 &&
|
|
|
|
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 &&
|
2022-03-17 11:13:14 +01:00
|
|
|
test_hook -C dst post-receive <<-\EOF &&
|
2014-08-18 22:46:58 +02:00
|
|
|
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 &&
|
2014-08-22 01:45:30 +02:00
|
|
|
git -C dst config receive.certnonceseed sekrit &&
|
2022-03-17 11:13:14 +01:00
|
|
|
test_hook -C dst post-receive <<-\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
|
|
|
# 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
|
|
|
'
|
|
|
|
|
2021-09-10 22:07:41 +02:00
|
|
|
test_expect_success GPGSSH 'ssh signed push sends push certificate' '
|
|
|
|
prepare_dst &&
|
|
|
|
git -C dst config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" &&
|
|
|
|
git -C dst config receive.certnonceseed sekrit &&
|
2022-03-17 11:13:14 +01:00
|
|
|
test_hook -C dst post-receive <<-\EOF &&
|
2021-09-10 22:07:41 +02:00
|
|
|
# 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 gpg.format ssh &&
|
|
|
|
test_config user.signingkey "${GPGSSH_KEY_PRIMARY}" &&
|
|
|
|
FINGERPRINT=$(ssh-keygen -lf "${GPGSSH_KEY_PRIMARY}" | awk "{print \$2;}") &&
|
|
|
|
git push --signed dst noop ff +noff &&
|
|
|
|
|
|
|
|
(
|
|
|
|
cat <<-\EOF &&
|
|
|
|
SIGNER=principal with number 1
|
|
|
|
KEY=FINGERPRINT
|
|
|
|
STATUS=G
|
|
|
|
NONCE_STATUS=OK
|
|
|
|
EOF
|
|
|
|
sed -n -e "s/^nonce /NONCE=/p" -e "/^$/q" dst/push-cert
|
|
|
|
) | sed -e "s|FINGERPRINT|$FINGERPRINT|" >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
|
|
|
|
'
|
|
|
|
|
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 &&
|
|
|
|
git -C dst config receive.certnonceseed sekrit &&
|
2022-03-17 11:13:14 +01:00
|
|
|
test_hook -C dst post-receive <<-\EOF &&
|
2014-10-22 16:57:49 +02:00
|
|
|
# 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 &&
|
|
|
|
git -C dst config receive.certnonceseed sekrit &&
|
2022-03-17 11:13:14 +01:00
|
|
|
test_hook -C dst post-receive <<-\EOF &&
|
2018-07-20 10:28:07 +02:00
|
|
|
# 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
|
|
|
|
'
|
|
|
|
|
2021-09-10 22:07:41 +02:00
|
|
|
test_expect_success GPGSSH 'fail without key and heed user.signingkey ssh' '
|
|
|
|
test_config gpg.format ssh &&
|
|
|
|
prepare_dst &&
|
|
|
|
git -C dst config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" &&
|
|
|
|
git -C dst config receive.certnonceseed sekrit &&
|
2022-03-17 11:13:14 +01:00
|
|
|
test_hook -C dst post-receive <<-\EOF &&
|
2021-09-10 22:07:41 +02:00
|
|
|
# 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 gpg.format ssh &&
|
|
|
|
test_config user.signingkey "" &&
|
|
|
|
(
|
|
|
|
sane_unset GIT_COMMITTER_EMAIL &&
|
|
|
|
test_must_fail git push --signed dst noop ff +noff
|
|
|
|
) &&
|
|
|
|
test_config user.signingkey "${GPGSSH_KEY_PRIMARY}" &&
|
|
|
|
FINGERPRINT=$(ssh-keygen -lf "${GPGSSH_KEY_PRIMARY}" | awk "{print \$2;}") &&
|
|
|
|
git push --signed dst noop ff +noff &&
|
|
|
|
|
|
|
|
(
|
|
|
|
cat <<-\EOF &&
|
|
|
|
SIGNER=principal with number 1
|
|
|
|
KEY=FINGERPRINT
|
|
|
|
STATUS=G
|
|
|
|
NONCE_STATUS=OK
|
|
|
|
EOF
|
|
|
|
sed -n -e "s/^nonce /NONCE=/p" -e "/^$/q" dst/push-cert
|
|
|
|
) | sed -e "s|FINGERPRINT|$FINGERPRINT|" >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
|
|
|
|
'
|
|
|
|
|
2020-09-19 16:47:50 +02:00
|
|
|
test_expect_success GPG 'failed atomic push does not execute GPG' '
|
|
|
|
prepare_dst &&
|
|
|
|
git -C dst config receive.certnonceseed sekrit &&
|
|
|
|
write_script gpg <<-EOF &&
|
|
|
|
# should check atomic push locally before running GPG.
|
|
|
|
exit 1
|
|
|
|
EOF
|
|
|
|
test_must_fail env PATH="$TRASH_DIRECTORY:$PATH" git push \
|
|
|
|
--signed --atomic --porcelain \
|
t5534: split stdout and stderr redirection
On atomic pushing failure with GnuPG, we expect a very specific output
in stdout due to `--porcelain` switch.
On such failure, we also write down some helpful hint into stderr
in order to help user understand what happens and how to continue from
those failures.
On a lot of system, those hint (in stderr) will be flushed first,
then those messages in stdout will be flushed. In such systems, the
current test code is fine as is.
However, we don't have such guarantee, (at least) there're some real
systems that writes those stream interleaved. On such systems, we may
see the stderr stream written in the middle of stdout stream.
Let's split those stream redirection. By splitting those stream,
the output stream will contain exactly what we want to compare,
thus, saving us a "sed" invocation.
While we're at it, change the `test_i18ncmp` to `test_cmp` because we
will never translate those messages (because of `--porcelain`).
Signed-off-by: Đoàn Trần Công Danh <congdanhqx@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-10-06 17:08:18 +02:00
|
|
|
dst noop ff noff >out 2>err &&
|
2020-09-19 16:47:50 +02:00
|
|
|
|
t5534: split stdout and stderr redirection
On atomic pushing failure with GnuPG, we expect a very specific output
in stdout due to `--porcelain` switch.
On such failure, we also write down some helpful hint into stderr
in order to help user understand what happens and how to continue from
those failures.
On a lot of system, those hint (in stderr) will be flushed first,
then those messages in stdout will be flushed. In such systems, the
current test code is fine as is.
However, we don't have such guarantee, (at least) there're some real
systems that writes those stream interleaved. On such systems, we may
see the stderr stream written in the middle of stdout stream.
Let's split those stream redirection. By splitting those stream,
the output stream will contain exactly what we want to compare,
thus, saving us a "sed" invocation.
While we're at it, change the `test_i18ncmp` to `test_cmp` because we
will never translate those messages (because of `--porcelain`).
Signed-off-by: Đoàn Trần Công Danh <congdanhqx@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-10-06 17:08:18 +02:00
|
|
|
test_i18ngrep ! "gpg failed to sign" err &&
|
2020-09-19 16:47:50 +02:00
|
|
|
cat >expect <<-EOF &&
|
|
|
|
To dst
|
|
|
|
= refs/heads/noop:refs/heads/noop [up to date]
|
|
|
|
! refs/heads/ff:refs/heads/ff [rejected] (atomic push failed)
|
|
|
|
! refs/heads/noff:refs/heads/noff [rejected] (non-fast-forward)
|
|
|
|
Done
|
|
|
|
EOF
|
t5534: split stdout and stderr redirection
On atomic pushing failure with GnuPG, we expect a very specific output
in stdout due to `--porcelain` switch.
On such failure, we also write down some helpful hint into stderr
in order to help user understand what happens and how to continue from
those failures.
On a lot of system, those hint (in stderr) will be flushed first,
then those messages in stdout will be flushed. In such systems, the
current test code is fine as is.
However, we don't have such guarantee, (at least) there're some real
systems that writes those stream interleaved. On such systems, we may
see the stderr stream written in the middle of stdout stream.
Let's split those stream redirection. By splitting those stream,
the output stream will contain exactly what we want to compare,
thus, saving us a "sed" invocation.
While we're at it, change the `test_i18ncmp` to `test_cmp` because we
will never translate those messages (because of `--porcelain`).
Signed-off-by: Đoàn Trần Công Danh <congdanhqx@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-10-06 17:08:18 +02:00
|
|
|
test_cmp expect out
|
2020-09-19 16:47:50 +02:00
|
|
|
'
|
|
|
|
|
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
|