public inbox for git@vger.kernel.org
 help / color / mirror / Atom feed
From: Justin Tobler <jltobler@gmail.com>
To: git@vger.kernel.org
Cc: sandals@crustytoothpaste.net, christian.couder@gmail.com,
	Justin Tobler <jltobler@gmail.com>
Subject: [PATCH 2/2] fast-import: add mode to re-sign invalid commit signatures
Date: Mon, 23 Feb 2026 13:41:46 -0600	[thread overview]
Message-ID: <20260223194146.3476768-3-jltobler@gmail.com> (raw)
In-Reply-To: <20260223194146.3476768-1-jltobler@gmail.com>

With git-fast-import(1), handling of signed commits is controlled via
the `--signed-commits=<mode>` option. When an invalid signature is
encountered, a user may want the option to re-sign the commit as opposed
to just stripping the signature. To faciliate this, introduce a
"re-sign-if-invalid" mode for the `--signed-commits` option.

Note that commits are re-signed using only the repository object format
hash algorithm. If a commit has an additional signature due to the
`compatObjectFormat` repository extension being set, the other signature
is stripped.

Signed-off-by: Justin Tobler <jltobler@gmail.com>
---
 Documentation/git-fast-import.adoc |   3 +
 builtin/fast-export.c              |   6 ++
 builtin/fast-import.c              |  43 +++++++--
 gpg-interface.c                    |   2 +
 gpg-interface.h                    |   1 +
 t/t9305-fast-import-signatures.sh  | 142 +++++++++++++++--------------
 6 files changed, 125 insertions(+), 72 deletions(-)

diff --git a/Documentation/git-fast-import.adoc b/Documentation/git-fast-import.adoc
index 479c4081da..b902a6e2b0 100644
--- a/Documentation/git-fast-import.adoc
+++ b/Documentation/git-fast-import.adoc
@@ -86,6 +86,9 @@ already trusted to run their own code.
 * `strip-if-invalid` will check signatures and, if they are invalid,
   will strip them and display a warning. The validation is performed
   in the same way as linkgit:git-verify-commit[1] does it.
+* `re-sign-if-invalid` is the same as `strip-if-invalid`, but additionally the
+  commits with invalid signatures are signed again, so that old invalid
+  signatures are replaced with new valid ones.
 
 Options for Frontends
 ~~~~~~~~~~~~~~~~~~~~~
diff --git a/builtin/fast-export.c b/builtin/fast-export.c
index 0c5d2386d8..76fad1dec5 100644
--- a/builtin/fast-export.c
+++ b/builtin/fast-export.c
@@ -825,6 +825,9 @@ static void handle_commit(struct commit *commit, struct rev_info *rev,
 		case SIGN_STRIP_IF_INVALID:
 			die(_("'strip-if-invalid' is not a valid mode for "
 			      "git fast-export with --signed-commits=<mode>"));
+		case SIGN_RESIGN_IF_INVALID:
+			die(_("'re-sign-if-invalid' is not a valid mode for "
+			      "git fast-export with --signed-commits=<mode>"));
 		default:
 			BUG("invalid signed_commit_mode value %d", signed_commit_mode);
 		}
@@ -970,6 +973,9 @@ static void handle_tag(const char *name, struct tag *tag)
 			case SIGN_STRIP_IF_INVALID:
 				die(_("'strip-if-invalid' is not a valid mode for "
 				      "git fast-export with --signed-tags=<mode>"));
+			case SIGN_RESIGN_IF_INVALID:
+				die(_("'re-sign-if-invalid' is not a valid mode for "
+				      "git fast-export with --signed-tags=<mode>"));
 			default:
 				BUG("invalid signed_commit_mode value %d", signed_commit_mode);
 			}
diff --git a/builtin/fast-import.c b/builtin/fast-import.c
index b8a7757cfd..e34a373d2f 100644
--- a/builtin/fast-import.c
+++ b/builtin/fast-import.c
@@ -2836,10 +2836,11 @@ static void finalize_commit_buffer(struct strbuf *new_data,
 	strbuf_addbuf(new_data, msg);
 }
 
