Skip to content

Commit ee66c79

Browse files
jltoblergitster
authored andcommitted
fast-import: add mode to sign commits with invalid signatures
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 sign the commit again as opposed to just stripping the signature. To facilitate this, introduce a "sign-if-invalid" mode for the `--signed-commits` option. Optionally, a key ID may be explicitly provided in the form `sign-if-invalid[=<keyid>]` to specify which signing key should be used when signing invalid commit signatures. Note that to properly support interoperability mode when signing commit signatures, the commit buffer must be created in both the repository and compatability object formats to generate the appropriate signatures accordingly. As currently implemented, the commit buffer for the compatability object format is not reconstructed and thus signing commits in interoperability mode is not yet supported. Support may be added in the future. Signed-off-by: Justin Tobler <jltobler@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
1 parent 86ebf87 commit ee66c79

File tree

6 files changed

+202
-85
lines changed

6 files changed

+202
-85
lines changed

Documentation/git-fast-import.adoc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,10 @@ already trusted to run their own code.
8686
* `strip-if-invalid` will check signatures and, if they are invalid,
8787
will strip them and display a warning. The validation is performed
8888
in the same way as linkgit:git-verify-commit[1] does it.
89+
* `sign-if-invalid[=<keyid>]`, similar to `strip-if-invalid`, verifies
90+
commit signatures and replaces invalid signatures with newly created ones.
91+
Valid signatures are left unchanged. If `<keyid>` is provided, that key is
92+
used for signing; otherwise the configured default signing key is used.
8993

9094
Options for Frontends
9195
~~~~~~~~~~~~~~~~~~~~~

builtin/fast-export.c

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

67-
if (parse_sign_mode(arg, val))
67+
if (parse_sign_mode(arg, val, NULL))
6868
return error(_("unknown %s mode: %s"), opt->long_name, arg);
6969

7070
return 0;
@@ -825,6 +825,9 @@ static void handle_commit(struct commit *commit, struct rev_info *rev,
825825
case SIGN_STRIP_IF_INVALID:
826826
die(_("'strip-if-invalid' is not a valid mode for "
827827
"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>"));
828831
default:
829832
BUG("invalid signed_commit_mode value %d", signed_commit_mode);
830833
}
@@ -970,6 +973,9 @@ static void handle_tag(const char *name, struct tag *tag)
970973
case SIGN_STRIP_IF_INVALID:
971974
die(_("'strip-if-invalid' is not a valid mode for "
972975
"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>"));
973979
default:
974980
BUG("invalid signed_commit_mode value %d", signed_commit_mode);
975981
}

builtin/fast-import.c

Lines changed: 79 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,7 @@ static const char *global_prefix;
190190

191191
static enum sign_mode signed_tag_mode = SIGN_VERBATIM;
192192
static enum sign_mode signed_commit_mode = SIGN_VERBATIM;
193+
static const char *signed_commit_keyid;
193194

