Skip to content

Commit 4f94101

Browse files
committed
Merge branch 'cc/fast-import-strip-signed-tags' into seen
"git fast-import" is taught to handle signed tags, just like it recently learned to handle signed commits, in different ways. Comments? * 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 the GPGSM prereq first doc: git-tag: stop focussing on GPG signed tags
2 parents 808515f + a579a66 commit 4f94101

File tree

8 files changed

+224
-21
lines changed

8 files changed

+224
-21
lines changed

Documentation/git-fast-import.adoc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,11 @@ fast-import stream! This option is enabled automatically for
6666
remote-helpers that use the `import` capability, as they are
6767
already trusted to run their own code.
6868

69+
--signed-tags=(verbatim|warn-verbatim|warn-strip|strip|abort)::
70+
Specify how to handle signed tags. Behaves in the same way
71+
as the same option in linkgit:git-fast-export[1], except that
72+
default is 'verbatim' (instead of 'abort').
73+
6974
--signed-commits=(verbatim|warn-verbatim|warn-strip|strip|abort)::
7075
Specify how to handle signed commits. Behaves in the same way
7176
as the same option in linkgit:git-fast-export[1], except that

Documentation/git-tag.adoc

Lines changed: 35 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ git-tag(1)
33

44
NAME
55
----
6-
git-tag - Create, list, delete or verify a tag object signed with GPG
6+
git-tag - Create, list, delete or verify tags
77

88

99
SYNOPSIS
@@ -38,17 +38,18 @@ and `-a`, `-s`, and `-u <key-id>` are absent, `-a` is implied.
3838
Otherwise, a tag reference that points directly at the given object
3939
(i.e., a lightweight tag) is created.
4040

41-
A GnuPG signed tag object will be created when `-s` or `-u
42-
<key-id>` is used. When `-u <key-id>` is not used, the
43-
committer identity for the current user is used to find the
44-
GnuPG key for signing. The configuration variable `gpg.program`
45-
is used to specify custom GnuPG binary.
41+
A cryptographically signed tag object will be created when `-s` or
42+
`-u <key-id>` is used. The signing backend (GPG, X.509, SSH, etc.) is
43+
controlled by the `gpg.format` configuration variable, defaulting to
44+
OpenPGP. When `-u <key-id>` is not used, the committer identity for
45+
the current user is used to find the key for signing. The
46+
configuration variable `gpg.program` is used to specify a custom
47+
signing binary.
4648

4749
Tag objects (created with `-a`, `-s`, or `-u`) are called "annotated"
4850
tags; they contain a creation date, the tagger name and e-mail, a
49-
tagging message, and an optional GnuPG signature. Whereas a
50-
"lightweight" tag is simply a name for an object (usually a commit
51-
object).
51+
tagging message, and an optional signature. Whereas a "lightweight"
52+
tag is simply a name for an object (usually a commit object).
5253

5354
Annotated tags are meant for release while lightweight tags are meant
5455
for private or temporary object labels. For this reason, some git
@@ -68,14 +69,22 @@ OPTIONS
6869
The default behavior of tag GPG-signing is controlled by `tag.gpgSign`
6970
configuration variable if it exists, or disabled otherwise.
7071
See linkgit:git-config[1].
72+
Make a signed tag, using the default signing key. The signing
73+
backend used depends on the `gpg.format` configuration
74+
variable. The default key is determined by the backend. For
75+
GPG, it's based on the committer's email address, while for
76+
SSH it may be a specific key file or agent identity. See
77+
linkgit:git-config[1].
7178

7279
`--no-sign`::
7380
Override `tag.gpgSign` configuration variable that is
7481
set to force each and every tag to be signed.
7582

7683
`-u <key-id>`::
7784
`--local-user=<key-id>`::
78-
Make a GPG-signed tag, using the given key.
85+
Make a signed tag using the given key. The format of the
86+
<key-id> and the backend used depend on the `gpg.format`
87+
configuration variable. See linkgit:git-config[1].
7988

8089
`-f`::
8190
`--force`::
@@ -88,6 +97,7 @@ OPTIONS
8897
`-v`::
8998
`--verify`::
9099
Verify the GPG signature of the given tag names.
100+
Verify the signature of the given tag names.
91101

92102
`-n<num>`::
93103
_<num>_ specifies how many lines from the annotation, if any,
@@ -235,12 +245,25 @@ it in the repository configuration as follows:
235245

236246
-------------------------------------
237247
[user]
238-
signingKey = <gpg-key-id>
248+
signingKey = <key-id>
239249
-------------------------------------
240250

