Merge branch 'mg/verify-commit'
Add 'verify-commit' to be used in a way similar to 'verify-tag' is used. Further work on verifying the mergetags might be needed. * mg/verify-commit: t7510: test verify-commit t7510: exit for loop with test result verify-commit: scriptable commit signature verification gpg-interface: provide access to the payload gpg-interface: provide clear helper for struct signature_check
This commit is contained in:
commit
39177c7f18
28
Documentation/git-verify-commit.txt
Normal file
28
Documentation/git-verify-commit.txt
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
git-verify-commit(1)
|
||||||
|
====================
|
||||||
|
|
||||||
|
NAME
|
||||||
|
----
|
||||||
|
git-verify-commit - Check the GPG signature of commits
|
||||||
|
|
||||||
|
SYNOPSIS
|
||||||
|
--------
|
||||||
|
[verse]
|
||||||
|
'git verify-commit' <commit>...
|
||||||
|
|
||||||
|
DESCRIPTION
|
||||||
|
-----------
|
||||||
|
Validates the gpg signature created by 'git commit -S'.
|
||||||
|
|
||||||
|
OPTIONS
|
||||||
|
-------
|
||||||
|
-v::
|
||||||
|
--verbose::
|
||||||
|
Print the contents of the commit object before validating it.
|
||||||
|
|
||||||
|
<commit>...::
|
||||||
|
SHA-1 identifiers of Git commit objects.
|
||||||
|
|
||||||
|
GIT
|
||||||
|
---
|
||||||
|
Part of the linkgit:git[1] suite
|
1
Makefile
1
Makefile
@ -999,6 +999,7 @@ BUILTIN_OBJS += builtin/update-ref.o
|
|||||||
BUILTIN_OBJS += builtin/update-server-info.o
|
BUILTIN_OBJS += builtin/update-server-info.o
|
||||||
BUILTIN_OBJS += builtin/upload-archive.o
|
BUILTIN_OBJS += builtin/upload-archive.o
|
||||||
BUILTIN_OBJS += builtin/var.o
|
BUILTIN_OBJS += builtin/var.o
|
||||||
|
BUILTIN_OBJS += builtin/verify-commit.o
|
||||||
BUILTIN_OBJS += builtin/verify-pack.o
|
BUILTIN_OBJS += builtin/verify-pack.o
|
||||||
BUILTIN_OBJS += builtin/verify-tag.o
|
BUILTIN_OBJS += builtin/verify-tag.o
|
||||||
BUILTIN_OBJS += builtin/write-tree.o
|
BUILTIN_OBJS += builtin/write-tree.o
|
||||||
|
@ -128,6 +128,7 @@ extern int cmd_update_server_info(int argc, const char **argv, const char *prefi
|
|||||||
extern int cmd_upload_archive(int argc, const char **argv, const char *prefix);
|
extern int cmd_upload_archive(int argc, const char **argv, const char *prefix);
|
||||||
extern int cmd_upload_archive_writer(int argc, const char **argv, const char *prefix);
|
extern int cmd_upload_archive_writer(int argc, const char **argv, const char *prefix);
|
||||||
extern int cmd_var(int argc, const char **argv, const char *prefix);
|
extern int cmd_var(int argc, const char **argv, const char *prefix);
|
||||||
|
extern int cmd_verify_commit(int argc, const char **argv, const char *prefix);
|
||||||
extern int cmd_verify_tag(int argc, const char **argv, const char *prefix);
|
extern int cmd_verify_tag(int argc, const char **argv, const char *prefix);
|
||||||
extern int cmd_version(int argc, const char **argv, const char *prefix);
|
extern int cmd_version(int argc, const char **argv, const char *prefix);
|
||||||
extern int cmd_whatchanged(int argc, const char **argv, const char *prefix);
|
extern int cmd_whatchanged(int argc, const char **argv, const char *prefix);
|
||||||
|
@ -1282,10 +1282,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
|
|||||||
printf(_("Commit %s has a good GPG signature by %s\n"),
|
printf(_("Commit %s has a good GPG signature by %s\n"),
|
||||||
hex, signature_check.signer);
|
hex, signature_check.signer);
|
||||||
|
|
||||||
free(signature_check.gpg_output);
|
signature_check_clear(&signature_check);
|
||||||
free(signature_check.gpg_status);
|
|
||||||
free(signature_check.signer);
|
|
||||||
free(signature_check.key);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
93
builtin/verify-commit.c
Normal file
93
builtin/verify-commit.c
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
/*
|
||||||
|
* Builtin "git commit-commit"
|
||||||
|
*
|
||||||
|
* Copyright (c) 2014 Michael J Gruber <git@drmicha.warpmail.net>
|
||||||
|
*
|
||||||
|
* Based on git-verify-tag
|
||||||
|
*/
|
||||||
|
#include "cache.h"
|
||||||
|
#include "builtin.h"
|
||||||
|
#include "commit.h"
|
||||||
|
#include "run-command.h"
|
||||||
|
#include <signal.h>
|
||||||
|
#include "parse-options.h"
|
||||||
|
#include "gpg-interface.h"
|
||||||
|
|
||||||
|
static const char * const verify_commit_usage[] = {
|
||||||
|
N_("git verify-commit [-v|--verbose] <commit>..."),
|
||||||
|
NULL
|
||||||
|
};
|
||||||
|
|
||||||
|
static int run_gpg_verify(const unsigned char *sha1, const char *buf, unsigned long size, int verbose)
|
||||||
|
{
|
||||||
|
struct signature_check signature_check;
|
||||||
|
|
||||||
|
memset(&signature_check, 0, sizeof(signature_check));
|
||||||
|
|
||||||
|
check_commit_signature(lookup_commit(sha1), &signature_check);
|
||||||
|
|
||||||
|
if (verbose && signature_check.payload)
|
||||||
|
fputs(signature_check.payload, stdout);
|
||||||
|
|
||||||
|
if (signature_check.gpg_output)
|
||||||
|
fputs(signature_check.gpg_output, stderr);
|
||||||
|
|
||||||
|
signature_check_clear(&signature_check);
|
||||||
|
return signature_check.result != 'G';
|
||||||
|
}
|
||||||
|
|
||||||
|
static int verify_commit(const char *name, int verbose)
|
||||||
|
{
|
||||||
|
enum object_type type;
|
||||||
|
unsigned char sha1[20];
|
||||||
|
char *buf;
|
||||||
|
unsigned long size;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
if (get_sha1(name, sha1))
|
||||||
|
return error("commit '%s' not found.", name);
|
||||||
|
|
||||||
|
buf = read_sha1_file(sha1, &type, &size);
|
||||||
|
if (!buf)
|
||||||
|
return error("%s: unable to read file.", name);
|
||||||
|
if (type != OBJ_COMMIT)
|
||||||
|
return error("%s: cannot verify a non-commit object of type %s.",
|
||||||
|
name, typename(type));
|
||||||
|
|
||||||
|
ret = run_gpg_verify(sha1, buf, size, verbose);
|
||||||
|
|
||||||
|
free(buf);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int git_verify_commit_config(const char *var, const char *value, void *cb)
|
||||||
|
{
|
||||||
|
int status = git_gpg_config(var, value, cb);
|
||||||
|
if (status)
|
||||||
|
return status;
|
||||||
|
return git_default_config(var, value, cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
int cmd_verify_commit(int argc, const char **argv, const char *prefix)
|
||||||
|
{
|
||||||
|
int i = 1, verbose = 0, had_error = 0;
|
||||||
|
const struct option verify_commit_options[] = {
|
||||||
|
OPT__VERBOSE(&verbose, N_("print commit contents")),
|
||||||
|
OPT_END()
|
||||||
|
};
|
||||||
|
|
||||||
|
git_config(git_verify_commit_config, NULL);
|
||||||
|
|
||||||
|
argc = parse_options(argc, argv, prefix, verify_commit_options,
|
||||||
|
verify_commit_usage, PARSE_OPT_KEEP_ARGV0);
|
||||||
|
if (argc <= i)
|
||||||
|
usage_with_options(verify_commit_usage, verify_commit_options);
|
||||||
|
|
||||||
|
/* sometimes the program was terminated because this signal
|
||||||
|
* was received in the process of writing the gpg input: */
|
||||||
|
signal(SIGPIPE, SIG_IGN);
|
||||||
|
while (i < argc)
|
||||||
|
if (verify_commit(argv[i++], verbose))
|
||||||
|
had_error = 1;
|
||||||
|
return had_error;
|
||||||
|
}
|
@ -132,6 +132,7 @@ git-update-server-info synchingrepositories
|
|||||||
git-upload-archive synchelpers
|
git-upload-archive synchelpers
|
||||||
git-upload-pack synchelpers
|
git-upload-pack synchelpers
|
||||||
git-var plumbinginterrogators
|
git-var plumbinginterrogators
|
||||||
|
git-verify-commit ancillaryinterrogators
|
||||||
git-verify-pack plumbinginterrogators
|
git-verify-pack plumbinginterrogators
|
||||||
git-verify-tag ancillaryinterrogators
|
git-verify-tag ancillaryinterrogators
|
||||||
gitweb ancillaryinterrogators
|
gitweb ancillaryinterrogators
|
||||||
|
1
commit.c
1
commit.c
@ -1270,6 +1270,7 @@ void check_commit_signature(const struct commit* commit, struct signature_check
|
|||||||
&gpg_output, &gpg_status);
|
&gpg_output, &gpg_status);
|
||||||
if (status && !gpg_output.len)
|
if (status && !gpg_output.len)
|
||||||
goto out;
|
goto out;
|
||||||
|
sigc->payload = strbuf_detach(&payload, NULL);
|
||||||
sigc->gpg_output = strbuf_detach(&gpg_output, NULL);
|
sigc->gpg_output = strbuf_detach(&gpg_output, NULL);
|
||||||
sigc->gpg_status = strbuf_detach(&gpg_status, NULL);
|
sigc->gpg_status = strbuf_detach(&gpg_status, NULL);
|
||||||
parse_gpg_output(sigc);
|
parse_gpg_output(sigc);
|
||||||
|
1
git.c
1
git.c
@ -478,6 +478,7 @@ static struct cmd_struct commands[] = {
|
|||||||
{ "upload-archive", cmd_upload_archive },
|
{ "upload-archive", cmd_upload_archive },
|
||||||
{ "upload-archive--writer", cmd_upload_archive_writer },
|
{ "upload-archive--writer", cmd_upload_archive_writer },
|
||||||
{ "var", cmd_var, RUN_SETUP_GENTLY },
|
{ "var", cmd_var, RUN_SETUP_GENTLY },
|
||||||
|
{ "verify-commit", cmd_verify_commit, RUN_SETUP },
|
||||||
{ "verify-pack", cmd_verify_pack },
|
{ "verify-pack", cmd_verify_pack },
|
||||||
{ "verify-tag", cmd_verify_tag, RUN_SETUP },
|
{ "verify-tag", cmd_verify_tag, RUN_SETUP },
|
||||||
{ "version", cmd_version },
|
{ "version", cmd_version },
|
||||||
|
@ -7,6 +7,20 @@
|
|||||||
static char *configured_signing_key;
|
static char *configured_signing_key;
|
||||||
static const char *gpg_program = "gpg";
|
static const char *gpg_program = "gpg";
|
||||||
|
|
||||||
|
void signature_check_clear(struct signature_check *sigc)
|
||||||
|
{
|
||||||
|
free(sigc->payload);
|
||||||
|
free(sigc->gpg_output);
|
||||||
|
free(sigc->gpg_status);
|
||||||
|
free(sigc->signer);
|
||||||
|
free(sigc->key);
|
||||||
|
sigc->payload = NULL;
|
||||||
|
sigc->gpg_output = NULL;
|
||||||
|
sigc->gpg_status = NULL;
|
||||||
|
sigc->signer = NULL;
|
||||||
|
sigc->key = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
void set_signing_key(const char *key)
|
void set_signing_key(const char *key)
|
||||||
{
|
{
|
||||||
free(configured_signing_key);
|
free(configured_signing_key);
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
#define GPG_INTERFACE_H
|
#define GPG_INTERFACE_H
|
||||||
|
|
||||||
struct signature_check {
|
struct signature_check {
|
||||||
|
char *payload;
|
||||||
char *gpg_output;
|
char *gpg_output;
|
||||||
char *gpg_status;
|
char *gpg_status;
|
||||||
char result; /* 0 (not checked),
|
char result; /* 0 (not checked),
|
||||||
@ -13,6 +14,7 @@ struct signature_check {
|
|||||||
char *key;
|
char *key;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
extern void signature_check_clear(struct signature_check *sigc);
|
||||||
extern int sign_buffer(struct strbuf *buffer, struct strbuf *signature, const char *signing_key);
|
extern int sign_buffer(struct strbuf *buffer, struct strbuf *signature, const char *signing_key);
|
||||||
extern int verify_signed_buffer(const char *payload, size_t payload_size, const char *signature, size_t signature_size, struct strbuf *gpg_output, struct strbuf *gpg_status);
|
extern int verify_signed_buffer(const char *payload, size_t payload_size, const char *signature, size_t signature_size, struct strbuf *gpg_output, struct strbuf *gpg_status);
|
||||||
extern int git_gpg_config(const char *, const char *, void *);
|
extern int git_gpg_config(const char *, const char *, void *);
|
||||||
|
2
pretty.c
2
pretty.c
@ -1520,8 +1520,6 @@ void format_commit_message(const struct commit *commit,
|
|||||||
|
|
||||||
free(context.commit_encoding);
|
free(context.commit_encoding);
|
||||||
unuse_commit_buffer(commit, context.message);
|
unuse_commit_buffer(commit, context.message);
|
||||||
free(context.signature_check.gpg_output);
|
|
||||||
free(context.signature_check.signer);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void pp_header(struct pretty_print_context *pp,
|
static void pp_header(struct pretty_print_context *pp,
|
||||||
|
@ -48,10 +48,11 @@ test_expect_success GPG 'create signed commits' '
|
|||||||
git tag eighth-signed-alt
|
git tag eighth-signed-alt
|
||||||
'
|
'
|
||||||
|
|
||||||
test_expect_success GPG 'show signatures' '
|
test_expect_success GPG 'verify and show signatures' '
|
||||||
(
|
(
|
||||||
for commit in initial second merge fourth-signed fifth-signed sixth-signed seventh-signed
|
for commit in initial second merge fourth-signed fifth-signed sixth-signed seventh-signed
|
||||||
do
|
do
|
||||||
|
git verify-commit $commit &&
|
||||||
git show --pretty=short --show-signature $commit >actual &&
|
git show --pretty=short --show-signature $commit >actual &&
|
||||||
grep "Good signature from" actual &&
|
grep "Good signature from" actual &&
|
||||||
! grep "BAD signature from" actual &&
|
! grep "BAD signature from" actual &&
|
||||||
@ -61,6 +62,7 @@ test_expect_success GPG 'show signatures' '
|
|||||||
(
|
(
|
||||||
for commit in merge^2 fourth-unsigned sixth-unsigned seventh-unsigned
|
for commit in merge^2 fourth-unsigned sixth-unsigned seventh-unsigned
|
||||||
do
|
do
|
||||||
|
test_must_fail git verify-commit $commit &&
|
||||||
git show --pretty=short --show-signature $commit >actual &&
|
git show --pretty=short --show-signature $commit >actual &&
|
||||||
! grep "Good signature from" actual &&
|
! grep "Good signature from" actual &&
|
||||||
! grep "BAD signature from" actual &&
|
! grep "BAD signature from" actual &&
|
||||||
@ -79,11 +81,25 @@ test_expect_success GPG 'show signatures' '
|
|||||||
)
|
)
|
||||||
'
|
'
|
||||||
|
|
||||||
|
test_expect_success GPG 'show signed commit with signature' '
|
||||||
|
git show -s initial >commit &&
|
||||||
|
git show -s --show-signature initial >show &&
|
||||||
|
git verify-commit -v initial >verify.1 2>verify.2 &&
|
||||||
|
git cat-file commit initial >cat &&
|
||||||
|
grep -v "gpg: " show >show.commit &&
|
||||||
|
grep "gpg: " show >show.gpg &&
|
||||||
|
grep -v "^ " cat | grep -v "^gpgsig " >cat.commit &&
|
||||||
|
test_cmp show.commit commit &&
|
||||||
|
test_cmp show.gpg verify.2 &&
|
||||||
|
test_cmp cat.commit verify.1
|
||||||
|
'
|
||||||
|
|
||||||
test_expect_success GPG 'detect fudged signature' '
|
test_expect_success GPG 'detect fudged signature' '
|
||||||
git cat-file commit seventh-signed >raw &&
|
git cat-file commit seventh-signed >raw &&
|
||||||
|
|
||||||
sed -e "s/seventh/7th forged/" raw >forged1 &&
|
sed -e "s/seventh/7th forged/" raw >forged1 &&
|
||||||
git hash-object -w -t commit forged1 >forged1.commit &&
|
git hash-object -w -t commit forged1 >forged1.commit &&
|
||||||
|
! git verify-commit $(cat forged1.commit) &&
|
||||||
git show --pretty=short --show-signature $(cat forged1.commit) >actual1 &&
|
git show --pretty=short --show-signature $(cat forged1.commit) >actual1 &&
|
||||||
grep "BAD signature from" actual1 &&
|
grep "BAD signature from" actual1 &&
|
||||||
! grep "Good signature from" actual1
|
! grep "Good signature from" actual1
|
||||||
@ -94,6 +110,7 @@ test_expect_success GPG 'detect fudged signature with NUL' '
|
|||||||
cat raw >forged2 &&
|
cat raw >forged2 &&
|
||||||
echo Qwik | tr "Q" "\000" >>forged2 &&
|
echo Qwik | tr "Q" "\000" >>forged2 &&
|
||||||
git hash-object -w -t commit forged2 >forged2.commit &&
|
git hash-object -w -t commit forged2 >forged2.commit &&
|
||||||
|
! git verify-commit $(cat forged2.commit) &&
|
||||||
git show --pretty=short --show-signature $(cat forged2.commit) >actual2 &&
|
git show --pretty=short --show-signature $(cat forged2.commit) >actual2 &&
|
||||||
grep "BAD signature from" actual2 &&
|
grep "BAD signature from" actual2 &&
|
||||||
! grep "Good signature from" actual2
|
! grep "Good signature from" actual2
|
||||||
@ -102,6 +119,7 @@ test_expect_success GPG 'detect fudged signature with NUL' '
|
|||||||
test_expect_success GPG 'amending already signed commit' '
|
test_expect_success GPG 'amending already signed commit' '
|
||||||
git checkout fourth-signed^0 &&
|
git checkout fourth-signed^0 &&
|
||||||
git commit --amend -S --no-edit &&
|
git commit --amend -S --no-edit &&
|
||||||
|
git verify-commit HEAD &&
|
||||||
git show -s --show-signature HEAD >actual &&
|
git show -s --show-signature HEAD >actual &&
|
||||||
grep "Good signature from" actual &&
|
grep "Good signature from" actual &&
|
||||||
! grep "BAD signature from" actual
|
! grep "BAD signature from" actual
|
||||||
|
Loading…
Reference in New Issue
Block a user