Skip to content

Commit f1edda9

Browse files
committed
Merge branch 'jt/fast-import-signed-modes'
Handling of signed commits and tags in fast-import has been made more configurable. * jt/fast-import-signed-modes: fast-import: add 'abort-if-invalid' mode to '--signed-tags=<mode>' fast-import: add 'sign-if-invalid' mode to '--signed-tags=<mode>' fast-import: add 'strip-if-invalid' mode to '--signed-tags=<mode>' fast-import: add 'abort-if-invalid' mode to '--signed-commits=<mode>' fast-export: check for unsupported signing modes earlier
2 parents 1678b7d + ddd7c7a commit f1edda9

File tree

7 files changed

+197
-29
lines changed

7 files changed

+197
-29
lines changed

Documentation/git-fast-import.adoc

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -66,11 +66,10 @@ 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)`::
69+
`--signed-tags=<mode>`::
7070
Specify how to handle signed tags. Behaves in the same way as
71-
the `--signed-commits=<mode>` below, except that the
72-
`strip-if-invalid` mode is not yet supported. Like for signed
73-
commits, the default mode is `verbatim`.
71+
the `--signed-commits=<mode>` below. Like for signed commits,
72+
the default mode is `verbatim`.
7473

7574
`--signed-commits=<mode>`::
7675
Specify how to handle signed commits. The following <mode>s
@@ -90,6 +89,8 @@ already trusted to run their own code.
9089
commit signatures and replaces invalid signatures with newly created ones.
9190
Valid signatures are left unchanged. If `<keyid>` is provided, that key is
9291
used for signing; otherwise the configured default signing key is used.
92+
* `abort-if-invalid` will make this program die when encountering a signed
93+
commit that is unable to be verified.
9394

9495
Options for Frontends
9596
~~~~~~~~~~~~~~~~~~~~~

builtin/fast-export.c

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,8 @@ static int parse_opt_sign_mode(const struct option *opt,
6464
if (unset)
6565
return 0;
6666

67-
if (parse_sign_mode(arg, val, NULL))
67+
if (parse_sign_mode(arg, val, NULL) || (*val == SIGN_STRIP_IF_INVALID) ||
68+
(*val == SIGN_SIGN_IF_INVALID) || (*val == SIGN_ABORT_IF_INVALID))
6869
return error(_("unknown %s mode: %s"), opt->long_name, arg);
6970

7071
return 0;
@@ -822,12 +823,6 @@ static void handle_commit(struct commit *commit, struct rev_info *rev,
822823
die(_("encountered signed commit %s; use "
823824
"--signed-commits=<mode> to handle it"),
824825
oid_to_hex(&commit->object.oid));
825-
case SIGN_STRIP_IF_INVALID:
826-
die(_("'strip-if-invalid' is not a valid mode for "
827-
"git fast-export with --signed-commits=<mode>"));
828-
case SIGN_SIGN_IF_INVALID:
829-
die(_("'sign-if-invalid' is not a valid mode for "
830-
"git fast-export with --signed-commits=<mode>"));
831826
default:
832827
BUG("invalid signed_commit_mode value %d", signed_commit_mode);
833828
}
@@ -970,12 +965,6 @@ static void handle_tag(const char *name, struct tag *tag)
970965
die(_("encountered signed tag %s; use "
971966
"--signed-tags=<mode> to handle it"),
972967
oid_to_hex(&tag->object.oid));
973-
case SIGN_STRIP_IF_INVALID:
974-
die(_("'strip-if-invalid' is not a valid mode for "
975-
"git fast-export with --signed-tags=<mode>"));
976-
case SIGN_SIGN_IF_INVALID:
977-
die(_("'sign-if-invalid' is not a valid mode for "
978-
"git fast-export with --signed-tags=<mode>"));
979968
default:
980969
BUG("invalid signed_commit_mode value %d", signed_commit_mode);
981970
}

builtin/fast-import.c

Lines changed: 60 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,7 @@ static const char *global_prefix;
191191
static enum sign_mode signed_tag_mode = SIGN_VERBATIM;
192192
static enum sign_mode signed_commit_mode = SIGN_VERBATIM;
193193
static const char *signed_commit_keyid;
194+
static const char *signed_tag_keyid;
194195

195196
/* Memory pools */
196197
static struct mem_pool fi_mem_pool = {
@@ -2892,6 +2893,9 @@ static void handle_signature_if_invalid(struct strbuf *new_data,
28922893
ret = verify_commit_buffer(tmp_buf.buf, tmp_buf.len, &signature_check);
28932894

28942895
if (ret) {
2896+
if (mode == SIGN_ABORT_IF_INVALID)
2897+
die(_("aborting due to invalid signature"));
2898+
28952899
warn_invalid_signature(&signature_check, msg->buf, mode);
28962900

28972901
if (mode == SIGN_SIGN_IF_INVALID) {
@@ -2983,6 +2987,7 @@ static void parse_new_commit(const char *arg)
29832987
case SIGN_VERBATIM:
29842988
case SIGN_STRIP_IF_INVALID:
29852989
case SIGN_SIGN_IF_INVALID:
2990+
case SIGN_ABORT_IF_INVALID:
29862991
import_one_signature(&sig_sha1, &sig_sha256, v);
29872992
break;
29882993

@@ -3068,7 +3073,8 @@ static void parse_new_commit(const char *arg)
30683073
encoding);
30693074

30703075
if ((signed_commit_mode == SIGN_STRIP_IF_INVALID ||
3071-
signed_commit_mode == SIGN_SIGN_IF_INVALID) &&
3076+
signed_commit_mode == SIGN_SIGN_IF_INVALID ||
3077+
signed_commit_mode == SIGN_ABORT_IF_INVALID) &&
30723078
(sig_sha1.hash_algo || sig_sha256.hash_algo))
30733079
handle_signature_if_invalid(&new_data, &sig_sha1, &sig_sha256,
30743080
&msg, signed_commit_mode);
@@ -3084,7 +3090,50 @@ static void parse_new_commit(const char *arg)
30843090
b->last_commit = object_count_by_type[OBJ_COMMIT];
30853091
}
30863092

3087-
static void handle_tag_signature(struct strbuf *msg, const char *name)
3093+
static void handle_tag_signature_if_invalid(struct strbuf *buf,
3094+
struct strbuf *msg,
3095+
size_t sig_offset)
3096+
{
3097+
struct strbuf signature = STRBUF_INIT;
3098+
struct strbuf payload = STRBUF_INIT;
3099+
struct signature_check sigc = { 0 };
3100+
3101+
strbuf_addbuf(&payload, buf);
3102+
strbuf_addch(&payload, '\n');
3103+
strbuf_add(&payload, msg->buf, sig_offset);
3104+
strbuf_add(&signature, msg->buf + sig_offset, msg->len - sig_offset);
3105+
3106+
sigc.payload_type = SIGNATURE_PAYLOAD_TAG;
3107+
sigc.payload = strbuf_detach(&payload, &sigc.payload_len);
3108+
3109+
if (!check_signature(&sigc, signature.buf, signature.len))
3110+
goto out;
3111+
3112+
if (signed_tag_mode == SIGN_ABORT_IF_INVALID)
3113+
die(_("aborting due to invalid signature"));
3114+
3115+
strbuf_setlen(msg, sig_offset);
3116+
3117+
if (signed_tag_mode == SIGN_SIGN_IF_INVALID) {
3118+
strbuf_attach(&payload, sigc.payload, sigc.payload_len,
3119+
sigc.payload_len + 1);
3120+
sigc.payload = NULL;
3121+
strbuf_reset(&signature);
3122+
3123+
if (sign_buffer(&payload, &signature, signed_tag_keyid,
3124+
SIGN_BUFFER_USE_DEFAULT_KEY))
3125+
die(_("failed to sign tag object"));
3126+
3127+
strbuf_addbuf(msg, &signature);
3128+
}
3129+
3130+
out:
3131+
signature_check_clear(&sigc);
3132+
strbuf_release(&signature);
3133+
strbuf_release(&payload);
3134+
}
3135+
3136+
static void handle_tag_signature(struct strbuf *buf, struct strbuf *msg, const char *name)
30883137
{
30893138
size_t sig_offset = parse_signed_buffer(msg->buf, msg->len);
30903139

@@ -3110,17 +3159,16 @@ static void handle_tag_signature(struct strbuf *msg, const char *name)
31103159
/* Truncate the buffer to remove the signature */
31113160
strbuf_setlen(msg, sig_offset);
31123161
break;
3162+
case SIGN_ABORT_IF_INVALID:
3163+
case SIGN_SIGN_IF_INVALID:
3164+
case SIGN_STRIP_IF_INVALID:
3165+
handle_tag_signature_if_invalid(buf, msg, sig_offset);
3166+
break;
31133167

31143168
/* Third, aborting modes */
31153169
case SIGN_ABORT:
31163170
die(_("encountered signed tag; use "
31173171
"--signed-tags=<mode> to handle it"));
3118-
case SIGN_STRIP_IF_INVALID:
3119-
die(_("'strip-if-invalid' is not a valid mode for "
3120-
"git fast-import with --signed-tags=<mode>"));
3121-
case SIGN_SIGN_IF_INVALID:
3122-
die(_("'sign-if-invalid' is not a valid mode for "
3123-
"git fast-import with --signed-tags=<mode>"));
31243172
default:
31253173
BUG("invalid signed_tag_mode value %d from tag '%s'",
31263174
signed_tag_mode, name);
@@ -3190,8 +3238,6 @@ static void parse_new_tag(const char *arg)
31903238
/* tag payload/message */
31913239
parse_data(&msg, 0, NULL);
31923240

3193-
handle_tag_signature(&msg, t->name);
3194-
31953241
/* build the tag object */
31963242
strbuf_reset(&new_data);
31973243

@@ -3203,6 +3249,9 @@ static void parse_new_tag(const char *arg)
32033249
if (tagger)
32043250
strbuf_addf(&new_data,
32053251
"tagger %s\n", tagger);
3252+
3253+
handle_tag_signature(&new_data, &msg, t->name);
3254+
32063255
strbuf_addch(&new_data, '\n');
32073256
strbuf_addbuf(&new_data, &msg);
32083257
free(tagger);
@@ -3713,7 +3762,7 @@ static int parse_one_option(const char *option)
37133762
if (parse_sign_mode(option, &signed_commit_mode, &signed_commit_keyid))
37143763
usagef(_("unknown --signed-commits mode '%s'"), option);
37153764
} else if (skip_prefix(option, "signed-tags=", &option)) {
3716-
if (parse_sign_mode(option, &signed_tag_mode, NULL))
3765+
if (parse_sign_mode(option, &signed_tag_mode, &signed_tag_keyid))
37173766
usagef(_("unknown --signed-tags mode '%s'"), option);
37183767
} else if (!strcmp(option, "quiet")) {
37193768
show_stats = 0;

gpg-interface.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1164,6 +1164,8 @@ int parse_sign_mode(const char *arg, enum sign_mode *mode, const char **keyid)
11641164
*mode = SIGN_WARN_STRIP;
11651165
} else if (!strcmp(arg, "strip")) {
11661166
*mode = SIGN_STRIP;
1167+
} else if (!strcmp(arg, "abort-if-invalid")) {
1168+
*mode = SIGN_ABORT_IF_INVALID;
11671169
} else if (!strcmp(arg, "strip-if-invalid")) {
11681170
*mode = SIGN_STRIP_IF_INVALID;
11691171
} else if (!strcmp(arg, "sign-if-invalid")) {

gpg-interface.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ void print_signature_buffer(const struct signature_check *sigc,
115115
/* Modes for --signed-tags=<mode> and --signed-commits=<mode> options. */
116116
enum sign_mode {
117117
SIGN_ABORT,
118+
SIGN_ABORT_IF_INVALID,
118119
SIGN_WARN_VERBATIM,
119120
SIGN_VERBATIM,
120121
SIGN_WARN_STRIP,

t/t9305-fast-import-signatures.sh

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ test_expect_success RUST,GPG 'strip both OpenPGP signatures with --signed-commit
103103
test_line_count = 2 out
104104
'
105105

106-
for mode in strip-if-invalid sign-if-invalid
106+
for mode in strip-if-invalid sign-if-invalid abort-if-invalid
107107
do
108108
test_expect_success GPG "import commit with no signature with --signed-commits=$mode" '
109109
git fast-export main >output &&
@@ -135,6 +135,14 @@ do
135135
# corresponding `data <length>` command would have to be changed too.
136136
sed "s/OpenPGP signed commit/OpenPGP forged commit/" output >modified &&
137137
138+
if test "$mode" = abort-if-invalid
139+
then
140+
test_must_fail git -C new fast-import --quiet \
141+
--signed-commits=$mode <modified >log 2>&1 &&
142+
test_grep "aborting due to invalid signature" log &&
143+
return 0
144+
fi &&
145+
138146
git -C new fast-import --quiet --signed-commits=$mode <modified >log 2>&1 &&
139147
140148
IMPORTED=$(git -C new rev-parse --verify refs/heads/openpgp-signing) &&

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

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,4 +77,122 @@ test_expect_success GPGSSH 'import SSH signed tag with --signed-tags=strip' '
7777
test_grep ! "SSH SIGNATURE" out
7878
'
7979

80+
for mode in strip-if-invalid sign-if-invalid abort-if-invalid
81+
do
82+
test_expect_success GPG "import tag with no signature with --signed-tags=$mode" '
83+
test_when_finished rm -rf import &&
84+
git init import &&
85+
86+
git fast-export --signed-tags=verbatim >output &&
87+
git -C import fast-import --quiet --signed-tags=$mode <output >log 2>&1 &&
88+
test_must_be_empty log
89+
'
90+
91+
test_expect_success GPG "keep valid OpenPGP signature with --signed-tags=$mode" '
92+
test_when_finished rm -rf import &&
93+
git init import &&
94+
95+
git fast-export --signed-tags=verbatim openpgp-signed >output &&
96+
git -C import fast-import --quiet --signed-tags=$mode <output >log 2>&1 &&
97+
IMPORTED=$(git -C import rev-parse --verify refs/tags/openpgp-signed) &&
98+
test $OPENPGP_SIGNED = $IMPORTED &&
99+
git -C import cat-file tag "$IMPORTED" >actual &&
100+
test_grep -E "^-----BEGIN PGP SIGNATURE-----" actual &&
101+
test_must_be_empty log
102+
'
103+
104+
test_expect_success GPG "handle signature invalidated by message change with --signed-tags=$mode" '
105+
test_when_finished rm -rf import &&
106+
git init import &&
107+
108+
git fast-export --signed-tags=verbatim openpgp-signed >output &&
109+
110+
# Change the tag message, which invalidates the signature. The tag
111+
# message length should not change though, otherwise the corresponding
112+
# `data <length>` command would have to be changed too.
113+
sed "s/OpenPGP signed tag/OpenPGP forged tag/" output >modified &&
114+
115+
if test "$mode" = abort-if-invalid
116+
then
117+
test_must_fail git -C import fast-import --quiet \
118+
--signed-tags=$mode <modified >log 2>&1 &&
119+
test_grep "aborting due to invalid signature" log &&
120+
return 0
121+
fi &&
122+
123+
git -C import fast-import --quiet --signed-tags=$mode <modified >log 2>&1 &&
124+
125+
IMPORTED=$(git -C import rev-parse --verify refs/tags/openpgp-signed) &&
126+
test $OPENPGP_SIGNED != $IMPORTED &&
127+
git -C import cat-file tag "$IMPORTED" >actual &&
128+
129+
if test "$mode" = strip-if-invalid
130+
then
131+
test_grep ! -E "^-----BEGIN PGP SIGNATURE-----" actual
132+
else
133+
test_grep -E "^-----BEGIN PGP SIGNATURE-----" actual &&
134+
git -C import verify-tag "$IMPORTED"
135+
fi &&
136+
137+
test_must_be_empty log
138+
'
139+
140+
test_expect_success GPGSM "keep valid X.509 signature with --signed-tags=$mode" '
141+
test_when_finished rm -rf import &&
142+
git init import &&
143+
144+
git fast-export --signed-tags=verbatim x509-signed >output &&
145+
git -C import fast-import --quiet --signed-tags=$mode <output >log 2>&1 &&
146+
IMPORTED=$(git -C import rev-parse --verify refs/tags/x509-signed) &&
147+
test $X509_SIGNED = $IMPORTED &&
148+
git -C import cat-file tag x509-signed >actual &&
149+
test_grep -E "^-----BEGIN SIGNED MESSAGE-----" actual &&
150+
test_must_be_empty log
151+
'
152+
153+
test_expect_success GPGSSH "keep valid SSH signature with --signed-tags=$mode" '
154+
test_when_finished rm -rf import &&
155+
git init import &&
156+
157+
test_config -C import gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" &&
158+
159+
git fast-export --signed-tags=verbatim ssh-signed >output &&
160+
git -C import fast-import --quiet --signed-tags=$mode <output >log 2>&1 &&
161+
IMPORTED=$(git -C import rev-parse --verify refs/tags/ssh-signed) &&
162+
test $SSH_SIGNED = $IMPORTED &&
163+
git -C import cat-file tag ssh-signed >actual &&
164+
test_grep -E "^-----BEGIN SSH SIGNATURE-----" actual &&
165+
test_must_be_empty log
166+
'
167+
done
168+
169+
test_expect_success GPGSSH 'sign invalid tag with explicit keyid' '
170+
test_when_finished rm -rf import &&
171+
git init import &&
172+
173+
git fast-export --signed-tags=verbatim ssh-signed >output &&
174+
175+
# Change the tag message, which invalidates the signature. The tag
176+
# message length should not change though, otherwise the corresponding
177+
# `data <length>` command would have to be changed too.
178+
sed "s/SSH signed tag/SSH forged tag/" output >modified &&
179+
180+
# Configure the target repository with an invalid default signing key.
181+
test_config -C import user.signingkey "not-a-real-key-id" &&
182+
test_config -C import gpg.format ssh &&
183+
test_config -C import gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" &&
184+
test_must_fail git -C import fast-import --quiet \
185+
--signed-tags=sign-if-invalid <modified >/dev/null 2>&1 &&
186+
187+
# Import using explicitly provided signing key.
188+
git -C import fast-import --quiet \
189+
--signed-tags=sign-if-invalid="${GPGSSH_KEY_PRIMARY}" <modified &&
190+
191+
IMPORTED=$(git -C import rev-parse --verify refs/tags/ssh-signed) &&
192+
test $SSH_SIGNED != $IMPORTED &&
193+
git -C import cat-file tag "$IMPORTED" >actual &&
194+
test_grep -E "^-----BEGIN SSH SIGNATURE-----" actual &&
195+
git -C import verify-tag "$IMPORTED"
196+
'
197+
80198
test_done

0 commit comments

Comments
 (0)