[git] GnuPG - branch, neal/next, updated. gnupg-2.1.8-43-ge806e4d

by Neal H. Walfield cvs at cvs.gnupg.org
Tue Sep 22 23:39:42 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  d47cb081f1504ebb7581a8f84d1caf3cf961c968 (commit)
       via  e806e4dc85a7f5b3d7928853037280b84246c2d0 (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 (d47cb081f1504ebb7581a8f84d1caf3cf961c968)
            \
             N -- N -- N (e806e4dc85a7f5b3d7928853037280b84246c2d0)

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 e806e4dc85a7f5b3d7928853037280b84246c2d0
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..dda5c42 100644
--- a/g10/gpg.c
+++ b/g10/gpg.c
@@ -59,6 +59,7 @@
 #include "gc-opt-flags.h"
 #include "asshelp.h"
 #include "call-dirmngr.h"
+#include "tofu.h"
 #include "../common/init.h"
 #include "../common/shareddefs.h"
 
@@ -384,6 +385,7 @@ enum cmd_and_opt_values
     oFakedSystemTime,
     oNoAutostart,
     oPrintPKARecords,
+    oTOFUDefaultTrust,
 
     oNoop
   };
@@ -669,6 +671,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 +1940,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 +1953,22 @@ 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, "bad") == 0)
+    opt.tofu_default_trust = TOFU_BINDING_BAD;
+  else if (ascii_strcasecmp (default_trust, "ask") == 0)
+    opt.tofu_default_trust = TOFU_BINDING_ASK;
+  else if (ascii_strcasecmp (default_trust, "auto") == 0)
+    opt.tofu_default_trust = TOFU_BINDING_AUTO;
+  else if (ascii_strcasecmp (default_trust, "good") == 0)
+    opt.tofu_default_trust = TOFU_BINDING_GOOD;
+  else
+    log_error("unknown default trust '%s'"
+	      " (expected 'bad', 'ask', 'auto' or 'good')\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
@@ -2148,6 +2173,7 @@ main (int argc, char **argv)
 #else
     opt.trust_model = TM_AUTO;
 #endif
+    opt.tofu_default_trust = TOFU_BINDING_AUTO;
     opt.mangle_dos_filenames = 0;
     opt.min_cert_level = 2;
     set_screen_dimensions ();
@@ -2551,6 +2577,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..21d9843 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_UNKNOWN;
+  unsigned int trustlevel = TRUST_UNKNOWN;
   int rc=0;
 
   rc = get_pubkey( pk, sig->keyid );
@@ -537,6 +539,78 @@ check_signatures_trust( PKT_signature *sig )
     log_info(_("WARNING: this key might be revoked (revocation key"
 	       " not present)\n"));
 
+  /* XXX: This isn't exactly the right approach.  We do the trust
+     check here, but we repeat it in get_validity.  */
+  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 = 0;
+      int user_ids_expired = 0;
+
+      char fingerprint[MAX_FINGERPRINT_LEN];
+      size_t fingerprint_len = sizeof (fingerprint);
+
+      fingerprint_from_pk (pk, fingerprint, &fingerprint_len);
+      assert (fingerprint_len == sizeof (fingerprint));
+
+      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;
+
+	  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;
+	    }
+
+	  user_ids ++;
+
+	  tl = tofu_register (fingerprint, user_id->name,
+			      sig->digest, sig->digest_len,
+			      sig->timestamp, "unknown");
+	  if (tl == TRUST_EXPIRED)
+	    user_ids_expired ++;
+	  else if (tl == TRUST_UNDEFINED || tl == TRUST_UNKNOWN)
+	    ;
+	  else if (tl == TRUST_NEVER)
+	    tofu_trustlevel = TRUST_NEVER;
+	  else
+	    {
+	      assert (tl == TRUST_MARGINAL
+		      || tl == TRUST_FULLY
+		      || tl == TRUST_ULTIMATE);
+
+	      if (tl > tofu_trustlevel)
+		/* XXX: We we really want the max?  */
+		tofu_trustlevel = tl;
+	    }
+	}
+
+      if (user_ids_expired == user_ids)
+	/* Only if all of the user ids are expired do we decide that
+	   the key is expired.  */
+	tofu_trustlevel = TRUST_EXPIRED;
+
+      if (opt.trust_model == TM_TOFU)
+	goto reveal;
+    }
+
+  /* WoT.  */
+
   trustlevel = get_validity (pk, NULL);
 
   if ( (trustlevel & TRUST_FLAG_REVOKED) )
