[PATCH 2/2] fast-import: add mode to re-sign invalid commit signatures
- From
Justin Tobler <jltobler@gmail.com>
- Date
- Feb 23, 2026, 19:41 UTC
- Message-ID
- <20260223194146.3476768-3-jltobler@gmail.com>
- 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