This commit introduces command line options for git tag to allow adding trusted time-stamps from a Time Stamping Authority according to RFC3161. The SHA-1 has used for a time-stamp signature is generated from the header data and the tag message, if present. After obtaining the time-stamp signature, it is inserted into the object header under the `timesig`-key in a custom PEM-like format. If the tag is also GPG-signed, the GPG signature includes the time-stamp signature to prevent attackers from altering the time-stamp signature or replacing it. However, it is still possible to create tags with only a GPG signature or only a time-stamp, although it is recommended to additionally GPG-sign time-stamp signatures for the reasons stated above. In contrast to the GPG signature, the time-stamp signatures are part of the header, emulating the way GPG signatures of signed commits are stored. This facilitates implementing RFC3161 time-stamps for commits eventually. Signed-off-by: Anton Würfel <anton.wuerfel@xxxxxx> Signed-off-by: Phillip Raffeck <phillip.raffeck@xxxxxx> --- builtin/tag.c | 55 ++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 52 insertions(+), 3 deletions(-) diff --git a/builtin/tag.c b/builtin/tag.c index 1705c94..9b3d2a1 100644 --- a/builtin/tag.c +++ b/builtin/tag.c @@ -18,9 +18,10 @@ #include "sha1-array.h" #include "column.h" #include "ref-filter.h" +#include "rfc3161.h" static const char * const git_tag_usage[] = { - N_("git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>] <tagname> [<head>]"), + N_("git tag [-a | -s | -u <key-id> | -t] [-f] [-m <msg> | -F <file>] <tagname> [<head>]"), N_("git tag -d <tagname>..."), N_("git tag -l [-n[<num>]] [--contains <commit>] [--points-at <object>]" "\n\t\t[--format=<format>] [--[no-]merged [<commit>]] [<pattern>...]"), @@ -118,6 +119,39 @@ static int do_sign(struct strbuf *buffer) return sign_buffer(buffer, buffer, get_signing_key()); } +static int do_timesig(struct strbuf *buffer) +{ + struct strbuf sig = STRBUF_INIT; + int inspos, copypos; + + /* find the end of the header */ + inspos = strstr(buffer->buf, "\n\n") - buffer->buf + 1; + + if (create_time_signature(buffer, &sig)) { + strbuf_release(&sig); + return -1; + } + + for (copypos = 0; sig.buf[copypos]; ) { + const char *bol = sig.buf + copypos; + const char *eol = strchrnul(bol, '\n'); + int len = (eol - bol) + !!*eol; + + if (!copypos) { + strbuf_insert(buffer, inspos, time_sig_header, + time_sig_header_len); + inspos += time_sig_header_len; + } + strbuf_insert(buffer, inspos++, " ", 1); + strbuf_insert(buffer, inspos, bol, len); + inspos += len; + copypos += len; + } + strbuf_release(&sig); + + return 0; +} + static const char tag_template[] = N_("\nWrite a message for tag:\n %s\n" "Lines starting with '%c' will be ignored.\n"); @@ -193,8 +227,11 @@ static void write_tag_body(int fd, const unsigned char *sha1) free(buf); } -static int build_tag_object(struct strbuf *buf, int sign, unsigned char *result) +static int build_tag_object(struct strbuf *buf, int sign, int timesig, + unsigned char *result) { + if (timesig && do_timesig(buf) < 0) + return error(_("unable to generate time-stamp signature")); if (sign && do_sign(buf) < 0) return error(_("unable to sign the tag")); if (write_sha1_file(buf->buf, buf->len, tag_type, result) < 0) @@ -205,6 +242,7 @@ static int build_tag_object(struct strbuf *buf, int sign, unsigned char *result) struct create_tag_options { unsigned int message_given:1; unsigned int sign; + unsigned int timesig; enum { CLEANUP_NONE, CLEANUP_SPACE, @@ -276,7 +314,7 @@ static void create_tag(const unsigned char *object, const char *tag, strbuf_insert(buf, 0, header_buf, header_len); - if (build_tag_object(buf, opt->sign, result) < 0) { + if (build_tag_object(buf, opt->sign, opt->timesig, result) < 0) { if (path) fprintf(stderr, _("The tag message has been left in %s\n"), path); @@ -350,6 +388,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix) N_("tag message"), parse_msg_arg), OPT_FILENAME('F', "file", &msgfile, N_("read message from file")), OPT_BOOL('s', "sign", &opt.sign, N_("annotated and GPG-signed tag")), + OPT_BOOL('t', "timestamp", &opt.timesig, N_("add trusted RFC3161 time-stamp")), OPT_STRING(0, "cleanup", &cleanup_arg, N_("mode"), N_("how to strip spaces and #comments from message")), OPT_STRING('u', "local-user", &keyid, N_("key-id"), @@ -387,6 +426,16 @@ int cmd_tag(int argc, const char **argv, const char *prefix) } if (opt.sign) annotate = 1; + +#if defined(NO_CURL) || defined(NO_OPENSSL) + if (opt.timesig) + return error("git has been compiled without RFC3161 time-stamp support. " + "NO_CURL and NO_OPENSSL must not be defined"); +#else + if (opt.timesig) + annotate = 1; +#endif + if (argc == 0 && !cmdmode) cmdmode = 'l'; -- 2.8.0.rc0.62.gfc8aefa.dirty -- To unsubscribe from this list: send the line "unsubscribe git" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html