@@ -612,6 +686,11 @@ check_signatures_trust( PKT_signature *sig )
         }
     }
 
+ reveal:
+  /* If TOFU or WoT is not enabled, this still works: the defaults are
+     TRUST_UNDEFINED.  */
+  trustlevel = tofu_wot_trust_combine (tofu_trustlevel, 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..5b6c823
--- /dev/null
+++ b/g10/tofu.c
@@ -0,0 +1,1183 @@
+/* XXX:
+
+   - Add indices.
+
+   - Add support for multiple DBs?
+
+   - Add some test cases.
+
+   - Make sure encryption works.
+ */
+
+#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"
+
+const char *
+tofu_binding_judgment_str (enum tofu_binding_judgment judgment)
+{
+  switch (judgment)
+    {
+    case TOFU_BINDING_BAD: return "bad";
+    case TOFU_BINDING_ASK: return "ask";
+    case TOFU_BINDING_AUTO: return "automatic";
+    case TOFU_BINDING_GOOD: return "good";
+    default: return "unknown";
+    }
+}
+
+int
+tofu_trust_judgment_to_trust (enum tofu_binding_judgment judgment)
+{
+  if (judgment == TOFU_BINDING_AUTO)
+    /* If JUDGMENT is AUTO, fallback to OPT.TOFU_DEFAULT_TRUST.  */
+    judgment = opt.tofu_default_trust;
+
+  switch (judgment)
+    {
+    case TOFU_BINDING_BAD:
+      return TRUST_NEVER;
+    case TOFU_BINDING_ASK:
+      return TRUST_ASK;
+    case TOFU_BINDING_AUTO:
+      /* If OPT.TOFU_DEFAULT_TRUST is also AUTO, then trust fully.  */
+      return TRUST_FULLY;
+    case TOFU_BINDING_GOOD:
+      return TRUST_FULLY;
+    default:
+      log_bug ("Bad value for trust judgment: %d\n",
+	       opt.tofu_default_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)
+{
+  if (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;
+    }
+
+  rc = sqlite3_exec (db, "begin transaction;", NULL, NULL, &err);
+  if (rc)
+    {
+      log_error ("Error beginning transaction on TOFU database: %s\n", err);
+      sqlite3_free (err);
+      return 0;
+    }
+
+  /* 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);
+      goto out;
+    }
+
+  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);
+      goto out;
+    }
+
+  /* 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 TOFU_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));\n"
+     "create index bindings_fingerprint_email\n"
+     " on bindings (fingerprint, email);",
+     TOFU_BINDING_BAD, TOFU_BINDING_ASK, TOFU_BINDING_AUTO, TOFU_BINDING_GOOD);
+  if (rc)
+    {
+      log_error ("Error initialization TOFU database (bindings): %s\n",
+		 err);
+      sqlite3_free (err);
+      goto out;
+    }
+
+  /* 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);
+      goto out;
+    }
+
+ out:
+  if (rc)
+    {
+      rc = sqlite3_exec (db, "abort transaction;", NULL, NULL, &err);
+      if (rc)
+	{
+	  log_error ("%s: Error aborting transaction: %s\n", __func__, err);
+	  sqlite3_free (err);
+	}
+      return 0;
+    }
+  else
+    {
+      rc = sqlite3_exec (db, "commit transaction;", NULL, NULL, &err);
+      if (rc)
+	{
+	  log_error ("%s: Error commit transaction: %s\n", __func__, err);
+	  sqlite3_free (err);
+	  return 0;
+	}
+      return 1;
+    }
+}
+
+/* Opens and initializes the TOFU database.  Return's NULL on
+   failure.  */
+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) == 0)
+    {
+      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 == TOFU_BINDING_BAD
+	 || judgment == TOFU_BINDING_ASK
+	 || judgment == TOFU_BINDING_AUTO
+	 || judgment == TOFU_BINDING_GOOD))
+    log_bug ("TOFU: %s: Bad value for judgment (%d)!\n", __func__, judgment);
+
+  log_info ("TOFU: Set trust judgment for binding <%s, %s> to %s (%d).\n",
+	    fingerprint, email,
+	    tofu_binding_judgment_str (judgment), judgment);
+
+  rc = sqlite3_exec_printf
+    (db, NULL, NULL, &err,
+     "insert or replace into bindings\n"
+     " (oid, fingerprint, email, user_id, time, judgment)\n"
+     " values (\n"
+     /* If we don't explicitly reuse the OID, then SQLite will
+	reallocate a new one.  We just need to search for the OID
+	based on the fingerprint and email since they are unique.  */
+     "  (select oid from bindings where fingerprint = %Q and email = %Q),\n"
+     "  %Q, %Q, %Q, strftime('%%s','now'), %d);",
+     fingerprint, email, fingerprint, email, user_id, judgment);
+  if (rc)
+    {
+      log_error ("TOFU: Error updating binding database"
+		 " (inserting <%s, %s, %s>): %s\n",
+		 fingerprint, email, tofu_binding_judgment_str (judgment),
+		 err);
+      sqlite3_free (err);
+      return gpg_error (GPG_ERR_GENERAL);
+    }
+
+  return 0;
+}
+
+
+/* Collect results of a select count (*) ...; style query.  Aborts if
+   the argument is not a valid integer (or real of the form X.0).  */
+static int
+get_single_integer_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;
+}
+
+/* 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 and each row contains 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 statistics about
+   signatures.  */
+struct signature_stats
+{
+  struct signature_stats *next;
+
+  /* The user-assigned judgment for this binding.  */
+  enum tofu_binding_judgment judgment;
+
+  /* How long ago the signature was created (rounded to a multiple of
+     TIME_AGO_UNIT_SMALL, etc.).  */
+  long time_ago;
+  /* Number of signatures during this time.  */
+  unsigned long count;
+
+  /* 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 the 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 ("%s: Error converting %s to an integer (tail = `%s')\n",
+		 __func__, 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 ("%s: Error converting %s to an integer (tail = `%s')\n",
+		 __func__, 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 ("%s: Error converting %s to an integer (tail = `%s')\n",
+		 __func__, 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
+
+/* Convert from seconds to time units.
+
+   Note: T should already be a multiple of TIME_AGO_UNIT_SMALL or
+   TIME_AGO_UNIT_MEDIUM or TIME_AGO_UNIT_LARGE.  */
+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;
+}
+
+/* Return the appropriate unit (respecting whether it is plural or
+   singular).  */
+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;
+}
+
+
+#define GET_TRUST_ERROR -1
+#define JUDGMENT_NONE -2
+
+/* Return the trust judgment (TRUST_NEVER, etc.) for the binding
+   <fingerprint, email>.  If we don't have a judgment, returns
+   JUDGMENT_NONE.  If an error occurs, returns GET_TRUST_ERROR.  */
+static int
+get_trust (sqlite3 *db, const char *fingerprint, const char *email,
+	   const char *user_id)
+{
+  strlist_t fingerprints = NULL;
+  int judgment;
+  int rc;
+  char *err = NULL;
+  int trust_level = TRUST_UNKNOWN;
+  int count;
+
+  assert (GET_TRUST_ERROR != TRUST_UNKNOWN &&
+	  GET_TRUST_ERROR != TRUST_EXPIRED &&
+	  GET_TRUST_ERROR != TRUST_UNDEFINED &&
+	  GET_TRUST_ERROR != TRUST_NEVER &&
+	  GET_TRUST_ERROR != TRUST_MARGINAL &&
+	  GET_TRUST_ERROR != TRUST_FULLY &&
+	  GET_TRUST_ERROR != TRUST_ULTIMATE);
+
+  /* Check if the <FINGERPRINT, EMAIL> binding is known (JUDGMENT_NONE
+     cannot appear in the DB.  Thus, if JUDGMENT is still
+     JUDGMENT_NONE after executing the query, then the result set was
+     empty.)  */
+  judgment = JUDGMENT_NONE;
+  assert (judgment != TOFU_BINDING_BAD
+	  && judgment != TOFU_BINDING_ASK
+	  && judgment != TOFU_BINDING_AUTO
+	  && judgment != TOFU_BINDING_GOOD);
+  rc = sqlite3_exec_printf
+    (db, get_single_integer_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 for existing bad bindings): %s (%d)\n",
+		 err, rc);
+      sqlite3_free (err);
+      judgment = GET_TRUST_ERROR;
+      goto out;
+    }
+
+  switch (judgment)
+    {
+    case TOFU_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 = tofu_trust_judgment_to_trust (judgment);
+      goto out;
+
+    case TOFU_BINDING_GOOD:
+      /* The saved judgement is good.  We don't need to ask the user
+	 anything.  */
+      log_info ("TOFO: Good binding: <%s, %s>\n",
+		fingerprint, email);
+      trust_level = tofu_trust_judgment_to_trust (judgment);
+      goto out;
+
+    case TOFU_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 (default trust: %s) binding: <%s, %s>\n",
+		tofu_binding_judgment_str (opt.tofu_default_trust),
+		fingerprint, email);
+      if (opt.tofu_default_trust == TOFU_BINDING_ASK)
+	/* We need to ask the user what to do.  Case #1 below.  */
+	;
+      else
+	{
+	  assert (opt.tofu_default_trust == TOFU_BINDING_BAD
+		  || opt.tofu_default_trust == TOFU_BINDING_AUTO
+		  || opt.tofu_default_trust == TOFU_BINDING_GOOD);
+	  trust_level = tofu_trust_judgment_to_trust (judgment);
+	  goto out;
+	}
+
+      break;
+
+    case TOFU_BINDING_ASK:
+      /* We need to ask the user what to do.  Case #2 below.  */
+      break;
+
+    case JUDGMENT_NONE:
+      /* The binding is new, we need to check for conflicts.  Case #3
+	 below.  */
+      break;
+
+    default:
+      log_bug ("%s: Impossible value for judgment (%d)\n", __func__, judgment);
+    }
+
+
+  /* We get here if:
+
+       1. The saved judgment is auto and the default trust judgment is
+          ask (need to ask),
+
+       2. The saved trust judgement is undefined (last time, the user
+          selected accept once or reject once) (need to ask), or,
+
+       3. We don't yet have a saved trust judgment (judgment == JUDGMENT_NONE)
+          (need to check for a conflict).
+   */
+
+
+  /* Look for conflicts.  This is need in all 3 cases.
+
+     Get the fingerprints of any bindings that share the email
+     address.  Note: if the binding in question is in the DB, it will
+     also be returned.  Thus, if the result set is empty, then this is
+     a new binding.  */
+  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 != TOFU_BINDING_ASK)
+    /* New binding with no conflict and a concrete default judgment.
+
+       We've never observed a binding with this email address (COUNT
+       is 0 and the above query would return the current binding if it
+       were in the DB) and we have a default judgment, which is not to
+       ask the user.  */
+    {
+      /* If we've seen this binding, then we've seen this email and
+	 judgment couldn't possibly be JUDGMENT_NONE.  */
+      assert (judgment == JUDGMENT_NONE);
+
+      log_info ("TOFU: New binding <%s, %s>, no conflict.\n",
+		email, fingerprint);
+
+      if (record_binding (db, fingerprint, email, user_id,
+			  TOFU_BINDING_AUTO) != 0)
+	{
+	  log_error ("TOFU: Error setting binding's trust level to auto\n");
+	  trust_level = GET_TRUST_ERROR;
+	  goto out;
+	}
+
+      trust_level = tofu_trust_judgment_to_trust (TOFU_BINDING_AUTO);
+      goto out;
+    }
+
+#if 1
+  /* XXX: Is this behavior desirable?  */
+  if (judgment == JUDGMENT_NONE)
+    /* This is a new binding and we have a conflict.  Mark any
+       conflicting bindings that have an automatic judgment as now
+       requiring confirmation.  */
+    {
+      rc = sqlite3_exec_printf
+	(db, NULL, NULL, &err,
+	 "update bindings set judgment = %d"
+	 " where email = %Q and judgment = %d;",
+	 TOFU_BINDING_ASK, email, TOFU_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 == JUDGMENT_NONE && length(fingerprints) > 0),
+
+       - This is a new binding and opt.tofu_default_trust is set to
+         ask.  (judgment == JUDGMENT_NONE && opt.tofu_default_trust ==
+         TOFU_BINDING_ASK), or,
+
+       - The trust judgment is ask (the user deferred last time)
+         (judgment == TOFU_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 == JUDGMENT_NONE)
+      es_fprintf (fp, "The binding <%s, %s> is NOT known.  ",
+		  email, fingerprint);
+    es_fprintf (fp, "Please indicate whether you believe the binding ");
+    if (judgment != JUDGMENT_NONE)
+      es_fprintf (fp, "<%s, %s> ", email, fingerprint);
+    es_fprintf (fp, "is legitimate (good) or a forgery (bad).\n\n");
+
+
+    /* 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 where 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,
+			tofu_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:",
+			      tofu_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);
+	trim_spaces(response);
+	cpr_kill_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.  */
+		    judgment = TOFU_BINDING_GOOD;
+		    trust_level = tofu_trust_judgment_to_trust (judgment);
+		    if (record_binding (db, fingerprint, email, user_id,
+					judgment) != 0)
+		      /* If there's an error registering the
+			 binding, don't save the signature.  */
+		      trust_level = GET_TRUST_ERROR;
+		    break;
+		  case 1: /* Accept once.  */
+		    judgment = TOFU_BINDING_ASK;
+		    trust_level =
+		      tofu_trust_judgment_to_trust (TOFU_BINDING_GOOD);
+		    if (record_binding (db, fingerprint, email, user_id,
+					judgment) != 0)
+		      trust_level = GET_TRUST_ERROR;
+		    break;
+		  case 2: /* Reject once.  */
+		    judgment = TOFU_BINDING_ASK;
+		    trust_level =
+		      tofu_trust_judgment_to_trust (TOFU_BINDING_BAD);
+		    if (record_binding (db, fingerprint, email, user_id,
+					judgment) != 0)
+		      trust_level = GET_TRUST_ERROR;
+		    break;
+		  case 3: /* Bad.  */
+		    judgment = TOFU_BINDING_BAD;
+		    trust_level = tofu_trust_judgment_to_trust (judgment);
+		    if (record_binding (db, fingerprint, email, user_id,
+					judgment) != 0)
+		      trust_level = GET_TRUST_ERROR;
+		    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:
+  free_strlist (fingerprints);
+
+  return trust_level;
+}
+
+
+int
+tofu_register (const byte *fingerprint_bin, const char *user_id,
+	       const byte *sig_digest_bin, int sig_digest_bin_len,
+	       time_t sig_time, const char *origin)
+{
+  sqlite3 *db;
+  char fingerprint[MAX_FINGERPRINT_LEN * 2 + 1];
+  char *email = NULL;
+  char *err = NULL;
+  int rc;
+  int trust_level = TRUST_UNKNOWN;
+  char *sig_digest;
+  unsigned long c;
+
+  db = opendb ();
+  if (! db)
+    {
+      log_info ("TOFU: Error opening DB.\n");
+      goto die;
+    }
+
+  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";
+
+  trust_level = get_trust (db, fingerprint, email, user_id);
+  if (trust_level == GET_TRUST_ERROR)
+    /* An error.  */
+    {
+      trust_level = TRUST_UNKNOWN;
+      goto die;
+    }
+
+  /* Save the observed signature in the DB.  */
+  sig_digest = make_radix64_string (sig_digest_bin, sig_digest_bin_len);
+
+  /* We do a query and then an insert.  Make sure they are atomic
+     by wrapping them in a transaction.  */
+  rc = sqlite3_exec (db, "begin transaction;", NULL, NULL, &err);
+  if (rc)
+    {
+      log_error ("Error beginning transaction on TOFU database: %s\n", err);
+      sqlite3_free (err);
+      goto die;
+    }
+
+  /* If we've already seen this signature before, then don't add
+     it again.  */
+  rc = sqlite3_exec_printf
+    (db, get_single_integer_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);
+  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);
+	}
+    }
+
+  /* It only matters whether we abort or commit the transaction
+     (so long as we do something) if we execute the insert.  */
+  if (rc)
+    rc = sqlite3_exec (db, "abort transaction;", NULL, NULL, &err);
+  else
+    rc = sqlite3_exec (db, "commit transaction;", NULL, NULL, &err);
+  if (rc)
+    {
+      log_error ("Error ending transaction on TOFU database: %s\n", err);
+      sqlite3_free (err);
+      goto die;
+    }
+
+ die:
+  xfree (email);
+  closedb (db);
+
+  return trust_level;
+}
+
+int
+tofu_wot_trust_combine (int tofu, int wot)
+{
+  assert (tofu == TRUST_UNKNOWN
+	  || tofu == TRUST_EXPIRED
+	  || tofu == TRUST_UNDEFINED
+	  || tofu == TRUST_NEVER
+	  || tofu == TRUST_MARGINAL
+	  || tofu == TRUST_FULLY
+	  || tofu == TRUST_ULTIMATE);
+  assert (wot == TRUST_UNKNOWN
+	  || wot == TRUST_EXPIRED
+	  || wot == TRUST_UNDEFINED
+	  || wot == TRUST_NEVER
+	  || wot == TRUST_MARGINAL
+	  || wot == TRUST_FULLY
+	  || wot == TRUST_ULTIMATE);
+
+  /* We first consider negative trust judgments.  These trump positive
+     trust judgments.  */
+  if (tofu == TRUST_NEVER || wot == TRUST_NEVER)
+    /* TRUST_NEVER trumps everything else.  */
+    return TRUST_NEVER;
+  if (tofu == TRUST_EXPIRED || wot == TRUST_EXPIRED)
+    /* TRUST_EXPIRED trumps everything but TRUST_NEVER.  */
+    return TRUST_EXPIRED;
+
+  /* Now we only have positive or neutral trust judgments.  We take
+     the max.  */
+  if (tofu == TRUST_ULTIMATE || wot == TRUST_ULTIMATE)
+    return TRUST_ULTIMATE;
+  if (tofu == TRUST_FULLY || wot == TRUST_FULLY)
+    return TRUST_FULLY;
+  if (tofu == TRUST_MARGINAL || wot == TRUST_MARGINAL)
+    return TRUST_MARGINAL;
+  if (tofu == TRUST_UNDEFINED || wot == TRUST_UNDEFINED)
+    return TRUST_UNDEFINED;
+  return TRUST_UNKNOWN;
+}
+
+int
+tofu_get_validity (const byte *fingerprint_bin, const char *user_id)
+{
+  sqlite3 *db;
+  char fingerprint[MAX_FINGERPRINT_LEN * 2 + 1];
+  char *email = NULL;
+  int trust_level = TRUST_UNDEFINED;
+
+  db = opendb ();
+  if (! db)
+    {
+      log_info ("TOFU: Error opening DB.\n");
+      goto die;
+    }
+
+  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));
+
+  trust_level = get_trust (db, fingerprint, email, user_id);
+  if (trust_level == GET_TRUST_ERROR)
+    /* An error.  */
+    trust_level = TRUST_UNKNOWN;
+
+ die:
+  xfree (email);
+  closedb (db);
+
+  return trust_level;
+}
diff --git a/g10/tofu.h b/g10/tofu.h
new file mode 100644
index 0000000..eff9e26
--- /dev/null
+++ b/g10/tofu.h
@@ -0,0 +1,57 @@
+#ifndef G10_TOFU_H
+#define G10_TOFU_H
+
+#include <config.h>
+
+/* For each binding, we have a trust judgment.  There are four
+   possible trust judgments.  */
+enum tofu_binding_judgment
+  {
+    /* The user explicitly marked the binding as bad.  In this case,
+       we always return TRUST_NEVER.  */
+    TOFU_BINDING_BAD = -1,
+
+    /* The user deferred a definitive judgment about the binding
+       (accept once / reject once) and the next time we see this
+       binding, we should ask the user what to do.  */
+    TOFU_BINDING_ASK = 0,
+
+    /* We made a default judgment.  This is only done if there is no
+       conflict with another binding (that is, the email address is
+       not part of another known key).  The default judgment is
+       configurable (and specified using: --tofu-default-trust).
+
+       Note: when we make a default judgment, we still save
+       BINDING_AUTO with the binding, not the default judgment.  This
+       way, if the user invokes gpg again, but with a different value
+       for --tofu-default-trust, a different decision is made.  */
+    TOFU_BINDING_AUTO = 1,
+
+    /* The user explicitly marked the binding as good.  In this case,
+       we return TRUST_FULLY.  */
+    TOFU_BINDING_GOOD = 2
+  };
+
+/* Return a string representation of a trust judgment.  Returns
+   "unknown" if JUDGMENT is not valid.  */
+const char *tofu_binding_judgment_str (enum tofu_binding_judgment judgment);
+
+/* Convert a trust judgment (e.g., TOFU_BINDING_BAD) to a trust value
+   (e.g., TRUST_BAD) in light of the current configuration.  */
+int tofu_trust_judgment_to_trust (enum tofu_binding_judgment judgment);
+
+
+/* PRIMARY_KEY_FINGERPRINT should be MAX_FINGERPRINT_LEN bytes
+   long.  */
+int tofu_register (const byte *primary_key_fingerprint, const char *name,
+		   const byte *sigs_digest, int sigs_digest_len,
+		   time_t sig_time, const char *origin);
+
+/* Determine the validity (TRUST_NEVER, etc.) of a given binding.  */
+int tofu_get_validity (const byte *fingerprint_bin, const char *user_id);
+
+/* When in PGP+WOT mode, we need to combine TOFU and WOT trust
+   judgments.  */
+int tofu_wot_trust_combine (int tofu, int wot);
+
+#endif
diff --git a/g10/trustdb.c b/g10/trustdb.c
index b16682d..dc50486 100644
--- a/g10/trustdb.c
+++ b/g10/trustdb.c
@@ -40,6 +40,7 @@
 #include "i18n.h"
 #include "tdbio.h"
 #include "trustdb.h"
