Skip to content

Commit 5216bcb

Browse files
committed
Merge branch 'cc/fast-import-export-signature-names'
Clean up the way how signature on commit objects are exported to and imported from fast-import stream. * cc/fast-import-export-signature-names: fast-(import|export): improve on commit signature output format
2 parents 9881326 + b5b3ddb commit 5216bcb

File tree

7 files changed

+312
-44
lines changed

7 files changed

+312
-44
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+
While all the signatures of a commit are exported, an importer may
66+
choose to accept only some of them. For example
67+
linkgit:git-fast-import[1] currently stores at most one signature per
68+
Git hash algorithm in each commit.
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: 32 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,39 @@ 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 data
525+
....
526+
527+
The `gpgsig` command takes two arguments:
528+
529+
* `<git-hash-algo>` specifies which Git object format this signature
530+
applies to, either `sha1` or `sha256`. This allows to know which
531+
representation of the commit was signed (the SHA-1 or the SHA-256
532+
version) which helps with both signature verification and
533+
interoperability between repos with different hash functions.
534+
535+
* `<signature-format>` specifies the type of signature, such as
536+
`openpgp`, `x509`, `ssh`, or `unknown`. This is a convenience for
537+
tools that process the stream, so they don't have to parse the ASCII
538+
armor to identify the signature type.
539+
540+
A commit may have at most one signature for the SHA-1 object format
541+
(stored in the "gpgsig" header) and one for the SHA-256 object format
542+
(stored in the "gpgsig-sha256" header).
543+
544+
See below for a detailed description of the `data` command which
545+
contains the raw signature data.
546+
547+
Signatures are not yet checked in the current implementation
548+
though. (Already setting the `extensions.compatObjectFormat`
549+
configuration option might help with verifying both SHA-1 and SHA-256
550+
object format signatures when it will be implemented.)
525551

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

529555
`encoding`
530556
^^^^^^^^^^

builtin/fast-export.c