-static void handle_strip_if_invalid(struct strbuf *new_data,
-				    struct signature_data *sig_sha1,
-				    struct signature_data *sig_sha256,
-				    struct strbuf *msg)
+static void handle_invalid_signature(struct strbuf *new_data,
+				     struct signature_data *sig_sha1,
+				     struct signature_data *sig_sha256,
+				     struct strbuf *msg,
+				     enum sign_mode mode)
 {
 	struct strbuf tmp_buf = STRBUF_INIT;
 	struct signature_check signature_check = { 0 };
@@ -2866,6 +2867,30 @@ static void handle_strip_if_invalid(struct strbuf *new_data,
 			warning(_("stripping invalid signature for commit\n"
 				  "  allegedly by %s"), signer);
 
+		if (mode == SIGN_RESIGN_IF_INVALID) {
+			struct strbuf signature = STRBUF_INIT;
+			struct strbuf payload = STRBUF_INIT;
+			char *key = get_signing_key();
+
+			/*
+			 * Commits are resigned using the repository object
+			 * format hash algorithm only. Consequently if
+			 * extensions.compatObjectFormat is set, the
+			 * compatability hash is not currently used to
+			 * additionally sign the commit. If the commit payload
+			 * were reconstructed in the compatability format, it
+			 * would be possible to generate the other signature
+			 * accordingly though.
+			 */
+			strbuf_addstr(&payload, signature_check.payload);
+			sign_buffer(&payload, &signature, key);
+			add_header_signature(new_data, &signature, the_hash_algo);
+
+			strbuf_release(&signature);
+			strbuf_release(&payload);
+			free(key);
+		}
+
 		finalize_commit_buffer(new_data, NULL, NULL, msg);
 	} else {
 		strbuf_swap(new_data, &tmp_buf);
@@ -2927,6 +2952,7 @@ static void parse_new_commit(const char *arg)
 			/* fallthru */
 		case SIGN_VERBATIM:
 		case SIGN_STRIP_IF_INVALID:
+		case SIGN_RESIGN_IF_INVALID:
 			import_one_signature(&sig_sha1, &sig_sha256, v);
 			break;
 
@@ -3011,9 +3037,11 @@ static void parse_new_commit(const char *arg)
 			"encoding %s\n",
 			encoding);
 
-	if (signed_commit_mode == SIGN_STRIP_IF_INVALID &&
+	if ((signed_commit_mode == SIGN_STRIP_IF_INVALID ||
+	     signed_commit_mode == SIGN_RESIGN_IF_INVALID) &&
 	    (sig_sha1.hash_algo || sig_sha256.hash_algo))
-		handle_strip_if_invalid(&new_data, &sig_sha1, &sig_sha256, &msg);
+		handle_invalid_signature(&new_data, &sig_sha1, &sig_sha256, &msg,
+					 signed_commit_mode);
 	else
 		finalize_commit_buffer(&new_data, &sig_sha1, &sig_sha256, &msg);
 
@@ -3060,6 +3088,9 @@ static void handle_tag_signature(struct strbuf *msg, const char *name)
 	case SIGN_STRIP_IF_INVALID:
 		die(_("'strip-if-invalid' is not a valid mode for "
 		      "git fast-import with --signed-tags=<mode>"));
+	case SIGN_RESIGN_IF_INVALID:
+		die(_("'re-sign-if-invalid' is not a valid mode for "
+		      "git fast-import with --signed-tags=<mode>"));
 	default:
 		BUG("invalid signed_tag_mode value %d from tag '%s'",
 		    signed_tag_mode, name);
diff --git a/gpg-interface.c b/gpg-interface.c
index 87fb6605fb..e7eb42d9d6 100644
--- a/gpg-interface.c
+++ b/gpg-interface.c
@@ -1156,6 +1156,8 @@ int parse_sign_mode(const char *arg, enum sign_mode *mode)
 		*mode = SIGN_STRIP;
 	else if (!strcmp(arg, "strip-if-invalid"))
 		*mode = SIGN_STRIP_IF_INVALID;
+	else if (!strcmp(arg, "re-sign-if-invalid"))
+		*mode = SIGN_RESIGN_IF_INVALID;
 	else
 		return -1;
 	return 0;
diff --git a/gpg-interface.h b/gpg-interface.h
index 789d1ffac4..2ab2a21e1a 100644
--- a/gpg-interface.h
+++ b/gpg-interface.h
@@ -112,6 +112,7 @@ enum sign_mode {
 	SIGN_WARN_STRIP,
 	SIGN_STRIP,
 	SIGN_STRIP_IF_INVALID,
+	SIGN_RESIGN_IF_INVALID,
 };
 
 /*
diff --git a/t/t9305-fast-import-signatures.sh b/t/t9305-fast-import-signatures.sh
index 022dae02e4..b52fb75976 100755
--- a/t/t9305-fast-import-signatures.sh
+++ b/t/t9305-fast-import-signatures.sh
@@ -103,71 +103,81 @@ test_expect_success GPG 'strip both OpenPGP signatures with --signed-commits=war
 	test_line_count = 2 out
 '
 
-test_expect_success GPG 'import commit with no signature with --signed-commits=strip-if-invalid' '
-	git fast-export main >output &&
-	git -C new fast-import --quiet --signed-commits=strip-if-invalid <output >log 2>&1 &&
-	test_must_be_empty log
-'
-
-test_expect_success GPG 'keep valid OpenPGP signature with --signed-commits=strip-if-invalid' '
-	rm -rf new &&
-	git init new &&
-
-	git fast-export --signed-commits=verbatim openpgp-signing >output &&
-	git -C new fast-import --quiet --signed-commits=strip-if-invalid <output >log 2>&1 &&
-	IMPORTED=$(git -C new rev-parse --verify refs/heads/openpgp-signing) &&
-	test $OPENPGP_SIGNING = $IMPORTED &&
-	git -C new cat-file commit "$IMPORTED" >actual &&
-	test_grep -E "^gpgsig(-sha256)? " actual &&
-	test_must_be_empty log
-'
-
-test_expect_success GPG 'strip signature invalidated by message change with --signed-commits=strip-if-invalid' '
-	rm -rf new &&
-	git init new &&
-
-	git fast-export --signed-commits=verbatim openpgp-signing >output &&
-
-	# Change the commit message, which invalidates the signature.
-	# The commit message length should not change though, otherwise the
-	# corresponding `data <length>` command would have to be changed too.
-	sed "s/OpenPGP signed commit/OpenPGP forged commit/" output >modified &&
-
-	git -C new fast-import --quiet --signed-commits=strip-if-invalid <modified >log 2>&1 &&
-
-	IMPORTED=$(git -C new rev-parse --verify refs/heads/openpgp-signing) &&
-	test $OPENPGP_SIGNING != $IMPORTED &&
-	git -C new cat-file commit "$IMPORTED" >actual &&
-	test_grep ! -E "^gpgsig" actual &&
-	test_grep "stripping invalid signature" log
-'
-
-test_expect_success GPGSM 'keep valid X.509 signature with --signed-commits=strip-if-invalid' '
-	rm -rf new &&
-	git init new &&
-
-	git fast-export --signed-commits=verbatim x509-signing >output &&
-	git -C new fast-import --quiet --signed-commits=strip-if-invalid <output >log 2>&1 &&
-	IMPORTED=$(git -C new rev-parse --verify refs/heads/x509-signing) &&
-	test $X509_SIGNING = $IMPORTED &&
-	git -C new cat-file commit "$IMPORTED" >actual &&
-	test_grep -E "^gpgsig(-sha256)? " actual &&
-	test_must_be_empty log
-'
-
-test_expect_success GPGSSH 'keep valid SSH signature with --signed-commits=strip-if-invalid' '
-	rm -rf new &&
-	git init new &&
-
-	test_config -C new gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" &&
-
-	git fast-export --signed-commits=verbatim ssh-signing >output &&
-	git -C new fast-import --quiet --signed-commits=strip-if-invalid <output >log 2>&1 &&
-	IMPORTED=$(git -C new rev-parse --verify refs/heads/ssh-signing) &&
-	test $SSH_SIGNING = $IMPORTED &&
-	git -C new cat-file commit "$IMPORTED" >actual &&
-	test_grep -E "^gpgsig(-sha256)? " actual &&
-	test_must_be_empty log
-'
+for mode in strip-if-invalid re-sign-if-invalid
+do
+	test_expect_success GPG "import commit with no signature with --signed-commits=$mode" '
+		git fast-export main >output &&
+		git -C new fast-import --quiet --signed-commits=$mode <output >log 2>&1 &&
+		test_must_be_empty log
+	'
+
+	test_expect_success GPG "keep valid OpenPGP signature with --signed-commits=$mode" '
+		rm -rf new &&
+		git init new &&
+
+		git fast-export --signed-commits=verbatim openpgp-signing >output &&
+		git -C new fast-import --quiet --signed-commits=$mode <output >log 2>&1 &&
+		IMPORTED=$(git -C new rev-parse --verify refs/heads/openpgp-signing) &&
+		test $OPENPGP_SIGNING = $IMPORTED &&
+		git -C new cat-file commit "$IMPORTED" >actual &&
+		test_grep -E "^gpgsig(-sha256)? " actual &&
+		test_must_be_empty log
+	'
+
+	test_expect_success GPG "strip signature invalidated by message change with --signed-commits=$mode" '
+		rm -rf new &&
+		git init new &&
+
+		git fast-export --signed-commits=verbatim openpgp-signing >output &&
+
+		# Change the commit message, which invalidates the signature.
+		# The commit message length should not change though, otherwise the
+		# corresponding `data <length>` command would have to be changed too.
+		sed "s/OpenPGP signed commit/OpenPGP forged commit/" output >modified &&
+
+		git -C new fast-import --quiet --signed-commits=$mode <modified >log 2>&1 &&
+
+		IMPORTED=$(git -C new rev-parse --verify refs/heads/openpgp-signing) &&
+		test $OPENPGP_SIGNING != $IMPORTED &&
+		git -C new cat-file commit "$IMPORTED" >actual &&
+		test_grep "stripping invalid signature" log &&
+
+		if test "$mode" = strip-if-invalid
+		then
+			test_grep ! -E "^gpgsig" actual
+		else
+			test_grep -E "^gpgsig(-sha256)? " actual &&
+			git -C new verify-commit "$IMPORTED"
+		fi
+	'
+
+	test_expect_success GPGSM "keep valid X.509 signature with --signed-commits=$mode" '
+		rm -rf new &&
+		git init new &&
+
+		git fast-export --signed-commits=verbatim x509-signing >output &&
+		git -C new fast-import --quiet --signed-commits=$mode <output >log 2>&1 &&
+		IMPORTED=$(git -C new rev-parse --verify refs/heads/x509-signing) &&
+		test $X509_SIGNING = $IMPORTED &&
+		git -C new cat-file commit "$IMPORTED" >actual &&
+		test_grep -E "^gpgsig(-sha256)? " actual &&
+		test_must_be_empty log
+	'
+
+	test_expect_success GPGSSH "keep valid SSH signature with --signed-commits=$mode" '
+		rm -rf new &&
+		git init new &&
+
+		test_config -C new gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" &&
+
+		git fast-export --signed-commits=verbatim ssh-signing >output &&
+		git -C new fast-import --quiet --signed-commits=$mode <output >log 2>&1 &&
+		IMPORTED=$(git -C new rev-parse --verify refs/heads/ssh-signing) &&
+		test $SSH_SIGNING = $IMPORTED &&
+		git -C new cat-file commit "$IMPORTED" >actual &&
+		test_grep -E "^gpgsig(-sha256)? " actual &&
+		test_must_be_empty log
+	'
+done
 
 test_done
-- 
2.53.0


  parent reply	other threads:[~2026-02-23 19:42 UTC|newest]

Thread overview: 60+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-02-23 19:41 [PATCH 0/2] fast-import: add mode to re-sign invalid commit signatures Justin Tobler
2026-02-23 19:41 ` [PATCH 1/2] commit: remove unused forward declaration Justin Tobler
2026-02-24  9:35   ` Patrick Steinhardt
2026-02-23 19:41 ` Justin Tobler [this message]
2026-02-24  9:33   ` [PATCH 2/2] fast-import: add mode to re-sign invalid commit signatures Patrick Steinhardt
2026-02-24 18:33     ` Justin Tobler
2026-02-24 13:40 ` [PATCH 0/2] " Christian Couder
2026-02-24 22:41 ` brian m. carlson
2026-02-24 22:45   ` Junio C Hamano
2026-03-02 22:49   ` Justin Tobler
2026-03-06 20:53 ` [PATCH v2 0/3] " Justin Tobler
2026-03-06 20:53   ` [PATCH v2 1/3] commit: remove unused forward declaration Justin Tobler
2026-03-06 20:53   ` [PATCH v2 2/3] gpg-interface: introduce sign_buffer_with_key() Justin Tobler
2026-03-10  9:01     ` Christian Couder
2026-03-10 18:04       ` Justin Tobler
2026-03-06 20:53   ` [PATCH v2 3/3] fast-import: add mode to re-sign invalid commit signatures Justin Tobler
2026-03-10  9:27     ` Christian Couder
2026-03-10 18:09       ` Justin Tobler
2026-03-10 20:11   ` [PATCH v3 0/3] " Justin Tobler
2026-03-10 20:11     ` [PATCH v3 1/3] commit: remove unused forward declaration Justin Tobler
2026-03-10 22:29       ` Junio C Hamano
2026-03-10 20:11     ` [PATCH v3 2/3] gpg-interface: introduce sign_buffer_with_key() Justin Tobler
2026-03-10 22:33       ` Junio C Hamano
2026-03-10 20:11     ` [PATCH v3 3/3] fast-import: add mode to re-sign invalid commit signatures Justin Tobler
2026-03-10 20:49     ` [PATCH v3 0/3] " Junio C Hamano
2026-03-10 21:06       ` Justin Tobler
2026-03-10 21:20         ` Junio C Hamano
2026-03-10 22:13           ` Justin Tobler
2026-03-10 22:39             ` Junio C Hamano
2026-03-10 23:03               ` Justin Tobler
2026-03-11 17:31     ` [PATCH v4 " Justin Tobler
2026-03-11 17:31       ` [PATCH v4 1/3] commit: remove unused forward declaration Justin Tobler
2026-03-11 17:31       ` [PATCH v4 2/3] gpg-interface: introduce sign_buffer_with_key() Justin Tobler
2026-03-12 10:22         ` Patrick Steinhardt
2026-03-12 13:58           ` Justin Tobler
2026-03-11 17:31       ` [PATCH v4 3/3] fast-import: add mode to sign commits with invalid signatures Justin Tobler
2026-03-12 10:23         ` Patrick Steinhardt
2026-03-12 14:08           ` Justin Tobler
2026-03-12 14:22             ` Patrick Steinhardt
2026-03-12 17:21               ` Justin Tobler
2026-03-12 19:22       ` [PATCH v5 0/3] fast-import: add mode to re-sign invalid commit signatures Justin Tobler
2026-03-12 19:22         ` [PATCH v5 1/3] commit: remove unused forward declaration Justin Tobler
2026-03-12 19:22         ` [PATCH v5 2/3] gpg-interface: allow sign_buffer() to use default signing key Justin Tobler
2026-03-12 20:20           ` Junio C Hamano
2026-03-12 20:24             ` Justin Tobler
2026-03-12 19:22         ` [PATCH v5 3/3] fast-import: add mode to sign commits with invalid signatures Justin Tobler
2026-03-12 20:20           ` Junio C Hamano
2026-03-12 20:29             ` Justin Tobler
2026-03-12 23:58           ` Jeff King
2026-03-13  0:17             ` Justin Tobler
2026-03-12 20:20         ` [PATCH v5 0/3] fast-import: add mode to re-sign invalid commit signatures Junio C Hamano
2026-03-12 20:30           ` Justin Tobler
2026-03-13  1:39         ` [PATCH v6 " Justin Tobler
2026-03-13  1:39           ` [PATCH v6 1/3] commit: remove unused forward declaration Justin Tobler
2026-03-13  1:39           ` [PATCH v6 2/3] gpg-interface: allow sign_buffer() to use default signing key Justin Tobler
2026-03-13  6:31             ` Patrick Steinhardt
2026-03-13  1:39           ` [PATCH v6 3/3] fast-import: add mode to sign commits with invalid signatures Justin Tobler
2026-03-13  6:31             ` Patrick Steinhardt
2026-03-13  4:29           ` [PATCH v6 0/3] fast-import: add mode to re-sign invalid commit signatures Junio C Hamano
2026-03-13  6:31             ` Patrick Steinhardt

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260223194146.3476768-3-jltobler@gmail.com \
    --to=jltobler@gmail.com \
    --cc=christian.couder@gmail.com \
    --cc=git@vger.kernel.org \
    --cc=sandals@crustytoothpaste.net \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox