From 10df06ee8f9192309bf124872438f7c32457e1c6 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Sat, 21 May 2016 10:29:49 +0200 Subject: [PATCH] api: Return Tofu info for signatures. * src/gpgme.h.in (gpgme_tofu_policy_t): New. (gpgme_status_code_t): Add status codes for TOFU. (struct _gpgme_tofu_info, gpgme_tofu_info_t): New. (struct _gpgme_signature): Add field 'tofu'. * src/status-table.c (status_table): Add new codes. * src/verify.c: Include limits.h. (release_tofu_info): New. (release_op_data): Call that. (parse_tofu_user): New. (parse_tofu_stats): New. (parse_tofu_stats_long): New. (_gpgme_verify_status_handler): Handle TOFU status lines. * tests/run-verify.c (print_description): New. (print_result): print tofu info. Signed-off-by: Werner Koch --- NEWS | 9 ++- src/gpgme.h.in | 61 +++++++++++++- src/status-table.c | 3 + src/verify.c | 196 +++++++++++++++++++++++++++++++++++++++++++++ tests/run-verify.c | 39 +++++++++ 5 files changed, 306 insertions(+), 2 deletions(-) diff --git a/NEWS b/NEWS index 119866e1..04cfe12d 100644 --- a/NEWS +++ b/NEWS @@ -6,8 +6,15 @@ Noteworthy changes in version 1.7.0 (unreleased) [C25/A14/R_] * Interface changes relative to the 1.6.0 release: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ gpgme_pubkey_algo_string NEW. - gpgme_set_ctx_flag NEW. GPGME_PK_EDDSA NEW. + gpgme_set_ctx_flag NEW. + gpgme_signature_t EXTENDED: New field tofu. + gpgme_tofu_policy_t NEW. + gpgme_tofu_info_t NEW. + GPGME_STATUS_KEY_CONSIDERED NEW. + GPGME_STATUS_TOFU_USER NEW. + GPGME_STATUS_TOFU_STATS NEW. + GPGME_STATUS_TOFU_STATS_LONG NEW. Noteworthy changes in version 1.6.0 (2015-08-26) [C25/A14/R0] diff --git a/src/gpgme.h.in b/src/gpgme.h.in index 5f7896de..335ed6b5 100644 --- a/src/gpgme.h.in +++ b/src/gpgme.h.in @@ -371,6 +371,19 @@ typedef enum gpgme_validity_t; +/* The TOFU policies. */ +typedef enum + { + GPGME_TOFU_POLICY_NONE = 0, + GPGME_TOFU_POLICY_AUTO = 1, + GPGME_TOFU_POLICY_GOOD = 2, + GPGME_TOFU_POLICY_UNKNOWN = 3, + GPGME_TOFU_POLICY_BAD = 4, + GPGME_TOFU_POLICY_ASK = 5 + } +gpgme_tofu_policy_t; + + /* The available protocols. */ typedef enum { @@ -533,7 +546,10 @@ typedef enum GPGME_STATUS_KEY_NOT_CREATED = 91, GPGME_STATUS_INQUIRE_MAXLEN = 92, GPGME_STATUS_FAILURE = 93, - GPGME_STATUS_KEY_CONSIDERED = 94 + GPGME_STATUS_KEY_CONSIDERED = 94, + GPGME_STATUS_TOFU_USER = 95, + GPGME_STATUS_TOFU_STATS = 96, + GPGME_STATUS_TOFU_STATS_LONG = 97 } gpgme_status_code_t; @@ -1533,6 +1549,46 @@ typedef enum } gpgme_sigsum_t; + +struct _gpgme_tofu_info +{ + struct _gpgme_tofu_info *next; + + /* The mail address (addr-spec from RFC5322) of the tofu binding. */ + char *address; + + /* The fingerprint of the primary key. */ + char *fpr; + + /* The TOFU validity: + * 0 := conflict + * 1 := key without history + * 2 := key with too little history + * 3 := key with enough history for basic trust + * 4 := key with a lot of history + */ + unsigned int validity : 3; + + /* The TOFU policy (gpgme_tofu_policy_t). */ + unsigned int policy : 4; + + unsigned int _rfu : 25; + + /* Number of signatures seen for this binding. Capped at USHRT_MAX. */ + unsigned short signcount; + unsigned short reserved; + + /* Number of seconds since the first and the most recently seen + * message was verified. */ + unsigned int firstseen; + unsigned int lastseen; + + /* If non-NULL a human readable string summarizing the TOFU data. */ + char *description; +}; +typedef struct _gpgme_tofu_info *gpgme_tofu_info_t; + + struct _gpgme_signature { struct _gpgme_signature *next; @@ -1578,6 +1634,9 @@ struct _gpgme_signature /* The mailbox from the PKA information or NULL. */ char *pka_address; + + /* If non-NULL, TOFU info for this signature are available. */ + gpgme_tofu_info_t tofu; }; typedef struct _gpgme_signature *gpgme_signature_t; diff --git a/src/status-table.c b/src/status-table.c index e70cb8bd..5850a361 100644 --- a/src/status-table.c +++ b/src/status-table.c @@ -124,6 +124,9 @@ static struct status_table_s status_table[] = { "SIG_SUBPACKET", GPGME_STATUS_SIG_SUBPACKET }, { "SIGEXPIRED", GPGME_STATUS_SIGEXPIRED }, { "SUCCESS", GPGME_STATUS_SUCCESS }, + { "TOFU_STATS", GPGME_STATUS_TOFU_STATS }, + { "TOFU_STATS_LONG", GPGME_STATUS_TOFU_STATS_LONG }, + { "TOFU_USER", GPGME_STATUS_TOFU_USER }, { "TRUNCATED", GPGME_STATUS_TRUNCATED }, { "TRUST_FULLY", GPGME_STATUS_TRUST_FULLY }, { "TRUST_MARGINAL", GPGME_STATUS_TRUST_MARGINAL }, diff --git a/src/verify.c b/src/verify.c index 4781d995..e6c9665f 100644 --- a/src/verify.c +++ b/src/verify.c @@ -26,6 +26,7 @@ #include #include #include +#include #include "gpgme.h" #include "debug.h" @@ -48,6 +49,22 @@ typedef struct } *op_data_t; +static void +release_tofu_info (gpgme_tofu_info_t t) +{ + while (t) + { + gpgme_tofu_info_t t2 = t->next; + + free (t->address); + free (t->fpr); + free (t->description); + free (t); + t = t2; + } +} + + static void release_op_data (void *hook) { @@ -71,6 +88,7 @@ release_op_data (void *hook) free (sig->fpr); if (sig->pka_address) free (sig->pka_address); + release_tofu_info (sig->tofu); free (sig); sig = next; } @@ -635,6 +653,169 @@ parse_trust (gpgme_signature_t sig, gpgme_status_code_t code, char *args) } +/* Parse a TOFU_USER line and put the info into SIG. */ +static gpgme_error_t +parse_tofu_user (gpgme_signature_t sig, char *args) +{ + gpg_error_t err; + char *tail; + gpgme_tofu_info_t ti, ti2; + + tail = strchr (args, ' '); + if (!tail || tail == args) + return trace_gpg_error (GPG_ERR_INV_ENGINE); /* No fingerprint. */ + *tail++ = 0; + + ti = calloc (1, sizeof *ti); + if (!ti) + return gpg_error_from_syserror (); + + ti->fpr = strdup (args); + if (!ti->fpr) + { + free (ti); + return gpg_error_from_syserror (); + } + + args = tail; + tail = strchr (args, ' '); + if (tail == args) + return trace_gpg_error (GPG_ERR_INV_ENGINE); /* No addr-spec. */ + if (tail) + *tail = 0; + + err = _gpgme_decode_percent_string (args, &ti->address, 0, 0); + if (err) + { + free (ti); + return err; + } + + /* Append to the tofu info list. */ + if (!sig->tofu) + sig->tofu = ti; + else + { + for (ti2 = sig->tofu; ti2->next; ti2 = ti2->next) + ; + ti2->next = ti; + } + + return 0; +} + + +/* Parse a TOFU_STATS line and store it in the last tofu info of SIG. + * + * TOFU_STATS 0 [ [ ]] + */ +static gpgme_error_t +parse_tofu_stats (gpgme_signature_t sig, char *args) +{ + gpgme_error_t err; + gpgme_tofu_info_t ti; + char *field[6]; + int nfields; + unsigned long uval; + + if (!sig->tofu) + return trace_gpg_error (GPG_ERR_INV_ENGINE); /* No TOFU_USER seen. */ + for (ti = sig->tofu; ti->next; ti = ti->next) + ; + if (ti->firstseen || ti->signcount || ti->validity || ti->policy) + return trace_gpg_error (GPG_ERR_INV_ENGINE); /* Already seen. */ + + nfields = _gpgme_split_fields (args, field, DIM (field)); + if (nfields < 3) + return trace_gpg_error (GPG_ERR_INV_ENGINE); /* Required args missing. */ + + /* 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[0], &uval); + if (err || uval > 7) + return trace_gpg_error (GPG_ERR_INV_ENGINE); + ti->validity = uval; + + /* Parse the sign-count. */ + err = _gpgme_strtoul_field (field[1], &uval); + if (err) + return trace_gpg_error (GPG_ERR_INV_ENGINE); + if (uval > USHRT_MAX) + uval = USHRT_MAX; + ti->signcount = uval; + + /* We skip the 0, which is RFU. */ + + if (nfields == 3) + return 0; /* All mandatory fields parsed. */ + + /* Parse the policy. */ + if (!strcmp (field[3], "none")) + ti->policy = GPGME_TOFU_POLICY_NONE; + else if (!strcmp (field[3], "auto")) + ti->policy = GPGME_TOFU_POLICY_AUTO; + else if (!strcmp (field[3], "good")) + ti->policy = GPGME_TOFU_POLICY_GOOD; + else if (!strcmp (field[3], "bad")) + ti->policy = GPGME_TOFU_POLICY_BAD; + else if (!strcmp (field[3], "ask")) + ti->policy = GPGME_TOFU_POLICY_ASK; + else /* "unknown" and invalid policy strings. */ + ti->policy = GPGME_TOFU_POLICY_UNKNOWN; + + if (nfields == 4) + return 0; /* No more optional fields. */ + + /* Parse first and last seen (none or both are required). */ + if (nfields < 6) + return trace_gpg_error (GPG_ERR_INV_ENGINE); /* "tm2" missing. */ + err = _gpgme_strtoul_field (field[4], &uval); + if (err) + return trace_gpg_error (GPG_ERR_INV_ENGINE); + if (uval > UINT_MAX) + uval = UINT_MAX; + ti->firstseen = uval; + err = _gpgme_strtoul_field (field[5], &uval); + if (err) + return trace_gpg_error (GPG_ERR_INV_ENGINE); + if (uval > UINT_MAX) + uval = UINT_MAX; + ti->lastseen = uval; + + return 0; +} + + +/* Parse a TOFU_STATS_LONG line and store it in the last tofu info of SIG. */ +static gpgme_error_t +parse_tofu_stats_long (gpgme_signature_t sig, char *args, int raw) +{ + gpgme_error_t err; + gpgme_tofu_info_t ti; + char *p; + + if (!sig->tofu) + return trace_gpg_error (GPG_ERR_INV_ENGINE); /* No TOFU_USER seen. */ + for (ti = sig->tofu; ti->next; ti = ti->next) + ; + if (ti->description) + return trace_gpg_error (GPG_ERR_INV_ENGINE); /* Already seen. */ + + err = _gpgme_decode_percent_string (args, &ti->description, 0, 0); + if (err) + return err; + + /* Remove the non-breaking spaces. */ + if (!raw) + { + for (p = ti->description; *p; p++) + if (*p == '~') + *p = ' '; + } + return 0; +} + + /* Parse an error status line and if SET_STATUS is true update the result status as appropriate. With SET_STATUS being false, only check for an error. */ @@ -766,6 +947,21 @@ _gpgme_verify_status_handler (void *priv, gpgme_status_code_t code, char *args) sig->pka_address = strdup (args); break; + case GPGME_STATUS_TOFU_USER: + opd->only_newsig_seen = 0; + return sig ? parse_tofu_user (sig, args) + /* */ : trace_gpg_error (GPG_ERR_INV_ENGINE); + + case GPGME_STATUS_TOFU_STATS: + opd->only_newsig_seen = 0; + return sig ? parse_tofu_stats (sig, args) + /* */ : trace_gpg_error (GPG_ERR_INV_ENGINE); + + case GPGME_STATUS_TOFU_STATS_LONG: + opd->only_newsig_seen = 0; + return sig ? parse_tofu_stats_long (sig, args, ctx->raw_description) + /* */ : trace_gpg_error (GPG_ERR_INV_ENGINE); + case GPGME_STATUS_ERROR: opd->only_newsig_seen = 0; /* Some error stati are informational, so we don't return an diff --git a/tests/run-verify.c b/tests/run-verify.c index b7be3203..df8cbf65 100644 --- a/tests/run-verify.c +++ b/tests/run-verify.c @@ -93,10 +93,24 @@ print_validity (gpgme_validity_t val) } +static void +print_description (const char *text, int indent) +{ + for (; *text; text++) + { + putchar (*text); + if (*text == '\n') + printf ("%*s", indent, ""); + } + putchar ('\n'); +} + + static void print_result (gpgme_verify_result_t result) { gpgme_signature_t sig; + gpgme_tofu_info_t ti; int count = 0; printf ("Original file name: %s\n", nonnull(result->file_name)); @@ -126,6 +140,30 @@ print_result (gpgme_verify_result_t result) ); printf (" notations .: %s\n", sig->notations? "yes":"no"); + for (ti = sig->tofu; ti; ti = ti->next) + { + printf (" tofu addr .: %s\n", ti->address); + if (!sig->fpr || strcmp (sig->fpr, ti->fpr)) + printf (" WARNING .: fpr mismatch (%s)\n", ti->fpr); + printf (" validity : %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 (" sigcount : %hu\n", ti->signcount); + printf (" firstseen: %u\n", ti->firstseen); + printf (" lastseen : %u\n", ti->lastseen); + printf (" desc ....: "); + print_description (nonnull (ti->description), 15); + } } } @@ -230,6 +268,7 @@ main (int argc, char **argv) gpgme_set_status_cb (ctx, status_cb, NULL); gpgme_set_ctx_flag (ctx, "full-status", "1"); } + /* gpgme_set_ctx_flag (ctx, "raw-description", "1"); */ err = gpgme_data_new_from_stream (&sig, fp_sig); if (err)