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 formatmaint
commit
5216bcbc84
|
|
@ -50,6 +50,23 @@ resulting tag will have an invalid signature.
|
|||
is the same as how earlier versions of this command without
|
||||
this option behaved.
|
||||
+
|
||||
When exported, a signature starts with:
|
||||
+
|
||||
gpgsig <git-hash-algo> <signature-format>
|
||||
+
|
||||
where <git-hash-algo> is the Git object hash so either "sha1" or
|
||||
"sha256", and <signature-format> is the signature type, so "openpgp",
|
||||
"x509", "ssh" or "unknown".
|
||||
+
|
||||
For example, an OpenPGP signature on a SHA-1 commit starts with
|
||||
`gpgsig sha1 openpgp`, while an SSH signature on a SHA-256 commit
|
||||
starts with `gpgsig sha256 ssh`.
|
||||
+
|
||||
While all the signatures of a commit are exported, an importer may
|
||||
choose to accept only some of them. For example
|
||||
linkgit:git-fast-import[1] currently stores at most one signature per
|
||||
Git hash algorithm in each commit.
|
||||
+
|
||||
NOTE: This is highly experimental and the format of the data stream may
|
||||
change in the future without compatibility guarantees.
|
||||
|
||||
|
|
|
|||
|
|
@ -445,7 +445,7 @@ one).
|
|||
original-oid?
|
||||
('author' (SP <name>)? SP LT <email> GT SP <when> LF)?
|
||||
'committer' (SP <name>)? SP LT <email> GT SP <when> LF
|
||||
('gpgsig' SP <alg> LF data)?
|
||||
('gpgsig' SP <algo> SP <format> LF data)?
|
||||
('encoding' SP <encoding> LF)?
|
||||
data
|
||||
('from' SP <commit-ish> LF)?
|
||||
|
|
@ -518,13 +518,39 @@ their syntax.
|
|||
^^^^^^^^
|
||||
|
||||
The optional `gpgsig` command is used to include a PGP/GPG signature
|
||||
that signs the commit data.
|
||||
or other cryptographic signature that signs the commit data.
|
||||
|
||||
Here <alg> specifies which hashing algorithm is used for this
|
||||
signature, either `sha1` or `sha256`.
|
||||
....
|
||||
'gpgsig' SP <git-hash-algo> SP <signature-format> LF data
|
||||
....
|
||||
|
||||
NOTE: This is highly experimental and the format of the data stream may
|
||||
change in the future without compatibility guarantees.
|
||||
The `gpgsig` command takes two arguments:
|
||||
|
||||
* `<git-hash-algo>` specifies which Git object format this signature
|
||||
applies to, either `sha1` or `sha256`. This allows to know which
|
||||
representation of the commit was signed (the SHA-1 or the SHA-256
|
||||
version) which helps with both signature verification and
|
||||
interoperability between repos with different hash functions.
|
||||
|
||||
* `<signature-format>` specifies the type of signature, such as
|
||||
`openpgp`, `x509`, `ssh`, or `unknown`. This is a convenience for
|
||||
tools that process the stream, so they don't have to parse the ASCII
|
||||
armor to identify the signature type.
|
||||
|
||||
A commit may have at most one signature for the SHA-1 object format
|
||||
(stored in the "gpgsig" header) and one for the SHA-256 object format
|
||||
(stored in the "gpgsig-sha256" header).
|
||||
|
||||
See below for a detailed description of the `data` command which
|
||||
contains the raw signature data.
|
||||
|
||||
Signatures are not yet checked in the current implementation
|
||||
though. (Already setting the `extensions.compatObjectFormat`
|
||||
configuration option might help with verifying both SHA-1 and SHA-256
|
||||
object format signatures when it will be implemented.)
|
||||
|
||||
NOTE: This is highly experimental and the format of the `gpgsig`
|
||||
command may change in the future without compatibility guarantees.
|
||||
|
||||
`encoding`
|
||||
^^^^^^^^^^
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@
|
|||
#include "quote.h"
|
||||
#include "remote.h"
|
||||
#include "blob.h"
|
||||
#include "gpg-interface.h"
|
||||
|
||||
static const char *const fast_export_usage[] = {
|
||||
N_("git fast-export [<rev-list-opts>]"),
|
||||
|
|
@ -652,6 +653,38 @@ static const char *find_commit_multiline_header(const char *msg,
|
|||
return strbuf_detach(&val, NULL);
|
||||
}
|
||||
|
||||
static void print_signature(const char *signature, const char *object_hash)
|
||||
{
|
||||
if (!signature)
|
||||
return;
|
||||
|
||||
printf("gpgsig %s %s\ndata %u\n%s\n",
|
||||
object_hash,
|
||||
get_signature_format(signature),
|
||||
(unsigned)strlen(signature),
|
||||
signature);
|
||||
}
|
||||
|
||||
static const char *append_signatures_for_header(struct string_list *signatures,
|
||||
const char *pos,
|
||||
const char *header,
|
||||
const char *object_hash)
|
||||
{
|
||||
const char *signature;
|
||||
const char *start = pos;
|
||||
const char *end = pos;
|
||||
|
||||
while ((signature = find_commit_multiline_header(start + 1,
|
||||
header,
|
||||
&end))) {
|
||||
string_list_append(signatures, signature)->util = (void *)object_hash;
|
||||
free((char *)signature);
|
||||
start = end;
|
||||
}
|
||||
|
||||
return end;
|
||||
}
|
||||
|
||||
static void handle_commit(struct commit *commit, struct rev_info *rev,
|
||||
struct string_list *paths_of_changed_objects)
|
||||
{
|
||||
|
|
@ -660,7 +693,7 @@ static void handle_commit(struct commit *commit, struct rev_info *rev,
|
|||
const char *author, *author_end, *committer, *committer_end;
|
||||
const char *encoding = NULL;
|
||||
size_t encoding_len;
|
||||
const char *signature_alg = NULL, *signature = NULL;
|
||||
struct string_list signatures = STRING_LIST_INIT_DUP;
|
||||
const char *message;
|
||||
char *reencoded = NULL;
|
||||
struct commit_list *p;
|
||||
|
|
@ -700,10 +733,11 @@ static void handle_commit(struct commit *commit, struct rev_info *rev,
|
|||
}
|
||||
|
||||
if (*commit_buffer_cursor == '\n') {
|
||||
if ((signature = find_commit_multiline_header(commit_buffer_cursor + 1, "gpgsig", &commit_buffer_cursor)))
|
||||
signature_alg = "sha1";
|
||||
else if ((signature = find_commit_multiline_header(commit_buffer_cursor + 1, "gpgsig-sha256", &commit_buffer_cursor)))
|
||||
signature_alg = "sha256";
|
||||
const char *after_sha1 = append_signatures_for_header(&signatures, commit_buffer_cursor,
|
||||
"gpgsig", "sha1");
|
||||
const char *after_sha256 = append_signatures_for_header(&signatures, commit_buffer_cursor,
|
||||
"gpgsig-sha256", "sha256");
|
||||
commit_buffer_cursor = (after_sha1 > after_sha256) ? after_sha1 : after_sha256;
|
||||
}
|
||||
|
||||
message = strstr(commit_buffer_cursor, "\n\n");
|
||||
|
|
@ -769,30 +803,30 @@ static void handle_commit(struct commit *commit, struct rev_info *rev,
|
|||
printf("%.*s\n%.*s\n",
|
||||
(int)(author_end - author), author,
|
||||
(int)(committer_end - committer), committer);
|
||||
if (signature) {
|
||||
if (signatures.nr) {
|
||||
switch (signed_commit_mode) {
|
||||
case SIGN_ABORT:
|
||||
die("encountered signed commit %s; use "
|
||||
"--signed-commits=<mode> to handle it",
|
||||
oid_to_hex(&commit->object.oid));
|
||||
case SIGN_WARN_VERBATIM:
|
||||
warning("exporting signed commit %s",
|
||||
oid_to_hex(&commit->object.oid));
|
||||
warning("exporting %"PRIuMAX" signature(s) for commit %s",
|
||||
(uintmax_t)signatures.nr, oid_to_hex(&commit->object.oid));
|
||||
/* fallthru */
|
||||
case SIGN_VERBATIM:
|
||||
printf("gpgsig %s\ndata %u\n%s",
|
||||
signature_alg,
|
||||
(unsigned)strlen(signature),
|
||||
signature);
|
||||
for (size_t i = 0; i < signatures.nr; i++) {
|
||||
struct string_list_item *item = &signatures.items[i];
|
||||
print_signature(item->string, item->util);
|
||||
}
|
||||
break;
|
||||
case SIGN_WARN_STRIP:
|
||||
warning("stripping signature from commit %s",
|
||||
warning("stripping signature(s) from commit %s",
|
||||
oid_to_hex(&commit->object.oid));
|
||||
/* fallthru */
|
||||
case SIGN_STRIP:
|
||||
break;
|
||||
}
|
||||
free((char *)signature);
|
||||
string_list_clear(&signatures, 0);
|
||||
}
|
||||
if (!reencoded && encoding)
|
||||
printf("encoding %.*s\n", (int)encoding_len, encoding);
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@
|
|||
#include "commit-reach.h"
|
||||
#include "khash.h"
|
||||
#include "date.h"
|
||||
#include "gpg-interface.h"
|
||||
|
||||
#define PACK_ID_BITS 16
|
||||
#define MAX_PACK_ID ((1<<PACK_ID_BITS)-1)
|
||||
|
|
@ -2716,15 +2717,82 @@ static struct hash_list *parse_merge(unsigned int *count)
|
|||
return list;
|
||||
}
|
||||
|
||||
struct signature_data {
|
||||
char *hash_algo; /* "sha1" or "sha256" */
|
||||
char *sig_format; /* "openpgp", "x509", "ssh", or "unknown" */
|
||||
struct strbuf data; /* The actual signature data */
|
||||
};
|
||||
|
||||
static void parse_one_signature(struct signature_data *sig, const char *v)
|
||||
{
|
||||
char *args = xstrdup(v); /* Will be freed when sig->hash_algo is freed */
|
||||
char *space = strchr(args, ' ');
|
||||
|
||||
if (!space)
|
||||
die("Expected gpgsig format: 'gpgsig <hash-algo> <signature-format>', "
|
||||
"got 'gpgsig %s'", args);
|
||||
*space = '\0';
|
||||
|
||||
sig->hash_algo = args;
|
||||
sig->sig_format = space + 1;
|
||||
|
||||
/* Validate hash algorithm */
|
||||
if (strcmp(sig->hash_algo, "sha1") &&
|
||||
strcmp(sig->hash_algo, "sha256"))
|
||||
die("Unknown git hash algorithm in gpgsig: '%s'", sig->hash_algo);
|
||||
|
||||
/* Validate signature format */
|
||||
if (!valid_signature_format(sig->sig_format))
|
||||
die("Invalid signature format in gpgsig: '%s'", sig->sig_format);
|
||||
if (!strcmp(sig->sig_format, "unknown"))
|
||||
warning("'unknown' signature format in gpgsig");
|
||||
|
||||
/* Read signature data */
|
||||
read_next_command();
|
||||
parse_data(&sig->data, 0, NULL);
|
||||
}
|
||||
|
||||
static void add_gpgsig_to_commit(struct strbuf *commit_data,
|
||||
const char *header,
|
||||
struct signature_data *sig)
|
||||
{
|
||||
struct string_list siglines = STRING_LIST_INIT_NODUP;
|
||||
|
||||
if (!sig->hash_algo)
|
||||
return;
|
||||
|
||||
strbuf_addstr(commit_data, header);
|
||||
string_list_split_in_place(&siglines, sig->data.buf, "\n", -1);
|
||||
strbuf_add_separated_string_list(commit_data, "\n ", &siglines);
|
||||
strbuf_addch(commit_data, '\n');
|
||||
string_list_clear(&siglines, 1);
|
||||
strbuf_release(&sig->data);
|
||||
free(sig->hash_algo);
|
||||
}
|
||||
|
||||
static void store_signature(struct signature_data *stored_sig,
|
||||
struct signature_data *new_sig,
|
||||
const char *hash_type)
|
||||
{
|
||||
if (stored_sig->hash_algo) {
|
||||
warning("multiple %s signatures found, "
|
||||
"ignoring additional signature",
|
||||
hash_type);
|
||||
strbuf_release(&new_sig->data);
|
||||
free(new_sig->hash_algo);
|
||||
} else {
|
||||
*stored_sig = *new_sig;
|
||||
}
|
||||
}
|
||||
|
||||
static void parse_new_commit(const char *arg)
|
||||
{
|
||||
static struct strbuf sig = STRBUF_INIT;
|
||||
static struct strbuf msg = STRBUF_INIT;
|
||||
struct string_list siglines = STRING_LIST_INIT_NODUP;
|
||||
struct signature_data sig_sha1 = { NULL, NULL, STRBUF_INIT };
|
||||
struct signature_data sig_sha256 = { NULL, NULL, STRBUF_INIT };
|
||||
struct branch *b;
|
||||
char *author = NULL;
|
||||
char *committer = NULL;
|
||||
char *sig_alg = NULL;
|
||||
char *encoding = NULL;
|
||||
struct hash_list *merge_list = NULL;
|
||||
unsigned int merge_count;
|
||||
|
|
@ -2748,13 +2816,23 @@ static void parse_new_commit(const char *arg)
|
|||
}
|
||||
if (!committer)
|
||||
die("Expected committer but didn't get one");
|
||||
if (skip_prefix(command_buf.buf, "gpgsig ", &v)) {
|
||||
sig_alg = xstrdup(v);
|
||||
|
||||
/* Process signatures (up to 2: one "sha1" and one "sha256") */
|
||||
while (skip_prefix(command_buf.buf, "gpgsig ", &v)) {
|
||||
struct signature_data sig = { NULL, NULL, STRBUF_INIT };
|
||||
|
||||
parse_one_signature(&sig, v);
|
||||
|
||||
if (!strcmp(sig.hash_algo, "sha1"))
|
||||
store_signature(&sig_sha1, &sig, "SHA-1");
|
||||
else if (!strcmp(sig.hash_algo, "sha256"))
|
||||
store_signature(&sig_sha256, &sig, "SHA-256");
|
||||
else
|
||||
BUG("parse_one_signature() returned unknown hash algo");
|
||||
|
||||
read_next_command();
|
||||
parse_data(&sig, 0, NULL);
|
||||
read_next_command();
|
||||
} else
|
||||
strbuf_setlen(&sig, 0);
|
||||
}
|
||||
|
||||
if (skip_prefix(command_buf.buf, "encoding ", &v)) {
|
||||
encoding = xstrdup(v);
|
||||
read_next_command();
|
||||
|
|
@ -2828,23 +2906,14 @@ static void parse_new_commit(const char *arg)
|
|||
strbuf_addf(&new_data,
|
||||
"encoding %s\n",
|
||||
encoding);
|
||||
if (sig_alg) {
|
||||
if (!strcmp(sig_alg, "sha1"))
|
||||
strbuf_addstr(&new_data, "gpgsig ");
|
||||
else if (!strcmp(sig_alg, "sha256"))
|
||||
strbuf_addstr(&new_data, "gpgsig-sha256 ");
|
||||
else
|
||||
die("Expected gpgsig algorithm sha1 or sha256, got %s", sig_alg);
|
||||
string_list_split_in_place(&siglines, sig.buf, "\n", -1);
|
||||
strbuf_add_separated_string_list(&new_data, "\n ", &siglines);
|
||||
strbuf_addch(&new_data, '\n');
|
||||
}
|
||||
|
||||
add_gpgsig_to_commit(&new_data, "gpgsig ", &sig_sha1);
|
||||
add_gpgsig_to_commit(&new_data, "gpgsig-sha256 ", &sig_sha256);
|
||||
|
||||
strbuf_addch(&new_data, '\n');
|
||||
strbuf_addbuf(&new_data, &msg);
|
||||
string_list_clear(&siglines, 1);
|
||||
free(author);
|
||||
free(committer);
|
||||
free(sig_alg);
|
||||
free(encoding);
|
||||
|
||||
if (!store_object(OBJ_COMMIT, &new_data, NULL, &b->oid, next_mark))
|
||||
|
|
|
|||
|
|
@ -144,6 +144,18 @@ static struct gpg_format *get_format_by_sig(const char *sig)
|
|||
return NULL;
|
||||
}
|
||||
|
||||
const char *get_signature_format(const char *buf)
|
||||
{
|
||||
struct gpg_format *format = get_format_by_sig(buf);
|
||||
return format ? format->name : "unknown";
|
||||
}
|
||||
|
||||
int valid_signature_format(const char *format)
|
||||
{
|
||||
return (!!get_format_by_name(format) ||
|
||||
!strcmp(format, "unknown"));
|
||||
}
|
||||
|
||||
void signature_check_clear(struct signature_check *sigc)
|
||||
{
|
||||
FREE_AND_NULL(sigc->payload);
|
||||
|
|
|
|||
|
|
@ -47,6 +47,18 @@ struct signature_check {
|
|||
|
||||
void signature_check_clear(struct signature_check *sigc);
|
||||
|
||||
/*
|
||||
* Return the format of the signature (like "openpgp", "x509", "ssh"
|
||||
* or "unknown").
|
||||
*/
|
||||
const char *get_signature_format(const char *buf);
|
||||
|
||||
/*
|
||||
* Is the signature format valid (like "openpgp", "x509", "ssh" or
|
||||
* "unknown")
|
||||
*/
|
||||
int valid_signature_format(const char *format);
|
||||
|
||||
/*
|
||||
* Look at a GPG signed tag object. If such a signature exists, store it in
|
||||
* signature and the signed content in payload. Return 1 if a signature was
|
||||
|
|
|
|||
|
|
@ -314,7 +314,7 @@ test_expect_success GPG 'signed-commits=abort' '
|
|||
test_expect_success GPG 'signed-commits=verbatim' '
|
||||
|
||||
git fast-export --signed-commits=verbatim --reencode=no commit-signing >output &&
|
||||
grep "^gpgsig sha" output &&
|
||||
test_grep -E "^gpgsig $GIT_DEFAULT_HASH openpgp" output &&
|
||||
grep "encoding ISO-8859-1" output &&
|
||||
(
|
||||
cd new &&
|
||||
|
|
@ -328,7 +328,7 @@ test_expect_success GPG 'signed-commits=verbatim' '
|
|||
test_expect_success GPG 'signed-commits=warn-verbatim' '
|
||||
|
||||
git fast-export --signed-commits=warn-verbatim --reencode=no commit-signing >output 2>err &&
|
||||
grep "^gpgsig sha" output &&
|
||||
test_grep -E "^gpgsig $GIT_DEFAULT_HASH openpgp" output &&
|
||||
grep "encoding ISO-8859-1" output &&
|
||||
test -s err &&
|
||||
(
|
||||
|
|
@ -369,6 +369,62 @@ test_expect_success GPG 'signed-commits=warn-strip' '
|
|||
|
||||
'
|
||||
|
||||
test_expect_success GPGSM 'setup X.509 signed commit' '
|
||||
|
||||
git checkout -b x509-signing main &&
|
||||
test_config gpg.format x509 &&
|
||||
test_config user.signingkey $GIT_COMMITTER_EMAIL &&
|
||||
echo "X.509 content" >file &&
|
||||
git add file &&
|
||||
git commit -S -m "X.509 signed commit" &&
|
||||
X509_COMMIT=$(git rev-parse HEAD) &&
|
||||
git checkout main
|
||||
|
||||
'
|
||||
|
||||
test_expect_success GPGSM 'round-trip X.509 signed commit' '
|
||||
|
||||
git fast-export --signed-commits=verbatim x509-signing >output &&
|
||||
test_grep -E "^gpgsig $GIT_DEFAULT_HASH x509" output &&
|
||||
(
|
||||
cd new &&
|
||||
git fast-import &&
|
||||
git cat-file commit refs/heads/x509-signing >actual &&
|
||||
grep "^gpgsig" actual &&
|
||||
IMPORTED=$(git rev-parse refs/heads/x509-signing) &&
|
||||
test $X509_COMMIT = $IMPORTED
|
||||
) <output
|
||||
|
||||
'
|
||||
|
||||
test_expect_success GPGSSH 'setup SSH signed commit' '
|
||||
|
||||
git checkout -b ssh-signing main &&
|
||||
test_config gpg.format ssh &&
|
||||
test_config user.signingkey "${GPGSSH_KEY_PRIMARY}" &&
|
||||
echo "SSH content" >file &&
|
||||
git add file &&
|
||||
git commit -S -m "SSH signed commit" &&
|
||||
SSH_COMMIT=$(git rev-parse HEAD) &&
|
||||
git checkout main
|
||||
|
||||
'
|
||||
|
||||
test_expect_success GPGSSH 'round-trip SSH signed commit' '
|
||||
|
||||
git fast-export --signed-commits=verbatim ssh-signing >output &&
|
||||
test_grep -E "^gpgsig $GIT_DEFAULT_HASH ssh" output &&
|
||||
(
|
||||
cd new &&
|
||||
git fast-import &&
|
||||
git cat-file commit refs/heads/ssh-signing >actual &&
|
||||
grep "^gpgsig" actual &&
|
||||
IMPORTED=$(git rev-parse refs/heads/ssh-signing) &&
|
||||
test $SSH_COMMIT = $IMPORTED
|
||||
) <output
|
||||
|
||||
'
|
||||
|
||||
test_expect_success 'setup submodule' '
|
||||
|
||||
test_config_global protocol.file.allow always &&
|
||||
|
|
@ -905,4 +961,46 @@ test_expect_success 'fast-export handles --end-of-options' '
|
|||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success GPG 'setup a commit with dual signatures on its SHA-1 and SHA-256 formats' '
|
||||
# Create a signed SHA-256 commit
|
||||
git init --object-format=sha256 explicit-sha256 &&
|
||||
git -C explicit-sha256 config extensions.compatObjectFormat sha1 &&
|
||||
git -C explicit-sha256 checkout -b dual-signed &&
|
||||
test_commit -C explicit-sha256 A &&
|
||||
echo B >explicit-sha256/B &&
|
||||
git -C explicit-sha256 add B &&
|
||||
test_tick &&
|
||||
git -C explicit-sha256 commit -S -m "signed" B &&
|
||||
SHA256_B=$(git -C explicit-sha256 rev-parse dual-signed) &&
|
||||
|
||||
# Create the corresponding SHA-1 commit
|
||||
SHA1_B=$(git -C explicit-sha256 rev-parse --output-object-format=sha1 dual-signed) &&
|
||||
|
||||
# Check that the resulting SHA-1 commit has both signatures
|
||||
echo $SHA1_B | git -C explicit-sha256 cat-file --batch >out &&
|
||||
test_grep -E "^gpgsig " out &&
|
||||
test_grep -E "^gpgsig-sha256 " out
|
||||
'
|
||||
|
||||
test_expect_success GPG 'export and import of doubly signed commit' '
|
||||
git -C explicit-sha256 fast-export --signed-commits=verbatim dual-signed >output &&
|
||||
test_grep -E "^gpgsig sha1 openpgp" output &&
|
||||
test_grep -E "^gpgsig sha256 openpgp" output &&
|
||||
|
||||
(
|
||||
cd new &&
|
||||
git fast-import &&
|
||||
git cat-file commit refs/heads/dual-signed >actual &&
|
||||
test_grep -E "^gpgsig " actual &&
|
||||
test_grep -E "^gpgsig-sha256 " actual &&
|
||||
IMPORTED=$(git rev-parse refs/heads/dual-signed) &&
|
||||
if test "$GIT_DEFAULT_HASH" = "sha1"
|
||||
then
|
||||
test $SHA1_B = $IMPORTED
|
||||
else
|
||||
test $SHA256_B = $IMPORTED
|
||||
fi
|
||||
) <output
|
||||
'
|
||||
|
||||
test_done
|
||||
|
|
|
|||
Loading…
Reference in New Issue