+#include "tofu.h"
 
 
 typedef struct key_item **KeyHashTable; /* see new_key_hash_table() */
@@ -379,6 +380,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";
@@ -972,7 +975,8 @@ tdb_get_validity_core (PKT_public_key *pk, PKT_user_id *uid,
   TRUSTREC trec, vrec;
   gpg_error_t err;
   ulong recno;
-  unsigned int validity;
+  unsigned int tofu_validity = TRUST_UNKNOWN;
+  unsigned int validity = TRUST_UNKNOWN;
 
   init_trustdb ();
 
@@ -993,6 +997,78 @@ tdb_get_validity_core (PKT_public_key *pk, PKT_user_id *uid,
       goto leave;
     }
 
+  if (opt.trust_model == TM_TOFU || opt.trust_model == TM_TOFU_PGP)
+    {
+      kbnode_t user_id_node;
+      int user_ids = 0;
+      int user_ids_expired = 0;
+
+      char fingerprint[MAX_FINGERPRINT_LEN];
+      size_t fingerprint_len = sizeof (fingerprint);
+
+      fingerprint_from_pk (pk, fingerprint, &fingerprint_len);
+      assert (fingerprint_len == sizeof (fingerprint));
+
+      if (! uid)
+	user_id_node = get_pubkeyblock (pk->main_keyid);
+
+      while (uid
+	     || (user_id_node = find_next_kbnode (user_id_node, PKT_USER_ID)))
+	{
+	  unsigned int tl;
+	  PKT_user_id *user_id;
+
+	  if (uid)
+	    user_id = uid;
+	  else
+	    user_id = user_id_node->pkt->pkt.user_id;
+
+	  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;
+	    }
+
+	  user_ids ++;
+
+	  tl = tofu_get_validity (fingerprint, user_id->name);
+	  if (tl == TRUST_EXPIRED)
+	    user_ids_expired ++;
+	  else if (tl == TRUST_UNDEFINED || tl == TRUST_UNKNOWN)
+	    ;
+	  else if (tl == TRUST_NEVER)
+	    tofu_validity = TRUST_NEVER;
+	  else
+	    {
+	      assert (tl == TRUST_MARGINAL
+		      || tl == TRUST_FULLY
+		      || tl == TRUST_ULTIMATE);
+
+	      if (tl > tofu_validity)
+		/* XXX: We we really want the max?  */
+		tofu_validity = tl;
+	    }
+
+	  if (uid)
+	    /* If the caller specified a user id, then we stop
+	       now.  */
+	    break;
+	}
+
+      if (opt.trust_model == TM_TOFU)
+	goto leave;
+    }
+
   err = read_trust_record (main_pk, &trec);
   if (err && gpg_err_code (err) != GPG_ERR_NOT_FOUND)
     {
@@ -1046,7 +1122,10 @@ tdb_get_validity_core (PKT_public_key *pk, PKT_user_id *uid,
   pk->flags.disabled_valid = 1;
 
  leave:
-  if (pending_check_trustdb)
+  validity = tofu_wot_trust_combine (tofu_validity, validity);
+
+  if (opt.trust_model != TM_TOFU
+      && pending_check_trustdb)
     validity |= TRUST_FLAG_PENDING_CHECK;
 
   return validity;
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:
 g10/pkclist.c |  65 ++++----
 g10/tofu.c    | 472 ++++++++++++++++++++++++++++++++++++----------------------
 g10/tofu.h    |  13 +-
 g10/trustdb.c |  81 +++++++++-
 4 files changed, 417 insertions(+), 214 deletions(-)


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




More information about the Gnupg-commits mailing list