From 4c36345e04cbef7edb94557119acba9f9a38c26f Mon Sep 17 00:00:00 2001 From: Justin Tobler Date: Thu, 26 Mar 2026 14:14:11 -0500 Subject: fast-import: add 'abort-if-invalid' mode to '--signed-commits=' The '--signed-commits=' option for git-fast-import(1) configures how signed commits are handled when encountered. In cases where an invalid commit signature is encountered, a user may wish to abort the operation entirely. Introduce an 'abort-if-invalid' mode to do so. Signed-off-by: Justin Tobler Signed-off-by: Junio C Hamano --- t/t9305-fast-import-signatures.sh | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) (limited to 't') diff --git a/t/t9305-fast-import-signatures.sh b/t/t9305-fast-import-signatures.sh index 18707b3f6c..5667693afd 100755 --- a/t/t9305-fast-import-signatures.sh +++ b/t/t9305-fast-import-signatures.sh @@ -103,7 +103,7 @@ test_expect_success RUST,GPG 'strip both OpenPGP signatures with --signed-commit test_line_count = 2 out ' -for mode in strip-if-invalid sign-if-invalid +for mode in strip-if-invalid sign-if-invalid abort-if-invalid do test_expect_success GPG "import commit with no signature with --signed-commits=$mode" ' git fast-export main >output && @@ -135,6 +135,14 @@ do # corresponding `data ` command would have to be changed too. sed "s/OpenPGP signed commit/OpenPGP forged commit/" output >modified && + if test "$mode" = abort-if-invalid + then + test_must_fail git -C new fast-import --quiet \ + --signed-commits=$mode log 2>&1 && + test_grep "aborting due to invalid signature" log && + return 0 + fi && + git -C new fast-import --quiet --signed-commits=$mode log 2>&1 && IMPORTED=$(git -C new rev-parse --verify refs/heads/openpgp-signing) && -- cgit v1.3 From 817b0428797742829b57538210ad8404b09f9cb1 Mon Sep 17 00:00:00 2001 From: Justin Tobler Date: Thu, 26 Mar 2026 14:14:12 -0500 Subject: fast-import: add 'strip-if-invalid' mode to '--signed-tags=' With c20f112e51 (fast-import: add 'strip-if-invalid' mode to --signed-commits=, 2025-11-17), git-fast-import(1) learned to verify commit signatures during import and strip signatures that fail verification. Extend the same behavior to signed tag objects by introducing a 'strip-if-invalid' mode for the '--signed-tags' option. Signed-off-by: Justin Tobler Signed-off-by: Junio C Hamano --- Documentation/git-fast-import.adoc | 7 ++-- builtin/fast-import.c | 40 +++++++++++++++++---- t/t9306-fast-import-signed-tags.sh | 73 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 110 insertions(+), 10 deletions(-) (limited to 't') diff --git a/Documentation/git-fast-import.adoc b/Documentation/git-fast-import.adoc index 288f2b2a7e..d68bc52b7e 100644 --- a/Documentation/git-fast-import.adoc +++ b/Documentation/git-fast-import.adoc @@ -66,11 +66,10 @@ 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)`:: +`--signed-tags=`:: Specify how to handle signed tags. Behaves in the same way as - the `--signed-commits=` below, except that the - `strip-if-invalid` mode is not yet supported. Like for signed - commits, the default mode is `verbatim`. + the `--signed-commits=` below. Like for signed commits, + the default mode is `verbatim`. `--signed-commits=`:: Specify how to handle signed commits. The following s diff --git a/builtin/fast-import.c b/builtin/fast-import.c index 08ea27242d..5e89829aea 100644 --- a/builtin/fast-import.c +++ b/builtin/fast-import.c @@ -3089,7 +3089,34 @@ 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) +static void handle_tag_signature_if_invalid(struct strbuf *buf, + struct strbuf *msg, + size_t sig_offset) +{ + struct strbuf signature = STRBUF_INIT; + struct strbuf payload = STRBUF_INIT; + struct signature_check sigc = { 0 }; + + strbuf_addbuf(&payload, buf); + strbuf_addch(&payload, '\n'); + strbuf_add(&payload, msg->buf, sig_offset); + strbuf_add(&signature, msg->buf + sig_offset, msg->len - sig_offset); + + sigc.payload_type = SIGNATURE_PAYLOAD_TAG; + sigc.payload = strbuf_detach(&payload, &sigc.payload_len); + + if (!check_signature(&sigc, signature.buf, signature.len)) + goto out; + + strbuf_setlen(msg, sig_offset); + +out: + signature_check_clear(&sigc); + strbuf_release(&signature); + strbuf_release(&payload); +} + +static void handle_tag_signature(struct strbuf *buf, struct strbuf *msg, const char *name) { size_t sig_offset = parse_signed_buffer(msg->buf, msg->len); @@ -3115,6 +3142,9 @@ static void handle_tag_signature(struct strbuf *msg, const char *name) /* Truncate the buffer to remove the signature */ strbuf_setlen(msg, sig_offset); break; + case SIGN_STRIP_IF_INVALID: + handle_tag_signature_if_invalid(buf, msg, sig_offset); + break; /* Third, aborting modes */ case SIGN_ABORT: @@ -3123,9 +3153,6 @@ static void handle_tag_signature(struct strbuf *msg, const char *name) case SIGN_ABORT_IF_INVALID: die(_("'abort-if-invalid' is not a valid mode for " "git fast-import with --signed-tags=")); - case SIGN_STRIP_IF_INVALID: - die(_("'strip-if-invalid' is not a valid mode for " - "git fast-import with --signed-tags=")); case SIGN_SIGN_IF_INVALID: die(_("'sign-if-invalid' is not a valid mode for " "git fast-import with --signed-tags=")); @@ -3198,8 +3225,6 @@ 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); @@ -3211,6 +3236,9 @@ static void parse_new_tag(const char *arg) if (tagger) strbuf_addf(&new_data, "tagger %s\n", tagger); + + handle_tag_signature(&new_data, &msg, t->name); + strbuf_addch(&new_data, '\n'); strbuf_addbuf(&new_data, &msg); free(tagger); diff --git a/t/t9306-fast-import-signed-tags.sh b/t/t9306-fast-import-signed-tags.sh index 363619e7d1..fd43b0b52a 100755 --- a/t/t9306-fast-import-signed-tags.sh +++ b/t/t9306-fast-import-signed-tags.sh @@ -77,4 +77,77 @@ test_expect_success GPGSSH 'import SSH signed tag with --signed-tags=strip' ' test_grep ! "SSH SIGNATURE" out ' +for mode in strip-if-invalid +do + test_expect_success GPG "import tag with no signature with --signed-tags=$mode" ' + test_when_finished rm -rf import && + git init import && + + git fast-export --signed-tags=verbatim >output && + git -C import fast-import --quiet --signed-tags=$mode log 2>&1 && + test_must_be_empty log + ' + + test_expect_success GPG "keep valid OpenPGP signature with --signed-tags=$mode" ' + test_when_finished rm -rf import && + git init import && + + git fast-export --signed-tags=verbatim openpgp-signed >output && + git -C import fast-import --quiet --signed-tags=$mode log 2>&1 && + IMPORTED=$(git -C import rev-parse --verify refs/tags/openpgp-signed) && + test $OPENPGP_SIGNED = $IMPORTED && + git -C import cat-file tag "$IMPORTED" >actual && + test_grep -E "^-----BEGIN PGP SIGNATURE-----" actual && + test_must_be_empty log + ' + + test_expect_success GPG "handle signature invalidated by message change with --signed-tags=$mode" ' + test_when_finished rm -rf import && + git init import && + + git fast-export --signed-tags=verbatim openpgp-signed >output && + + # Change the tag message, which invalidates the signature. The tag + # message length should not change though, otherwise the corresponding + # `data ` command would have to be changed too. + sed "s/OpenPGP signed tag/OpenPGP forged tag/" output >modified && + + git -C import fast-import --quiet --signed-tags=$mode log 2>&1 && + + IMPORTED=$(git -C import rev-parse --verify refs/tags/openpgp-signed) && + test $OPENPGP_SIGNED != $IMPORTED && + git -C import cat-file tag "$IMPORTED" >actual && + test_grep ! -E "^-----BEGIN PGP SIGNATURE-----" actual && + test_must_be_empty log + ' + + test_expect_success GPGSM "keep valid X.509 signature with --signed-tags=$mode" ' + test_when_finished rm -rf import && + git init import && + + git fast-export --signed-tags=verbatim x509-signed >output && + git -C import fast-import --quiet --signed-tags=$mode log 2>&1 && + IMPORTED=$(git -C import rev-parse --verify refs/tags/x509-signed) && + test $X509_SIGNED = $IMPORTED && + git -C import cat-file tag x509-signed >actual && + test_grep -E "^-----BEGIN SIGNED MESSAGE-----" actual && + test_must_be_empty log + ' + + test_expect_success GPGSSH "keep valid SSH signature with --signed-tags=$mode" ' + test_when_finished rm -rf import && + git init import && + + test_config -C import gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" && + + git fast-export --signed-tags=verbatim ssh-signed >output && + git -C import fast-import --quiet --signed-tags=$mode log 2>&1 && + IMPORTED=$(git -C import rev-parse --verify refs/tags/ssh-signed) && + test $SSH_SIGNED = $IMPORTED && + git -C import cat-file tag ssh-signed >actual && + test_grep -E "^-----BEGIN SSH SIGNATURE-----" actual && + test_must_be_empty log + ' +done + test_done -- cgit v1.3 From 2b1546c03cc3e02e51261fa38fe47a4f1b4e295b Mon Sep 17 00:00:00 2001 From: Justin Tobler Date: Thu, 26 Mar 2026 14:14:13 -0500 Subject: fast-import: add 'sign-if-invalid' mode to '--signed-tags=' With ee66c793f8 (fast-import: add mode to sign commits with invalid signatures, 2026-03-12), git-fast-import(1) learned to verify commit signatures during import and replace signatures that fail verification with a newly generated one. Extend the same behavior to signed tag objects by introducing a 'sign-if-invalid' mode for the '--signed-tags' option. Signed-off-by: Justin Tobler Signed-off-by: Junio C Hamano --- builtin/fast-import.c | 20 +++++++++++++++---- t/t9306-fast-import-signed-tags.sh | 41 ++++++++++++++++++++++++++++++++++++-- 2 files changed, 55 insertions(+), 6 deletions(-) (limited to 't') diff --git a/builtin/fast-import.c b/builtin/fast-import.c index 5e89829aea..783e0e7ab4 100644 --- a/builtin/fast-import.c +++ b/builtin/fast-import.c @@ -191,6 +191,7 @@ static const char *global_prefix; static enum sign_mode signed_tag_mode = SIGN_VERBATIM; static enum sign_mode signed_commit_mode = SIGN_VERBATIM; static const char *signed_commit_keyid; +static const char *signed_tag_keyid; /* Memory pools */ static struct mem_pool fi_mem_pool = { @@ -3110,6 +3111,19 @@ static void handle_tag_signature_if_invalid(struct strbuf *buf, strbuf_setlen(msg, sig_offset); + if (signed_tag_mode == SIGN_SIGN_IF_INVALID) { + strbuf_attach(&payload, sigc.payload, sigc.payload_len, + sigc.payload_len + 1); + sigc.payload = NULL; + strbuf_reset(&signature); + + if (sign_buffer(&payload, &signature, signed_tag_keyid, + SIGN_BUFFER_USE_DEFAULT_KEY)) + die(_("failed to sign tag object")); + + strbuf_addbuf(msg, &signature); + } + out: signature_check_clear(&sigc); strbuf_release(&signature); @@ -3142,6 +3156,7 @@ static void handle_tag_signature(struct strbuf *buf, struct strbuf *msg, const c /* Truncate the buffer to remove the signature */ strbuf_setlen(msg, sig_offset); break; + case SIGN_SIGN_IF_INVALID: case SIGN_STRIP_IF_INVALID: handle_tag_signature_if_invalid(buf, msg, sig_offset); break; @@ -3153,9 +3168,6 @@ static void handle_tag_signature(struct strbuf *buf, struct strbuf *msg, const c case SIGN_ABORT_IF_INVALID: die(_("'abort-if-invalid' is not a valid mode for " "git fast-import with --signed-tags=")); - case SIGN_SIGN_IF_INVALID: - die(_("'sign-if-invalid' is not a valid mode for " - "git fast-import with --signed-tags=")); default: BUG("invalid signed_tag_mode value %d from tag '%s'", signed_tag_mode, name); @@ -3749,7 +3761,7 @@ static int parse_one_option(const char *option) if (parse_sign_mode(option, &signed_commit_mode, &signed_commit_keyid)) usagef(_("unknown --signed-commits mode '%s'"), option); } else if (skip_prefix(option, "signed-tags=", &option)) { - if (parse_sign_mode(option, &signed_tag_mode, NULL)) + if (parse_sign_mode(option, &signed_tag_mode, &signed_tag_keyid)) usagef(_("unknown --signed-tags mode '%s'"), option); } else if (!strcmp(option, "quiet")) { show_stats = 0; diff --git a/t/t9306-fast-import-signed-tags.sh b/t/t9306-fast-import-signed-tags.sh index fd43b0b52a..bb4c8008ef 100755 --- a/t/t9306-fast-import-signed-tags.sh +++ b/t/t9306-fast-import-signed-tags.sh @@ -77,7 +77,7 @@ test_expect_success GPGSSH 'import SSH signed tag with --signed-tags=strip' ' test_grep ! "SSH SIGNATURE" out ' -for mode in strip-if-invalid +for mode in strip-if-invalid sign-if-invalid do test_expect_success GPG "import tag with no signature with --signed-tags=$mode" ' test_when_finished rm -rf import && @@ -117,7 +117,15 @@ do IMPORTED=$(git -C import rev-parse --verify refs/tags/openpgp-signed) && test $OPENPGP_SIGNED != $IMPORTED && git -C import cat-file tag "$IMPORTED" >actual && - test_grep ! -E "^-----BEGIN PGP SIGNATURE-----" actual && + + if test "$mode" = strip-if-invalid + then + test_grep ! -E "^-----BEGIN PGP SIGNATURE-----" actual + else + test_grep -E "^-----BEGIN PGP SIGNATURE-----" actual && + git -C import verify-tag "$IMPORTED" + fi && + test_must_be_empty log ' @@ -150,4 +158,33 @@ do ' done +test_expect_success GPGSSH 'sign invalid tag with explicit keyid' ' + test_when_finished rm -rf import && + git init import && + + git fast-export --signed-tags=verbatim ssh-signed >output && + + # Change the tag message, which invalidates the signature. The tag + # message length should not change though, otherwise the corresponding + # `data ` command would have to be changed too. + sed "s/SSH signed tag/SSH forged tag/" output >modified && + + # Configure the target repository with an invalid default signing key. + test_config -C import user.signingkey "not-a-real-key-id" && + test_config -C import gpg.format ssh && + test_config -C import gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" && + test_must_fail git -C import fast-import --quiet \ + --signed-tags=sign-if-invalid /dev/null 2>&1 && + + # Import using explicitly provided signing key. + git -C import fast-import --quiet \ + --signed-tags=sign-if-invalid="${GPGSSH_KEY_PRIMARY}" actual && + test_grep -E "^-----BEGIN SSH SIGNATURE-----" actual && + git -C import verify-tag "$IMPORTED" +' + test_done -- cgit v1.3 From ddd7c7ab12a25850e96f550567ef06fb9bea0cc0 Mon Sep 17 00:00:00 2001 From: Justin Tobler Date: Thu, 26 Mar 2026 14:14:14 -0500 Subject: fast-import: add 'abort-if-invalid' mode to '--signed-tags=' In git-fast-import(1), the 'abort-if-invalid' mode for the '--signed-commits' option verifies commit signatures during import and aborts the entire operation when verification fails. Extend the same behavior to signed tag objects by introducing an 'abort-if-invalid' mode for the '--signed-tags' option. Signed-off-by: Justin Tobler Signed-off-by: Junio C Hamano --- builtin/fast-import.c | 7 ++++--- t/t9306-fast-import-signed-tags.sh | 10 +++++++++- 2 files changed, 13 insertions(+), 4 deletions(-) (limited to 't') diff --git a/builtin/fast-import.c b/builtin/fast-import.c index 783e0e7ab4..cd1181023d 100644 --- a/builtin/fast-import.c +++ b/builtin/fast-import.c @@ -3109,6 +3109,9 @@ static void handle_tag_signature_if_invalid(struct strbuf *buf, if (!check_signature(&sigc, signature.buf, signature.len)) goto out; + if (signed_tag_mode == SIGN_ABORT_IF_INVALID) + die(_("aborting due to invalid signature")); + strbuf_setlen(msg, sig_offset); if (signed_tag_mode == SIGN_SIGN_IF_INVALID) { @@ -3156,6 +3159,7 @@ static void handle_tag_signature(struct strbuf *buf, struct strbuf *msg, const c /* Truncate the buffer to remove the signature */ strbuf_setlen(msg, sig_offset); break; + case SIGN_ABORT_IF_INVALID: case SIGN_SIGN_IF_INVALID: case SIGN_STRIP_IF_INVALID: handle_tag_signature_if_invalid(buf, msg, sig_offset); @@ -3165,9 +3169,6 @@ static void handle_tag_signature(struct strbuf *buf, struct strbuf *msg, const c case SIGN_ABORT: die(_("encountered signed tag; use " "--signed-tags= to handle it")); - case SIGN_ABORT_IF_INVALID: - die(_("'abort-if-invalid' is not a valid mode for " - "git fast-import with --signed-tags=")); default: BUG("invalid signed_tag_mode value %d from tag '%s'", signed_tag_mode, name); diff --git a/t/t9306-fast-import-signed-tags.sh b/t/t9306-fast-import-signed-tags.sh index bb4c8008ef..ec2b241cdb 100755 --- a/t/t9306-fast-import-signed-tags.sh +++ b/t/t9306-fast-import-signed-tags.sh @@ -77,7 +77,7 @@ test_expect_success GPGSSH 'import SSH signed tag with --signed-tags=strip' ' test_grep ! "SSH SIGNATURE" out ' -for mode in strip-if-invalid sign-if-invalid +for mode in strip-if-invalid sign-if-invalid abort-if-invalid do test_expect_success GPG "import tag with no signature with --signed-tags=$mode" ' test_when_finished rm -rf import && @@ -112,6 +112,14 @@ do # `data ` command would have to be changed too. sed "s/OpenPGP signed tag/OpenPGP forged tag/" output >modified && + if test "$mode" = abort-if-invalid + then + test_must_fail git -C import fast-import --quiet \ + --signed-tags=$mode log 2>&1 && + test_grep "aborting due to invalid signature" log && + return 0 + fi && + git -C import fast-import --quiet --signed-tags=$mode log 2>&1 && IMPORTED=$(git -C import rev-parse --verify refs/tags/openpgp-signed) && -- cgit v1.3