251+
The signing backend is controlled by the `gpg.format` configuration
252+
variable, which defaults to `openpgp` for GPG signing. To sign tags
253+
using other technologies like X.509 or SSH, set this variable to
254+
`x509` or `ssh` respectively.
255+
256+
You can also specify the path to the signing program for each
257+
format. The `gpg.program` variable (or its synonym
258+
`gpg.openpgp.program`) is used for the OpenPGP backend. For other
259+
backends, the configuration is `gpg.<format>.program`, for example
260+
`gpg.ssh.program` for SSH signing.
261+
241262
`pager.tag` is only respected when listing tags, i.e., when `-l` is
242263
used or implied. The default is to use a pager.
243-
See linkgit:git-config[1].
264+
265+
See linkgit:git-config[1] for more details and other configuration
266+
variables.
244267

245268
DISCUSSION
246269
----------

builtin/fast-export.c

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -931,9 +931,8 @@ static void handle_tag(const char *name, struct tag *tag)
931931

932932
/* handle signed tags */
933933
if (message) {
934-
const char *signature = strstr(message,
935-
"\n-----BEGIN PGP SIGNATURE-----\n");
936-
if (signature)
934+
size_t sig_offset = parse_signed_buffer(message, message_size);
935+
if (sig_offset < message_size)
937936
switch (signed_tag_mode) {
938937
case SIGN_ABORT:
939938
die("encountered signed tag %s; use "
@@ -950,7 +949,7 @@ static void handle_tag(const char *name, struct tag *tag)
950949
oid_to_hex(&tag->object.oid));
951950
/* fallthru */
952951
case SIGN_STRIP:
953-
message_size = signature + 1 - message;
952+
message_size = sig_offset;
954953
break;
955954
}
956955
}

builtin/fast-import.c

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,7 @@ static int global_argc;
188188
static const char **global_argv;
189189
static const char *global_prefix;
190190

191+
static enum sign_mode signed_tag_mode = SIGN_VERBATIM;
191192
static enum sign_mode signed_commit_mode = SIGN_VERBATIM;
192193

193194
/* Memory pools */
@@ -2963,6 +2964,43 @@ static void parse_new_commit(const char *arg)
29632964
b->last_commit = object_count_by_type[OBJ_COMMIT];
29642965
}
29652966

