Add commit & tag signing/verification via SSH keys using ssh-keygen
RE: [PATCH v6 5/9] ssh signing: parse ssh-keygen output and verify signatures
In reply to Fabian StelzerQuoted 72 lines · Fabian Stelzer
Quoted 58 lines · Randall S. Becker
Quoted 57 lines · Fabian Stelzer
Subject: Re: [PATCH v6 5/9] ssh signing: parse ssh-keygen output and verify signatures
Quoted 37 lines · Randall S. Becker
Quoted 36 lines · Fabian Stelzer
Quoted 26 lines · Randall S. Becker
Quoted 25 lines · Fabian Stelzer
Subject: Re: [PATCH v6 5/9] ssh signing: parse ssh-keygen output and verify signatures
Quoted 20 lines · Randall S. Becker
Quoted 7 lines · Junio
Quoted 4 lines · Fabian Stelzer
Quoted 1 line · Jonathan Tan
Also, is this output documented to be stable even across locales?
Not really :/ (it currently is not locale specific)
Quoted 8 lines
We probably want to defeat l10n of the message by spawning it in the C locale regardless.
Quoted 2 lines
The documentation states to only check the commands exit code. Do we trust the exit code enough to rely on it for verification?
Is the exit code sufficient to learn who signed it? Without knowing that, we cannot see if the principal is in or not in our
keychain, no?
Have we not had issues in the past depending on exit code? I'm not sure this can be made entirely portable.
Quoted 6 lines
Quoted 6 lines
To find the principal (who signed it) we don't have to parse the output. Since verification is first a call to look up the principals matching the signatures public key from the allowedSignersFile and then trying verification with each one we already know which one matched (usually there is only one. I think multiples is only possible with an SSH
CA).
Quoted 2 lines
Quoted 2 lines
Of course this even more relies on the exit code of ssh-keygen.
Quoted 9 lines
Quoted 6 lines
Quoted 4 lines
Quoted 1 line
Not sure which is more portable and reliable. Parsing the textual output or the exit code. At the moment my patch does both.
What about a configurable exit code for this? See the comment below about that.
I'm not sure what you mean. Something like "treat exit(123) as success"?
How about gpg.ssh.successExit=123 or something like that.
I don't quite understand what the benefit would be. Do you have any
specific portability problems/concerns where the ssh-keygen format is different or exit codes differ? I think using a script that provides exit(0) on success and the
correct output to wrap ssh-keygen and setting it in gpg.ssh.command can already cover edge cases when needed.Quoted 1 line
Quoted 4 lines
Quoted 4 lines
Quoted 3 lines
Is there documentation on the possible arguments the patch series will use for this so one can create a wrapper script? I had to look into
the code to find out what
GIT_SSH_COMMANDactually required when the ssh variant was "ssh". I'd rather not have to do that in thiscase.
Quoted 8 lines
Quoted 4 lines
Quoted 1 line
The documentation in ssh-keygen(1) is quite good and straight forward
for verification and signing. Again if you have any specific portability concerns i'd be glad to help.I do know the ssh-keygen interface and that does not really answer my doubts.
My point here is that ssh-keygen is not always available in the same form on all platforms. Providing a full emulation of all arguments is
Quoted 8 lines
not effective or likely even possible, and a waste of time. I'm asking for documentation on what specific options you are using for each function. OpenSSL is not available everywhere, and even where it is, the latest versions are not always available. It is important to know what the specific interface is being used.
Quoted 2 lines
Fair enough. Where would you expect to look for such documentation? I'm not sure sth like
config/gpg.txtis the right place for this.
My suggestion is wherever gpg.ssh.command is documented. So really, I think config/gpg.txt is the place. It's that or we create some common location for compatibility layer documentation (what I would really prefer). If there is a good place to put that, I might be willing to take on the documentation task, but my $DAYJOB is keeping me from anything heavy at this point.
With my thanks,
Randall
PATCH v6 6/9 ssh signing: add test prereqs
In reply to Fabian Stelzer via GitGitGadgetFrom: Fabian Stelzer <[email protected]>
generate some ssh keys and a allowedSignersFile for testing
--- t/lib-gpg.sh | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+)
t/lib-gpg.sh +29 -0
@@ -87,6 +87,35 @@ test_lazy_prereq RFC1991 ' echo | gpg --homedir "${GNUPGHOME}" -b --rfc1991 >/dev/null ' +test_lazy_prereq GPGSSH ' + ssh_version=$(ssh-keygen -Y find-principals -n "git" 2>&1) + test $? != 127 || exit 1 + echo $ssh_version | grep -q "find-principals:missing signature file" + test $? = 0 || exit 1; + mkdir -p "${GNUPGHOME}" && + chmod 0700 "${GNUPGHOME}" && + ssh-keygen -t ed25519 -N "" -C "git ed25519 key" -f "${GNUPGHOME}/ed25519_ssh_signing_key" >/dev/null && + echo "\"principal with number 1\" $(cat "${GNUPGHOME}/ed25519_ssh_signing_key.pub")" >> "${GNUPGHOME}/ssh.all_valid.allowedSignersFile" && + ssh-keygen -t rsa -b 2048 -N "" -C "git rsa2048 key" -f "${GNUPGHOME}/rsa_2048_ssh_signing_key" >/dev/null && + echo "\"principal with number 2\" $(cat "${GNUPGHOME}/rsa_2048_ssh_signing_key.pub")" >> "${GNUPGHOME}/ssh.all_valid.allowedSignersFile" && + ssh-keygen -t ed25519 -N "super_secret" -C "git ed25519 encrypted key" -f "${GNUPGHOME}/protected_ssh_signing_key" >/dev/null && + echo "\"principal with number 3\" $(cat "${GNUPGHOME}/protected_ssh_signing_key.pub")" >> "${GNUPGHOME}/ssh.all_valid.allowedSignersFile" && + cat "${GNUPGHOME}/ssh.all_valid.allowedSignersFile" && + ssh-keygen -t ed25519 -N "" -f "${GNUPGHOME}/untrusted_ssh_signing_key" >/dev/null +' + +SIGNING_KEY_PRIMARY="${GNUPGHOME}/ed25519_ssh_signing_key" +SIGNING_KEY_SECONDARY="${GNUPGHOME}/rsa_2048_ssh_signing_key" +SIGNING_KEY_UNTRUSTED="${GNUPGHOME}/untrusted_ssh_signing_key" +SIGNING_KEY_WITH_PASSPHRASE="${GNUPGHOME}/protected_ssh_signing_key" +SIGNING_KEY_PASSPHRASE="super_secret" +SIGNING_ALLOWED_SIGNERS="${GNUPGHOME}/ssh.all_valid.allowedSignersFile" + +GOOD_SIGNATURE_TRUSTED='Good "git" signature for' +GOOD_SIGNATURE_UNTRUSTED='Good "git" signature with' +KEY_NOT_TRUSTED="No principal matched" +BAD_SIGNATURE="Signature verification failed" + sanitize_pgp() { perl -ne ' /^-----END PGP/ and $in_pgp = 0;
Signature · 1 line
gitgitgadget
Re: [PATCH v6 6/9] ssh signing: add test prereqs
Quoted 36 lines · Fabian Stelzer via GitGitGadget
From: Fabian Stelzer <[email protected]>
generate some ssh keys and a allowedSignersFile for testing
Signed-off-by Fabian Stelzer <[email protected]>--- t/lib-gpg.sh | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+)t/lib-gpg.sh +19
-0@@ -87,6 +87,35 @@ test_lazy_prereq RFC1991 ' echo | gpg --homedir "${GNUPGHOME}" -b --rfc1991 >/dev/null ' +test_lazy_prereq GPGSSH ' + ssh_version=$(ssh-keygen -Y find-principals -n "git" 2>&1) + test $? != 127 || exit 1 + echo $ssh_version | grep -q "find-principals:missing signature file" + test $? = 0 || exit 1; + mkdir -p "${GNUPGHOME}" && + chmod 0700 "${GNUPGHOME}" && + ssh-keygen -t ed25519 -N "" -C "git ed25519 key" -f "${GNUPGHOME}/ed25519_ssh_signing_key" >/dev/null && + echo "\"principal with number 1\" $(cat "${GNUPGHOME}/ed25519_ssh_signing_key.pub")" >> "${GNUPGHOME}/ssh.all_valid.allowedSignersFile" && + ssh-keygen -t rsa -b 2048 -N "" -C "git rsa2048 key" -f "${GNUPGHOME}/rsa_2048_ssh_signing_key" >/dev/null && + echo "\"principal with number 2\" $(cat "${GNUPGHOME}/rsa_2048_ssh_signing_key.pub")" >> "${GNUPGHOME}/ssh.all_valid.allowedSignersFile" && + ssh-keygen -t ed25519 -N "super_secret" -C "git ed25519 encrypted key" -f "${GNUPGHOME}/protected_ssh_signing_key" >/dev/null && + echo "\"principal with number 3\" $(cat "${GNUPGHOME}/protected_ssh_signing_key.pub")" >> "${GNUPGHOME}/ssh.all_valid.allowedSignersFile" && + cat "${GNUPGHOME}/ssh.all_valid.allowedSignersFile" && + ssh-keygen -t ed25519 -N "" -f "${GNUPGHOME}/untrusted_ssh_signing_key" >/dev/null +' + +SIGNING_KEY_PRIMARY="${GNUPGHOME}/ed25519_ssh_signing_key" +SIGNING_KEY_SECONDARY="${GNUPGHOME}/rsa_2048_ssh_signing_key"
t/lib-gpg.sh test_lazy_prereq RFC1991 ' · line 118
Quoted 10 lines
t/lib-gpg.sh +10
-0+SIGNING_KEY_UNTRUSTED="${GNUPGHOME}/untrusted_ssh_signing_key" +SIGNING_KEY_WITH_PASSPHRASE="${GNUPGHOME}/protected_ssh_signing_key" +SIGNING_KEY_PASSPHRASE="super_secret" +SIGNING_ALLOWED_SIGNERS="${GNUPGHOME}/ssh.all_valid.allowedSignersFile" + +GOOD_SIGNATURE_TRUSTED='Good "git" signature for' +GOOD_SIGNATURE_UNTRUSTED='Good "git" signature with' +KEY_NOT_TRUSTED="No principal matched" +BAD_SIGNATURE="Signature verification failed" +
Is there a reason why we don't use these variables in the script above?
Also, in general I feel that it's better to add tests in the same commit where new features are added, rather than having standalone test commits.
Quoted 6 lines
t/lib-gpg.sh +0
-0sanitize_pgp() { perl -ne ' /^-----END PGP/ and $in_pgp = 0;Signature · 1 line
gitgitgadget
Re: [PATCH v6 6/9] ssh signing: add test prereqs
Quoted 11 lines · Josh Steadmon
t/lib-gpg.sh test_lazy_prereq RFC1991 ' · line 118
Quoted 5 lines
...
t/lib-gpg.sh +4
-0+GOOD_SIGNATURE_UNTRUSTED='Good "git" signature with' +KEY_NOT_TRUSTED="No principal matched" +BAD_SIGNATURE="Signature verification failed" +Is there a reason why we don't use these variables in the script above?
Also, in general I feel that it's better to add tests in the same commit where new features are added, rather than having standalone test commits.
Again, good suggestions.
Thanks for excellent reviews.
Re: [PATCH v6 6/9] ssh signing: add test prereqs
In reply to Josh SteadmonQuoted 35 lines · Josh Steadmon
Quoted 21 lines · Fabian Stelzer via GitGitGadget
From: Fabian Stelzer <[email protected]>
t/lib-gpg.sh +19
-0+test_lazy_prereq GPGSSH ' + ssh_version=$(ssh-keygen -Y find-principals -n "git" 2>&1) + test $? != 127 || exit 1 + echo $ssh_version | grep -q "find-principals:missing signature file" + test $? = 0 || exit 1; + mkdir -p "${GNUPGHOME}" && + chmod 0700 "${GNUPGHOME}" && + ssh-keygen -t ed25519 -N "" -C "git ed25519 key" -f "${GNUPGHOME}/ed25519_ssh_signing_key" >/dev/null && + echo "\"principal with number 1\" $(cat "${GNUPGHOME}/ed25519_ssh_signing_key.pub")" >> "${GNUPGHOME}/ssh.all_valid.allowedSignersFile" && + ssh-keygen -t rsa -b 2048 -N "" -C "git rsa2048 key" -f "${GNUPGHOME}/rsa_2048_ssh_signing_key" >/dev/null && + echo "\"principal with number 2\" $(cat "${GNUPGHOME}/rsa_2048_ssh_signing_key.pub")" >> "${GNUPGHOME}/ssh.all_valid.allowedSignersFile" && + ssh-keygen -t ed25519 -N "super_secret" -C "git ed25519 encrypted key" -f "${GNUPGHOME}/protected_ssh_signing_key" >/dev/null && + echo "\"principal with number 3\" $(cat "${GNUPGHOME}/protected_ssh_signing_key.pub")" >> "${GNUPGHOME}/ssh.all_valid.allowedSignersFile" && + cat "${GNUPGHOME}/ssh.all_valid.allowedSignersFile" && + ssh-keygen -t ed25519 -N "" -f "${GNUPGHOME}/untrusted_ssh_signing_key" >/dev/null +' + +SIGNING_KEY_PRIMARY="${GNUPGHOME}/ed25519_ssh_signing_key" +SIGNING_KEY_SECONDARY="${GNUPGHOME}/rsa_2048_ssh_signing_key"t/lib-gpg.sh test_lazy_prereq RFC1991 ' · line 118
Quoted 10 lines
t/lib-gpg.sh +10
-0+SIGNING_KEY_UNTRUSTED="${GNUPGHOME}/untrusted_ssh_signing_key" +SIGNING_KEY_WITH_PASSPHRASE="${GNUPGHOME}/protected_ssh_signing_key" +SIGNING_KEY_PASSPHRASE="super_secret" +SIGNING_ALLOWED_SIGNERS="${GNUPGHOME}/ssh.all_valid.allowedSignersFile" + +GOOD_SIGNATURE_TRUSTED='Good "git" signature for' +GOOD_SIGNATURE_UNTRUSTED='Good "git" signature with' +KEY_NOT_TRUSTED="No principal matched" +BAD_SIGNATURE="Signature verification failed" +Is there a reason why we don't use these variables in the script above?
Quoted 4 lines
Also, in general I feel that it's better to add tests in the same commit where new features are added, rather than having standalone test commits.
Intially i wanted to fill them in the prereq but couldn't acces them in the tests then.
Thanks, i have moved the variables above the prereq and used them there as well. makes sense. Also i have prefixed them now with GPGSSH so we don't collide with any other tests accidentally.
PATCH v6 7/9 ssh signing: duplicate t7510 tests for commits
In reply to Fabian Stelzer via GitGitGadgetFrom: Fabian Stelzer <[email protected]>
--- t/t7528-signed-commit-ssh.sh | 398 +++++++++++++++++++++++++++++++++++ 1 file changed, 398 insertions(+) create mode 100755 t/t7528-signed-commit-ssh.sh
t/t7528-signed-commit-ssh.sh new file +398 -0
@@ -0,0 +1,398 @@ +#!/bin/sh + +test_description='ssh signed commit tests' +GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main +export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME + +. ./test-lib.sh +GNUPGHOME_NOT_USED=$GNUPGHOME +. "$TEST_DIRECTORY/lib-gpg.sh" + +test_expect_success GPGSSH 'create signed commits' ' + test_oid_cache <<-\EOF && + header sha1:gpgsig + header sha256:gpgsig-sha256 + EOF + + test_when_finished "test_unconfig commit.gpgsign" && + test_config gpg.format ssh && + test_config user.signingkey "${SIGNING_KEY_PRIMARY}" && + + echo 1 >file && git add file && + test_tick && git commit -S -m initial && + git tag initial && + git branch side && + + echo 2 >file && test_tick && git commit -a -S -m second && + git tag second && + + git checkout side && + echo 3 >elif && git add elif && + test_tick && git commit -m "third on side" && + + git checkout main && + test_tick && git merge -S side && + git tag merge && + + echo 4 >file && test_tick && git commit -a -m "fourth unsigned" && + git tag fourth-unsigned && + + test_tick && git commit --amend -S -m "fourth signed" && + git tag fourth-signed && + + git config commit.gpgsign true && + echo 5 >file && test_tick && git commit -a -m "fifth signed" && + git tag fifth-signed && + + git config commit.gpgsign false && + echo 6 >file && test_tick && git commit -a -m "sixth" && + git tag sixth-unsigned && + + git config commit.gpgsign true && + echo 7 >file && test_tick && git commit -a -m "seventh" --no-gpg-sign && + git tag seventh-unsigned && + + test_tick && git rebase -f HEAD^^ && git tag sixth-signed HEAD^ && + git tag seventh-signed && + + echo 8 >file && test_tick && git commit -a -m eighth -S"${SIGNING_KEY_UNTRUSTED}" && + git tag eighth-signed-alt && + + # commit.gpgsign is still on but this must not be signed + echo 9 | git commit-tree HEAD^{tree} >oid && + test_line_count = 1 oid && + git tag ninth-unsigned $(cat oid) && + # explicit -S of course must sign. + echo 10 | git commit-tree -S HEAD^{tree} >oid && + test_line_count = 1 oid && + git tag tenth-signed $(cat oid) && + + # --gpg-sign[=<key-id>] must sign. + echo 11 | git commit-tree --gpg-sign HEAD^{tree} >oid && + test_line_count = 1 oid && + git tag eleventh-signed $(cat oid) && + echo 12 | git commit-tree --gpg-sign="${SIGNING_KEY_UNTRUSTED}" HEAD^{tree} >oid && + test_line_count = 1 oid && + git tag twelfth-signed-alt $(cat oid) +' + +test_expect_success GPGSSH 'verify and show signatures' ' + test_config gpg.ssh.allowedSignersFile "${SIGNING_ALLOWED_SIGNERS}" && + test_config gpg.mintrustlevel UNDEFINED && + ( + for commit in initial second merge fourth-signed \ + fifth-signed sixth-signed seventh-signed tenth-signed \ + eleventh-signed + do + git verify-commit $commit && + git show --pretty=short --show-signature $commit >actual && + grep "${GOOD_SIGNATURE_TRUSTED}" actual && + ! grep "${BAD_SIGNATURE}" actual && + echo $commit OK || exit 1 + done + ) && + ( + for commit in merge^2 fourth-unsigned sixth-unsigned \ + seventh-unsigned ninth-unsigned + do + test_must_fail git verify-commit $commit && + git show --pretty=short --show-signature $commit >actual && + ! grep "${GOOD_SIGNATURE_TRUSTED}" actual && + ! grep "${BAD_SIGNATURE}" actual && + echo $commit OK || exit 1 + done + ) && + ( + for commit in eighth-signed-alt twelfth-signed-alt + do + git show --pretty=short --show-signature $commit >actual && + grep "${GOOD_SIGNATURE_UNTRUSTED}" actual && + ! grep "${BAD_SIGNATURE}" actual && + grep "${KEY_NOT_TRUSTED}" actual && + echo $commit OK || exit 1 + done + ) +' + +test_expect_success GPGSSH 'verify-commit exits success on untrusted signature' ' + test_config gpg.ssh.allowedSignersFile "${SIGNING_ALLOWED_SIGNERS}" && + git verify-commit eighth-signed-alt 2>actual && + grep "${GOOD_SIGNATURE_UNTRUSTED}" actual && + ! grep "${BAD_SIGNATURE}" actual && + grep "${KEY_NOT_TRUSTED}" actual +' + +test_expect_success GPGSSH 'verify-commit exits success with matching minTrustLevel' ' + test_config gpg.ssh.allowedSignersFile "${SIGNING_ALLOWED_SIGNERS}" && + test_config gpg.minTrustLevel fully && + git verify-commit sixth-signed +' + +test_expect_success GPGSSH 'verify-commit exits success with low minTrustLevel' ' + test_config gpg.ssh.allowedSignersFile "${SIGNING_ALLOWED_SIGNERS}" && + test_config gpg.minTrustLevel marginal && + git verify-commit sixth-signed +' + +test_expect_success GPGSSH 'verify-commit exits failure with high minTrustLevel' ' + test_config gpg.minTrustLevel ultimate && + test_must_fail git verify-commit eighth-signed-alt +' + +test_expect_success GPGSSH 'verify signatures with --raw' ' + test_config gpg.ssh.allowedSignersFile "${SIGNING_ALLOWED_SIGNERS}" && + ( + for commit in initial second merge fourth-signed fifth-signed sixth-signed seventh-signed + do + git verify-commit --raw $commit 2>actual && + grep "${GOOD_SIGNATURE_TRUSTED}" actual && + ! grep "${BAD_SIGNATURE}" actual && + echo $commit OK || exit 1 + done + ) && + ( + for commit in merge^2 fourth-unsigned sixth-unsigned seventh-unsigned + do + test_must_fail git verify-commit --raw $commit 2>actual && + ! grep "${GOOD_SIGNATURE_TRUSTED}" actual && + ! grep "${BAD_SIGNATURE}" actual && + echo $commit OK || exit 1 + done + ) && + ( + for commit in eighth-signed-alt + do + git verify-commit --raw $commit 2>actual && + grep "${GOOD_SIGNATURE_UNTRUSTED}" actual && + ! grep "${BAD_SIGNATURE}" actual && + echo $commit OK || exit 1 + done + ) +' + +test_expect_success GPGSSH 'proper header is used for hash algorithm' ' + git cat-file commit fourth-signed >output && + grep "^$(test_oid header) -----BEGIN SSH SIGNATURE-----" output +' + +test_expect_success GPGSSH 'show signed commit with signature' ' + test_config gpg.ssh.allowedSignersFile "${SIGNING_ALLOWED_SIGNERS}" && + git show -s initial >commit && + git show -s --show-signature initial >show && + git verify-commit -v initial >verify.1 2>verify.2 && + git cat-file commit initial >cat && + grep -v -e "${GOOD_SIGNATURE_TRUSTED}" -e "Warning: " show >show.commit && + grep -e "${GOOD_SIGNATURE_TRUSTED}" -e "Warning: " show >show.gpg && + grep -v "^ " cat | grep -v "^gpgsig.* " >cat.commit && + test_cmp show.commit commit && + test_cmp show.gpg verify.2 && + test_cmp cat.commit verify.1 +' + +test_expect_success GPGSSH 'detect fudged signature' ' + test_config gpg.ssh.allowedSignersFile "${SIGNING_ALLOWED_SIGNERS}" && + git cat-file commit seventh-signed >raw && + sed -e "s/^seventh/7th forged/" raw >forged1 && + git hash-object -w -t commit forged1 >forged1.commit && + test_must_fail git verify-commit $(cat forged1.commit) && + git show --pretty=short --show-signature $(cat forged1.commit) >actual1 && + grep "${BAD_SIGNATURE}" actual1 && + ! grep "${GOOD_SIGNATURE_TRUSTED}" actual1 && + ! grep "${GOOD_SIGNATURE_UNTRUSTED}" actual1 +' + +test_expect_success GPGSSH 'detect fudged signature with NUL' ' + test_config gpg.ssh.allowedSignersFile "${SIGNING_ALLOWED_SIGNERS}" && + git cat-file commit seventh-signed >raw && + cat raw >forged2 && + echo Qwik | tr "Q" "\000" >>forged2 && + git hash-object -w -t commit forged2 >forged2.commit && + test_must_fail git verify-commit $(cat forged2.commit) && + git show --pretty=short --show-signature $(cat forged2.commit) >actual2 && + grep "${BAD_SIGNATURE}" actual2 && + ! grep "${GOOD_SIGNATURE_TRUSTED}" actual2 +' + +test_expect_success GPGSSH 'amending already signed commit' ' + test_config gpg.format ssh && + test_config user.signingkey "${SIGNING_KEY_PRIMARY}" && + test_config gpg.ssh.allowedSignersFile "${SIGNING_ALLOWED_SIGNERS}" && + git checkout fourth-signed^0 && + git commit --amend -S --no-edit && + git verify-commit HEAD && + git show -s --show-signature HEAD >actual && + grep "${GOOD_SIGNATURE_TRUSTED}" actual && + ! grep "${BAD_SIGNATURE}" actual +' + +test_expect_success GPGSSH 'show good signature with custom format' ' + test_config gpg.ssh.allowedSignersFile "${SIGNING_ALLOWED_SIGNERS}" && + FINGERPRINT=$(ssh-keygen -lf "${SIGNING_KEY_PRIMARY}" | awk "{print \$2;}") && + cat >expect.tmpl <<-\EOF && + G + FINGERPRINT + principal with number 1 + FINGERPRINT + + EOF + sed "s|FINGERPRINT|$FINGERPRINT|g" expect.tmpl >expect && + git log -1 --format="%G?%n%GK%n%GS%n%GF%n%GP" sixth-signed >actual && + test_cmp expect actual +' + +test_expect_success GPGSSH 'show bad signature with custom format' ' + test_config gpg.ssh.allowedSignersFile "${SIGNING_ALLOWED_SIGNERS}" && + cat >expect <<-\EOF && + B + + + + + EOF + git log -1 --format="%G?%n%GK%n%GS%n%GF%n%GP" $(cat forged1.commit) >actual && + test_cmp expect actual +' + +test_expect_success GPGSSH 'show untrusted signature with custom format' ' + test_config gpg.ssh.allowedSignersFile "${SIGNING_ALLOWED_SIGNERS}" && + cat >expect.tmpl <<-\EOF && + U + FINGERPRINT + + FINGERPRINT + + EOF + git log -1 --format="%G?%n%GK%n%GS%n%GF%n%GP" eighth-signed-alt >actual && + FINGERPRINT=$(ssh-keygen -lf "${SIGNING_KEY_UNTRUSTED}" | awk "{print \$2;}") && + sed "s|FINGERPRINT|$FINGERPRINT|g" expect.tmpl >expect && + test_cmp expect actual +' + +test_expect_success GPGSSH 'show untrusted signature with undefined trust level' ' + test_config gpg.ssh.allowedSignersFile "${SIGNING_ALLOWED_SIGNERS}" && + cat >expect.tmpl <<-\EOF && + undefined + FINGERPRINT + + FINGERPRINT + + EOF + git log -1 --format="%GT%n%GK%n%GS%n%GF%n%GP" eighth-signed-alt >actual && + FINGERPRINT=$(ssh-keygen -lf "${SIGNING_KEY_UNTRUSTED}" | awk "{print \$2;}") && + sed "s|FINGERPRINT|$FINGERPRINT|g" expect.tmpl >expect && + test_cmp expect actual +' + +test_expect_success GPGSSH 'show untrusted signature with ultimate trust level' ' + test_config gpg.ssh.allowedSignersFile "${SIGNING_ALLOWED_SIGNERS}" && + cat >expect.tmpl <<-\EOF && + fully + FINGERPRINT + principal with number 1 + FINGERPRINT + + EOF + git log -1 --format="%GT%n%GK%n%GS%n%GF%n%GP" sixth-signed >actual && + FINGERPRINT=$(ssh-keygen -lf "${SIGNING_KEY_PRIMARY}" | awk "{print \$2;}") && + sed "s|FINGERPRINT|$FINGERPRINT|g" expect.tmpl >expect && + test_cmp expect actual +' + +test_expect_success GPGSSH 'show lack of signature with custom format' ' + cat >expect <<-\EOF && + N + + + + + EOF + git log -1 --format="%G?%n%GK%n%GS%n%GF%n%GP" seventh-unsigned >actual && + test_cmp expect actual +' + +test_expect_success GPGSSH 'log.showsignature behaves like --show-signature' ' + test_config gpg.ssh.allowedSignersFile "${SIGNING_ALLOWED_SIGNERS}" && + test_config log.showsignature true && + git show initial >actual && + grep "${GOOD_SIGNATURE_TRUSTED}" actual +' + +test_expect_success GPGSSH 'check config gpg.format values' ' + test_config gpg.format ssh && + test_config user.signingkey "${SIGNING_KEY_PRIMARY}" && + test_config gpg.format ssh && + git commit -S --amend -m "success" && + test_config gpg.format OpEnPgP && + test_must_fail git commit -S --amend -m "fail" +' + +test_expect_failure GPGSSH 'detect fudged commit with double signature (TODO)' ' + sed -e "/gpgsig/,/END PGP/d" forged1 >double-base && + sed -n -e "/gpgsig/,/END PGP/p" forged1 | \ + sed -e "s/^$(test_oid header)//;s/^ //" | gpg --dearmor >double-sig1.sig && + gpg -o double-sig2.sig -u 29472784 --detach-sign double-base && + cat double-sig1.sig double-sig2.sig | gpg --enarmor >double-combined.asc && + sed -e "s/^\(-.*\)ARMORED FILE/\1SIGNATURE/;1s/^/$(test_oid header) /;2,\$s/^/ /" \ + double-combined.asc > double-gpgsig && + sed -e "/committer/r double-gpgsig" double-base >double-commit && + git hash-object -w -t commit double-commit >double-commit.commit && + test_must_fail git verify-commit $(cat double-commit.commit) && + git show --pretty=short --show-signature $(cat double-commit.commit) >double-actual && + grep "BAD signature from" double-actual && + grep "Good signature from" double-actual +' + +test_expect_failure GPGSSH 'show double signature with custom format (TODO)' ' + cat >expect <<-\EOF && + E + + + + + EOF + git log -1 --format="%G?%n%GK%n%GS%n%GF%n%GP" $(cat double-commit.commit) >actual && + test_cmp expect actual +' + + +test_expect_failure GPGSSH 'verify-commit verifies multiply signed commits (TODO)' ' + git init multiply-signed && + cd multiply-signed && + test_commit first && + echo 1 >second && + git add second && + tree=$(git write-tree) && + parent=$(git rev-parse HEAD^{commit}) && + git commit --gpg-sign -m second && + git cat-file commit HEAD && + # Avoid trailing whitespace. + sed -e "s/^Q//" -e "s/^Z/ /" >commit <<-EOF && + Qtree $tree + Qparent $parent + Qauthor A U Thor <[email protected]> 1112912653 -0700 + Qcommitter C O Mitter <[email protected]> 1112912653 -0700 + Qgpgsig -----BEGIN PGP SIGNATURE----- + QZ + Q iHQEABECADQWIQRz11h0S+chaY7FTocTtvUezd5DDQUCX/uBDRYcY29tbWl0dGVy + Q QGV4YW1wbGUuY29tAAoJEBO29R7N3kMNd+8AoK1I8mhLHviPH+q2I5fIVgPsEtYC + Q AKCTqBh+VabJceXcGIZuF0Ry+udbBQ== + Q =tQ0N + Q -----END PGP SIGNATURE----- + Qgpgsig-sha256 -----BEGIN PGP SIGNATURE----- + QZ + Q iHQEABECADQWIQRz11h0S+chaY7FTocTtvUezd5DDQUCX/uBIBYcY29tbWl0dGVy + Q QGV4YW1wbGUuY29tAAoJEBO29R7N3kMN/NEAn0XO9RYSBj2dFyozi0JKSbssYMtO + Q AJwKCQ1BQOtuwz//IjU8TiS+6S4iUw== + Q =pIwP + Q -----END PGP SIGNATURE----- + Q + Qsecond + EOF + head=$(git hash-object -t commit -w commit) && + git reset --hard $head && + git verify-commit $head 2>actual && + grep "Good signature from" actual && + ! grep "BAD signature from" actual +' + +test_done
Signature · 1 line
gitgitgadget
PATCH v6 8/9 ssh signing: add more tests for logs, tags & push certs
In reply to Fabian Stelzer via GitGitGadgetFrom: Fabian Stelzer <[email protected]>
--- t/t4202-log.sh | 23 +++++ t/t5534-push-signed.sh | 101 +++++++++++++++++++ t/t7031-verify-tag-signed-ssh.sh | 161 +++++++++++++++++++++++++++++++ 3 files changed, 285 insertions(+) create mode 100755 t/t7031-verify-tag-signed-ssh.sh
t/t4202-log.sh +23 -0
@@ -1616,6 +1616,16 @@ test_expect_success GPGSM 'setup signed branch x509' ' git commit -S -m signed_commit ' +test_expect_success GPGSSH 'setup sshkey signed branch' ' + test_config gpg.format ssh && + test_config user.signingkey "${SIGNING_KEY_PRIMARY}" && + test_when_finished "git reset --hard && git checkout main" && + git checkout -b signed-ssh main && + echo foo >foo && + git add foo && + git commit -S -m signed_commit +' + test_expect_success GPGSM 'log x509 fingerprint' ' echo "F8BF62E0693D0694816377099909C779FA23FD65 | " >expect && git log -n1 --format="%GF | %GP" signed-x509 >actual &&
@@ -1628,6 +1638,13 @@ test_expect_success GPGSM 'log OpenPGP fingerprint' ' test_cmp expect actual ' +test_expect_success GPGSSH 'log ssh key fingerprint' ' + test_config gpg.ssh.allowedSignersFile "${SIGNING_ALLOWED_SIGNERS}" && + ssh-keygen -lf "${SIGNING_KEY_PRIMARY}" | awk "{print \$2\" | \"}" >expect && + git log -n1 --format="%GF | %GP" signed-ssh >actual && + test_cmp expect actual +' + test_expect_success GPG 'log --graph --show-signature' ' git log --graph --show-signature -n1 signed >actual && grep "^| gpg: Signature made" actual &&
@@ -1640,6 +1657,12 @@ test_expect_success GPGSM 'log --graph --show-signature x509' ' grep "^| gpgsm: Good signature" actual ' +test_expect_success GPGSSH 'log --graph --show-signature ssh' ' + test_config gpg.ssh.allowedSignersFile "${SIGNING_ALLOWED_SIGNERS}" && + git log --graph --show-signature -n1 signed-ssh >actual && + grep "${GOOD_SIGNATURE_TRUSTED}" actual +' + test_expect_success GPG 'log --graph --show-signature for merged tag' ' test_when_finished "git reset --hard && git checkout main" && git checkout -b plain main &&
t/t5534-push-signed.sh +101 -0
113 lines not on this page · raw message
t/t7031-verify-tag-signed-ssh.sh new file +161 -0
161 lines not on this page · raw message
Signature · 1 line
gitgitgadget
PATCH v6 9/9 ssh signing: add documentation
In reply to Fabian Stelzer via GitGitGadgetFrom: Fabian Stelzer <[email protected]>
--- Documentation/config/gpg.txt | 39 +++++++++++++++++++++++++++++++++-- Documentation/config/user.txt | 6 ++++++ 2 files changed, 43 insertions(+), 2 deletions(-)
Documentation/config/gpg.txt +37 -2
@@ -11,13 +11,13 @@ gpg.program:: gpg.format:: Specifies which key format to use when signing with `--gpg-sign`. - Default is "openpgp" and another possible value is "x509". + Default is "openpgp". Other possible values are "x509", "ssh". gpg.<format>.program:: Use this to customize the program used for the signing format you chose. (see `gpg.program` and `gpg.format`) `gpg.program` can still be used as a legacy synonym for `gpg.openpgp.program`. The default - value for `gpg.x509.program` is "gpgsm". + value for `gpg.x509.program` is "gpgsm" and `gpg.ssh.program` is "ssh-keygen". gpg.minTrustLevel:: Specifies a minimum trust level for signature verification. If
@@ -33,3 +33,38 @@ gpg.minTrustLevel:: * `marginal` * `fully` * `ultimate` + +gpg.ssh.allowedSignersFile:: + A file containing ssh public keys which you are willing to trust. + The file consists of one or more lines of principals followed by an ssh + public key. + e.g.: [email protected],[email protected] ssh-rsa AAAAX1... + See ssh-keygen(1) "ALLOWED SIGNERS" for details. + The principal is only used to identify the key and is available when + verifying a signature. ++ +SSH has no concept of trust levels like gpg does. To be able to differentiate +between valid signatures and trusted signatures the trust level of a signature +verification is set to `fully` when the public key is present in the allowedSignersFile. +Therefore to only mark fully trusted keys as verified set gpg.minTrustLevel to `fully`. +Otherwise valid but untrusted signatures will still verify but show no principal +name of the signer. ++ +This file can be set to a location outside of the repository and every developer +maintains their own trust store. A central repository server could generate this +file automatically from ssh keys with push access to verify the code against. +In a corporate setting this file is probably generated at a global location +from automation that already handles developer ssh keys. ++ +A repository that only allows signed commits can store the file +in the repository itself using a path relative to the top-level of the working tree. +This way only committers with an already valid key can add or change keys in the keyring. ++ +Using a SSH CA key with the cert-authority option +(see ssh-keygen(1) "CERTIFICATES") is also valid. + +gpg.ssh.revocationFile:: + Either a SSH KRL or a list of revoked public keys (without the principal prefix). + See ssh-keygen(1) for details. + If a public key is found in this file then it will always be treated + as having trust level "never" and signatures will show as invalid.
Documentation/config/user.txt +6 -0
@@ -36,3 +36,9 @@ user.signingKey:: commit, you can override the default selection with this variable. This option is passed unchanged to gpg's --local-user parameter, so you may specify a key using any method that gpg supports. + If gpg.format is set to "ssh" this can contain the literal ssh public + key (e.g.: "ssh-rsa XXXXXX identifier") or a file which contains it and + corresponds to the private key used for signing. The private key + needs to be available via ssh-agent. Alternatively it can be set to + a file containing a private key directly. If not set git will call + "ssh-add -L" and try to use the first key available.
Signature · 1 line
gitgitgadget
Re: [PATCH v6 0/9] ssh signing: Add commit & tag signing/verification via SSH keys using ssh-keygen
In reply to Fabian Stelzer via GitGitGadgetQuoted 9 lines · Fabian Stelzer via GitGitGadget
openssh 8.7 will add valid-after, valid-before options to the allowed keys keyring. This allows us to pass the commit timestamp to the verification call and make key rollover possible and still be able to verify older commits. Set valid-after=NOW when adding your key to the keyring and set valid-before to make it fail if used after a certain date. Software like gitolite/github or corporate automation can do this automatically when ssh push keys are addded / removed I will add this feature in a follow up patch afterwards.
I read above as "set valid-before=<some date> and valid-after=<now> to limit key validity for several days from now". Is it right?
Signature · 1 line
An old man doll... just what I always wanted! - Clara
Re: [PATCH v6 0/9] ssh signing: Add commit & tag signing/verification via SSH keys using ssh-keygen
Quoted 11 lines · Bagas Sanjaya
Quoted 9 lines · Fabian Stelzer via GitGitGadget
openssh 8.7 will add valid-after, valid-before options to the allowed keys keyring. This allows us to pass the commit timestamp to the verification call and make key rollover possible and still be able to verify older commits. Set valid-after=NOW when adding your key to the keyring and set valid-before to make it fail if used after a certain date. Software like gitolite/github or corporate automation can do this automatically when ssh push keys are addded / removed I will add this feature in a follow up patch afterwards.
Quoted 2 lines
I read above as "set valid-before=<some date> and valid-after=<now> to limit key validity for several days from now". Is it right?
no. "NOW" is not meant literally but in the sense to add the current date when adding the key. I'll edit the description. But this feature in general will follow in a separate patchset with proper documentation anyway.
v7 cover ssh signing: Add commit & tag signing/verification via SSH keys using ssh-keygen
In reply to Fabian Stelzer via GitGitGadgetopenssh 8.7 will add valid-after, valid-before options to the allowed keys keyring. This allows us to pass the commit timestamp to the verification call and make key rollover possible and still be able to verify older commits. Set valid-after to the current date when adding your key to the keyring and set valid-before to make it fail if used after a certain date. Software like gitolite/github or corporate automation can do this automatically when ssh push keys are addded / removed I will add this feature in a follow up patch afterwards.
v3 addresses some issues & refactoring and splits the large commit into several smaller ones.
v4:
* restructures and cleans up the whole patch set - patches build on its own now and commit messages try to explain whats going on
* got rid of the if branches and used callback functions in the format struct
* fixed a bug with whitespace in principal identifiers that required a rewrite of the parse_ssh_output function
* rewrote documentation to be more clear - also renamed keyring back to allowedSignersFile
v5:
* moved t7527 to t7528 to not collide with another patch in "seen"
* clean up return logic for failed signing & verification
* some minor renames / reformatting to make things clearer
v6: fixed tests when using shm output dir
v7:
* change unknown signing key behavior to fail verify-commit/tag just like gpg does
* add test for unknown signing keys for ssh & gpg
* made default signing key retrieval configurable (gpg.ssh.defaultKeyCommand). We could default this to "ssh-add -L" but would risk some users signing with a wrong key
* die() instead of error in case of incompatible signatures to match current BUG() behaviour more
* various review fixes (early return for config parse, missing free, comments)
* got rid of strcmp("ssh") branches and used format configurable callbacks everywhere
* moved documentation changes into the commits adding the specific functionality
The test 'verify-commit verifies multiply signed commits' relies on the commit/author date that was incremented via test_tick in the inital function doing all the commits even though it creates its own. This should be reset or otherwise set to a known state. But I'm not sure how.
Fabian Stelzer (9):
ssh signing: preliminary refactoring and clean-up ssh signing: add test prereqs
ssh signing: add ssh key format and signing code ssh signing: retrieve a default key from ssh-agent ssh signing: provide a textual signing_key_id ssh signing: verify signatures using ssh-keygen ssh signing: duplicate t7510 tests for commits ssh signing: tests for logs, tags & push certs ssh signing: test that gpg fails for unkown keys
Documentation/config/gpg.txt | 45 ++- Documentation/config/user.txt | 7 + builtin/receive-pack.c | 4 + fmt-merge-msg.c | 6 +- gpg-interface.c | 571 ++++++++++++++++++++++++++++--- gpg-interface.h | 8 +- log-tree.c | 8 +- pretty.c | 4 +- send-pack.c | 8 +- t/lib-gpg.sh | 28 ++ t/t4202-log.sh | 23 ++ t/t5534-push-signed.sh | 101 ++++++ t/t7031-verify-tag-signed-ssh.sh | 161 +++++++++ t/t7510-signed-commit.sh | 29 +- t/t7528-signed-commit-ssh.sh | 398 +++++++++++++++++++++ 15 files changed, 1335 insertions(+), 66 deletions(-) create mode 100755 t/t7031-verify-tag-signed-ssh.sh create mode 100755 t/t7528-signed-commit-ssh.sh
base-commit: 940fe202adcbf9fa1825c648d97cbe1b90d26aec
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-1041%2FFStelzer%2Fsshsign-v7
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-1041/FStelzer/sshsign-v7
Pull-Request: https://github.com/git/git/pull/1041
Range-diff vs v6:
1: 7c8502c65b8 ! 1: 91fd0159e1f ssh signing: preliminary refactoring and clean-up
@@ gpg-interface.c: static int verify_signed_buffer(const char *payload, size_t pay
- parse_gpg_output(sigc);
+ fmt = get_format_by_sig(signature);
+ if (!fmt)
-+ return error(_("bad/incompatible signature '%s'"), signature);
++ die(_("bad/incompatible signature '%s'"), signature);
+
+ status = fmt->verify_signed_buffer(sigc, fmt, payload, plen, signature,
+ slen);
@@ gpg-interface.c: const char *get_signing_key(void)
+}
+
+static int sign_buffer_gpg(struct strbuf *buffer, struct strbuf *signature,
-+ const char *signing_key)
++ const char *signing_key)
{
struct child_process gpg = CHILD_PROCESS_INIT;
int ret;
6: 18a26ca49e7 ! 2: fe98052a3ea ssh signing: add test prereqs
@@ Metadata
## Commit message ##
ssh signing: add test prereqs - generate some ssh keys and a allowedSignersFile for testing
+ Generate some ssh keys and a allowedSignersFile for testing
Signed-off-by: Fabian Stelzer <[email protected]>
@@ t/lib-gpg.sh: test_lazy_prereq RFC1991 '
echo | gpg --homedir "${GNUPGHOME}" -b --rfc1991 >/dev/null
'
++GPGSSH_KEY_PRIMARY="${GNUPGHOME}/ed25519_ssh_signing_key"
++GPGSSH_KEY_SECONDARY="${GNUPGHOME}/rsa_2048_ssh_signing_key"
++GPGSSH_KEY_UNTRUSTED="${GNUPGHOME}/untrusted_ssh_signing_key"
++GPGSSH_KEY_WITH_PASSPHRASE="${GNUPGHOME}/protected_ssh_signing_key"
++GPGSSH_KEY_PASSPHRASE="super_secret"
++GPGSSH_ALLOWED_SIGNERS="${GNUPGHOME}/ssh.all_valid.allowedSignersFile"
++
++GPGSSH_GOOD_SIGNATURE_TRUSTED='Good "git" signature for'
++GPGSSH_GOOD_SIGNATURE_UNTRUSTED='Good "git" signature with'
++GPGSSH_KEY_NOT_TRUSTED="No principal matched"
++GPGSSH_BAD_SIGNATURE="Signature verification failed"
++
+test_lazy_prereq GPGSSH '
+ ssh_version=$(ssh-keygen -Y find-principals -n "git" 2>&1)
+ test $? != 127 || exit 1
@@ t/lib-gpg.sh: test_lazy_prereq RFC1991 '
+ test $? = 0 || exit 1;
+ mkdir -p "${GNUPGHOME}" &&
+ chmod 0700 "${GNUPGHOME}" &&
-+ ssh-keygen -t ed25519 -N "" -C "git ed25519 key" -f "${GNUPGHOME}/ed25519_ssh_signing_key" >/dev/null &&
-+ echo "\"principal with number 1\" $(cat "${GNUPGHOME}/ed25519_ssh_signing_key.pub")" >> "${GNUPGHOME}/ssh.all_valid.allowedSignersFile" &&
-+ ssh-keygen -t rsa -b 2048 -N "" -C "git rsa2048 key" -f "${GNUPGHOME}/rsa_2048_ssh_signing_key" >/dev/null &&
-+ echo "\"principal with number 2\" $(cat "${GNUPGHOME}/rsa_2048_ssh_signing_key.pub")" >> "${GNUPGHOME}/ssh.all_valid.allowedSignersFile" &&
-+ ssh-keygen -t ed25519 -N "super_secret" -C "git ed25519 encrypted key" -f "${GNUPGHOME}/protected_ssh_signing_key" >/dev/null &&
-+ echo "\"principal with number 3\" $(cat "${GNUPGHOME}/protected_ssh_signing_key.pub")" >> "${GNUPGHOME}/ssh.all_valid.allowedSignersFile" &&
-+ cat "${GNUPGHOME}/ssh.all_valid.allowedSignersFile" &&
-+ ssh-keygen -t ed25519 -N "" -f "${GNUPGHOME}/untrusted_ssh_signing_key" >/dev/null
++ ssh-keygen -t ed25519 -N "" -C "git ed25519 key" -f "${GPGSSH_KEY_PRIMARY}" >/dev/null &&
++ echo "\"principal with number 1\" $(cat "${GPGSSH_KEY_PRIMARY}.pub")" >> "${GPGSSH_ALLOWED_SIGNERS}" &&
++ ssh-keygen -t rsa -b 2048 -N "" -C "git rsa2048 key" -f "${GPGSSH_KEY_SECONDARY}" >/dev/null &&
++ echo "\"principal with number 2\" $(cat "${GPGSSH_KEY_SECONDARY}.pub")" >> "${GPGSSH_ALLOWED_SIGNERS}" &&
++ ssh-keygen -t ed25519 -N "${GPGSSH_KEY_PASSPHRASE}" -C "git ed25519 encrypted key" -f "${GPGSSH_KEY_WITH_PASSPHRASE}" >/dev/null &&
++ echo "\"principal with number 3\" $(cat "${GPGSSH_KEY_WITH_PASSPHRASE}.pub")" >> "${GPGSSH_ALLOWED_SIGNERS}" &&
++ ssh-keygen -t ed25519 -N "" -f "${GPGSSH_KEY_UNTRUSTED}" >/dev/null
+'
-+
-+SIGNING_KEY_PRIMARY="${GNUPGHOME}/ed25519_ssh_signing_key"
-+SIGNING_KEY_SECONDARY="${GNUPGHOME}/rsa_2048_ssh_signing_key"
-+SIGNING_KEY_UNTRUSTED="${GNUPGHOME}/untrusted_ssh_signing_key"
-+SIGNING_KEY_WITH_PASSPHRASE="${GNUPGHOME}/protected_ssh_signing_key"
-+SIGNING_KEY_PASSPHRASE="super_secret"
-+SIGNING_ALLOWED_SIGNERS="${GNUPGHOME}/ssh.all_valid.allowedSignersFile"
-+
-+GOOD_SIGNATURE_TRUSTED='Good "git" signature for'
-+GOOD_SIGNATURE_UNTRUSTED='Good "git" signature with'
-+KEY_NOT_TRUSTED="No principal matched"
-+BAD_SIGNATURE="Signature verification failed"
+
sanitize_pgp() {
perl -ne ' 2: f05bab16096 ! 3: 80d2d55d22e ssh signing: add ssh signature format and signing using ssh keys
@@ Metadata
Author: Fabian Stelzer <[email protected]>
## Commit message ##
- ssh signing: add ssh signature format and signing using ssh keys
+ ssh signing: add ssh key format and signing code
- implements the actual sign_buffer_ssh operation and move some shared
+ Implements the actual sign_buffer_ssh operation and move some shared
cleanup code into a strbuf function
Set gpg.format = ssh and user.signingkey to either a ssh public key
@@ Commit message
+ ## Documentation/config/gpg.txt ##
+@@ Documentation/config/gpg.txt: gpg.program::
+
+ gpg.format::
+ Specifies which key format to use when signing with --gpg-sign.
+- Default is "openpgp" and another possible value is "x509".
++ Default is "openpgp". Other possible values are "x509", "ssh".
+
+ gpg.<format>.program::
+ Use this to customize the program used for the signing format you
+ chose. (see gpg.program and gpg.format) gpg.program can still
+ be used as a legacy synonym for gpg.openpgp.program. The default
+- value for gpg.x509.program is "gpgsm".
++ value for gpg.x509.program is "gpgsm" and gpg.ssh.program is "ssh-keygen".
+
+ gpg.minTrustLevel::
+ Specifies a minimum trust level for signature verification. If
+
+ ## Documentation/config/user.txt ##
+@@ Documentation/config/user.txt: user.signingKey::
+ commit, you can override the default selection with this variable.
+ This option is passed unchanged to gpg's --local-user parameter,
+ so you may specify a key using any method that gpg supports.
++ If gpg.format is set to "ssh" this can contain the literal ssh public
++ key (e.g.: "ssh-rsa XXXXXX identifier") or a file which contains it and
++ corresponds to the private key used for signing. The private key
++ needs to be available via ssh-agent. Alternatively it can be set to
++ a file containing a private key directly.
+
## gpg-interface.c ##
@@ gpg-interface.c: static const char *x509_sigs[] = {
NULL
@@ gpg-interface.c: int sign_buffer(struct strbuf *buffer, struct strbuf *signature
+}
+
static int sign_buffer_gpg(struct strbuf *buffer, struct strbuf *signature,
- const char *signing_key)
+ const char *signing_key)
{
struct child_process gpg = CHILD_PROCESS_INIT;
int ret;
@@ gpg-interface.c: static int sign_buffer_gpg(struct strbuf *buffer, struct strbuf
+ key_file->filename.buf);
+ goto out;
+ }
-+ ssh_signing_key_file = key_file->filename.buf;
++ ssh_signing_key_file = strbuf_detach(&key_file->filename, NULL);
+ } else {
+ /* We assume a file */
+ ssh_signing_key_file = expand_user_path(signing_key, 1);
@@ gpg-interface.c: static int sign_buffer_gpg(struct strbuf *buffer, struct strbuf
+ delete_tempfile(&buffer_file);
+ strbuf_release(&signer_stderr);
+ strbuf_release(&ssh_signature_filename);
++ FREE_AND_NULL(ssh_signing_key_file);
+ return ret;
+}
3: 071e6173d8e ! 4: 83ece42e1de ssh signing: retrieve a default key from ssh-agent
@@ Metadata
## Commit message ##
ssh signing: retrieve a default key from ssh-agent
- if user.signingkey is not set and a ssh signature is requested we call
- ssh-add -L and use the first key we get
+ If user.signingkey is not set and a ssh signature is requested we call
+ gpg.ssh.defaultKeyCommand (typically "ssh-add -L") and use the first key we get
Signed-off-by: Fabian Stelzer <[email protected]>
+ ## Documentation/config/gpg.txt ##
+@@ Documentation/config/gpg.txt: gpg.minTrustLevel::
+ * `marginal`
+ * `fully`
+ * `ultimate`
++
++gpg.ssh.defaultKeyCommand:
++ This command that will be run when user.signingkey is not set and a ssh
++ signature is requested. On successful exit a valid ssh public key is
++ expected in the first line of its output. To automatically use the first
++ available key from your ssh-agent set this to "ssh-add -L".
+
+ ## Documentation/config/user.txt ##
+@@ Documentation/config/user.txt: user.signingKey::
+ key (e.g.: "ssh-rsa XXXXXX identifier") or a file which contains it and
+ corresponds to the private key used for signing. The private key
+ needs to be available via ssh-agent. Alternatively it can be set to
+- a file containing a private key directly.
++ a file containing a private key directly. If not set git will call
++ gpg.ssh.defaultKeyCommand (e.g.: "ssh-add -L") and try to use the first
++ key available.
+
## gpg-interface.c ##
+@@
+ #include "gpg-interface.h"
+ #include "sigchain.h"
+ #include "tempfile.h"
++#include "alias.h"
+
+ static char *configured_signing_key;
++static const char *ssh_default_key_command;
+ static enum signature_trust_level configured_min_trust_level = TRUST_UNDEFINED;
+
+ struct gpg_format {
+@@ gpg-interface.c: struct gpg_format {
+ size_t signature_size);
+ int (*sign_buffer)(struct strbuf *buffer, struct strbuf *signature,
+ const char *signing_key);
++ const char *(*get_default_key)(void);
+ };
+
+ static const char *openpgp_verify_args[] = {
+@@ gpg-interface.c: static int sign_buffer_gpg(struct strbuf *buffer, struct strbuf *signature,
+ static int sign_buffer_ssh(struct strbuf *buffer, struct strbuf *signature,
+ const char *signing_key);
+
++static const char *get_default_ssh_signing_key(void);
++
+ static struct gpg_format gpg_format[] = {
+ {
+ .name = "openpgp",
+@@ gpg-interface.c: static struct gpg_format gpg_format[] = {
+ .sigs = openpgp_sigs,
+ .verify_signed_buffer = verify_gpg_signed_buffer,
+ .sign_buffer = sign_buffer_gpg,
++ .get_default_key = NULL,
+ },
+ {
+ .name = "x509",
+@@ gpg-interface.c: static struct gpg_format gpg_format[] = {
+ .sigs = x509_sigs,
+ .verify_signed_buffer = verify_gpg_signed_buffer,
+ .sign_buffer = sign_buffer_gpg,
++ .get_default_key = NULL,
+ },
+ {
+ .name = "ssh",
+@@ gpg-interface.c: static struct gpg_format gpg_format[] = {
+ .verify_args = ssh_verify_args,
+ .sigs = ssh_sigs,
+ .verify_signed_buffer = NULL, /* TODO */
+- .sign_buffer = sign_buffer_ssh
++ .sign_buffer = sign_buffer_ssh,
++ .get_default_key = get_default_ssh_signing_key,
+ },
+ };
+
+@@ gpg-interface.c: int git_gpg_config(const char *var, const char *value, void *cb)
+ return 0;
+ }
+
++ if (!strcmp(var, "gpg.ssh.defaultkeycommand")) {
++ if (!value)
++ return config_error_nonbool(var);
++ return git_config_string(&ssh_default_key_command, var, value);
++ }
++
+ if (!strcmp(var, "gpg.program") || !strcmp(var, "gpg.openpgp.program"))
+ fmtname = "openpgp";
+
@@ gpg-interface.c: int git_gpg_config(const char *var, const char *value, void *cb)
return 0;
}
+/* Returns the first public key from an ssh-agent to use for signing */
-+static char *get_default_ssh_signing_key(void)
++static const char *get_default_ssh_signing_key(void)
+{
-+ struct child_process ssh_add = CHILD_PROCESS_INIT;
++ struct child_process ssh_default_key = CHILD_PROCESS_INIT;
+ int ret = -1;
-+ struct strbuf key_stdout = STRBUF_INIT;
++ struct strbuf key_stdout = STRBUF_INIT, key_stderr = STRBUF_INIT;
+ struct strbuf **keys;
++ char *key_command = NULL;
++ const char **argv;
++ int n;
++ char *default_key = NULL;
++
++ if (!ssh_default_key_command)
++ die(_("either user.signingkey or gpg.ssh.defaultKeyCommand needs to be configured"));
++
++ key_command = xstrdup(ssh_default_key_command);
++ n = split_cmdline(key_command, &argv);
++
++ if (n < 0)
++ die("malformed build-time gpg.ssh.defaultKeyCommand: %s",
++ split_cmdline_strerror(n));
++
++ strvec_pushv(&ssh_default_key.args, argv);
++ ret = pipe_command(&ssh_default_key, NULL, 0, &key_stdout, 0,
++ &key_stderr, 0);
+
-+ strvec_pushl(&ssh_add.args, "ssh-add", "-L", NULL);
-+ ret = pipe_command(&ssh_add, NULL, 0, &key_stdout, 0, NULL, 0);
+ if (!ret) {
+ keys = strbuf_split_max(&key_stdout, '\n', 2);
-+ if (keys[0])
-+ return strbuf_detach(keys[0], NULL);
++ if (keys[0] && starts_with(keys[0]->buf, "ssh-")) {
++ default_key = strbuf_detach(keys[0], NULL);
++ } else {
++ warning(_("gpg.ssh.defaultKeycommand succeeded but returned no keys: %s %s"),
++ key_stderr.buf, key_stdout.buf);
++ }
++
++ strbuf_list_free(keys);
++ } else {
++ warning(_("gpg.ssh.defaultKeyCommand failed: %s %s"),
++ key_stderr.buf, key_stdout.buf);
+ }
+
++ free(key_command);
++ free(argv);
+ strbuf_release(&key_stdout);
-+ return "";
++
++ return default_key;
+}
+
const char *get_signing_key(void)
@@ gpg-interface.c: int git_gpg_config(const char *var, const char *value, void *cb
if (configured_signing_key)
return configured_signing_key;
- return git_committer_info(IDENT_STRICT|IDENT_NO_DATE);
-+ if (!strcmp(use_format->name, "ssh")) {
-+ return get_default_ssh_signing_key();
-+ } else {
-+ return git_committer_info(IDENT_STRICT | IDENT_NO_DATE);
++ if (use_format->get_default_key) {
++ return use_format->get_default_key();
+ }
++
++ return git_committer_info(IDENT_STRICT | IDENT_NO_DATE);
}
int sign_buffer(struct strbuf *buffer, struct strbuf *signature, const char *signing_key) 4: 7d1d131ff5b ! 5: 76bc9eb4079 ssh signing: provide a textual representation of the signing key
@@ Metadata
Author: Fabian Stelzer <[email protected]>
## Commit message ##
- ssh signing: provide a textual representation of the signing key
+ ssh signing: provide a textual signing_key_id
- for ssh the user.signingkey can be a filename/path or even a literal ssh pubkey.
- in push certs and textual output we prefer the ssh fingerprint instead.
+ For ssh the user.signingkey can be a filename/path or even a literal ssh pubkey.
+ In push certs and textual output we prefer the ssh fingerprint instead.
## gpg-interface.c ##
+@@ gpg-interface.c: struct gpg_format {
+ int (*sign_buffer)(struct strbuf *buffer, struct strbuf *signature,
+ const char *signing_key);
+ const char *(*get_default_key)(void);
++ const char *(*get_key_id)(void);
+ };
+
+ static const char *openpgp_verify_args[] = {
+@@ gpg-interface.c: static int sign_buffer_ssh(struct strbuf *buffer, struct strbuf *signature, +
+ static const char *get_default_ssh_signing_key(void);
+
++static const char *get_ssh_key_id(void);
++
+ static struct gpg_format gpg_format[] = {
+ {
+ .name = "openpgp",
+@@ gpg-interface.c: static struct gpg_format gpg_format[] = {
+ .verify_signed_buffer = verify_gpg_signed_buffer,
+ .sign_buffer = sign_buffer_gpg,
+ .get_default_key = NULL,
++ .get_key_id = NULL,
+ },
+ {
+ .name = "x509",
+@@ gpg-interface.c: static struct gpg_format gpg_format[] = {
+ .verify_signed_buffer = verify_gpg_signed_buffer,
+ .sign_buffer = sign_buffer_gpg,
+ .get_default_key = NULL,
++ .get_key_id = NULL,
+ },
+ {
+ .name = "ssh",
+@@ gpg-interface.c: static struct gpg_format gpg_format[] = {
+ .verify_signed_buffer = NULL, /* TODO */
+ .sign_buffer = sign_buffer_ssh,
+ .get_default_key = get_default_ssh_signing_key,
++ .get_key_id = get_ssh_key_id,
+ },
+ };
+
@@ gpg-interface.c: int git_gpg_config(const char *var, const char *value, void *cb)
return 0;
}
@@ gpg-interface.c: int git_gpg_config(const char *var, const char *value, void *cb
+ * With SSH Signing this can contain a filename or a public key
+ * For textual representation we usually want a fingerprint
+ */
-+ if (istarts_with(signing_key, "ssh-")) {
++ if (starts_with(signing_key, "ssh-")) {
+ strvec_pushl(&ssh_keygen.args, "ssh-keygen", "-lf", "-", NULL);
+ ret = pipe_command(&ssh_keygen, signing_key,
+ strlen(signing_key), &fingerprint_stdout, 0,
@@ gpg-interface.c: int git_gpg_config(const char *var, const char *value, void *cb
+}
+
/* Returns the first public key from an ssh-agent to use for signing */
- static char *get_default_ssh_signing_key(void)
+ static const char *get_default_ssh_signing_key(void)
{
-@@ gpg-interface.c: static char *get_default_ssh_signing_key(void)
- return "";
+@@ gpg-interface.c: static const char *get_default_ssh_signing_key(void)
+ return default_key;
}
-+/* Returns a textual but unique representation ot the signing key */ ++static const char *get_ssh_key_id(void) {
++ return get_ssh_key_fingerprint(get_signing_key());
++}
++
++/* Returns a textual but unique representation of the signing key */
+const char *get_signing_key_id(void)
+{
-+ if (!strcmp(use_format->name, "ssh")) {
-+ return get_ssh_key_fingerprint(get_signing_key());
-+ } else {
-+ /* GPG/GPGSM only store a key id on this variable */
-+ return get_signing_key();
++ if (use_format->get_key_id) {
++ return use_format->get_key_id();
+ }
++
++ /* GPG/GPGSM only store a key id on this variable */
++ return get_signing_key();
+}
+
const char *get_signing_key(void)
5: 725764018ce ! 6: dc092c79796 ssh signing: parse ssh-keygen output and verify signatures
@@ Metadata
Author: Fabian Stelzer <[email protected]>
## Commit message ##
- ssh signing: parse ssh-keygen output and verify signatures
+ ssh signing: verify signatures using ssh-keygen
- to verify a ssh signature we first call ssh-keygen -Y find-principal to
+ To verify a ssh signature we first call ssh-keygen -Y find-principal to
look up the signing principal by their public key from the
allowedSignersFile. If the key is found then we do a verify. Otherwise
we only validate the signature but can not verify the signers identity.
@@ Commit message
SIGNERS") which contains valid public keys and a principal (usually
user@domain). Depending on the environment this file can be managed by
the individual developer or for example generated by the central
- repository server from known ssh keys with push access. If the
- repository only allows signed commits / pushes then the file can even be
- stored inside it.
+ repository server from known ssh keys with push access. This file is usually
+ stored outside the repository, but if the repository only allows signed
+ commits/pushes, the user might choose to store it in the repository.
To revoke a key put the public key without the principal prefix into
gpg.ssh.revocationKeyring or generate a KRL (see ssh-keygen(1)
@@ Commit message
Using SSH CA Keys with these files is also possible. Add
"cert-authority" as key option between the principal and the key to mark
it as a CA and all keys signed by it as valid for this CA.
+ See "CERTIFICATES" in ssh-keygen(1).
Signed-off-by: Fabian Stelzer <[email protected]>
+ ## Documentation/config/gpg.txt ##
+@@ Documentation/config/gpg.txt: gpg.ssh.defaultKeyCommand:
+ signature is requested. On successful exit a valid ssh public key is
+ expected in the first line of its output. To automatically use the first
+ available key from your ssh-agent set this to "ssh-add -L".
++
++gpg.ssh.allowedSignersFile::
++ A file containing ssh public keys which you are willing to trust.
++ The file consists of one or more lines of principals followed by an ssh
++ public key.
++ e.g.: [email protected],[email protected] ssh-rsa AAAAX1...
++ See ssh-keygen(1) "ALLOWED SIGNERS" for details.
++ The principal is only used to identify the key and is available when
++ verifying a signature.
+++
++SSH has no concept of trust levels like gpg does. To be able to differentiate
++between valid signatures and trusted signatures the trust level of a signature
++verification is set to `fully` when the public key is present in the allowedSignersFile.
++Therefore to only mark fully trusted keys as verified set gpg.minTrustLevel to `fully`.
++Otherwise valid but untrusted signatures will still verify but show no principal
++name of the signer.
+++
++This file can be set to a location outside of the repository and every developer
++maintains their own trust store. A central repository server could generate this
++file automatically from ssh keys with push access to verify the code against.
++In a corporate setting this file is probably generated at a global location
++from automation that already handles developer ssh keys.
+++
++A repository that only allows signed commits can store the file
++in the repository itself using a path relative to the top-level of the working tree.
++This way only committers with an already valid key can add or change keys in the keyring.
+++
++Using a SSH CA key with the cert-authority option
++(see ssh-keygen(1) "CERTIFICATES") is also valid.
++
++gpg.ssh.revocationFile::
++ Either a SSH KRL or a list of revoked public keys (without the principal prefix).
++ See ssh-keygen(1) for details.
++ If a public key is found in this file then it will always be treated
++ as having trust level "never" and signatures will show as invalid.
+
## builtin/receive-pack.c ##
@@ builtin/receive-pack.c: static int receive_pack_config(const char *var, const char *value, void *cb)
{
int status = parse_hide_refs_config(var, value, "receive");
-+ git_gpg_config(var, value, NULL);
++ if (status)
++ return status;
+
++ status = git_gpg_config(var, value, NULL);
if (status)
return status;
@@ gpg-interface.c
#include "gpg-interface.h"
#include "sigchain.h"
#include "tempfile.h"
+ #include "alias.h"
static char *configured_signing_key;
-+static const char *ssh_allowed_signers, *ssh_revocation_file;
+-static const char *ssh_default_key_command;
++static const char *ssh_default_key_command, *ssh_allowed_signers, *ssh_revocation_file;
static enum signature_trust_level configured_min_trust_level = TRUST_UNDEFINED;
struct gpg_format {
@@ gpg-interface.c: static struct gpg_format gpg_format[] = {
.sigs = ssh_sigs,
- .verify_signed_buffer = NULL, /* TODO */
+ .verify_signed_buffer = verify_ssh_signed_buffer,
- .sign_buffer = sign_buffer_ssh
- },
- };
+ .sign_buffer = sign_buffer_ssh,
+ .get_default_key = get_default_ssh_signing_key,
+ .get_key_id = get_ssh_key_id,
@@ gpg-interface.c: static int verify_gpg_signed_buffer(struct signature_check *sigc,
return ret;
}
@@ gpg-interface.c: static int verify_gpg_signed_buffer(struct signature_check *sig
+static void parse_ssh_output(struct signature_check *sigc)
+{
+ const char *line, *principal, *search;
++ char *key = NULL;
+
+ /*
-+ * ssh-keysign output should be:
++ * ssh-keygen output should be:
+ * Good "git" signature for PRINCIPAL with RSA key SHA256:FINGERPRINT
-+ * Good "git" signature for PRINCIPAL WITH WHITESPACE with RSA key SHA256:FINGERPRINT
++ *
+ * or for valid but unknown keys:
+ * Good "git" signature with RSA key SHA256:FINGERPRINT
++ *
++ * Note that "PRINCIPAL" can contain whitespace, "RSA" and
++ * "SHA256" part could be a different token that names of
++ * the algorithms used, and "FINGERPRINT" is a hexadecimal
++ * string. By finding the last occurence of " with ", we can
++ * reliably parse out the PRINCIPAL.
+ */
+ sigc->result = 'B';
+ sigc->trust_level = TRUST_NEVER;
@@ gpg-interface.c: static int verify_gpg_signed_buffer(struct signature_check *sig
+ line = search + 1;
+ } while (search != NULL);
+ sigc->signer = xmemdupz(principal, line - principal - 1);
-+ sigc->fingerprint = xstrdup(strstr(line, "key") + 4);
-+ sigc->key = xstrdup(sigc->fingerprint);
+ } else if (skip_prefix(line, "Good \"git\" signature with ", &line)) {
+ /* Valid signature, but key unknown */
+ sigc->result = 'G';
+ sigc->trust_level = TRUST_UNDEFINED;
++ } else {
++ return;
++ }
++
++ key = strstr(line, "key");
++ if (key) {
+ sigc->fingerprint = xstrdup(strstr(line, "key") + 4);
+ sigc->key = xstrdup(sigc->fingerprint);
++ } else {
++ /*
++ * Output did not match what we expected
++ * Treat the signature as bad
++ */
++ sigc->result = 'B';
+ }
+}
+
@@ gpg-interface.c: static int verify_gpg_signed_buffer(struct signature_check *sig
+ goto out;
+ }
+ if (ret || !ssh_keygen_out.len) {
-+ /* We did not find a matching principal in the allowedSigners - Check
-+ * without validation */
++ /*
++ * We did not find a matching principal in the allowedSigners
++ * Check without validation
++ */
+ child_process_init(&ssh_keygen);
+ strvec_pushl(&ssh_keygen.args, fmt->program,
+ "-Y", "check-novalidate",
+ "-n", "git",
+ "-s", buffer_file->filename.buf,
+ NULL);
-+ ret = pipe_command(&ssh_keygen, payload, payload_size,
++ pipe_command(&ssh_keygen, payload, payload_size,
+ &ssh_keygen_out, 0, &ssh_keygen_err, 0);
++
++ /*
++ * Fail on unknown keys
++ * we still call check-novalidate to display the signature info
++ */
++ ret = -1;
+ } else {
+ /* Check every principal we found (one per line) */
+ for (line = ssh_keygen_out.buf; *line;
@@ gpg-interface.c: static int verify_gpg_signed_buffer(struct signature_check *sig
+ strbuf_release(&ssh_keygen_out);
+ strbuf_release(&ssh_keygen_err);
+ strvec_push(&ssh_keygen.args, fmt->program);
-+ /* We found principals - Try with each until we find a
-+ * match */
++ /*
++ * We found principals
++ * Try with each until we find a match
++ */
+ strvec_pushl(&ssh_keygen.args, "-Y", "verify",
+ "-n", "git",
+ "-f", ssh_allowed_signers,
@@ gpg-interface.c: static int verify_gpg_signed_buffer(struct signature_check *sig
+
+ FREE_AND_NULL(principal);
+
-+ ret &= starts_with(ssh_keygen_out.buf, "Good");
-+ if (ret == 0)
++ if (!ret)
++ ret = !starts_with(ssh_keygen_out.buf, "Good");
++
++ if (!ret)
+ break;
+ }
+ }
@@ gpg-interface.c: static int verify_gpg_signed_buffer(struct signature_check *sig
size_t slen, struct signature_check *sigc)
{
@@ gpg-interface.c: int git_gpg_config(const char *var, const char *value, void *cb)
- return 0;
+ return git_config_string(&ssh_default_key_command, var, value);
}
+ if (!strcmp(var, "gpg.ssh.allowedsignersfile")) {
@@ gpg-interface.c: int git_gpg_config(const char *var, const char *value, void *cb
+ return git_config_string(&ssh_allowed_signers, var, value);
+ }
+
-+ if (!strcmp(var, "gpg.ssh.revocationFile")) {
++ if (!strcmp(var, "gpg.ssh.revocationfile")) {
+ if (!value)
+ return config_error_nonbool(var);
+ return git_config_string(&ssh_revocation_file, var, value);
7: 01da9a07934 ! 7: c17441566d9 ssh signing: duplicate t7510 tests for commits
@@ t/t7528-signed-commit-ssh.sh (new)
+
+ test_when_finished "test_unconfig commit.gpgsign" &&
+ test_config gpg.format ssh &&
-+ test_config user.signingkey "${SIGNING_KEY_PRIMARY}" &&
++ test_config user.signingkey "${GPGSSH_KEY_PRIMARY}" &&
+
+ echo 1 >file && git add file &&
+ test_tick && git commit -S -m initial &&
@@ t/t7528-signed-commit-ssh.sh (new)
+ test_tick && git rebase -f HEAD^^ && git tag sixth-signed HEAD^ &&
+ git tag seventh-signed &&
+
-+ echo 8 >file && test_tick && git commit -a -m eighth -S"${SIGNING_KEY_UNTRUSTED}" &&
++ echo 8 >file && test_tick && git commit -a -m eighth -S"${GPGSSH_KEY_UNTRUSTED}" &&
+ git tag eighth-signed-alt &&
+
+ # commit.gpgsign is still on but this must not be signed
@@ t/t7528-signed-commit-ssh.sh (new)
+ echo 11 | git commit-tree --gpg-sign HEAD^{tree} >oid &&
+ test_line_count = 1 oid &&
+ git tag eleventh-signed $(cat oid) &&
-+ echo 12 | git commit-tree --gpg-sign="${SIGNING_KEY_UNTRUSTED}" HEAD^{tree} >oid &&
++ echo 12 | git commit-tree --gpg-sign="${GPGSSH_KEY_UNTRUSTED}" HEAD^{tree} >oid &&
+ test_line_count = 1 oid &&
+ git tag twelfth-signed-alt $(cat oid)
+'
+
+test_expect_success GPGSSH 'verify and show signatures' '
-+ test_config gpg.ssh.allowedSignersFile "${SIGNING_ALLOWED_SIGNERS}" &&
++ test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" &&
+ test_config gpg.mintrustlevel UNDEFINED &&
+ (
+ for commit in initial second merge fourth-signed \
@@ t/t7528-signed-commit-ssh.sh (new)
+ do
+ git verify-commit $commit &&
+ git show --pretty=short --show-signature $commit >actual &&
-+ grep "${GOOD_SIGNATURE_TRUSTED}" actual &&
-+ ! grep "${BAD_SIGNATURE}" actual &&
++ grep "${GPGSSH_GOOD_SIGNATURE_TRUSTED}" actual &&
++ ! grep "${GPGSSH_BAD_SIGNATURE}" actual &&
+ echo $commit OK || exit 1
+ done
+ ) &&
@@ t/t7528-signed-commit-ssh.sh (new)
+ do
+ test_must_fail git verify-commit $commit &&
+ git show --pretty=short --show-signature $commit >actual &&
-+ ! grep "${GOOD_SIGNATURE_TRUSTED}" actual &&
-+ ! grep "${BAD_SIGNATURE}" actual &&
++ ! grep "${GPGSSH_GOOD_SIGNATURE_TRUSTED}" actual &&
++ ! grep "${GPGSSH_BAD_SIGNATURE}" actual &&
+ echo $commit OK || exit 1
+ done
+ ) &&
@@ t/t7528-signed-commit-ssh.sh (new)
+ for commit in eighth-signed-alt twelfth-signed-alt
+ do
+ git show --pretty=short --show-signature $commit >actual &&
-+ grep "${GOOD_SIGNATURE_UNTRUSTED}" actual &&
-+ ! grep "${BAD_SIGNATURE}" actual &&
++ grep "${GPGSSH_GOOD_SIGNATURE_UNTRUSTED}" actual &&
++ ! grep "${GPGSSH_BAD_SIGNATURE}" actual &&
+ grep "${KEY_NOT_TRUSTED}" actual &&
+ echo $commit OK || exit 1
+ done
+ )
+'
+
-+test_expect_success GPGSSH 'verify-commit exits success on untrusted signature' '
-+ test_config gpg.ssh.allowedSignersFile "${SIGNING_ALLOWED_SIGNERS}" &&
-+ git verify-commit eighth-signed-alt 2>actual &&
-+ grep "${GOOD_SIGNATURE_UNTRUSTED}" actual &&
-+ ! grep "${BAD_SIGNATURE}" actual &&
++test_expect_success GPGSSH 'verify-commit exits failure on untrusted signature' '
++ test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" &&
++ test_must_fail git verify-commit eighth-signed-alt 2>actual &&
++ grep "${GPGSSH_GOOD_SIGNATURE_UNTRUSTED}" actual &&
++ ! grep "${GPGSSH_BAD_SIGNATURE}" actual &&
+ grep "${KEY_NOT_TRUSTED}" actual
+'
+
+test_expect_success GPGSSH 'verify-commit exits success with matching minTrustLevel' '
-+ test_config gpg.ssh.allowedSignersFile "${SIGNING_ALLOWED_SIGNERS}" &&
++ test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" &&
+ test_config gpg.minTrustLevel fully &&
+ git verify-commit sixth-signed
+'
+
+test_expect_success GPGSSH 'verify-commit exits success with low minTrustLevel' '
-+ test_config gpg.ssh.allowedSignersFile "${SIGNING_ALLOWED_SIGNERS}" &&
++ test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" &&
+ test_config gpg.minTrustLevel marginal &&
+ git verify-commit sixth-signed
+'
@@ t/t7528-signed-commit-ssh.sh (new)
+'
+
+test_expect_success GPGSSH 'verify signatures with --raw' '
-+ test_config gpg.ssh.allowedSignersFile "${SIGNING_ALLOWED_SIGNERS}" &&
++ test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" &&
+ (
+ for commit in initial second merge fourth-signed fifth-signed sixth-signed seventh-signed
+ do
+ git verify-commit --raw $commit 2>actual &&
-+ grep "${GOOD_SIGNATURE_TRUSTED}" actual &&
-+ ! grep "${BAD_SIGNATURE}" actual &&
++ grep "${GPGSSH_GOOD_SIGNATURE_TRUSTED}" actual &&
++ ! grep "${GPGSSH_BAD_SIGNATURE}" actual &&
+ echo $commit OK || exit 1
+ done
+ ) &&
@@ t/t7528-signed-commit-ssh.sh (new)
+ for commit in merge^2 fourth-unsigned sixth-unsigned seventh-unsigned
+ do
+ test_must_fail git verify-commit --raw $commit 2>actual &&
-+ ! grep "${GOOD_SIGNATURE_TRUSTED}" actual &&
-+ ! grep "${BAD_SIGNATURE}" actual &&
++ ! grep "${GPGSSH_GOOD_SIGNATURE_TRUSTED}" actual &&
++ ! grep "${GPGSSH_BAD_SIGNATURE}" actual &&
+ echo $commit OK || exit 1
+ done
+ ) &&
+ (
+ for commit in eighth-signed-alt
+ do
-+ git verify-commit --raw $commit 2>actual &&
-+ grep "${GOOD_SIGNATURE_UNTRUSTED}" actual &&
-+ ! grep "${BAD_SIGNATURE}" actual &&
++ test_must_fail git verify-commit --raw $commit 2>actual &&
++ grep "${GPGSSH_GOOD_SIGNATURE_UNTRUSTED}" actual &&
++ ! grep "${GPGSSH_BAD_SIGNATURE}" actual &&
+ echo $commit OK || exit 1
+ done
+ )
@@ t/t7528-signed-commit-ssh.sh (new)
+'
+
+test_expect_success GPGSSH 'show signed commit with signature' '
-+ test_config gpg.ssh.allowedSignersFile "${SIGNING_ALLOWED_SIGNERS}" &&
++ test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" &&
+ git show -s initial >commit &&
+ git show -s --show-signature initial >show &&
+ git verify-commit -v initial >verify.1 2>verify.2 &&
+ git cat-file commit initial >cat &&
-+ grep -v -e "${GOOD_SIGNATURE_TRUSTED}" -e "Warning: " show >show.commit &&
-+ grep -e "${GOOD_SIGNATURE_TRUSTED}" -e "Warning: " show >show.gpg &&
++ grep -v -e "${GPGSSH_GOOD_SIGNATURE_TRUSTED}" -e "Warning: " show >show.commit &&
++ grep -e "${GPGSSH_GOOD_SIGNATURE_TRUSTED}" -e "Warning: " show >show.gpg &&
+ grep -v "^ " cat | grep -v "^gpgsig.* " >cat.commit &&
+ test_cmp show.commit commit &&
+ test_cmp show.gpg verify.2 &&
@@ t/t7528-signed-commit-ssh.sh (new)
+'
+
+test_expect_success GPGSSH 'detect fudged signature' '
-+ test_config gpg.ssh.allowedSignersFile "${SIGNING_ALLOWED_SIGNERS}" &&
++ test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" &&
+ git cat-file commit seventh-signed >raw &&
+ sed -e "s/^seventh/7th forged/" raw >forged1 &&
+ git hash-object -w -t commit forged1 >forged1.commit &&
+ test_must_fail git verify-commit $(cat forged1.commit) &&
+ git show --pretty=short --show-signature $(cat forged1.commit) >actual1 &&
-+ grep "${BAD_SIGNATURE}" actual1 &&
-+ ! grep "${GOOD_SIGNATURE_TRUSTED}" actual1 &&
-+ ! grep "${GOOD_SIGNATURE_UNTRUSTED}" actual1
++ grep "${GPGSSH_BAD_SIGNATURE}" actual1 &&
++ ! grep "${GPGSSH_GOOD_SIGNATURE_TRUSTED}" actual1 &&
++ ! grep "${GPGSSH_GOOD_SIGNATURE_UNTRUSTED}" actual1
+'
+
+test_expect_success GPGSSH 'detect fudged signature with NUL' '
-+ test_config gpg.ssh.allowedSignersFile "${SIGNING_ALLOWED_SIGNERS}" &&
++ test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" &&
+ git cat-file commit seventh-signed >raw &&
+ cat raw >forged2 &&
+ echo Qwik | tr "Q" "\000" >>forged2 &&
+ git hash-object -w -t commit forged2 >forged2.commit &&
+ test_must_fail git verify-commit $(cat forged2.commit) &&
+ git show --pretty=short --show-signature $(cat forged2.commit) >actual2 &&
-+ grep "${BAD_SIGNATURE}" actual2 &&
-+ ! grep "${GOOD_SIGNATURE_TRUSTED}" actual2
++ grep "${GPGSSH_BAD_SIGNATURE}" actual2 &&
++ ! grep "${GPGSSH_GOOD_SIGNATURE_TRUSTED}" actual2
+'
+
+test_expect_success GPGSSH 'amending already signed commit' '
+ test_config gpg.format ssh &&
-+ test_config user.signingkey "${SIGNING_KEY_PRIMARY}" &&
-+ test_config gpg.ssh.allowedSignersFile "${SIGNING_ALLOWED_SIGNERS}" &&
++ test_config user.signingkey "${GPGSSH_KEY_PRIMARY}" &&
++ test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" &&
+ git checkout fourth-signed^0 &&
+ git commit --amend -S --no-edit &&
+ git verify-commit HEAD &&
+ git show -s --show-signature HEAD >actual &&
-+ grep "${GOOD_SIGNATURE_TRUSTED}" actual &&
-+ ! grep "${BAD_SIGNATURE}" actual
++ grep "${GPGSSH_GOOD_SIGNATURE_TRUSTED}" actual &&
++ ! grep "${GPGSSH_BAD_SIGNATURE}" actual
+'
+
+test_expect_success GPGSSH 'show good signature with custom format' '
-+ test_config gpg.ssh.allowedSignersFile "${SIGNING_ALLOWED_SIGNERS}" &&
-+ FINGERPRINT=$(ssh-keygen -lf "${SIGNING_KEY_PRIMARY}" | awk "{print \$2;}") &&
++ test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" &&
++ FINGERPRINT=$(ssh-keygen -lf "${GPGSSH_KEY_PRIMARY}" | awk "{print \$2;}") &&
+ cat >expect.tmpl <<-\EOF &&
+ G
+ FINGERPRINT
@@ t/t7528-signed-commit-ssh.sh (new)
+'
+
+test_expect_success GPGSSH 'show bad signature with custom format' '
-+ test_config gpg.ssh.allowedSignersFile "${SIGNING_ALLOWED_SIGNERS}" &&
++ test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" &&
+ cat >expect <<-\EOF &&
+ B
+
@@ t/t7528-signed-commit-ssh.sh (new)
+'
+
+test_expect_success GPGSSH 'show untrusted signature with custom format' '
-+ test_config gpg.ssh.allowedSignersFile "${SIGNING_ALLOWED_SIGNERS}" &&
++ test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" &&
+ cat >expect.tmpl <<-\EOF &&
+ U
+ FINGERPRINT
@@ t/t7528-signed-commit-ssh.sh (new)
+
+ EOF
+ git log -1 --format="%G?%n%GK%n%GS%n%GF%n%GP" eighth-signed-alt >actual &&
-+ FINGERPRINT=$(ssh-keygen -lf "${SIGNING_KEY_UNTRUSTED}" | awk "{print \$2;}") &&
++ FINGERPRINT=$(ssh-keygen -lf "${GPGSSH_KEY_UNTRUSTED}" | awk "{print \$2;}") &&
+ sed "s|FINGERPRINT|$FINGERPRINT|g" expect.tmpl >expect &&
+ test_cmp expect actual
+'
+
+test_expect_success GPGSSH 'show untrusted signature with undefined trust level' '
-+ test_config gpg.ssh.allowedSignersFile "${SIGNING_ALLOWED_SIGNERS}" &&
++ test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" &&
+ cat >expect.tmpl <<-\EOF &&
+ undefined
+ FINGERPRINT
@@ t/t7528-signed-commit-ssh.sh (new)
+
+ EOF
+ git log -1 --format="%GT%n%GK%n%GS%n%GF%n%GP" eighth-signed-alt >actual &&
-+ FINGERPRINT=$(ssh-keygen -lf "${SIGNING_KEY_UNTRUSTED}" | awk "{print \$2;}") &&
++ FINGERPRINT=$(ssh-keygen -lf "${GPGSSH_KEY_UNTRUSTED}" | awk "{print \$2;}") &&
+ sed "s|FINGERPRINT|$FINGERPRINT|g" expect.tmpl >expect &&
+ test_cmp expect actual
+'
+
+test_expect_success GPGSSH 'show untrusted signature with ultimate trust level' '
-+ test_config gpg.ssh.allowedSignersFile "${SIGNING_ALLOWED_SIGNERS}" &&
++ test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" &&
+ cat >expect.tmpl <<-\EOF &&
+ fully
+ FINGERPRINT
@@ t/t7528-signed-commit-ssh.sh (new)
+
+ EOF
+ git log -1 --format="%GT%n%GK%n%GS%n%GF%n%GP" sixth-signed >actual &&
-+ FINGERPRINT=$(ssh-keygen -lf "${SIGNING_KEY_PRIMARY}" | awk "{print \$2;}") &&
++ FINGERPRINT=$(ssh-keygen -lf "${GPGSSH_KEY_PRIMARY}" | awk "{print \$2;}") &&
+ sed "s|FINGERPRINT|$FINGERPRINT|g" expect.tmpl >expect &&
+ test_cmp expect actual
+'
@@ t/t7528-signed-commit-ssh.sh (new)
+'
+
+test_expect_success GPGSSH 'log.showsignature behaves like --show-signature' '
-+ test_config gpg.ssh.allowedSignersFile "${SIGNING_ALLOWED_SIGNERS}" &&
++ test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" &&
+ test_config log.showsignature true &&
+ git show initial >actual &&
-+ grep "${GOOD_SIGNATURE_TRUSTED}" actual
++ grep "${GPGSSH_GOOD_SIGNATURE_TRUSTED}" actual
+'
+
+test_expect_success GPGSSH 'check config gpg.format values' '
+ test_config gpg.format ssh &&
-+ test_config user.signingkey "${SIGNING_KEY_PRIMARY}" &&
++ test_config user.signingkey "${GPGSSH_KEY_PRIMARY}" &&
+ test_config gpg.format ssh &&
+ git commit -S --amend -m "success" &&
+ test_config gpg.format OpEnPgP &&
8: d9707443f5c ! 8: 0763517d62d ssh signing: add more tests for logs, tags & push certs
@@ Metadata
Author: Fabian Stelzer <[email protected]>
## Commit message ##
- ssh signing: add more tests for logs, tags & push certs
+ ssh signing: tests for logs, tags & push certs
Signed-off-by: Fabian Stelzer <[email protected]>
@@ t/t4202-log.sh: test_expect_success GPGSM 'setup signed branch x509' '
+test_expect_success GPGSSH 'setup sshkey signed branch' '
+ test_config gpg.format ssh &&
-+ test_config user.signingkey "${SIGNING_KEY_PRIMARY}" &&
++ test_config user.signingkey "${GPGSSH_KEY_PRIMARY}" &&
+ test_when_finished "git reset --hard && git checkout main" &&
+ git checkout -b signed-ssh main &&
+ echo foo >foo &&
@@ t/t4202-log.sh: test_expect_success GPGSM 'log OpenPGP fingerprint' '
'
+test_expect_success GPGSSH 'log ssh key fingerprint' '
-+ test_config gpg.ssh.allowedSignersFile "${SIGNING_ALLOWED_SIGNERS}" &&
-+ ssh-keygen -lf "${SIGNING_KEY_PRIMARY}" | awk "{print \$2\" | \"}" >expect &&
++ test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" &&
++ ssh-keygen -lf "${GPGSSH_KEY_PRIMARY}" | awk "{print \$2\" | \"}" >expect &&
+ git log -n1 --format="%GF | %GP" signed-ssh >actual &&
+ test_cmp expect actual
+'
@@ t/t4202-log.sh: test_expect_success GPGSM 'log --graph --show-signature x509' '
'
+test_expect_success GPGSSH 'log --graph --show-signature ssh' '
-+ test_config gpg.ssh.allowedSignersFile "${SIGNING_ALLOWED_SIGNERS}" &&
++ test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" &&
+ git log --graph --show-signature -n1 signed-ssh >actual &&
+ grep "${GOOD_SIGNATURE_TRUSTED}" actual
+'
@@ t/t5534-push-signed.sh: test_expect_success GPG 'signed push sends push certific
+test_expect_success GPGSSH 'ssh signed push sends push certificate' '
+ prepare_dst &&
+ mkdir -p dst/.git/hooks &&
-+ git -C dst config gpg.ssh.allowedSignersFile "${SIGNING_ALLOWED_SIGNERS}" &&
++ git -C dst config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" &&
+ git -C dst config receive.certnonceseed sekrit &&
+ write_script dst/.git/hooks/post-receive <<-\EOF &&
+ # discard the update list
@@ t/t5534-push-signed.sh: test_expect_success GPG 'signed push sends push certific
+ EOF
+
+ test_config gpg.format ssh &&
-+ test_config user.signingkey "${SIGNING_KEY_PRIMARY}" &&
-+ FINGERPRINT=$(ssh-keygen -lf "${SIGNING_KEY_PRIMARY}" | awk "{print \$2;}") &&
++ test_config user.signingkey "${GPGSSH_KEY_PRIMARY}" &&
++ FINGERPRINT=$(ssh-keygen -lf "${GPGSSH_KEY_PRIMARY}" | awk "{print \$2;}") &&
+ git push --signed dst noop ff +noff &&
+
+ (
@@ t/t5534-push-signed.sh: test_expect_success GPGSM 'fail without key and heed use
+ test_config gpg.format ssh &&
+ prepare_dst &&
+ mkdir -p dst/.git/hooks &&
-+ git -C dst config gpg.ssh.allowedSignersFile "${SIGNING_ALLOWED_SIGNERS}" &&
++ git -C dst config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" &&
+ git -C dst config receive.certnonceseed sekrit &&
+ write_script dst/.git/hooks/post-receive <<-\EOF &&
+ # discard the update list
@@ t/t5534-push-signed.sh: test_expect_success GPGSM 'fail without key and heed use
+ sane_unset GIT_COMMITTER_EMAIL &&
+ test_must_fail git push --signed dst noop ff +noff
+ ) &&
-+ test_config user.signingkey "${SIGNING_KEY_PRIMARY}" &&
-+ FINGERPRINT=$(ssh-keygen -lf "${SIGNING_KEY_PRIMARY}" | awk "{print \$2;}") &&
++ test_config user.signingkey "${GPGSSH_KEY_PRIMARY}" &&
++ FINGERPRINT=$(ssh-keygen -lf "${GPGSSH_KEY_PRIMARY}" | awk "{print \$2;}") &&
+ git push --signed dst noop ff +noff &&
+
+ (
@@ t/t7031-verify-tag-signed-ssh.sh (new)
+test_expect_success GPGSSH 'create signed tags ssh' '
+ test_when_finished "test_unconfig commit.gpgsign" &&
+ test_config gpg.format ssh &&
-+ test_config user.signingkey "${SIGNING_KEY_PRIMARY}" &&
++ test_config user.signingkey "${GPGSSH_KEY_PRIMARY}" &&
+
+ echo 1 >file && git add file &&
+ test_tick && git commit -m initial &&
@@ t/t7031-verify-tag-signed-ssh.sh (new)
+ git tag -m seventh -s seventh-signed &&
+
+ echo 8 >file && test_tick && git commit -a -m eighth &&
-+ git tag -u"${SIGNING_KEY_UNTRUSTED}" -m eighth eighth-signed-alt
++ git tag -u"${GPGSSH_KEY_UNTRUSTED}" -m eighth eighth-signed-alt
+'
+
+test_expect_success GPGSSH 'verify and show ssh signatures' '
-+ test_config gpg.ssh.allowedSignersFile "${SIGNING_ALLOWED_SIGNERS}" &&
++ test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" &&
+ (
+ for tag in initial second merge fourth-signed sixth-signed seventh-signed
+ do
+ git verify-tag $tag 2>actual &&
-+ grep "${GOOD_SIGNATURE_TRUSTED}" actual &&
-+ ! grep "${BAD_SIGNATURE}" actual &&
++ grep "${GPGSSH_GOOD_SIGNATURE_TRUSTED}" actual &&
++ ! grep "${GPGSSH_BAD_SIGNATURE}" actual &&
+ echo $tag OK || exit 1
+ done
+ ) &&
@@ t/t7031-verify-tag-signed-ssh.sh (new)
+ for tag in fourth-unsigned fifth-unsigned sixth-unsigned
+ do
+ test_must_fail git verify-tag $tag 2>actual &&
-+ ! grep "${GOOD_SIGNATURE_TRUSTED}" actual &&
-+ ! grep "${BAD_SIGNATURE}" actual &&
++ ! grep "${GPGSSH_GOOD_SIGNATURE_TRUSTED}" actual &&
++ ! grep "${GPGSSH_BAD_SIGNATURE}" actual &&
+ echo $tag OK || exit 1
+ done
+ ) &&
+ (
+ for tag in eighth-signed-alt
+ do
-+ git verify-tag $tag 2>actual &&
-+ grep "${GOOD_SIGNATURE_UNTRUSTED}" actual &&
-+ ! grep "${BAD_SIGNATURE}" actual &&
-+ grep "${KEY_NOT_TRUSTED}" actual &&
++ test_must_fail git verify-tag $tag 2>actual &&
++ grep "${GPGSSH_GOOD_SIGNATURE_UNTRUSTED}" actual &&
++ ! grep "${GPGSSH_BAD_SIGNATURE}" actual &&
++ grep "${GPGSSH_KEY_NOT_TRUSTED}" actual &&
+ echo $tag OK || exit 1
+ done
+ )
+'
+
+test_expect_success GPGSSH 'detect fudged ssh signature' '
-+ test_config gpg.ssh.allowedSignersFile "${SIGNING_ALLOWED_SIGNERS}" &&
++ test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" &&
+ git cat-file tag seventh-signed >raw &&
+ sed -e "/^tag / s/seventh/7th forged/" raw >forged1 &&
+ git hash-object -w -t tag forged1 >forged1.tag &&
+ test_must_fail git verify-tag $(cat forged1.tag) 2>actual1 &&
-+ grep "${BAD_SIGNATURE}" actual1 &&
-+ ! grep "${GOOD_SIGNATURE_TRUSTED}" actual1 &&
-+ ! grep "${GOOD_SIGNATURE_UNTRUSTED}" actual1
++ grep "${GPGSSH_BAD_SIGNATURE}" actual1 &&
++ ! grep "${GPGSSH_GOOD_SIGNATURE_TRUSTED}" actual1 &&
++ ! grep "${GPGSSH_GOOD_SIGNATURE_UNTRUSTED}" actual1
+'
+
+test_expect_success GPGSSH 'verify ssh signatures with --raw' '
-+ test_config gpg.ssh.allowedSignersFile "${SIGNING_ALLOWED_SIGNERS}" &&
++ test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" &&
+ (
+ for tag in initial second merge fourth-signed sixth-signed seventh-signed
+ do
+ git verify-tag --raw $tag 2>actual &&
-+ grep "${GOOD_SIGNATURE_TRUSTED}" actual &&
-+ ! grep "${BAD_SIGNATURE}" actual &&
++ grep "${GPGSSH_GOOD_SIGNATURE_TRUSTED}" actual &&
++ ! grep "${GPGSSH_BAD_SIGNATURE}" actual &&
+ echo $tag OK || exit 1
+ done
+ ) &&
@@ t/t7031-verify-tag-signed-ssh.sh (new)
+ for tag in fourth-unsigned fifth-unsigned sixth-unsigned
+ do
+ test_must_fail git verify-tag --raw $tag 2>actual &&
-+ ! grep "${GOOD_SIGNATURE_TRUSTED}" actual &&
-+ ! grep "${BAD_SIGNATURE}" actual &&
++ ! grep "${GPGSSH_GOOD_SIGNATURE_TRUSTED}" actual &&
++ ! grep "${GPGSSH_BAD_SIGNATURE}" actual &&
+ echo $tag OK || exit 1
+ done
+ ) &&
+ (
+ for tag in eighth-signed-alt
+ do
-+ git verify-tag --raw $tag 2>actual &&
-+ grep "${GOOD_SIGNATURE_UNTRUSTED}" actual &&
-+ ! grep "${BAD_SIGNATURE}" actual &&
++ test_must_fail git verify-tag --raw $tag 2>actual &&
++ grep "${GPGSSH_GOOD_SIGNATURE_UNTRUSTED}" actual &&
++ ! grep "${GPGSSH_BAD_SIGNATURE}" actual &&
+ echo $tag OK || exit 1
+ done
+ )
+'
+
+test_expect_success GPGSSH 'verify signatures with --raw ssh' '
-+ test_config gpg.ssh.allowedSignersFile "${SIGNING_ALLOWED_SIGNERS}" &&
++ test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" &&
+ git verify-tag --raw sixth-signed 2>actual &&
-+ grep "${GOOD_SIGNATURE_TRUSTED}" actual &&
-+ ! grep "${BAD_SIGNATURE}" actual &&
++ grep "${GPGSSH_GOOD_SIGNATURE_TRUSTED}" actual &&
++ ! grep "${GPGSSH_BAD_SIGNATURE}" actual &&
+ echo sixth-signed OK
+'
+
+test_expect_success GPGSSH 'verify multiple tags ssh' '
-+ test_config gpg.ssh.allowedSignersFile "${SIGNING_ALLOWED_SIGNERS}" &&
++ test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" &&
+ tags="seventh-signed sixth-signed" &&
+ for i in $tags
+ do
+ git verify-tag -v --raw $i || return 1
+ done >expect.stdout 2>expect.stderr.1 &&
-+ grep "^${GOOD_SIGNATURE_TRUSTED}" <expect.stderr.1 >expect.stderr &&
++ grep "^${GPGSSH_GOOD_SIGNATURE_TRUSTED}" <expect.stderr.1 >expect.stderr &&
+ git verify-tag -v --raw $tags >actual.stdout 2>actual.stderr.1 &&
-+ grep "^${GOOD_SIGNATURE_TRUSTED}" <actual.stderr.1 >actual.stderr &&
++ grep "^${GPGSSH_GOOD_SIGNATURE_TRUSTED}" <actual.stderr.1 >actual.stderr &&
+ test_cmp expect.stdout actual.stdout &&
+ test_cmp expect.stderr actual.stderr
+'
+
+test_expect_success GPGSSH 'verifying tag with --format - ssh' '
-+ test_config gpg.ssh.allowedSignersFile "${SIGNING_ALLOWED_SIGNERS}" &&
++ test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" &&
+ cat >expect <<-\EOF &&
+ tagname : fourth-signed
+ EOF
9: 275af516eba < -: ----------- ssh signing: add documentation
-: ----------- > 9: a5add98197a ssh signing: test that gpg fails for unkown keysSignature · 1 line
gitgitgadget
PATCH v7 1/9 ssh signing: preliminary refactoring and clean-up
From: Fabian Stelzer <[email protected]>
Openssh v8.2p1 added some new options to ssh-keygen for signature creation and verification. These allow us to use ssh keys for git signatures easily.
In our corporate environment we use PIV x509 Certs on Yubikeys for email signing/encryption and ssh keys which I think is quite common
(at least for the email part). This way we can establish the correct trust for the SSH Keys without setting up a separate GPG Infrastructure (which is still quite painful for users) or implementing x509 signing support for git (which lacks good forwarding mechanisms).
Using ssh agent forwarding makes this feature easily usable in todays development environments where code is often checked out in remote VMs / containers. In such a setup the keyring & revocationKeyring can be centrally generated from the x509 CA information and distributed to the users.
To be able to implement new signing formats this commit:
- makes the sigc structure more generic by renaming "gpg_output" to "output"
- introduces function pointers in the gpg_format structure to call format specific signing and verification functions
- moves format detection from verify_signed_buffer into the check_signature api function and calls the format specific verify
- renames and wraps sign_buffer to handle format specific signing logic as well
--- fmt-merge-msg.c | 6 +-- gpg-interface.c | 104 +++++++++++++++++++++++++++++------------------- gpg-interface.h | 2 +- log-tree.c | 8 ++-- pretty.c | 4 +- 5 files changed, 74 insertions(+), 50 deletions(-)
fmt-merge-msg.c +3 -3
@@ -526,11 +526,11 @@ static void fmt_merge_msg_sigs(struct strbuf *out) buf = payload.buf; len = payload.len;if (check_signature (payload.buf, payload.len, sig.buf, - sig.len, &sigc) && - !sigc.gpg_output) + sig.len, &sigc) && + !sigc.output)strbuf_addstr (&sig,"gpg verification failed.\n" );else -strbuf_addstr (&sig, sigc.gpg_output); +strbuf_addstr (&sig, sigc.output); }signature_check_clear (&sigc);
gpg-interface.c +64 -40
176 lines not on this page · raw message
gpg-interface.h +1 -1
@@ -17,7 +17,7 @@ enum signature_trust_level {struct signature_check {char *payload; -char *gpg_output; +char *output;char *gpg_status;/*
log-tree.c +4 -4
@@ -515,10 +515,10 @@ static void show_signature(struct rev_info *opt, struct commit *commit) status =check_signature (payload.buf, payload.len, signature.buf, signature.len, &sigc); -if (status && !sigc.gpg_output) +if (status && !sigc.output)show_sig_lines (opt, status,"No signature\n" );else -show_sig_lines (opt, status, sigc.gpg_output); +show_sig_lines (opt, status, sigc.output);signature_check_clear (&sigc); out:
@@ -585,8 +585,8 @@ static int show_one_mergetag(struct commit *commit,/* could have a good signature */ status =check_signature (payload.buf, payload.len, signature.buf, signature.len, &sigc); -if (sigc.gpg_output) -strbuf_addstr (&verify_message, sigc.gpg_output); +if (sigc.output) +strbuf_addstr (&verify_message, sigc.output);else strbuf_addstr (&verify_message,"No signature\n" );signature_check_clear (&sigc);
pretty.c +2 -2
@@ -1432,8 +1432,8 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */check_commit_signature (c->commit, &(c->signature_check));switch (placeholder[1 ]) {case 'G' : -if (c->signature_check.gpg_output) -strbuf_addstr (sb, c->signature_check.gpg_output); +if (c->signature_check.output) +strbuf_addstr (sb, c->signature_check.output);break ;case '?' :switch (c->signature_check.result) {
Signature · 1 line
gitgitgadget
PATCH v7 2/9 ssh signing: add test prereqs
In reply to Fabian Stelzer via GitGitGadgetFrom: Fabian Stelzer <[email protected]>
Generate some ssh keys and a allowedSignersFile for testing
--- t/lib-gpg.sh | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+)
t/lib-gpg.sh +28 -0
@@ -87,6 +87,34 @@ test_lazy_prereq RFC1991 ' echo | gpg --homedir "${GNUPGHOME}" -b --rfc1991 >/dev/null ' +GPGSSH_KEY_PRIMARY="${GNUPGHOME}/ed25519_ssh_signing_key" +GPGSSH_KEY_SECONDARY="${GNUPGHOME}/rsa_2048_ssh_signing_key" +GPGSSH_KEY_UNTRUSTED="${GNUPGHOME}/untrusted_ssh_signing_key" +GPGSSH_KEY_WITH_PASSPHRASE="${GNUPGHOME}/protected_ssh_signing_key" +GPGSSH_KEY_PASSPHRASE="super_secret" +GPGSSH_ALLOWED_SIGNERS="${GNUPGHOME}/ssh.all_valid.allowedSignersFile" + +GPGSSH_GOOD_SIGNATURE_TRUSTED='Good "git" signature for' +GPGSSH_GOOD_SIGNATURE_UNTRUSTED='Good "git" signature with' +GPGSSH_KEY_NOT_TRUSTED="No principal matched" +GPGSSH_BAD_SIGNATURE="Signature verification failed" + +test_lazy_prereq GPGSSH ' + ssh_version=$(ssh-keygen -Y find-principals -n "git" 2>&1) + test $? != 127 || exit 1 + echo $ssh_version | grep -q "find-principals:missing signature file" + test $? = 0 || exit 1; + mkdir -p "${GNUPGHOME}" && + chmod 0700 "${GNUPGHOME}" && + ssh-keygen -t ed25519 -N "" -C "git ed25519 key" -f "${GPGSSH_KEY_PRIMARY}" >/dev/null && + echo "\"principal with number 1\" $(cat "${GPGSSH_KEY_PRIMARY}.pub")" >> "${GPGSSH_ALLOWED_SIGNERS}" && + ssh-keygen -t rsa -b 2048 -N "" -C "git rsa2048 key" -f "${GPGSSH_KEY_SECONDARY}" >/dev/null && + echo "\"principal with number 2\" $(cat "${GPGSSH_KEY_SECONDARY}.pub")" >> "${GPGSSH_ALLOWED_SIGNERS}" && + ssh-keygen -t ed25519 -N "${GPGSSH_KEY_PASSPHRASE}" -C "git ed25519 encrypted key" -f "${GPGSSH_KEY_WITH_PASSPHRASE}" >/dev/null && + echo "\"principal with number 3\" $(cat "${GPGSSH_KEY_WITH_PASSPHRASE}.pub")" >> "${GPGSSH_ALLOWED_SIGNERS}" && + ssh-keygen -t ed25519 -N "" -f "${GPGSSH_KEY_UNTRUSTED}" >/dev/null +' + sanitize_pgp() { perl -ne ' /^-----END PGP/ and $in_pgp = 0;
Signature · 1 line
gitgitgadget
PATCH v7 3/9 ssh signing: add ssh key format and signing code
In reply to Fabian Stelzer via GitGitGadgetFrom: Fabian Stelzer <[email protected]>
Implements the actual sign_buffer_ssh operation and move some shared cleanup code into a strbuf function
Set gpg.format = ssh and user.signingkey to either a ssh public key string (like from an authorized_keys file), or a ssh key file. If the key file or the config value itself contains only a public key then the private key needs to be available via ssh-agent.
gpg.ssh.program can be set to an alternative location of ssh-keygen. A somewhat recent openssh version (8.2p1+) of ssh-keygen is needed for this feature. Since only ssh-keygen is needed it can this way be installed seperately without upgrading your system openssh packages.
--- Documentation/config/gpg.txt | 4 +- Documentation/config/user.txt | 5 ++ gpg-interface.c | 138 ++++++++++++++++++++++++++++++++-- 3 files changed, 137 insertions(+), 10 deletions(-)
Documentation/config/gpg.txt +2 -2
@@ -11,13 +11,13 @@ gpg.program:: gpg.format:: Specifies which key format to use when signing with `--gpg-sign`. - Default is "openpgp" and another possible value is "x509". + Default is "openpgp". Other possible values are "x509", "ssh". gpg.<format>.program:: Use this to customize the program used for the signing format you chose. (see `gpg.program` and `gpg.format`) `gpg.program` can still be used as a legacy synonym for `gpg.openpgp.program`. The default - value for `gpg.x509.program` is "gpgsm". + value for `gpg.x509.program` is "gpgsm" and `gpg.ssh.program` is "ssh-keygen". gpg.minTrustLevel:: Specifies a minimum trust level for signature verification. If
Documentation/config/user.txt +5 -0
@@ -36,3 +36,8 @@ user.signingKey:: commit, you can override the default selection with this variable. This option is passed unchanged to gpg's --local-user parameter, so you may specify a key using any method that gpg supports. + If gpg.format is set to "ssh" this can contain the literal ssh public + key (e.g.: "ssh-rsa XXXXXX identifier") or a file which contains it and + corresponds to the private key used for signing. The private key + needs to be available via ssh-agent. Alternatively it can be set to + a file containing a private key directly.
gpg-interface.c +130 -8
179 lines not on this page · raw message
Signature · 1 line
gitgitgadget
PATCH v7 4/9 ssh signing: retrieve a default key from ssh-agent
In reply to Fabian Stelzer via GitGitGadgetFrom: Fabian Stelzer <[email protected]>
If user.signingkey is not set and a ssh signature is requested we call gpg.ssh.defaultKeyCommand (typically "ssh-add -L") and use the first key we get
--- Documentation/config/gpg.txt | 6 +++ Documentation/config/user.txt | 4 +- gpg-interface.c | 70 ++++++++++++++++++++++++++++++++++- 3 files changed, 77 insertions(+), 3 deletions(-)
Documentation/config/gpg.txt +6 -0
@@ -33,3 +33,9 @@ gpg.minTrustLevel:: * `marginal` * `fully` * `ultimate` + +gpg.ssh.defaultKeyCommand: + This command that will be run when user.signingkey is not set and a ssh + signature is requested. On successful exit a valid ssh public key is + expected in the first line of its output. To automatically use the first + available key from your ssh-agent set this to "ssh-add -L".
Documentation/config/user.txt +3 -1
@@ -40,4 +40,6 @@ user.signingKey:: key (e.g.: "ssh-rsa XXXXXX identifier") or a file which contains it and corresponds to the private key used for signing. The private key needs to be available via ssh-agent. Alternatively it can be set to - a file containing a private key directly. + a file containing a private key directly. If not set git will call + gpg.ssh.defaultKeyCommand (e.g.: "ssh-add -L") and try to use the first + key available.
gpg-interface.c +68 -2
124 lines not on this page · raw message
Signature · 1 line
gitgitgadget
PATCH v7 5/9 ssh signing: provide a textual signing_key_id
In reply to Fabian Stelzer via GitGitGadgetFrom: Fabian Stelzer <[email protected]>
For ssh the user.signingkey can be a filename/path or even a literal ssh pubkey. In push certs and textual output we prefer the ssh fingerprint instead.
--- gpg-interface.c | 56 +++++++++++++++++++++++++++++++++++++++++++++++++ gpg-interface.h | 6 ++++++ send-pack.c | 8 +++---- 3 files changed, 66 insertions(+), 4 deletions(-)
gpg-interface.c +56 -0
@@ -24,6 +24,7 @@ struct gpg_format {int (*sign_buffer)(struct strbuf *buffer,struct strbuf *signature,const char *signing_key);const char *(*get_default_key)(void ); +const char *(*get_key_id)(void ); };static const char *openpgp_verify_args[] = {
@@ -61,6 +62,8 @@ static int sign_buffer_ssh(struct strbuf *buffer, struct strbuf *signature,static const char *get_default_ssh_signing_key (void ); +static const char *get_ssh_key_id (void ); +static struct gpg_format gpg_format[] = { { .name ="openpgp" ,
@@ -70,6 +73,7 @@ static struct gpg_format gpg_format[] = { .verify_signed_buffer = verify_gpg_signed_buffer, .sign_buffer = sign_buffer_gpg, .get_default_key = NULL, + .get_key_id = NULL, }, { .name = "x509",
@@ -79,6 +83,7 @@ static struct gpg_format gpg_format[] = { .verify_signed_buffer = verify_gpg_signed_buffer, .sign_buffer = sign_buffer_gpg, .get_default_key = NULL, + .get_key_id = NULL, }, { .name = "ssh",
@@ -88,6 +93,7 @@ static struct gpg_format gpg_format[] = { .verify_signed_buffer = NULL, /* TODO */ .sign_buffer = sign_buffer_ssh, .get_default_key = get_default_ssh_signing_key, + .get_key_id = get_ssh_key_id, }, };
@@ -484,6 +490,41 @@ int git_gpg_config(const char *var, const char *value, void *cb)return 0 ; } +static char *get_ssh_key_fingerprint (const char *signing_key) +{ +struct child_process ssh_keygen =CHILD_PROCESS_INIT ; +int ret =-1 ; +struct strbuf fingerprint_stdout =STRBUF_INIT ; +struct strbuf **fingerprint; + +/* +* With SSH Signing this can contain a filename or a public key +* For textual representation we usually want a fingerprint +*/ +if (starts_with (signing_key,"ssh-" )) { +strvec_pushl (&ssh_keygen.args,"ssh-keygen" ,"-lf" ,"-" ,NULL ); + ret =pipe_command (&ssh_keygen, signing_key, +strlen (signing_key), &fingerprint_stdout,0 , +NULL ,0 ); + }else { +strvec_pushl (&ssh_keygen.args,"ssh-keygen" ,"-lf" , + configured_signing_key,NULL ); + ret =pipe_command (&ssh_keygen,NULL ,0 , &fingerprint_stdout,0 , +NULL ,0 ); + } + +if (!!ret) +die_errno (_ ("failed to get the ssh fingerprint for key '%s'" ), + signing_key); + + fingerprint =strbuf_split_max (&fingerprint_stdout,' ' ,3 ); +if (!fingerprint[1 ]) +die_errno (_ ("failed to get the ssh fingerprint for key '%s'" ), + signing_key); + +return strbuf_detach (fingerprint[1 ],NULL ); +} +/* Returns the first public key from an ssh-agent to use for signing */ static const char *get_default_ssh_signing_key (void ) {
@@ -532,6 +573,21 @@ static const char *get_default_ssh_signing_key(void)return default_key; } +static const char *get_ssh_key_id (void ) { +return get_ssh_key_fingerprint (get_signing_key ()); +} + +/* Returns a textual but unique representation of the signing key */ +const char *get_signing_key_id (void ) +{ +if (use_format->get_key_id) { +return use_format->get_key_id (); + } + +/* GPG/GPGSM only store a key id on this variable */ +return get_signing_key (); +} +const char *get_signing_key (void ) {if (configured_signing_key)
gpg-interface.h +6 -0
@@ -64,6 +64,12 @@ int sign_buffer(struct strbuf *buffer, struct strbuf *signature,int git_gpg_config (const char *,const char *,void *);void set_signing_key (const char *);const char *get_signing_key (void ); + +/* +* Returns a textual unique representation of the signing key in use +* Either a GPG KeyID or a SSH Key Fingerprint +*/ +const char *get_signing_key_id (void );int check_signature (const char *payload,size_t plen,const char *signature,size_t slen,struct signature_check *sigc);
send-pack.c +4 -4
@@ -341,13 +341,13 @@ static int generate_push_cert(struct strbuf *req_buf, {const struct ref *ref;struct string_list_item *item; -char *signing_key =xstrdup (get_signing_key ()); +char *signing_key_id =xstrdup (get_signing_key_id ());const char *cp, *np;struct strbuf cert =STRBUF_INIT ;int update_seen =0 ;strbuf_addstr (&cert,"certificate version 0.1\n" ); -strbuf_addf (&cert,"pusher %s " , signing_key); +strbuf_addf (&cert,"pusher %s " , signing_key_id);datestamp (&cert);strbuf_addch (&cert,'\n' );if (args->url && *args->url) {
@@ -374,7 +374,7 @@ static int generate_push_cert(struct strbuf *req_buf,if (!update_seen) goto free_return; -if (sign_buffer (&cert, &cert, signing_key)) +if (sign_buffer (&cert, &cert,get_signing_key ()))die (_ ("failed to sign the push certificate" ));packet_buf_write (req_buf,"push-cert%c%s" ,0 , cap_string);
@@ -386,7 +386,7 @@ static int generate_push_cert(struct strbuf *req_buf,packet_buf_write (req_buf,"push-cert-end\n" ); free_return: -free (signing_key); +free (signing_key_id);strbuf_release (&cert);return update_seen; }
Signature · 1 line
gitgitgadget
PATCH v7 6/9 ssh signing: verify signatures using ssh-keygen
In reply to Fabian Stelzer via GitGitGadgetFrom: Fabian Stelzer <[email protected]>
To verify a ssh signature we first call ssh-keygen -Y find-principal to look up the signing principal by their public key from the allowedSignersFile. If the key is found then we do a verify. Otherwise we only validate the signature but can not verify the signers identity.
Verification uses the gpg.ssh.allowedSignersFile (see ssh-keygen(1) "ALLOWED SIGNERS") which contains valid public keys and a principal (usually user@domain). Depending on the environment this file can be managed by the individual developer or for example generated by the central repository server from known ssh keys with push access. This file is usually stored outside the repository, but if the repository only allows signed commits/pushes, the user might choose to store it in the repository.
To revoke a key put the public key without the principal prefix into gpg.ssh.revocationKeyring or generate a KRL (see ssh-keygen(1) "KEY REVOCATION LISTS"). The same considerations about who to trust for verification as with the allowedSignersFile apply.
Using SSH CA Keys with these files is also possible. Add "cert-authority" as key option between the principal and the key to mark it as a CA and all keys signed by it as valid for this CA. See "CERTIFICATES" in ssh-keygen(1).
--- Documentation/config/gpg.txt | 35 ++++++ builtin/receive-pack.c | 4 + gpg-interface.c | 209 ++++++++++++++++++++++++++++++++++- 3 files changed, 246 insertions(+), 2 deletions(-)
Documentation/config/gpg.txt +35 -0
@@ -39,3 +39,38 @@ gpg.ssh.defaultKeyCommand: signature is requested. On successful exit a valid ssh public key is expected in the first line of its output. To automatically use the first available key from your ssh-agent set this to "ssh-add -L". + +gpg.ssh.allowedSignersFile:: + A file containing ssh public keys which you are willing to trust. + The file consists of one or more lines of principals followed by an ssh + public key. + e.g.: [email protected],[email protected] ssh-rsa AAAAX1... + See ssh-keygen(1) "ALLOWED SIGNERS" for details. + The principal is only used to identify the key and is available when + verifying a signature. ++ +SSH has no concept of trust levels like gpg does. To be able to differentiate +between valid signatures and trusted signatures the trust level of a signature +verification is set to `fully` when the public key is present in the allowedSignersFile. +Therefore to only mark fully trusted keys as verified set gpg.minTrustLevel to `fully`. +Otherwise valid but untrusted signatures will still verify but show no principal +name of the signer. ++ +This file can be set to a location outside of the repository and every developer +maintains their own trust store. A central repository server could generate this +file automatically from ssh keys with push access to verify the code against. +In a corporate setting this file is probably generated at a global location +from automation that already handles developer ssh keys. ++ +A repository that only allows signed commits can store the file +in the repository itself using a path relative to the top-level of the working tree. +This way only committers with an already valid key can add or change keys in the keyring. ++ +Using a SSH CA key with the cert-authority option +(see ssh-keygen(1) "CERTIFICATES") is also valid. + +gpg.ssh.revocationFile:: + Either a SSH KRL or a list of revoked public keys (without the principal prefix). + See ssh-keygen(1) for details. + If a public key is found in this file then it will always be treated + as having trust level "never" and signatures will show as invalid.
builtin/receive-pack.c +4 -0
@@ -131,6 +131,10 @@ static int receive_pack_config(const char *var, const char *value, void *cb) {int status =parse_hide_refs_config (var, value,"receive" ); +if (status) +return status; + + status =git_gpg_config (var, value,NULL );if (status)return status;
gpg-interface.c +207 -2
245 lines not on this page · raw message
Signature · 1 line
gitgitgadget
Re: [PATCH v7 6/9] ssh signing: verify signatures using ssh-keygen
Quoted 5 lines · Fabian Stelzer via GitGitGadget
builtin/receive-pack.c +0
-0
builtin/receive-pack.c static int receive_pack_config(const char *var, const char *value, void *cb) · line 139
Quoted 9 lines
builtin/receive-pack.c +4
-0@@ -131,6 +131,10 @@ static int receive_pack_config(const char *var, const char *value, void *cb) {int status =parse_hide_refs_config (var, value,"receive" ); +if (status) +return status; + + status =git_gpg_config (var, value,NULL );if (status)return status;
Hmph, it feels a bit odd for a misconfigured "transfer.hiderefs" to prevent GPG related configuration from getting read, but is this because a failure from receive_pack_config() will immediately kill the process without doing any harm to the system? If so, the code is good as written.
Re: [PATCH v7 6/9] ssh signing: verify signatures using ssh-keygen
Quoted 10 lines · Junio C Hamano
Quoted 8 lines · Fabian Stelzer via GitGitGadget
builtin/receive-pack.c +0
-0@@ -131,6 +131,10 @@ static int receive_pack_config(const char *var, const char *value, void *cb) {int status =parse_hide_refs_config (var, value,"receive" );
Quoted 13 lines
builtin/receive-pack.c static int receive_pack_config(const char *var, const char *value, void *cb) · line 139
Quoted 6 lines
builtin/receive-pack.c +4
-0+if (status) +return status; + + status =git_gpg_config (var, value,NULL );if (status)return status;Hmph, it feels a bit odd for a misconfigured "transfer.hiderefs" to prevent GPG related configuration from getting read, but is this because a failure from
receive_pack_config()will immediately kill the process without doing any harm to the system? If so, the code is good as written.
I think i misunderstood the comment from Jonathan about this. He wrote:
"Check the return value of git_gpg_config() to see if that config was processed by that function - if yes, we can return early."
Looking at git_gpg_config i don't think i can actually determine by its return code if a value was successfully processed (it will also return 0 when nothing happened).
The return in case parse_hide_refs fails was already in place before my change and returning on git_gpg_config failure is done in most of the other commands calling it. builtin/send-pack.c is the exception but i have no idea why.
Generally i think a broken config should die() early as it does in this case with the return.
Re: [PATCH v7 6/9] ssh signing: verify signatures using ssh-keygen
Quoted 2 lines · Fabian Stelzer
Generally i think a broken config should
die()early as it does in this case with the return.
Yes, I was just making sure if somebody took a look at the callchain to make sure it dies, as I didn't ;-)
Thanks.
PATCH v7 7/9 ssh signing: duplicate t7510 tests for commits
In reply to Fabian Stelzer via GitGitGadgetFrom: Fabian Stelzer <[email protected]>
--- t/t7528-signed-commit-ssh.sh | 398 +++++++++++++++++++++++++++++++++++ 1 file changed, 398 insertions(+) create mode 100755 t/t7528-signed-commit-ssh.sh
t/t7528-signed-commit-ssh.sh new file +398 -0
@@ -0,0 +1,398 @@ +#!/bin/sh + +test_description='ssh signed commit tests' +GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main +export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME + +. ./test-lib.sh +GNUPGHOME_NOT_USED=$GNUPGHOME +. "$TEST_DIRECTORY/lib-gpg.sh" + +test_expect_success GPGSSH 'create signed commits' ' + test_oid_cache <<-\EOF && + header sha1:gpgsig + header sha256:gpgsig-sha256 + EOF + + test_when_finished "test_unconfig commit.gpgsign" && + test_config gpg.format ssh && + test_config user.signingkey "${GPGSSH_KEY_PRIMARY}" && + + echo 1 >file && git add file && + test_tick && git commit -S -m initial && + git tag initial && + git branch side && + + echo 2 >file && test_tick && git commit -a -S -m second && + git tag second && + + git checkout side && + echo 3 >elif && git add elif && + test_tick && git commit -m "third on side" && + + git checkout main && + test_tick && git merge -S side && + git tag merge && + + echo 4 >file && test_tick && git commit -a -m "fourth unsigned" && + git tag fourth-unsigned && + + test_tick && git commit --amend -S -m "fourth signed" && + git tag fourth-signed && + + git config commit.gpgsign true && + echo 5 >file && test_tick && git commit -a -m "fifth signed" && + git tag fifth-signed && + + git config commit.gpgsign false && + echo 6 >file && test_tick && git commit -a -m "sixth" && + git tag sixth-unsigned && + + git config commit.gpgsign true && + echo 7 >file && test_tick && git commit -a -m "seventh" --no-gpg-sign && + git tag seventh-unsigned && + + test_tick && git rebase -f HEAD^^ && git tag sixth-signed HEAD^ && + git tag seventh-signed && + + echo 8 >file && test_tick && git commit -a -m eighth -S"${GPGSSH_KEY_UNTRUSTED}" && + git tag eighth-signed-alt && + + # commit.gpgsign is still on but this must not be signed + echo 9 | git commit-tree HEAD^{tree} >oid && + test_line_count = 1 oid && + git tag ninth-unsigned $(cat oid) && + # explicit -S of course must sign. + echo 10 | git commit-tree -S HEAD^{tree} >oid && + test_line_count = 1 oid && + git tag tenth-signed $(cat oid) && + + # --gpg-sign[=<key-id>] must sign. + echo 11 | git commit-tree --gpg-sign HEAD^{tree} >oid && + test_line_count = 1 oid && + git tag eleventh-signed $(cat oid) && + echo 12 | git commit-tree --gpg-sign="${GPGSSH_KEY_UNTRUSTED}" HEAD^{tree} >oid && + test_line_count = 1 oid && + git tag twelfth-signed-alt $(cat oid) +' + +test_expect_success GPGSSH 'verify and show signatures' ' + test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" && + test_config gpg.mintrustlevel UNDEFINED && + ( + for commit in initial second merge fourth-signed \ + fifth-signed sixth-signed seventh-signed tenth-signed \ + eleventh-signed + do + git verify-commit $commit && + git show --pretty=short --show-signature $commit >actual && + grep "${GPGSSH_GOOD_SIGNATURE_TRUSTED}" actual && + ! grep "${GPGSSH_BAD_SIGNATURE}" actual && + echo $commit OK || exit 1 + done + ) && + ( + for commit in merge^2 fourth-unsigned sixth-unsigned \ + seventh-unsigned ninth-unsigned + do + test_must_fail git verify-commit $commit && + git show --pretty=short --show-signature $commit >actual && + ! grep "${GPGSSH_GOOD_SIGNATURE_TRUSTED}" actual && + ! grep "${GPGSSH_BAD_SIGNATURE}" actual && + echo $commit OK || exit 1 + done + ) && + ( + for commit in eighth-signed-alt twelfth-signed-alt + do + git show --pretty=short --show-signature $commit >actual && + grep "${GPGSSH_GOOD_SIGNATURE_UNTRUSTED}" actual && + ! grep "${GPGSSH_BAD_SIGNATURE}" actual && + grep "${KEY_NOT_TRUSTED}" actual && + echo $commit OK || exit 1 + done + ) +' + +test_expect_success GPGSSH 'verify-commit exits failure on untrusted signature' ' + test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" && + test_must_fail git verify-commit eighth-signed-alt 2>actual && + grep "${GPGSSH_GOOD_SIGNATURE_UNTRUSTED}" actual && + ! grep "${GPGSSH_BAD_SIGNATURE}" actual && + grep "${KEY_NOT_TRUSTED}" actual +' + +test_expect_success GPGSSH 'verify-commit exits success with matching minTrustLevel' ' + test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" && + test_config gpg.minTrustLevel fully && + git verify-commit sixth-signed +' + +test_expect_success GPGSSH 'verify-commit exits success with low minTrustLevel' ' + test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" && + test_config gpg.minTrustLevel marginal && + git verify-commit sixth-signed +' + +test_expect_success GPGSSH 'verify-commit exits failure with high minTrustLevel' ' + test_config gpg.minTrustLevel ultimate && + test_must_fail git verify-commit eighth-signed-alt +' + +test_expect_success GPGSSH 'verify signatures with --raw' ' + test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" && + ( + for commit in initial second merge fourth-signed fifth-signed sixth-signed seventh-signed + do + git verify-commit --raw $commit 2>actual && + grep "${GPGSSH_GOOD_SIGNATURE_TRUSTED}" actual && + ! grep "${GPGSSH_BAD_SIGNATURE}" actual && + echo $commit OK || exit 1 + done + ) && + ( + for commit in merge^2 fourth-unsigned sixth-unsigned seventh-unsigned + do + test_must_fail git verify-commit --raw $commit 2>actual && + ! grep "${GPGSSH_GOOD_SIGNATURE_TRUSTED}" actual && + ! grep "${GPGSSH_BAD_SIGNATURE}" actual && + echo $commit OK || exit 1 + done + ) && + ( + for commit in eighth-signed-alt + do + test_must_fail git verify-commit --raw $commit 2>actual && + grep "${GPGSSH_GOOD_SIGNATURE_UNTRUSTED}" actual && + ! grep "${GPGSSH_BAD_SIGNATURE}" actual && + echo $commit OK || exit 1 + done + ) +' + +test_expect_success GPGSSH 'proper header is used for hash algorithm' ' + git cat-file commit fourth-signed >output && + grep "^$(test_oid header) -----BEGIN SSH SIGNATURE-----" output +' + +test_expect_success GPGSSH 'show signed commit with signature' ' + test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" && + git show -s initial >commit && + git show -s --show-signature initial >show && + git verify-commit -v initial >verify.1 2>verify.2 && + git cat-file commit initial >cat && + grep -v -e "${GPGSSH_GOOD_SIGNATURE_TRUSTED}" -e "Warning: " show >show.commit && + grep -e "${GPGSSH_GOOD_SIGNATURE_TRUSTED}" -e "Warning: " show >show.gpg && + grep -v "^ " cat | grep -v "^gpgsig.* " >cat.commit && + test_cmp show.commit commit && + test_cmp show.gpg verify.2 && + test_cmp cat.commit verify.1 +' + +test_expect_success GPGSSH 'detect fudged signature' ' + test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" && + git cat-file commit seventh-signed >raw && + sed -e "s/^seventh/7th forged/" raw >forged1 && + git hash-object -w -t commit forged1 >forged1.commit && + test_must_fail git verify-commit $(cat forged1.commit) && + git show --pretty=short --show-signature $(cat forged1.commit) >actual1 && + grep "${GPGSSH_BAD_SIGNATURE}" actual1 && + ! grep "${GPGSSH_GOOD_SIGNATURE_TRUSTED}" actual1 && + ! grep "${GPGSSH_GOOD_SIGNATURE_UNTRUSTED}" actual1 +' + +test_expect_success GPGSSH 'detect fudged signature with NUL' ' + test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" && + git cat-file commit seventh-signed >raw && + cat raw >forged2 && + echo Qwik | tr "Q" "\000" >>forged2 && + git hash-object -w -t commit forged2 >forged2.commit && + test_must_fail git verify-commit $(cat forged2.commit) && + git show --pretty=short --show-signature $(cat forged2.commit) >actual2 && + grep "${GPGSSH_BAD_SIGNATURE}" actual2 && + ! grep "${GPGSSH_GOOD_SIGNATURE_TRUSTED}" actual2 +' + +test_expect_success GPGSSH 'amending already signed commit' ' + test_config gpg.format ssh && + test_config user.signingkey "${GPGSSH_KEY_PRIMARY}" && + test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" && + git checkout fourth-signed^0 && + git commit --amend -S --no-edit && + git verify-commit HEAD && + git show -s --show-signature HEAD >actual && + grep "${GPGSSH_GOOD_SIGNATURE_TRUSTED}" actual && + ! grep "${GPGSSH_BAD_SIGNATURE}" actual +' + +test_expect_success GPGSSH 'show good signature with custom format' ' + test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" && + FINGERPRINT=$(ssh-keygen -lf "${GPGSSH_KEY_PRIMARY}" | awk "{print \$2;}") && + cat >expect.tmpl <<-\EOF && + G + FINGERPRINT + principal with number 1 + FINGERPRINT + + EOF + sed "s|FINGERPRINT|$FINGERPRINT|g" expect.tmpl >expect && + git log -1 --format="%G?%n%GK%n%GS%n%GF%n%GP" sixth-signed >actual && + test_cmp expect actual +' + +test_expect_success GPGSSH 'show bad signature with custom format' ' + test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" && + cat >expect <<-\EOF && + B + + + + + EOF + git log -1 --format="%G?%n%GK%n%GS%n%GF%n%GP" $(cat forged1.commit) >actual && + test_cmp expect actual +' + +test_expect_success GPGSSH 'show untrusted signature with custom format' ' + test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" && + cat >expect.tmpl <<-\EOF && + U + FINGERPRINT + + FINGERPRINT + + EOF + git log -1 --format="%G?%n%GK%n%GS%n%GF%n%GP" eighth-signed-alt >actual && + FINGERPRINT=$(ssh-keygen -lf "${GPGSSH_KEY_UNTRUSTED}" | awk "{print \$2;}") && + sed "s|FINGERPRINT|$FINGERPRINT|g" expect.tmpl >expect && + test_cmp expect actual +' + +test_expect_success GPGSSH 'show untrusted signature with undefined trust level' ' + test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" && + cat >expect.tmpl <<-\EOF && + undefined + FINGERPRINT + + FINGERPRINT + + EOF + git log -1 --format="%GT%n%GK%n%GS%n%GF%n%GP" eighth-signed-alt >actual && + FINGERPRINT=$(ssh-keygen -lf "${GPGSSH_KEY_UNTRUSTED}" | awk "{print \$2;}") && + sed "s|FINGERPRINT|$FINGERPRINT|g" expect.tmpl >expect && + test_cmp expect actual +' + +test_expect_success GPGSSH 'show untrusted signature with ultimate trust level' ' + test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" && + cat >expect.tmpl <<-\EOF && + fully + FINGERPRINT + principal with number 1 + FINGERPRINT + + EOF + git log -1 --format="%GT%n%GK%n%GS%n%GF%n%GP" sixth-signed >actual && + FINGERPRINT=$(ssh-keygen -lf "${GPGSSH_KEY_PRIMARY}" | awk "{print \$2;}") && + sed "s|FINGERPRINT|$FINGERPRINT|g" expect.tmpl >expect && + test_cmp expect actual +' + +test_expect_success GPGSSH 'show lack of signature with custom format' ' + cat >expect <<-\EOF && + N + + + + + EOF + git log -1 --format="%G?%n%GK%n%GS%n%GF%n%GP" seventh-unsigned >actual && + test_cmp expect actual +' + +test_expect_success GPGSSH 'log.showsignature behaves like --show-signature' ' + test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" && + test_config log.showsignature true && + git show initial >actual && + grep "${GPGSSH_GOOD_SIGNATURE_TRUSTED}" actual +' + +test_expect_success GPGSSH 'check config gpg.format values' ' + test_config gpg.format ssh && + test_config user.signingkey "${GPGSSH_KEY_PRIMARY}" && + test_config gpg.format ssh && + git commit -S --amend -m "success" && + test_config gpg.format OpEnPgP && + test_must_fail git commit -S --amend -m "fail" +' + +test_expect_failure GPGSSH 'detect fudged commit with double signature (TODO)' ' + sed -e "/gpgsig/,/END PGP/d" forged1 >double-base && + sed -n -e "/gpgsig/,/END PGP/p" forged1 | \ + sed -e "s/^$(test_oid header)//;s/^ //" | gpg --dearmor >double-sig1.sig && + gpg -o double-sig2.sig -u 29472784 --detach-sign double-base && + cat double-sig1.sig double-sig2.sig | gpg --enarmor >double-combined.asc && + sed -e "s/^\(-.*\)ARMORED FILE/\1SIGNATURE/;1s/^/$(test_oid header) /;2,\$s/^/ /" \ + double-combined.asc > double-gpgsig && + sed -e "/committer/r double-gpgsig" double-base >double-commit && + git hash-object -w -t commit double-commit >double-commit.commit && + test_must_fail git verify-commit $(cat double-commit.commit) && + git show --pretty=short --show-signature $(cat double-commit.commit) >double-actual && + grep "BAD signature from" double-actual && + grep "Good signature from" double-actual +' + +test_expect_failure GPGSSH 'show double signature with custom format (TODO)' ' + cat >expect <<-\EOF && + E + + + + + EOF + git log -1 --format="%G?%n%GK%n%GS%n%GF%n%GP" $(cat double-commit.commit) >actual && + test_cmp expect actual +' + + +test_expect_failure GPGSSH 'verify-commit verifies multiply signed commits (TODO)' ' + git init multiply-signed && + cd multiply-signed && + test_commit first && + echo 1 >second && + git add second && + tree=$(git write-tree) && + parent=$(git rev-parse HEAD^{commit}) && + git commit --gpg-sign -m second && + git cat-file commit HEAD && + # Avoid trailing whitespace. + sed -e "s/^Q//" -e "s/^Z/ /" >commit <<-EOF && + Qtree $tree + Qparent $parent + Qauthor A U Thor <[email protected]> 1112912653 -0700 + Qcommitter C O Mitter <[email protected]> 1112912653 -0700 + Qgpgsig -----BEGIN PGP SIGNATURE----- + QZ + Q iHQEABECADQWIQRz11h0S+chaY7FTocTtvUezd5DDQUCX/uBDRYcY29tbWl0dGVy + Q QGV4YW1wbGUuY29tAAoJEBO29R7N3kMNd+8AoK1I8mhLHviPH+q2I5fIVgPsEtYC + Q AKCTqBh+VabJceXcGIZuF0Ry+udbBQ== + Q =tQ0N + Q -----END PGP SIGNATURE----- + Qgpgsig-sha256 -----BEGIN PGP SIGNATURE----- + QZ + Q iHQEABECADQWIQRz11h0S+chaY7FTocTtvUezd5DDQUCX/uBIBYcY29tbWl0dGVy + Q QGV4YW1wbGUuY29tAAoJEBO29R7N3kMN/NEAn0XO9RYSBj2dFyozi0JKSbssYMtO + Q AJwKCQ1BQOtuwz//IjU8TiS+6S4iUw== + Q =pIwP + Q -----END PGP SIGNATURE----- + Q + Qsecond + EOF + head=$(git hash-object -t commit -w commit) && + git reset --hard $head && + git verify-commit $head 2>actual && + grep "Good signature from" actual && + ! grep "BAD signature from" actual +' + +test_done
Signature · 1 line
gitgitgadget
PATCH v7 8/9 ssh signing: tests for logs, tags & push certs
In reply to Fabian Stelzer via GitGitGadgetFrom: Fabian Stelzer <[email protected]>
--- t/t4202-log.sh | 23 +++++ t/t5534-push-signed.sh | 101 +++++++++++++++++++ t/t7031-verify-tag-signed-ssh.sh | 161 +++++++++++++++++++++++++++++++ 3 files changed, 285 insertions(+) create mode 100755 t/t7031-verify-tag-signed-ssh.sh
t/t4202-log.sh +23 -0
@@ -1616,6 +1616,16 @@ test_expect_success GPGSM 'setup signed branch x509' ' git commit -S -m signed_commit ' +test_expect_success GPGSSH 'setup sshkey signed branch' ' + test_config gpg.format ssh && + test_config user.signingkey "${GPGSSH_KEY_PRIMARY}" && + test_when_finished "git reset --hard && git checkout main" && + git checkout -b signed-ssh main && + echo foo >foo && + git add foo && + git commit -S -m signed_commit +' + test_expect_success GPGSM 'log x509 fingerprint' ' echo "F8BF62E0693D0694816377099909C779FA23FD65 | " >expect && git log -n1 --format="%GF | %GP" signed-x509 >actual &&
@@ -1628,6 +1638,13 @@ test_expect_success GPGSM 'log OpenPGP fingerprint' ' test_cmp expect actual ' +test_expect_success GPGSSH 'log ssh key fingerprint' ' + test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" && + ssh-keygen -lf "${GPGSSH_KEY_PRIMARY}" | awk "{print \$2\" | \"}" >expect && + git log -n1 --format="%GF | %GP" signed-ssh >actual && + test_cmp expect actual +' + test_expect_success GPG 'log --graph --show-signature' ' git log --graph --show-signature -n1 signed >actual && grep "^| gpg: Signature made" actual &&
@@ -1640,6 +1657,12 @@ test_expect_success GPGSM 'log --graph --show-signature x509' ' grep "^| gpgsm: Good signature" actual ' +test_expect_success GPGSSH 'log --graph --show-signature ssh' ' + test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" && + git log --graph --show-signature -n1 signed-ssh >actual && + grep "${GOOD_SIGNATURE_TRUSTED}" actual +' + test_expect_success GPG 'log --graph --show-signature for merged tag' ' test_when_finished "git reset --hard && git checkout main" && git checkout -b plain main &&
t/t5534-push-signed.sh +101 -0
113 lines not on this page · raw message
t/t7031-verify-tag-signed-ssh.sh new file +161 -0
161 lines not on this page · raw message
Signature · 1 line
gitgitgadget
PATCH v7 9/9 ssh signing: test that gpg fails for unkown keys
In reply to Fabian Stelzer via GitGitGadgetFrom: Fabian Stelzer <[email protected]>
Test that verify-commit/tag will fail when a gpg key is completely unknown. To do this we have to generate a key, use it for a signature and delete it from our keyring aferwards completely.
--- t/t7510-signed-commit.sh | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-)
t/t7510-signed-commit.sh +28 -1
@@ -71,7 +71,25 @@ test_expect_success GPG 'create signed commits' ' git tag eleventh-signed $(cat oid) && echo 12 | git commit-tree --gpg-sign=B7227189 HEAD^{tree} >oid && test_line_count = 1 oid && - git tag twelfth-signed-alt $(cat oid) + git tag twelfth-signed-alt $(cat oid) && + + cat >keydetails <<-\EOF && + Key-Type: RSA + Key-Length: 2048 + Subkey-Type: RSA + Subkey-Length: 2048 + Name-Real: Unknown User + Name-Email: [email protected] + Expire-Date: 0 + %no-ask-passphrase + %no-protection + EOF + gpg --batch --gen-key keydetails && + echo 13 >file && git commit -a -S"[email protected]" -m thirteenth && + git tag thirteenth-signed && + DELETE_FINGERPRINT=$(gpg -K --with-colons --fingerprint --batch [email protected] | grep "^fpr" | head -n 1 | awk -F ":" "{print \$10;}") && + gpg --batch --yes --delete-secret-keys $DELETE_FINGERPRINT && + gpg --batch --yes --delete-keys [email protected] ' test_expect_success GPG 'verify and show signatures' '
@@ -110,6 +128,13 @@ test_expect_success GPG 'verify and show signatures' ' ) ' +test_expect_success GPG 'verify-commit exits failure on unknown signature' ' + test_must_fail git verify-commit thirteenth-signed 2>actual && + ! grep "Good signature from" actual && + ! grep "BAD signature from" actual && + grep -q -F -e "No public key" -e "public key not found" actual +' + test_expect_success GPG 'verify-commit exits success on untrusted signature' ' git verify-commit eighth-signed-alt 2>actual && grep "Good signature from" actual &&
@@ -338,6 +363,8 @@ test_expect_success GPG 'show double signature with custom format' ' ' +# NEEDSWORK: This test relies on the test_tick commit/author dates from the first +# 'create signed commits' test even though it creates its own test_expect_success GPG 'verify-commit verifies multiply signed commits' ' git init multiply-signed && cd multiply-signed &&
Signature · 1 line
gitgitgadget
Re: [PATCH v7 0/9] ssh signing: Add commit & tag signing/verification via SSH keys using ssh-keygen
In reply to Fabian Stelzer via GitGitGadgetQuoted 8 lines · Fabian Stelzer via GitGitGadget
openssh 8.7 will add valid-after, valid-before options to the allowed keys keyring. This allows us to pass the commit timestamp to the verification call and make key rollover possible and still be able to verify older commits. Set valid-after to the current date when adding your key to the keyring and set valid-before to make it fail if used after a certain date. Software like gitolite/github or corporate automation can do this automatically when ssh push keys are addded / removed I will add this feature in a follow up patch afterwards.
Has this follow-on work happened already?
The previous rounds saw enough reviews and responses, but this round didn't. Usually no response means no interest from the community, but let's see if somebody other than the author actually tried the feature, and and want to tell us about their experience, either positive or negative?
As the basic step of the topic, possibly to be built upon laster, I am tempted to say that this v7 may want to be cooked in 'next' for wider exposure.
I'll typofix the topmost commit before doing so, though.
Thanks.
1: 4ff5911494 ! 1: b88bcd013b ssh signing: test that gpg fails for unkown keys
@@ Metadata
Author: Fabian Stelzer <[email protected]>
## Commit message ##
- ssh signing: test that gpg fails for unkown keys
+ ssh signing: test that gpg fails for unknown keys
Test that verify-commit/tag will fail when a gpg key is completely unknown. To do this we have to generate a key, use it for a signature
Re: [PATCH v7 0/9] ssh signing: Add commit & tag signing/verification via SSH keys using ssh-keygen
Quoted 5 lines · Junio C Hamano
The previous rounds saw enough reviews and responses, but this round didn't. Usually no response means no interest from the community, but let's see if somebody other than the author actually tried the feature, and and want to tell us about their experience, either positive or negative?
I've been using this feature (including this round) on and off and I've been happy with it. I ran into a small bug in an earlier version which has since been fixed, but other than that I haven't had any issues. The setup and use is all pretty easy.
Admittedly, I haven't been daily-driving this feature, as I didn't want to put SSH-signed commits in repositories in case the format changes in the future.
Re: [PATCH v7 0/9] ssh signing: Add commit & tag signing/verification via SSH keys using ssh-keygen
In reply to Junio C HamanoQuoted 11 lines · Junio C Hamano
Quoted 8 lines · Fabian Stelzer via GitGitGadget
openssh 8.7 will add valid-after, valid-before options to the allowed keys keyring. This allows us to pass the commit timestamp to the verification call and make key rollover possible and still be able to verify older commits. Set valid-after to the current date when adding your key to the keyring and set valid-before to make it fail if used after a certain date. Software like gitolite/github or corporate automation can do this automatically when ssh push keys are addded / removed I will add this feature in a follow up patch afterwards.
Has this follow-on work happened already?
I have this prepared but not ready for submission. I wanted to wait until openssh 8.7 is released (which happened recently) to make sure their api for this newly added feature does not change.
I will be on vacation for the next 2 weeks but can submit it afterwards. I have a few additional features in mind but wanted to wait for the basic functionality to settle before piling stuff on top.
I'd like to add a "Trust on First Use" mode that will add keys to your allowedSIgners File when encountered the first time (this could very similar to how .ssh/known/hosts works).
The idea came from here: https://lwn.net/Articles/803619/
Also signing support for git format-patch/am would be nice (ssh signatures are much smaller then gpg and shouldnt be too bad in emails. Not as minimal as minisign but with easier/more established key handling)
Quoted 6 lines
The previous rounds saw enough reviews and responses, but this round didn't. Usually no response means no interest from the community, but let's see if somebody other than the author actually tried the feature, and and want to tell us about their experience, either positive or negative?
I will roll this out to our corporate env after my vacation but can understand that people are hesitant to push commits with it since older git versions will BUG() on verification of the new signatures. But at least github handles it well ("GitHub supports GPG and S/MIME signatures. We don’t know what type of signature this is."). I have not tested with other Forges yet.
Quoted 6 lines
As the basic step of the topic, possibly to be built upon laster, I am tempted to say that this v7 may want to be cooked in 'next' for wider exposure.
I'll typofix the topmost commit before doing so, though.
Thanks
Re: [PATCH v7 0/9] ssh signing: Add commit & tag signing/verification via SSH keys using ssh-keygen
Quoted 6 lines · Fabian Stelzer
I have this prepared but not ready for submission. I wanted to wait until openssh 8.7 is released (which happened recently) to make sure their api for this newly added feature does not change.
I will be on vacation for the next 2 weeks but can submit it afterwards. I have a few additional features in mind but wanted to wait for the basic functionality to settle before piling stuff on top.
Reasonable.
In the meantime, people seem to be finding issues with OpenSSH 8.7's keygen, so before doing any *new* things, we'd like to see an update to make the stuff already posted and reviewed to work with the newer OpenSSH. Hoping that the fix for the incompatibility with 8.7 is small enough, I am planning to keep the version we already have in our tree (in 'next' but not in 'master'), so that an incremental patch will be able to highlight what the differences are when the bug is fixed.
After the dust settles, of course, trust on first use may be one of the first sensible thing to add, and there may be other enhancements, but let's see a solid base to build upon.
And please continue enjoying your vacation ;-) Looking forwared to hearing from you when you come back.
[Reference]
* https://lore.kernel.org/git/CAPUEspgnRFNRoFuEvP1hpY3iKukk3OnF4zk85wkdkmiVuPuRTw@mail.gmail.com/
Re: [PATCH v7 0/9] ssh signing: Add commit & tag signing/verification via SSH keys using ssh-keygen
Quoted 10 lines · Junio C Hamano
Quoted 6 lines · Fabian Stelzer
I have this prepared but not ready for submission. I wanted to wait until openssh 8.7 is released (which happened recently) to make sure their api for this newly added feature does not change.
I will be on vacation for the next 2 weeks but can submit it afterwards. I have a few additional features in mind but wanted to wait for the basic functionality to settle before piling stuff on top.Reasonable.
Quoted 8 lines
In the meantime, people seem to be finding issues with OpenSSH 8.7's keygen, so before doing any *new* things, we'd like to see an update to make the stuff already posted and reviewed to work with the newer OpenSSH. Hoping that the fix for the incompatibility with 8.7 is small enough, I am planning to keep the version we already have in our tree (in 'next' but not in 'master'), so that an incremental patch will be able to highlight what the differences are when the bug is fixed.
It it not so much an incompatibility but a hard bug in ssh-keygen of my own making :/
There is nothing we can do on the git side to fix this since the find-principal call will always segfault no matter what.
I added an optional parameter some time ago for printing the public key on verify to make "trust on first use" easier when we get to it. Unfortunately this bug made it into 8.7 but is already fixed in master. Thanks to Carlo for spotting it and sending a patch.
I guess i owe openssh writing a test for it since the command seems to not have any at all.
I'm not sure how git wants to handle this since i don't know when a fixed openssh release will be available and we certainly shouldn't include the signing feature in a release until they do.
I can't really find a way of detecting the broken version since there's no version or anything else i could find in the ssh-keygen tool.
I will continue writing some tests for the verify-time/key validity feature. The tests will need some version/feature detection from ssh-keygen as well so maybe i will still stumble on something that allows us to detect and warn on this.
Re: [PATCH v7 0/9] ssh signing: Add commit & tag signing/verification via SSH keys using ssh-keygen
Quoted 4 lines · Fabian Stelzer
It it not so much an incompatibility but a hard bug in ssh-keygen of my own making :/
There is nothing we can do on the git side to fix this since the find-principal call will always segfault no matter what.
So... we cannot do anythying utnil a corrected OpenSSH is made available, but once we can link with a corrected one, do we need to do anything further on the patches in your topic?
I am guessing that the ideal endgame would be that we can merge what we have down to 'master' and ship it in a release with a note that says "OpenSSH 8.7 is broken---do not use the ssh signing feature if you cannot update to OpenSSH X.Y (or stay at 8.6)", and that is why I haven't kicked the topic out of 'next' and kept it there.
Quoted 4 lines
I will continue writing some tests for the verify-time/key validity feature. The tests will need some version/feature detection from ssh-keygen as well so maybe i will still stumble on something that allows us to detect and warn on this.
Thanks.
Re: [PATCH v7 0/9] ssh signing: Add commit & tag signing/verification via SSH keys using ssh-keygen
Quoted 9 lines · Junio C Hamano
Quoted 4 lines · Fabian Stelzer
It it not so much an incompatibility but a hard bug in ssh-keygen of my own making :/
There is nothing we can do on the git side to fix this since the find-principal call will always segfault no matter what.So... we cannot do anythying utnil a corrected OpenSSH is made available, but once we can link with a corrected one, do we need to do anything further on the patches in your topic?
OpenSSH will probably release a new version in October.
I will send a new diff of my patch in a bit after the CI runs are through fixing a bug with some buffers that could sometimes lead to memory corruption (i war releasing a buffer while still iterating over its contents), a small test fix and a minor improvement using git_config_pathname instead of string.
Besides that i think its good.
For the key lifetime changes that require openssh 8.7 i will send a new patchset afterwards.
Quoted 6 lines
I am guessing that the ideal endgame would be that we can merge what we have down to 'master' and ship it in a release with a note that says "OpenSSH 8.7 is broken---do not use the ssh signing feature if you cannot update to OpenSSH X.Y (or stay at 8.6)", and that is why I haven't kicked the topic out of 'next' and kept it there.
Sounds good to me.
Thanks
Re: [PATCH v7 0/9] ssh signing: Add commit & tag signing/verification via SSH keys using ssh-keygen
Quoted 9 lines · Fabian Stelzer
Quoted 6 lines · Junio C Hamano
Quoted 4 lines · Fabian Stelzer
It it not so much an incompatibility but a hard bug in ssh-keygen of my own making :/
There is nothing we can do on the git side to fix this since the find-principal call will always segfault no matter what.
Quoted 5 lines
Quoted 3 lines
So... we cannot do anythying utnil a corrected OpenSSH is made available, but once we can link with a corrected one, do we need to do anything further on the patches in your topic?
OpenSSH will probably release a new version in October.
FWIW the crashing bug is only in master (I found it while testing OpenBSD 7 beta).
AFAIK, once that is fixed the suite runs cleanly, but still does not when run against
an OpenSSH 4.7 release (hadn't check why, but AFAIK wasn't the crash from what I recall)
Quoted 5 lines
I will send a new diff of my patch in a bit after the CI runs are through fixing a bug with some buffers that could sometimes lead to memory corruption (i war releasing a buffer while still iterating over its contents), a small test fix and a minor improvement using
git_config_pathnameinstead of string.
notice that since your patches are already in next (and I know it is late since I saw
your update), you need to send only incremental patches now, instead.
Carlo
v8 cover ssh signing: Add commit & tag signing/verification via SSH keys using ssh-keygen
In reply to Fabian Stelzer via GitGitGadgetopenssh 8.7 will add valid-after, valid-before options to the allowed keys keyring. This allows us to pass the commit timestamp to the verification call and make key rollover possible and still be able to verify older commits. Set valid-after to the current date when adding your key to the keyring and set valid-before to make it fail if used after a certain date. Software like gitolite/github or corporate automation can do this automatically when ssh push keys are addded / removed I will add this feature in a follow up patch afterwards since the released 8.7 version has a broken ssh-keygen implementation which will break ssh signing completely.
v7:
* change unknown signing key behavior to fail verify-commit/tag just like gpg does
* add test for unknown signing keys for ssh & gpg
* made default signing key retrieval configurable (gpg.ssh.defaultKeyCommand). We could default this to "ssh-add -L" but would risk some users signing with a wrong key
* die() instead of error in case of incompatible signatures to match current BUG() behaviour more
* various review fixes (early return for config parse, missing free, comments)
* got rid of strcmp("ssh") branches and used format configurable callbacks everywhere
* moved documentation changes into the commits adding the specific functionality
v8:
* fixes a bug around find-principals buffer i was releasing while still iterating over it. Uses separate strbufs now.
* rename a wrong variable in the tests
* use git_config_pathname instead of string where applicable
Fabian Stelzer (9):
ssh signing: preliminary refactoring and clean-up ssh signing: add test prereqs
ssh signing: add ssh key format and signing code ssh signing: retrieve a default key from ssh-agent ssh signing: provide a textual signing_key_id ssh signing: verify signatures using ssh-keygen ssh signing: duplicate t7510 tests for commits ssh signing: tests for logs, tags & push certs ssh signing: test that gpg fails for unknown keys
Documentation/config/gpg.txt | 45 ++- Documentation/config/user.txt | 7 + builtin/receive-pack.c | 4 + fmt-merge-msg.c | 6 +- gpg-interface.c | 577 ++++++++++++++++++++++++++++--- gpg-interface.h | 8 +- log-tree.c | 8 +- pretty.c | 4 +- send-pack.c | 8 +- t/lib-gpg.sh | 28 ++ t/t4202-log.sh | 23 ++ t/t5534-push-signed.sh | 101 ++++++ t/t7031-verify-tag-signed-ssh.sh | 161 +++++++++ t/t7510-signed-commit.sh | 29 +- t/t7528-signed-commit-ssh.sh | 398 +++++++++++++++++++++ 15 files changed, 1341 insertions(+), 66 deletions(-) create mode 100755 t/t7031-verify-tag-signed-ssh.sh create mode 100755 t/t7528-signed-commit-ssh.sh
base-commit: 8463beaeb69fe0b7f651065813def4aa6827cd5d
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-1041%2FFStelzer%2Fsshsign-v8
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-1041/FStelzer/sshsign-v8
Pull-Request: https://github.com/git/git/pull/1041
Range-diff vs v7:
1: 91fd0159e1f = 1: b0bee197a05 ssh signing: preliminary refactoring and clean-up
2: fe98052a3ea = 2: d08327ecb25 ssh signing: add test prereqs
3: 80d2d55d22e = 3: c1e9bba8da0 ssh signing: add ssh key format and signing code
4: 83ece42e1de = 4: 8c430fc7a1b ssh signing: retrieve a default key from ssh-agent
5: 76bc9eb4079 = 5: 0864ed04670 ssh signing: provide a textual signing_key_id
6: dc092c79796 ! 6: cfd66180249 ssh signing: verify signatures using ssh-keygen
@@ gpg-interface.c: static int verify_gpg_signed_buffer(struct signature_check *sig
+ const char *line;
+ size_t trust_size;
+ char *principal;
++ struct strbuf ssh_principals_out = STRBUF_INIT;
++ struct strbuf ssh_principals_err = STRBUF_INIT;
+ struct strbuf ssh_keygen_out = STRBUF_INIT;
+ struct strbuf ssh_keygen_err = STRBUF_INIT;
+
@@ gpg-interface.c: static int verify_gpg_signed_buffer(struct signature_check *sig
+ "-f", ssh_allowed_signers,
+ "-s", buffer_file->filename.buf,
+ NULL);
-+ ret = pipe_command(&ssh_keygen, NULL, 0, &ssh_keygen_out, 0,
-+ &ssh_keygen_err, 0);
-+ if (ret && strstr(ssh_keygen_err.buf, "usage:")) {
++ ret = pipe_command(&ssh_keygen, NULL, 0, &ssh_principals_out, 0,
++ &ssh_principals_err, 0);
++ if (ret && strstr(ssh_principals_err.buf, "usage:")) {
+ error(_("ssh-keygen -Y find-principals/verify is needed for ssh signature verification (available in openssh version 8.2p1+)"));
+ goto out;
+ }
-+ if (ret || !ssh_keygen_out.len) {
++ if (ret || !ssh_principals_out.len) {
+ /*
+ * We did not find a matching principal in the allowedSigners
+ * Check without validation
@@ gpg-interface.c: static int verify_gpg_signed_buffer(struct signature_check *sig
+ ret = -1;
+ } else {
+ /* Check every principal we found (one per line) */
-+ for (line = ssh_keygen_out.buf; *line;
++ for (line = ssh_principals_out.buf; *line;
+ line = strchrnul(line + 1, '\n')) {
+ while (*line == '\n')
+ line++;
@@ gpg-interface.c: static int verify_gpg_signed_buffer(struct signature_check *sig
+ sigc->payload = xmemdupz(payload, payload_size);
+ strbuf_stripspace(&ssh_keygen_out, 0);
+ strbuf_stripspace(&ssh_keygen_err, 0);
++ /* Add stderr outputs to show the user actual ssh-keygen errors */
++ strbuf_add(&ssh_keygen_out, ssh_principals_err.buf, ssh_principals_err.len);
+ strbuf_add(&ssh_keygen_out, ssh_keygen_err.buf, ssh_keygen_err.len);
+ sigc->output = strbuf_detach(&ssh_keygen_out, NULL);
+ sigc->gpg_status = xstrdup(sigc->output);
@@ gpg-interface.c: static int verify_gpg_signed_buffer(struct signature_check *sig
+out:
+ if (buffer_file)
+ delete_tempfile(&buffer_file);
++ strbuf_release(&ssh_principals_out);
++ strbuf_release(&ssh_principals_err);
+ strbuf_release(&ssh_keygen_out);
+ strbuf_release(&ssh_keygen_err);
+
@@ gpg-interface.c: int git_gpg_config(const char *var, const char *value, void *cb
+ if (!strcmp(var, "gpg.ssh.allowedsignersfile")) {
+ if (!value)
+ return config_error_nonbool(var);
-+ return git_config_string(&ssh_allowed_signers, var, value);
++ return git_config_pathname(&ssh_allowed_signers, var, value);
+ }
+
+ if (!strcmp(var, "gpg.ssh.revocationfile")) {
+ if (!value)
+ return config_error_nonbool(var);
-+ return git_config_string(&ssh_revocation_file, var, value);
++ return git_config_pathname(&ssh_revocation_file, var, value);
+ }
+
if (!strcmp(var, "gpg.program") || !strcmp(var, "gpg.openpgp.program"))
7: c17441566d9 ! 7: c8e21dc97f1 ssh signing: duplicate t7510 tests for commits
@@ t/t7528-signed-commit-ssh.sh (new)
+ git show --pretty=short --show-signature $commit >actual &&
+ grep "${GPGSSH_GOOD_SIGNATURE_UNTRUSTED}" actual &&
+ ! grep "${GPGSSH_BAD_SIGNATURE}" actual &&
-+ grep "${KEY_NOT_TRUSTED}" actual &&
++ grep "${GPGSSH_KEY_NOT_TRUSTED}" actual &&
+ echo $commit OK || exit 1
+ done
+ )
@@ t/t7528-signed-commit-ssh.sh (new)
+ test_must_fail git verify-commit eighth-signed-alt 2>actual &&
+ grep "${GPGSSH_GOOD_SIGNATURE_UNTRUSTED}" actual &&
+ ! grep "${GPGSSH_BAD_SIGNATURE}" actual &&
-+ grep "${KEY_NOT_TRUSTED}" actual
++ grep "${GPGSSH_KEY_NOT_TRUSTED}" actual
+'
+
+test_expect_success GPGSSH 'verify-commit exits success with matching minTrustLevel' '
8: 0763517d62d = 8: b66e3e0284c ssh signing: tests for logs, tags & push certs
9: a5add98197a ! 9: 07afb94ed83 ssh signing: test that gpg fails for unkown keys @@ Metadata
Author: Fabian Stelzer <[email protected]>
## Commit message ##
- ssh signing: test that gpg fails for unkown keys
+ ssh signing: test that gpg fails for unknown keys
Test that verify-commit/tag will fail when a gpg key is completely unknown. To do this we have to generate a key, use it for a signature
Signature · 1 line
gitgitgadget
PATCH v8 1/9 ssh signing: preliminary refactoring and clean-up
From: Fabian Stelzer <[email protected]>
Openssh v8.2p1 added some new options to ssh-keygen for signature creation and verification. These allow us to use ssh keys for git signatures easily.
In our corporate environment we use PIV x509 Certs on Yubikeys for email signing/encryption and ssh keys which I think is quite common
(at least for the email part). This way we can establish the correct trust for the SSH Keys without setting up a separate GPG Infrastructure (which is still quite painful for users) or implementing x509 signing support for git (which lacks good forwarding mechanisms).
Using ssh agent forwarding makes this feature easily usable in todays development environments where code is often checked out in remote VMs / containers. In such a setup the keyring & revocationKeyring can be centrally generated from the x509 CA information and distributed to the users.
To be able to implement new signing formats this commit:
- makes the sigc structure more generic by renaming "gpg_output" to "output"
- introduces function pointers in the gpg_format structure to call format specific signing and verification functions
- moves format detection from verify_signed_buffer into the check_signature api function and calls the format specific verify
- renames and wraps sign_buffer to handle format specific signing logic as well
--- fmt-merge-msg.c | 6 +-- gpg-interface.c | 104 +++++++++++++++++++++++++++++------------------- gpg-interface.h | 2 +- log-tree.c | 8 ++-- pretty.c | 4 +- 5 files changed, 74 insertions(+), 50 deletions(-)
fmt-merge-msg.c +3 -3
@@ -528,11 +528,11 @@ static void fmt_merge_msg_sigs(struct strbuf *out) buf = payload.buf; len = payload.len;if (check_signature (payload.buf, payload.len, sig.buf, - sig.len, &sigc) && - !sigc.gpg_output) + sig.len, &sigc) && + !sigc.output)strbuf_addstr (&sig,"gpg verification failed.\n" );else -strbuf_addstr (&sig, sigc.gpg_output); +strbuf_addstr (&sig, sigc.output); }signature_check_clear (&sigc);
gpg-interface.c +64 -40
176 lines not on this page · raw message
gpg-interface.h +1 -1
@@ -17,7 +17,7 @@ enum signature_trust_level {struct signature_check {char *payload; -char *gpg_output; +char *output;char *gpg_status;/*
log-tree.c +4 -4
@@ -515,10 +515,10 @@ static void show_signature(struct rev_info *opt, struct commit *commit) status =check_signature (payload.buf, payload.len, signature.buf, signature.len, &sigc); -if (status && !sigc.gpg_output) +if (status && !sigc.output)show_sig_lines (opt, status,"No signature\n" );else -show_sig_lines (opt, status, sigc.gpg_output); +show_sig_lines (opt, status, sigc.output);signature_check_clear (&sigc); out:
@@ -585,8 +585,8 @@ static int show_one_mergetag(struct commit *commit,/* could have a good signature */ status =check_signature (payload.buf, payload.len, signature.buf, signature.len, &sigc); -if (sigc.gpg_output) -strbuf_addstr (&verify_message, sigc.gpg_output); +if (sigc.output) +strbuf_addstr (&verify_message, sigc.output);else strbuf_addstr (&verify_message,"No signature\n" );signature_check_clear (&sigc);
pretty.c +2 -2
@@ -1432,8 +1432,8 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */check_commit_signature (c->commit, &(c->signature_check));switch (placeholder[1 ]) {case 'G' : -if (c->signature_check.gpg_output) -strbuf_addstr (sb, c->signature_check.gpg_output); +if (c->signature_check.output) +strbuf_addstr (sb, c->signature_check.output);break ;case '?' :switch (c->signature_check.result) {
Signature · 1 line
gitgitgadget
PATCH v8 2/9 ssh signing: add test prereqs
In reply to Fabian Stelzer via GitGitGadgetFrom: Fabian Stelzer <[email protected]>
Generate some ssh keys and a allowedSignersFile for testing
--- t/lib-gpg.sh | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+)
t/lib-gpg.sh +28 -0
@@ -87,6 +87,34 @@ test_lazy_prereq RFC1991 ' echo | gpg --homedir "${GNUPGHOME}" -b --rfc1991 >/dev/null ' +GPGSSH_KEY_PRIMARY="${GNUPGHOME}/ed25519_ssh_signing_key" +GPGSSH_KEY_SECONDARY="${GNUPGHOME}/rsa_2048_ssh_signing_key" +GPGSSH_KEY_UNTRUSTED="${GNUPGHOME}/untrusted_ssh_signing_key" +GPGSSH_KEY_WITH_PASSPHRASE="${GNUPGHOME}/protected_ssh_signing_key" +GPGSSH_KEY_PASSPHRASE="super_secret" +GPGSSH_ALLOWED_SIGNERS="${GNUPGHOME}/ssh.all_valid.allowedSignersFile" + +GPGSSH_GOOD_SIGNATURE_TRUSTED='Good "git" signature for' +GPGSSH_GOOD_SIGNATURE_UNTRUSTED='Good "git" signature with' +GPGSSH_KEY_NOT_TRUSTED="No principal matched" +GPGSSH_BAD_SIGNATURE="Signature verification failed" + +test_lazy_prereq GPGSSH ' + ssh_version=$(ssh-keygen -Y find-principals -n "git" 2>&1) + test $? != 127 || exit 1 + echo $ssh_version | grep -q "find-principals:missing signature file" + test $? = 0 || exit 1; + mkdir -p "${GNUPGHOME}" && + chmod 0700 "${GNUPGHOME}" && + ssh-keygen -t ed25519 -N "" -C "git ed25519 key" -f "${GPGSSH_KEY_PRIMARY}" >/dev/null && + echo "\"principal with number 1\" $(cat "${GPGSSH_KEY_PRIMARY}.pub")" >> "${GPGSSH_ALLOWED_SIGNERS}" && + ssh-keygen -t rsa -b 2048 -N "" -C "git rsa2048 key" -f "${GPGSSH_KEY_SECONDARY}" >/dev/null && + echo "\"principal with number 2\" $(cat "${GPGSSH_KEY_SECONDARY}.pub")" >> "${GPGSSH_ALLOWED_SIGNERS}" && + ssh-keygen -t ed25519 -N "${GPGSSH_KEY_PASSPHRASE}" -C "git ed25519 encrypted key" -f "${GPGSSH_KEY_WITH_PASSPHRASE}" >/dev/null && + echo "\"principal with number 3\" $(cat "${GPGSSH_KEY_WITH_PASSPHRASE}.pub")" >> "${GPGSSH_ALLOWED_SIGNERS}" && + ssh-keygen -t ed25519 -N "" -f "${GPGSSH_KEY_UNTRUSTED}" >/dev/null +' + sanitize_pgp() { perl -ne ' /^-----END PGP/ and $in_pgp = 0;
Signature · 1 line
gitgitgadget
PATCH v8 3/9 ssh signing: add ssh key format and signing code
In reply to Fabian Stelzer via GitGitGadgetFrom: Fabian Stelzer <[email protected]>
Implements the actual sign_buffer_ssh operation and move some shared cleanup code into a strbuf function
Set gpg.format = ssh and user.signingkey to either a ssh public key string (like from an authorized_keys file), or a ssh key file. If the key file or the config value itself contains only a public key then the private key needs to be available via ssh-agent.
gpg.ssh.program can be set to an alternative location of ssh-keygen. A somewhat recent openssh version (8.2p1+) of ssh-keygen is needed for this feature. Since only ssh-keygen is needed it can this way be installed seperately without upgrading your system openssh packages.
--- Documentation/config/gpg.txt | 4 +- Documentation/config/user.txt | 5 ++ gpg-interface.c | 138 ++++++++++++++++++++++++++++++++-- 3 files changed, 137 insertions(+), 10 deletions(-)
Documentation/config/gpg.txt +2 -2
@@ -11,13 +11,13 @@ gpg.program:: gpg.format:: Specifies which key format to use when signing with `--gpg-sign`. - Default is "openpgp" and another possible value is "x509". + Default is "openpgp". Other possible values are "x509", "ssh". gpg.<format>.program:: Use this to customize the program used for the signing format you chose. (see `gpg.program` and `gpg.format`) `gpg.program` can still be used as a legacy synonym for `gpg.openpgp.program`. The default - value for `gpg.x509.program` is "gpgsm". + value for `gpg.x509.program` is "gpgsm" and `gpg.ssh.program` is "ssh-keygen". gpg.minTrustLevel:: Specifies a minimum trust level for signature verification. If
Documentation/config/user.txt +5 -0
@@ -36,3 +36,8 @@ user.signingKey:: commit, you can override the default selection with this variable. This option is passed unchanged to gpg's --local-user parameter, so you may specify a key using any method that gpg supports. + If gpg.format is set to "ssh" this can contain the literal ssh public + key (e.g.: "ssh-rsa XXXXXX identifier") or a file which contains it and + corresponds to the private key used for signing. The private key + needs to be available via ssh-agent. Alternatively it can be set to + a file containing a private key directly.
gpg-interface.c +130 -8
179 lines not on this page · raw message
Signature · 1 line
gitgitgadget
PATCH v8 4/9 ssh signing: retrieve a default key from ssh-agent
In reply to Fabian Stelzer via GitGitGadgetFrom: Fabian Stelzer <[email protected]>
If user.signingkey is not set and a ssh signature is requested we call gpg.ssh.defaultKeyCommand (typically "ssh-add -L") and use the first key we get
--- Documentation/config/gpg.txt | 6 +++ Documentation/config/user.txt | 4 +- gpg-interface.c | 70 ++++++++++++++++++++++++++++++++++- 3 files changed, 77 insertions(+), 3 deletions(-)
Documentation/config/gpg.txt +6 -0
@@ -33,3 +33,9 @@ gpg.minTrustLevel:: * `marginal` * `fully` * `ultimate` + +gpg.ssh.defaultKeyCommand: + This command that will be run when user.signingkey is not set and a ssh + signature is requested. On successful exit a valid ssh public key is + expected in the first line of its output. To automatically use the first + available key from your ssh-agent set this to "ssh-add -L".
Documentation/config/user.txt +3 -1
@@ -40,4 +40,6 @@ user.signingKey:: key (e.g.: "ssh-rsa XXXXXX identifier") or a file which contains it and corresponds to the private key used for signing. The private key needs to be available via ssh-agent. Alternatively it can be set to - a file containing a private key directly. + a file containing a private key directly. If not set git will call + gpg.ssh.defaultKeyCommand (e.g.: "ssh-add -L") and try to use the first + key available.
gpg-interface.c +68 -2
124 lines not on this page · raw message
Signature · 1 line
gitgitgadget
PATCH v8 5/9 ssh signing: provide a textual signing_key_id
In reply to Fabian Stelzer via GitGitGadgetFrom: Fabian Stelzer <[email protected]>
For ssh the user.signingkey can be a filename/path or even a literal ssh pubkey. In push certs and textual output we prefer the ssh fingerprint instead.
--- gpg-interface.c | 56 +++++++++++++++++++++++++++++++++++++++++++++++++ gpg-interface.h | 6 ++++++ send-pack.c | 8 +++---- 3 files changed, 66 insertions(+), 4 deletions(-)
gpg-interface.c +56 -0
@@ -24,6 +24,7 @@ struct gpg_format {int (*sign_buffer)(struct strbuf *buffer,struct strbuf *signature,const char *signing_key);const char *(*get_default_key)(void ); +const char *(*get_key_id)(void ); };static const char *openpgp_verify_args[] = {
@@ -61,6 +62,8 @@ static int sign_buffer_ssh(struct strbuf *buffer, struct strbuf *signature,static const char *get_default_ssh_signing_key (void ); +static const char *get_ssh_key_id (void ); +static struct gpg_format gpg_format[] = { { .name ="openpgp" ,
@@ -70,6 +73,7 @@ static struct gpg_format gpg_format[] = { .verify_signed_buffer = verify_gpg_signed_buffer, .sign_buffer = sign_buffer_gpg, .get_default_key = NULL, + .get_key_id = NULL, }, { .name = "x509",
@@ -79,6 +83,7 @@ static struct gpg_format gpg_format[] = { .verify_signed_buffer = verify_gpg_signed_buffer, .sign_buffer = sign_buffer_gpg, .get_default_key = NULL, + .get_key_id = NULL, }, { .name = "ssh",
@@ -88,6 +93,7 @@ static struct gpg_format gpg_format[] = { .verify_signed_buffer = NULL, /* TODO */ .sign_buffer = sign_buffer_ssh, .get_default_key = get_default_ssh_signing_key, + .get_key_id = get_ssh_key_id, }, };
@@ -484,6 +490,41 @@ int git_gpg_config(const char *var, const char *value, void *cb)return 0 ; } +static char *get_ssh_key_fingerprint (const char *signing_key) +{ +struct child_process ssh_keygen =CHILD_PROCESS_INIT ; +int ret =-1 ; +struct strbuf fingerprint_stdout =STRBUF_INIT ; +struct strbuf **fingerprint; + +/* +* With SSH Signing this can contain a filename or a public key +* For textual representation we usually want a fingerprint +*/ +if (starts_with (signing_key,"ssh-" )) { +strvec_pushl (&ssh_keygen.args,"ssh-keygen" ,"-lf" ,"-" ,NULL ); + ret =pipe_command (&ssh_keygen, signing_key, +strlen (signing_key), &fingerprint_stdout,0 , +NULL ,0 ); + }else { +strvec_pushl (&ssh_keygen.args,"ssh-keygen" ,"-lf" , + configured_signing_key,NULL ); + ret =pipe_command (&ssh_keygen,NULL ,0 , &fingerprint_stdout,0 , +NULL ,0 ); + } + +if (!!ret) +die_errno (_ ("failed to get the ssh fingerprint for key '%s'" ), + signing_key); + + fingerprint =strbuf_split_max (&fingerprint_stdout,' ' ,3 ); +if (!fingerprint[1 ]) +die_errno (_ ("failed to get the ssh fingerprint for key '%s'" ), + signing_key); + +return strbuf_detach (fingerprint[1 ],NULL ); +} +/* Returns the first public key from an ssh-agent to use for signing */ static const char *get_default_ssh_signing_key (void ) {
@@ -532,6 +573,21 @@ static const char *get_default_ssh_signing_key(void)return default_key; } +static const char *get_ssh_key_id (void ) { +return get_ssh_key_fingerprint (get_signing_key ()); +} + +/* Returns a textual but unique representation of the signing key */ +const char *get_signing_key_id (void ) +{ +if (use_format->get_key_id) { +return use_format->get_key_id (); + } + +/* GPG/GPGSM only store a key id on this variable */ +return get_signing_key (); +} +const char *get_signing_key (void ) {if (configured_signing_key)
gpg-interface.h +6 -0
@@ -64,6 +64,12 @@ int sign_buffer(struct strbuf *buffer, struct strbuf *signature,int git_gpg_config (const char *,const char *,void *);void set_signing_key (const char *);const char *get_signing_key (void ); + +/* +* Returns a textual unique representation of the signing key in use +* Either a GPG KeyID or a SSH Key Fingerprint +*/ +const char *get_signing_key_id (void );int check_signature (const char *payload,size_t plen,const char *signature,size_t slen,struct signature_check *sigc);
send-pack.c +4 -4
@@ -341,13 +341,13 @@ static int generate_push_cert(struct strbuf *req_buf, {const struct ref *ref;struct string_list_item *item; -char *signing_key =xstrdup (get_signing_key ()); +char *signing_key_id =xstrdup (get_signing_key_id ());const char *cp, *np;struct strbuf cert =STRBUF_INIT ;int update_seen =0 ;strbuf_addstr (&cert,"certificate version 0.1\n" ); -strbuf_addf (&cert,"pusher %s " , signing_key); +strbuf_addf (&cert,"pusher %s " , signing_key_id);datestamp (&cert);strbuf_addch (&cert,'\n' );if (args->url && *args->url) {
@@ -374,7 +374,7 @@ static int generate_push_cert(struct strbuf *req_buf,if (!update_seen) goto free_return; -if (sign_buffer (&cert, &cert, signing_key)) +if (sign_buffer (&cert, &cert,get_signing_key ()))die (_ ("failed to sign the push certificate" ));packet_buf_write (req_buf,"push-cert%c%s" ,0 , cap_string);
@@ -386,7 +386,7 @@ static int generate_push_cert(struct strbuf *req_buf,packet_buf_write (req_buf,"push-cert-end\n" ); free_return: -free (signing_key); +free (signing_key_id);strbuf_release (&cert);return update_seen; }
Signature · 1 line
gitgitgadget
PATCH v8 6/9 ssh signing: verify signatures using ssh-keygen
In reply to Fabian Stelzer via GitGitGadgetFrom: Fabian Stelzer <[email protected]>
To verify a ssh signature we first call ssh-keygen -Y find-principal to look up the signing principal by their public key from the allowedSignersFile. If the key is found then we do a verify. Otherwise we only validate the signature but can not verify the signers identity.
Verification uses the gpg.ssh.allowedSignersFile (see ssh-keygen(1) "ALLOWED SIGNERS") which contains valid public keys and a principal (usually user@domain). Depending on the environment this file can be managed by the individual developer or for example generated by the central repository server from known ssh keys with push access. This file is usually stored outside the repository, but if the repository only allows signed commits/pushes, the user might choose to store it in the repository.
To revoke a key put the public key without the principal prefix into gpg.ssh.revocationKeyring or generate a KRL (see ssh-keygen(1) "KEY REVOCATION LISTS"). The same considerations about who to trust for verification as with the allowedSignersFile apply.
Using SSH CA Keys with these files is also possible. Add "cert-authority" as key option between the principal and the key to mark it as a CA and all keys signed by it as valid for this CA. See "CERTIFICATES" in ssh-keygen(1).
--- Documentation/config/gpg.txt | 35 ++++++ builtin/receive-pack.c | 4 + gpg-interface.c | 215 ++++++++++++++++++++++++++++++++++- 3 files changed, 252 insertions(+), 2 deletions(-)
Documentation/config/gpg.txt +35 -0
@@ -39,3 +39,38 @@ gpg.ssh.defaultKeyCommand: signature is requested. On successful exit a valid ssh public key is expected in the first line of its output. To automatically use the first available key from your ssh-agent set this to "ssh-add -L". + +gpg.ssh.allowedSignersFile:: + A file containing ssh public keys which you are willing to trust. + The file consists of one or more lines of principals followed by an ssh + public key. + e.g.: [email protected],[email protected] ssh-rsa AAAAX1... + See ssh-keygen(1) "ALLOWED SIGNERS" for details. + The principal is only used to identify the key and is available when + verifying a signature. ++ +SSH has no concept of trust levels like gpg does. To be able to differentiate +between valid signatures and trusted signatures the trust level of a signature +verification is set to `fully` when the public key is present in the allowedSignersFile. +Therefore to only mark fully trusted keys as verified set gpg.minTrustLevel to `fully`. +Otherwise valid but untrusted signatures will still verify but show no principal +name of the signer. ++ +This file can be set to a location outside of the repository and every developer +maintains their own trust store. A central repository server could generate this +file automatically from ssh keys with push access to verify the code against. +In a corporate setting this file is probably generated at a global location +from automation that already handles developer ssh keys. ++ +A repository that only allows signed commits can store the file +in the repository itself using a path relative to the top-level of the working tree. +This way only committers with an already valid key can add or change keys in the keyring. ++ +Using a SSH CA key with the cert-authority option +(see ssh-keygen(1) "CERTIFICATES") is also valid. + +gpg.ssh.revocationFile:: + Either a SSH KRL or a list of revoked public keys (without the principal prefix). + See ssh-keygen(1) for details. + If a public key is found in this file then it will always be treated + as having trust level "never" and signatures will show as invalid.
builtin/receive-pack.c +4 -0
@@ -131,6 +131,10 @@ static int receive_pack_config(const char *var, const char *value, void *cb) {int status =parse_hide_refs_config (var, value,"receive" ); +if (status) +return status; + + status =git_gpg_config (var, value,NULL );if (status)return status;
gpg-interface.c +213 -2
251 lines not on this page · raw message
Signature · 1 line
gitgitgadget
PATCH v8 7/9 ssh signing: duplicate t7510 tests for commits
In reply to Fabian Stelzer via GitGitGadgetFrom: Fabian Stelzer <[email protected]>
--- t/t7528-signed-commit-ssh.sh | 398 +++++++++++++++++++++++++++++++++++ 1 file changed, 398 insertions(+) create mode 100755 t/t7528-signed-commit-ssh.sh
t/t7528-signed-commit-ssh.sh new file +398 -0
@@ -0,0 +1,398 @@ +#!/bin/sh + +test_description='ssh signed commit tests' +GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main +export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME + +. ./test-lib.sh +GNUPGHOME_NOT_USED=$GNUPGHOME +. "$TEST_DIRECTORY/lib-gpg.sh" + +test_expect_success GPGSSH 'create signed commits' ' + test_oid_cache <<-\EOF && + header sha1:gpgsig + header sha256:gpgsig-sha256 + EOF + + test_when_finished "test_unconfig commit.gpgsign" && + test_config gpg.format ssh && + test_config user.signingkey "${GPGSSH_KEY_PRIMARY}" && + + echo 1 >file && git add file && + test_tick && git commit -S -m initial && + git tag initial && + git branch side && + + echo 2 >file && test_tick && git commit -a -S -m second && + git tag second && + + git checkout side && + echo 3 >elif && git add elif && + test_tick && git commit -m "third on side" && + + git checkout main && + test_tick && git merge -S side && + git tag merge && + + echo 4 >file && test_tick && git commit -a -m "fourth unsigned" && + git tag fourth-unsigned && + + test_tick && git commit --amend -S -m "fourth signed" && + git tag fourth-signed && + + git config commit.gpgsign true && + echo 5 >file && test_tick && git commit -a -m "fifth signed" && + git tag fifth-signed && + + git config commit.gpgsign false && + echo 6 >file && test_tick && git commit -a -m "sixth" && + git tag sixth-unsigned && + + git config commit.gpgsign true && + echo 7 >file && test_tick && git commit -a -m "seventh" --no-gpg-sign && + git tag seventh-unsigned && + + test_tick && git rebase -f HEAD^^ && git tag sixth-signed HEAD^ && + git tag seventh-signed && + + echo 8 >file && test_tick && git commit -a -m eighth -S"${GPGSSH_KEY_UNTRUSTED}" && + git tag eighth-signed-alt && + + # commit.gpgsign is still on but this must not be signed + echo 9 | git commit-tree HEAD^{tree} >oid && + test_line_count = 1 oid && + git tag ninth-unsigned $(cat oid) && + # explicit -S of course must sign. + echo 10 | git commit-tree -S HEAD^{tree} >oid && + test_line_count = 1 oid && + git tag tenth-signed $(cat oid) && + + # --gpg-sign[=<key-id>] must sign. + echo 11 | git commit-tree --gpg-sign HEAD^{tree} >oid && + test_line_count = 1 oid && + git tag eleventh-signed $(cat oid) && + echo 12 | git commit-tree --gpg-sign="${GPGSSH_KEY_UNTRUSTED}" HEAD^{tree} >oid && + test_line_count = 1 oid && + git tag twelfth-signed-alt $(cat oid) +' + +test_expect_success GPGSSH 'verify and show signatures' ' + test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" && + test_config gpg.mintrustlevel UNDEFINED && + ( + for commit in initial second merge fourth-signed \ + fifth-signed sixth-signed seventh-signed tenth-signed \ + eleventh-signed + do + git verify-commit $commit && + git show --pretty=short --show-signature $commit >actual && + grep "${GPGSSH_GOOD_SIGNATURE_TRUSTED}" actual && + ! grep "${GPGSSH_BAD_SIGNATURE}" actual && + echo $commit OK || exit 1 + done + ) && + ( + for commit in merge^2 fourth-unsigned sixth-unsigned \ + seventh-unsigned ninth-unsigned + do + test_must_fail git verify-commit $commit && + git show --pretty=short --show-signature $commit >actual && + ! grep "${GPGSSH_GOOD_SIGNATURE_TRUSTED}" actual && + ! grep "${GPGSSH_BAD_SIGNATURE}" actual && + echo $commit OK || exit 1 + done + ) && + ( + for commit in eighth-signed-alt twelfth-signed-alt + do + git show --pretty=short --show-signature $commit >actual && + grep "${GPGSSH_GOOD_SIGNATURE_UNTRUSTED}" actual && + ! grep "${GPGSSH_BAD_SIGNATURE}" actual && + grep "${GPGSSH_KEY_NOT_TRUSTED}" actual && + echo $commit OK || exit 1 + done + ) +' + +test_expect_success GPGSSH 'verify-commit exits failure on untrusted signature' ' + test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" && + test_must_fail git verify-commit eighth-signed-alt 2>actual && + grep "${GPGSSH_GOOD_SIGNATURE_UNTRUSTED}" actual && + ! grep "${GPGSSH_BAD_SIGNATURE}" actual && + grep "${GPGSSH_KEY_NOT_TRUSTED}" actual +' + +test_expect_success GPGSSH 'verify-commit exits success with matching minTrustLevel' ' + test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" && + test_config gpg.minTrustLevel fully && + git verify-commit sixth-signed +' + +test_expect_success GPGSSH 'verify-commit exits success with low minTrustLevel' ' + test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" && + test_config gpg.minTrustLevel marginal && + git verify-commit sixth-signed +' + +test_expect_success GPGSSH 'verify-commit exits failure with high minTrustLevel' ' + test_config gpg.minTrustLevel ultimate && + test_must_fail git verify-commit eighth-signed-alt +' + +test_expect_success GPGSSH 'verify signatures with --raw' ' + test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" && + ( + for commit in initial second merge fourth-signed fifth-signed sixth-signed seventh-signed + do + git verify-commit --raw $commit 2>actual && + grep "${GPGSSH_GOOD_SIGNATURE_TRUSTED}" actual && + ! grep "${GPGSSH_BAD_SIGNATURE}" actual && + echo $commit OK || exit 1 + done + ) && + ( + for commit in merge^2 fourth-unsigned sixth-unsigned seventh-unsigned + do + test_must_fail git verify-commit --raw $commit 2>actual && + ! grep "${GPGSSH_GOOD_SIGNATURE_TRUSTED}" actual && + ! grep "${GPGSSH_BAD_SIGNATURE}" actual && + echo $commit OK || exit 1 + done + ) && + ( + for commit in eighth-signed-alt + do + test_must_fail git verify-commit --raw $commit 2>actual && + grep "${GPGSSH_GOOD_SIGNATURE_UNTRUSTED}" actual && + ! grep "${GPGSSH_BAD_SIGNATURE}" actual && + echo $commit OK || exit 1 + done + ) +' + +test_expect_success GPGSSH 'proper header is used for hash algorithm' ' + git cat-file commit fourth-signed >output && + grep "^$(test_oid header) -----BEGIN SSH SIGNATURE-----" output +' + +test_expect_success GPGSSH 'show signed commit with signature' ' + test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" && + git show -s initial >commit && + git show -s --show-signature initial >show && + git verify-commit -v initial >verify.1 2>verify.2 && + git cat-file commit initial >cat && + grep -v -e "${GPGSSH_GOOD_SIGNATURE_TRUSTED}" -e "Warning: " show >show.commit && + grep -e "${GPGSSH_GOOD_SIGNATURE_TRUSTED}" -e "Warning: " show >show.gpg && + grep -v "^ " cat | grep -v "^gpgsig.* " >cat.commit && + test_cmp show.commit commit && + test_cmp show.gpg verify.2 && + test_cmp cat.commit verify.1 +' + +test_expect_success GPGSSH 'detect fudged signature' ' + test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" && + git cat-file commit seventh-signed >raw && + sed -e "s/^seventh/7th forged/" raw >forged1 && + git hash-object -w -t commit forged1 >forged1.commit && + test_must_fail git verify-commit $(cat forged1.commit) && + git show --pretty=short --show-signature $(cat forged1.commit) >actual1 && + grep "${GPGSSH_BAD_SIGNATURE}" actual1 && + ! grep "${GPGSSH_GOOD_SIGNATURE_TRUSTED}" actual1 && + ! grep "${GPGSSH_GOOD_SIGNATURE_UNTRUSTED}" actual1 +' + +test_expect_success GPGSSH 'detect fudged signature with NUL' ' + test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" && + git cat-file commit seventh-signed >raw && + cat raw >forged2 && + echo Qwik | tr "Q" "\000" >>forged2 && + git hash-object -w -t commit forged2 >forged2.commit && + test_must_fail git verify-commit $(cat forged2.commit) && + git show --pretty=short --show-signature $(cat forged2.commit) >actual2 && + grep "${GPGSSH_BAD_SIGNATURE}" actual2 && + ! grep "${GPGSSH_GOOD_SIGNATURE_TRUSTED}" actual2 +' + +test_expect_success GPGSSH 'amending already signed commit' ' + test_config gpg.format ssh && + test_config user.signingkey "${GPGSSH_KEY_PRIMARY}" && + test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" && + git checkout fourth-signed^0 && + git commit --amend -S --no-edit && + git verify-commit HEAD && + git show -s --show-signature HEAD >actual && + grep "${GPGSSH_GOOD_SIGNATURE_TRUSTED}" actual && + ! grep "${GPGSSH_BAD_SIGNATURE}" actual +' + +test_expect_success GPGSSH 'show good signature with custom format' ' + test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" && + FINGERPRINT=$(ssh-keygen -lf "${GPGSSH_KEY_PRIMARY}" | awk "{print \$2;}") && + cat >expect.tmpl <<-\EOF && + G + FINGERPRINT + principal with number 1 + FINGERPRINT + + EOF + sed "s|FINGERPRINT|$FINGERPRINT|g" expect.tmpl >expect && + git log -1 --format="%G?%n%GK%n%GS%n%GF%n%GP" sixth-signed >actual && + test_cmp expect actual +' + +test_expect_success GPGSSH 'show bad signature with custom format' ' + test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" && + cat >expect <<-\EOF && + B + + + + + EOF + git log -1 --format="%G?%n%GK%n%GS%n%GF%n%GP" $(cat forged1.commit) >actual && + test_cmp expect actual +' + +test_expect_success GPGSSH 'show untrusted signature with custom format' ' + test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" && + cat >expect.tmpl <<-\EOF && + U + FINGERPRINT + + FINGERPRINT + + EOF + git log -1 --format="%G?%n%GK%n%GS%n%GF%n%GP" eighth-signed-alt >actual && + FINGERPRINT=$(ssh-keygen -lf "${GPGSSH_KEY_UNTRUSTED}" | awk "{print \$2;}") && + sed "s|FINGERPRINT|$FINGERPRINT|g" expect.tmpl >expect && + test_cmp expect actual +' + +test_expect_success GPGSSH 'show untrusted signature with undefined trust level' ' + test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" && + cat >expect.tmpl <<-\EOF && + undefined + FINGERPRINT + + FINGERPRINT + + EOF + git log -1 --format="%GT%n%GK%n%GS%n%GF%n%GP" eighth-signed-alt >actual && + FINGERPRINT=$(ssh-keygen -lf "${GPGSSH_KEY_UNTRUSTED}" | awk "{print \$2;}") && + sed "s|FINGERPRINT|$FINGERPRINT|g" expect.tmpl >expect && + test_cmp expect actual +' + +test_expect_success GPGSSH 'show untrusted signature with ultimate trust level' ' + test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" && + cat >expect.tmpl <<-\EOF && + fully + FINGERPRINT + principal with number 1 + FINGERPRINT + + EOF + git log -1 --format="%GT%n%GK%n%GS%n%GF%n%GP" sixth-signed >actual && + FINGERPRINT=$(ssh-keygen -lf "${GPGSSH_KEY_PRIMARY}" | awk "{print \$2;}") && + sed "s|FINGERPRINT|$FINGERPRINT|g" expect.tmpl >expect && + test_cmp expect actual +' + +test_expect_success GPGSSH 'show lack of signature with custom format' ' + cat >expect <<-\EOF && + N + + + + + EOF + git log -1 --format="%G?%n%GK%n%GS%n%GF%n%GP" seventh-unsigned >actual && + test_cmp expect actual +' + +test_expect_success GPGSSH 'log.showsignature behaves like --show-signature' ' + test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" && + test_config log.showsignature true && + git show initial >actual && + grep "${GPGSSH_GOOD_SIGNATURE_TRUSTED}" actual +' + +test_expect_success GPGSSH 'check config gpg.format values' ' + test_config gpg.format ssh && + test_config user.signingkey "${GPGSSH_KEY_PRIMARY}" && + test_config gpg.format ssh && + git commit -S --amend -m "success" && + test_config gpg.format OpEnPgP && + test_must_fail git commit -S --amend -m "fail" +' + +test_expect_failure GPGSSH 'detect fudged commit with double signature (TODO)' ' + sed -e "/gpgsig/,/END PGP/d" forged1 >double-base && + sed -n -e "/gpgsig/,/END PGP/p" forged1 | \ + sed -e "s/^$(test_oid header)//;s/^ //" | gpg --dearmor >double-sig1.sig && + gpg -o double-sig2.sig -u 29472784 --detach-sign double-base && + cat double-sig1.sig double-sig2.sig | gpg --enarmor >double-combined.asc && + sed -e "s/^\(-.*\)ARMORED FILE/\1SIGNATURE/;1s/^/$(test_oid header) /;2,\$s/^/ /" \ + double-combined.asc > double-gpgsig && + sed -e "/committer/r double-gpgsig" double-base >double-commit && + git hash-object -w -t commit double-commit >double-commit.commit && + test_must_fail git verify-commit $(cat double-commit.commit) && + git show --pretty=short --show-signature $(cat double-commit.commit) >double-actual && + grep "BAD signature from" double-actual && + grep "Good signature from" double-actual +' + +test_expect_failure GPGSSH 'show double signature with custom format (TODO)' ' + cat >expect <<-\EOF && + E + + + + + EOF + git log -1 --format="%G?%n%GK%n%GS%n%GF%n%GP" $(cat double-commit.commit) >actual && + test_cmp expect actual +' + + +test_expect_failure GPGSSH 'verify-commit verifies multiply signed commits (TODO)' ' + git init multiply-signed && + cd multiply-signed && + test_commit first && + echo 1 >second && + git add second && + tree=$(git write-tree) && + parent=$(git rev-parse HEAD^{commit}) && + git commit --gpg-sign -m second && + git cat-file commit HEAD && + # Avoid trailing whitespace. + sed -e "s/^Q//" -e "s/^Z/ /" >commit <<-EOF && + Qtree $tree + Qparent $parent + Qauthor A U Thor <[email protected]> 1112912653 -0700 + Qcommitter C O Mitter <[email protected]> 1112912653 -0700 + Qgpgsig -----BEGIN PGP SIGNATURE----- + QZ + Q iHQEABECADQWIQRz11h0S+chaY7FTocTtvUezd5DDQUCX/uBDRYcY29tbWl0dGVy + Q QGV4YW1wbGUuY29tAAoJEBO29R7N3kMNd+8AoK1I8mhLHviPH+q2I5fIVgPsEtYC + Q AKCTqBh+VabJceXcGIZuF0Ry+udbBQ== + Q =tQ0N + Q -----END PGP SIGNATURE----- + Qgpgsig-sha256 -----BEGIN PGP SIGNATURE----- + QZ + Q iHQEABECADQWIQRz11h0S+chaY7FTocTtvUezd5DDQUCX/uBIBYcY29tbWl0dGVy + Q QGV4YW1wbGUuY29tAAoJEBO29R7N3kMN/NEAn0XO9RYSBj2dFyozi0JKSbssYMtO + Q AJwKCQ1BQOtuwz//IjU8TiS+6S4iUw== + Q =pIwP + Q -----END PGP SIGNATURE----- + Q + Qsecond + EOF + head=$(git hash-object -t commit -w commit) && + git reset --hard $head && + git verify-commit $head 2>actual && + grep "Good signature from" actual && + ! grep "BAD signature from" actual +' + +test_done
Signature · 1 line
gitgitgadget
PATCH v8 8/9 ssh signing: tests for logs, tags & push certs
In reply to Fabian Stelzer via GitGitGadgetFrom: Fabian Stelzer <[email protected]>
--- t/t4202-log.sh | 23 +++++ t/t5534-push-signed.sh | 101 +++++++++++++++++++ t/t7031-verify-tag-signed-ssh.sh | 161 +++++++++++++++++++++++++++++++ 3 files changed, 285 insertions(+) create mode 100755 t/t7031-verify-tag-signed-ssh.sh
t/t4202-log.sh +23 -0
@@ -1616,6 +1616,16 @@ test_expect_success GPGSM 'setup signed branch x509' ' git commit -S -m signed_commit ' +test_expect_success GPGSSH 'setup sshkey signed branch' ' + test_config gpg.format ssh && + test_config user.signingkey "${GPGSSH_KEY_PRIMARY}" && + test_when_finished "git reset --hard && git checkout main" && + git checkout -b signed-ssh main && + echo foo >foo && + git add foo && + git commit -S -m signed_commit +' + test_expect_success GPGSM 'log x509 fingerprint' ' echo "F8BF62E0693D0694816377099909C779FA23FD65 | " >expect && git log -n1 --format="%GF | %GP" signed-x509 >actual &&
@@ -1628,6 +1638,13 @@ test_expect_success GPGSM 'log OpenPGP fingerprint' ' test_cmp expect actual ' +test_expect_success GPGSSH 'log ssh key fingerprint' ' + test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" && + ssh-keygen -lf "${GPGSSH_KEY_PRIMARY}" | awk "{print \$2\" | \"}" >expect && + git log -n1 --format="%GF | %GP" signed-ssh >actual && + test_cmp expect actual +' + test_expect_success GPG 'log --graph --show-signature' ' git log --graph --show-signature -n1 signed >actual && grep "^| gpg: Signature made" actual &&
@@ -1640,6 +1657,12 @@ test_expect_success GPGSM 'log --graph --show-signature x509' ' grep "^| gpgsm: Good signature" actual ' +test_expect_success GPGSSH 'log --graph --show-signature ssh' ' + test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" && + git log --graph --show-signature -n1 signed-ssh >actual && + grep "${GOOD_SIGNATURE_TRUSTED}" actual +' + test_expect_success GPG 'log --graph --show-signature for merged tag' ' test_when_finished "git reset --hard && git checkout main" && git checkout -b plain main &&
t/t5534-push-signed.sh +101 -0
113 lines not on this page · raw message
t/t7031-verify-tag-signed-ssh.sh new file +161 -0
161 lines not on this page · raw message
Signature · 1 line
gitgitgadget
PATCH v8 9/9 ssh signing: test that gpg fails for unknown keys
In reply to Fabian Stelzer via GitGitGadgetFrom: Fabian Stelzer <[email protected]>
Test that verify-commit/tag will fail when a gpg key is completely unknown. To do this we have to generate a key, use it for a signature and delete it from our keyring aferwards completely.
--- t/t7510-signed-commit.sh | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-)
t/t7510-signed-commit.sh +28 -1
@@ -71,7 +71,25 @@ test_expect_success GPG 'create signed commits' ' git tag eleventh-signed $(cat oid) && echo 12 | git commit-tree --gpg-sign=B7227189 HEAD^{tree} >oid && test_line_count = 1 oid && - git tag twelfth-signed-alt $(cat oid) + git tag twelfth-signed-alt $(cat oid) && + + cat >keydetails <<-\EOF && + Key-Type: RSA + Key-Length: 2048 + Subkey-Type: RSA + Subkey-Length: 2048 + Name-Real: Unknown User + Name-Email: [email protected] + Expire-Date: 0 + %no-ask-passphrase + %no-protection + EOF + gpg --batch --gen-key keydetails && + echo 13 >file && git commit -a -S"[email protected]" -m thirteenth && + git tag thirteenth-signed && + DELETE_FINGERPRINT=$(gpg -K --with-colons --fingerprint --batch [email protected] | grep "^fpr" | head -n 1 | awk -F ":" "{print \$10;}") && + gpg --batch --yes --delete-secret-keys $DELETE_FINGERPRINT && + gpg --batch --yes --delete-keys [email protected] ' test_expect_success GPG 'verify and show signatures' '
@@ -110,6 +128,13 @@ test_expect_success GPG 'verify and show signatures' ' ) ' +test_expect_success GPG 'verify-commit exits failure on unknown signature' ' + test_must_fail git verify-commit thirteenth-signed 2>actual && + ! grep "Good signature from" actual && + ! grep "BAD signature from" actual && + grep -q -F -e "No public key" -e "public key not found" actual +' + test_expect_success GPG 'verify-commit exits success on untrusted signature' ' git verify-commit eighth-signed-alt 2>actual && grep "Good signature from" actual &&
@@ -338,6 +363,8 @@ test_expect_success GPG 'show double signature with custom format' ' ' +# NEEDSWORK: This test relies on the test_tick commit/author dates from the first +# 'create signed commits' test even though it creates its own test_expect_success GPG 'verify-commit verifies multiply signed commits' ' git init multiply-signed && cd multiply-signed &&
Signature · 1 line
gitgitgadget
t7510-signed-commit.sh hangs on old gpg, regression in 1bfb57f642d (was: [PATCH v8 9/9] ssh signing: test that gpg fails for unknown keys)
Quoted 57 lines · Fabian Stelzer via GitGitGadget
From: Fabian Stelzer <[email protected]>
Test that verify-commit/tag will fail when a gpg key is completely unknown. To do this we have to generate a key, use it for a signature and delete it from our keyring aferwards completely.
Signed-off-by Fabian Stelzer <[email protected]>--- t/t7510-signed-commit.sh | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-)t/t7510-signed-commit.sh +26
-1@@ -71,7 +71,25 @@ test_expect_success GPG 'create signed commits' ' git tag eleventh-signed $(cat oid) && echo 12 | git commit-tree --gpg-sign=B7227189 HEAD^{tree} >oid && test_line_count = 1 oid && - git tag twelfth-signed-alt $(cat oid) + git tag twelfth-signed-alt $(cat oid) && + + cat >keydetails <<-\EOF && + Key-Type: RSA + Key-Length: 2048 + Subkey-Type: RSA + Subkey-Length: 2048 + Name-Real: Unknown User + Name-Email: [email protected] + Expire-Date: 0 + %no-ask-passphrase + %no-protection + EOF + gpg --batch --gen-key keydetails && + echo 13 >file && git commit -a -S"[email protected]" -m thirteenth && + git tag thirteenth-signed && + DELETE_FINGERPRINT=$(gpg -K --with-colons --fingerprint --batch [email protected] | grep "^fpr" | head -n 1 | awk -F ":" "{print \$10;}") && + gpg --batch --yes --delete-secret-keys $DELETE_FINGERPRINT && + gpg --batch --yes --delete-keys [email protected] ' test_expect_success GPG 'verify and show signatures' '@@ -110,6 +128,13 @@ test_expect_success GPG 'verify and show signatures' ' ) ' +test_expect_success GPG 'verify-commit exits failure on unknown signature' ' + test_must_fail git verify-commit thirteenth-signed 2>actual && + ! grep "Good signature from" actual && + ! grep "BAD signature from" actual && + grep -q -F -e "No public key" -e "public key not found" actual +' + test_expect_success GPG 'verify-commit exits success on untrusted signature' ' git verify-commit eighth-signed-alt 2>actual && grep "Good signature from" actual &&
t/t7510-signed-commit.sh test_expect_success GPG 'show double signature with custom format' ' · line 370
Quoted 8 lines
t/t7510-signed-commit.sh +2
-0@@ -338,6 +363,8 @@ test_expect_success GPG 'show double signature with custom format' ' ' +# NEEDSWORK: This test relies on the test_tick commit/author dates from the first +# 'create signed commits' test even though it creates its own test_expect_success GPG 'verify-commit verifies multiply signed commits' ' git init multiply-signed && cd multiply-signed &&
The t7510-signed-commit.sh script hangs on startup with this change, and with -vx we show:
[...]
++ git tag twelfth-signed-alt 17f06d503ee50df92746c17f6cced6feb5940cf5
++ cat
++ gpg --batch --gen-key keydetails
gpg: skipping control `%no-protection' ()This is on a CentOS 7.9 box on the GCC Farm:
[avar@gcc135 t]$ uname -a ; gpg --version
Linux gcc135.osuosl.org 4.18.0-80.7.2.el7.ppc64le #1 SMP Thu Sep 12 15:45:05 UTC 2019 ppc64le ppc64le ppc64le GNU/Linux
gpg (GnuPG) 2.0.22
libgcrypt 1.5.3
Copyright (C) 2013 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Home: ~/.gnupg
Supported algorithms:
Pubkey: RSA, ?, ?, ELG, DSA
Cipher: IDEA, 3DES, CAST5, BLOWFISH, AES, AES192, AES256, TWOFISH,
CAMELLIA128, CAMELLIA192, CAMELLIA256
Hash: MD5, SHA1, RIPEMD160, SHA256, SHA384, SHA512, SHA224
Compression: Uncompressed, ZIP, ZLIB, BZIP2Re: t7510-signed-commit.sh hangs on old gpg, regression in 1bfb57f642d (was: [PATCH v8 9/9] ssh signing: test that gpg fails for unknown keys)
Quoted 35 lines · Ævar Arnfjörð Bjarmason
Quoted 11 lines · Fabian Stelzer via GitGitGadget
From: Fabian Stelzer <[email protected]>
Test that verify-commit/tag will fail when a gpg key is completely unknown. To do this we have to generate a key, use it for a signature and delete it from our keyring aferwards completely.
Signed-off-by Fabian Stelzer <[email protected]>t/t7510-signed-commit.sh +3
-0+ + cat >keydetails <<-\EOF && + Key-Type: RSAt/t7510-signed-commit.sh test_expect_success GPG 'create signed commits' ' · line 87
Quoted 10 lines
t/t7510-signed-commit.sh +10
-0+ Key-Length: 2048 + Subkey-Type: RSA + Subkey-Length: 2048 + Name-Real: Unknown User + Name-Email: [email protected] + Expire-Date: 0 + %no-ask-passphrase + %no-protection + EOF + gpg --batch --gen-key keydetails &&The
t7510-signed-commit.shscript hangs on startup with this change, and with -vx we show:[...] ++ git tag twelfth-signed-alt 17f06d503ee50df92746c17f6cced6feb5940cf5 ++ cat ++ gpg --batch --gen-key keydetails gpg: skipping control `%no-protection' ()This is on a CentOS 7.9 box on the GCC Farm:
Quoted 16 lines
[avar@gcc135 t]$ uname -a ; gpg --version Linux gcc135.osuosl.org 4.18.0-80.7.2.el7.ppc64le #1 SMP Thu Sep 12 15:45:05 UTC 2019 ppc64le ppc64le ppc64le GNU/Linux gpg (GnuPG) 2.0.22 libgcrypt 1.5.3 Copyright (C) 2013 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html> This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Home: ~/.gnupg Supported algorithms: Pubkey: RSA, ?, ?, ELG, DSA Cipher: IDEA, 3DES, CAST5, BLOWFISH, AES, AES192, AES256, TWOFISH, CAMELLIA128, CAMELLIA192, CAMELLIA256 Hash: MD5, SHA1, RIPEMD160, SHA256, SHA384, SHA512, SHA224 Compression: Uncompressed, ZIP, ZLIB, BZIP2
Hm. I have an identical centos 7.9 installation (same versions/features) and the key is generated without issues. Does the VM maybe have not enough entropy for generating a gpg key? Otherwise we could of course pre-generate the key and commit it. I'm usually not a fan of this since over time it can become unclear how it was generated or if the committed version still matches what would be generated today. But of course I don't want to slow down CI with rsa key generation stuff :/
If missing entropy is the problem, then maybe CI could benefit from something like haveged in general (other tests might want more entropy too).
Re: t7510-signed-commit.sh hangs on old gpg, regression in 1bfb57f642d (was: [PATCH v8 9/9] ssh signing: test that gpg fails for unknown keys)
Quoted 9 lines · Fabian Stelzer
Hm. I have an identical centos 7.9 installation (same versions/features) and the key is generated without issues. Does the VM maybe have not enough entropy for generating a gpg key?
Otherwise we could of course pre-generate the key and commit it. I'm usually not a fan of this since over time it can become unclear how it was generated or if the committed version still matches what would be generated today. But of course I don't want to slow down CI with rsa key generation stuff :/ If missing entropy is the problem, then maybe CI could benefit from something like haveged in general (other tests might want more entropy too).
GnuPG is notorious for using /dev/random for generating keys, so yes, this is likely to block in a variety of situations. We don't see this on newer systems because they've replaced the blocking /dev/random with a non-blocking one except for when the CSPRNG hasn't been seeded at least once.
The problem isn't lack of entropy, but the fact that there's no reason to use /dev/random since /dev/urandom is suitable for all cryptographic needs once initialized. On modern versions of Linux, one just uses getrandom(2), which deals with the uninitialized case and otherwise doesn't block. However, CentOS 7 is old.
Signature · 2 lines
brian m. carlson (he/him or they/them) Toronto, Ontario, CA
Re: t7510-signed-commit.sh hangs on old gpg, regression in 1bfb57f642d (was: [PATCH v8 9/9] ssh signing: test that gpg fails for unknown keys)
In reply to Fabian StelzerQuoted 53 lines · Fabian Stelzer
Quoted 35 lines · Ævar Arnfjörð Bjarmason
Quoted 11 lines · Fabian Stelzer via GitGitGadget
From: Fabian Stelzer <[email protected]>
Test that verify-commit/tag will fail when a gpg key is completely unknown. To do this we have to generate a key, use it for a signature and delete it from our keyring aferwards completely.
Signed-off-by Fabian Stelzer <[email protected]>(quoted diff) +3
-0+ + cat >keydetails <<-\EOF && + Key-Type: RSAReview
Quoted 10 lines
(quoted diff) +10
-0+ Key-Length: 2048 + Subkey-Type: RSA + Subkey-Length: 2048 + Name-Real: Unknown User + Name-Email: [email protected] + Expire-Date: 0 + %no-ask-passphrase + %no-protection + EOF + gpg --batch --gen-key keydetails &&The
t7510-signed-commit.shscript hangs on startup with this change, and with -vx we show:[...] ++ git tag twelfth-signed-alt 17f06d503ee50df92746c17f6cced6feb5940cf5 ++ cat ++ gpg --batch --gen-key keydetails gpg: skipping control `%no-protection' ()This is on a CentOS 7.9 box on the GCC Farm:
Quoted 16 lines
[avar@gcc135 t]$ uname -a ; gpg --version Linux gcc135.osuosl.org 4.18.0-80.7.2.el7.ppc64le #1 SMP Thu Sep 12 15:45:05 UTC 2019 ppc64le ppc64le ppc64le GNU/Linux gpg (GnuPG) 2.0.22 libgcrypt 1.5.3 Copyright (C) 2013 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html> This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Home: ~/.gnupg Supported algorithms: Pubkey: RSA, ?, ?, ELG, DSA Cipher: IDEA, 3DES, CAST5, BLOWFISH, AES, AES192, AES256, TWOFISH, CAMELLIA128, CAMELLIA192, CAMELLIA256 Hash: MD5, SHA1, RIPEMD160, SHA256, SHA384, SHA512, SHA224 Compression: Uncompressed, ZIP, ZLIB, BZIP2
Quoted 11 lines
Hm. I have an identical centos 7.9 installation (same versions/features) and the key is generated without issues. Does the VM maybe have not enough entropy for generating a gpg key? Otherwise we could of course pre-generate the key and commit it. I'm usually not a fan of this since over time it can become unclear how it was generated or if the committed version still matches what would be generated today.
But of course I don't want to slow down CI with rsa key generation stuff :/ If missing entropy is the problem, then maybe CI could benefit from something like haveged in general (other tests might want more entropy too).
Late reply. It's not a VM, but yes. I've confirmed that it's due to /dev/random hanging.
I don't understand why we need to generate a key at all.
It looks like your 1bfb57f642d (ssh signing: test that gpg fails for unknown keys, 2021-09-10) is just trying to test the case where we sign with a key, and then don't have that key anymore.
The below POC patch seems to work just as well, and will succeed with:
./t7510-signed-commit.sh --run=1,3
Of course a lot of other tests now fail, because they relied on the [email protected] key.
But that seems easily solved by just moving this test to its own file, or deleting/re-importing the key for just that test or whatever. If we truly need yet another key why are we making it on the fly instead of adding it to t/lib-gpg/keyring.gpg like the others?
t/t7510-signed-commit.sh +3 -15
@@ -73,23 +73,11 @@ test_expect_success GPG 'create signed commits' ' test_line_count = 1 oid && git tag twelfth-signed-alt $(cat oid) && - cat >keydetails <<-\EOF && - Key-Type: RSA - Key-Length: 2048 - Subkey-Type: RSA - Subkey-Length: 2048 - Name-Real: Unknown User - Name-Email: [email protected] - Expire-Date: 0 - %no-ask-passphrase - %no-protection - EOF - gpg --batch --gen-key keydetails && - echo 13 >file && git commit -a -S"[email protected]" -m thirteenth && + echo 13 >file && git commit -a -S"[email protected]" -m thirteenth && git tag thirteenth-signed && - DELETE_FINGERPRINT=$(gpg -K --with-colons --fingerprint --batch [email protected] | grep "^fpr" | head -n 1 | awk -F ":" "{print \$10;}") && + DELETE_FINGERPRINT=$(gpg -K --with-colons --fingerprint --batch [email protected] | grep "^fpr" | head -n 1 | awk -F ":" "{print \$10;}") && gpg --batch --yes --delete-secret-keys $DELETE_FINGERPRINT && - gpg --batch --yes --delete-keys [email protected] + gpg --batch --yes --delete-keys [email protected] ' test_expect_success GPG 'verify and show signatures' '
Re: t7510-signed-commit.sh hangs on old gpg, regression in 1bfb57f642d (was: [PATCH v8 9/9] ssh signing: test that gpg fails for unknown keys)
Quoted 13 lines · Ævar Arnfjörð Bjarmason
Quoted 12 lines
Hm. I have an identical centos 7.9 installation (same versions/features) and the key is generated without issues. Does the VM maybe have not enough entropy for generating a gpg key? Otherwise we could of course pre-generate the key and commit it. I'm usually not a fan of this since over time it can become unclear how it was generated or if the committed version still matches what would be generated today.
But of course I don't want to slow down CI with rsa key generation stuff :/ If missing entropy is the problem, then maybe CI could benefit from something like haveged in general (other tests might want more entropy too).
Quoted 4 lines
Late reply. It's not a VM, but yes. I've confirmed that it's due to
/dev/randomhanging.I don't understand why we need to generate a key at all.
You are right, we don't need to. I initially toyed with the GPG commands to disable/export/reimport a key but without success (I'm not terribly familiar with GPG though).
Quoted 5 lines
It looks like your
1bfb57f642d(ssh signing: test that gpg fails for unknown keys, 2021-09-10) is just trying to test the case where we sign with a key, and then don't have that key anymore.
It tests verifying a commit for which the key is not in our keyring at all. All the other tests only use present keys (with varying trust levels) or completely unsigned commits for the failure check.
I think we could do the following though and simply point git to an empty keyring to be able to verify this:
t/t7510-signed-commit.sh +2 -20
@@ -71,25 +71,7 @@ test_expect_success GPG 'create signed commits' ' git tag eleventh-signed $(cat oid) && echo 12 | git commit-tree --gpg-sign=B7227189 HEAD^{tree} >oid && test_line_count = 1 oid && - git tag twelfth-signed-alt $(cat oid) && - - cat >keydetails <<-\EOF && - Key-Type: RSA - Key-Length: 2048 - Subkey-Type: RSA - Subkey-Length: 2048 - Name-Real: Unknown User - Name-Email: [email protected] - Expire-Date: 0 - %no-ask-passphrase - %no-protection - EOF - gpg --batch --gen-key keydetails && - echo 13 >file && git commit -a -S"[email protected]" -m thirteenth && - git tag thirteenth-signed && - DELETE_FINGERPRINT=$(gpg -K --with-colons --fingerprint --batch [email protected] | grep "^fpr" | head -n 1 | awk -F ":" "{print \$10;}") && - gpg --batch --yes --delete-secret-keys $DELETE_FINGERPRINT && - gpg --batch --yes --delete-keys [email protected] + git tag twelfth-signed-alt $(cat oid) ' test_expect_success GPG 'verify and show signatures' '
@@ -129,7 +111,7 @@ test_expect_success GPG 'verify and show signatures' ' ' test_expect_success GPG 'verify-commit exits failure on unknown signature' ' - test_must_fail git verify-commit thirteenth-signed 2>actual && + GNUPGHOME=./empty_home test_must_fail git verify-commit initial 2>actual && ! grep "Good signature from" actual && ! grep "BAD signature from" actual && grep -q -F -e "No public key" -e "public key not found" actual
Re: [PATCH v8 0/9] ssh signing: Add commit & tag signing/verification via SSH keys using ssh-keygen
In reply to Fabian Stelzer via GitGitGadgetQuoted 6 lines · Fabian Stelzer via GitGitGadget
v8:
* fixes a bug around find-principals buffer i was releasing while still iterating over it. Uses separate strbufs now.
* rename a wrong variable in the tests
* usegit_config_pathnameinstead of string where applicable
I guess I'd better kick the topic out of 'next' before doing anything else, as it still seems to want to be replaceable wholesale. Somehow I was given a (probably false) impression that the previous one was in a more or less testable shape and we can go incremental already, which was why I merged v7 to 'next'.
Will queue later, but may not get around to it today.
Thanks.
Re: [PATCH v8 0/9] ssh signing: Add commit & tag signing/verification via SSH keys using ssh-keygen
Quoted 8 lines · Junio C Hamano
Quoted 6 lines · Fabian Stelzer via GitGitGadget
v8:
* fixes a bug around find-principals buffer i was releasing while still iterating over it. Uses separate strbufs now.
* rename a wrong variable in the tests
* usegit_config_pathnameinstead of string where applicable
Quoted 5 lines
I guess I'd better kick the topic out of 'next' before doing anything else, as it still seems to want to be replaceable wholesale. Somehow I was given a (probably false) impression that the previous one was in a more or less testable shape and we can go incremental already, which was why I merged v7 to 'next'.
Sorry, i think i'm just not familiar with the process. What do i do when the patch is in next and someone (or myself) find other bugs during testing? Do i send a new patch based on "next" or update my patchset but not squashing the fixup commits?
Re: [PATCH v8 0/9] ssh signing: Add commit & tag signing/verification via SSH keys using ssh-keygen
Quoted 4 lines · Fabian Stelzer
Sorry, i think i'm just not familiar with the process. What do i do when the patch is in next and someone (or myself) find other bugs during testing? Do i send a new patch based on "next" or update my patchset but not squashing the fixup commits?
In general, you'd send an incremental update on top of what you submitted and has been queued in my tree so far. Looking for the merge of the topic from the tip of 'next':
$ git show -s "next^{/^Merge branch 'fs/ssh-signing' into next}" |
grep "^Merge:"
Merge: 348fe07b87 b88bcd013b
$ git log --oneline --reverse master..b88bcd013b
c222385164 ssh signing: preliminary refactoring and clean-up
3a3fdc0b4e ssh signing: add test prereqs
c7e2d30efe ssh signing: add ssh key format and signing code
5493722122 ssh signing: retrieve a default key from ssh-agent
6869f1f60c ssh signing: provide a textual signing_key_id
9048bb3c9b ssh signing: verify signatures using ssh-keygen
587967698a ssh signing: duplicate t7510 tests for commits
52ac6bd36f ssh signing: tests for logs, tags & push certs
b88bcd013b ssh signing: test that gpg fails for unknown keyswe learn that b88bcd013b is the tip, so you'd send follow-up patches to either fix a bug that exists in the tree of b88bcd013b, or enhance a feature on top of the tree of b88bcd013b.
But since I am already ejecting the previous round out of 'next', let's remember to do so the next time. We will have to wait until mid October (if I recall what I thought I read from you correctly) anyway, so until then we can iterate outside the 'next' branch.
Thanks.