diff options
Diffstat (limited to 'gpg-interface.c')
-rw-r--r-- | gpg-interface.c | 158 |
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); |