зеркало из https://github.com/microsoft/git.git
ssh signing: add ssh key format and signing code
Implements the actual sign_buffer_ssh operation and move some shared cleanup code into a strbuf function Set gpg.format = ssh and user.signingkey to either a ssh public key string (like from an authorized_keys file), or a ssh key file. If the key file or the config value itself contains only a public key then the private key needs to be available via ssh-agent. gpg.ssh.program can be set to an alternative location of ssh-keygen. A somewhat recent openssh version (8.2p1+) of ssh-keygen is needed for this feature. Since only ssh-keygen is needed it can this way be installed seperately without upgrading your system openssh packages. Signed-off-by: Fabian Stelzer <fs@gigacodes.de> Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
Родитель
64625c728f
Коммит
29b315778e
|
@ -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
|
||||||
|
|
|
@ -36,3 +36,8 @@ 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.
|
||||||
|
|
138
gpg-interface.c
138
gpg-interface.c
|
@ -41,12 +41,20 @@ 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,
|
static int verify_gpg_signed_buffer(struct signature_check *sigc,
|
||||||
struct gpg_format *fmt, const char *payload,
|
struct gpg_format *fmt, const char *payload,
|
||||||
size_t payload_size, const char *signature,
|
size_t payload_size, const char *signature,
|
||||||
size_t signature_size);
|
size_t signature_size);
|
||||||
static int sign_buffer_gpg(struct strbuf *buffer, struct strbuf *signature,
|
static int sign_buffer_gpg(struct strbuf *buffer, struct strbuf *signature,
|
||||||
const char *signing_key);
|
const char *signing_key);
|
||||||
|
static int sign_buffer_ssh(struct strbuf *buffer, struct strbuf *signature,
|
||||||
|
const char *signing_key);
|
||||||
|
|
||||||
static struct gpg_format gpg_format[] = {
|
static struct gpg_format gpg_format[] = {
|
||||||
{
|
{
|
||||||
|
@ -65,6 +73,14 @@ static struct gpg_format gpg_format[] = {
|
||||||
.verify_signed_buffer = verify_gpg_signed_buffer,
|
.verify_signed_buffer = verify_gpg_signed_buffer,
|
||||||
.sign_buffer = sign_buffer_gpg,
|
.sign_buffer = sign_buffer_gpg,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
.name = "ssh",
|
||||||
|
.program = "ssh-keygen",
|
||||||
|
.verify_args = ssh_verify_args,
|
||||||
|
.sigs = ssh_sigs,
|
||||||
|
.verify_signed_buffer = NULL, /* TODO */
|
||||||
|
.sign_buffer = sign_buffer_ssh
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
static struct gpg_format *use_format = &gpg_format[0];
|
static struct gpg_format *use_format = &gpg_format[0];
|
||||||
|
@ -443,6 +459,9 @@ int git_gpg_config(const char *var, const char *value, void *cb)
|
||||||
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);
|
||||||
|
@ -463,12 +482,30 @@ int sign_buffer(struct strbuf *buffer, struct strbuf *signature, const char *sig
|
||||||
return use_format->sign_buffer(buffer, signature, 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,
|
static int sign_buffer_gpg(struct strbuf *buffer, struct strbuf *signature,
|
||||||
const char *signing_key)
|
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,
|
||||||
|
@ -494,13 +531,98 @@ static int sign_buffer_gpg(struct strbuf *buffer, struct strbuf *signature,
|
||||||
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;
|
||||||
|
}
|
||||||
|
|
Загрузка…
Ссылка в новой задаче