Merge branch 'cc/fast-import-strip-signed-tags'
"git fast-import" is taught to handle signed tags, just like it recently learned to handle signed commits, in different ways. * cc/fast-import-strip-signed-tags: fast-import: add '--signed-tags=<mode>' option fast-export: handle all kinds of tag signatures t9350: properly count annotated tags lib-gpg: allow tests with GPGSM or GPGSSH prereq first doc: git-tag: stop focusing on GPG signed tagsmain
commit
3deb97fe24
|
|
@ -66,6 +66,11 @@ fast-import stream! This option is enabled automatically for
|
|||
remote-helpers that use the `import` capability, as they are
|
||||
already trusted to run their own code.
|
||||
|
||||
--signed-tags=(verbatim|warn-verbatim|warn-strip|strip|abort)::
|
||||
Specify how to handle signed tags. Behaves in the same way
|
||||
as the same option in linkgit:git-fast-export[1], except that
|
||||
default is 'verbatim' (instead of 'abort').
|
||||
|
||||
--signed-commits=(verbatim|warn-verbatim|warn-strip|strip|abort)::
|
||||
Specify how to handle signed commits. Behaves in the same way
|
||||
as the same option in linkgit:git-fast-export[1], except that
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ git-tag(1)
|
|||
|
||||
NAME
|
||||
----
|
||||
git-tag - Create, list, delete or verify a tag object signed with GPG
|
||||
git-tag - Create, list, delete or verify tags
|
||||
|
||||
|
||||
SYNOPSIS
|
||||
|
|
@ -38,15 +38,17 @@ and `-a`, `-s`, and `-u <key-id>` are absent, `-a` is implied.
|
|||
Otherwise, a tag reference that points directly at the given object
|
||||
(i.e., a lightweight tag) is created.
|
||||
|
||||
A GnuPG signed tag object will be created when `-s` or `-u
|
||||
<key-id>` is used. When `-u <key-id>` is not used, the
|
||||
committer identity for the current user is used to find the
|
||||
GnuPG key for signing. The configuration variable `gpg.program`
|
||||
is used to specify custom GnuPG binary.
|
||||
A cryptographically signed tag object will be created when `-s` or
|
||||
`-u <key-id>` is used. The signing backend (GPG, X.509, SSH, etc.) is
|
||||
controlled by the `gpg.format` configuration variable, defaulting to
|
||||
OpenPGP. When `-u <key-id>` is not used, the committer identity for
|
||||
the current user is used to find the key for signing. The
|
||||
configuration variable `gpg.program` is used to specify a custom
|
||||
signing binary.
|
||||
|
||||
Tag objects (created with `-a`, `-s`, or `-u`) are called "annotated"
|
||||
tags; they contain a creation date, the tagger name and e-mail, a
|
||||
tagging message, and an optional GnuPG signature. Whereas a
|
||||
tagging message, and an optional cryptographic signature. Whereas a
|
||||
"lightweight" tag is simply a name for an object (usually a commit
|
||||
object).
|
||||
|
||||
|
|
@ -64,10 +66,12 @@ OPTIONS
|
|||
|
||||
`-s`::
|
||||
`--sign`::
|
||||
Make a GPG-signed tag, using the default e-mail address's key.
|
||||
The default behavior of tag GPG-signing is controlled by `tag.gpgSign`
|
||||
configuration variable if it exists, or disabled otherwise.
|
||||
See linkgit:git-config[1].
|
||||
Make a cryptographically signed tag, using the default signing
|
||||
key. The signing backend used depends on the `gpg.format`
|
||||
configuration variable. The default key is determined by the
|
||||
backend. For GPG, it's based on the committer's email address,
|
||||
while for SSH it may be a specific key file or agent
|
||||
identity. See linkgit:git-config[1].
|
||||
|
||||
`--no-sign`::
|
||||
Override `tag.gpgSign` configuration variable that is
|
||||
|
|
@ -75,7 +79,10 @@ OPTIONS
|
|||
|
||||
`-u <key-id>`::
|
||||
`--local-user=<key-id>`::
|
||||
Make a GPG-signed tag, using the given key.
|
||||
Make a cryptographically signed tag using the given key. The
|
||||
format of the <key-id> and the backend used depend on the
|
||||
`gpg.format` configuration variable. See
|
||||
linkgit:git-config[1].
|
||||
|
||||
`-f`::
|
||||
`--force`::
|
||||
|
|
@ -87,7 +94,7 @@ OPTIONS
|
|||
|
||||
`-v`::
|
||||
`--verify`::
|
||||
Verify the GPG signature of the given tag names.
|
||||
Verify the cryptographic signature of the given tags.
|
||||
|
||||
`-n<num>`::
|
||||
_<num>_ specifies how many lines from the annotation, if any,
|
||||
|
|
@ -235,12 +242,23 @@ it in the repository configuration as follows:
|
|||
|
||||
-------------------------------------
|
||||
[user]
|
||||
signingKey = <gpg-key-id>
|
||||
signingKey = <key-id>
|
||||
-------------------------------------
|
||||
|
||||
The signing backend can be chosen via the `gpg.format` configuration
|
||||
variable, which defaults to `openpgp`. See linkgit:git-config[1]
|
||||
for a list of other supported formats.
|
||||
|
||||
The path to the program used for each signing backend can be specified
|
||||
with the `gpg.<format>.program` configuration variable. For the
|
||||
`openpgp` backend, `gpg.program` can be used as a synonym for
|
||||
`gpg.openpgp.program`. See linkgit:git-config[1] for details.
|
||||
|
||||
`pager.tag` is only respected when listing tags, i.e., when `-l` is
|
||||
used or implied. The default is to use a pager.
|
||||
See linkgit:git-config[1].
|
||||
|
||||
See linkgit:git-config[1] for more details and other configuration
|
||||
variables.
|
||||
|
||||
DISCUSSION
|
||||
----------
|
||||
|
|
|
|||
|
|
@ -931,9 +931,8 @@ static void handle_tag(const char *name, struct tag *tag)
|
|||
|
||||
/* handle signed tags */
|
||||
if (message) {
|
||||
const char *signature = strstr(message,
|
||||
"\n-----BEGIN PGP SIGNATURE-----\n");
|
||||
if (signature)
|
||||
size_t sig_offset = parse_signed_buffer(message, message_size);
|
||||
if (sig_offset < message_size)
|
||||
switch (signed_tag_mode) {
|
||||
case SIGN_ABORT:
|
||||
die("encountered signed tag %s; use "
|
||||
|
|
@ -950,7 +949,7 @@ static void handle_tag(const char *name, struct tag *tag)
|
|||
oid_to_hex(&tag->object.oid));
|
||||
/* fallthru */
|
||||
case SIGN_STRIP:
|
||||
message_size = signature + 1 - message;
|
||||
message_size = sig_offset;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -188,6 +188,7 @@ static int global_argc;
|
|||
static const char **global_argv;
|
||||
static const char *global_prefix;
|
||||
|
||||
static enum sign_mode signed_tag_mode = SIGN_VERBATIM;
|
||||
static enum sign_mode signed_commit_mode = SIGN_VERBATIM;
|
||||
|
||||
/* Memory pools */
|
||||
|
|
@ -2963,6 +2964,43 @@ static void parse_new_commit(const char *arg)
|
|||
b->last_commit = object_count_by_type[OBJ_COMMIT];
|
||||
}
|
||||
|
||||
static void handle_tag_signature(struct strbuf *msg, const char *name)
|
||||
{
|
||||
size_t sig_offset = parse_signed_buffer(msg->buf, msg->len);
|
||||
|
||||
/* If there is no signature, there is nothing to do. */
|
||||
if (sig_offset >= msg->len)
|
||||
return;
|
||||
|
||||
switch (signed_tag_mode) {
|
||||
|
||||
/* First, modes that don't change anything */
|
||||
case SIGN_ABORT:
|
||||
die(_("encountered signed tag; use "
|
||||
"--signed-tags=<mode> to handle it"));
|
||||
case SIGN_WARN_VERBATIM:
|
||||
warning(_("importing a tag signature verbatim for tag '%s'"), name);
|
||||
/* fallthru */
|
||||
case SIGN_VERBATIM:
|
||||
/* Nothing to do, the signature will be put into the imported tag. */
|
||||
break;
|
||||
|
||||
/* Second, modes that remove the signature */
|
||||
case SIGN_WARN_STRIP:
|
||||
warning(_("stripping a tag signature for tag '%s'"), name);
|
||||
/* fallthru */
|
||||
case SIGN_STRIP:
|
||||
/* Truncate the buffer to remove the signature */
|
||||
strbuf_setlen(msg, sig_offset);
|
||||
break;
|
||||
|
||||
/* Third, BUG */
|
||||
default:
|
||||
BUG("invalid signed_tag_mode value %d from tag '%s'",
|
||||
signed_tag_mode, name);
|
||||
}
|
||||
}
|
||||
|
||||
static void parse_new_tag(const char *arg)
|
||||
{
|
||||
static struct strbuf msg = STRBUF_INIT;
|
||||
|
|
@ -3026,6 +3064,8 @@ static void parse_new_tag(const char *arg)
|
|||
/* tag payload/message */
|
||||
parse_data(&msg, 0, NULL);
|
||||
|
||||
handle_tag_signature(&msg, t->name);
|
||||
|
||||
/* build the tag object */
|
||||
strbuf_reset(&new_data);
|
||||
|
||||
|
|
@ -3546,6 +3586,9 @@ static int parse_one_option(const char *option)
|
|||
} else if (skip_prefix(option, "signed-commits=", &option)) {
|
||||
if (parse_sign_mode(option, &signed_commit_mode))
|
||||
usagef(_("unknown --signed-commits mode '%s'"), option);
|
||||
} else if (skip_prefix(option, "signed-tags=", &option)) {
|
||||
if (parse_sign_mode(option, &signed_tag_mode))
|
||||
usagef(_("unknown --signed-tags mode '%s'"), option);
|
||||
} else if (!strcmp(option, "quiet")) {
|
||||
show_stats = 0;
|
||||
quiet = 1;
|
||||
|
|
|
|||
24
t/lib-gpg.sh
24
t/lib-gpg.sh
|
|
@ -9,6 +9,16 @@
|
|||
GNUPGHOME="$(pwd)/gpghome"
|
||||
export GNUPGHOME
|
||||
|
||||
# All the "test_lazy_prereq GPG*" below should use
|
||||
# `prepare_gnupghome()` either directly or through a call to
|
||||
# `test_have_prereq GPG*`. That's because `gpg` and `gpgsm`
|
||||
# only create the directory specified using "$GNUPGHOME" or
|
||||
# `--homedir` if it's the default (usually "~/.gnupg").
|
||||
prepare_gnupghome() {
|
||||
mkdir -p "$GNUPGHOME" &&
|
||||
chmod 0700 "$GNUPGHOME"
|
||||
}
|
||||
|
||||
test_lazy_prereq GPG '
|
||||
gpg_version=$(gpg --version 2>&1)
|
||||
test $? != 127 || exit 1
|
||||
|
|
@ -38,8 +48,7 @@ test_lazy_prereq GPG '
|
|||
# To export ownertrust:
|
||||
# gpg --homedir /tmp/gpghome --export-ownertrust \
|
||||
# > lib-gpg/ownertrust
|
||||
mkdir "$GNUPGHOME" &&
|
||||
chmod 0700 "$GNUPGHOME" &&
|
||||
prepare_gnupghome &&
|
||||
(gpgconf --kill all || : ) &&
|
||||
gpg --homedir "${GNUPGHOME}" --import \
|
||||
"$TEST_DIRECTORY"/lib-gpg/keyring.gpg &&
|
||||
|
|
@ -63,6 +72,14 @@ test_lazy_prereq GPG2 '
|
|||
;;
|
||||
*)
|
||||
(gpgconf --kill all || : ) &&
|
||||
|
||||
# NEEDSWORK: prepare_gnupghome() should definitely be
|
||||
# called here, but it looks like it exposes a
|
||||
# pre-existing, hidden bug by allowing some tests in
|
||||
# t1016-compatObjectFormat.sh to run instead of being
|
||||
# skipped. See:
|
||||
# https://lore.kernel.org/git/ZoV8b2RvYxLOotSJ@teonanacatl.net/
|
||||
|
||||
gpg --homedir "${GNUPGHOME}" --import \
|
||||
"$TEST_DIRECTORY"/lib-gpg/keyring.gpg &&
|
||||
gpg --homedir "${GNUPGHOME}" --import-ownertrust \
|
||||
|
|
@ -132,8 +149,7 @@ test_lazy_prereq GPGSSH '
|
|||
test $? = 0 || exit 1;
|
||||
|
||||
# Setup some keys and an allowed signers file
|
||||
mkdir -p "${GNUPGHOME}" &&
|
||||
chmod 0700 "${GNUPGHOME}" &&
|
||||
prepare_gnupghome &&
|
||||
(setfacl -k "${GNUPGHOME}" 2>/dev/null || true) &&
|
||||
ssh-keygen -t ed25519 -N "" -C "git ed25519 key" -f "${GPGSSH_KEY_PRIMARY}" >/dev/null &&
|
||||
ssh-keygen -t rsa -b 2048 -N "" -C "git rsa2048 key" -f "${GPGSSH_KEY_SECONDARY}" >/dev/null &&
|
||||
|
|
|
|||
|
|
@ -1037,6 +1037,7 @@ integration_tests = [
|
|||
't9303-fast-import-compression.sh',
|
||||
't9304-fast-import-marks.sh',
|
||||
't9305-fast-import-signatures.sh',
|
||||
't9306-fast-import-signed-tags.sh',
|
||||
't9350-fast-export.sh',
|
||||
't9351-fast-export-anonymize.sh',
|
||||
't9400-git-cvsserver-server.sh',
|
||||
|
|
|
|||
|
|
@ -0,0 +1,80 @@
|
|||
#!/bin/sh
|
||||
|
||||
test_description='git fast-import --signed-tags=<mode>'
|
||||
|
||||
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
|
||||
|
||||
. ./test-lib.sh
|
||||
. "$TEST_DIRECTORY/lib-gpg.sh"
|
||||
|
||||
test_expect_success 'set up unsigned initial commit and import repo' '
|
||||
test_commit first &&
|
||||
git init new
|
||||
'
|
||||
|
||||
test_expect_success 'import no signed tag with --signed-tags=abort' '
|
||||
git fast-export --signed-tags=verbatim >output &&
|
||||
git -C new fast-import --quiet --signed-tags=abort <output
|
||||
'
|
||||
|
||||
test_expect_success GPG 'set up OpenPGP signed tag' '
|
||||
git tag -s -m "OpenPGP signed tag" openpgp-signed first &&
|
||||
OPENPGP_SIGNED=$(git rev-parse --verify refs/tags/openpgp-signed) &&
|
||||
git fast-export --signed-tags=verbatim openpgp-signed >output
|
||||
'
|
||||
|
||||
test_expect_success GPG 'import OpenPGP signed tag with --signed-tags=abort' '
|
||||
test_must_fail git -C new fast-import --quiet --signed-tags=abort <output
|
||||
'
|
||||
|
||||
test_expect_success GPG 'import OpenPGP signed tag with --signed-tags=verbatim' '
|
||||
git -C new fast-import --quiet --signed-tags=verbatim <output >log 2>&1 &&
|
||||
IMPORTED=$(git -C new rev-parse --verify refs/tags/openpgp-signed) &&
|
||||
test $OPENPGP_SIGNED = $IMPORTED &&
|
||||
test_must_be_empty log
|
||||
'
|
||||
|
||||
test_expect_success GPGSM 'setup X.509 signed tag' '
|
||||
test_config gpg.format x509 &&
|
||||
test_config user.signingkey $GIT_COMMITTER_EMAIL &&
|
||||
|
||||
git tag -s -m "X.509 signed tag" x509-signed first &&
|
||||
X509_SIGNED=$(git rev-parse --verify refs/tags/x509-signed) &&
|
||||
git fast-export --signed-tags=verbatim x509-signed >output
|
||||
'
|
||||
|
||||
test_expect_success GPGSM 'import X.509 signed tag with --signed-tags=warn-strip' '
|
||||
git -C new fast-import --quiet --signed-tags=warn-strip <output >log 2>&1 &&
|
||||
test_grep "stripping a tag signature for tag '\''x509-signed'\''" log &&
|
||||
IMPORTED=$(git -C new rev-parse --verify refs/tags/x509-signed) &&
|
||||
test $X509_SIGNED != $IMPORTED &&
|
||||
git -C new cat-file -p x509-signed >out &&
|
||||
test_grep ! "SIGNED MESSAGE" out
|
||||
'
|
||||
|
||||
test_expect_success GPGSSH 'setup SSH signed tag' '
|
||||
test_config gpg.format ssh &&
|
||||
test_config user.signingkey "${GPGSSH_KEY_PRIMARY}" &&
|
||||
|
||||
git tag -s -m "SSH signed tag" ssh-signed first &&
|
||||
SSH_SIGNED=$(git rev-parse --verify refs/tags/ssh-signed) &&
|
||||
git fast-export --signed-tags=verbatim ssh-signed >output
|
||||
'
|
||||
|
||||
test_expect_success GPGSSH 'import SSH signed tag with --signed-tags=warn-verbatim' '
|
||||
git -C new fast-import --quiet --signed-tags=warn-verbatim <output >log 2>&1 &&
|
||||
test_grep "importing a tag signature verbatim for tag '\''ssh-signed'\''" log &&
|
||||
IMPORTED=$(git -C new rev-parse --verify refs/tags/ssh-signed) &&
|
||||
test $SSH_SIGNED = $IMPORTED
|
||||
'
|
||||
|
||||
test_expect_success GPGSSH 'import SSH signed tag with --signed-tags=strip' '
|
||||
git -C new fast-import --quiet --signed-tags=strip <output >log 2>&1 &&
|
||||
test_must_be_empty log &&
|
||||
IMPORTED=$(git -C new rev-parse --verify refs/tags/ssh-signed) &&
|
||||
test $SSH_SIGNED != $IMPORTED &&
|
||||
git -C new cat-file -p ssh-signed >out &&
|
||||
test_grep ! "SSH SIGNATURE" out
|
||||
'
|
||||
|
||||
test_done
|
||||
|
|
@ -35,6 +35,7 @@ test_expect_success 'setup' '
|
|||
git commit -m sitzt file2 &&
|
||||
test_tick &&
|
||||
git tag -a -m valentin muss &&
|
||||
ANNOTATED_TAG_COUNT=1 &&
|
||||
git merge -s ours main
|
||||
|
||||
'
|
||||
|
|
@ -229,7 +230,8 @@ EOF
|
|||
|
||||
test_expect_success 'set up faked signed tag' '
|
||||
|
||||
git fast-import <signed-tag-import
|
||||
git fast-import <signed-tag-import &&
|
||||
ANNOTATED_TAG_COUNT=$((ANNOTATED_TAG_COUNT + 1))
|
||||
|
||||
'
|
||||
|
||||
|
|
@ -277,6 +279,42 @@ test_expect_success 'signed-tags=warn-strip' '
|
|||
test -s err
|
||||
'
|
||||
|
||||
test_expect_success GPGSM 'setup X.509 signed tag' '
|
||||
test_config gpg.format x509 &&
|
||||
test_config user.signingkey $GIT_COMMITTER_EMAIL &&
|
||||
|
||||
git tag -s -m "X.509 signed tag" x509-signed $(git rev-parse HEAD) &&
|
||||
ANNOTATED_TAG_COUNT=$((ANNOTATED_TAG_COUNT + 1))
|
||||
'
|
||||
|
||||
test_expect_success GPGSM 'signed-tags=verbatim with X.509' '
|
||||
git fast-export --signed-tags=verbatim x509-signed > output &&
|
||||
test_grep "SIGNED MESSAGE" output
|
||||
'
|
||||
|
||||
test_expect_success GPGSM 'signed-tags=strip with X.509' '
|
||||
git fast-export --signed-tags=strip x509-signed > output &&
|
||||
test_grep ! "SIGNED MESSAGE" output
|
||||
'
|
||||
|
||||
test_expect_success GPGSSH 'setup SSH signed tag' '
|
||||
test_config gpg.format ssh &&
|
||||
test_config user.signingkey "${GPGSSH_KEY_PRIMARY}" &&
|
||||
|
||||
git tag -s -m "SSH signed tag" ssh-signed $(git rev-parse HEAD) &&
|
||||
ANNOTATED_TAG_COUNT=$((ANNOTATED_TAG_COUNT + 1))
|
||||
'
|
||||
|
||||
test_expect_success GPGSSH 'signed-tags=verbatim with SSH' '
|
||||
git fast-export --signed-tags=verbatim ssh-signed > output &&
|
||||
test_grep "SSH SIGNATURE" output
|
||||
'
|
||||
|
||||
test_expect_success GPGSSH 'signed-tags=strip with SSH' '
|
||||
git fast-export --signed-tags=strip ssh-signed > output &&
|
||||
test_grep ! "SSH SIGNATURE" output
|
||||
'
|
||||
|
||||
test_expect_success GPG 'set up signed commit' '
|
||||
|
||||
# Generate a commit with both "gpgsig" and "encoding" set, so
|
||||
|
|
@ -491,8 +529,9 @@ test_expect_success 'fast-export -C -C | fast-import' '
|
|||
test_expect_success 'fast-export | fast-import when main is tagged' '
|
||||
|
||||
git tag -m msg last &&
|
||||
ANNOTATED_TAG_COUNT=$((ANNOTATED_TAG_COUNT + 1)) &&
|
||||
git fast-export -C -C --signed-tags=strip --all > output &&
|
||||
test $(grep -c "^tag " output) = 3
|
||||
test $(grep -c "^tag " output) = $ANNOTATED_TAG_COUNT
|
||||
|
||||
'
|
||||
|
||||
|
|
@ -506,12 +545,13 @@ test_expect_success 'cope with tagger-less tags' '
|
|||
|
||||
TAG=$(git hash-object --literally -t tag -w tag-content) &&
|
||||
git update-ref refs/tags/sonnenschein $TAG &&
|
||||
ANNOTATED_TAG_COUNT=$((ANNOTATED_TAG_COUNT + 1)) &&
|
||||
git fast-export -C -C --signed-tags=strip --all > output &&
|
||||
test $(grep -c "^tag " output) = 4 &&
|
||||
test $(grep -c "^tag " output) = $ANNOTATED_TAG_COUNT &&
|
||||
! grep "Unspecified Tagger" output &&
|
||||
git fast-export -C -C --signed-tags=strip --all \
|
||||
--fake-missing-tagger > output &&
|
||||
test $(grep -c "^tag " output) = 4 &&
|
||||
test $(grep -c "^tag " output) = $ANNOTATED_TAG_COUNT &&
|
||||
grep "Unspecified Tagger" output
|
||||
|
||||
'
|
||||
|
|
|
|||
Loading…
Reference in New Issue