summaryrefslogtreecommitdiff
path: root/gpg-interface.c
diff options
context:
space:
mode:
Diffstat (limited to 'gpg-interface.c')
-rw-r--r--gpg-interface.c158
1 files changed, 122 insertions, 36 deletions
diff --git a/gpg-interface.c b/gpg-interface.c
index 3e7255a2a9..17b1e44baa 100644
--- a/gpg-interface.c
+++ b/gpg-interface.c
@@ -19,8 +19,8 @@ struct gpg_format {
const char **verify_args;
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,
+ struct gpg_format *fmt,
+ const char *signature,
size_t signature_size);
int (*sign_buffer)(struct strbuf *buffer, struct strbuf *signature,
const char *signing_key);
@@ -53,12 +53,12 @@ static const char *ssh_sigs[] = {
};
static int verify_gpg_signed_buffer(struct signature_check *sigc,
- struct gpg_format *fmt, const char *payload,
- size_t payload_size, const char *signature,
+ struct gpg_format *fmt,
+ 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,
+ struct gpg_format *fmt,
+ const char *signature,
size_t signature_size);
static int sign_buffer_gpg(struct strbuf *buffer, struct strbuf *signature,
const char *signing_key);
@@ -314,8 +314,8 @@ error:
}
static int verify_gpg_signed_buffer(struct signature_check *sigc,
- struct gpg_format *fmt, const char *payload,
- size_t payload_size, const char *signature,
+ struct gpg_format *fmt,
+ const char *signature,
size_t signature_size)
{
struct child_process gpg = CHILD_PROCESS_INIT;
@@ -343,14 +343,13 @@ static int verify_gpg_signed_buffer(struct signature_check *sigc,
NULL);
sigchain_push(SIGPIPE, SIG_IGN);
- ret = pipe_command(&gpg, payload, payload_size, &gpg_stdout, 0,
+ ret = pipe_command(&gpg, sigc->payload, sigc->payload_len, &gpg_stdout, 0,
&gpg_stderr, 0);
sigchain_pop(SIGPIPE);
delete_tempfile(&temp);
ret |= !strstr(gpg_stdout.buf, "\n[GNUPG:] GOODSIG ");
- sigc->payload = xmemdupz(payload, payload_size);
sigc->output = strbuf_detach(&gpg_stderr, NULL);
sigc->gpg_status = strbuf_detach(&gpg_stdout, NULL);
@@ -426,20 +425,26 @@ cleanup:
}
static int verify_ssh_signed_buffer(struct signature_check *sigc,
- struct gpg_format *fmt, const char *payload,
- size_t payload_size, const char *signature,
+ struct gpg_format *fmt,
+ 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;
+ struct strbuf verify_time = STRBUF_INIT;
+ const struct date_mode verify_date_mode = {
+ .type = DATE_STRFTIME,
+ .strftime_fmt = "%Y%m%d%H%M%S",
+ /* SSH signing key validity has no timezone information - Use the local timezone */
+ .local = 1,
+ };
if (!ssh_allowed_signers) {
error(_("gpg.ssh.allowedSignersFile needs to be configured and exist for ssh signature verification"));
@@ -457,11 +462,16 @@ static int verify_ssh_signed_buffer(struct signature_check *sigc,
return -1;
}
+ if (sigc->payload_timestamp)
+ strbuf_addf(&verify_time, "-Overify-time=%s",
+ show_date(sigc->payload_timestamp, 0, &verify_date_mode));
+
/* 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,
+ verify_time.buf,
NULL);
ret = pipe_command(&ssh_keygen, NULL, 0, &ssh_principals_out, 0,
&ssh_principals_err, 0);
@@ -479,8 +489,9 @@ static int verify_ssh_signed_buffer(struct signature_check *sigc,
"-Y", "check-novalidate",
"-n", "git",
"-s", buffer_file->filename.buf,
+ verify_time.buf,
NULL);
- pipe_command(&ssh_keygen, payload, payload_size,
+ pipe_command(&ssh_keygen, sigc->payload, sigc->payload_len,
&ssh_keygen_out, 0, &ssh_keygen_err, 0);
/*
@@ -490,15 +501,30 @@ static int verify_ssh_signed_buffer(struct signature_check *sigc,
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;
+ const char *next;
+ for (line = ssh_principals_out.buf;
+ *line;
+ line = next) {
+ const char *end_of_text;
+
+ next = end_of_text = strchrnul(line, '\n');
+
+ /* Did we find a LF, and did we have CR before it? */
+ if (*end_of_text &&
+ line < end_of_text &&
+ end_of_text[-1] == '\r')
+ end_of_text--;
- trust_size = strcspn(line, "\n");
- principal = xmemdupz(line, trust_size);
+ /* Unless we hit NUL, skip over the LF we found */
+ if (*next)
+ next++;
+
+ /* Not all lines are data. Skip empty ones */
+ if (line == end_of_text)
+ continue;
+
+ /* We now know we have an non-empty line. Process it */
+ principal = xmemdupz(line, end_of_text - line);
child_process_init(&ssh_keygen);
strbuf_release(&ssh_keygen_out);
@@ -513,6 +539,7 @@ static int verify_ssh_signed_buffer(struct signature_check *sigc,
"-f", ssh_allowed_signers,
"-I", principal,
"-s", buffer_file->filename.buf,
+ verify_time.buf,
NULL);
if (ssh_revocation_file) {
@@ -526,7 +553,7 @@ static int verify_ssh_signed_buffer(struct signature_check *sigc,
}
sigchain_push(SIGPIPE, SIG_IGN);
- ret = pipe_command(&ssh_keygen, payload, payload_size,
+ ret = pipe_command(&ssh_keygen, sigc->payload, sigc->payload_len,
&ssh_keygen_out, 0, &ssh_keygen_err, 0);
sigchain_pop(SIGPIPE);
@@ -540,7 +567,6 @@ static int verify_ssh_signed_buffer(struct signature_check *sigc,
}
}
- 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 */
@@ -558,12 +584,48 @@ out:
strbuf_release(&ssh_principals_err);
strbuf_release(&ssh_keygen_out);
strbuf_release(&ssh_keygen_err);
+ strbuf_release(&verify_time);
return ret;
}
-int check_signature(const char *payload, size_t plen, const char *signature,
- size_t slen, struct signature_check *sigc)
+static int parse_payload_metadata(struct signature_check *sigc)
+{
+ const char *ident_line = NULL;
+ size_t ident_len;
+ struct ident_split ident;
+ const char *signer_header;
+
+ switch (sigc->payload_type) {
+ case SIGNATURE_PAYLOAD_COMMIT:
+ signer_header = "committer";
+ break;
+ case SIGNATURE_PAYLOAD_TAG:
+ signer_header = "tagger";
+ break;
+ case SIGNATURE_PAYLOAD_UNDEFINED:
+ case SIGNATURE_PAYLOAD_PUSH_CERT:
+ /* Ignore payloads we don't want to parse */
+ return 0;
+ default:
+ BUG("invalid value for sigc->payload_type");
+ }
+
+ ident_line = find_commit_header(sigc->payload, signer_header, &ident_len);
+ if (!ident_line || !ident_len)
+ return 1;
+
+ if (split_ident_line(&ident, ident_line, ident_len))
+ return 1;
+
+ if (!sigc->payload_timestamp && ident.date_begin && ident.date_end)
+ sigc->payload_timestamp = parse_timestamp(ident.date_begin, NULL, 10);
+
+ return 0;
+}
+
+int check_signature(struct signature_check *sigc,
+ const char *signature, size_t slen)
{
struct gpg_format *fmt;
int status;
@@ -575,8 +637,10 @@ int check_signature(const char *payload, size_t plen, const char *signature,
if (!fmt)
die(_("bad/incompatible signature '%s'"), signature);
- status = fmt->verify_signed_buffer(sigc, fmt, payload, plen, signature,
- slen);
+ if (parse_payload_metadata(sigc))
+ return 1;
+
+ status = fmt->verify_signed_buffer(sigc, fmt, signature, slen);
if (status && !sigc->output)
return !!status;
@@ -593,7 +657,7 @@ void print_signature_buffer(const struct signature_check *sigc, unsigned flags)
sigc->output;
if (flags & GPG_VERIFY_VERBOSE && sigc->payload)
- fputs(sigc->payload, stdout);
+ fwrite(sigc->payload, 1, sigc->payload_len, stdout);
if (output)
fputs(output, stderr);
@@ -707,6 +771,21 @@ int git_gpg_config(const char *var, const char *value, void *cb)
return 0;
}
+/*
+ * Returns 1 if `string` contains a literal ssh key, 0 otherwise
+ * `key` will be set to the start of the actual key if a prefix is present.
+ */
+static int is_literal_ssh_key(const char *string, const char **key)
+{
+ if (skip_prefix(string, "key::", key))
+ return 1;
+ if (starts_with(string, "ssh-")) {
+ *key = string;
+ return 1;
+ }
+ return 0;
+}
+
static char *get_ssh_key_fingerprint(const char *signing_key)
{
struct child_process ssh_keygen = CHILD_PROCESS_INIT;
@@ -714,15 +793,16 @@ static char *get_ssh_key_fingerprint(const char *signing_key)
struct strbuf fingerprint_stdout = STRBUF_INIT;
struct strbuf **fingerprint;
char *fingerprint_ret;
+ const char *literal_key = NULL;
/*
* 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-")) {
+ if (is_literal_ssh_key(signing_key, &literal_key)) {
strvec_pushl(&ssh_keygen.args, "ssh-keygen", "-lf", "-", NULL);
- ret = pipe_command(&ssh_keygen, signing_key,
- strlen(signing_key), &fingerprint_stdout, 0,
+ ret = pipe_command(&ssh_keygen, literal_key,
+ strlen(literal_key), &fingerprint_stdout, 0,
NULL, 0);
} else {
strvec_pushl(&ssh_keygen.args, "ssh-keygen", "-lf",
@@ -757,6 +837,7 @@ static const char *get_default_ssh_signing_key(void)
const char **argv;
int n;
char *default_key = NULL;
+ const char *literal_key = NULL;
if (!ssh_default_key_command)
die(_("either user.signingkey or gpg.ssh.defaultKeyCommand needs to be configured"));
@@ -774,7 +855,11 @@ static const char *get_default_ssh_signing_key(void)
if (!ret) {
keys = strbuf_split_max(&key_stdout, '\n', 2);
- if (keys[0] && starts_with(keys[0]->buf, "ssh-")) {
+ if (keys[0] && is_literal_ssh_key(keys[0]->buf, &literal_key)) {
+ /*
+ * We only use `is_literal_ssh_key` here to check validity
+ * The prefix will be stripped when the key is used.
+ */
default_key = strbuf_detach(keys[0], NULL);
} else {
warning(_("gpg.ssh.defaultKeyCommand succeeded but returned no keys: %s %s"),
@@ -889,19 +974,20 @@ static int sign_buffer_ssh(struct strbuf *buffer, struct strbuf *signature,
struct tempfile *key_file = NULL, *buffer_file = NULL;
char *ssh_signing_key_file = NULL;
struct strbuf ssh_signature_filename = STRBUF_INIT;
+ const char *literal_key = NULL;
if (!signing_key || signing_key[0] == '\0')
return error(
_("user.signingkey needs to be set for ssh signing"));
- if (starts_with(signing_key, "ssh-")) {
+ if (is_literal_ssh_key(signing_key, &literal_key)) {
/* 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 ||
+ keylen = strlen(literal_key);
+ if (write_in_full(key_file->fd, literal_key, keylen) < 0 ||
close_tempfile_gently(key_file) < 0) {
error_errno(_("failed writing ssh signing key to '%s'"),
key_file->filename.buf);