2967+
static void handle_tag_signature(struct strbuf *msg, const char *name)
2968+
{
2969+
size_t sig_offset = parse_signed_buffer(msg->buf, msg->len);
2970+
2971+
/* If there is no signature, there is nothing to do. */
2972+
if (sig_offset >= msg->len)
2973+
return;
2974+
2975+
switch (signed_tag_mode) {
2976+
2977+
/* First, modes that don't change anything */
2978+
case SIGN_ABORT:
2979+
die("encountered signed tag; use "
2980+
"--signed-tags=<mode> to handle it");
2981+
case SIGN_WARN_VERBATIM:
2982+
warning(_("importing a tag signature verbatim for tag '%s'"), name);
2983+
/* fallthru */
2984+
case SIGN_VERBATIM:
2985+
/* Nothing to do, the signature will be put into the imported tag. */
2986+
break;
2987+
2988+
/* Second, modes that remove the signature */
2989+
case SIGN_WARN_STRIP:
2990+
warning(_("stripping a tag signature for tag '%s'"), name);
2991+
/* fallthru */
2992+
case SIGN_STRIP:
2993+
/* Truncate the buffer to remove the signature */
2994+
strbuf_setlen(msg, sig_offset);
2995+
break;
2996+
2997+
/* Third, BUG */
2998+
default:
2999+
BUG("invalid signed_tag_mode value %d from tag '%s'",
3000+
signed_tag_mode, name);
3001+
}
3002+
}
3003+
29663004
static void parse_new_tag(const char *arg)
29673005
{
29683006
static struct strbuf msg = STRBUF_INIT;
@@ -3026,6 +3064,8 @@ static void parse_new_tag(const char *arg)
30263064
/* tag payload/message */
30273065
parse_data(&msg, 0, NULL);
30283066

3067+
handle_tag_signature(&msg, t->name);
3068+
30293069
/* build the tag object */
30303070
strbuf_reset(&new_data);
30313071

@@ -3546,6 +3586,9 @@ static int parse_one_option(const char *option)
35463586
} else if (skip_prefix(option, "signed-commits=", &option)) {
35473587
if (parse_sign_mode(option, &signed_commit_mode))
35483588
usagef(_("unknown --signed-commits mode '%s'"), option);
3589+
} else if (skip_prefix(option, "signed-tags=", &option)) {
3590+
if (parse_sign_mode(option, &signed_tag_mode))
3591+
usagef(_("unknown --signed-tags mode '%s'"), option);
35493592
} else if (!strcmp(option, "quiet")) {
35503593
show_stats = 0;
35513594
quiet = 1;

t/lib-gpg.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ test_lazy_prereq GPG '
3838
# To export ownertrust:
3939
# gpg --homedir /tmp/gpghome --export-ownertrust \
4040
# > lib-gpg/ownertrust
41-
mkdir "$GNUPGHOME" &&
41+
mkdir -p "$GNUPGHOME" &&
4242
chmod 0700 "$GNUPGHOME" &&
4343
(gpgconf --kill all || : ) &&
4444
gpg --homedir "${GNUPGHOME}" --import \

t/meson.build

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1042,6 +1042,7 @@ integration_tests = [
10421042
't9303-fast-import-compression.sh',
10431043
't9304-fast-import-marks.sh',
10441044
't9305-fast-import-signatures.sh',
1045+
't9306-fast-import-signed-tags.sh',
10451046
't9350-fast-export.sh',
10461047
't9351-fast-export-anonymize.sh',
10471048
't9400-git-cvsserver-server.sh',

t/t9306-fast-import-signed-tags.sh

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
#!/bin/sh
2+
3+
test_description='git fast-import --signed-tags=<mode>'
4+
5+
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
6+
7+
. ./test-lib.sh
8+
. "$TEST_DIRECTORY/lib-gpg.sh"
9+
10+
test_expect_success 'set up unsigned initial commit and import repo' '
11+
test_commit first &&
12+
git init new
13+
'
14+
15+
test_expect_success 'import no signed tag with --signed-tags=abort' '
16+
git fast-export --signed-tags=verbatim >output &&
17+
git -C new fast-import --quiet --signed-tags=abort <output
18+
'
19+
20+
test_expect_success GPG 'set up OpenPGP signed tag' '
21+
git tag -s -m "OpenPGP signed tag" openpgp-signed first &&
22+
OPENPGP_SIGNED=$(git rev-parse --verify refs/tags/openpgp-signed) &&
23+
git fast-export --signed-tags=verbatim openpgp-signed >output
24+
'
25+
26+
test_expect_success GPG 'import OpenPGP signed tag with --signed-tags=abort' '
27+
test_must_fail git -C new fast-import --quiet --signed-tags=abort <output
28+
'
29+
30+
test_expect_success GPG 'import OpenPGP signed tag with --signed-tags=verbatim' '
31+
git -C new fast-import --quiet --signed-tags=verbatim <output >log 2>&1 &&
32+
IMPORTED=$(git -C new rev-parse --verify refs/tags/openpgp-signed) &&
33+
test $OPENPGP_SIGNED = $IMPORTED &&
34+
test_must_be_empty log
35+
'
36+
37+
test_expect_success GPGSM 'setup X.509 signed tag' '
38+
test_config gpg.format x509 &&
39+
test_config user.signingkey $GIT_COMMITTER_EMAIL &&
40+
41+
git tag -s -m "X.509 signed tag" x509-signed first &&
42+
X509_SIGNED=$(git rev-parse --verify refs/tags/x509-signed) &&
43+
git fast-export --signed-tags=verbatim x509-signed >output
44+
'
45+
46+
test_expect_success GPGSM 'import X.509 signed tag with --signed-tags=warn-strip' '
47+
git -C new fast-import --quiet --signed-tags=warn-strip <output >log 2>&1 &&
48+
test_grep "stripping a tag signature for tag '\''x509-signed'\''" log &&
49+
IMPORTED=$(git -C new rev-parse --verify refs/tags/x509-signed) &&
50+
test $X509_SIGNED != $IMPORTED &&
51+
git -C new cat-file -p x509-signed >out &&
52+
test_grep ! "SIGNED MESSAGE" out
53+
'
54+
55+
test_expect_success GPGSSH 'setup SSH signed tag' '
56+
test_config gpg.format ssh &&
57+
test_config user.signingkey "${GPGSSH_KEY_PRIMARY}" &&
58+
59+
git tag -s -m "SSH signed tag" ssh-signed first &&
60+
SSH_SIGNED=$(git rev-parse --verify refs/tags/ssh-signed) &&
61+
git fast-export --signed-tags=verbatim ssh-signed >output
62+
'
63+
64+
test_expect_success GPGSSH 'import SSH signed tag with --signed-tags=warn-verbatim' '
65+
git -C new fast-import --quiet --signed-tags=warn-verbatim <output >log 2>&1 &&
66+
test_grep "importing a tag signature verbatim for tag '\''ssh-signed'\''" log &&
67+
IMPORTED=$(git -C new rev-parse --verify refs/tags/ssh-signed) &&
68+
test $SSH_SIGNED = $IMPORTED
69+
'
70+
71+
test_expect_success GPGSSH 'import SSH signed tag with --signed-tags=strip' '
72+
git -C new fast-import --quiet --signed-tags=strip <output >log 2>&1 &&
73+
test_must_be_empty log &&
74+
IMPORTED=$(git -C new rev-parse --verify refs/tags/ssh-signed) &&
75+
test $SSH_SIGNED != $IMPORTED &&
76+
git -C new cat-file -p ssh-signed >out &&
77+
test_grep ! "SSH SIGNATURE" out
78+
'
79+
80+
test_done

t/t9350-fast-export.sh

Lines changed: 56 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ test_expect_success 'setup' '
3535
git commit -m sitzt file2 &&
3636
test_tick &&
3737
git tag -a -m valentin muss &&
38+
ANNOTATED_TAG_COUNT=1 &&
3839
git merge -s ours main
3940
4041
'
@@ -229,7 +230,8 @@ EOF
229230

230231
test_expect_success 'set up faked signed tag' '
231232
232-
git fast-import <signed-tag-import
233+
git fast-import <signed-tag-import &&
234+
ANNOTATED_TAG_COUNT=$((ANNOTATED_TAG_COUNT + 1))
233235
234236
'
235237

@@ -277,6 +279,54 @@ test_expect_success 'signed-tags=warn-strip' '
277279
test -s err
278280
'
279281

282+
test_expect_success GPGSM 'setup X.509 signed tag' '
283+
284+
test_config gpg.format x509 &&
285+
test_config user.signingkey $GIT_COMMITTER_EMAIL &&
286+
287+
git tag -s -m "X.509 signed tag" x509-signed $(git rev-parse HEAD) &&
288+
ANNOTATED_TAG_COUNT=$((ANNOTATED_TAG_COUNT + 1))
289+
290+
'
291+
292+
test_expect_success GPGSM 'signed-tags=verbatim with X.509' '
293+
294+
git fast-export --signed-tags=verbatim x509-signed > output &&
295+
test_grep "SIGNED MESSAGE" output
296+
297+
'
298+
299+
test_expect_success GPGSM 'signed-tags=strip with X.509' '
300+
301+
git fast-export --signed-tags=strip x509-signed > output &&
302+
test_grep ! "SIGNED MESSAGE" output
303+
304+
'
305+
306+
test_expect_success GPGSSH 'setup SSH signed tag' '
307+
308+
test_config gpg.format ssh &&
309+
test_config user.signingkey "${GPGSSH_KEY_PRIMARY}" &&
310+
311+
git tag -s -m "SSH signed tag" ssh-signed $(git rev-parse HEAD) &&
312+
ANNOTATED_TAG_COUNT=$((ANNOTATED_TAG_COUNT + 1))
313+
314+
'
315+
316+
test_expect_success GPGSSH 'signed-tags=verbatim with SSH' '
317+
318+
git fast-export --signed-tags=verbatim ssh-signed > output &&
319+
test_grep "SSH SIGNATURE" output
320+
321+
'
322+
323+
test_expect_success GPGSSH 'signed-tags=strip with SSH' '
324+
325+
git fast-export --signed-tags=strip ssh-signed > output &&
326+
test_grep ! "SSH SIGNATURE" output
327+
328+
'
329+
280330
test_expect_success GPG 'set up signed commit' '
281331
282332
# Generate a commit with both "gpgsig" and "encoding" set, so
@@ -491,8 +541,9 @@ test_expect_success 'fast-export -C -C | fast-import' '
491541
test_expect_success 'fast-export | fast-import when main is tagged' '
492542
493543
git tag -m msg last &&
544+
ANNOTATED_TAG_COUNT=$((ANNOTATED_TAG_COUNT + 1)) &&
494545
git fast-export -C -C --signed-tags=strip --all > output &&
495-
test $(grep -c "^tag " output) = 3
546+
test $(grep -c "^tag " output) = $ANNOTATED_TAG_COUNT
496547
497548
'
498549

@@ -506,12 +557,13 @@ test_expect_success 'cope with tagger-less tags' '
506557
507558
TAG=$(git hash-object --literally -t tag -w tag-content) &&
508559
git update-ref refs/tags/sonnenschein $TAG &&
560+
ANNOTATED_TAG_COUNT=$((ANNOTATED_TAG_COUNT + 1)) &&
509561
git fast-export -C -C --signed-tags=strip --all > output &&
510-
test $(grep -c "^tag " output) = 4 &&
562+
test $(grep -c "^tag " output) = $ANNOTATED_TAG_COUNT &&
511563
! grep "Unspecified Tagger" output &&
512564
git fast-export -C -C --signed-tags=strip --all \
513565
--fake-missing-tagger > output &&
514-
test $(grep -c "^tag " output) = 4 &&
566+
test $(grep -c "^tag " output) = $ANNOTATED_TAG_COUNT &&
515567
grep "Unspecified Tagger" output
516568
517569
'

0 commit comments

Comments
 (0)