All of lore.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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.