api: Return Tofu info for signatures.
authorWerner Koch <wk@gnupg.org>
Sat, 21 May 2016 08:29:49 +0000 (10:29 +0200)
committerWerner Koch <wk@gnupg.org>
Sat, 21 May 2016 08:32:51 +0000 (10:32 +0200)
* 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>
NEWS
src/gpgme.h.in
src/status-table.c
src/verify.c
tests/run-verify.c

diff --git a/NEWS b/NEWS
index 119866e..04cfe12 100644 (file)
--- 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]
index 5f7896d..335ed6b 100644 (file)
@@ -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;
 
index e70cb8b..5850a36 100644 (file)
@@ -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 },
index 4781d99..e6c9665 100644 (file)
@@ -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
index b7be320..df8cbf6 100644 (file)
@@ -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)