From 9ee103957e4136337b92d238283f8ef30fd4a7c5 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Thu, 25 Aug 2016 11:38:03 +0200 Subject: [PATCH] core: Add GPGME_KEYLIST_MODE_WITH_TOFU. * src/gpgme.h.in (GPGME_KEYLIST_MODE_WITH_TOFU): New. * src/engine-gpg.c (gpg_keylist_build_options): Use that. * src/keylist.c: Include limits.h. (parse_tfs_record): New. (keylist_colon_handler): Support TFS record. * tests/run-keylist.c: Include time.h. (isotimestr): New. (main): Add option --tofu. Print TOFU info. * tests/run-verify.c: Include time.h. (isotimestr): New. (print_result): Use isotimestr for TOFU dates. Signed-off-by: Werner Koch --- NEWS | 1 + doc/gpgme.texi | 5 +++ src/engine-gpg.c | 7 ++++ src/gpgme.h.in | 3 +- src/keylist.c | 100 +++++++++++++++++++++++++++++++++++++++++--- tests/run-keylist.c | 60 ++++++++++++++++++++++---- tests/run-verify.c | 25 ++++++++++- 7 files changed, 186 insertions(+), 15 deletions(-) diff --git a/NEWS b/NEWS index 1294e0b7..da331b4b 100644 --- a/NEWS +++ b/NEWS @@ -26,6 +26,7 @@ Noteworthy changes in version 1.7.0 (unreleased) [C25/A14/R_] GPGME_STATUS_TOFU_STATS NEW. GPGME_STATUS_TOFU_STATS_LONG NEW. GPGME_STATUS_NOTATION_FLAGS NEW. + GPGME_KEYLIST_MODE_WITH_TOFU NEW. GPGME_DATA_TYPE_PGP_ENCRYPTED NEW. GPGME_DATA_TYPE_PGP_SIGNATURE NEW. GPGME_DATA_ENCODING_MIME NEW. diff --git a/doc/gpgme.texi b/doc/gpgme.texi index 02551d98..dfc9548b 100644 --- a/doc/gpgme.texi +++ b/doc/gpgme.texi @@ -2683,6 +2683,11 @@ signature notations on key signatures should be included in the listed keys. This only works if @code{GPGME_KEYLIST_MODE_SIGS} is also enabled. +@item GPGME_KEYLIST_MODE_WITH_TOFU +The @code{GPGME_KEYLIST_MODE_WITH_TOFU} symbol specifies that +information pertaining to the TOFU trust model should be included in +the listed keys. + @item GPGME_KEYLIST_MODE_WITH_SECRET The @code{GPGME_KEYLIST_MODE_WITH_SECRET} returns information about the presence of a corresponding secret key in a public key listing. A diff --git a/src/engine-gpg.c b/src/engine-gpg.c index 3edac6ca..7036ee08 100644 --- a/src/engine-gpg.c +++ b/src/engine-gpg.c @@ -2338,8 +2338,13 @@ gpg_keylist_build_options (engine_gpg_t gpg, int secret_only, err = add_arg (gpg, "--with-fingerprint"); } + if (!err && (mode & GPGME_KEYLIST_MODE_WITH_TOFU) + && have_gpg_version (gpg, "2.1.16")) + err = add_arg (gpg, "--with-tofu-info"); + if (!err && (mode & GPGME_KEYLIST_MODE_WITH_SECRET)) err = add_arg (gpg, "--with-secret"); + if (!err && (mode & GPGME_KEYLIST_MODE_SIGS) && (mode & GPGME_KEYLIST_MODE_SIG_NOTATIONS)) @@ -2348,6 +2353,7 @@ gpg_keylist_build_options (engine_gpg_t gpg, int secret_only, if (!err) err = add_arg (gpg, "show-sig-subpackets=\"20,26\""); } + if (!err) { if ( (mode & GPGME_KEYLIST_MODE_EXTERN) ) @@ -2379,6 +2385,7 @@ gpg_keylist_build_options (engine_gpg_t gpg, int secret_only, ? "--check-sigs" : "--list-keys")); } } + if (!err) err = add_arg (gpg, "--"); diff --git a/src/gpgme.h.in b/src/gpgme.h.in index 79a7b9fd..57f34469 100644 --- a/src/gpgme.h.in +++ b/src/gpgme.h.in @@ -411,6 +411,7 @@ gpgme_protocol_t; #define GPGME_KEYLIST_MODE_SIGS 4 #define GPGME_KEYLIST_MODE_SIG_NOTATIONS 8 #define GPGME_KEYLIST_MODE_WITH_SECRET 16 +#define GPGME_KEYLIST_MODE_WITH_TOFU 32 #define GPGME_KEYLIST_MODE_EPHEMERAL 128 #define GPGME_KEYLIST_MODE_VALIDATE 256 @@ -843,7 +844,7 @@ struct _gpgme_user_id * NULL is stored. */ char *address; - /* The malloced tofo information or NULL. */ + /* The malloced TOFU information or NULL. */ gpgme_tofu_info_t tofu; }; typedef struct _gpgme_user_id *gpgme_user_id_t; diff --git a/src/keylist.c b/src/keylist.c index 38ddd0c5..9f1e68db 100644 --- a/src/keylist.c +++ b/src/keylist.c @@ -33,6 +33,7 @@ #include #include #include +#include /* Suppress warning for accessing deprecated member "class". */ #define _GPGME_IN_GPGME @@ -403,6 +404,84 @@ parse_sec_field15 (gpgme_key_t key, gpgme_subkey_t subkey, char *field) } +/* Parse a tfs record. */ +static gpg_error_t +parse_tfs_record (gpgme_user_id_t uid, char **field, int nfield) +{ + gpg_error_t err; + gpgme_tofu_info_t ti; + unsigned long uval; + + /* We add only the first TOFU record in case future versions emit + * several. */ + if (uid->tofu) + return 0; + + /* Check that we have enough fields and that the version is supported. */ + if (nfield < 8 || atoi(field[1]) != 1) + return trace_gpg_error (GPG_ERR_INV_ENGINE); + + ti = calloc (1, sizeof *ti); + if (!ti) + return gpg_error_from_syserror (); + + /* Note that we allow a value of up to 7 which is what we can store + * in the ti->validity. */ + err = _gpgme_strtoul_field (field[2], &uval); + if (err || uval > 7) + goto inv_engine; + ti->validity = uval; + + /* Parse the sign-count. */ + err = _gpgme_strtoul_field (field[3], &uval); + if (err) + goto inv_engine; + if (uval > USHRT_MAX) + uval = USHRT_MAX; + ti->signcount = uval; + + /* Parse the encr-count. */ + err = _gpgme_strtoul_field (field[4], &uval); + if (err) + goto inv_engine; + if (uval > USHRT_MAX) + uval = USHRT_MAX; + ti->encrcount = uval; + + /* Parse the policy. */ + if (!strcmp (field[5], "none")) + ti->policy = GPGME_TOFU_POLICY_NONE; + else if (!strcmp (field[5], "auto")) + ti->policy = GPGME_TOFU_POLICY_AUTO; + else if (!strcmp (field[5], "good")) + ti->policy = GPGME_TOFU_POLICY_GOOD; + else if (!strcmp (field[5], "bad")) + ti->policy = GPGME_TOFU_POLICY_BAD; + else if (!strcmp (field[5], "ask")) + ti->policy = GPGME_TOFU_POLICY_ASK; + else /* "unknown" and invalid policy strings. */ + ti->policy = GPGME_TOFU_POLICY_UNKNOWN; + + /* Parse first and last seen timestamps. */ + err = _gpgme_strtoul_field (field[6], &uval); + if (err) + goto inv_engine; + ti->firstseen = uval; + err = _gpgme_strtoul_field (field[7], &uval); + if (err) + goto inv_engine; + ti->lastseen = uval; + + /* Ready. */ + uid->tofu = ti; + return 0; + + inv_engine: + free (ti); + return trace_gpg_error (GPG_ERR_INV_ENGINE); +} + + /* We have read an entire key into tmp_key and should now finish it. It is assumed that this releases tmp_key. */ static void @@ -426,7 +505,7 @@ keylist_colon_handler (void *priv, char *line) gpgme_ctx_t ctx = (gpgme_ctx_t) priv; enum { - RT_NONE, RT_SIG, RT_UID, RT_SUB, RT_PUB, RT_FPR, RT_GRP, + RT_NONE, RT_SIG, RT_UID, RT_TFS, RT_SUB, RT_PUB, RT_FPR, RT_GRP, RT_SSB, RT_SEC, RT_CRT, RT_CRS, RT_REV, RT_SPK } rectype = RT_NONE; @@ -483,6 +562,8 @@ keylist_colon_handler (void *priv, char *line) rectype = RT_GRP; else if (!strcmp (field[0], "uid") && key) rectype = RT_UID; + else if (!strcmp (field[0], "tfs") && key) + rectype = RT_TFS; else if (!strcmp (field[0], "sub") && key) rectype = RT_SUB; else if (!strcmp (field[0], "ssb") && key) @@ -492,10 +573,10 @@ keylist_colon_handler (void *priv, char *line) else rectype = RT_NONE; - /* Only look at signatures immediately following a user ID. For - this, clear the user ID pointer when encountering anything but a - signature. */ - if (rectype != RT_SIG && rectype != RT_REV) + /* Only look at signature and trust info records immediately + following a user ID. For this, clear the user ID pointer when + encountering anything but a signature or trust record. */ + if (rectype != RT_SIG && rectype != RT_REV && rectype != RT_TFS) opd->tmp_uid = NULL; /* Only look at subpackets immediately following a signature. For @@ -695,6 +776,15 @@ keylist_colon_handler (void *priv, char *line) } break; + case RT_TFS: + if (opd->tmp_uid) + { + err = parse_tfs_record (opd->tmp_uid, field, fields); + if (err) + return err; + } + break; + case RT_FPR: /* Field 10 has the fingerprint (take only the first one). */ if (fields >= 10 && field[9] && *field[9]) diff --git a/tests/run-keylist.c b/tests/run-keylist.c index bae2dbb9..00f874da 100644 --- a/tests/run-keylist.c +++ b/tests/run-keylist.c @@ -26,6 +26,7 @@ #include #include #include +#include #include @@ -49,6 +50,7 @@ show_usage (int ex) " --local use GPGME_KEYLIST_MODE_LOCAL\n" " --extern use GPGME_KEYLIST_MODE_EXTERN\n" " --sigs use GPGME_KEYLIST_MODE_SIGS\n" + " --tofu use GPGME_KEYLIST_MODE_TOFU\n" " --sig-notations use GPGME_KEYLIST_MODE_SIG_NOTATIONS\n" " --ephemeral use GPGME_KEYLIST_MODE_EPHEMERAL\n" " --validate use GPGME_KEYLIST_MODE_VALIDATE\n" @@ -60,6 +62,26 @@ show_usage (int ex) } +static const char * +isotimestr (unsigned long value) +{ + time_t t; + static char buffer[25+5]; + struct tm *tp; + + if (!value) + return "none"; + t = value; + + tp = gmtime (&t); + snprintf (buffer, sizeof buffer, "%04d-%02d-%02d %02d:%02d:%02d", + 1900+tp->tm_year, tp->tm_mon+1, tp->tm_mday, + tp->tm_hour, tp->tm_min, tp->tm_sec); + return buffer; +} + + + int main (int argc, char **argv) { @@ -120,6 +142,11 @@ main (int argc, char **argv) mode |= GPGME_KEYLIST_MODE_EXTERN; argc--; argv++; } + else if (!strcmp (*argv, "--tofu")) + { + mode |= GPGME_KEYLIST_MODE_WITH_TOFU; + argc--; argv++; + } else if (!strcmp (*argv, "--sigs")) { mode |= GPGME_KEYLIST_MODE_SIGS; @@ -181,6 +208,7 @@ main (int argc, char **argv) while (!(err = gpgme_op_keylist_next (ctx, &key))) { gpgme_user_id_t uid; + gpgme_tofu_info_t ti; int nuids; int nsub; @@ -233,24 +261,42 @@ main (int argc, char **argv) for (nuids=0, uid=key->uids; uid; uid = uid->next, nuids++) { printf ("userid %d: %s\n", nuids, nonnull(uid->uid)); - printf (" mbox %d: %s\n", nuids, nonnull(uid->address)); + printf (" mbox: %s\n", nonnull(uid->address)); if (uid->email && uid->email != uid->address) - printf (" email %d: %s\n", nuids, uid->email); + printf (" email: %s\n", uid->email); if (uid->name) - printf (" name %d: %s\n", nuids, uid->name); + printf (" name: %s\n", uid->name); if (uid->comment) - printf (" cmmnt %d: %s\n", nuids, uid->comment); - printf (" valid %d: %s\n", nuids, + printf (" cmmnt: %s\n", uid->comment); + printf (" valid: %s\n", uid->validity == GPGME_VALIDITY_UNKNOWN? "unknown": uid->validity == GPGME_VALIDITY_UNDEFINED? "undefined": uid->validity == GPGME_VALIDITY_NEVER? "never": uid->validity == GPGME_VALIDITY_MARGINAL? "marginal": uid->validity == GPGME_VALIDITY_FULL? "full": uid->validity == GPGME_VALIDITY_ULTIMATE? "ultimate": "[?]"); + if ((ti = uid->tofu)) + { + printf (" tofu: %u (%s)\n", ti->validity, + ti->validity == 0? "conflict" : + ti->validity == 1? "no history" : + ti->validity == 2? "little history" : + ti->validity == 3? "enough history" : + ti->validity == 4? "lot of history" : "?"); + printf (" policy: %u (%s)\n", ti->policy, + ti->policy == GPGME_TOFU_POLICY_NONE? "none" : + ti->policy == GPGME_TOFU_POLICY_AUTO? "auto" : + ti->policy == GPGME_TOFU_POLICY_GOOD? "good" : + ti->policy == GPGME_TOFU_POLICY_UNKNOWN? "unknown" : + ti->policy == GPGME_TOFU_POLICY_BAD? "bad" : + ti->policy == GPGME_TOFU_POLICY_ASK? "ask" : "?"); + printf (" nsigs: %hu\n", ti->signcount); + printf (" nencr: %hu\n", ti->encrcount); + printf (" first: %s\n", isotimestr (ti->firstseen)); + printf (" last: %s\n", isotimestr (ti->lastseen)); + } } - - putchar ('\n'); if (import) diff --git a/tests/run-verify.c b/tests/run-verify.c index ef4dd32e..3c18d3b6 100644 --- a/tests/run-verify.c +++ b/tests/run-verify.c @@ -26,6 +26,7 @@ #include #include #include +#include #include @@ -36,6 +37,26 @@ static int verbose; + +static const char * +isotimestr (unsigned long value) +{ + time_t t; + static char buffer[25+5]; + struct tm *tp; + + if (!value) + return "none"; + t = value; + + tp = gmtime (&t); + snprintf (buffer, sizeof buffer, "%04d-%02d-%02d %02d:%02d:%02d", + 1900+tp->tm_year, tp->tm_mon+1, tp->tm_mday, + tp->tm_hour, tp->tm_min, tp->tm_sec); + return buffer; +} + + static gpg_error_t status_cb (void *opaque, const char *keyword, const char *value) { @@ -177,8 +198,8 @@ print_result (gpgme_verify_result_t result) ti->policy == GPGME_TOFU_POLICY_BAD? "bad" : ti->policy == GPGME_TOFU_POLICY_ASK? "ask" : "?"); printf (" sigcount : %hu\n", ti->signcount); - printf (" firstseen: %u\n", ti->firstseen); - printf (" lastseen : %u\n", ti->lastseen); + printf (" firstseen: %s\n", isotimestr (ti->firstseen)); + printf (" lastseen : %s\n", isotimestr (ti->lastseen)); printf (" desc ....: "); print_description (nonnull (ti->description), 15); }