aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--NEWS9
-rw-r--r--src/gpgme.h.in61
-rw-r--r--src/status-table.c3
-rw-r--r--src/verify.c196
-rw-r--r--tests/run-verify.c39
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 <string.h>
#include <errno.h>
#include <assert.h>
+#include <limits.h>
#include "gpgme.h"
#include "debug.h"
@@ -49,6 +50,22 @@ typedef struct
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)
{
op_data_t opd = (op_data_t) 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 <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
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
@@ -94,9 +94,23 @@ 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)