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 <wk@gnupg.org>
This commit is contained in:
Werner Koch 2016-05-21 10:29:49 +02:00
parent dac2c5441d
commit 10df06ee8f
No known key found for this signature in database
GPG Key ID: E3FDFF218E45B72B
5 changed files with 306 additions and 2 deletions

9
NEWS
View File

@ -6,8 +6,15 @@ Noteworthy changes in version 1.7.0 (unreleased) [C25/A14/R_]
* Interface changes relative to the 1.6.0 release: * Interface changes relative to the 1.6.0 release:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
gpgme_pubkey_algo_string NEW. gpgme_pubkey_algo_string NEW.
gpgme_set_ctx_flag NEW.
GPGME_PK_EDDSA 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] Noteworthy changes in version 1.6.0 (2015-08-26) [C25/A14/R0]

View File

@ -371,6 +371,19 @@ typedef enum
gpgme_validity_t; 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. */ /* The available protocols. */
typedef enum typedef enum
{ {
@ -533,7 +546,10 @@ typedef enum
GPGME_STATUS_KEY_NOT_CREATED = 91, GPGME_STATUS_KEY_NOT_CREATED = 91,
GPGME_STATUS_INQUIRE_MAXLEN = 92, GPGME_STATUS_INQUIRE_MAXLEN = 92,
GPGME_STATUS_FAILURE = 93, 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; gpgme_status_code_t;
@ -1533,6 +1549,46 @@ typedef enum
} }
gpgme_sigsum_t; 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
{ {
struct _gpgme_signature *next; struct _gpgme_signature *next;
@ -1578,6 +1634,9 @@ struct _gpgme_signature
/* The mailbox from the PKA information or NULL. */ /* The mailbox from the PKA information or NULL. */
char *pka_address; 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; typedef struct _gpgme_signature *gpgme_signature_t;

View File

@ -124,6 +124,9 @@ static struct status_table_s status_table[] =
{ "SIG_SUBPACKET", GPGME_STATUS_SIG_SUBPACKET }, { "SIG_SUBPACKET", GPGME_STATUS_SIG_SUBPACKET },
{ "SIGEXPIRED", GPGME_STATUS_SIGEXPIRED }, { "SIGEXPIRED", GPGME_STATUS_SIGEXPIRED },
{ "SUCCESS", GPGME_STATUS_SUCCESS }, { "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 }, { "TRUNCATED", GPGME_STATUS_TRUNCATED },
{ "TRUST_FULLY", GPGME_STATUS_TRUST_FULLY }, { "TRUST_FULLY", GPGME_STATUS_TRUST_FULLY },
{ "TRUST_MARGINAL", GPGME_STATUS_TRUST_MARGINAL }, { "TRUST_MARGINAL", GPGME_STATUS_TRUST_MARGINAL },

View File

@ -26,6 +26,7 @@
#include <string.h> #include <string.h>
#include <errno.h> #include <errno.h>
#include <assert.h> #include <assert.h>
#include <limits.h>
#include "gpgme.h" #include "gpgme.h"
#include "debug.h" #include "debug.h"
@ -48,6 +49,22 @@ typedef struct
} *op_data_t; } *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 static void
release_op_data (void *hook) release_op_data (void *hook)
{ {
@ -71,6 +88,7 @@ release_op_data (void *hook)
free (sig->fpr); free (sig->fpr);
if (sig->pka_address) if (sig->pka_address)
free (sig->pka_address); free (sig->pka_address);
release_tofu_info (sig->tofu);
free (sig); free (sig);
sig = next; 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 <validity> <sign-count> 0 [<policy> [<tm1> <tm2>]]
*/
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 /* Parse an error status line and if SET_STATUS is true update the
result status as appropriate. With SET_STATUS being false, only result status as appropriate. With SET_STATUS being false, only
check for an error. */ 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); sig->pka_address = strdup (args);
break; 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: case GPGME_STATUS_ERROR:
opd->only_newsig_seen = 0; opd->only_newsig_seen = 0;
/* Some error stati are informational, so we don't return an /* Some error stati are informational, so we don't return an

View File

@ -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 static void
print_result (gpgme_verify_result_t result) print_result (gpgme_verify_result_t result)
{ {
gpgme_signature_t sig; gpgme_signature_t sig;
gpgme_tofu_info_t ti;
int count = 0; int count = 0;
printf ("Original file name: %s\n", nonnull(result->file_name)); 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", printf (" notations .: %s\n",
sig->notations? "yes":"no"); 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_status_cb (ctx, status_cb, NULL);
gpgme_set_ctx_flag (ctx, "full-status", "1"); 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); err = gpgme_data_new_from_stream (&sig, fp_sig);
if (err) if (err)