[git] GnuPG - branch, neal/next, updated. gnupg-2.1.8-41-g7fc7f29

by Neal H. Walfield cvs at cvs.gnupg.org
Tue Sep 22 01:24:49 CEST 2015


This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "The GNU Privacy Guard".

The branch, neal/next has been updated
  discards  c764bd201db458c2e3fe0a2ae17592547bc5d9b5 (commit)
       via  7fc7f29a53f1e0d46b1fdc8d7c264f1cbdfa0935 (commit)
       via  1542dc604b9c3e6a6a99750c48f7800e72584a89 (commit)
       via  708b7eccdef8d274bd5578b9a5fd908e9685c795 (commit)

This update added new revisions after undoing existing revisions.  That is
to say, the old revision is not a strict subset of the new revision.  This
situation occurs when you --force push a change and generate a repository
containing something like this:

 * -- * -- B -- O -- O -- O (c764bd201db458c2e3fe0a2ae17592547bc5d9b5)
            \
             N -- N -- N (7fc7f29a53f1e0d46b1fdc8d7c264f1cbdfa0935)

When this happens we assume that you've already had alert emails for all
of the O revisions, and so we here report only the revisions in the N
branch from the common base, B.

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
commit 7fc7f29a53f1e0d46b1fdc8d7c264f1cbdfa0935
Author: Neal H. Walfield <neal at g10code.com>
Date:   Sat Sep 19 01:32:51 2015 +0200

    TOFU stuff.

diff --git a/configure.ac b/configure.ac
index 27fc9a9..bee69ec 100644
--- a/configure.ac
+++ b/configure.ac
@@ -780,6 +780,12 @@ DL_LIBS=$LIBS
 AC_SUBST(DL_LIBS)
 LIBS="$gnupg_dlopen_save_libs"
 
+# Checks for g10
+
+PKG_CHECK_MODULES(SQLITE3, sqlite3)
+AC_SUBST(SQLITE3_CFLAGS)
+AC_SUBST(SQLITE3_LIBS)
+
 # Checks for g13
 
 AC_PATH_PROG(ENCFS, encfs, /usr/bin/encfs)
diff --git a/g10/Makefile.am b/g10/Makefile.am
index 2fd52b3..cf5a19f 100644
--- a/g10/Makefile.am
+++ b/g10/Makefile.am
@@ -25,7 +25,7 @@ AM_CPPFLAGS = -I$(top_srcdir)/common
 
 include $(top_srcdir)/am/cmacros.am
 
-AM_CFLAGS = $(LIBGCRYPT_CFLAGS) \
+AM_CFLAGS = $(SQLITE3_CFLAGS) $(LIBGCRYPT_CFLAGS) \
             $(LIBASSUAN_CFLAGS) $(GPG_ERROR_CFLAGS)
 
 needed_libs = ../kbx/libkeybox.a $(libcommon)
@@ -125,7 +125,8 @@ gpg2_SOURCES  = gpg.c		\
 	      call-agent.c call-agent.h \
 	      trust.c $(trust_source) \
 	      $(card_source) \
-	      exec.c exec.h
+	      exec.c exec.h \
+	      tofu.h tofu.c
 
 gpgv2_SOURCES = gpgv.c           \
 	      $(common_source)  \
@@ -140,7 +141,7 @@ gpgv2_SOURCES = gpgv.c           \
 
 LDADD =  $(needed_libs) ../common/libgpgrl.a \
          $(ZLIBS) $(LIBINTL) $(CAPLIBS) $(NETLIBS)
-gpg2_LDADD = $(LDADD) $(LIBGCRYPT_LIBS) $(LIBREADLINE) \
+gpg2_LDADD = $(LDADD) $(SQLITE3_LIBS) $(LIBGCRYPT_LIBS) $(LIBREADLINE) \
              $(LIBASSUAN_LIBS) $(GPG_ERROR_LIBS) \
 	     $(LIBICONV) $(resource_objs) $(extra_sys_libs)
 gpg2_LDFLAGS = $(extra_bin_ldflags)
diff --git a/g10/gpg.c b/g10/gpg.c
index 9454b53..b5efc02 100644
--- a/g10/gpg.c
+++ b/g10/gpg.c
@@ -384,6 +384,7 @@ enum cmd_and_opt_values
     oFakedSystemTime,
     oNoAutostart,
     oPrintPKARecords,
+    oTOFUDefaultTrust,
 
     oNoop
   };
@@ -669,6 +670,7 @@ static ARGPARSE_OPTS opts[] = {
   ARGPARSE_s_i (oDefCertLevel, "default-cert-check-level", "@"), /* old */
   ARGPARSE_s_n (oAlwaysTrust, "always-trust", "@"),
   ARGPARSE_s_s (oTrustModel, "trust-model", "@"),
+  ARGPARSE_s_s (oTOFUDefaultTrust, "tofu-default-trust", "@"),
   ARGPARSE_s_s (oSetFilename, "set-filename", "@"),
   ARGPARSE_s_n (oForYourEyesOnly, "for-your-eyes-only", "@"),
   ARGPARSE_s_n (oNoForYourEyesOnly, "no-for-your-eyes-only", "@"),
@@ -1937,6 +1939,12 @@ parse_trust_model(const char *model)
     opt.trust_model=TM_ALWAYS;
   else if(ascii_strcasecmp(model,"direct")==0)
     opt.trust_model=TM_DIRECT;
+  else if(ascii_strcasecmp(model,"tofu")==0)
+    opt.trust_model=TM_TOFU;
+  else if(ascii_strcasecmp(model,"tofu+pgp")==0)
+    opt.trust_model=TM_TOFU_PGP;
+  else if(ascii_strcasecmp(model,"tofu+wot")==0)
+    opt.trust_model=TM_TOFU_PGP;
   else if(ascii_strcasecmp(model,"auto")==0)
     opt.trust_model=TM_AUTO;
   else
@@ -1944,6 +1952,21 @@ parse_trust_model(const char *model)
 }
 #endif /*NO_TRUST_MODELS*/
 
+static void
+parse_tofu_default_trust (const char *default_trust)
+{
+  if (ascii_strcasecmp(default_trust, "ask") == 0)
+    opt.tofu_default_trust = TRUST_ASK;
+  else if (ascii_strcasecmp(default_trust, "undefined") == 0)
+    opt.tofu_default_trust = TRUST_UNDEFINED;
+  else if (ascii_strcasecmp(default_trust, "marginal") == 0)
+    opt.tofu_default_trust = TRUST_MARGINAL;
+  else if (ascii_strcasecmp(default_trust, "full") == 0
+	   || ascii_strcasecmp(default_trust, "fully") == 0)
+    opt.tofu_default_trust = TRUST_FULLY;
+  else
+    log_error("unknown default trust '%s'\n", default_trust);
+}
 
 /* This fucntion called to initialized a new control object.  It is
    assumed that this object has been zeroed out before calling this
@@ -2551,6 +2574,9 @@ main (int argc, char **argv)
 	    parse_trust_model(pargs.r.ret_str);
 	    break;
 #endif /*!NO_TRUST_MODELS*/
