Merge branch 'fs/ssh-signing'
Use ssh public crypto for object and push-cert signing. * fs/ssh-signing: ssh signing: test that gpg fails for unknown keys ssh signing: tests for logs, tags & push certs ssh signing: duplicate t7510 tests for commits ssh signing: verify signatures using ssh-keygen ssh signing: provide a textual signing_key_id ssh signing: retrieve a default key from ssh-agent ssh signing: add ssh key format and signing code ssh signing: add test prereqs ssh signing: preliminary refactoring and clean-up
This commit is contained in:
commit
18c6653da0
@ -11,13 +11,13 @@ gpg.program::
|
|||||||
|
|
||||||
gpg.format::
|
gpg.format::
|
||||||
Specifies which key format to use when signing with `--gpg-sign`.
|
Specifies which key format to use when signing with `--gpg-sign`.
|
||||||
Default is "openpgp" and another possible value is "x509".
|
Default is "openpgp". Other possible values are "x509", "ssh".
|
||||||
|
|
||||||
gpg.<format>.program::
|
gpg.<format>.program::
|
||||||
Use this to customize the program used for the signing format you
|
Use this to customize the program used for the signing format you
|
||||||
chose. (see `gpg.program` and `gpg.format`) `gpg.program` can still
|
chose. (see `gpg.program` and `gpg.format`) `gpg.program` can still
|
||||||
be used as a legacy synonym for `gpg.openpgp.program`. The default
|
be used as a legacy synonym for `gpg.openpgp.program`. The default
|
||||||
value for `gpg.x509.program` is "gpgsm".
|
value for `gpg.x509.program` is "gpgsm" and `gpg.ssh.program` is "ssh-keygen".
|
||||||
|
|
||||||
gpg.minTrustLevel::
|
gpg.minTrustLevel::
|
||||||
Specifies a minimum trust level for signature verification. If
|
Specifies a minimum trust level for signature verification. If
|
||||||
@ -33,3 +33,44 @@ gpg.minTrustLevel::
|
|||||||
* `marginal`
|
* `marginal`
|
||||||
* `fully`
|
* `fully`
|
||||||
* `ultimate`
|
* `ultimate`
|
||||||
|
|
||||||
|
gpg.ssh.defaultKeyCommand:
|
||||||
|
This command that will be run when user.signingkey is not set and a ssh
|
||||||
|
signature is requested. On successful exit a valid ssh public key is
|
||||||
|
expected in the first line of its output. To automatically use the first
|
||||||
|
available key from your ssh-agent set this to "ssh-add -L".
|
||||||
|
|
||||||
|
gpg.ssh.allowedSignersFile::
|
||||||
|
A file containing ssh public keys which you are willing to trust.
|
||||||
|
The file consists of one or more lines of principals followed by an ssh
|
||||||
|
public key.
|
||||||
|
e.g.: user1@example.com,user2@example.com ssh-rsa AAAAX1...
|
||||||
|
See ssh-keygen(1) "ALLOWED SIGNERS" for details.
|
||||||
|
The principal is only used to identify the key and is available when
|
||||||
|
verifying a signature.
|
||||||
|
+
|
||||||
|
SSH has no concept of trust levels like gpg does. To be able to differentiate
|
||||||
|
between valid signatures and trusted signatures the trust level of a signature
|
||||||
|
verification is set to `fully` when the public key is present in the allowedSignersFile.
|
||||||
|
Therefore to only mark fully trusted keys as verified set gpg.minTrustLevel to `fully`.
|
||||||
|
Otherwise valid but untrusted signatures will still verify but show no principal
|
||||||
|
name of the signer.
|
||||||
|
+
|
||||||
|
This file can be set to a location outside of the repository and every developer
|
||||||
|
maintains their own trust store. A central repository server could generate this
|
||||||
|
file automatically from ssh keys with push access to verify the code against.
|
||||||
|
In a corporate setting this file is probably generated at a global location
|
||||||
|
from automation that already handles developer ssh keys.
|
||||||
|
+
|
||||||
|
A repository that only allows signed commits can store the file
|
||||||
|
in the repository itself using a path relative to the top-level of the working tree.
|
||||||
|
This way only committers with an already valid key can add or change keys in the keyring.
|
||||||
|
+
|
||||||
|
Using a SSH CA key with the cert-authority option
|
||||||
|
(see ssh-keygen(1) "CERTIFICATES") is also valid.
|
||||||
|
|
||||||
|
gpg.ssh.revocationFile::
|
||||||
|
Either a SSH KRL or a list of revoked public keys (without the principal prefix).
|
||||||
|
See ssh-keygen(1) for details.
|
||||||
|
If a public key is found in this file then it will always be treated
|
||||||
|
as having trust level "never" and signatures will show as invalid.
|
||||||
|
@ -36,3 +36,10 @@ user.signingKey::
|
|||||||
commit, you can override the default selection with this variable.
|
commit, you can override the default selection with this variable.
|
||||||
This option is passed unchanged to gpg's --local-user parameter,
|
This option is passed unchanged to gpg's --local-user parameter,
|
||||||
so you may specify a key using any method that gpg supports.
|
so you may specify a key using any method that gpg supports.
|
||||||
|
If gpg.format is set to "ssh" this can contain the literal ssh public
|
||||||
|
key (e.g.: "ssh-rsa XXXXXX identifier") or a file which contains it and
|
||||||
|
corresponds to the private key used for signing. The private key
|
||||||
|
needs to be available via ssh-agent. Alternatively it can be set to
|
||||||
|
a file containing a private key directly. If not set git will call
|
||||||
|
gpg.ssh.defaultKeyCommand (e.g.: "ssh-add -L") and try to use the first
|
||||||
|
key available.
|
||||||
|
@ -132,6 +132,10 @@ static int receive_pack_config(const char *var, const char *value, void *cb)
|
|||||||
{
|
{
|
||||||
int status = parse_hide_refs_config(var, value, "receive");
|
int status = parse_hide_refs_config(var, value, "receive");
|
||||||
|
|
||||||
|
if (status)
|
||||||
|
return status;
|
||||||
|
|
||||||
|
status = git_gpg_config(var, value, NULL);
|
||||||
if (status)
|
if (status)
|
||||||
return status;
|
return status;
|
||||||
|
|
||||||
|
@ -528,11 +528,11 @@ static void fmt_merge_msg_sigs(struct strbuf *out)
|
|||||||
buf = payload.buf;
|
buf = payload.buf;
|
||||||
len = payload.len;
|
len = payload.len;
|
||||||
if (check_signature(payload.buf, payload.len, sig.buf,
|
if (check_signature(payload.buf, payload.len, sig.buf,
|
||||||
sig.len, &sigc) &&
|
sig.len, &sigc) &&
|
||||||
!sigc.gpg_output)
|
!sigc.output)
|
||||||
strbuf_addstr(&sig, "gpg verification failed.\n");
|
strbuf_addstr(&sig, "gpg verification failed.\n");
|
||||||
else
|
else
|
||||||
strbuf_addstr(&sig, sigc.gpg_output);
|
strbuf_addstr(&sig, sigc.output);
|
||||||
}
|
}
|
||||||
signature_check_clear(&sigc);
|
signature_check_clear(&sigc);
|
||||||
|
|
||||||
|
577
gpg-interface.c
577
gpg-interface.c
@ -3,11 +3,14 @@
|
|||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "run-command.h"
|
#include "run-command.h"
|
||||||
#include "strbuf.h"
|
#include "strbuf.h"
|
||||||
|
#include "dir.h"
|
||||||
#include "gpg-interface.h"
|
#include "gpg-interface.h"
|
||||||
#include "sigchain.h"
|
#include "sigchain.h"
|
||||||
#include "tempfile.h"
|
#include "tempfile.h"
|
||||||
|
#include "alias.h"
|
||||||
|
|
||||||
static char *configured_signing_key;
|
static char *configured_signing_key;
|
||||||
|
static const char *ssh_default_key_command, *ssh_allowed_signers, *ssh_revocation_file;
|
||||||
static enum signature_trust_level configured_min_trust_level = TRUST_UNDEFINED;
|
static enum signature_trust_level configured_min_trust_level = TRUST_UNDEFINED;
|
||||||
|
|
||||||
struct gpg_format {
|
struct gpg_format {
|
||||||
@ -15,6 +18,14 @@ struct gpg_format {
|
|||||||
const char *program;
|
const char *program;
|
||||||
const char **verify_args;
|
const char **verify_args;
|
||||||
const char **sigs;
|
const char **sigs;
|
||||||
|
int (*verify_signed_buffer)(struct signature_check *sigc,
|
||||||
|
struct gpg_format *fmt, const char *payload,
|
||||||
|
size_t payload_size, const char *signature,
|
||||||
|
size_t signature_size);
|
||||||
|
int (*sign_buffer)(struct strbuf *buffer, struct strbuf *signature,
|
||||||
|
const char *signing_key);
|
||||||
|
const char *(*get_default_key)(void);
|
||||||
|
const char *(*get_key_id)(void);
|
||||||
};
|
};
|
||||||
|
|
||||||
static const char *openpgp_verify_args[] = {
|
static const char *openpgp_verify_args[] = {
|
||||||
@ -35,14 +46,59 @@ static const char *x509_sigs[] = {
|
|||||||
NULL
|
NULL
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static const char *ssh_verify_args[] = { NULL };
|
||||||
|
static const char *ssh_sigs[] = {
|
||||||
|
"-----BEGIN SSH SIGNATURE-----",
|
||||||
|
NULL
|
||||||
|
};
|
||||||
|
|
||||||
|
static int verify_gpg_signed_buffer(struct signature_check *sigc,
|
||||||
|
struct gpg_format *fmt, const char *payload,
|
||||||
|
size_t payload_size, const char *signature,
|
||||||
|
size_t signature_size);
|
||||||
|
static int verify_ssh_signed_buffer(struct signature_check *sigc,
|
||||||
|
struct gpg_format *fmt, const char *payload,
|
||||||
|
size_t payload_size, const char *signature,
|
||||||
|
size_t signature_size);
|
||||||
|
static int sign_buffer_gpg(struct strbuf *buffer, struct strbuf *signature,
|
||||||
|
const char *signing_key);
|
||||||
|
static int sign_buffer_ssh(struct strbuf *buffer, struct strbuf *signature,
|
||||||
|
const char *signing_key);
|
||||||
|
|
||||||
|
static const char *get_default_ssh_signing_key(void);
|
||||||
|
|
||||||
|
static const char *get_ssh_key_id(void);
|
||||||
|
|
||||||
static struct gpg_format gpg_format[] = {
|
static struct gpg_format gpg_format[] = {
|
||||||
{ .name = "openpgp", .program = "gpg",
|
{
|
||||||
.verify_args = openpgp_verify_args,
|
.name = "openpgp",
|
||||||
.sigs = openpgp_sigs
|
.program = "gpg",
|
||||||
|
.verify_args = openpgp_verify_args,
|
||||||
|
.sigs = openpgp_sigs,
|
||||||
|
.verify_signed_buffer = verify_gpg_signed_buffer,
|
||||||
|
.sign_buffer = sign_buffer_gpg,
|
||||||
|
.get_default_key = NULL,
|
||||||
|
.get_key_id = NULL,
|
||||||
},
|
},
|
||||||
{ .name = "x509", .program = "gpgsm",
|
{
|
||||||
.verify_args = x509_verify_args,
|
.name = "x509",
|
||||||
.sigs = x509_sigs
|
.program = "gpgsm",
|
||||||
|
.verify_args = x509_verify_args,
|
||||||
|
.sigs = x509_sigs,
|
||||||
|
.verify_signed_buffer = verify_gpg_signed_buffer,
|
||||||
|
.sign_buffer = sign_buffer_gpg,
|
||||||
|
.get_default_key = NULL,
|
||||||
|
.get_key_id = NULL,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.name = "ssh",
|
||||||
|
.program = "ssh-keygen",
|
||||||
|
.verify_args = ssh_verify_args,
|
||||||
|
.sigs = ssh_sigs,
|
||||||
|
.verify_signed_buffer = verify_ssh_signed_buffer,
|
||||||
|
.sign_buffer = sign_buffer_ssh,
|
||||||
|
.get_default_key = get_default_ssh_signing_key,
|
||||||
|
.get_key_id = get_ssh_key_id,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -72,7 +128,7 @@ static struct gpg_format *get_format_by_sig(const char *sig)
|
|||||||
void signature_check_clear(struct signature_check *sigc)
|
void signature_check_clear(struct signature_check *sigc)
|
||||||
{
|
{
|
||||||
FREE_AND_NULL(sigc->payload);
|
FREE_AND_NULL(sigc->payload);
|
||||||
FREE_AND_NULL(sigc->gpg_output);
|
FREE_AND_NULL(sigc->output);
|
||||||
FREE_AND_NULL(sigc->gpg_status);
|
FREE_AND_NULL(sigc->gpg_status);
|
||||||
FREE_AND_NULL(sigc->signer);
|
FREE_AND_NULL(sigc->signer);
|
||||||
FREE_AND_NULL(sigc->key);
|
FREE_AND_NULL(sigc->key);
|
||||||
@ -257,16 +313,16 @@ error:
|
|||||||
FREE_AND_NULL(sigc->key);
|
FREE_AND_NULL(sigc->key);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int verify_signed_buffer(const char *payload, size_t payload_size,
|
static int verify_gpg_signed_buffer(struct signature_check *sigc,
|
||||||
const char *signature, size_t signature_size,
|
struct gpg_format *fmt, const char *payload,
|
||||||
struct strbuf *gpg_output,
|
size_t payload_size, const char *signature,
|
||||||
struct strbuf *gpg_status)
|
size_t signature_size)
|
||||||
{
|
{
|
||||||
struct child_process gpg = CHILD_PROCESS_INIT;
|
struct child_process gpg = CHILD_PROCESS_INIT;
|
||||||
struct gpg_format *fmt;
|
|
||||||
struct tempfile *temp;
|
struct tempfile *temp;
|
||||||
int ret;
|
int ret;
|
||||||
struct strbuf buf = STRBUF_INIT;
|
struct strbuf gpg_stdout = STRBUF_INIT;
|
||||||
|
struct strbuf gpg_stderr = STRBUF_INIT;
|
||||||
|
|
||||||
temp = mks_tempfile_t(".git_vtag_tmpXXXXXX");
|
temp = mks_tempfile_t(".git_vtag_tmpXXXXXX");
|
||||||
if (!temp)
|
if (!temp)
|
||||||
@ -279,10 +335,6 @@ static int verify_signed_buffer(const char *payload, size_t payload_size,
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt = get_format_by_sig(signature);
|
|
||||||
if (!fmt)
|
|
||||||
BUG("bad signature '%s'", signature);
|
|
||||||
|
|
||||||
strvec_push(&gpg.args, fmt->program);
|
strvec_push(&gpg.args, fmt->program);
|
||||||
strvec_pushv(&gpg.args, fmt->verify_args);
|
strvec_pushv(&gpg.args, fmt->verify_args);
|
||||||
strvec_pushl(&gpg.args,
|
strvec_pushl(&gpg.args,
|
||||||
@ -290,18 +342,216 @@ static int verify_signed_buffer(const char *payload, size_t payload_size,
|
|||||||
"--verify", temp->filename.buf, "-",
|
"--verify", temp->filename.buf, "-",
|
||||||
NULL);
|
NULL);
|
||||||
|
|
||||||
if (!gpg_status)
|
|
||||||
gpg_status = &buf;
|
|
||||||
|
|
||||||
sigchain_push(SIGPIPE, SIG_IGN);
|
sigchain_push(SIGPIPE, SIG_IGN);
|
||||||
ret = pipe_command(&gpg, payload, payload_size,
|
ret = pipe_command(&gpg, payload, payload_size, &gpg_stdout, 0,
|
||||||
gpg_status, 0, gpg_output, 0);
|
&gpg_stderr, 0);
|
||||||
sigchain_pop(SIGPIPE);
|
sigchain_pop(SIGPIPE);
|
||||||
|
|
||||||
delete_tempfile(&temp);
|
delete_tempfile(&temp);
|
||||||
|
|
||||||
ret |= !strstr(gpg_status->buf, "\n[GNUPG:] GOODSIG ");
|
ret |= !strstr(gpg_stdout.buf, "\n[GNUPG:] GOODSIG ");
|
||||||
strbuf_release(&buf); /* no matter it was used or not */
|
sigc->payload = xmemdupz(payload, payload_size);
|
||||||
|
sigc->output = strbuf_detach(&gpg_stderr, NULL);
|
||||||
|
sigc->gpg_status = strbuf_detach(&gpg_stdout, NULL);
|
||||||
|
|
||||||
|
parse_gpg_output(sigc);
|
||||||
|
|
||||||
|
strbuf_release(&gpg_stdout);
|
||||||
|
strbuf_release(&gpg_stderr);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void parse_ssh_output(struct signature_check *sigc)
|
||||||
|
{
|
||||||
|
const char *line, *principal, *search;
|
||||||
|
char *key = NULL;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ssh-keygen output should be:
|
||||||
|
* Good "git" signature for PRINCIPAL with RSA key SHA256:FINGERPRINT
|
||||||
|
*
|
||||||
|
* or for valid but unknown keys:
|
||||||
|
* Good "git" signature with RSA key SHA256:FINGERPRINT
|
||||||
|
*
|
||||||
|
* Note that "PRINCIPAL" can contain whitespace, "RSA" and
|
||||||
|
* "SHA256" part could be a different token that names of
|
||||||
|
* the algorithms used, and "FINGERPRINT" is a hexadecimal
|
||||||
|
* string. By finding the last occurence of " with ", we can
|
||||||
|
* reliably parse out the PRINCIPAL.
|
||||||
|
*/
|
||||||
|
sigc->result = 'B';
|
||||||
|
sigc->trust_level = TRUST_NEVER;
|
||||||
|
|
||||||
|
line = xmemdupz(sigc->output, strcspn(sigc->output, "\n"));
|
||||||
|
|
||||||
|
if (skip_prefix(line, "Good \"git\" signature for ", &line)) {
|
||||||
|
/* Valid signature and known principal */
|
||||||
|
sigc->result = 'G';
|
||||||
|
sigc->trust_level = TRUST_FULLY;
|
||||||
|
|
||||||
|
/* Search for the last "with" to get the full principal */
|
||||||
|
principal = line;
|
||||||
|
do {
|
||||||
|
search = strstr(line, " with ");
|
||||||
|
if (search)
|
||||||
|
line = search + 1;
|
||||||
|
} while (search != NULL);
|
||||||
|
sigc->signer = xmemdupz(principal, line - principal - 1);
|
||||||
|
} else if (skip_prefix(line, "Good \"git\" signature with ", &line)) {
|
||||||
|
/* Valid signature, but key unknown */
|
||||||
|
sigc->result = 'G';
|
||||||
|
sigc->trust_level = TRUST_UNDEFINED;
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
key = strstr(line, "key");
|
||||||
|
if (key) {
|
||||||
|
sigc->fingerprint = xstrdup(strstr(line, "key") + 4);
|
||||||
|
sigc->key = xstrdup(sigc->fingerprint);
|
||||||
|
} else {
|
||||||
|
/*
|
||||||
|
* Output did not match what we expected
|
||||||
|
* Treat the signature as bad
|
||||||
|
*/
|
||||||
|
sigc->result = 'B';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int verify_ssh_signed_buffer(struct signature_check *sigc,
|
||||||
|
struct gpg_format *fmt, const char *payload,
|
||||||
|
size_t payload_size, const char *signature,
|
||||||
|
size_t signature_size)
|
||||||
|
{
|
||||||
|
struct child_process ssh_keygen = CHILD_PROCESS_INIT;
|
||||||
|
struct tempfile *buffer_file;
|
||||||
|
int ret = -1;
|
||||||
|
const char *line;
|
||||||
|
size_t trust_size;
|
||||||
|
char *principal;
|
||||||
|
struct strbuf ssh_principals_out = STRBUF_INIT;
|
||||||
|
struct strbuf ssh_principals_err = STRBUF_INIT;
|
||||||
|
struct strbuf ssh_keygen_out = STRBUF_INIT;
|
||||||
|
struct strbuf ssh_keygen_err = STRBUF_INIT;
|
||||||
|
|
||||||
|
if (!ssh_allowed_signers) {
|
||||||
|
error(_("gpg.ssh.allowedSignersFile needs to be configured and exist for ssh signature verification"));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer_file = mks_tempfile_t(".git_vtag_tmpXXXXXX");
|
||||||
|
if (!buffer_file)
|
||||||
|
return error_errno(_("could not create temporary file"));
|
||||||
|
if (write_in_full(buffer_file->fd, signature, signature_size) < 0 ||
|
||||||
|
close_tempfile_gently(buffer_file) < 0) {
|
||||||
|
error_errno(_("failed writing detached signature to '%s'"),
|
||||||
|
buffer_file->filename.buf);
|
||||||
|
delete_tempfile(&buffer_file);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Find the principal from the signers */
|
||||||
|
strvec_pushl(&ssh_keygen.args, fmt->program,
|
||||||
|
"-Y", "find-principals",
|
||||||
|
"-f", ssh_allowed_signers,
|
||||||
|
"-s", buffer_file->filename.buf,
|
||||||
|
NULL);
|
||||||
|
ret = pipe_command(&ssh_keygen, NULL, 0, &ssh_principals_out, 0,
|
||||||
|
&ssh_principals_err, 0);
|
||||||
|
if (ret && strstr(ssh_principals_err.buf, "usage:")) {
|
||||||
|
error(_("ssh-keygen -Y find-principals/verify is needed for ssh signature verification (available in openssh version 8.2p1+)"));
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
if (ret || !ssh_principals_out.len) {
|
||||||
|
/*
|
||||||
|
* We did not find a matching principal in the allowedSigners
|
||||||
|
* Check without validation
|
||||||
|
*/
|
||||||
|
child_process_init(&ssh_keygen);
|
||||||
|
strvec_pushl(&ssh_keygen.args, fmt->program,
|
||||||
|
"-Y", "check-novalidate",
|
||||||
|
"-n", "git",
|
||||||
|
"-s", buffer_file->filename.buf,
|
||||||
|
NULL);
|
||||||
|
pipe_command(&ssh_keygen, payload, payload_size,
|
||||||
|
&ssh_keygen_out, 0, &ssh_keygen_err, 0);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Fail on unknown keys
|
||||||
|
* we still call check-novalidate to display the signature info
|
||||||
|
*/
|
||||||
|
ret = -1;
|
||||||
|
} else {
|
||||||
|
/* Check every principal we found (one per line) */
|
||||||
|
for (line = ssh_principals_out.buf; *line;
|
||||||
|
line = strchrnul(line + 1, '\n')) {
|
||||||
|
while (*line == '\n')
|
||||||
|
line++;
|
||||||
|
if (!*line)
|
||||||
|
break;
|
||||||
|
|
||||||
|
trust_size = strcspn(line, "\n");
|
||||||
|
principal = xmemdupz(line, trust_size);
|
||||||
|
|
||||||
|
child_process_init(&ssh_keygen);
|
||||||
|
strbuf_release(&ssh_keygen_out);
|
||||||
|
strbuf_release(&ssh_keygen_err);
|
||||||
|
strvec_push(&ssh_keygen.args, fmt->program);
|
||||||
|
/*
|
||||||
|
* We found principals
|
||||||
|
* Try with each until we find a match
|
||||||
|
*/
|
||||||
|
strvec_pushl(&ssh_keygen.args, "-Y", "verify",
|
||||||
|
"-n", "git",
|
||||||
|
"-f", ssh_allowed_signers,
|
||||||
|
"-I", principal,
|
||||||
|
"-s", buffer_file->filename.buf,
|
||||||
|
NULL);
|
||||||
|
|
||||||
|
if (ssh_revocation_file) {
|
||||||
|
if (file_exists(ssh_revocation_file)) {
|
||||||
|
strvec_pushl(&ssh_keygen.args, "-r",
|
||||||
|
ssh_revocation_file, NULL);
|
||||||
|
} else {
|
||||||
|
warning(_("ssh signing revocation file configured but not found: %s"),
|
||||||
|
ssh_revocation_file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sigchain_push(SIGPIPE, SIG_IGN);
|
||||||
|
ret = pipe_command(&ssh_keygen, payload, payload_size,
|
||||||
|
&ssh_keygen_out, 0, &ssh_keygen_err, 0);
|
||||||
|
sigchain_pop(SIGPIPE);
|
||||||
|
|
||||||
|
FREE_AND_NULL(principal);
|
||||||
|
|
||||||
|
if (!ret)
|
||||||
|
ret = !starts_with(ssh_keygen_out.buf, "Good");
|
||||||
|
|
||||||
|
if (!ret)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sigc->payload = xmemdupz(payload, payload_size);
|
||||||
|
strbuf_stripspace(&ssh_keygen_out, 0);
|
||||||
|
strbuf_stripspace(&ssh_keygen_err, 0);
|
||||||
|
/* Add stderr outputs to show the user actual ssh-keygen errors */
|
||||||
|
strbuf_add(&ssh_keygen_out, ssh_principals_err.buf, ssh_principals_err.len);
|
||||||
|
strbuf_add(&ssh_keygen_out, ssh_keygen_err.buf, ssh_keygen_err.len);
|
||||||
|
sigc->output = strbuf_detach(&ssh_keygen_out, NULL);
|
||||||
|
sigc->gpg_status = xstrdup(sigc->output);
|
||||||
|
|
||||||
|
parse_ssh_output(sigc);
|
||||||
|
|
||||||
|
out:
|
||||||
|
if (buffer_file)
|
||||||
|
delete_tempfile(&buffer_file);
|
||||||
|
strbuf_release(&ssh_principals_out);
|
||||||
|
strbuf_release(&ssh_principals_err);
|
||||||
|
strbuf_release(&ssh_keygen_out);
|
||||||
|
strbuf_release(&ssh_keygen_err);
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
@ -309,35 +559,32 @@ static int verify_signed_buffer(const char *payload, size_t payload_size,
|
|||||||
int check_signature(const char *payload, size_t plen, const char *signature,
|
int check_signature(const char *payload, size_t plen, const char *signature,
|
||||||
size_t slen, struct signature_check *sigc)
|
size_t slen, struct signature_check *sigc)
|
||||||
{
|
{
|
||||||
struct strbuf gpg_output = STRBUF_INIT;
|
struct gpg_format *fmt;
|
||||||
struct strbuf gpg_status = STRBUF_INIT;
|
|
||||||
int status;
|
int status;
|
||||||
|
|
||||||
sigc->result = 'N';
|
sigc->result = 'N';
|
||||||
sigc->trust_level = -1;
|
sigc->trust_level = -1;
|
||||||
|
|
||||||
status = verify_signed_buffer(payload, plen, signature, slen,
|
fmt = get_format_by_sig(signature);
|
||||||
&gpg_output, &gpg_status);
|
if (!fmt)
|
||||||
if (status && !gpg_output.len)
|
die(_("bad/incompatible signature '%s'"), signature);
|
||||||
goto out;
|
|
||||||
sigc->payload = xmemdupz(payload, plen);
|
status = fmt->verify_signed_buffer(sigc, fmt, payload, plen, signature,
|
||||||
sigc->gpg_output = strbuf_detach(&gpg_output, NULL);
|
slen);
|
||||||
sigc->gpg_status = strbuf_detach(&gpg_status, NULL);
|
|
||||||
parse_gpg_output(sigc);
|
if (status && !sigc->output)
|
||||||
|
return !!status;
|
||||||
|
|
||||||
status |= sigc->result != 'G';
|
status |= sigc->result != 'G';
|
||||||
status |= sigc->trust_level < configured_min_trust_level;
|
status |= sigc->trust_level < configured_min_trust_level;
|
||||||
|
|
||||||
out:
|
|
||||||
strbuf_release(&gpg_status);
|
|
||||||
strbuf_release(&gpg_output);
|
|
||||||
|
|
||||||
return !!status;
|
return !!status;
|
||||||
}
|
}
|
||||||
|
|
||||||
void print_signature_buffer(const struct signature_check *sigc, unsigned flags)
|
void print_signature_buffer(const struct signature_check *sigc, unsigned flags)
|
||||||
{
|
{
|
||||||
const char *output = flags & GPG_VERIFY_RAW ?
|
const char *output = flags & GPG_VERIFY_RAW ? sigc->gpg_status :
|
||||||
sigc->gpg_status : sigc->gpg_output;
|
sigc->output;
|
||||||
|
|
||||||
if (flags & GPG_VERIFY_VERBOSE && sigc->payload)
|
if (flags & GPG_VERIFY_VERBOSE && sigc->payload)
|
||||||
fputs(sigc->payload, stdout);
|
fputs(sigc->payload, stdout);
|
||||||
@ -419,12 +666,33 @@ int git_gpg_config(const char *var, const char *value, void *cb)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!strcmp(var, "gpg.ssh.defaultkeycommand")) {
|
||||||
|
if (!value)
|
||||||
|
return config_error_nonbool(var);
|
||||||
|
return git_config_string(&ssh_default_key_command, var, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!strcmp(var, "gpg.ssh.allowedsignersfile")) {
|
||||||
|
if (!value)
|
||||||
|
return config_error_nonbool(var);
|
||||||
|
return git_config_pathname(&ssh_allowed_signers, var, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!strcmp(var, "gpg.ssh.revocationfile")) {
|
||||||
|
if (!value)
|
||||||
|
return config_error_nonbool(var);
|
||||||
|
return git_config_pathname(&ssh_revocation_file, var, value);
|
||||||
|
}
|
||||||
|
|
||||||
if (!strcmp(var, "gpg.program") || !strcmp(var, "gpg.openpgp.program"))
|
if (!strcmp(var, "gpg.program") || !strcmp(var, "gpg.openpgp.program"))
|
||||||
fmtname = "openpgp";
|
fmtname = "openpgp";
|
||||||
|
|
||||||
if (!strcmp(var, "gpg.x509.program"))
|
if (!strcmp(var, "gpg.x509.program"))
|
||||||
fmtname = "x509";
|
fmtname = "x509";
|
||||||
|
|
||||||
|
if (!strcmp(var, "gpg.ssh.program"))
|
||||||
|
fmtname = "ssh";
|
||||||
|
|
||||||
if (fmtname) {
|
if (fmtname) {
|
||||||
fmt = get_format_by_name(fmtname);
|
fmt = get_format_by_name(fmtname);
|
||||||
return git_config_string(&fmt->program, var, value);
|
return git_config_string(&fmt->program, var, value);
|
||||||
@ -433,18 +701,144 @@ int git_gpg_config(const char *var, const char *value, void *cb)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static char *get_ssh_key_fingerprint(const char *signing_key)
|
||||||
|
{
|
||||||
|
struct child_process ssh_keygen = CHILD_PROCESS_INIT;
|
||||||
|
int ret = -1;
|
||||||
|
struct strbuf fingerprint_stdout = STRBUF_INIT;
|
||||||
|
struct strbuf **fingerprint;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* With SSH Signing this can contain a filename or a public key
|
||||||
|
* For textual representation we usually want a fingerprint
|
||||||
|
*/
|
||||||
|
if (starts_with(signing_key, "ssh-")) {
|
||||||
|
strvec_pushl(&ssh_keygen.args, "ssh-keygen", "-lf", "-", NULL);
|
||||||
|
ret = pipe_command(&ssh_keygen, signing_key,
|
||||||
|
strlen(signing_key), &fingerprint_stdout, 0,
|
||||||
|
NULL, 0);
|
||||||
|
} else {
|
||||||
|
strvec_pushl(&ssh_keygen.args, "ssh-keygen", "-lf",
|
||||||
|
configured_signing_key, NULL);
|
||||||
|
ret = pipe_command(&ssh_keygen, NULL, 0, &fingerprint_stdout, 0,
|
||||||
|
NULL, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!!ret)
|
||||||
|
die_errno(_("failed to get the ssh fingerprint for key '%s'"),
|
||||||
|
signing_key);
|
||||||
|
|
||||||
|
fingerprint = strbuf_split_max(&fingerprint_stdout, ' ', 3);
|
||||||
|
if (!fingerprint[1])
|
||||||
|
die_errno(_("failed to get the ssh fingerprint for key '%s'"),
|
||||||
|
signing_key);
|
||||||
|
|
||||||
|
return strbuf_detach(fingerprint[1], NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Returns the first public key from an ssh-agent to use for signing */
|
||||||
|
static const char *get_default_ssh_signing_key(void)
|
||||||
|
{
|
||||||
|
struct child_process ssh_default_key = CHILD_PROCESS_INIT;
|
||||||
|
int ret = -1;
|
||||||
|
struct strbuf key_stdout = STRBUF_INIT, key_stderr = STRBUF_INIT;
|
||||||
|
struct strbuf **keys;
|
||||||
|
char *key_command = NULL;
|
||||||
|
const char **argv;
|
||||||
|
int n;
|
||||||
|
char *default_key = NULL;
|
||||||
|
|
||||||
|
if (!ssh_default_key_command)
|
||||||
|
die(_("either user.signingkey or gpg.ssh.defaultKeyCommand needs to be configured"));
|
||||||
|
|
||||||
|
key_command = xstrdup(ssh_default_key_command);
|
||||||
|
n = split_cmdline(key_command, &argv);
|
||||||
|
|
||||||
|
if (n < 0)
|
||||||
|
die("malformed build-time gpg.ssh.defaultKeyCommand: %s",
|
||||||
|
split_cmdline_strerror(n));
|
||||||
|
|
||||||
|
strvec_pushv(&ssh_default_key.args, argv);
|
||||||
|
ret = pipe_command(&ssh_default_key, NULL, 0, &key_stdout, 0,
|
||||||
|
&key_stderr, 0);
|
||||||
|
|
||||||
|
if (!ret) {
|
||||||
|
keys = strbuf_split_max(&key_stdout, '\n', 2);
|
||||||
|
if (keys[0] && starts_with(keys[0]->buf, "ssh-")) {
|
||||||
|
default_key = strbuf_detach(keys[0], NULL);
|
||||||
|
} else {
|
||||||
|
warning(_("gpg.ssh.defaultKeycommand succeeded but returned no keys: %s %s"),
|
||||||
|
key_stderr.buf, key_stdout.buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
strbuf_list_free(keys);
|
||||||
|
} else {
|
||||||
|
warning(_("gpg.ssh.defaultKeyCommand failed: %s %s"),
|
||||||
|
key_stderr.buf, key_stdout.buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
free(key_command);
|
||||||
|
free(argv);
|
||||||
|
strbuf_release(&key_stdout);
|
||||||
|
|
||||||
|
return default_key;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *get_ssh_key_id(void) {
|
||||||
|
return get_ssh_key_fingerprint(get_signing_key());
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Returns a textual but unique representation of the signing key */
|
||||||
|
const char *get_signing_key_id(void)
|
||||||
|
{
|
||||||
|
if (use_format->get_key_id) {
|
||||||
|
return use_format->get_key_id();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* GPG/GPGSM only store a key id on this variable */
|
||||||
|
return get_signing_key();
|
||||||
|
}
|
||||||
|
|
||||||
const char *get_signing_key(void)
|
const char *get_signing_key(void)
|
||||||
{
|
{
|
||||||
if (configured_signing_key)
|
if (configured_signing_key)
|
||||||
return configured_signing_key;
|
return configured_signing_key;
|
||||||
return git_committer_info(IDENT_STRICT|IDENT_NO_DATE);
|
if (use_format->get_default_key) {
|
||||||
|
return use_format->get_default_key();
|
||||||
|
}
|
||||||
|
|
||||||
|
return git_committer_info(IDENT_STRICT | IDENT_NO_DATE);
|
||||||
}
|
}
|
||||||
|
|
||||||
int sign_buffer(struct strbuf *buffer, struct strbuf *signature, const char *signing_key)
|
int sign_buffer(struct strbuf *buffer, struct strbuf *signature, const char *signing_key)
|
||||||
|
{
|
||||||
|
return use_format->sign_buffer(buffer, signature, signing_key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Strip CR from the line endings, in case we are on Windows.
|
||||||
|
* NEEDSWORK: make it trim only CRs before LFs and rename
|
||||||
|
*/
|
||||||
|
static void remove_cr_after(struct strbuf *buffer, size_t offset)
|
||||||
|
{
|
||||||
|
size_t i, j;
|
||||||
|
|
||||||
|
for (i = j = offset; i < buffer->len; i++) {
|
||||||
|
if (buffer->buf[i] != '\r') {
|
||||||
|
if (i != j)
|
||||||
|
buffer->buf[j] = buffer->buf[i];
|
||||||
|
j++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
strbuf_setlen(buffer, j);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int sign_buffer_gpg(struct strbuf *buffer, struct strbuf *signature,
|
||||||
|
const char *signing_key)
|
||||||
{
|
{
|
||||||
struct child_process gpg = CHILD_PROCESS_INIT;
|
struct child_process gpg = CHILD_PROCESS_INIT;
|
||||||
int ret;
|
int ret;
|
||||||
size_t i, j, bottom;
|
size_t bottom;
|
||||||
struct strbuf gpg_status = STRBUF_INIT;
|
struct strbuf gpg_status = STRBUF_INIT;
|
||||||
|
|
||||||
strvec_pushl(&gpg.args,
|
strvec_pushl(&gpg.args,
|
||||||
@ -470,13 +864,98 @@ int sign_buffer(struct strbuf *buffer, struct strbuf *signature, const char *sig
|
|||||||
return error(_("gpg failed to sign the data"));
|
return error(_("gpg failed to sign the data"));
|
||||||
|
|
||||||
/* Strip CR from the line endings, in case we are on Windows. */
|
/* Strip CR from the line endings, in case we are on Windows. */
|
||||||
for (i = j = bottom; i < signature->len; i++)
|
remove_cr_after(signature, bottom);
|
||||||
if (signature->buf[i] != '\r') {
|
|
||||||
if (i != j)
|
|
||||||
signature->buf[j] = signature->buf[i];
|
|
||||||
j++;
|
|
||||||
}
|
|
||||||
strbuf_setlen(signature, j);
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int sign_buffer_ssh(struct strbuf *buffer, struct strbuf *signature,
|
||||||
|
const char *signing_key)
|
||||||
|
{
|
||||||
|
struct child_process signer = CHILD_PROCESS_INIT;
|
||||||
|
int ret = -1;
|
||||||
|
size_t bottom, keylen;
|
||||||
|
struct strbuf signer_stderr = STRBUF_INIT;
|
||||||
|
struct tempfile *key_file = NULL, *buffer_file = NULL;
|
||||||
|
char *ssh_signing_key_file = NULL;
|
||||||
|
struct strbuf ssh_signature_filename = STRBUF_INIT;
|
||||||
|
|
||||||
|
if (!signing_key || signing_key[0] == '\0')
|
||||||
|
return error(
|
||||||
|
_("user.signingkey needs to be set for ssh signing"));
|
||||||
|
|
||||||
|
if (starts_with(signing_key, "ssh-")) {
|
||||||
|
/* A literal ssh key */
|
||||||
|
key_file = mks_tempfile_t(".git_signing_key_tmpXXXXXX");
|
||||||
|
if (!key_file)
|
||||||
|
return error_errno(
|
||||||
|
_("could not create temporary file"));
|
||||||
|
keylen = strlen(signing_key);
|
||||||
|
if (write_in_full(key_file->fd, signing_key, keylen) < 0 ||
|
||||||
|
close_tempfile_gently(key_file) < 0) {
|
||||||
|
error_errno(_("failed writing ssh signing key to '%s'"),
|
||||||
|
key_file->filename.buf);
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
ssh_signing_key_file = strbuf_detach(&key_file->filename, NULL);
|
||||||
|
} else {
|
||||||
|
/* We assume a file */
|
||||||
|
ssh_signing_key_file = expand_user_path(signing_key, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer_file = mks_tempfile_t(".git_signing_buffer_tmpXXXXXX");
|
||||||
|
if (!buffer_file) {
|
||||||
|
error_errno(_("could not create temporary file"));
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (write_in_full(buffer_file->fd, buffer->buf, buffer->len) < 0 ||
|
||||||
|
close_tempfile_gently(buffer_file) < 0) {
|
||||||
|
error_errno(_("failed writing ssh signing key buffer to '%s'"),
|
||||||
|
buffer_file->filename.buf);
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
strvec_pushl(&signer.args, use_format->program,
|
||||||
|
"-Y", "sign",
|
||||||
|
"-n", "git",
|
||||||
|
"-f", ssh_signing_key_file,
|
||||||
|
buffer_file->filename.buf,
|
||||||
|
NULL);
|
||||||
|
|
||||||
|
sigchain_push(SIGPIPE, SIG_IGN);
|
||||||
|
ret = pipe_command(&signer, NULL, 0, NULL, 0, &signer_stderr, 0);
|
||||||
|
sigchain_pop(SIGPIPE);
|
||||||
|
|
||||||
|
if (ret) {
|
||||||
|
if (strstr(signer_stderr.buf, "usage:"))
|
||||||
|
error(_("ssh-keygen -Y sign is needed for ssh signing (available in openssh version 8.2p1+)"));
|
||||||
|
|
||||||
|
error("%s", signer_stderr.buf);
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
bottom = signature->len;
|
||||||
|
|
||||||
|
strbuf_addbuf(&ssh_signature_filename, &buffer_file->filename);
|
||||||
|
strbuf_addstr(&ssh_signature_filename, ".sig");
|
||||||
|
if (strbuf_read_file(signature, ssh_signature_filename.buf, 0) < 0) {
|
||||||
|
error_errno(
|
||||||
|
_("failed reading ssh signing data buffer from '%s'"),
|
||||||
|
ssh_signature_filename.buf);
|
||||||
|
}
|
||||||
|
unlink_or_warn(ssh_signature_filename.buf);
|
||||||
|
|
||||||
|
/* Strip CR from the line endings, in case we are on Windows. */
|
||||||
|
remove_cr_after(signature, bottom);
|
||||||
|
|
||||||
|
out:
|
||||||
|
if (key_file)
|
||||||
|
delete_tempfile(&key_file);
|
||||||
|
if (buffer_file)
|
||||||
|
delete_tempfile(&buffer_file);
|
||||||
|
strbuf_release(&signer_stderr);
|
||||||
|
strbuf_release(&ssh_signature_filename);
|
||||||
|
FREE_AND_NULL(ssh_signing_key_file);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
@ -17,7 +17,7 @@ enum signature_trust_level {
|
|||||||
|
|
||||||
struct signature_check {
|
struct signature_check {
|
||||||
char *payload;
|
char *payload;
|
||||||
char *gpg_output;
|
char *output;
|
||||||
char *gpg_status;
|
char *gpg_status;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -64,6 +64,12 @@ int sign_buffer(struct strbuf *buffer, struct strbuf *signature,
|
|||||||
int git_gpg_config(const char *, const char *, void *);
|
int git_gpg_config(const char *, const char *, void *);
|
||||||
void set_signing_key(const char *);
|
void set_signing_key(const char *);
|
||||||
const char *get_signing_key(void);
|
const char *get_signing_key(void);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Returns a textual unique representation of the signing key in use
|
||||||
|
* Either a GPG KeyID or a SSH Key Fingerprint
|
||||||
|
*/
|
||||||
|
const char *get_signing_key_id(void);
|
||||||
int check_signature(const char *payload, size_t plen,
|
int check_signature(const char *payload, size_t plen,
|
||||||
const char *signature, size_t slen,
|
const char *signature, size_t slen,
|
||||||
struct signature_check *sigc);
|
struct signature_check *sigc);
|
||||||
|
@ -515,10 +515,10 @@ static void show_signature(struct rev_info *opt, struct commit *commit)
|
|||||||
|
|
||||||
status = check_signature(payload.buf, payload.len, signature.buf,
|
status = check_signature(payload.buf, payload.len, signature.buf,
|
||||||
signature.len, &sigc);
|
signature.len, &sigc);
|
||||||
if (status && !sigc.gpg_output)
|
if (status && !sigc.output)
|
||||||
show_sig_lines(opt, status, "No signature\n");
|
show_sig_lines(opt, status, "No signature\n");
|
||||||
else
|
else
|
||||||
show_sig_lines(opt, status, sigc.gpg_output);
|
show_sig_lines(opt, status, sigc.output);
|
||||||
signature_check_clear(&sigc);
|
signature_check_clear(&sigc);
|
||||||
|
|
||||||
out:
|
out:
|
||||||
@ -585,8 +585,8 @@ static int show_one_mergetag(struct commit *commit,
|
|||||||
/* could have a good signature */
|
/* could have a good signature */
|
||||||
status = check_signature(payload.buf, payload.len,
|
status = check_signature(payload.buf, payload.len,
|
||||||
signature.buf, signature.len, &sigc);
|
signature.buf, signature.len, &sigc);
|
||||||
if (sigc.gpg_output)
|
if (sigc.output)
|
||||||
strbuf_addstr(&verify_message, sigc.gpg_output);
|
strbuf_addstr(&verify_message, sigc.output);
|
||||||
else
|
else
|
||||||
strbuf_addstr(&verify_message, "No signature\n");
|
strbuf_addstr(&verify_message, "No signature\n");
|
||||||
signature_check_clear(&sigc);
|
signature_check_clear(&sigc);
|
||||||
|
4
pretty.c
4
pretty.c
@ -1436,8 +1436,8 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */
|
|||||||
check_commit_signature(c->commit, &(c->signature_check));
|
check_commit_signature(c->commit, &(c->signature_check));
|
||||||
switch (placeholder[1]) {
|
switch (placeholder[1]) {
|
||||||
case 'G':
|
case 'G':
|
||||||
if (c->signature_check.gpg_output)
|
if (c->signature_check.output)
|
||||||
strbuf_addstr(sb, c->signature_check.gpg_output);
|
strbuf_addstr(sb, c->signature_check.output);
|
||||||
break;
|
break;
|
||||||
case '?':
|
case '?':
|
||||||
switch (c->signature_check.result) {
|
switch (c->signature_check.result) {
|
||||||
|
@ -341,13 +341,13 @@ static int generate_push_cert(struct strbuf *req_buf,
|
|||||||
{
|
{
|
||||||
const struct ref *ref;
|
const struct ref *ref;
|
||||||
struct string_list_item *item;
|
struct string_list_item *item;
|
||||||
char *signing_key = xstrdup(get_signing_key());
|
char *signing_key_id = xstrdup(get_signing_key_id());
|
||||||
const char *cp, *np;
|
const char *cp, *np;
|
||||||
struct strbuf cert = STRBUF_INIT;
|
struct strbuf cert = STRBUF_INIT;
|
||||||
int update_seen = 0;
|
int update_seen = 0;
|
||||||
|
|
||||||
strbuf_addstr(&cert, "certificate version 0.1\n");
|
strbuf_addstr(&cert, "certificate version 0.1\n");
|
||||||
strbuf_addf(&cert, "pusher %s ", signing_key);
|
strbuf_addf(&cert, "pusher %s ", signing_key_id);
|
||||||
datestamp(&cert);
|
datestamp(&cert);
|
||||||
strbuf_addch(&cert, '\n');
|
strbuf_addch(&cert, '\n');
|
||||||
if (args->url && *args->url) {
|
if (args->url && *args->url) {
|
||||||
@ -374,7 +374,7 @@ static int generate_push_cert(struct strbuf *req_buf,
|
|||||||
if (!update_seen)
|
if (!update_seen)
|
||||||
goto free_return;
|
goto free_return;
|
||||||
|
|
||||||
if (sign_buffer(&cert, &cert, signing_key))
|
if (sign_buffer(&cert, &cert, get_signing_key()))
|
||||||
die(_("failed to sign the push certificate"));
|
die(_("failed to sign the push certificate"));
|
||||||
|
|
||||||
packet_buf_write(req_buf, "push-cert%c%s", 0, cap_string);
|
packet_buf_write(req_buf, "push-cert%c%s", 0, cap_string);
|
||||||
@ -386,7 +386,7 @@ static int generate_push_cert(struct strbuf *req_buf,
|
|||||||
packet_buf_write(req_buf, "push-cert-end\n");
|
packet_buf_write(req_buf, "push-cert-end\n");
|
||||||
|
|
||||||
free_return:
|
free_return:
|
||||||
free(signing_key);
|
free(signing_key_id);
|
||||||
strbuf_release(&cert);
|
strbuf_release(&cert);
|
||||||
return update_seen;
|
return update_seen;
|
||||||
}
|
}
|
||||||
|
28
t/lib-gpg.sh
28
t/lib-gpg.sh
@ -87,6 +87,34 @@ test_lazy_prereq RFC1991 '
|
|||||||
echo | gpg --homedir "${GNUPGHOME}" -b --rfc1991 >/dev/null
|
echo | gpg --homedir "${GNUPGHOME}" -b --rfc1991 >/dev/null
|
||||||
'
|
'
|
||||||
|
|
||||||
|
GPGSSH_KEY_PRIMARY="${GNUPGHOME}/ed25519_ssh_signing_key"
|
||||||
|
GPGSSH_KEY_SECONDARY="${GNUPGHOME}/rsa_2048_ssh_signing_key"
|
||||||
|
GPGSSH_KEY_UNTRUSTED="${GNUPGHOME}/untrusted_ssh_signing_key"
|
||||||
|
GPGSSH_KEY_WITH_PASSPHRASE="${GNUPGHOME}/protected_ssh_signing_key"
|
||||||
|
GPGSSH_KEY_PASSPHRASE="super_secret"
|
||||||
|
GPGSSH_ALLOWED_SIGNERS="${GNUPGHOME}/ssh.all_valid.allowedSignersFile"
|
||||||
|
|
||||||
|
GPGSSH_GOOD_SIGNATURE_TRUSTED='Good "git" signature for'
|
||||||
|
GPGSSH_GOOD_SIGNATURE_UNTRUSTED='Good "git" signature with'
|
||||||
|
GPGSSH_KEY_NOT_TRUSTED="No principal matched"
|
||||||
|
GPGSSH_BAD_SIGNATURE="Signature verification failed"
|
||||||
|
|
||||||
|
test_lazy_prereq GPGSSH '
|
||||||
|
ssh_version=$(ssh-keygen -Y find-principals -n "git" 2>&1)
|
||||||
|
test $? != 127 || exit 1
|
||||||
|
echo $ssh_version | grep -q "find-principals:missing signature file"
|
||||||
|
test $? = 0 || exit 1;
|
||||||
|
mkdir -p "${GNUPGHOME}" &&
|
||||||
|
chmod 0700 "${GNUPGHOME}" &&
|
||||||
|
ssh-keygen -t ed25519 -N "" -C "git ed25519 key" -f "${GPGSSH_KEY_PRIMARY}" >/dev/null &&
|
||||||
|
echo "\"principal with number 1\" $(cat "${GPGSSH_KEY_PRIMARY}.pub")" >> "${GPGSSH_ALLOWED_SIGNERS}" &&
|
||||||
|
ssh-keygen -t rsa -b 2048 -N "" -C "git rsa2048 key" -f "${GPGSSH_KEY_SECONDARY}" >/dev/null &&
|
||||||
|
echo "\"principal with number 2\" $(cat "${GPGSSH_KEY_SECONDARY}.pub")" >> "${GPGSSH_ALLOWED_SIGNERS}" &&
|
||||||
|
ssh-keygen -t ed25519 -N "${GPGSSH_KEY_PASSPHRASE}" -C "git ed25519 encrypted key" -f "${GPGSSH_KEY_WITH_PASSPHRASE}" >/dev/null &&
|
||||||
|
echo "\"principal with number 3\" $(cat "${GPGSSH_KEY_WITH_PASSPHRASE}.pub")" >> "${GPGSSH_ALLOWED_SIGNERS}" &&
|
||||||
|
ssh-keygen -t ed25519 -N "" -f "${GPGSSH_KEY_UNTRUSTED}" >/dev/null
|
||||||
|
'
|
||||||
|
|
||||||
sanitize_pgp() {
|
sanitize_pgp() {
|
||||||
perl -ne '
|
perl -ne '
|
||||||
/^-----END PGP/ and $in_pgp = 0;
|
/^-----END PGP/ and $in_pgp = 0;
|
||||||
|
@ -1616,6 +1616,16 @@ test_expect_success GPGSM 'setup signed branch x509' '
|
|||||||
git commit -S -m signed_commit
|
git commit -S -m signed_commit
|
||||||
'
|
'
|
||||||
|
|
||||||
|
test_expect_success GPGSSH 'setup sshkey signed branch' '
|
||||||
|
test_config gpg.format ssh &&
|
||||||
|
test_config user.signingkey "${GPGSSH_KEY_PRIMARY}" &&
|
||||||
|
test_when_finished "git reset --hard && git checkout main" &&
|
||||||
|
git checkout -b signed-ssh main &&
|
||||||
|
echo foo >foo &&
|
||||||
|
git add foo &&
|
||||||
|
git commit -S -m signed_commit
|
||||||
|
'
|
||||||
|
|
||||||
test_expect_success GPGSM 'log x509 fingerprint' '
|
test_expect_success GPGSM 'log x509 fingerprint' '
|
||||||
echo "F8BF62E0693D0694816377099909C779FA23FD65 | " >expect &&
|
echo "F8BF62E0693D0694816377099909C779FA23FD65 | " >expect &&
|
||||||
git log -n1 --format="%GF | %GP" signed-x509 >actual &&
|
git log -n1 --format="%GF | %GP" signed-x509 >actual &&
|
||||||
@ -1628,6 +1638,13 @@ test_expect_success GPGSM 'log OpenPGP fingerprint' '
|
|||||||
test_cmp expect actual
|
test_cmp expect actual
|
||||||
'
|
'
|
||||||
|
|
||||||
|
test_expect_success GPGSSH 'log ssh key fingerprint' '
|
||||||
|
test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" &&
|
||||||
|
ssh-keygen -lf "${GPGSSH_KEY_PRIMARY}" | awk "{print \$2\" | \"}" >expect &&
|
||||||
|
git log -n1 --format="%GF | %GP" signed-ssh >actual &&
|
||||||
|
test_cmp expect actual
|
||||||
|
'
|
||||||
|
|
||||||
test_expect_success GPG 'log --graph --show-signature' '
|
test_expect_success GPG 'log --graph --show-signature' '
|
||||||
git log --graph --show-signature -n1 signed >actual &&
|
git log --graph --show-signature -n1 signed >actual &&
|
||||||
grep "^| gpg: Signature made" actual &&
|
grep "^| gpg: Signature made" actual &&
|
||||||
@ -1640,6 +1657,12 @@ test_expect_success GPGSM 'log --graph --show-signature x509' '
|
|||||||
grep "^| gpgsm: Good signature" actual
|
grep "^| gpgsm: Good signature" actual
|
||||||
'
|
'
|
||||||
|
|
||||||
|
test_expect_success GPGSSH 'log --graph --show-signature ssh' '
|
||||||
|
test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" &&
|
||||||
|
git log --graph --show-signature -n1 signed-ssh >actual &&
|
||||||
|
grep "${GOOD_SIGNATURE_TRUSTED}" actual
|
||||||
|
'
|
||||||
|
|
||||||
test_expect_success GPG 'log --graph --show-signature for merged tag' '
|
test_expect_success GPG 'log --graph --show-signature for merged tag' '
|
||||||
test_when_finished "git reset --hard && git checkout main" &&
|
test_when_finished "git reset --hard && git checkout main" &&
|
||||||
git checkout -b plain main &&
|
git checkout -b plain main &&
|
||||||
|
@ -137,6 +137,53 @@ test_expect_success GPG 'signed push sends push certificate' '
|
|||||||
test_cmp expect dst/push-cert-status
|
test_cmp expect dst/push-cert-status
|
||||||
'
|
'
|
||||||
|
|
||||||
|
test_expect_success GPGSSH 'ssh signed push sends push certificate' '
|
||||||
|
prepare_dst &&
|
||||||
|
mkdir -p dst/.git/hooks &&
|
||||||
|
git -C dst config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" &&
|
||||||
|
git -C dst config receive.certnonceseed sekrit &&
|
||||||
|
write_script dst/.git/hooks/post-receive <<-\EOF &&
|
||||||
|
# discard the update list
|
||||||
|
cat >/dev/null
|
||||||
|
# record the push certificate
|
||||||
|
if test -n "${GIT_PUSH_CERT-}"
|
||||||
|
then
|
||||||
|
git cat-file blob $GIT_PUSH_CERT >../push-cert
|
||||||
|
fi &&
|
||||||
|
|
||||||
|
cat >../push-cert-status <<E_O_F
|
||||||
|
SIGNER=${GIT_PUSH_CERT_SIGNER-nobody}
|
||||||
|
KEY=${GIT_PUSH_CERT_KEY-nokey}
|
||||||
|
STATUS=${GIT_PUSH_CERT_STATUS-nostatus}
|
||||||
|
NONCE_STATUS=${GIT_PUSH_CERT_NONCE_STATUS-nononcestatus}
|
||||||
|
NONCE=${GIT_PUSH_CERT_NONCE-nononce}
|
||||||
|
E_O_F
|
||||||
|
|
||||||
|
EOF
|
||||||
|
|
||||||
|
test_config gpg.format ssh &&
|
||||||
|
test_config user.signingkey "${GPGSSH_KEY_PRIMARY}" &&
|
||||||
|
FINGERPRINT=$(ssh-keygen -lf "${GPGSSH_KEY_PRIMARY}" | awk "{print \$2;}") &&
|
||||||
|
git push --signed dst noop ff +noff &&
|
||||||
|
|
||||||
|
(
|
||||||
|
cat <<-\EOF &&
|
||||||
|
SIGNER=principal with number 1
|
||||||
|
KEY=FINGERPRINT
|
||||||
|
STATUS=G
|
||||||
|
NONCE_STATUS=OK
|
||||||
|
EOF
|
||||||
|
sed -n -e "s/^nonce /NONCE=/p" -e "/^$/q" dst/push-cert
|
||||||
|
) | sed -e "s|FINGERPRINT|$FINGERPRINT|" >expect &&
|
||||||
|
|
||||||
|
noop=$(git rev-parse noop) &&
|
||||||
|
ff=$(git rev-parse ff) &&
|
||||||
|
noff=$(git rev-parse noff) &&
|
||||||
|
grep "$noop $ff refs/heads/ff" dst/push-cert &&
|
||||||
|
grep "$noop $noff refs/heads/noff" dst/push-cert &&
|
||||||
|
test_cmp expect dst/push-cert-status
|
||||||
|
'
|
||||||
|
|
||||||
test_expect_success GPG 'inconsistent push options in signed push not allowed' '
|
test_expect_success GPG 'inconsistent push options in signed push not allowed' '
|
||||||
# First, invoke receive-pack with dummy input to obtain its preamble.
|
# First, invoke receive-pack with dummy input to obtain its preamble.
|
||||||
prepare_dst &&
|
prepare_dst &&
|
||||||
@ -276,6 +323,60 @@ test_expect_success GPGSM 'fail without key and heed user.signingkey x509' '
|
|||||||
test_cmp expect dst/push-cert-status
|
test_cmp expect dst/push-cert-status
|
||||||
'
|
'
|
||||||
|
|
||||||
|
test_expect_success GPGSSH 'fail without key and heed user.signingkey ssh' '
|
||||||
|
test_config gpg.format ssh &&
|
||||||
|
prepare_dst &&
|
||||||
|
mkdir -p dst/.git/hooks &&
|
||||||
|
git -C dst config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" &&
|
||||||
|
git -C dst config receive.certnonceseed sekrit &&
|
||||||
|
write_script dst/.git/hooks/post-receive <<-\EOF &&
|
||||||
|
# discard the update list
|
||||||
|
cat >/dev/null
|
||||||
|
# record the push certificate
|
||||||
|
if test -n "${GIT_PUSH_CERT-}"
|
||||||
|
then
|
||||||
|
git cat-file blob $GIT_PUSH_CERT >../push-cert
|
||||||
|
fi &&
|
||||||
|
|
||||||
|
cat >../push-cert-status <<E_O_F
|
||||||
|
SIGNER=${GIT_PUSH_CERT_SIGNER-nobody}
|
||||||
|
KEY=${GIT_PUSH_CERT_KEY-nokey}
|
||||||
|
STATUS=${GIT_PUSH_CERT_STATUS-nostatus}
|
||||||
|
NONCE_STATUS=${GIT_PUSH_CERT_NONCE_STATUS-nononcestatus}
|
||||||
|
NONCE=${GIT_PUSH_CERT_NONCE-nononce}
|
||||||
|
E_O_F
|
||||||
|
|
||||||
|
EOF
|
||||||
|
|
||||||
|
test_config user.email hasnokey@nowhere.com &&
|
||||||
|
test_config gpg.format ssh &&
|
||||||
|
test_config user.signingkey "" &&
|
||||||
|
(
|
||||||
|
sane_unset GIT_COMMITTER_EMAIL &&
|
||||||
|
test_must_fail git push --signed dst noop ff +noff
|
||||||
|
) &&
|
||||||
|
test_config user.signingkey "${GPGSSH_KEY_PRIMARY}" &&
|
||||||
|
FINGERPRINT=$(ssh-keygen -lf "${GPGSSH_KEY_PRIMARY}" | awk "{print \$2;}") &&
|
||||||
|
git push --signed dst noop ff +noff &&
|
||||||
|
|
||||||
|
(
|
||||||
|
cat <<-\EOF &&
|
||||||
|
SIGNER=principal with number 1
|
||||||
|
KEY=FINGERPRINT
|
||||||
|
STATUS=G
|
||||||
|
NONCE_STATUS=OK
|
||||||
|
EOF
|
||||||
|
sed -n -e "s/^nonce /NONCE=/p" -e "/^$/q" dst/push-cert
|
||||||
|
) | sed -e "s|FINGERPRINT|$FINGERPRINT|" >expect &&
|
||||||
|
|
||||||
|
noop=$(git rev-parse noop) &&
|
||||||
|
ff=$(git rev-parse ff) &&
|
||||||
|
noff=$(git rev-parse noff) &&
|
||||||
|
grep "$noop $ff refs/heads/ff" dst/push-cert &&
|
||||||
|
grep "$noop $noff refs/heads/noff" dst/push-cert &&
|
||||||
|
test_cmp expect dst/push-cert-status
|
||||||
|
'
|
||||||
|
|
||||||
test_expect_success GPG 'failed atomic push does not execute GPG' '
|
test_expect_success GPG 'failed atomic push does not execute GPG' '
|
||||||
prepare_dst &&
|
prepare_dst &&
|
||||||
git -C dst config receive.certnonceseed sekrit &&
|
git -C dst config receive.certnonceseed sekrit &&
|
||||||
|
161
t/t7031-verify-tag-signed-ssh.sh
Executable file
161
t/t7031-verify-tag-signed-ssh.sh
Executable file
@ -0,0 +1,161 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
test_description='signed tag tests'
|
||||||
|
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
|
||||||
|
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
|
||||||
|
|
||||||
|
. ./test-lib.sh
|
||||||
|
. "$TEST_DIRECTORY/lib-gpg.sh"
|
||||||
|
|
||||||
|
test_expect_success GPGSSH 'create signed tags ssh' '
|
||||||
|
test_when_finished "test_unconfig commit.gpgsign" &&
|
||||||
|
test_config gpg.format ssh &&
|
||||||
|
test_config user.signingkey "${GPGSSH_KEY_PRIMARY}" &&
|
||||||
|
|
||||||
|
echo 1 >file && git add file &&
|
||||||
|
test_tick && git commit -m initial &&
|
||||||
|
git tag -s -m initial initial &&
|
||||||
|
git branch side &&
|
||||||
|
|
||||||
|
echo 2 >file && test_tick && git commit -a -m second &&
|
||||||
|
git tag -s -m second second &&
|
||||||
|
|
||||||
|
git checkout side &&
|
||||||
|
echo 3 >elif && git add elif &&
|
||||||
|
test_tick && git commit -m "third on side" &&
|
||||||
|
|
||||||
|
git checkout main &&
|
||||||
|
test_tick && git merge -S side &&
|
||||||
|
git tag -s -m merge merge &&
|
||||||
|
|
||||||
|
echo 4 >file && test_tick && git commit -a -S -m "fourth unsigned" &&
|
||||||
|
git tag -a -m fourth-unsigned fourth-unsigned &&
|
||||||
|
|
||||||
|
test_tick && git commit --amend -S -m "fourth signed" &&
|
||||||
|
git tag -s -m fourth fourth-signed &&
|
||||||
|
|
||||||
|
echo 5 >file && test_tick && git commit -a -m "fifth" &&
|
||||||
|
git tag fifth-unsigned &&
|
||||||
|
|
||||||
|
git config commit.gpgsign true &&
|
||||||
|
echo 6 >file && test_tick && git commit -a -m "sixth" &&
|
||||||
|
git tag -a -m sixth sixth-unsigned &&
|
||||||
|
|
||||||
|
test_tick && git rebase -f HEAD^^ && git tag -s -m 6th sixth-signed HEAD^ &&
|
||||||
|
git tag -m seventh -s seventh-signed &&
|
||||||
|
|
||||||
|
echo 8 >file && test_tick && git commit -a -m eighth &&
|
||||||
|
git tag -u"${GPGSSH_KEY_UNTRUSTED}" -m eighth eighth-signed-alt
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success GPGSSH 'verify and show ssh signatures' '
|
||||||
|
test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" &&
|
||||||
|
(
|
||||||
|
for tag in initial second merge fourth-signed sixth-signed seventh-signed
|
||||||
|
do
|
||||||
|
git verify-tag $tag 2>actual &&
|
||||||
|
grep "${GPGSSH_GOOD_SIGNATURE_TRUSTED}" actual &&
|
||||||
|
! grep "${GPGSSH_BAD_SIGNATURE}" actual &&
|
||||||
|
echo $tag OK || exit 1
|
||||||
|
done
|
||||||
|
) &&
|
||||||
|
(
|
||||||
|
for tag in fourth-unsigned fifth-unsigned sixth-unsigned
|
||||||
|
do
|
||||||
|
test_must_fail git verify-tag $tag 2>actual &&
|
||||||
|
! grep "${GPGSSH_GOOD_SIGNATURE_TRUSTED}" actual &&
|
||||||
|
! grep "${GPGSSH_BAD_SIGNATURE}" actual &&
|
||||||
|
echo $tag OK || exit 1
|
||||||
|
done
|
||||||
|
) &&
|
||||||
|
(
|
||||||
|
for tag in eighth-signed-alt
|
||||||
|
do
|
||||||
|
test_must_fail git verify-tag $tag 2>actual &&
|
||||||
|
grep "${GPGSSH_GOOD_SIGNATURE_UNTRUSTED}" actual &&
|
||||||
|
! grep "${GPGSSH_BAD_SIGNATURE}" actual &&
|
||||||
|
grep "${GPGSSH_KEY_NOT_TRUSTED}" actual &&
|
||||||
|
echo $tag OK || exit 1
|
||||||
|
done
|
||||||
|
)
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success GPGSSH 'detect fudged ssh signature' '
|
||||||
|
test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" &&
|
||||||
|
git cat-file tag seventh-signed >raw &&
|
||||||
|
sed -e "/^tag / s/seventh/7th forged/" raw >forged1 &&
|
||||||
|
git hash-object -w -t tag forged1 >forged1.tag &&
|
||||||
|
test_must_fail git verify-tag $(cat forged1.tag) 2>actual1 &&
|
||||||
|
grep "${GPGSSH_BAD_SIGNATURE}" actual1 &&
|
||||||
|
! grep "${GPGSSH_GOOD_SIGNATURE_TRUSTED}" actual1 &&
|
||||||
|
! grep "${GPGSSH_GOOD_SIGNATURE_UNTRUSTED}" actual1
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success GPGSSH 'verify ssh signatures with --raw' '
|
||||||
|
test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" &&
|
||||||
|
(
|
||||||
|
for tag in initial second merge fourth-signed sixth-signed seventh-signed
|
||||||
|
do
|
||||||
|
git verify-tag --raw $tag 2>actual &&
|
||||||
|
grep "${GPGSSH_GOOD_SIGNATURE_TRUSTED}" actual &&
|
||||||
|
! grep "${GPGSSH_BAD_SIGNATURE}" actual &&
|
||||||
|
echo $tag OK || exit 1
|
||||||
|
done
|
||||||
|
) &&
|
||||||
|
(
|
||||||
|
for tag in fourth-unsigned fifth-unsigned sixth-unsigned
|
||||||
|
do
|
||||||
|
test_must_fail git verify-tag --raw $tag 2>actual &&
|
||||||
|
! grep "${GPGSSH_GOOD_SIGNATURE_TRUSTED}" actual &&
|
||||||
|
! grep "${GPGSSH_BAD_SIGNATURE}" actual &&
|
||||||
|
echo $tag OK || exit 1
|
||||||
|
done
|
||||||
|
) &&
|
||||||
|
(
|
||||||
|
for tag in eighth-signed-alt
|
||||||
|
do
|
||||||
|
test_must_fail git verify-tag --raw $tag 2>actual &&
|
||||||
|
grep "${GPGSSH_GOOD_SIGNATURE_UNTRUSTED}" actual &&
|
||||||
|
! grep "${GPGSSH_BAD_SIGNATURE}" actual &&
|
||||||
|
echo $tag OK || exit 1
|
||||||
|
done
|
||||||
|
)
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success GPGSSH 'verify signatures with --raw ssh' '
|
||||||
|
test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" &&
|
||||||
|
git verify-tag --raw sixth-signed 2>actual &&
|
||||||
|
grep "${GPGSSH_GOOD_SIGNATURE_TRUSTED}" actual &&
|
||||||
|
! grep "${GPGSSH_BAD_SIGNATURE}" actual &&
|
||||||
|
echo sixth-signed OK
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success GPGSSH 'verify multiple tags ssh' '
|
||||||
|
test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" &&
|
||||||
|
tags="seventh-signed sixth-signed" &&
|
||||||
|
for i in $tags
|
||||||
|
do
|
||||||
|
git verify-tag -v --raw $i || return 1
|
||||||
|
done >expect.stdout 2>expect.stderr.1 &&
|
||||||
|
grep "^${GPGSSH_GOOD_SIGNATURE_TRUSTED}" <expect.stderr.1 >expect.stderr &&
|
||||||
|
git verify-tag -v --raw $tags >actual.stdout 2>actual.stderr.1 &&
|
||||||
|
grep "^${GPGSSH_GOOD_SIGNATURE_TRUSTED}" <actual.stderr.1 >actual.stderr &&
|
||||||
|
test_cmp expect.stdout actual.stdout &&
|
||||||
|
test_cmp expect.stderr actual.stderr
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success GPGSSH 'verifying tag with --format - ssh' '
|
||||||
|
test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" &&
|
||||||
|
cat >expect <<-\EOF &&
|
||||||
|
tagname : fourth-signed
|
||||||
|
EOF
|
||||||
|
git verify-tag --format="tagname : %(tag)" "fourth-signed" >actual &&
|
||||||
|
test_cmp expect actual
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success GPGSSH 'verifying a forged tag with --format should fail silently - ssh' '
|
||||||
|
test_must_fail git verify-tag --format="tagname : %(tag)" $(cat forged1.tag) >actual-forged &&
|
||||||
|
test_must_be_empty actual-forged
|
||||||
|
'
|
||||||
|
|
||||||
|
test_done
|
@ -71,7 +71,25 @@ test_expect_success GPG 'create signed commits' '
|
|||||||
git tag eleventh-signed $(cat oid) &&
|
git tag eleventh-signed $(cat oid) &&
|
||||||
echo 12 | git commit-tree --gpg-sign=B7227189 HEAD^{tree} >oid &&
|
echo 12 | git commit-tree --gpg-sign=B7227189 HEAD^{tree} >oid &&
|
||||||
test_line_count = 1 oid &&
|
test_line_count = 1 oid &&
|
||||||
git tag twelfth-signed-alt $(cat oid)
|
git tag twelfth-signed-alt $(cat oid) &&
|
||||||
|
|
||||||
|
cat >keydetails <<-\EOF &&
|
||||||
|
Key-Type: RSA
|
||||||
|
Key-Length: 2048
|
||||||
|
Subkey-Type: RSA
|
||||||
|
Subkey-Length: 2048
|
||||||
|
Name-Real: Unknown User
|
||||||
|
Name-Email: unknown@git.com
|
||||||
|
Expire-Date: 0
|
||||||
|
%no-ask-passphrase
|
||||||
|
%no-protection
|
||||||
|
EOF
|
||||||
|
gpg --batch --gen-key keydetails &&
|
||||||
|
echo 13 >file && git commit -a -S"unknown@git.com" -m thirteenth &&
|
||||||
|
git tag thirteenth-signed &&
|
||||||
|
DELETE_FINGERPRINT=$(gpg -K --with-colons --fingerprint --batch unknown@git.com | grep "^fpr" | head -n 1 | awk -F ":" "{print \$10;}") &&
|
||||||
|
gpg --batch --yes --delete-secret-keys $DELETE_FINGERPRINT &&
|
||||||
|
gpg --batch --yes --delete-keys unknown@git.com
|
||||||
'
|
'
|
||||||
|
|
||||||
test_expect_success GPG 'verify and show signatures' '
|
test_expect_success GPG 'verify and show signatures' '
|
||||||
@ -110,6 +128,13 @@ test_expect_success GPG 'verify and show signatures' '
|
|||||||
)
|
)
|
||||||
'
|
'
|
||||||
|
|
||||||
|
test_expect_success GPG 'verify-commit exits failure on unknown signature' '
|
||||||
|
test_must_fail git verify-commit thirteenth-signed 2>actual &&
|
||||||
|
! grep "Good signature from" actual &&
|
||||||
|
! grep "BAD signature from" actual &&
|
||||||
|
grep -q -F -e "No public key" -e "public key not found" actual
|
||||||
|
'
|
||||||
|
|
||||||
test_expect_success GPG 'verify-commit exits success on untrusted signature' '
|
test_expect_success GPG 'verify-commit exits success on untrusted signature' '
|
||||||
git verify-commit eighth-signed-alt 2>actual &&
|
git verify-commit eighth-signed-alt 2>actual &&
|
||||||
grep "Good signature from" actual &&
|
grep "Good signature from" actual &&
|
||||||
@ -338,6 +363,8 @@ test_expect_success GPG 'show double signature with custom format' '
|
|||||||
'
|
'
|
||||||
|
|
||||||
|
|
||||||
|
# NEEDSWORK: This test relies on the test_tick commit/author dates from the first
|
||||||
|
# 'create signed commits' test even though it creates its own
|
||||||
test_expect_success GPG 'verify-commit verifies multiply signed commits' '
|
test_expect_success GPG 'verify-commit verifies multiply signed commits' '
|
||||||
git init multiply-signed &&
|
git init multiply-signed &&
|
||||||
cd multiply-signed &&
|
cd multiply-signed &&
|
||||||
|
398
t/t7528-signed-commit-ssh.sh
Executable file
398
t/t7528-signed-commit-ssh.sh
Executable file
@ -0,0 +1,398 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
test_description='ssh signed commit tests'
|
||||||
|
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
|
||||||
|
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
|
||||||
|
|
||||||
|
. ./test-lib.sh
|
||||||
|
GNUPGHOME_NOT_USED=$GNUPGHOME
|
||||||
|
. "$TEST_DIRECTORY/lib-gpg.sh"
|
||||||
|
|
||||||
|
test_expect_success GPGSSH 'create signed commits' '
|
||||||
|
test_oid_cache <<-\EOF &&
|
||||||
|
header sha1:gpgsig
|
||||||
|
header sha256:gpgsig-sha256
|
||||||
|
EOF
|
||||||
|
|
||||||
|
test_when_finished "test_unconfig commit.gpgsign" &&
|
||||||
|
test_config gpg.format ssh &&
|
||||||
|
test_config user.signingkey "${GPGSSH_KEY_PRIMARY}" &&
|
||||||
|
|
||||||
|
echo 1 >file && git add file &&
|
||||||
|
test_tick && git commit -S -m initial &&
|
||||||
|
git tag initial &&
|
||||||
|
git branch side &&
|
||||||
|
|
||||||
|
echo 2 >file && test_tick && git commit -a -S -m second &&
|
||||||
|
git tag second &&
|
||||||
|
|
||||||
|
git checkout side &&
|
||||||
|
echo 3 >elif && git add elif &&
|
||||||
|
test_tick && git commit -m "third on side" &&
|
||||||
|
|
||||||
|
git checkout main &&
|
||||||
|
test_tick && git merge -S side &&
|
||||||
|
git tag merge &&
|
||||||
|
|
||||||
|
echo 4 >file && test_tick && git commit -a -m "fourth unsigned" &&
|
||||||
|
git tag fourth-unsigned &&
|
||||||
|
|
||||||
|
test_tick && git commit --amend -S -m "fourth signed" &&
|
||||||
|
git tag fourth-signed &&
|
||||||
|
|
||||||
|
git config commit.gpgsign true &&
|
||||||
|
echo 5 >file && test_tick && git commit -a -m "fifth signed" &&
|
||||||
|
git tag fifth-signed &&
|
||||||
|
|
||||||
|
git config commit.gpgsign false &&
|
||||||
|
echo 6 >file && test_tick && git commit -a -m "sixth" &&
|
||||||
|
git tag sixth-unsigned &&
|
||||||
|
|
||||||
|
git config commit.gpgsign true &&
|
||||||
|
echo 7 >file && test_tick && git commit -a -m "seventh" --no-gpg-sign &&
|
||||||
|
git tag seventh-unsigned &&
|
||||||
|
|
||||||
|
test_tick && git rebase -f HEAD^^ && git tag sixth-signed HEAD^ &&
|
||||||
|
git tag seventh-signed &&
|
||||||
|
|
||||||
|
echo 8 >file && test_tick && git commit -a -m eighth -S"${GPGSSH_KEY_UNTRUSTED}" &&
|
||||||
|
git tag eighth-signed-alt &&
|
||||||
|
|
||||||
|
# commit.gpgsign is still on but this must not be signed
|
||||||
|
echo 9 | git commit-tree HEAD^{tree} >oid &&
|
||||||
|
test_line_count = 1 oid &&
|
||||||
|
git tag ninth-unsigned $(cat oid) &&
|
||||||
|
# explicit -S of course must sign.
|
||||||
|
echo 10 | git commit-tree -S HEAD^{tree} >oid &&
|
||||||
|
test_line_count = 1 oid &&
|
||||||
|
git tag tenth-signed $(cat oid) &&
|
||||||
|
|
||||||
|
# --gpg-sign[=<key-id>] must sign.
|
||||||
|
echo 11 | git commit-tree --gpg-sign HEAD^{tree} >oid &&
|
||||||
|
test_line_count = 1 oid &&
|
||||||
|
git tag eleventh-signed $(cat oid) &&
|
||||||
|
echo 12 | git commit-tree --gpg-sign="${GPGSSH_KEY_UNTRUSTED}" HEAD^{tree} >oid &&
|
||||||
|
test_line_count = 1 oid &&
|
||||||
|
git tag twelfth-signed-alt $(cat oid)
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success GPGSSH 'verify and show signatures' '
|
||||||
|
test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" &&
|
||||||
|
test_config gpg.mintrustlevel UNDEFINED &&
|
||||||
|
(
|
||||||
|
for commit in initial second merge fourth-signed \
|
||||||
|
fifth-signed sixth-signed seventh-signed tenth-signed \
|
||||||
|
eleventh-signed
|
||||||
|
do
|
||||||
|
git verify-commit $commit &&
|
||||||
|
git show --pretty=short --show-signature $commit >actual &&
|
||||||
|
grep "${GPGSSH_GOOD_SIGNATURE_TRUSTED}" actual &&
|
||||||
|
! grep "${GPGSSH_BAD_SIGNATURE}" actual &&
|
||||||
|
echo $commit OK || exit 1
|
||||||
|
done
|
||||||
|
) &&
|
||||||
|
(
|
||||||
|
for commit in merge^2 fourth-unsigned sixth-unsigned \
|
||||||
|
seventh-unsigned ninth-unsigned
|
||||||
|
do
|
||||||
|
test_must_fail git verify-commit $commit &&
|
||||||
|
git show --pretty=short --show-signature $commit >actual &&
|
||||||
|
! grep "${GPGSSH_GOOD_SIGNATURE_TRUSTED}" actual &&
|
||||||
|
! grep "${GPGSSH_BAD_SIGNATURE}" actual &&
|
||||||
|
echo $commit OK || exit 1
|
||||||
|
done
|
||||||
|
) &&
|
||||||
|
(
|
||||||
|
for commit in eighth-signed-alt twelfth-signed-alt
|
||||||
|
do
|
||||||
|
git show --pretty=short --show-signature $commit >actual &&
|
||||||
|
grep "${GPGSSH_GOOD_SIGNATURE_UNTRUSTED}" actual &&
|
||||||
|
! grep "${GPGSSH_BAD_SIGNATURE}" actual &&
|
||||||
|
grep "${GPGSSH_KEY_NOT_TRUSTED}" actual &&
|
||||||
|
echo $commit OK || exit 1
|
||||||
|
done
|
||||||
|
)
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success GPGSSH 'verify-commit exits failure on untrusted signature' '
|
||||||
|
test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" &&
|
||||||
|
test_must_fail git verify-commit eighth-signed-alt 2>actual &&
|
||||||
|
grep "${GPGSSH_GOOD_SIGNATURE_UNTRUSTED}" actual &&
|
||||||
|
! grep "${GPGSSH_BAD_SIGNATURE}" actual &&
|
||||||
|
grep "${GPGSSH_KEY_NOT_TRUSTED}" actual
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success GPGSSH 'verify-commit exits success with matching minTrustLevel' '
|
||||||
|
test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" &&
|
||||||
|
test_config gpg.minTrustLevel fully &&
|
||||||
|
git verify-commit sixth-signed
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success GPGSSH 'verify-commit exits success with low minTrustLevel' '
|
||||||
|
test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" &&
|
||||||
|
test_config gpg.minTrustLevel marginal &&
|
||||||
|
git verify-commit sixth-signed
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success GPGSSH 'verify-commit exits failure with high minTrustLevel' '
|
||||||
|
test_config gpg.minTrustLevel ultimate &&
|
||||||
|
test_must_fail git verify-commit eighth-signed-alt
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success GPGSSH 'verify signatures with --raw' '
|
||||||
|
test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" &&
|
||||||
|
(
|
||||||
|
for commit in initial second merge fourth-signed fifth-signed sixth-signed seventh-signed
|
||||||
|
do
|
||||||
|
git verify-commit --raw $commit 2>actual &&
|
||||||
|
grep "${GPGSSH_GOOD_SIGNATURE_TRUSTED}" actual &&
|
||||||
|
! grep "${GPGSSH_BAD_SIGNATURE}" actual &&
|
||||||
|
echo $commit OK || exit 1
|
||||||
|
done
|
||||||
|
) &&
|
||||||
|
(
|
||||||
|
for commit in merge^2 fourth-unsigned sixth-unsigned seventh-unsigned
|
||||||
|
do
|
||||||
|
test_must_fail git verify-commit --raw $commit 2>actual &&
|
||||||
|
! grep "${GPGSSH_GOOD_SIGNATURE_TRUSTED}" actual &&
|
||||||
|
! grep "${GPGSSH_BAD_SIGNATURE}" actual &&
|
||||||
|
echo $commit OK || exit 1
|
||||||
|
done
|
||||||
|
) &&
|
||||||
|
(
|
||||||
|
for commit in eighth-signed-alt
|
||||||
|
do
|
||||||
|
test_must_fail git verify-commit --raw $commit 2>actual &&
|
||||||
|
grep "${GPGSSH_GOOD_SIGNATURE_UNTRUSTED}" actual &&
|
||||||
|
! grep "${GPGSSH_BAD_SIGNATURE}" actual &&
|
||||||
|
echo $commit OK || exit 1
|
||||||
|
done
|
||||||
|
)
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success GPGSSH 'proper header is used for hash algorithm' '
|
||||||
|
git cat-file commit fourth-signed >output &&
|
||||||
|
grep "^$(test_oid header) -----BEGIN SSH SIGNATURE-----" output
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success GPGSSH 'show signed commit with signature' '
|
||||||
|
test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" &&
|
||||||
|
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 -e "${GPGSSH_GOOD_SIGNATURE_TRUSTED}" -e "Warning: " show >show.commit &&
|
||||||
|
grep -e "${GPGSSH_GOOD_SIGNATURE_TRUSTED}" -e "Warning: " 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 GPGSSH 'detect fudged signature' '
|
||||||
|
test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" &&
|
||||||
|
git cat-file commit seventh-signed >raw &&
|
||||||
|
sed -e "s/^seventh/7th forged/" raw >forged1 &&
|
||||||
|
git hash-object -w -t commit forged1 >forged1.commit &&
|
||||||
|
test_must_fail git verify-commit $(cat forged1.commit) &&
|
||||||
|
git show --pretty=short --show-signature $(cat forged1.commit) >actual1 &&
|
||||||
|
grep "${GPGSSH_BAD_SIGNATURE}" actual1 &&
|
||||||
|
! grep "${GPGSSH_GOOD_SIGNATURE_TRUSTED}" actual1 &&
|
||||||
|
! grep "${GPGSSH_GOOD_SIGNATURE_UNTRUSTED}" actual1
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success GPGSSH 'detect fudged signature with NUL' '
|
||||||
|
test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" &&
|
||||||
|
git cat-file commit seventh-signed >raw &&
|
||||||
|
cat raw >forged2 &&
|
||||||
|
echo Qwik | tr "Q" "\000" >>forged2 &&
|
||||||
|
git hash-object -w -t commit forged2 >forged2.commit &&
|
||||||
|
test_must_fail git verify-commit $(cat forged2.commit) &&
|
||||||
|
git show --pretty=short --show-signature $(cat forged2.commit) >actual2 &&
|
||||||
|
grep "${GPGSSH_BAD_SIGNATURE}" actual2 &&
|
||||||
|
! grep "${GPGSSH_GOOD_SIGNATURE_TRUSTED}" actual2
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success GPGSSH 'amending already signed commit' '
|
||||||
|
test_config gpg.format ssh &&
|
||||||
|
test_config user.signingkey "${GPGSSH_KEY_PRIMARY}" &&
|
||||||
|
test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" &&
|
||||||
|
git checkout fourth-signed^0 &&
|
||||||
|
git commit --amend -S --no-edit &&
|
||||||
|
git verify-commit HEAD &&
|
||||||
|
git show -s --show-signature HEAD >actual &&
|
||||||
|
grep "${GPGSSH_GOOD_SIGNATURE_TRUSTED}" actual &&
|
||||||
|
! grep "${GPGSSH_BAD_SIGNATURE}" actual
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success GPGSSH 'show good signature with custom format' '
|
||||||
|
test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" &&
|
||||||
|
FINGERPRINT=$(ssh-keygen -lf "${GPGSSH_KEY_PRIMARY}" | awk "{print \$2;}") &&
|
||||||
|
cat >expect.tmpl <<-\EOF &&
|
||||||
|
G
|
||||||
|
FINGERPRINT
|
||||||
|
principal with number 1
|
||||||
|
FINGERPRINT
|
||||||
|
|
||||||
|
EOF
|
||||||
|
sed "s|FINGERPRINT|$FINGERPRINT|g" expect.tmpl >expect &&
|
||||||
|
git log -1 --format="%G?%n%GK%n%GS%n%GF%n%GP" sixth-signed >actual &&
|
||||||
|
test_cmp expect actual
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success GPGSSH 'show bad signature with custom format' '
|
||||||
|
test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" &&
|
||||||
|
cat >expect <<-\EOF &&
|
||||||
|
B
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
EOF
|
||||||
|
git log -1 --format="%G?%n%GK%n%GS%n%GF%n%GP" $(cat forged1.commit) >actual &&
|
||||||
|
test_cmp expect actual
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success GPGSSH 'show untrusted signature with custom format' '
|
||||||
|
test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" &&
|
||||||
|
cat >expect.tmpl <<-\EOF &&
|
||||||
|
U
|
||||||
|
FINGERPRINT
|
||||||
|
|
||||||
|
FINGERPRINT
|
||||||
|
|
||||||
|
EOF
|
||||||
|
git log -1 --format="%G?%n%GK%n%GS%n%GF%n%GP" eighth-signed-alt >actual &&
|
||||||
|
FINGERPRINT=$(ssh-keygen -lf "${GPGSSH_KEY_UNTRUSTED}" | awk "{print \$2;}") &&
|
||||||
|
sed "s|FINGERPRINT|$FINGERPRINT|g" expect.tmpl >expect &&
|
||||||
|
test_cmp expect actual
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success GPGSSH 'show untrusted signature with undefined trust level' '
|
||||||
|
test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" &&
|
||||||
|
cat >expect.tmpl <<-\EOF &&
|
||||||
|
undefined
|
||||||
|
FINGERPRINT
|
||||||
|
|
||||||
|
FINGERPRINT
|
||||||
|
|
||||||
|
EOF
|
||||||
|
git log -1 --format="%GT%n%GK%n%GS%n%GF%n%GP" eighth-signed-alt >actual &&
|
||||||
|
FINGERPRINT=$(ssh-keygen -lf "${GPGSSH_KEY_UNTRUSTED}" | awk "{print \$2;}") &&
|
||||||
|
sed "s|FINGERPRINT|$FINGERPRINT|g" expect.tmpl >expect &&
|
||||||
|
test_cmp expect actual
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success GPGSSH 'show untrusted signature with ultimate trust level' '
|
||||||
|
test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" &&
|
||||||
|
cat >expect.tmpl <<-\EOF &&
|
||||||
|
fully
|
||||||
|
FINGERPRINT
|
||||||
|
principal with number 1
|
||||||
|
FINGERPRINT
|
||||||
|
|
||||||
|
EOF
|
||||||
|
git log -1 --format="%GT%n%GK%n%GS%n%GF%n%GP" sixth-signed >actual &&
|
||||||
|
FINGERPRINT=$(ssh-keygen -lf "${GPGSSH_KEY_PRIMARY}" | awk "{print \$2;}") &&
|
||||||
|
sed "s|FINGERPRINT|$FINGERPRINT|g" expect.tmpl >expect &&
|
||||||
|
test_cmp expect actual
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success GPGSSH 'show lack of signature with custom format' '
|
||||||
|
cat >expect <<-\EOF &&
|
||||||
|
N
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
EOF
|
||||||
|
git log -1 --format="%G?%n%GK%n%GS%n%GF%n%GP" seventh-unsigned >actual &&
|
||||||
|
test_cmp expect actual
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success GPGSSH 'log.showsignature behaves like --show-signature' '
|
||||||
|
test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" &&
|
||||||
|
test_config log.showsignature true &&
|
||||||
|
git show initial >actual &&
|
||||||
|
grep "${GPGSSH_GOOD_SIGNATURE_TRUSTED}" actual
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success GPGSSH 'check config gpg.format values' '
|
||||||
|
test_config gpg.format ssh &&
|
||||||
|
test_config user.signingkey "${GPGSSH_KEY_PRIMARY}" &&
|
||||||
|
test_config gpg.format ssh &&
|
||||||
|
git commit -S --amend -m "success" &&
|
||||||
|
test_config gpg.format OpEnPgP &&
|
||||||
|
test_must_fail git commit -S --amend -m "fail"
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_failure GPGSSH 'detect fudged commit with double signature (TODO)' '
|
||||||
|
sed -e "/gpgsig/,/END PGP/d" forged1 >double-base &&
|
||||||
|
sed -n -e "/gpgsig/,/END PGP/p" forged1 | \
|
||||||
|
sed -e "s/^$(test_oid header)//;s/^ //" | gpg --dearmor >double-sig1.sig &&
|
||||||
|
gpg -o double-sig2.sig -u 29472784 --detach-sign double-base &&
|
||||||
|
cat double-sig1.sig double-sig2.sig | gpg --enarmor >double-combined.asc &&
|
||||||
|
sed -e "s/^\(-.*\)ARMORED FILE/\1SIGNATURE/;1s/^/$(test_oid header) /;2,\$s/^/ /" \
|
||||||
|
double-combined.asc > double-gpgsig &&
|
||||||
|
sed -e "/committer/r double-gpgsig" double-base >double-commit &&
|
||||||
|
git hash-object -w -t commit double-commit >double-commit.commit &&
|
||||||
|
test_must_fail git verify-commit $(cat double-commit.commit) &&
|
||||||
|
git show --pretty=short --show-signature $(cat double-commit.commit) >double-actual &&
|
||||||
|
grep "BAD signature from" double-actual &&
|
||||||
|
grep "Good signature from" double-actual
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_failure GPGSSH 'show double signature with custom format (TODO)' '
|
||||||
|
cat >expect <<-\EOF &&
|
||||||
|
E
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
EOF
|
||||||
|
git log -1 --format="%G?%n%GK%n%GS%n%GF%n%GP" $(cat double-commit.commit) >actual &&
|
||||||
|
test_cmp expect actual
|
||||||
|
'
|
||||||
|
|
||||||
|
|
||||||
|
test_expect_failure GPGSSH 'verify-commit verifies multiply signed commits (TODO)' '
|
||||||
|
git init multiply-signed &&
|
||||||
|
cd multiply-signed &&
|
||||||
|
test_commit first &&
|
||||||
|
echo 1 >second &&
|
||||||
|
git add second &&
|
||||||
|
tree=$(git write-tree) &&
|
||||||
|
parent=$(git rev-parse HEAD^{commit}) &&
|
||||||
|
git commit --gpg-sign -m second &&
|
||||||
|
git cat-file commit HEAD &&
|
||||||
|
# Avoid trailing whitespace.
|
||||||
|
sed -e "s/^Q//" -e "s/^Z/ /" >commit <<-EOF &&
|
||||||
|
Qtree $tree
|
||||||
|
Qparent $parent
|
||||||
|
Qauthor A U Thor <author@example.com> 1112912653 -0700
|
||||||
|
Qcommitter C O Mitter <committer@example.com> 1112912653 -0700
|
||||||
|
Qgpgsig -----BEGIN PGP SIGNATURE-----
|
||||||
|
QZ
|
||||||
|
Q iHQEABECADQWIQRz11h0S+chaY7FTocTtvUezd5DDQUCX/uBDRYcY29tbWl0dGVy
|
||||||
|
Q QGV4YW1wbGUuY29tAAoJEBO29R7N3kMNd+8AoK1I8mhLHviPH+q2I5fIVgPsEtYC
|
||||||
|
Q AKCTqBh+VabJceXcGIZuF0Ry+udbBQ==
|
||||||
|
Q =tQ0N
|
||||||
|
Q -----END PGP SIGNATURE-----
|
||||||
|
Qgpgsig-sha256 -----BEGIN PGP SIGNATURE-----
|
||||||
|
QZ
|
||||||
|
Q iHQEABECADQWIQRz11h0S+chaY7FTocTtvUezd5DDQUCX/uBIBYcY29tbWl0dGVy
|
||||||
|
Q QGV4YW1wbGUuY29tAAoJEBO29R7N3kMN/NEAn0XO9RYSBj2dFyozi0JKSbssYMtO
|
||||||
|
Q AJwKCQ1BQOtuwz//IjU8TiS+6S4iUw==
|
||||||
|
Q =pIwP
|
||||||
|
Q -----END PGP SIGNATURE-----
|
||||||
|
Q
|
||||||
|
Qsecond
|
||||||
|
EOF
|
||||||
|
head=$(git hash-object -t commit -w commit) &&
|
||||||
|
git reset --hard $head &&
|
||||||
|
git verify-commit $head 2>actual &&
|
||||||
|
grep "Good signature from" actual &&
|
||||||
|
! grep "BAD signature from" actual
|
||||||
|
'
|
||||||
|
|
||||||
|
test_done
|
Loading…
Reference in New Issue
Block a user