@ -3,13 +3,14 @@
@@ -3,13 +3,14 @@
#include "config.h"
#include "run-command.h"
#include "strbuf.h"
#include "dir.h"
#include "gpg-interface.h"
#include "sigchain.h"
#include "tempfile.h"
#include "alias.h"
static char *configured_signing_key;
static const char *ssh_default_key_command;
static const char *ssh_default_key_command, *ssh_allowed_signers, *ssh_revocation_file;
static enum signature_trust_level configured_min_trust_level = TRUST_UNDEFINED;
struct gpg_format {
@ -55,6 +56,10 @@ static int verify_gpg_signed_buffer(struct signature_check *sigc,
@@ -55,6 +56,10 @@ 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,
@ -90,7 +95,7 @@ static struct gpg_format gpg_format[] = {
@@ -90,7 +95,7 @@ static struct gpg_format gpg_format[] = {
.program = "ssh-keygen",
.verify_args = ssh_verify_args,
.sigs = ssh_sigs,
.verify_signed_buffer = NULL, /* TODO */
.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,
@ -357,6 +362,200 @@ static int verify_gpg_signed_buffer(struct signature_check *sigc,
@@ -357,6 +362,200 @@ static int verify_gpg_signed_buffer(struct signature_check *sigc,
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;
}
int check_signature(const char *payload, size_t plen, const char *signature,
size_t slen, struct signature_check *sigc)
{
@ -473,6 +672,18 @@ int git_gpg_config(const char *var, const char *value, void *cb)
@@ -473,6 +672,18 @@ int git_gpg_config(const char *var, const char *value, void *cb)
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"))
fmtname = "openpgp";