Skip to content

Commit 4aadaa4

Browse files
committed
Merge branch 'cc/fast-import-export-signature-names' into seen
Clean up the way how signature on commit objects are exported to and imported from fast-import stream. Has been expecting a reroll for too long. cf. <[email protected]> cf. https://github.com/git/git/actions/runs/14671270673/job/41178138711 * cc/fast-import-export-signature-names: fast-(import|export): improve on commit signature output format
2 parents e2144f0 + 0161880 commit 4aadaa4

File tree

7 files changed

+274
-42
lines changed

7 files changed

+274
-42
lines changed

Documentation/git-fast-export.adoc

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,23 @@ resulting tag will have an invalid signature.
5050
is the same as how earlier versions of this command without
5151
this option behaved.
5252
+
53+
When exported, a signature starts with:
54+
+
55+
gpgsig <git-hash-algo> <signature-format>
56+
+
57+
where <git-hash-algo> is the Git object hash so either "sha1" or
58+
"sha256", and <signature-format> is the signature type, so "openpgp",
59+
"x509", "ssh" or "unknown".
60+
+
61+
For example, an OpenPGP signature on a SHA-1 commit starts with
62+
`gpgsig sha1 openpgp`, while an SSH signature on a SHA-256 commit
63+
starts with `gpgsig sha256 ssh`.
64+
+
65+
Currently for a given commit, at most one signature for the SHA-1
66+
object and one signature for the SHA-256 object are exported, each
67+
with their respective <git-hash-algo> identifier. A warning is
68+
emitted for each additional signature found.
69+
+
5370
NOTE: This is highly experimental and the format of the data stream may
5471
change in the future without compatibility guarantees.
5572

Documentation/git-fast-import.adoc

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -445,7 +445,7 @@ one).
445445
original-oid?
446446
('author' (SP <name>)? SP LT <email> GT SP <when> LF)?
447447
'committer' (SP <name>)? SP LT <email> GT SP <when> LF
448-
('gpgsig' SP <alg> LF data)?
448+
('gpgsig' SP <algo> SP <format> LF data)?
449449
('encoding' SP <encoding> LF)?
450450
data
451451
('from' SP <commit-ish> LF)?
@@ -518,13 +518,32 @@ their syntax.
518518
^^^^^^^^
519519

520520
The optional `gpgsig` command is used to include a PGP/GPG signature
521-
that signs the commit data.
521+
or other cryptographic signature that signs the commit data.
522522

523-
Here <alg> specifies which hashing algorithm is used for this
524-
signature, either `sha1` or `sha256`.
523+
....
524+
'gpgsig' SP <git-hash-algo> SP <signature-format> LF
525+
data
526+
....
527+
528+
The `gpgsig` command takes two arguments:
529+
530+
* `<git-hash-algo>` specifies which Git object format this signature
531+
applies to, either `sha1` or `sha256`.
532+
533+
* `<signature-format>` specifies the type of signature, such as
534+
`openpgp`, `x509`, `ssh`, or `unknown`.
535+
536+
A commit may have at most one signature for the SHA-1 object format
537+
(stored in the "gpgsig" header) and one for the SHA-256 object format
538+
(stored in the "gpgsig-sha256" header).
539+
540+
See below for a detailed description of the `data` command which
541+
contains the raw signature data.
542+
543+
Signatures are not yet checked in the current implementation though.
525544

526-
NOTE: This is highly experimental and the format of the data stream may
527-
change in the future without compatibility guarantees.
545+
NOTE: This is highly experimental and the format of the `gpgsig`
546+
command may change in the future without compatibility guarantees.
528547

529548
`encoding`
530549
^^^^^^^^^^

builtin/fast-export.c

Lines changed: 55 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
#include "quote.h"
3030
#include "remote.h"
3131
#include "blob.h"
32+
#include "gpg-interface.h"
3233

