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

by Neal H. Walfield cvs at cvs.gnupg.org
Tue Sep 22 20:17:54 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  7fc7f29a53f1e0d46b1fdc8d7c264f1cbdfa0935 (commit)
       via  d47cb081f1504ebb7581a8f84d1caf3cf961c968 (commit)
       via  12ff806d1b63d08cb43d131065d51353495d9346 (commit)
       via  2167951b275bae51cf669c02547e2e7ea8fbe2ee (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 (7fc7f29a53f1e0d46b1fdc8d7c264f1cbdfa0935)
            \
             N -- N -- N (d47cb081f1504ebb7581a8f84d1caf3cf961c968)

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 d47cb081f1504ebb7581a8f84d1caf3cf961c968
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..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..be4ecd1
--- /dev/null
+++ b/g10/tofu.c
@@ -0,0 +1,1059 @@
+/* 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)
+{
+  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));",
+     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 1;
+    }
+
+  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: Marking binding <%s, %s> as %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"
+     " (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, tofu_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 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;
+}
+
+
+/* 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
+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 (! db)
+    {
+      log_info ("TOFU: Error opening DB.\n");
+      return TRUST_UNDEFINED;
+    }
+
+  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 != TOFU_BINDING_BAD
+	  && judgment != TOFU_BINDING_ASK
+	  && judgment != TOFU_BINDING_AUTO
+	  && judgment != TOFU_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 for existing bad bindings): %s (%d)\n",
+		 err, rc);
+      sqlite3_free (err);
+      goto die;
+    }
+
+  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 -2:
+      /* 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 == -2)
+          (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)
+    /* 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.
+
+       In other words, we have a new binding, no conflict and a
+       concrete default judgment.  */
+    {
+      /* If we've seen this binding, then we've seen this email and
+	 judgment couldn't possibly be -2.  */
+      assert (judgment == -2);
+
+      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");
+	  goto die;
+	}
+
+      trust_level = tofu_trust_judgment_to_trust (TOFU_BINDING_AUTO);
+      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 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 == -2 && length(fingerprints) > 0),
+
+       - This is a new binding and opt.tofu_default_trust is set to
+         ask.  (judgment == -2 && 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 == -2)
+      es_fprintf (fp, "The binding <%s, %s> is NOT known.  ",
+		  email, fingerprint);
+    es_fprintf (fp, "Please indicate whether you believe the binding ");
+    if (judgment != -2)
+      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);
+	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);
+		    record_binding (db, fingerprint, email, user_id,
+				    judgment);
+		    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)
+		      /* If there's an error registering the
+			 binding, don't save the signature.  */
+		      save_sig = 0;
+		    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)
+		      /* See the comment for accept once.  */
+		      save_sig = 0;
+		    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)
+		      /* See the comment for accept once.  */
+		      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;
+
+      /* 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, 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);
+      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:
+  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..79c3165
--- /dev/null
+++ b/g10/tofu.h
@@ -0,0 +1,50 @@
+#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 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:
 common/ssh-utils.c   |   2 +-
 common/t-ssh-utils.c |  62 ++++++++
 g10/gpg.c            |  23 +--
 g10/tofu.c           | 417 +++++++++++++++++++++++++++++----------------------
 g10/tofu.h           |  38 +++++
 5 files changed, 355 insertions(+), 187 deletions(-)


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




More information about the Gnupg-commits mailing list