194195
/* Memory pools */
195196
static struct mem_pool fi_mem_pool = {
@@ -2836,26 +2837,15 @@ static void finalize_commit_buffer(struct strbuf *new_data,
28362837
strbuf_addbuf(new_data, msg);
28372838
}
28382839

2839-
static void handle_strip_if_invalid(struct strbuf *new_data,
2840-
struct signature_data *sig_sha1,
2841-
struct signature_data *sig_sha256,
2842-
struct strbuf *msg)
2840+
static void warn_invalid_signature(struct signature_check *check,
2841+
const char *msg, enum sign_mode mode)
28432842
{
2844-
struct strbuf tmp_buf = STRBUF_INIT;
2845-
struct signature_check signature_check = { 0 };
2846-
int ret;
2847-
2848-
/* Check signature in a temporary commit buffer */
2849-
strbuf_addbuf(&tmp_buf, new_data);
2850-
finalize_commit_buffer(&tmp_buf, sig_sha1, sig_sha256, msg);
2851-
ret = verify_commit_buffer(tmp_buf.buf, tmp_buf.len, &signature_check);
2852-
2853-
if (ret) {
2854-
const char *signer = signature_check.signer ?
2855-
signature_check.signer : _("unknown");
2856-
const char *subject;
2857-
int subject_len = find_commit_subject(msg->buf, &subject);
2843+
const char *signer = check->signer ? check->signer : _("unknown");
2844+
const char *subject;
2845+
int subject_len = find_commit_subject(msg, &subject);
28582846

2847+
switch (mode) {
2848+
case SIGN_STRIP_IF_INVALID:
28592849
if (subject_len > 100)
28602850
warning(_("stripping invalid signature for commit '%.100s...'\n"
28612851
" allegedly by %s"), subject, signer);
@@ -2865,6 +2855,67 @@ static void handle_strip_if_invalid(struct strbuf *new_data,
28652855
else
28662856
warning(_("stripping invalid signature for commit\n"
28672857
" allegedly by %s"), signer);
2858+
break;
2859+
case SIGN_SIGN_IF_INVALID:
2860+
if (subject_len > 100)
2861+
warning(_("replacing invalid signature for commit '%.100s...'\n"
2862+
" allegedly by %s"), subject, signer);
2863+
else if (subject_len > 0)
2864+
warning(_("replacing invalid signature for commit '%.*s'\n"
2865+
" allegedly by %s"), subject_len, subject, signer);
2866+
else
2867+
warning(_("replacing invalid signature for commit\n"
2868+
" allegedly by %s"), signer);
2869+
break;
2870+
default:
2871+
BUG("unsupported signing mode");
2872+
}
2873+
}
2874+
2875+
static void handle_signature_if_invalid(struct strbuf *new_data,
2876+
struct signature_data *sig_sha1,
2877+
struct signature_data *sig_sha256,
2878+
struct strbuf *msg,
2879+
enum sign_mode mode)
2880+
{
2881+
struct strbuf tmp_buf = STRBUF_INIT;
2882+
struct signature_check signature_check = { 0 };
2883+
int ret;
2884+
2885+
/* Check signature in a temporary commit buffer */
2886+
strbuf_addbuf(&tmp_buf, new_data);
2887+
finalize_commit_buffer(&tmp_buf, sig_sha1, sig_sha256, msg);
2888+
ret = verify_commit_buffer(tmp_buf.buf, tmp_buf.len, &signature_check);
2889+
2890+
if (ret) {
2891+
warn_invalid_signature(&signature_check, msg->buf, mode);
2892+
2893+
if (mode == SIGN_SIGN_IF_INVALID) {
2894+
struct strbuf signature = STRBUF_INIT;
2895+
struct strbuf payload = STRBUF_INIT;
2896+
2897+
/*
2898+
* NEEDSWORK: To properly support interoperability mode
2899+
* when signing commit signatures, the commit buffer
2900+
* must be provided in both the repository and
2901+
* compatibility object formats. As currently
2902+
* implemented, only the repository object format is
2903+
* considered meaning compatibility signatures cannot be
2904+
* generated. Thus, attempting to sign commit signatures
2905+
* in interoperability mode is currently unsupported.
2906+
*/
2907+
if (the_repository->compat_hash_algo)
2908+
die(_("signing commits in interoperability mode is unsupported"));
2909+
2910+
strbuf_addstr(&payload, signature_check.payload);
2911+
if (sign_buffer(&payload, &signature, signed_commit_keyid,
2912+
SIGN_BUFFER_USE_DEFAULT_KEY))
2913+
die(_("failed to sign commit object"));
2914+
add_header_signature(new_data, &signature, the_hash_algo);
2915+
2916+
strbuf_release(&signature);
2917+
strbuf_release(&payload);
2918+
}
28682919

28692920
finalize_commit_buffer(new_data, NULL, NULL, msg);
28702921
} else {
@@ -2927,6 +2978,7 @@ static void parse_new_commit(const char *arg)
29272978
/* fallthru */
29282979
case SIGN_VERBATIM:
29292980
case SIGN_STRIP_IF_INVALID:
2981+
case SIGN_SIGN_IF_INVALID:
29302982
import_one_signature(&sig_sha1, &sig_sha256, v);
29312983
break;
29322984

@@ -3011,9 +3063,11 @@ static void parse_new_commit(const char *arg)
30113063
"encoding %s\n",
30123064
encoding);
30133065

3014-
if (signed_commit_mode == SIGN_STRIP_IF_INVALID &&
3066+
if ((signed_commit_mode == SIGN_STRIP_IF_INVALID ||
3067+
signed_commit_mode == SIGN_SIGN_IF_INVALID) &&
30153068
(sig_sha1.hash_algo || sig_sha256.hash_algo))
3016-
handle_strip_if_invalid(&new_data, &sig_sha1, &sig_sha256, &msg);
3069+
handle_signature_if_invalid(&new_data, &sig_sha1, &sig_sha256,
3070+
&msg, signed_commit_mode);
30173071
else
30183072
finalize_commit_buffer(&new_data, &sig_sha1, &sig_sha256, &msg);
30193073

@@ -3060,6 +3114,9 @@ static void handle_tag_signature(struct strbuf *msg, const char *name)
30603114
case SIGN_STRIP_IF_INVALID:
30613115
die(_("'strip-if-invalid' is not a valid mode for "
30623116
"git fast-import with --signed-tags=<mode>"));
3117+
case SIGN_SIGN_IF_INVALID:
3118+
die(_("'sign-if-invalid' is not a valid mode for "
3119+
"git fast-import with --signed-tags=<mode>"));
30633120
default:
30643121
BUG("invalid signed_tag_mode value %d from tag '%s'",
30653122
signed_tag_mode, name);
@@ -3649,10 +3706,10 @@ static int parse_one_option(const char *option)
36493706
} else if (skip_prefix(option, "export-pack-edges=", &option)) {
36503707
option_export_pack_edges(option);
36513708
} else if (skip_prefix(option, "signed-commits=", &option)) {
3652-
if (parse_sign_mode(option, &signed_commit_mode))
3709+
if (parse_sign_mode(option, &signed_commit_mode, &signed_commit_keyid))
36533710
usagef(_("unknown --signed-commits mode '%s'"), option);
36543711
} else if (skip_prefix(option, "signed-tags=", &option)) {
3655-
if (parse_sign_mode(option, &signed_tag_mode))
3712+
if (parse_sign_mode(option, &signed_tag_mode, NULL))
36563713
usagef(_("unknown --signed-tags mode '%s'"), option);
36573714
} else if (!strcmp(option, "quiet")) {
36583715
show_stats = 0;

gpg-interface.c

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1152,21 +1152,28 @@ static int sign_buffer_ssh(struct strbuf *buffer, struct strbuf *signature,
11521152
return ret;
11531153
}
11541154

1155-
int parse_sign_mode(const char *arg, enum sign_mode *mode)
1155+
int parse_sign_mode(const char *arg, enum sign_mode *mode, const char **keyid)
11561156
{
1157-
if (!strcmp(arg, "abort"))
1157+
if (!strcmp(arg, "abort")) {
11581158
*mode = SIGN_ABORT;
1159-
else if (!strcmp(arg, "verbatim") || !strcmp(arg, "ignore"))
1159+
} else if (!strcmp(arg, "verbatim") || !strcmp(arg, "ignore")) {
11601160
*mode = SIGN_VERBATIM;
1161-
else if (!strcmp(arg, "warn-verbatim") || !strcmp(arg, "warn"))
1161+
} else if (!strcmp(arg, "warn-verbatim") || !strcmp(arg, "warn")) {
11621162
*mode = SIGN_WARN_VERBATIM;
1163-
else if (!strcmp(arg, "warn-strip"))
1163+
} else if (!strcmp(arg, "warn-strip")) {
11641164
*mode = SIGN_WARN_STRIP;
1165-
else if (!strcmp(arg, "strip"))
1165+
} else if (!strcmp(arg, "strip")) {
11661166
*mode = SIGN_STRIP;
1167-
else if (!strcmp(arg, "strip-if-invalid"))
1167+
} else if (!strcmp(arg, "strip-if-invalid")) {
11681168
*mode = SIGN_STRIP_IF_INVALID;
1169-
else
1169+
} else if (!strcmp(arg, "sign-if-invalid")) {
1170+
*mode = SIGN_SIGN_IF_INVALID;
1171+
} else if (skip_prefix(arg, "sign-if-invalid=", &arg)) {
1172+
*mode = SIGN_SIGN_IF_INVALID;
1173+
if (keyid)
1174+
*keyid = arg;
1175+
} else {
11701176
return -1;
1177+
}
11711178
return 0;
11721179
}

gpg-interface.h

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,12 +120,15 @@ enum sign_mode {
120120
SIGN_WARN_STRIP,
121121
SIGN_STRIP,
122122
SIGN_STRIP_IF_INVALID,
123+
SIGN_SIGN_IF_INVALID,
123124
};
124125

125126
/*
126127
* Return 0 if `arg` can be parsed into an `enum sign_mode`. Return -1
127-
* otherwise.
128+
* otherwise. If the parsed mode is SIGN_SIGN_IF_INVALID and GPG key provided in
129+
* the arguments in the form `sign-if-invalid=<keyid>`, the key-ID is parsed
130+
* into `char **keyid`.
128131
*/
129-
int parse_sign_mode(const char *arg, enum sign_mode *mode);
132+
int parse_sign_mode(const char *arg, enum sign_mode *mode, const char **keyid);
130133

131134
#endif

0 commit comments

Comments
 (0)