3334
static const char *const fast_export_usage[] = {
3435
N_("git fast-export [<rev-list-opts>]"),
@@ -652,6 +653,30 @@ static const char *find_commit_multiline_header(const char *msg,
652653
return strbuf_detach(&val, NULL);
653654
}
654655

656+
static void print_signature(const char *signature, const char *object_hash)
657+
{
658+
if (!signature)
659+
return;
660+
661+
printf("gpgsig %s %s\ndata %u\n%s",
662+
object_hash,
663+
get_signature_format(signature),
664+
(unsigned)strlen(signature),
665+
signature);
666+
}
667+
668+
static void warn_on_extra_sig(const char **pos, struct commit *commit, int is_sha1)
669+
{
670+
const char *header = is_sha1 ? "gpgsig" : "gpgsig-sha256";
671+
const char *extra_sig = find_commit_multiline_header(*pos + 1, header, pos);
672+
if (extra_sig) {
673+
const char *hash = is_sha1 ? "SHA-1" : "SHA-256";
674+
warning("more than one %s signature found on commit %s, using only the first one",
675+
hash, oid_to_hex(&commit->object.oid));
676+
free((char *)extra_sig);
677+
}
678+
}
679+
655680
static void handle_commit(struct commit *commit, struct rev_info *rev,
656681
struct string_list *paths_of_changed_objects)
657682
{
@@ -660,7 +685,8 @@ static void handle_commit(struct commit *commit, struct rev_info *rev,
660685
const char *author, *author_end, *committer, *committer_end;
661686
const char *encoding = NULL;
662687
size_t encoding_len;
663-
const char *signature_alg = NULL, *signature = NULL;
688+
const char *sig_sha1 = NULL;
689+
const char *sig_sha256 = NULL;
664690
const char *message;
665691
char *reencoded = NULL;
666692
struct commit_list *p;
@@ -700,10 +726,28 @@ static void handle_commit(struct commit *commit, struct rev_info *rev,
700726
}
701727

702728
if (*commit_buffer_cursor == '\n') {
703-
if ((signature = find_commit_multiline_header(commit_buffer_cursor + 1, "gpgsig", &commit_buffer_cursor)))
704-
signature_alg = "sha1";
705-
else if ((signature = find_commit_multiline_header(commit_buffer_cursor + 1, "gpgsig-sha256", &commit_buffer_cursor)))
706-
signature_alg = "sha256";
729+
const char *sig_cursor = commit_buffer_cursor;
730+
const char *after_sha1 = commit_buffer_cursor;
731+
const char *after_sha256 = commit_buffer_cursor;
732+
733+
/*
734+
* Find the first signature for each hash algorithm.
735+
* The searches must start from the same position.
736+
*/
737+
sig_sha1 = find_commit_multiline_header(sig_cursor + 1,
738+
"gpgsig",
739+
&after_sha1);
740+
sig_sha256 = find_commit_multiline_header(sig_cursor + 1,
741+
"gpgsig-sha256",
742+
&after_sha256);
743+
744+
/* Warn on any additional signatures, as they will be ignored. */
745+
if (sig_sha1)
746+
warn_on_extra_sig(&after_sha1, commit, 1);
747+
if (sig_sha256)
748+
warn_on_extra_sig(&after_sha256, commit, 0);
749+
750+
commit_buffer_cursor = (after_sha1 > after_sha256) ? after_sha1 : after_sha256;
707751
}
708752

709753
message = strstr(commit_buffer_cursor, "\n\n");
@@ -769,7 +813,7 @@ static void handle_commit(struct commit *commit, struct rev_info *rev,
769813
printf("%.*s\n%.*s\n",
770814
(int)(author_end - author), author,
771815
(int)(committer_end - committer), committer);
772-
if (signature) {
816+
if (sig_sha1 || sig_sha256) {
773817
switch (signed_commit_mode) {
774818
case SIGN_ABORT:
775819
die("encountered signed commit %s; use "
@@ -780,19 +824,18 @@ static void handle_commit(struct commit *commit, struct rev_info *rev,
780824
oid_to_hex(&commit->object.oid));
781825
/* fallthru */
782826
case SIGN_VERBATIM:
783-
printf("gpgsig %s\ndata %u\n%s",
784-
signature_alg,
785-
(unsigned)strlen(signature),
786-
signature);
827+
print_signature(sig_sha1, "sha1");
828+
print_signature(sig_sha256, "sha256");
787829
break;
788830
case SIGN_WARN_STRIP:
789-
warning("stripping signature from commit %s",
831+
warning("stripping signature(s) from commit %s",
790832
oid_to_hex(&commit->object.oid));
791833
/* fallthru */
792834
case SIGN_STRIP:
793835
break;
794836
}
795-
free((char *)signature);
837+
free((char *)sig_sha1);
838+
free((char *)sig_sha256);
796839
}
797840
if (!reencoded && encoding)
798841
printf("encoding %.*s\n", (int)encoding_len, encoding);

builtin/fast-import.c

Lines changed: 95 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
#include "commit-reach.h"
3030
#include "khash.h"
3131
#include "date.h"
32+
#include "gpg-interface.h"
3233

3334
#define PACK_ID_BITS 16
3435
#define MAX_PACK_ID ((1<<PACK_ID_BITS)-1)
@@ -2716,15 +2717,86 @@ static struct hash_list *parse_merge(unsigned int *count)
27162717
return list;
27172718
}
27182719

2720+
struct signature_data {
2721+
char *hash_algo; /* "sha1" or "sha256" */
2722+
char *sig_format; /* "openpgp", "x509", "ssh", "unknown" */
2723+
struct strbuf data; /* The actual signature data */
2724+
};
2725+
2726+
static void parse_one_signature(struct signature_data *sig, const char *v)
2727+
{
2728+
char *args = xstrdup(v); /* Will be freed when sig->hash_algo is freed */
2729+
char *space = strchr(args, ' ');
2730+
2731+
if (!space)
2732+
die("Expected gpgsig format: 'gpgsig <hash-algo> <signature-format>', "
2733+
"got 'gpgsig %s'", args);
2734+
*space++ = '\0';
2735+
2736+
sig->hash_algo = args;
2737+
sig->sig_format = space;
2738+
2739+
/* Remove any trailing newline from format */
2740+
space = strchr(sig->sig_format, '\n');
2741+
if (space)
2742+
*space = '\0';
2743+
2744+
/* Validate hash algorithm */
2745+
if (strcmp(sig->hash_algo, "sha1") &&
2746+
strcmp(sig->hash_algo, "sha256"))
2747+
die("Unknown git hash algorithm in gpgsig: '%s'", sig->hash_algo);
2748+
2749+
/* Validate signature format */
2750+
if (!valid_signature_format(sig->sig_format))
2751+
die("Invalid signature format in gpgsig: '%s'", sig->sig_format);
2752+
if (!strcmp(sig->sig_format, "unknown"))
2753+
warning("'unknown' signature format in gpgsig");
2754+
2755+
/* Read signature data */
2756+
read_next_command();
2757+
parse_data(&sig->data, 0, NULL);
2758+
}
2759+
2760+
static void add_gpgsig_to_commit(struct strbuf *commit_data,
2761+
const char *header,
2762+
struct signature_data *sig)
2763+
{
2764+
struct string_list siglines = STRING_LIST_INIT_NODUP;
2765+
2766+
if (!sig->hash_algo)
2767+
return;
2768+
2769+
strbuf_addstr(commit_data, header);
2770+
string_list_split_in_place(&siglines, sig->data.buf, "\n", -1);
2771+
strbuf_add_separated_string_list(commit_data, "\n ", &siglines);
2772+
strbuf_addch(commit_data, '\n');
2773+
string_list_clear(&siglines, 1);
2774+
strbuf_release(&sig->data);
2775+
free(sig->hash_algo);
2776+
}
2777+
2778+
static void store_signature(struct signature_data *stored_sig,
2779+
struct signature_data *new_sig,
2780+
const char *hash_type)
2781+
{
2782+
if (stored_sig->hash_algo) {
2783+
warning("Multiple %s signatures found, ignoring additional signature",
2784+
hash_type);
2785+
strbuf_release(&new_sig->data);
2786+
free(new_sig->hash_algo);
2787+
} else {
2788+
*stored_sig = *new_sig;
2789+
}
2790+
}
2791+
27192792
static void parse_new_commit(const char *arg)
27202793
{
2721-
static struct strbuf sig = STRBUF_INIT;
27222794
static struct strbuf msg = STRBUF_INIT;
2723-
struct string_list siglines = STRING_LIST_INIT_NODUP;
2795+
struct signature_data sig_sha1 = { NULL, NULL, STRBUF_INIT };
2796+
struct signature_data sig_sha256 = { NULL, NULL, STRBUF_INIT };
27242797
struct branch *b;
27252798
char *author = NULL;
27262799
char *committer = NULL;
2727-
char *sig_alg = NULL;
27282800
char *encoding = NULL;
27292801
struct hash_list *merge_list = NULL;
27302802
unsigned int merge_count;
@@ -2748,13 +2820,23 @@ static void parse_new_commit(const char *arg)
27482820
}
27492821
if (!committer)
27502822
die("Expected committer but didn't get one");
2751-
if (skip_prefix(command_buf.buf, "gpgsig ", &v)) {
2752-
sig_alg = xstrdup(v);
2753-
read_next_command();
2754-
parse_data(&sig, 0, NULL);
2823+
2824+
/* Process signatures (up to 2: one "sha1" and one "sha256") */
2825+
while (skip_prefix(command_buf.buf, "gpgsig ", &v)) {
2826+
struct signature_data sig = { NULL, NULL, STRBUF_INIT };
2827+
2828+
parse_one_signature(&sig, v);
2829+
2830+
if (!strcmp(sig.hash_algo, "sha1"))
2831+
store_signature(&sig_sha1, &sig, "SHA-1");
2832+
else if (!strcmp(sig.hash_algo, "sha256"))
2833+
store_signature(&sig_sha256, &sig, "SHA-256");
2834+
else
2835+
BUG("parse_one_signature() returned unknown hash algo");
2836+
27552837
read_next_command();
2756-
} else
2757-
strbuf_setlen(&sig, 0);
2838+
}
2839+
27582840
if (skip_prefix(command_buf.buf, "encoding ", &v)) {
27592841
encoding = xstrdup(v);
27602842
read_next_command();
@@ -2828,23 +2910,14 @@ static void parse_new_commit(const char *arg)
28282910
strbuf_addf(&new_data,
28292911
"encoding %s\n",
28302912
encoding);
2831-
if (sig_alg) {
2832-
if (!strcmp(sig_alg, "sha1"))
2833-
strbuf_addstr(&new_data, "gpgsig ");
2834-
else if (!strcmp(sig_alg, "sha256"))
2835-
strbuf_addstr(&new_data, "gpgsig-sha256 ");
2836-
else
2837-
die("Expected gpgsig algorithm sha1 or sha256, got %s", sig_alg);
2838-
string_list_split_in_place(&siglines, sig.buf, "\n", -1);
2839-
strbuf_add_separated_string_list(&new_data, "\n ", &siglines);
2840-
strbuf_addch(&new_data, '\n');
2841-
}
2913+
2914+
add_gpgsig_to_commit(&new_data, "gpgsig ", &sig_sha1);
2915+
add_gpgsig_to_commit(&new_data, "gpgsig-sha256 ", &sig_sha256);
2916+
28422917
strbuf_addch(&new_data, '\n');
28432918
strbuf_addbuf(&new_data, &msg);
2844-
string_list_clear(&siglines, 1);
28452919
free(author);
28462920
free(committer);
2847-
free(sig_alg);
28482921
free(encoding);
28492922

28502923
if (!store_object(OBJ_COMMIT, &new_data, NULL, &b->oid, next_mark))

gpg-interface.c

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,18 @@ static struct gpg_format *get_format_by_sig(const char *sig)
144144
return NULL;
145145
}
146146

147+
const char *get_signature_format(const char *buf)
148+
{
149+
struct gpg_format *format = get_format_by_sig(buf);
150+
return format ? format->name : "unknown";
151+
}
152+
153+
int valid_signature_format(const char *format)
154+
{
155+
return (!!get_format_by_name(format) ||
156+
!strcmp(format, "unknown"));
157+
}
158+
147159
void signature_check_clear(struct signature_check *sigc)
148160
{
149161
FREE_AND_NULL(sigc->payload);

gpg-interface.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,18 @@ struct signature_check {
4747

4848
void signature_check_clear(struct signature_check *sigc);
4949

50+
/*
51+
* Return the format of the signature (like "openpgp", "x509", "ssh"
52+
* or "unknown").
53+
*/
54+
const char *get_signature_format(const char *buf);
55+
56+
/*
57+
* Is the signature format valid (like "openpgp", "x509", "ssh" or
58+
* "unknown")
59+
*/
60+
int valid_signature_format(const char *format);
61+
5062
/*
5163
* Look at a GPG signed tag object. If such a signature exists, store it in
5264
* signature and the signed content in payload. Return 1 if a signature was

0 commit comments

Comments
 (0)