Lines changed: 48 additions & 14 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,38 @@ 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\n",
662+
object_hash,
663+
get_signature_format(signature),
664+
(unsigned)strlen(signature),
665+
signature);
666+
}
667+
668+
static const char *append_signatures_for_header(struct string_list *signatures,
669+
const char *pos,
670+
const char *header,
671+
const char *object_hash)
672+
{
673+
const char *signature;
674+
const char *start = pos;
675+
const char *end = pos;
676+
677+
while ((signature = find_commit_multiline_header(start + 1,
678+
header,
679+
&end))) {
680+
string_list_append(signatures, signature)->util = (void *)object_hash;
681+
free((char *)signature);
682+
start = end;
683+
}
684+
685+
return end;
686+
}
687+
655688
static void handle_commit(struct commit *commit, struct rev_info *rev,
656689
struct string_list *paths_of_changed_objects)
657690
{
@@ -660,7 +693,7 @@ static void handle_commit(struct commit *commit, struct rev_info *rev,
660693
const char *author, *author_end, *committer, *committer_end;
661694
const char *encoding = NULL;
662695
size_t encoding_len;
663-
const char *signature_alg = NULL, *signature = NULL;
696+
struct string_list signatures = STRING_LIST_INIT_DUP;
664697
const char *message;
665698
char *reencoded = NULL;
666699
struct commit_list *p;
@@ -700,10 +733,11 @@ static void handle_commit(struct commit *commit, struct rev_info *rev,
700733
}
701734

702735
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";
736+
const char *after_sha1 = append_signatures_for_header(&signatures, commit_buffer_cursor,
737+
"gpgsig", "sha1");
738+
const char *after_sha256 = append_signatures_for_header(&signatures, commit_buffer_cursor,
739+
"gpgsig-sha256", "sha256");
740+
commit_buffer_cursor = (after_sha1 > after_sha256) ? after_sha1 : after_sha256;
707741
}
708742

709743
message = strstr(commit_buffer_cursor, "\n\n");
@@ -769,30 +803,30 @@ static void handle_commit(struct commit *commit, struct rev_info *rev,
769803
printf("%.*s\n%.*s\n",
770804
(int)(author_end - author), author,
771805
(int)(committer_end - committer), committer);
772-
if (signature) {
806+
if (signatures.nr) {
773807
switch (signed_commit_mode) {
774808
case SIGN_ABORT:
775809
die("encountered signed commit %s; use "
776810
"--signed-commits=<mode> to handle it",
777811
oid_to_hex(&commit->object.oid));
778812
case SIGN_WARN_VERBATIM:
779-
warning("exporting signed commit %s",
780-
oid_to_hex(&commit->object.oid));
813+
warning("exporting %"PRIuMAX" signature(s) for commit %s",
814+
(uintmax_t)signatures.nr, oid_to_hex(&commit->object.oid));
781815
/* fallthru */
782816
case SIGN_VERBATIM:
783-
printf("gpgsig %s\ndata %u\n%s",
784-
signature_alg,
785-
(unsigned)strlen(signature),
786-
signature);
817+
for (size_t i = 0; i < signatures.nr; i++) {
818+
struct string_list_item *item = &signatures.items[i];
819+
print_signature(item->string, item->util);
820+
}
787821
break;
788822
case SIGN_WARN_STRIP:
789-
warning("stripping signature from commit %s",
823+
warning("stripping signature(s) from commit %s",
790824
oid_to_hex(&commit->object.oid));
791825
/* fallthru */
792826
case SIGN_STRIP:
793827
break;
794828
}
795-
free((char *)signature);
829+
string_list_clear(&signatures, 0);
796830
}
797831
if (!reencoded && encoding)
798832
printf("encoding %.*s\n", (int)encoding_len, encoding);

builtin/fast-import.c

Lines changed: 91 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,82 @@ 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", or "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 + 1;
2738+
2739+
/* Validate hash algorithm */
2740+
if (strcmp(sig->hash_algo, "sha1") &&
2741+
strcmp(sig->hash_algo, "sha256"))
2742+
die("Unknown git hash algorithm in gpgsig: '%s'", sig->hash_algo);
2743+
2744+
/* Validate signature format */
2745+
if (!valid_signature_format(sig->sig_format))
2746+
die("Invalid signature format in gpgsig: '%s'", sig->sig_format);
2747+
if (!strcmp(sig->sig_format, "unknown"))
2748+
warning("'unknown' signature format in gpgsig");
2749+
2750+
/* Read signature data */
2751+
read_next_command();
2752+
parse_data(&sig->data, 0, NULL);
2753+
}
2754+
2755+
static void add_gpgsig_to_commit(struct strbuf *commit_data,
2756+
const char *header,
2757+
struct signature_data *sig)
2758+
{
2759+
struct string_list siglines = STRING_LIST_INIT_NODUP;
2760+
2761+
if (!sig->hash_algo)
2762+
return;
2763+
2764+
strbuf_addstr(commit_data, header);
2765+
string_list_split_in_place(&siglines, sig->data.buf, "\n", -1);
2766+
strbuf_add_separated_string_list(commit_data, "\n ", &siglines);
2767+
strbuf_addch(commit_data, '\n');
2768+
string_list_clear(&siglines, 1);
2769+
strbuf_release(&sig->data);
2770+
free(sig->hash_algo);
2771+
}
2772+
2773+
static void store_signature(struct signature_data *stored_sig,
2774+
struct signature_data *new_sig,
2775+
const char *hash_type)
2776+
{
2777+
if (stored_sig->hash_algo) {
2778+
warning("multiple %s signatures found, "
2779+
"ignoring additional signature",
2780+
hash_type);
2781+
strbuf_release(&new_sig->data);
2782+
free(new_sig->hash_algo);
2783+
} else {
2784+
*stored_sig = *new_sig;
2785+
}
2786+
}
2787+
27192788
static void parse_new_commit(const char *arg)
27202789
{
2721-
static struct strbuf sig = STRBUF_INIT;
27222790
static struct strbuf msg = STRBUF_INIT;
2723-
struct string_list siglines = STRING_LIST_INIT_NODUP;
2791+
struct signature_data sig_sha1 = { NULL, NULL, STRBUF_INIT };
2792+
struct signature_data sig_sha256 = { NULL, NULL, STRBUF_INIT };
27242793
struct branch *b;
27252794
char *author = NULL;
27262795
char *committer = NULL;
2727-
char *sig_alg = NULL;
27282796
char *encoding = NULL;
27292797
struct hash_list *merge_list = NULL;
27302798
unsigned int merge_count;
@@ -2748,13 +2816,23 @@ static void parse_new_commit(const char *arg)
27482816
}
27492817
if (!committer)
27502818
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);
2819+
2820+
/* Process signatures (up to 2: one "sha1" and one "sha256") */
2821+
while (skip_prefix(command_buf.buf, "gpgsig ", &v)) {
2822+
struct signature_data sig = { NULL, NULL, STRBUF_INIT };
2823+
2824+
parse_one_signature(&sig, v);
2825+
2826+
if (!strcmp(sig.hash_algo, "sha1"))
2827+
store_signature(&sig_sha1, &sig, "SHA-1");
2828+
else if (!strcmp(sig.hash_algo, "sha256"))
2829+
store_signature(&sig_sha256, &sig, "SHA-256");
2830+
else
2831+
BUG("parse_one_signature() returned unknown hash algo");
2832+
27552833
read_next_command();
2756-
} else
2757-
strbuf_setlen(&sig, 0);
2834+
}
2835+
27582836
if (skip_prefix(command_buf.buf, "encoding ", &v)) {
27592837
encoding = xstrdup(v);
27602838
read_next_command();
@@ -2828,23 +2906,14 @@ static void parse_new_commit(const char *arg)
28282906
strbuf_addf(&new_data,
28292907
"encoding %s\n",
28302908
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-
}
2909+
2910+
add_gpgsig_to_commit(&new_data, "gpgsig ", &sig_sha1);
2911+
add_gpgsig_to_commit(&new_data, "gpgsig-sha256 ", &sig_sha256);
2912+
28422913
strbuf_addch(&new_data, '\n');
28432914
strbuf_addbuf(&new_data, &msg);
2844-
string_list_clear(&siglines, 1);
28452915
free(author);
28462916
free(committer);
2847-
free(sig_alg);
28482917
free(encoding);
28492918

28502919
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)