+	  case oTOFUDefaultTrust:
+	    parse_tofu_default_trust (pargs.r.ret_str);
+	    break;
 
 	  case oForceOwnertrust:
 	    log_info(_("Note: %s is not for normal use!\n"),
diff --git a/g10/mainproc.c b/g10/mainproc.c
index f7b7c6b..ef08b3a 100644
--- a/g10/mainproc.c
+++ b/g10/mainproc.c
@@ -835,6 +835,7 @@ do_check_sig (CTX c, kbnode_t node, int *is_selfsig,
   PKT_signature *sig;
   gcry_md_hd_t md = NULL;
   gcry_md_hd_t md2 = NULL;
+  gcry_md_hd_t md_good = NULL;
   int algo, rc;
 
   assert (node->pkt->pkttype == PKT_SIGNATURE);
@@ -910,8 +911,21 @@ do_check_sig (CTX c, kbnode_t node, int *is_selfsig,
     return GPG_ERR_SIG_CLASS;
 
   rc = signature_check2 (sig, md, NULL, is_expkey, is_revkey, NULL);
-  if (gpg_err_code (rc) == GPG_ERR_BAD_SIGNATURE && md2)
-    rc = signature_check2 (sig, md2, NULL, is_expkey, is_revkey, NULL);
+  if (! rc)
+    md_good = md;
+  else if (gpg_err_code (rc) == GPG_ERR_BAD_SIGNATURE && md2)
+    {
+      rc = signature_check2 (sig, md2, NULL, is_expkey, is_revkey, NULL);
+      if (! rc)
+	md_good = md2;
+    }
+
+  if (md_good)
+    {
+      unsigned char *buffer = gcry_md_read (md_good, 0);
+      sig->digest_len = gcry_md_get_algo_dlen (map_md_openpgp_to_gcry (algo));
+      memcpy (sig->digest, buffer, sig->digest_len);
+    }
 
   gcry_md_close (md);
   gcry_md_close (md2);
diff --git a/g10/options.h b/g10/options.h
index fd2f4a2..7ba2555 100644
--- a/g10/options.h
+++ b/g10/options.h
@@ -117,8 +117,10 @@ struct
      we started storing the trust model inside the trustdb. */
   enum
     {
-      TM_CLASSIC=0, TM_PGP=1, TM_EXTERNAL=2, TM_ALWAYS, TM_DIRECT, TM_AUTO
+      TM_CLASSIC=0, TM_PGP=1, TM_EXTERNAL=2,
+      TM_ALWAYS, TM_DIRECT, TM_AUTO, TM_TOFU, TM_TOFU_PGP
     } trust_model;
+  int tofu_default_trust;
   int force_ownertrust;
   enum
     {
diff --git a/g10/packet.h b/g10/packet.h
index 1906ec5..1aafbb2 100644
--- a/g10/packet.h
+++ b/g10/packet.h
@@ -175,6 +175,11 @@ typedef struct
   subpktarea_t *unhashed;    /* Ditto for unhashed data. */
   byte digest_start[2];      /* First 2 bytes of the digest. */
   gcry_mpi_t  data[PUBKEY_MAX_NSIG];
+  /* The message digest and its length (in bytes).  Note the maximum
+     digest length is 512 bits (64 bytes).  If DIGEST_LEN is 0, then
+     the digest's value has not been saved here.  */
+  byte digest[512 / 8];
+  int digest_len;
 } PKT_signature;
 
 #define ATTRIB_IMAGE 1
diff --git a/g10/pkclist.c b/g10/pkclist.c
index 9996d18..2271292 100644
--- a/g10/pkclist.c
+++ b/g10/pkclist.c
@@ -37,6 +37,7 @@
 #include "status.h"
 #include "photoid.h"
 #include "i18n.h"
+#include "tofu.h"
 
 #define CONTROL_D ('D' - 'A' + 1)
 
@@ -507,13 +508,14 @@ do_we_trust_pre( PKT_public_key *pk, unsigned int trustlevel )
 
 /****************
  * Check whether we can trust this signature.
- * Returns: Error if we shall not trust this signatures.
+ * Returns an error code if we should not trust this signature.
  */
 int
 check_signatures_trust( PKT_signature *sig )
 {
   PKT_public_key *pk = xmalloc_clear( sizeof *pk );
-  unsigned int trustlevel;
+  unsigned int tofu_trustlevel = TRUST_UNDEFINED;
+  unsigned int trustlevel = TRUST_UNDEFINED;
   int rc=0;
 
   rc = get_pubkey( pk, sig->keyid );
@@ -537,6 +539,61 @@ check_signatures_trust( PKT_signature *sig )
     log_info(_("WARNING: this key might be revoked (revocation key"
 	       " not present)\n"));
 
+  if (opt.trust_model == TM_TOFU
+      || opt.trust_model == TM_TOFU_PGP)
+    {
+      /* Get the keyblock.  We need the user ids.  */
+      kbnode_t keyblock = get_pubkeyblock (pk->main_keyid);
+      kbnode_t user_id_node = keyblock;
+      int user_ids;
+
+      char fingerprint[MAX_FINGERPRINT_LEN];
+      size_t fingerprint_len = sizeof (fingerprint);
+
+      fingerprint_from_pk (pk, fingerprint, &fingerprint_len);
+      assert (fingerprint_len == sizeof (fingerprint));
+
+      user_ids = 0;
+      while ((user_id_node = find_next_kbnode (user_id_node, PKT_USER_ID)))
+	{
+	  unsigned int tl;
+	  PKT_user_id *user_id = user_id_node->pkt->pkt.user_id;
+	  user_ids ++;
+
+	  if (user_id->is_revoked || user_id->is_expired)
+	    /* If the user id is revoked or expired, then skip it.  */
+	    {
+	      char *s;
+	      if (user_id->is_revoked && user_id->is_expired)
+		s = "revoked and expired";
+	      else if (user_id->is_revoked)
+		s = "revoked";
+	      else
+		s = "expire";
+
+	      log_info ("TOFU: Ignoring %s user id (%s)\n", s, user_id->name);
+
+	      continue;
+	    }
+
+	  tl = tofu_register (user_id->name, fingerprint,
+			      sig->digest, sig->digest_len,
+			      sig->timestamp, "unknown");
+	  if (tl == TRUST_UNDEFINED)
+	    ;
+	  else if (tl == TRUST_NEVER)
+	    tofu_trustlevel = TRUST_NEVER;
+	  else if (tl > tofu_trustlevel)
+	    /* XXX: We we really want the max?  */
+	    tofu_trustlevel = tl;
+	}
+
+      if (opt.trust_model == TM_TOFU)
+	goto reveal;
+    }
+
+  /* WoT.  */
+
   trustlevel = get_validity (pk, NULL);
 
   if ( (trustlevel & TRUST_FLAG_REVOKED) )
@@ -612,6 +669,33 @@ check_signatures_trust( PKT_signature *sig )
         }
     }
 
+ reveal:
+  if (tofu_trustlevel == TRUST_NEVER || trustlevel == TRUST_NEVER)
+    /* NEVER takes precedence.  */
+    trustlevel = TRUST_NEVER;
+  else if (tofu_trustlevel == TRUST_UNKNOWN
+	   || tofu_trustlevel == TRUST_EXPIRED
+	   || tofu_trustlevel == TRUST_UNDEFINED)
+    /* TRUSTLEVEL is at least as good.  */
+    ;
+  else if (trustlevel == TRUST_UNKNOWN
+	   || trustlevel == TRUST_EXPIRED
+	   || trustlevel == TRUST_UNDEFINED)
+    /* TOFU_TRUSTLEVEL is at least as good.  */
+    trustlevel = tofu_trustlevel;
+  else
+    {
+      assert (trustlevel == TRUST_MARGINAL
+	      || trustlevel == TRUST_FULLY
+	      || trustlevel == TRUST_ULTIMATE);
+      assert (tofu_trustlevel == TRUST_MARGINAL
+	      || tofu_trustlevel == TRUST_FULLY
+	      || tofu_trustlevel == TRUST_ULTIMATE);
+
+      if (tofu_trustlevel > trustlevel)
+	trustlevel = tofu_trustlevel;
+    }
+
   /* Now let the user know what up with the trustlevel. */
   switch ( (trustlevel & TRUST_MASK) )
     {
diff --git a/g10/tofu.c b/g10/tofu.c
new file mode 100644
index 0000000..643b4e0
--- /dev/null
+++ b/g10/tofu.c
@@ -0,0 +1,994 @@
+#include <config.h>
+#include <stdio.h>
+#include <assert.h>
+#include <sqlite3.h>
+
+#include "gpg.h"
+#include "types.h"
+#include "logging.h"
+#include "stringhelp.h"
+#include "options.h"
+#include "mbox-util.h"
+#include "i18n.h"
+#include "trustdb.h"
+
+#include "tofu.h"
+
+/* For each binding, we have a trust judgment.  There are four
+   possible trust judgments.  */
+enum binding_judgment
+  {
+    /* The user explicitly marked the binding as bad.  In this case,
+       we always return TRUST_NEVER.  */
+    BINDING_BAD = -1,
+    /* A decision about the binding has been deferred and we should
+       ask the user.  */
+    BINDING_ASK = 0,
+    /* GnuPG automatically decided that the binding is good due to a
+       lack of conflicts (the email address is not part of another
+       known key).  In this case, we return TRUST_MARGINAL.  */
+    BINDING_AUTO = 1,
+    /* The user explicitly marked the binding as good.  In this case,
+       we return TRUST_FULLY.  */
+    BINDING_GOOD = 2
+  };
+
+static const char *
+binding_judgment_str (enum binding_judgment judgment)
+{
+  switch (judgment)
+    {
+    case BINDING_BAD: return "bad";
+    case BINDING_ASK: return "ask";
+    case BINDING_AUTO: return "good (automatic)";
+    case BINDING_GOOD: return "good (explicit)";
+    default: return "unknown";
+    }
+}
+
+static const char *
+trust_str (int trust)
+{
+  switch (trust)
+    {
+    case TRUST_FULLY: return "fully";
+    case TRUST_MARGINAL: return "marginal";
+    case TRUST_NEVER: return "never";
+    case TRUST_UNDEFINED: return "undefined";
+    case TRUST_ASK: return "ask";
+    default: return "unknown";
+    }
+}
+
+static int
+trust_judgment_to_trust (enum binding_judgment judgment)
+{
+  switch (judgment)
+    {
+    case BINDING_BAD: return TRUST_NEVER;
+    case BINDING_ASK: return TRUST_ASK;
+    case BINDING_AUTO:
+      switch (opt.tofu_default_trust)
+	{
+	case 0:
+	case TRUST_FULLY:
+	  return TRUST_FULLY;
+	case TRUST_MARGINAL:
+	  return TRUST_MARGINAL;
+	case TRUST_NEVER:
+	  return TRUST_NEVER;
+	case TRUST_ASK:
+	  return TRUST_ASK;
+	}
+    case BINDING_GOOD: return TRUST_FULLY;
+    default:
+      log_bug ("Bad judment (%d)!", judgment);
+      return 0;
+    }
+}
+
+static enum binding_judgment
+trust_to_trust_judgment (int trust)
+{
+  switch (trust)
+    {
+    case TRUST_NEVER: return BINDING_BAD;
+    case 0: /* Uninitialized value for opt.tofu_default_trust.  */
+    case TRUST_MARGINAL:
+    case TRUST_FULLY: return BINDING_GOOD;
+    case TRUST_UNDEFINED:
+    case TRUST_ASK: return BINDING_ASK;
+    default:
+      log_bug ("No mapping to judment from trust value (%d)!", trust);
+      return 0;
+    }
+}
+
+static int
+sqlite3_exec_printf (sqlite3 *db,
+		     int (*callback)(void*,int,char**,char**), void *cookie,
+		     char **errmsg,
+		     const char *sql, ...)
+{
+  va_list ap;
+  int rc;
+  char *sql2;
+
+  va_start (ap, sql);
+  sql2 = sqlite3_vmprintf (sql, ap);
+  va_end (ap);
+
+#if 0
+  log_debug ("tofo db: executing: `%s'\n", sql2);
+#endif
+
+  rc = sqlite3_exec (db, sql2, callback, cookie, errmsg);
+
+  sqlite3_free (sql2);
+
+  return rc;
+}
+
+static void
+closedb (sqlite3 *db)
+{
+  sqlite3_close (db);
+}
+
+static int
+version_check_cb (void *cookie, int argc, char **argv, char **azColName)
+{
+  int *version = cookie;
+
+  if (argc != 1)
+    *version = 0;
+  if (strcmp (azColName[0], "version") != 0)
+    *version = 0;
+
+  if (strcmp (argv[0], "1") == 0)
+    *version = 1;
+  else
+    log_error ("tofu: unknown database version: %s\n", argv[0]);
+
+  /* Don't run again.  */
+  return 1;
+}
+
+/* Return 1 if the database is okay and 0 otherwise.  */
+static int
+initdb (sqlite3 *db)
+{
+  char *err = NULL;
+  int rc;
+  int version = -1;
+
+  rc = sqlite3_exec (db, "select version from version;", version_check_cb,
+		     &version, &err);
+  if (rc == SQLITE_ABORT && version == 1)
+    /* Happy, happy, joy, joy.  */
+    {
+      sqlite3_free (err);
+      return 1;
+    }
+
+  /* If the version table doesn't exist, then this might be a new
+     file and we need to initialize the whole sheband.  */
+  rc = sqlite3_exec (db,
+		     "create table version (version INTEGER);",
+		     NULL, NULL, &err);
+  if (rc)
+    {
+      log_error ("Error initialization TOFU database (version): %s\n",
+		 err);
+      sqlite3_free (err);
+      return 0;
+    }
+
+  rc = sqlite3_exec (db,
+		     "insert into version values (1);",
+		     NULL, NULL, &err);
+  if (rc)
+    {
+      log_error ("Error initialization TOFU database (version init): %s\n",
+		 err);
+      sqlite3_free (err);
+      return 0;
+    }
+
+  /* The list of <fingerprint, email> bindings and auxiliary data.
+
+       OID is a unique ID identifying this binding (and used by the
+         signatures table, see below).  Note: OIDs will never be
+         reused.
+
+       FINGERPRINT: The key's fingerprint.
+
+       EMAIL: The normalized email address.
+
+       USER_ID: The unmodified user id from which EMAIL was extracted.
+
+       TIME: The time this binding was first observed.
+
+       JUDGMENT: The trust judgment (-1, 0, 1, or 2; see the
+         documentation for BINDING_BAD, etc. above).  */
+  rc = sqlite3_exec_printf
+    (db, NULL, NULL, &err,
+     "create table bindings\n"
+     " (oid INTEGER PRIMARY KEY AUTOINCREMENT,\n"
+     "  fingerprint TEXT, email TEXT, user_id TEXT, time INTEGER,"
+     "  judgment BOOLEAN CHECK (judgment in (%d, %d, %d, %d)),\n"
+     "  unique (fingerprint, email));",
+     BINDING_BAD, BINDING_ASK, BINDING_AUTO, BINDING_GOOD);
+  if (rc)
+    {
+      log_error ("Error initialization TOFU database (bindings): %s\n",
+		 err);
+      sqlite3_free (err);
+      return 0;
+    }
+
+  /* The signatures that we have observed.
+
+     BINDING refers to a record in the bindings table, which describes
+       the binding.
+
+     SIG_DIGEST is the digest stored in the signature.
+
+     SIG_TIME is the timestamp stored in the signature.
+
+     ORIGIN is a free-form string that describes who fed this
+       signature to GnuPG (e.g., email:claws).
+
+     TIME is the time this signature was registered.  */
+  rc = sqlite3_exec (db,
+		     "create table signatures "
+		     " (binding INTEGER, sig_digest TEXT, origin TEXT,"
+		     "  sig_time INTEGER, time INTEGER,"
+		     "  primary key (binding, sig_digest, origin));",
+		     NULL, NULL, &err);
+  if (rc)
+    {
+      log_error ("Error initialization TOFU database (signatures): %s\n",
+		 err);
+      sqlite3_free (err);
+      return 0;
+    }
+
+  return 1;
+}
+
+/* Open the TOFU database.  */
+static sqlite3 *
+opendb (void)
+{
+  char *filename;
+  sqlite3 *db;
+  int rc = 0;
+
+  filename = make_filename (opt.homedir, "tofu.db", NULL);
+  rc = sqlite3_open (filename, &db);
+  if (rc)
+    {
+      log_error ("Can't open TOFU database ('%s'): %s\n",
+                 filename, sqlite3_errmsg (db));
+      sqlite3_close (db);
+      db = NULL;
+    }
+
+  xfree (filename);
+
+  if (db && ! initdb (db))
+    {
+      closedb (db);
+      db = NULL;
+    }
+
+  return db;
+}
+
+/* Record (or update) a trust judgment about a (possibly new)
+   binding.  */
+static gpg_error_t
+record_binding (sqlite3 *db, const char *fingerprint, const char *email,
+		const char *user_id, int judgment)
+{
+  int rc;
+  char *err = NULL;
+
+  if (! (judgment == BINDING_BAD
+	 || judgment == BINDING_ASK
+	 || judgment == BINDING_AUTO
+	 || judgment == BINDING_GOOD))
+    log_bug ("TOFU: %s: Bad value for judgment (%d)!\n", __func__, judgment);
+
+  log_info ("TOFU: Marking binding <%s, %s> as %s (%d).\n",
+	    fingerprint, email, binding_judgment_str (judgment), judgment);
+
+  rc = sqlite3_exec_printf
+    (db, NULL, NULL, &err,
+     "insert or replace into bindings\n"
+     " (fingerprint, email, user_id, time, judgment)\n"
+     " values (%Q, %Q, %Q, strftime('%%s','now'), %d);",
+     fingerprint, email, user_id, judgment);
+  if (rc)
+    {
+      log_error ("TOFU: Error updating binding database"
+		 " (inserting <%s, %s, %s>): %s\n",
+		 fingerprint, email, binding_judgment_str (judgment), err);
+      sqlite3_free (err);
+      return gpg_error (GPG_ERR_GENERAL);
+    }
+
+  return 0;
+}
+
+/* Collect the strings returned by a query in a simply string list.
+   Any NULL values are converted to the empty string.
+
+   If a result has 3 rows, which contain two columns, then the results
+   are added to the list as follows (the value is parentheses is the
+   1-based index in the final list):
+
+     row 1, col 2 (6)
+     row 1, col 1 (5)
+     row 2, col 2 (4)
+     row 2, col 1 (3)
+     row 3, col 2 (2)
+     row 3, col 1 (1)
+
+   This is because add_to_strlist pushes the results onto the front of
+   the list.  The end result is that the rows are backwards, but the
+   columns are in the expected order.  */
+static int
+strings_collect_cb (void *cookie, int argc, char **argv, char **azColName)
+{
+  int i;
+  strlist_t *strlist = cookie;
+
+  (void) azColName;
+
+  for (i = argc - 1; i >= 0; i --)
+    add_to_strlist (strlist, argv[i] ? argv[i] : "");
+
+  return 0;
+}
+
+/* Auxiliary data structure to collect the statistics about
+   signatures.  */
+struct signature_stats
+{
+  enum binding_judgment judgment;
+
+  /* How long ago the signature was created.  */
+  long time_ago;
+  unsigned long count;
+  struct signature_stats *next;
+
+  /* The key that generated this signature.  */
+  char fingerprint[1];
+};
+
+static void
+signature_stats_free (struct signature_stats *stats)
+{
+  while (stats)
+    {
+      struct signature_stats *next = stats->next;
+      xfree (stats);
+      stats = next;
+    }
+}
+
+/* Process rows that contain four columns:
+
+     <fingerprint, judgment, time ago, count>.  */
+static int
+signature_stats_collect_cb (void *cookie, int argc, char **argv,
+			    char **azColName)
+{
+  struct signature_stats **statsp = cookie;
+  struct signature_stats *stats = xmalloc (sizeof (*stats) + strlen (argv[0]));
+  char *tail;
+  int i = 0;
+
+  (void) azColName;
+
+  stats->next = *statsp;
+  *statsp = stats;
+
+  strcpy (stats->fingerprint, argv[i]);
+  i ++;
+
+  tail = NULL;
+  errno = 0;
+  stats->judgment = strtol (argv[i], &tail, 0);
+  if (errno || ! (strcmp (tail, ".0") == 0 || *tail == '\0'))
+    {
+      /* Abort.  */
+      log_error ("Error converting %s to an integer (tail = `%s')\n",
+		 argv[i], tail);
+      return 1;
+    }
+  i ++;
+
+  tail = NULL;
+  errno = 0;
+  stats->time_ago = strtol (argv[i], &tail, 0);
+  if (errno || ! (strcmp (tail, ".0") == 0 || *tail == '\0'))
+    {
+      /* Abort.  */
+      log_error ("Error converting %s to an integer (tail = `%s')\n",
+		 argv[i], tail);
+      return 1;
+    }
+  i ++;
+
+  tail = NULL;
+  errno = 0;
+  stats->count = strtoul (argv[i], &tail, 0);
+  if (errno || ! (strcmp (tail, ".0") == 0 || *tail == '\0'))
+    {
+      /* Abort.  */
+      log_error ("Error converting %s to an integer (tail = `%s')\n",
+		 argv[i], tail);
+      return 1;
+    }
+  i ++;
+
+  assert (argc == i);
+
+  return 0;
+}
+
+/* The grouping parameters when collecting signature statistics.  */
+
+/* If a message is signed a couple of hours in the future, just assume
+   some clock skew.  */
+#define TIME_AGO_FUTURE_IGNORE (2 * 60 * 60)
+#if 1
+#  define TIME_AGO_UNIT_SMALL 60
+#  define TIME_AGO_UNIT_SMALL_NAME _("minute")
+#  define TIME_AGO_UNIT_SMALL_NAME_PLURAL _("minutes")
+#  define TIME_AGO_MEDIUM_THRESHOLD (60 * TIME_AGO_UNIT_SMALL)
+#  define TIME_AGO_UNIT_MEDIUM (60 * 60)
+#  define TIME_AGO_UNIT_MEDIUM_NAME _("hour")
+#  define TIME_AGO_UNIT_MEDIUM_NAME_PLURAL _("hours")
+#  define TIME_AGO_LARGE_THRESHOLD (24 * 60 * TIME_AGO_UNIT_SMALL)
+#  define TIME_AGO_UNIT_LARGE (24 * 60 * 60)
+#  define TIME_AGO_UNIT_LARGE_NAME _("day")
+#  define TIME_AGO_UNIT_LARGE_NAME_PLURAL _("days")
+#else
+#  define TIME_AGO_UNIT_SMALL (24 * 60 * 60)
+#  define TIME_AGO_UNIT_SMALL_NAME _("day")
+#  define TIME_AGO_UNIT_SMALL_NAME_PLURAL _("days")
+#  define TIME_AGO_MEDIUM_THRESHOLD (4 * TIME_AGO_UNIT_SMALL)
+#  define TIME_AGO_UNIT_MEDIUM (7 * 24 * 60 * 60)
+#  define TIME_AGO_UNIT_MEDIUM_NAME _("week")
+#  define TIME_AGO_UNIT_MEDIUM_NAME_PLURAL _("weeks")
+#  define TIME_AGO_LARGE_THRESHOLD (28 * TIME_AGO_UNIT_SMALL)
+#  define TIME_AGO_UNIT_LARGE (30 * 24 * 60 * 60)
+#  define TIME_AGO_UNIT_LARGE_NAME _("month")
+#  define TIME_AGO_UNIT_LARGE_NAME_PLURAL _("months")
+#endif
+
+signed long
+time_ago_scale (signed long t)
+{
+  if (t < TIME_AGO_UNIT_MEDIUM)
+    return t / TIME_AGO_UNIT_SMALL;
+  if (t < TIME_AGO_UNIT_LARGE)
+    return t / TIME_AGO_UNIT_MEDIUM;
+  return t / TIME_AGO_UNIT_LARGE;
+}
+
+const char *
+time_ago_unit (signed long t)
+{
+  signed long t_scaled = time_ago_scale (t);
+
+  if (t < TIME_AGO_UNIT_MEDIUM)
+    {
+      if (t_scaled == 1)
+	return TIME_AGO_UNIT_SMALL_NAME;
+      return TIME_AGO_UNIT_SMALL_NAME_PLURAL;
+    }
+  if (t < TIME_AGO_UNIT_LARGE)
+    {
+      if (t_scaled == 1)
+	return TIME_AGO_UNIT_MEDIUM_NAME;
+      return TIME_AGO_UNIT_MEDIUM_NAME_PLURAL;
+    }
+  if (t_scaled == 1)
+    return TIME_AGO_UNIT_LARGE_NAME;
+  return TIME_AGO_UNIT_LARGE_NAME_PLURAL;
+}
+
+
+/* Collect results of a select count (*) ...; style query.  */
+static int
+collect_count_cb (void *cookie, int argc, char **argv, char **azColName)
+{
+  unsigned long int *count = cookie;
+  char *tail = NULL;
+
+  (void) azColName;
+
+  assert (argc == 1);
+
+  errno = 0;
+  *count = strtoul (argv[0], &tail, 0);
+  if (errno || *tail != '\0')
+    /* Abort.  */
+    return 1;
+  return 0;
+}
+
+
+int
+tofu_register (const char *user_id, const byte *fingerprint_bin,
+	       const byte *sig_digest_bin, int sig_digest_bin_len,
+	       time_t sig_time, const char *origin)
+{
+  sqlite3 *db = opendb ();
+  char fingerprint[MAX_FINGERPRINT_LEN * 2 + 1];
+  char *email = NULL;
+  char *err = NULL;
+  int rc;
+  int judgment;
+  strlist_t fingerprints = NULL;
+  int count;
+  int trust_level = TRUST_UNKNOWN;
+  int save_sig = 1;
+
+  if (opt.tofu_default_trust == 0)
+    /* Set the default.  */
+    opt.tofu_default_trust = TRUST_FULLY;
+
+  bin2hex (fingerprint_bin, MAX_FINGERPRINT_LEN, fingerprint);
+
+  if (! *user_id)
+    {
+      log_info ("TOFU: user id is empty.  Can't continue.\n");
+      goto die;
+    }
+
+  email = mailbox_from_userid (user_id);
+  if (! email)
+    /* Hmm, no email address was provided.  Just take the lower-case
+       version of the whole user id.  It could be a hostname, for
+       instance.  */
+    email = ascii_strlwr (xstrdup (user_id));
+
+  if (! origin)
+    /* The default origin is simply "unknown".  */
+    origin = "unknown";
+
+
+  /* Check if the <FINGERPRINT, EMAIL> binding is known (-2 is an
+     impossible value, which effectively means that the binding has
+     not yet been added to the DB).  */
+  judgment = -2;
+  assert (judgment != BINDING_BAD && judgment != BINDING_ASK
+	  && judgment != BINDING_AUTO && judgment != BINDING_GOOD);
+  rc = sqlite3_exec_printf
+    (db, collect_count_cb, &judgment, &err,
+     "select judgment from bindings where fingerprint = %Q and email = %Q",
+     fingerprint, email);
+  if (rc)
+    {
+      log_error ("Error reading from TOFU database"
+		 " (checking bad bindings existing): %s\n",
+		 err);
+      sqlite3_free (err);
+      goto die;
+    }
+
+  if (judgment == BINDING_BAD)
+    /* The saved judgement is bad.  We don't need to ask the user
+       anything.  */
+    {
+      log_info ("TOFO: Bad binding: <%s, %s>\n",
+		fingerprint, email);
+      trust_level = TRUST_NEVER;
+      goto out;
+    }
+  if (judgment == BINDING_GOOD)
+    /* The saved judgement is good.  We don't need to ask the user
+       anything.  */
+    {
+      log_info ("TOFO: Good (explicit) binding: <%s, %s>\n",
+		fingerprint, email);
+      trust_level = TRUST_FULLY;
+      goto out;
+    }
+  if (judgment == BINDING_AUTO)
+    /* The saved judgement is auto.  If opt.tofu_default_trust is not
+       ask, we don't have to ask the user anything.  */
+    {
+      log_info ("TOFO: auto (=> %s) binding: <%s, %s>\n",
+		trust_str (opt.tofu_default_trust), fingerprint, email);
+      if (opt.tofu_default_trust != TRUST_ASK)
+	{
+	  assert (opt.tofu_default_trust == TRUST_UNDEFINED
+		  || opt.tofu_default_trust == TRUST_MARGINAL
+		  || opt.tofu_default_trust == TRUST_FULLY);
+	  trust_level = opt.tofu_default_trust;
+	  goto out;
+	}
+    }
+
+  /* We either:
+
+       - Don't have a saved trust judgment (judgment == -2)
+
+       - The saved trust judgement is undefined (last time, the user
+         selected accept once or reject once), or,
+
+       - The saved judgment is auto and the default trust judgment is
+         ask.  */
+
+  /* Get the fingerprints of any bindings that share the email
+     address.  */
+  rc = sqlite3_exec_printf
+    (db, strings_collect_cb, &fingerprints, &err,
+     "select distinct fingerprint from bindings where email = %Q;",
+     email);
+  if (rc)
+    {
+      log_error ("TOFU: Error reading from database"
+		 " (listing fingerprints): %s\n",
+		 err);
+      sqlite3_free (err);
+      goto out;
+    }
+
+  count = strlist_length (fingerprints);
+  if (count == 0 && opt.tofu_default_trust != TRUST_ASK)
+    /* We've never observed a binding with this email address and we
+       have a default judgment, which is not to ask the user.  We're
+       effectively done.  */
+    {
+      /* If we've seen this binding, then we've seen this email and
+	 judgment can't be -2.  */
+      assert (judgment == -2);
+      assert (opt.tofu_default_trust == TRUST_UNDEFINED
+	      || opt.tofu_default_trust == TRUST_MARGINAL
+	      || opt.tofu_default_trust == TRUST_FULLY);
+
+      log_info ("TOFU: Automatically marking binding <%s, %s>.\n",
+		email, fingerprint);
+
+      if (! record_binding (db, fingerprint, email, user_id, BINDING_AUTO))
+	log_error ("TOFU: Error setting binding's trust level to auto\n");
+
+      trust_level = opt.tofu_default_trust;
+      goto out;
+    }
+
+#if 1
+  /* XXX: Is this behavior desirable?  */
+  if (judgment == -2)
+    /* This is a new binding and we have a conflict.  Mark any
+       conflicting bindings that have an automatic judgment as
+       requiring confirmation.  */
+    {
+      rc = sqlite3_exec_printf
+	(db, NULL, NULL, &err,
+	 "update bindings set judgment = %d"
+	 " where email = %Q and judgment = %d;",
+	 BINDING_ASK, email, BINDING_AUTO);
+      if (rc)
+	{
+	  log_error ("TOFU: Error changing judgement: %s\n", err);
+	  sqlite3_free (err);
+	  goto out;
+	}
+    }
+#endif
+
+  /* If we get here, we need to ask the user about the binding.  There
+     are three ways we could end up here:
+
+       - This is a new binding and there is a conflict
+         (judgment == -2 && length(fingerprints) > 0),
+
+       - This is a new binding and opt.tofu_default_trust is set to
+         ask.  (judgment == -2 && opt.tofu_default_trust ==
+         TRUST_ASK), or,
+
+       - The trust judgment is ask (the user deferred last time)
+         (judgment == BINDING_ASK).
+   */
+  {
+    estream_t fp;
+    strlist_t other_user_ids = NULL;
+    struct signature_stats *stats = NULL;
+    struct signature_stats *stats_iter = NULL;
+    char *prompt;
+    char *choices;
+
+    fp = es_fopenmem (0, "rw,samethread");
+    if (! fp)
+      log_fatal ("Error creating memory stream\n");
+
+    if (judgment == -2)
+      es_fprintf (fp, "The binding <%s, %s> is NOT known.  ",
+		  email, fingerprint);
+    es_fprintf (fp, "Please enter whether you believe the binding"
+		" <%s, %s> is good.\n\n",
+		email, fingerprint);
+
+    /* Find other user ids associated with this key and whether the
+       bindings are marked as good or bad.  */
+    rc = sqlite3_exec_printf
+      (db, strings_collect_cb, &other_user_ids, &err,
+       "select user_id, judgment from bindings fingerprint = %Q;",
+       fingerprint);
+    if (rc)
+      {
+	log_error ("TOFU: Error gathering other user ids: %s.\n", err);
+	sqlite3_free (err);
+	err = NULL;
+      }
+
+    if (other_user_ids)
+      {
+	strlist_t strlist_iter;
+
+	es_fprintf (fp, "Known user ids associated with this key:\n");
+	for (strlist_iter = other_user_ids;
+	     strlist_iter;
+	     strlist_iter = strlist_iter->next)
+	  {
+	    char *other_user_id = strlist_iter->d;
+	    char *other_judgment;
+
+	    assert (strlist_iter->next);
+	    strlist_iter = strlist_iter->next;
+	    other_judgment = strlist_iter->d;
+
+	    es_fprintf (fp, "  %s (%s)\n",
+			other_user_id,
+			binding_judgment_str (atoi (other_judgment)));
+	  }
+	es_fprintf (fp, "\n");
+
+	free_strlist (other_user_ids);
+      }
+
+    /* Find other keys associated with this email address.  */
+    rc = sqlite3_exec_printf
+      (db, signature_stats_collect_cb, &stats, &err,
+       "select fingerprint, judgment, time_ago, count(*)\n"
+       " from (select bindings.*,\n"
+       "        case\n"
+       /* From the future (but if its just a couple of hours in the
+	  future don't turn it into a warning)?  Or should we use
+	  small, medium or large units?  (Note: whatever we do, we
+	  keep the value in seconds.  Then when we group, everything
+	  that rounds to the same number of seconds is grouped.)  */
+       "         when delta < -%d then -1\n"
+       "         when delta < %d then max(0, round(delta / %d) * %d)\n"
+       "         when delta < %d then round(delta / %d) * %d\n"
+       "         else round(delta / %d) * %d\n"
+       "        end time_ago,\n"
+       "        delta time_ago_raw\n"
+       "       from (select *,\n"
+       "              cast(strftime('%%s','now') - sig_time as real) delta\n"
+       "             from signatures) ss\n"
+       "       left join bindings on ss.binding = bindings.oid)\n"
+       " where email = %Q\n"
+       " group by fingerprint, time_ago\n"
+       " order by fingerprint = %Q asc, fingerprint desc, time_ago desc;\n",
+       TIME_AGO_FUTURE_IGNORE,
+       TIME_AGO_MEDIUM_THRESHOLD, TIME_AGO_UNIT_SMALL, TIME_AGO_UNIT_SMALL,
+       TIME_AGO_LARGE_THRESHOLD, TIME_AGO_UNIT_MEDIUM, TIME_AGO_UNIT_MEDIUM,
+       TIME_AGO_UNIT_LARGE, TIME_AGO_UNIT_LARGE,
+       email, fingerprint);
+    if (rc)
+      {
+	strlist_t strlist_iter;
+
+	log_error ("TOFU: Error gathering signature stats: %s.\n",
+		   err);
+	sqlite3_free (err);
+	err = NULL;
+
+	es_fprintf
+	  (fp, "The email address (%s) is associated with %d keys:\n",
+	   email, count);
+	for (strlist_iter = fingerprints;
+	     strlist_iter;
+	     strlist_iter = strlist_iter->next)
+	  es_fprintf (fp, "  %s\n", strlist_iter->d);
+      }
+    else
+      {
+	char *key = NULL;
+
+	es_fprintf (fp, "Statistics for keys with the email `%s':\n", email);
+	for (stats_iter = stats; stats_iter; stats_iter = stats_iter->next)
+	  {
+	    if (! key || strcmp (key, stats_iter->fingerprint) != 0)
+	      {
+		int this_key;
+		key = stats_iter->fingerprint;
+		this_key = strcmp (key, fingerprint) == 0;
+		es_fprintf (fp, "  %s", key);
+		if (this_key)
+		  es_fprintf (fp, " (this key):");
+		else
+		  es_fprintf (fp, ", which was judged to be %s:",
+			      binding_judgment_str (stats_iter->judgment));
+		es_fprintf (fp, "\n");
+	      }
+
+	    if (stats_iter->time_ago == -1)
+	      es_fprintf (fp, "    %ld %s signed in the future.\n",
+			  stats_iter->count,
+			  stats_iter->count == 1 ? "message" : "messages");
+	    else
+	      es_fprintf (fp, "    %ld %s %ld %s ago.\n",
+			  stats_iter->count,
+			  stats_iter->count == 1 ? "message" : "messages",
+			  time_ago_scale (stats_iter->time_ago),
+			  time_ago_unit (stats_iter->time_ago));
+	  }
+      }
+
+    es_fputc ('\n', fp);
+    es_fprintf
+      (fp,
+       _("Normally, there is only a single key associated with an email\n"
+	 "address.  However, people sometimes generate a new key if\n"
+	 "their key is too old or they think it might be compromised.\n"
+	 "Alternatively, a new key may indicate a man-in-the-middle attack!\n"
+	 "Before accepting this key, you should talk to or call the person\n"
+	 "to make sure this new key is legitimate.\n"));
+
+    es_fputc ('\n', fp);
+    choices = _("gGaArRbB");
+    es_fprintf (fp, _("(G)ood/(A)ccept once/(R)eject once/(B)ad? "));
+
+    /* Add a NUL terminator.  */
+    es_fputc (0, fp);
+    if (es_fclose_snatch (fp, (void **) &prompt, NULL))
+      log_fatal ("error snatching memory stream\n");
+
+    while (1)
+      {
+	char *response;
+
+	if (strlen (choices) != 8)
+	  log_bug ("Bad TOFU conflict translation!  Please report.");
+
+	response = cpr_get ("tofu conflict", prompt);
+	if (strlen (response) == 1)
+	  {
+	    char *choice = strchr (choices, *response);
+	    if (choice)
+	      {
+		int c = ((size_t) choice - (size_t) choices) / 2;
+		assert (0 <= c && c <= 3);
+
+		switch (c)
+		  {
+		  case 0: /* Good.  */
+		    trust_level = TRUST_FULLY;
+		    record_binding (db, fingerprint, email, user_id,
+				    BINDING_GOOD);
+		    break;
+		  case 1: /* Accept once.  */
+		    /* XXX: If we are here, because ask is the default
+		       do we want to write BINDING_ASK or
+		       BINDING_AUTO.  We probably want
+		       BINDING_AUTO.  */
+		    trust_level = TRUST_FULLY;
+		    if (record_binding (db, fingerprint, email, user_id,
+					BINDING_ASK))
+		      /* If there's an error registering the
+			 binding, don't save the signature.  */
+		      save_sig = 0;
+		    break;
+		  case 2: /* Reject once.  */
+		    /* See the comment for accept once.  */
+		    trust_level = TRUST_NEVER;
+		    if (record_binding (db, fingerprint, email, user_id,
+					BINDING_ASK))
+		      save_sig = 0;
+		    break;
+		  case 3: /* Bad.  */
+		    trust_level = TRUST_NEVER;
+		    if (record_binding (db, fingerprint, email, user_id,
+					BINDING_BAD))
+		      save_sig = 0;
+		    break;
+		  default:
+		    log_bug ("c should be between 0 and 3 but it is %d!", c);
+		  }
+
+		break;
+	      }
+	  }
+	xfree (response);
+      }
+
+    xfree (prompt);
+
+    signature_stats_free (stats);
+  }
+
+ out:
+  if (save_sig)
+    /* Save the observed signature in the DB.  */
+    {
+      char *sig_digest =
+	make_radix64_string (sig_digest_bin, sig_digest_bin_len);
+      unsigned long c;
+
+      /* If we've already seen this signature before, then don't add
+	 it again.  */
+      rc = sqlite3_exec_printf
+	(db, collect_count_cb, &c, &err,
+	 "select count (*)\n"
+	 " from signatures left join bindings\n"
+	 "  on signatures.binding = bindings.oid\n"
+	 " where fingerprint = %Q and email = %Q and sig_time = 0x%lx\n"
+	 "  and sig_digest = %Q",
+	 fingerprint, email, (unsigned long) sig_time, sig_digest, origin);
+      if (rc)
+	{
+	  log_error ("Error reading from SIGNATURES database"
+		     " (checking existence): %s\n",
+		     err);
+	  sqlite3_free (err);
+	}
+      else if (c > 1)
+	/* Duplicates!  This should not happen.  In particular,
+	   because <fingerprint, email, sig_time, sig_digest> is the
+	   primary key!  */
+	log_debug ("SIGNATURES DB contains duplicate records"
+		   " <key: %s, %s, time: 0x%lx, sig: %s, %s>."
+		   "  Please report.\n",
+		   fingerprint, email, (unsigned long) sig_time,
+		   sig_digest, origin);
+      else if (c == 1)
+	log_debug ("Already observed the signature"
+		   " <key: %s, %s, time: 0x%lx, sig: %s, %s>\n",
+		   fingerprint, email, (unsigned long) sig_time,
+		   sig_digest, origin);
+      else
+	/* This is the first time that we've seen this signature.
+	   Record it.  */
+	{
+	  log_debug ("TOFU: Saving signature <%s, %s, %s>\n",
+		     fingerprint, email, sig_digest);
+
+	  assert (c == 0);
+
+	  rc = sqlite3_exec_printf
+	    (db, NULL, NULL, &err,
+	     "insert into signatures\n"
+	     " (binding, sig_digest, origin, sig_time, time)\n"
+	     " values\n"
+	     " ((select oid from bindings\n"
+	     "    where fingerprint = %Q and email = %Q),\n"
+	     "  %Q, %Q, 0x%lx, strftime('%%s', 'now'));",
+	     fingerprint, email, sig_digest, origin, (unsigned long) sig_time);
+	  if (rc)
+	    {
+	      log_error ("TOFU: Error updating DB"
+			 " (inserting into signatures table): %s\n",
+			 err);
+	      sqlite3_free (err);
+	    }
+	}
+    }
+
+ die:
+  free_strlist (fingerprints);
+  xfree (email);
+  closedb (db);
+
+  return trust_level;
+}
diff --git a/g10/tofu.h b/g10/tofu.h
new file mode 100644
index 0000000..fac844f
--- /dev/null
+++ b/g10/tofu.h
@@ -0,0 +1,12 @@
+#ifndef G10_TOFU_H
+#define G10_TOFU_H
+
+#include <config.h>
+
+/* PRIMARY_KEY_FINGERPRINT should be MAX_FINGERPRINT_LEN bytes
+   long.  */
+int tofu_register (const char *name, const byte *primary_key_fingerprint,
+		   const byte *sigs_digest, int sigs_digest_len, time_t sig_time,
+		   const char *origin);
+
+#endif
diff --git a/g10/trustdb.c b/g10/trustdb.c
index b16682d..7d8b4c4 100644
--- a/g10/trustdb.c
+++ b/g10/trustdb.c
@@ -379,6 +379,8 @@ trust_model_string(void)
     case TM_CLASSIC:  return "classic";
     case TM_PGP:      return "PGP";
     case TM_EXTERNAL: return "external";
+    case TM_TOFU:     return "TOFU";
+    case TM_TOFU_PGP: return "TOFU+PGP";
     case TM_ALWAYS:   return "always";
     case TM_DIRECT:   return "direct";
     default:          return "unknown";
diff --git a/g10/trustdb.h b/g10/trustdb.h
index 771a821..fc9bdee 100644
--- a/g10/trustdb.h
+++ b/g10/trustdb.h
@@ -30,6 +30,7 @@
 #define TRUST_MARGINAL	  4  /* m: marginally trusted */
 #define TRUST_FULLY	  5  /* f: fully trusted      */
 #define TRUST_ULTIMATE	  6  /* u: ultimately trusted */
+#define TRUST_ASK	 31  /* This is only used for TOFU.  */
 /* trust values not covered by the mask */
 #define TRUST_FLAG_REVOKED 32 /* r: revoked */
 #define TRUST_FLAG_SUB_REVOKED 64 /* r: revoked but for subkeys */

-----------------------------------------------------------------------

Summary of changes:
 agent/cvt-openpgp.c | 110 +------
 g10/gpg.c           |  20 ++
 g10/options.h       |   1 +
 g10/tofu.c          | 882 ++++++++++++++++++++++++++++++++++------------------
 g10/trustdb.h       |   1 +
 scd/app-openpgp.c   |  18 +-
 6 files changed, 632 insertions(+), 400 deletions(-)


hooks/post-receive
-- 
The GNU Privacy Guard
http://git.gnupg.org




More information about the Gnupg-commits mailing list