[git] GnuPG - branch, neal/next, updated. gnupg-2.1.8-58-ge6c065b

by Neal H. Walfield cvs at cvs.gnupg.org
Thu Oct 1 22:39:32 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  f009c9b4cb0c0a459351b793de9db15296cfa1a6 (commit)
       via  e6c065b058a612cbcb857b7223a1bc5bb7df7cf7 (commit)
       via  2acceba5cc299796c7b5b1851a9baeb75d9f32a1 (commit)
       via  2c60663a72f090573c4869e305b098b4b1fb23bd (commit)
       via  13a3f65968f4a8205ca664cc46b1a53de4dc489b (commit)
       via  a9895a5a72a851c5fcc70f16d5f8f588cc885751 (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 (f009c9b4cb0c0a459351b793de9db15296cfa1a6)
            \
             N -- N -- N (e6c065b058a612cbcb857b7223a1bc5bb7df7cf7)

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 e6c065b058a612cbcb857b7223a1bc5bb7df7cf7
Author: Neal H. Walfield <neal at g10code.com>
Date:   Sat Sep 19 01:32:51 2015 +0200

    TOFU stuff.

diff --git a/common/mkdir_p.c b/common/mkdir_p.c
index 6a54600..d3c9d01 100644
--- a/common/mkdir_p.c
+++ b/common/mkdir_p.c
@@ -31,10 +31,10 @@
 #define DEBUG 0
 
 int
-amkdir_p (char **directory_components)
+amkdir_p (const char **directory_components)
 {
   int count;
-  char **dirs;
+  const char **dirs;
   int i;
   int rc;
 
@@ -131,12 +131,12 @@ amkdir_p (char **directory_components)
 }
 
 int
-mkdir_p (char *directory_component, ...)
+mkdir_p (const char *directory_component, ...)
 {
   va_list ap;
   int i;
   int space = 1;
-  char **dirs = xmalloc (space * sizeof (char *));
+  const char **dirs = xmalloc (space * sizeof (char *));
   int rc;
 
   dirs[0] = directory_component;
diff --git a/common/mkdir_p.h b/common/mkdir_p.h
index ddf412b..fd3c106 100644
--- a/common/mkdir_p.h
+++ b/common/mkdir_p.h
@@ -29,7 +29,7 @@
    first try to create the directory "foo/bar" and then the directory
    "foo/bar/xyzzy".  On success returns 0, otherwise an error code is
    returned.  */
-int mkdir_p (char *directory_component, ...) GPGRT_ATTR_SENTINEL(0);
+int mkdir_p (const char *directory_component, ...) GPGRT_ATTR_SENTINEL(0);
 
 /* Like mkdir_p, but DIRECTORY_COMPONENTS is a NULL terminated
    array, e.g.:
@@ -37,6 +37,6 @@ int mkdir_p (char *directory_component, ...) GPGRT_ATTR_SENTINEL(0);
      char **dirs = { "foo", "bar", NULL };
      amkdir_p (dirs);
  */
-int amkdir_p (char **directory_components);
+int amkdir_p (const char **directory_components);
 
 #endif
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/doc/DETAILS b/doc/DETAILS
index 811b105..97079b0 100644
--- a/doc/DETAILS
+++ b/doc/DETAILS
@@ -206,6 +206,10 @@ described here.
 
     For pub, sub, sec, and ssb records this field is used for the ECC
     curve name.
+*** Field 18 - TOFU Policy
+
+    This is the TOFU policy.  It is either good, bad, unknown, ask or
+    auto.  This is only shows for uid records.
 
 ** Special fields
 
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..761e2ba 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"
 
@@ -162,6 +163,7 @@ enum cmd_and_opt_values
     aChangePIN,
     aPasswd,
     aServer,
+    aTOFUPolicy,
 
     oTextmode,
     oNoTextmode,
@@ -384,6 +386,7 @@ enum cmd_and_opt_values
     oFakedSystemTime,
     oNoAutostart,
     oPrintPKARecords,
+    oTOFUDefaultPolicy,
 
     oNoop
   };
@@ -474,6 +477,8 @@ static ARGPARSE_OPTS opts[] = {
   ARGPARSE_c (aPrimegen, "gen-prime", "@" ),
   ARGPARSE_c (aGenRandom,"gen-random", "@" ),
   ARGPARSE_c (aServer,   "server",  N_("run in server mode")),
+  ARGPARSE_c (aTOFUPolicy, "tofu-policy",
+	      N_("|VALUE|set the TOFU policy for a key (good, unknown, bad, ask, auto)")),
 
   ARGPARSE_group (301, N_("@\nOptions:\n ")),
 
@@ -669,6 +674,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 (oTOFUDefaultPolicy, "tofu-default-policy", "@"),
   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 +1943,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 +1956,26 @@ parse_trust_model(const char *model)
 }
 #endif /*NO_TRUST_MODELS*/
 
+static int
+parse_tofu_policy (const char *policy)
+{
+  if (ascii_strcasecmp (policy, "auto") == 0)
+    return TOFU_BINDING_AUTO;
+  else if (ascii_strcasecmp (policy, "good") == 0)
+    return TOFU_BINDING_GOOD;
+  else if (ascii_strcasecmp (policy, "unknown") == 0)
+    return TOFU_BINDING_UNKNOWN;
+  else if (ascii_strcasecmp (policy, "bad") == 0)
+    return TOFU_BINDING_BAD;
+  else if (ascii_strcasecmp (policy, "ask") == 0)
+    return TOFU_BINDING_ASK;
+  else
+    log_fatal("unknown TOFU policy '%s'"
+	      " (expected 'bad', 'ask', 'auto' or 'good')\n",
+	      policy);
+
+  BUG();
+}
 
 /* This fucntion called to initialized a new control object.  It is
    assumed that this object has been zeroed out before calling this
@@ -2148,6 +2180,7 @@ main (int argc, char **argv)
 #else
     opt.trust_model = TM_AUTO;
 #endif
+    opt.tofu_default_policy = TOFU_BINDING_AUTO;
     opt.mangle_dos_filenames = 0;
     opt.min_cert_level = 2;
     set_screen_dimensions ();
@@ -2370,6 +2403,10 @@ main (int argc, char **argv)
             opt.batch = 1;
             break;
 
+          case aTOFUPolicy:
+            set_cmd (&cmd, pargs.r_opt);
+            break;
+
 	  case oArmor: opt.armor = 1; opt.no_armor=0; break;
 	  case oOutput: opt.outfile = pargs.r.ret_str; break;
 	  case oMaxOutput: opt.max_output = pargs.r.ret_ulong; break;
@@ -2551,6 +2588,9 @@ main (int argc, char **argv)
 	    parse_trust_model(pargs.r.ret_str);
 	    break;
 #endif /*!NO_TRUST_MODELS*/
+	  case oTOFUDefaultPolicy:
+	    opt.tofu_default_policy = parse_tofu_policy (pargs.r.ret_str);
+	    break;
 
 	  case oForceOwnertrust:
 	    log_info(_("Note: %s is not for normal use!\n"),
@@ -4348,6 +4388,66 @@ main (int argc, char **argv)
         gcry_control (GCRYCTL_PRINT_CONFIG, stdout);
         break;
 
+      case aTOFUPolicy:
+	{
+	  int policy;
+	  int i;
+	  KEYDB_HANDLE hd;
+
+	  if (argc < 2)
+	    wrong_args("--tofu-policy POLICY KEYID [KEYID...]");
+
+	  policy = parse_tofu_policy (argv[0]);
+
+	  hd = keydb_new ();
+	  if (! hd)
+	    log_fatal (_("Failed to open the keyring DB.\n"));
+
+	  for (i = 1; i < argc; i ++)
+	    {
+	      KEYDB_SEARCH_DESC desc;
+	      kbnode_t kb;
+
+	      rc = classify_user_id (argv[i], &desc, 0);
+	      if (rc)
+		log_fatal (_("Failed to parse `%s'.\n"), argv[i]);
+
+	      if (! (desc.mode == KEYDB_SEARCH_MODE_SHORT_KID
+		     || desc.mode == KEYDB_SEARCH_MODE_LONG_KID
+		     || desc.mode == KEYDB_SEARCH_MODE_FPR16
+		     || desc.mode == KEYDB_SEARCH_MODE_FPR20
+		     || desc.mode == KEYDB_SEARCH_MODE_FPR
+		     || desc.mode == KEYDB_SEARCH_MODE_KEYGRIP))
+		log_fatal (_("`%s' does not appear to be a valid"
+			     " key id, fingerprint or key grip.\n"),
+			   argv[i]);
+
+	      rc = keydb_search_reset (hd);
+	      if (rc)
+		log_fatal (_("Failed to reset keyring handle.\n"));
+
+	      rc = keydb_search (hd, &desc, 1, NULL);
+	      if (gpg_err_code (rc) == GPG_ERR_NO_PUBKEY)
+		log_fatal (_("Key `%s' is not available\n"), argv[i]);
+	      else if (rc)
+		log_fatal (_("Failed to find key `%s'\n"), argv[i]);
+
+	      rc = keydb_get_keyblock (hd, &kb);
+	      if (rc)
+		log_fatal (_("Failed to read key `%s' from the keyring\n"),
+			   argv[i]);
+
+	      merge_keys_and_selfsig (kb);
+
+	      if (tofu_set_policy (kb, policy))
+		g10_exit (1);
+	    }
+
+	  keydb_release (hd);
+
+	}
+	break;
+
       case aListPackets:
 	opt.list_packets=2;
       default:
diff --git a/g10/gpgv.c b/g10/gpgv.c
index 412f4be..47ddfd3 100644
--- a/g10/gpgv.c
+++ b/g10/gpgv.c
@@ -285,10 +285,13 @@ get_validity_info (PKT_public_key *pk, PKT_user_id *uid)
 }
 
 unsigned int
-get_validity (PKT_public_key *pk, PKT_user_id *uid)
+get_validity (PKT_public_key *pk, PKT_user_id *uid, PKT_signature *sig,
+	      int may_ask)
 {
   (void)pk;
   (void)uid;
+  (void)sig;
+  (void)may_ask;
   return 0;
 }
 
@@ -592,3 +595,26 @@ gpg_dirmngr_get_pka (ctrl_t ctrl, const char *userid,
     *r_url = NULL;
   return gpg_error (GPG_ERR_NOT_FOUND);
 }
+
+enum tofu_binding_policy
+  {
+    tofu_binding_policy
+  };
+
+gpg_error_t
+tofu_get_policy (PKT_public_key *pk, PKT_user_id *user_id,
+		 enum tofu_binding_policy *policy)
+{
+  (void)pk;
+  (void)user_id;
+  (void)policy;
+  return gpg_error (GPG_ERR_GENERAL);
+}
+
+const char *
+tofu_binding_policy_str (enum tofu_binding_policy policy)
+{
+  (void)policy;
+
+  return "unknown";
+}
diff --git a/g10/keyedit.c b/g10/keyedit.c
index 143c748..7a5650a 100644
--- a/g10/keyedit.c
+++ b/g10/keyedit.c
@@ -47,6 +47,7 @@
 #include "keyserver-internal.h"
 #include "call-agent.h"
 #include "host2net.h"
+#include "tofu.h"
 
 static void show_prefs (PKT_user_id * uid, PKT_signature * selfsig,
 			int verbose);
@@ -2928,6 +2929,14 @@ show_key_with_all_names_colon (ctrl_t ctrl, estream_t fp, kbnode_t keyblock)
 	  if ((node->flag & NODFLG_MARK_A))
 	    es_putc ('m', fp);
 	  es_putc (':', fp);
+	  if (opt.trust_model == TM_TOFU || opt.trust_model == TM_TOFU_PGP)
+	    {
+	      enum tofu_binding_policy policy;
+	      if (! tofu_get_policy (primary, uid, &policy)
+		  && policy != TOFU_BINDING_NONE)
+		es_fprintf (fp, "%s", tofu_binding_policy_str (policy));
+	    }
+	  es_putc (':', fp);
 	  es_putc ('\n', fp);
 	}
     }
@@ -3043,7 +3052,8 @@ show_key_with_all_names (ctrl_t ctrl, estream_t fp,
 
 	      /* Show a warning once */
 	      if (!did_warn
-		  && (get_validity (pk, NULL) & TRUST_FLAG_PENDING_CHECK))
+		  && (get_validity (pk, NULL, NULL, 0)
+		      & TRUST_FLAG_PENDING_CHECK))
 		{
 		  did_warn = 1;
 		  do_warn = 1;
@@ -5335,7 +5345,7 @@ menu_revuid (KBNODE pub_keyblock)
 		/* If the trustdb has an entry for this key+uid then the
 		   trustdb needs an update. */
 		if (!update_trust
-		    && (get_validity (pk, uid) & TRUST_MASK) >=
+		    && (get_validity (pk, uid, NULL, 0) & TRUST_MASK) >=
 		    TRUST_UNDEFINED)
 		  update_trust = 1;
 #endif /*!NO_TRUST_MODELS*/
diff --git a/g10/keylist.c b/g10/keylist.c
index bfc6d49..84f0fe0 100644
--- a/g10/keylist.c
+++ b/g10/keylist.c
@@ -43,6 +43,7 @@
 #include "status.h"
 #include "call-agent.h"
 #include "mbox-util.h"
+#include "tofu.h"
 
 
 static void list_all (int, int);
@@ -97,7 +98,8 @@ public_key_list (ctrl_t ctrl, strlist_t list, int locate_mode)
 	es_fprintf (es_stdout, "o");
       if (trust_model != opt.trust_model)
 	es_fprintf (es_stdout, "t");
-      if (opt.trust_model == TM_PGP || opt.trust_model == TM_CLASSIC)
+      if (opt.trust_model == TM_PGP || opt.trust_model == TM_CLASSIC
+	  || opt.trust_model == TM_TOFU_PGP)
 	{
 	  if (marginals != opt.marginals_needed)
 	    es_fprintf (es_stdout, "m");
@@ -986,7 +988,7 @@ list_keyblock_print (KBNODE keyblock, int secret, int fpr,
      include, but it looks sort of confusing in the listing... */
   if (opt.list_options & LIST_SHOW_VALIDITY)
     {
-      int validity = get_validity (pk, NULL);
+      int validity = get_validity (pk, NULL, NULL, 0);
       es_fprintf (es_stdout, " [%s]", trust_value_to_string (validity));
     }
 #endif
@@ -1357,6 +1359,7 @@ list_keyblock_colon (KBNODE keyblock, int secret, int has_secret, int fpr)
       xfree (curve);
     }
   es_putc (':', es_stdout);		/* End of field 17. */
+  es_putc (':', es_stdout);		/* End of field 18. */
   es_putc ('\n', es_stdout);
 
   print_revokers (es_stdout, pk);
@@ -1414,6 +1417,15 @@ list_keyblock_colon (KBNODE keyblock, int secret, int has_secret, int fpr)
 	    es_fprintf (es_stdout, "%u %lu", uid->numattribs, uid->attrib_len);
 	  else
 	    es_write_sanitized (es_stdout, uid->name, uid->len, ":", NULL);
+	  es_fprintf (es_stdout, "::::::::");
+	  if (opt.trust_model == TM_TOFU || opt.trust_model == TM_TOFU_PGP)
+	    {
+	      enum tofu_binding_policy policy;
+	      if (! tofu_get_policy (pk, uid, &policy)
+		  && policy != TOFU_BINDING_NONE)
+		es_fprintf (es_stdout, "%s",
+			    tofu_binding_policy_str (policy));
+	    }
 	  es_putc (':', es_stdout);
 	  es_putc ('\n', es_stdout);
 	}
diff --git a/g10/mainproc.c b/g10/mainproc.c
index f7b7c6b..3ff757b 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);
@@ -1832,9 +1846,9 @@ check_sig_and_print (CTX c, kbnode_t node)
 
           assert (pk);
 
-          /* Get it before we print anything to avoid interrupting the
-             output with the "please do a --check-trustdb" line. */
-          valid = get_validity (pk, un->pkt->pkt.user_id);
+	  /* Since this is just informational, don't actually ask the
+	     user to update any trust information.  */
+          valid = get_validity (pk, un->pkt->pkt.user_id, sig, 0);
 
           keyid_str[17] = 0; /* cut off the "[uncertain]" part */
 
@@ -1923,8 +1937,11 @@ check_sig_and_print (CTX c, kbnode_t node)
                   else if (un->pkt->pkt.user_id->is_expired)
                     valid = _("expired");
                   else
+		    /* Since this is just informational, don't
+		       actually ask the user to update any trust
+		       information.  */
                     valid = (trust_value_to_string
-                             (get_validity (pk, un->pkt->pkt.user_id)));
+                             (get_validity (pk, un->pkt->pkt.user_id, sig, 0)));
                   log_printf (" [%s]\n",valid);
                 }
               else
diff --git a/g10/options.h b/g10/options.h
index fd2f4a2..51979c5 100644
--- a/g10/options.h
+++ b/g10/options.h
@@ -117,8 +117,16 @@ 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;
+  enum
+    {
+      TOFU_DB_SPLIT=0, TOFU_DB_FLAT
+    } tofu_db_format;
+  /* TOFU_BINDING_BAD, TOFU_BINDING_ASK, TOFU_BINDING_AUTO, or
+     TOFU_BINDING_GOOD.  */
+  int tofu_default_policy;
   int force_ownertrust;
   enum
     {
diff --git a/g10/packet.h b/g10/packet.h
index eb7da75..2c1b478 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..06ba86e 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,13 @@ 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 trustlevel = TRUST_UNKNOWN;
   int rc=0;
 
   rc = get_pubkey( pk, sig->keyid );
@@ -537,7 +538,7 @@ check_signatures_trust( PKT_signature *sig )
     log_info(_("WARNING: this key might be revoked (revocation key"
 	       " not present)\n"));
 
-  trustlevel = get_validity (pk, NULL);
+  trustlevel = get_validity (pk, NULL, sig, 1);
 
   if ( (trustlevel & TRUST_FLAG_REVOKED) )
     {
@@ -829,7 +830,7 @@ find_and_check_key (ctrl_t ctrl, const char *name, unsigned int use,
     }
 
   /* Key found and usable.  Check validity. */
-  trustlevel = get_validity (pk, pk->user_id);
+  trustlevel = get_validity (pk, pk->user_id, NULL, 1);
   if ( (trustlevel & TRUST_FLAG_DISABLED) )
     {
       /* Key has been disabled. */
@@ -1114,7 +1115,7 @@ build_pk_list (ctrl_t ctrl,
                 { /* Check validity of this key. */
                   int trustlevel;
 
-                  trustlevel = get_validity (pk, pk->user_id);
+                  trustlevel = get_validity (pk, pk->user_id, NULL, 1);
                   if ( (trustlevel & TRUST_FLAG_DISABLED) )
                     {
                       tty_printf (_("Public key is disabled.\n") );
diff --git a/g10/test-stubs.c b/g10/test-stubs.c
index c6f6d68..4829aea 100644
--- a/g10/test-stubs.c
+++ b/g10/test-stubs.c
@@ -104,10 +104,13 @@ get_validity_info (PKT_public_key *pk, PKT_user_id *uid)
 }
 
 unsigned int
-get_validity (PKT_public_key *pk, PKT_user_id *uid)
+get_validity (PKT_public_key *pk, PKT_user_id *uid, PKT_signature *sig,
+	      int may_ask)
 {
   (void)pk;
   (void)uid;
+  (void)sig;
+  (void)may_ask;
   return 0;
 }
 
@@ -411,3 +414,26 @@ gpg_dirmngr_get_pka (ctrl_t ctrl, const char *userid,
     *r_url = NULL;
   return gpg_error (GPG_ERR_NOT_FOUND);
 }
+
+enum tofu_binding_policy
+  {
+    tofu_binding_policy
+  };
+
+gpg_error_t
+tofu_get_policy (PKT_public_key *pk, PKT_user_id *user_id,
+		 enum tofu_binding_policy *policy)
+{
+  (void)pk;
+  (void)user_id;
+  (void)policy;
+  return gpg_error (GPG_ERR_GENERAL);
+}
+
+const char *
+tofu_binding_policy_str (enum tofu_binding_policy policy)
+{
+  (void)policy;
+
+  return "unknown";
+}
diff --git a/g10/tofu.c b/g10/tofu.c
new file mode 100644
index 0000000..3dd8314
--- /dev/null
+++ b/g10/tofu.c
@@ -0,0 +1,1668 @@
+/* XXX:
+
+   - Add some test cases.
+
+   - Format the fingerprints nicely when printing.
+ */
+
+#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 "mkdir_p.h"
+
+#include "tofu.h"
+
+enum db_type
+  {
+    DB_COMBINED,
+    DB_EMAIL,
+    DB_KEY
+  };
+
+struct db
+{
+  struct db *next;
+  enum db_type type;
+  sqlite3 *db;
+  char name[1];
+};
+
+const char *
+tofu_binding_policy_str (enum tofu_binding_policy policy)
+{
+  switch (policy)
+    {
+    case TOFU_BINDING_NONE: return "none";
+    case TOFU_BINDING_AUTO: return "auto";
+    case TOFU_BINDING_GOOD: return "good";
+    case TOFU_BINDING_UNKNOWN: return "unknown";
+    case TOFU_BINDING_BAD: return "bad";
+    case TOFU_BINDING_ASK: return "ask";
+    default: return "???";
+    }
+}
+
+int
+tofu_binding_policy_to_trust (enum tofu_binding_policy policy)
+{
+  if (policy == TOFU_BINDING_AUTO)
+    /* If POLICY is AUTO, fallback to OPT.TOFU_DEFAULT_POLICY.  */
+    policy = opt.tofu_default_policy;
+  if (policy == TOFU_BINDING_AUTO)
+    policy = TOFU_BINDING_GOOD;
+
+  switch (policy)
+    {
+    case TOFU_BINDING_GOOD:
+      return TRUST_FULLY;
+    case TOFU_BINDING_UNKNOWN:
+      return TRUST_UNKNOWN;
+    case TOFU_BINDING_BAD:
+      return TRUST_NEVER;
+    case TOFU_BINDING_ASK:
+      return TRUST_ASK;
+    default:
+      log_bug ("Bad value for trust policy: %d\n",
+	       opt.tofu_default_policy);
+      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 int
+version_check_cb (void *cookie, int argc, char **argv, char **azColName)
+{
+  int *version = cookie;
+
+  if (argc != 1 || strcmp (azColName[0], "version") != 0)
+    *version = -1;
+
+  if (strcmp (argv[0], "0") == 0)
+    *version = 0;
+  else
+    log_error (_("TOFU: unsupported 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, enum db_type type)
+{
+  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 == 0)
+    /* 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 initializing TOFU database (%s): %s\n"),
+		 "version", err);
+      sqlite3_free (err);
+      goto out;
+    }
+
+  rc = sqlite3_exec (db,
+		     "insert into version values (0);",
+		     NULL, NULL, &err);
+  if (rc)
+    {
+      log_error (_("Error initializing TOFU database (%s): %s\n"),
+		 "version, init", 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.
+
+       POLICY: The trust policy (-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,"
+     "  policy BOOLEAN CHECK (policy in (%d, %d, %d, %d, %d)),\n"
+     "  unique (fingerprint, email));\n"
+     "create index bindings_fingerprint_email\n"
+     " on bindings (fingerprint, email);",
+     TOFU_BINDING_AUTO, TOFU_BINDING_GOOD, TOFU_BINDING_UNKNOWN,
+     TOFU_BINDING_BAD, TOFU_BINDING_ASK);
+  if (rc)
+    {
+      log_error (_("Error initializing TOFU database (%s): %s\n"),
+		 "bindings", err);
+      sqlite3_free (err);
+      goto out;
+    }
+
+  if (type != DB_KEY)
+    {
+      /* 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 NOT NULL, sig_digest TEXT,"
+			 "  origin TEXT, sig_time INTEGER, time INTEGER,"
+			 "  primary key (binding, sig_digest, origin));",
+			 NULL, NULL, &err);
+      if (rc)
+	{
+	  log_error (_("Error initializing TOFU database (%s): %s\n"),
+		     "signatures", err);
+	  sqlite3_free (err);
+	  goto out;
+	}
+    }
+
+ out:
+  if (rc)
+    {
+      rc = sqlite3_exec (db, "rollback;", 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;
+    }
+}
+
+
+/* Open and initialize a low-level TOFU database.  Returns NULL on
+   failure.  */
+static sqlite3 *
+opendb (char *filename, enum db_type type)
+{
+  sqlite3 *db;
+  int filename_free = 0;
+  int rc;
+
+  if (opt.tofu_db_format == TOFU_DB_FLAT)
+    {
+      assert (! filename);
+      assert (type == DB_COMBINED);
+      filename = make_filename (opt.homedir, "tofu.db", NULL);
+      filename_free = 1;
+    }
+  else
+    assert (type == DB_EMAIL || type == DB_KEY);
+
+  assert (filename);
+
+  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;
+    }
+
+  if (filename_free)
+    xfree (filename);
+
+  if (db && initdb (db, type) == 0)
+    {
+      sqlite3_close (db);
+      db = NULL;
+    }
+
+  return db;
+}
+
+static sqlite3 *
+getdb (struct db *dbs, const char *name, enum db_type type)
+{
+  struct db *t = NULL;
+  int i;
+  sqlite3 *sqlitedb = NULL;
+  char *name_sanitized = NULL;
+  char *filename = NULL;
+
+  assert (! dbs->name[0]);
+
+  if (opt.tofu_db_format == TOFU_DB_FLAT)
+    /* When using the flat format, we only have a single DB.  */
+    {
+      assert (dbs->db);
+      assert (! dbs->next);
+      assert (type == DB_COMBINED);
+      return dbs->db;
+    }
+  else
+    /* When using the split format the first entry on the DB list is a
+       dummy entry.  */
+    assert (! dbs->db);
+
+  /* We have the split format.  */
+  assert (type == DB_EMAIL || type == DB_KEY);
+
+  /* Only allow alpha-numeric characters in the filename.  */
+  name_sanitized = xstrdup (name);
+  for (i = 0; name[i]; i ++)
+    {
+      char c = name_sanitized[i];
+      if (! (('a' <= c && c <= 'z')
+	     || ('A' <= c && c <= 'Z')
+	     || ('0' <= c && c <= '9')))
+	name_sanitized[i] = '_';
+    }
+
+  for (t = dbs; t; t = t->next)
+    if (type == t->type && strcmp (t->name, name_sanitized) == 0)
+      return t->db;
+
+  /* Open the DB.  The filename has the form:
+
+       tofu.d/TYPE/PREFIX/NAME.db
+
+     We use a short prefix to try to avoid having many files in a
+     single directory.  */
+  {
+    char *type_str = type == DB_EMAIL ? "email" : "key";
+    char prefix[3] = { name_sanitized[0], name_sanitized[1], 0 };
+    char *name_db;
+
+    /* Make the directory.  */
+    if (mkdir_p (opt.homedir, "tofu.d", type_str, prefix, NULL) != 0)
+      log_fatal ("Unable to create directory %s/%s/%s/%s",
+		 opt.homedir, "tofu.d", type_str, prefix);
+
+    name_db = xstrconcat (name_sanitized, ".db", NULL);
+    filename = make_filename
+      (opt.homedir, "tofu.d", type_str, prefix, name_db, NULL);
+    xfree (name_db);
+  }
+
+  sqlitedb = opendb (filename, type);
+  if (! sqlitedb)
+    goto out;
+
+  t = xmalloc (sizeof (struct db) + strlen (name_sanitized));
+  t->type = type;
+  t->db = sqlitedb;
+  strcpy (t->name, name_sanitized);
+
+  t->next = dbs->next;
+  dbs->next = t;
+
+ out:
+  xfree (filename);
+  xfree (name_sanitized);
+
+  if (! t)
+    return NULL;
+  return t->db;
+}
+
+
+/* Create a new DB meta-handle.  Returns NULL on error.  */
+static struct db *
+opendbs (void)
+{
+  sqlite3 *db = NULL;
+  struct db *dbs;
+
+  if (opt.tofu_db_format == TOFU_DB_FLAT)
+    {
+      db = opendb (NULL, DB_COMBINED);
+      if (! db)
+	return NULL;
+    }
+
+  dbs = xmalloc_clear (sizeof (*dbs));
+  dbs->db = db;
+  dbs->type = DB_COMBINED;
+
+  return dbs;
+}
+
+/* Release all of the resources associated with a DB meta-handle.  */
+static void
+closedbs (struct db *dbs)
+{
+  struct db *n;
+
+  /* The first entry is always the combined DB.  */
+  assert (dbs->type == DB_COMBINED);
+  if (opt.tofu_db_format == TOFU_DB_FLAT)
+    {
+      /* If we are using the flat format, then there is only ever the
+	 combined DB.  */
+      assert (! dbs->next);
+      assert (dbs->db);
+    }
+  else
+    /* In the split format, the combined record is just a place holder
+       so that we have a stable handle.  */
+    assert (! dbs->db);
+
+  while (dbs)
+    {
+      n = dbs->next;
+
+      if (dbs->db)
+	{
+	  assert (opt.tofu_db_format == TOFU_DB_SPLIT);
+	  assert (dbs->type != DB_COMBINED);
+	  assert (dbs->name[0]);
+
+	  sqlite3_close (dbs->db);
+	}
+      else
+	{
+	  assert (dbs->type == DB_COMBINED);
+	  assert (! dbs->name[0]);
+	}
+
+      xfree (dbs);
+
+      dbs = n;
+    }
+}
+
+
+/* Collect results of a select min (foo) ...; style query.  Aborts if
+   the argument is not a valid integer (or real of the form X.0).  */
+static int
+get_single_long_cb (void *cookie, int argc, char **argv, char **azColName)
+{
+  long *count = cookie;
+  char *tail = NULL;
+
+  (void) azColName;
+
+  assert (argc == 1);
+
+  errno = 0;
+  *count = strtol (argv[0], &tail, 0);
+  if (errno || *tail != '\0')
+    /* Abort.  */
+    return 1;
+  return 0;
+}
+
+/* Record (or update) a trust policy about a (possibly new)
+   binding.  */
+static gpg_error_t
+record_binding (struct db *dbs, const char *fingerprint, const char *email,
+		const char *user_id, int policy, int show_old)
+{
+  sqlite3 *db = NULL, *db2 = NULL;
+  int rc;
+  char *err = NULL;
+  long policy_old = TOFU_BINDING_NONE;
+  int i;
+
+  db = getdb (dbs, email, DB_EMAIL);
+  if (! db)
+    return gpg_error (GPG_ERR_GENERAL);
+
+  if (opt.tofu_db_format == TOFU_DB_SPLIT)
+    /* In the split format, we need to update two DBs.  */
+    {
+      db2 = getdb (dbs, fingerprint, DB_KEY);
+      if (! db2)
+	return gpg_error (GPG_ERR_GENERAL);
+
+      rc = sqlite3_exec (db, "begin transaction;", NULL, NULL, &err);
+      if (rc)
+	{
+	  log_error (_("Error beginning transaction on TOFU database `%s': %s\n"),
+		     dbs->name, err);
+	  sqlite3_free (err);
+	  return gpg_error (GPG_ERR_GENERAL);
+	}
+
+      rc = sqlite3_exec (db2, "begin transaction;", NULL, NULL, &err);
+      if (rc)
+	{
+	  log_error (_("Error beginning transaction on TOFU database `%s': %s\n"),
+		     dbs->name, err);
+	  sqlite3_free (err);
+	  goto out_revert_one;
+	}
+    }
+
+  if (! (policy == TOFU_BINDING_AUTO
+	 || policy == TOFU_BINDING_GOOD
+	 || policy == TOFU_BINDING_UNKNOWN
+	 || policy == TOFU_BINDING_BAD
+	 || policy == TOFU_BINDING_ASK))
+    log_bug ("TOFU: %s: Bad value for policy (%d)!\n", __func__, policy);
+
+  if (show_old)
+    /* Get the old policy.  Since this is just for informational
+       purposes, there is no need to start a transaction or to die if
+       there is a failure.  */
+    {
+      rc = sqlite3_exec_printf
+	(db, get_single_long_cb, &policy_old, &err,
+	 "select policy from bindings"
+	 " where fingerprint = %Q and email = %Q",
+	 fingerprint, email);
+      if (rc)
+	{
+	  log_debug ("TOFU: Error reading from binding database"
+		     " (reading policy for <%s, %s>): %s\n",
+		     fingerprint, email, err);
+	  sqlite3_free (err);
+	}
+    }
+
+  if (policy_old != TOFU_BINDING_NONE)
+    log_info (_("TOFU: Changing trust policy for binding <%s, %s>"
+		" from %s to %s.\n"),
+	      fingerprint, email,
+	      tofu_binding_policy_str (policy_old),
+	      tofu_binding_policy_str (policy));
+  else
+    log_info (_("TOFU: Set trust policy for binding <%s, %s> to %s.\n"),
+	      fingerprint, email,
+	      tofu_binding_policy_str (policy));
+
+  if (policy_old == policy)
+    /* Nothing to do.  */
+    goto out;
+
+  for (i = 0; i <= (opt.tofu_db_format == TOFU_DB_SPLIT); i ++)
+    {
+      rc = sqlite3_exec_printf
+	(i == 0 ? db : db2, NULL, NULL, &err,
+	 "insert or replace into bindings\n"
+	 " (oid, fingerprint, email, user_id, time, policy)\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, policy);
+      if (rc)
+	{
+	  log_error ("TOFU: Error updating binding database"
+		     " (inserting <%s, %s, %s>): %s\n",
+		     fingerprint, email, tofu_binding_policy_str (policy),
+		     err);
+	  sqlite3_free (err);
+	  goto out;
+	}
+    }
+
+ out:
+  if (opt.tofu_db_format == TOFU_DB_SPLIT)
+    /* We only need a transaction for the split format.  */
+    {
+      int rc2;
+
+      rc2 = sqlite3_exec_printf (db2, NULL, NULL, &err,
+				 rc ? "rollback;" : "end transaction;");
+      if (rc2)
+	{
+	  log_error (_("Error ending transaction on TOFU database: %s\n"),
+		     err);
+	  sqlite3_free (err);
+	}
+
+    out_revert_one:
+      rc2 = sqlite3_exec_printf (db, NULL, NULL, &err,
+				 rc ? "rollback;" : "end transaction;");
+      if (rc2)
+	{
+	  log_error (_("Error ending transaction on TOFU database: %s\n"),
+		     err);
+	  sqlite3_free (err);
+	}
+    }
+
+  return rc ? gpg_error (GPG_ERR_GENERAL) : 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_unsigned_long_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 policy for this binding.  */
+  enum tofu_binding_policy policy;
+
+  /* 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, policy, 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->policy = 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_POLICY_ERROR -1
+
+static enum tofu_binding_policy
+get_policy (struct db *dbs, const char *fingerprint, const char *email)
+{
+  sqlite3 *db;
+  int policy;
+  int rc;
+  char *err = NULL;
+
+  assert (GET_POLICY_ERROR != TOFU_BINDING_AUTO
+	  && GET_POLICY_ERROR != TOFU_BINDING_GOOD
+	  && GET_POLICY_ERROR != TOFU_BINDING_UNKNOWN
+	  && GET_POLICY_ERROR != TOFU_BINDING_BAD
+	  && GET_POLICY_ERROR != TOFU_BINDING_ASK);
+
+  db = getdb (dbs, email, DB_EMAIL);
+  if (! db)
+    return GET_POLICY_ERROR;
+
+  /* Check if the <FINGERPRINT, EMAIL> binding is known
+     (TOFU_BINDING_NONE cannot appear in the DB.  Thus, if POLICY is
+     still TOFU_BINDING_NONE after executing the query, then the
+     result set was empty.)  */
+  policy = TOFU_BINDING_NONE;
+  assert (policy != TOFU_BINDING_AUTO
+	  && policy != TOFU_BINDING_GOOD
+	  && policy != TOFU_BINDING_UNKNOWN
+	  && policy != TOFU_BINDING_BAD
+	  && policy != TOFU_BINDING_ASK);
+  rc = sqlite3_exec_printf
+    (db, get_single_unsigned_long_cb, &policy, &err,
+     "select policy 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);
+      policy = GET_POLICY_ERROR;
+    }
+
+  return policy;
+}
+
+#define GET_TRUST_ERROR -1
+
+/* Return the trust policy (TRUST_NEVER, etc.) for the binding
+   <fingerprint, email>.  If we don't have a policy, returns
+   TOFU_BINDING_NONE.  If an error occurs, returns GET_TRUST_ERROR.  */
+static int
+get_trust (struct db *dbs, const char *fingerprint, const char *email,
+	   const char *user_id, int may_ask)
+{
+  sqlite3 *db;
+  strlist_t fingerprints = NULL;
+  int policy;
+  int rc;
+  char *err = NULL;
+  int trust_level = TRUST_UNKNOWN;
+  int count;
+
+  if (opt.batch)
+    may_ask = 0;
+
+  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);
+
+  db = getdb (dbs, email, DB_EMAIL);
+  if (! db)
+    return GET_TRUST_ERROR;
+
+  policy = get_policy (dbs, fingerprint, email);
+  switch (policy)
+    {
+    case TOFU_BINDING_AUTO:
+      /* The saved judgement is auto.  If opt.tofu_default_policy is
+	 not ask, we don't have to ask the user anything.  */
+      log_info ("TOFU: auto (default trust: %s) binding: <%s, %s>\n",
+		tofu_binding_policy_str (opt.tofu_default_policy),
+		fingerprint, email);
+      if (opt.tofu_default_policy == TOFU_BINDING_ASK)
+	/* We need to ask the user what to do.  Case #1 below.  */
+	{
+	  if (! may_ask)
+	    {
+	      trust_level = TRUST_UNDEFINED;
+	      goto out;
+	    }
+	}
+      else
+	{
+	  assert (opt.tofu_default_policy == TOFU_BINDING_AUTO
+		  || opt.tofu_default_policy == TOFU_BINDING_GOOD
+		  || opt.tofu_default_policy == TOFU_BINDING_UNKNOWN
+		  || opt.tofu_default_policy == TOFU_BINDING_BAD);
+	  trust_level = tofu_binding_policy_to_trust (policy);
+	  goto out;
+	}
+
+      break;
+
+    case TOFU_BINDING_GOOD:
+    case TOFU_BINDING_UNKNOWN:
+    case TOFU_BINDING_BAD:
+      /* The saved judgement is good, unknown or bad.  We don't need
+	 to ask the user anything.  */
+      log_info ("TOFU: Known binding <%s, %s>'s policy: %s\n",
+		fingerprint, email, tofu_binding_policy_str (policy));
+      trust_level = tofu_binding_policy_to_trust (policy);
+      goto out;
+
+    case TOFU_BINDING_ASK:
+      /* We need to ask the user what to do.  Case #2 below.  */
+      if (! may_ask)
+	{
+	  trust_level = TRUST_UNDEFINED;
+	  goto out;
+	}
+
+      break;
+
+    case TOFU_BINDING_NONE:
+      /* The binding is new, we need to check for conflicts.  Case #3
+	 below.  */
+      break;
+
+    default:
+      log_bug ("%s: Impossible value for policy (%d)\n", __func__, policy);
+    }
+
+
+  /* We get here if:
+
+       1. The saved policy is auto and the default trust policy 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 policy (policy ==
+          TOFU_BINDING_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_policy != TOFU_BINDING_ASK)
+    /* New binding with no conflict and a concrete default policy.
+
+       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 policy, which is not to
+       ask the user.  */
+    {
+      /* If we've seen this binding, then we've seen this email and
+	 policy couldn't possibly be TOFU_BINDING_NONE.  */
+      assert (policy == TOFU_BINDING_NONE);
+
+      log_info ("TOFU: New binding <%s, %s>, no conflict.\n",
+		email, fingerprint);
+
+      if (record_binding (dbs, fingerprint, email, user_id,
+			  TOFU_BINDING_AUTO, 0) != 0)
+	{
+	  log_error ("TOFU: Error setting binding's trust level to %s\n",
+		     "auto");
+	  trust_level = GET_TRUST_ERROR;
+	  goto out;
+	}
+
+      trust_level = tofu_binding_policy_to_trust (TOFU_BINDING_AUTO);
+      goto out;
+    }
+
+#if 1
+  /* XXX: Is this behavior desirable?  */
+  if (policy == TOFU_BINDING_NONE)
+    /* This is a new binding and we have a conflict.  Mark any
+       conflicting bindings that have an automatic policy as now
+       requiring confirmation.  */
+    {
+      rc = sqlite3_exec_printf
+	(db, NULL, NULL, &err,
+	 "update bindings set policy = %d"
+	 " where email = %Q and policy = %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 (! may_ask)
+    /* We can only get here in the third case (no saved policy) and if
+       there is a conflict.  */
+    {
+      assert (policy == TOFU_BINDING_NONE);
+      trust_level = TRUST_UNDEFINED;
+
+      if (record_binding (dbs, fingerprint, email, user_id,
+			  TOFU_BINDING_ASK, 0) != 0)
+	log_error ("TOFU: Error setting binding's trust level to %s\n",
+		   "ask");
+
+      goto out;
+    }
+
+  /* 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
+         (policy == TOFU_BINDING_NONE && length(fingerprints) > 0),
+
+       - This is a new binding and opt.tofu_default_policy is set to
+         ask.  (policy == TOFU_BINDING_NONE && opt.tofu_default_policy ==
+         TOFU_BINDING_ASK), or,
+
+       - The trust policy is ask (the user deferred last time)
+         (policy == 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 (policy == TOFU_BINDING_NONE)
+      es_fprintf (fp, "The binding <%s, %s> is NOT known.  ",
+		  email, fingerprint);
+    es_fprintf (fp, "Please indicate whether you believe the binding ");
+    if (policy != TOFU_BINDING_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.  */
+    {
+      sqlite3 *db2;
+
+      if (opt.tofu_db_format == TOFU_DB_SPLIT)
+	/* In the split format, we need to search in the fingerprint
+	   DB, not the email DB.  */
+	db2 = getdb (dbs, fingerprint, DB_KEY);
+      else
+	db2 = db;
+
+      if (db2)
+	{
+	  rc = sqlite3_exec_printf
+	    (db2, strings_collect_cb, &other_user_ids, &err,
+	     "select user_id, policy 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_policy;
+
+	    assert (strlist_iter->next);
+	    strlist_iter = strlist_iter->next;
+	    other_policy = strlist_iter->d;
+
+	    es_fprintf (fp, "  %s (policy: %s)\n",
+			other_user_id,
+			tofu_binding_policy_str (atoi (other_policy)));
+	  }
+	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, policy, 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_policy_str (stats_iter->policy));
+		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 = _("gGaArUuRbB");
+    es_fprintf (fp, _("(G)ood/(A)ccept once/(U)nknown/(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) != 10)
+	  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.  */
+		    policy = TOFU_BINDING_GOOD;
+		    trust_level = tofu_binding_policy_to_trust (policy);
+		    if (record_binding (dbs, fingerprint, email, user_id,
+					policy, 0) != 0)
+		      /* If there's an error registering the
+			 binding, don't save the signature.  */
+		      trust_level = GET_TRUST_ERROR;
+		    break;
+		  case 1: /* Accept once.  */
+		    policy = TOFU_BINDING_ASK;
+		    trust_level =
+		      tofu_binding_policy_to_trust (TOFU_BINDING_GOOD);
+		    if (record_binding (dbs, fingerprint, email, user_id,
+					policy, 0) != 0)
+		      trust_level = GET_TRUST_ERROR;
+		    break;
+		  case 2: /* Unknown.  */
+		    policy = TOFU_BINDING_UNKNOWN;
+		    trust_level = tofu_binding_policy_to_trust (policy);
+		    if (record_binding (dbs, fingerprint, email, user_id,
+					policy, 0) != 0)
+		      trust_level = GET_TRUST_ERROR;
+		    break;
+		  case 3: /* Reject once.  */
+		    policy = TOFU_BINDING_ASK;
+		    trust_level =
+		      tofu_binding_policy_to_trust (TOFU_BINDING_BAD);
+		    if (record_binding (dbs, fingerprint, email, user_id,
+					policy, 0) != 0)
+		      trust_level = GET_TRUST_ERROR;
+		    break;
+		  case 4: /* Bad.  */
+		    policy = TOFU_BINDING_BAD;
+		    trust_level = tofu_binding_policy_to_trust (policy);
+		    if (record_binding (dbs, fingerprint, email, user_id,
+					policy, 0) != 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;
+}
+
+static char *
+email_from_user_id (const char *user_id)
+{
+  char *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));
+
+  return email;
+}
+
+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, int may_ask)
+{
+  struct db *dbs;
+  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;
+
+  dbs = opendbs ();
+  if (! dbs)
+    {
+      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 = email_from_user_id (user_id);
+
+  if (! origin)
+    /* The default origin is simply "unknown".  */
+    origin = "unknown";
+
+  trust_level = get_trust (dbs, fingerprint, email, user_id, may_ask);
+  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);
+
+  db = getdb (dbs, email, DB_EMAIL);
+  if (! db)
+    {
+      log_info ("TOFU: Error opening DB.\n");
+      goto die;
+    }
+
+  /* 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_unsigned_long_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, "rollback;", 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);
+  if (dbs)
+    closedbs (dbs);
+
+  return trust_level;
+}
+
+int
+tofu_wot_trust_combine (int tofu_base, int wot_base)
+{
+  int tofu = tofu_base & TRUST_MASK;
+  int wot = wot_base & TRUST_MASK;
+  int upper = (tofu_base & ~TRUST_MASK) | (wot_base & ~TRUST_MASK);
+
+  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 policys.  These trump positive
+     trust policies.  */
+  if (tofu == TRUST_NEVER || wot == TRUST_NEVER)
+    /* TRUST_NEVER trumps everything else.  */
+    return upper | TRUST_NEVER;
+  if (tofu == TRUST_EXPIRED || wot == TRUST_EXPIRED)
+    /* TRUST_EXPIRED trumps everything but TRUST_NEVER.  */
+    return upper | TRUST_EXPIRED;
+
+  /* Now we only have positive or neutral trust policies.  We take
+     the max.  */
+  if (tofu == TRUST_ULTIMATE || wot == TRUST_ULTIMATE)
+    return upper | TRUST_ULTIMATE;
+  if (tofu == TRUST_FULLY || wot == TRUST_FULLY)
+    return upper | TRUST_FULLY;
+  if (tofu == TRUST_MARGINAL || wot == TRUST_MARGINAL)
+    return upper | TRUST_MARGINAL;
+  if (tofu == TRUST_UNDEFINED || wot == TRUST_UNDEFINED)
+    return upper | TRUST_UNDEFINED;
+  return upper | TRUST_UNKNOWN;
+}
+
+int
+tofu_get_validity (const byte *fingerprint_bin, const char *user_id,
+		   int may_ask)
+{
+  struct db *dbs;
+  char fingerprint[MAX_FINGERPRINT_LEN * 2 + 1];
+  char *email = NULL;
+  int trust_level = TRUST_UNDEFINED;
+
+  dbs = opendbs ();
+  if (! dbs)
+    {
+      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 = email_from_user_id (user_id);
+
+  trust_level = get_trust (dbs, fingerprint, email, user_id, may_ask);
+  if (trust_level == GET_TRUST_ERROR)
+    /* An error.  */
+    trust_level = TRUST_UNKNOWN;
+
+ die:
+  xfree (email);
+  closedbs (dbs);
+
+  return trust_level;
+}
+
+int
+tofu_set_policy (kbnode_t kb, enum tofu_binding_policy policy)
+{
+  struct db *dbs;
+  PKT_public_key *pk;
+  char fingerprint_bin[MAX_FINGERPRINT_LEN];
+  size_t fingerprint_bin_len = sizeof (fingerprint_bin);
+  char fingerprint[MAX_FINGERPRINT_LEN * 2 + 1];
+
+  assert (kb->pkt->pkttype == PKT_PUBLIC_KEY);
+  pk = kb->pkt->pkt.public_key;
+
+  dbs = opendbs ();
+  if (! dbs)
+    {
+      log_info ("TOFU: Error opening DB.\n");
+      return gpg_error (GPG_ERR_GENERAL);
+    }
+
+  log_info ("Setting TOFU policy for %s to %s\n",
+	    keystr (pk->keyid), tofu_binding_policy_str (policy));
+  if (! (pk->main_keyid[0] == pk->keyid[0]
+	 && pk->main_keyid[1] == pk->keyid[1]))
+    log_bug ("%s: Passed a subkey, but expecting a primary key.\n", __func__);
+
+  fingerprint_from_pk (pk, fingerprint_bin, &fingerprint_bin_len);
+  assert (fingerprint_bin_len == sizeof (fingerprint_bin));
+
+  bin2hex (fingerprint_bin, MAX_FINGERPRINT_LEN, fingerprint);
+
+  for (; kb; kb = kb->next)
+    {
+      PKT_user_id *user_id;
+      char *email;
+
+      if (kb->pkt->pkttype != PKT_USER_ID)
+	continue;
+
+      user_id = kb->pkt->pkt.user_id;
+      if (user_id->is_revoked)
+	/* Skip revoked user ids.  (Don't skip expired user ids, the
+	   expiry can be changed.)  */
+	continue;
+
+      email = email_from_user_id (user_id->name);
+
+      record_binding (dbs, fingerprint, email, user_id->name, policy, 1);
+
+      xfree (email);
+    }
+
+  closedbs (dbs);
+
+  return 0;
+}
+
+int
+tofu_set_policy_by_keyid (u32 *keyid, enum tofu_binding_policy policy)
+{
+  kbnode_t keyblock = get_pubkeyblock (keyid);
+  if (! keyblock)
+    return gpg_error (GPG_ERR_NO_PUBKEY);
+
+  return tofu_set_policy (keyblock, policy);
+}
+
+gpg_error_t
+tofu_get_policy (PKT_public_key *pk, PKT_user_id *user_id,
+		 enum tofu_binding_policy *policy)
+{
+  struct db *dbs;
+  char fingerprint_bin[MAX_FINGERPRINT_LEN];
+  size_t fingerprint_bin_len = sizeof (fingerprint_bin);
+  char fingerprint[MAX_FINGERPRINT_LEN * 2 + 1];
+  char *email;
+
+  /* Make sure PK is a primary key.  */
+  assert (pk->main_keyid[0] == pk->keyid[0]
+	  && pk->main_keyid[1] == pk->keyid[1]);
+
+  dbs = opendbs ();
+  if (! dbs)
+    {
+      log_info ("TOFU: Error opening DB.\n");
+      return gpg_error (GPG_ERR_GENERAL);
+    }
+
+  fingerprint_from_pk (pk, fingerprint_bin, &fingerprint_bin_len);
+  assert (fingerprint_bin_len == sizeof (fingerprint_bin));
+
+  bin2hex (fingerprint_bin, MAX_FINGERPRINT_LEN, fingerprint);
+
+  email = email_from_user_id (user_id->name);
+
+  *policy = get_policy (dbs, fingerprint, email);
+
+  closedbs (dbs);
+  xfree (email);
+
+  if (*policy == GET_POLICY_ERROR)
+    return gpg_error (GPG_ERR_GENERAL);
+  return 0;
+}
diff --git a/g10/tofu.h b/g10/tofu.h
new file mode 100644
index 0000000..cd243bd
--- /dev/null
+++ b/g10/tofu.h
@@ -0,0 +1,78 @@
+#ifndef G10_TOFU_H
+#define G10_TOFU_H
+
+#include <config.h>
+
+/* For each binding, we have a trust policy.  There are four
+   possible trust policies.  */
+enum tofu_binding_policy
+  {
+    /* This value is used by tofu_get_policy to indicate that there is
+       no policy set for the specified binding.  */
+    TOFU_BINDING_NONE = 0,
+
+    /* We made a default policy decision.  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 policy is
+       configurable (and specified using: --tofu-default-policy).
+
+       Note: when using the default policy, we save TOFU_BINDING_AUTO
+       with the binding, not the policy that was in effect.  This way,
+       if the user invokes gpg again, but with a different value for
+       --tofu-default-policy, 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,
+
+    /* The user explicitly marked the binding as unknown.  In this
+       case, we return TRUST_UNKNOWN.  */
+    TOFU_BINDING_UNKNOWN = 3,
+
+    /* The user explicitly marked the binding as bad.  In this case,
+       we always return TRUST_NEVER.  */
+    TOFU_BINDING_BAD = 4,
+
+    /* The user deferred a definitive policy decision about the
+       binding (by selecting accept once or reject once).  The next
+       time we see this binding, we should ask the user what to
+       do.  */
+    TOFU_BINDING_ASK = 5
+  };
+
+/* Return a string representation of a trust policy.  Returns
+   "unknown" if POLICY is not valid.  */
+const char *tofu_binding_policy_str (enum tofu_binding_policy policy);
+
+/* Convert a trust policy (e.g., TOFU_BINDING_BAD) to a trust value
+   (e.g., TRUST_BAD) in light of the current configuration.  */
+int tofu_binding_policy_to_trust (enum tofu_binding_policy policy);
+
+
+/* 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, int may_ask);
+
+/* Determine the validity (TRUST_NEVER, etc.) of a given binding.  */
+int tofu_get_validity (const byte *fingerprint_bin, const char *user_id,
+		       int may_ask);
+
+/* Set the policy for all non-revoked and non-expired user ids in
+   the */
+int tofu_set_policy (kbnode_t kb, enum tofu_binding_policy policy);
+
+int tofu_set_policy_by_keyid (u32 *keyid, enum tofu_binding_policy policy);
+
+/* Return the policy.  If no policy has been set, returns
+   TOFU_BINDING_NONE.  */
+gpg_error_t tofu_get_policy (PKT_public_key *pk, PKT_user_id *user_id,
+			     enum tofu_binding_policy *policy);
+
+/* When in PGP+WOT mode, we need to combine TOFU and WOT trust
+   policies.  */
+int tofu_wot_trust_combine (int tofu, int wot);
+
+#endif
diff --git a/g10/trust.c b/g10/trust.c
index 316fe2f..38d957e 100644
--- a/g10/trust.c
+++ b/g10/trust.c
@@ -152,7 +152,7 @@ uid_trust_string_fixed (PKT_public_key *key, PKT_user_id *uid)
     return                         _("[ expired]");
   else if(key)
     {
-      switch (get_validity(key,uid)&TRUST_MASK)
+      switch (get_validity (key, uid, NULL, 0) & TRUST_MASK)
         {
         case TRUST_UNKNOWN:   return _("[ unknown]");
         case TRUST_EXPIRED:   return _("[ expired]");
@@ -298,7 +298,8 @@ check_or_update_trustdb (void)
  * otherwise, a reasonable value for the entire key is returned.
  */
 unsigned int
-get_validity (PKT_public_key *pk, PKT_user_id *uid)
+get_validity (PKT_public_key *pk, PKT_user_id *uid, PKT_signature *sig,
+	      int may_ask)
 {
   int rc;
   unsigned int validity;
@@ -330,7 +331,7 @@ get_validity (PKT_public_key *pk, PKT_user_id *uid)
 #ifdef NO_TRUST_MODELS
   validity = TRUST_UNKNOWN;
 #else
-  validity = tdb_get_validity_core (pk, uid, main_pk);
+  validity = tdb_get_validity_core (pk, uid, main_pk, sig, may_ask);
 #endif
 
  leave:
@@ -359,7 +360,7 @@ get_validity_info (PKT_public_key *pk, PKT_user_id *uid)
   if (!pk)
     return '?';  /* Just in case a NULL PK is passed.  */
 
-  trustlevel = get_validity (pk, uid);
+  trustlevel = get_validity (pk, uid, NULL, 0);
   if ((trustlevel & TRUST_FLAG_REVOKED))
     return 'r';
   return trust_letter (trustlevel);
@@ -374,7 +375,7 @@ get_validity_string (PKT_public_key *pk, PKT_user_id *uid)
   if (!pk)
     return "err";  /* Just in case a NULL PK is passed.  */
 
-  trustlevel = get_validity (pk, uid);
+  trustlevel = get_validity (pk, uid, NULL, 0);
   if ((trustlevel & TRUST_FLAG_REVOKED))
     return _("revoked");
   return trust_value_to_string (trustlevel);
diff --git a/g10/trustdb.c b/g10/trustdb.c
index b16682d..eb72ecf 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";
@@ -963,16 +966,21 @@ tdb_check_trustdb_stale (void)
 
 /*
  * Return the validity information for PK.  This is the core of
- * get_validity.
+ * get_validity.  If SIG is not NULL, then the trust is being
+ * evaluated in the context of the provided signature.  This is used
+ * by the TOFU code to record statistics.
  */
 unsigned int
 tdb_get_validity_core (PKT_public_key *pk, PKT_user_id *uid,
-                       PKT_public_key *main_pk)
+                       PKT_public_key *main_pk,
+		       PKT_signature *sig,
+		       int may_ask)
 {
   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,60 +1001,146 @@ tdb_get_validity_core (PKT_public_key *pk, PKT_user_id *uid,
       goto leave;
     }
 
-  err = read_trust_record (main_pk, &trec);
-  if (err && gpg_err_code (err) != GPG_ERR_NOT_FOUND)
-    {
-      tdbio_invalid ();
-      return 0;
-    }
-  if (gpg_err_code (err) == GPG_ERR_NOT_FOUND)
+  if (opt.trust_model == TM_TOFU || opt.trust_model == TM_TOFU_PGP)
     {
-      /* No record found.  */
-      validity = TRUST_UNKNOWN;
-      goto leave;
-    }
+      kbnode_t user_id_node;
+      int user_ids = 0;
+      int user_ids_expired = 0;
 
-  /* Loop over all user IDs */
-  recno = trec.r.trust.validlist;
-  validity = 0;
-  while (recno)
-    {
-      read_record (recno, &vrec, RECTYPE_VALID);
+      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)
+      /* If the caller didn't supply a user id then iterate over all
+	 uids.  */
+      if (! uid)
+	user_id_node = get_pubkeyblock (pk->main_keyid);
+
+      while (uid
+	     || (user_id_node = find_next_kbnode (user_id_node, PKT_USER_ID)))
 	{
-	  /* If a user ID is given we return the validity for that
-	     user ID ONLY.  If the namehash is not found, then there
-	     is no validity at all (i.e. the user ID wasn't
-	     signed). */
-	  if(memcmp(vrec.r.valid.namehash,uid->namehash,20)==0)
+	  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.  */
 	    {
-	      validity=(vrec.r.valid.validity & TRUST_MASK);
-	      break;
+	      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 ++;
+
+	  if (sig)
+	    tl = tofu_register (fingerprint, user_id->name,
+				sig->digest, sig->digest_len,
+				sig->timestamp, "unknown",
+				may_ask);
+	  else
+	    tl = tofu_get_validity (fingerprint, user_id->name, may_ask);
+
+	  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;
 	}
-      else
+    }
+
+  if (opt.trust_model == TM_TOFU_PGP
+      || opt.trust_model == TM_CLASSIC
+      || opt.trust_model == TM_PGP)
+    {
+      err = read_trust_record (main_pk, &trec);
+      if (err && gpg_err_code (err) != GPG_ERR_NOT_FOUND)
 	{
-	  /* If no namehash is given, we take the maximum validity
-	     over all user IDs */
-	  if ( validity < (vrec.r.valid.validity & TRUST_MASK) )
-	    validity = (vrec.r.valid.validity & TRUST_MASK);
+	  tdbio_invalid ();
+	  return 0;
+	}
+      if (gpg_err_code (err) == GPG_ERR_NOT_FOUND)
+	{
+	  /* No record found.  */
+	  validity = TRUST_UNKNOWN;
+	  goto leave;
 	}
 
-      recno = vrec.r.valid.next;
-    }
+      /* Loop over all user IDs */
+      recno = trec.r.trust.validlist;
+      validity = 0;
+      while (recno)
+	{
+	  read_record (recno, &vrec, RECTYPE_VALID);
 
-  if ( (trec.r.trust.ownertrust & TRUST_FLAG_DISABLED) )
-    {
-      validity |= TRUST_FLAG_DISABLED;
-      pk->flags.disabled = 1;
+	  if(uid)
+	    {
+	      /* If a user ID is given we return the validity for that
+		 user ID ONLY.  If the namehash is not found, then
+		 there is no validity at all (i.e. the user ID wasn't
+		 signed). */
+	      if(memcmp(vrec.r.valid.namehash,uid->namehash,20)==0)
+		{
+		  validity=(vrec.r.valid.validity & TRUST_MASK);
+		  break;
+		}
+	    }
+	  else
+	    {
+	      /* If no user ID is given, we take the maximum validity
+		 over all user IDs */
+	      if (validity < (vrec.r.valid.validity & TRUST_MASK))
+		validity = (vrec.r.valid.validity & TRUST_MASK);
+	    }
+
+	  recno = vrec.r.valid.next;
+	}
+
+      if ((trec.r.trust.ownertrust & TRUST_FLAG_DISABLED))
+	{
+	  validity |= TRUST_FLAG_DISABLED;
+	  pk->flags.disabled = 1;
+	}
+      else
+	pk->flags.disabled = 0;
+      pk->flags.disabled_valid = 1;
     }
-  else
-    pk->flags.disabled = 0;
-  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..0a0fbec 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 */
@@ -86,7 +87,8 @@ void revalidation_mark (void);
 void check_trustdb_stale (void);
 void check_or_update_trustdb (void);
 
-unsigned int get_validity (PKT_public_key *pk, PKT_user_id *uid);
+unsigned int get_validity (PKT_public_key *pk, PKT_user_id *uid,
+			   PKT_signature *sig, int may_ask);
 int get_validity_info (PKT_public_key *pk, PKT_user_id *uid);
 const char *get_validity_string (PKT_public_key *pk, PKT_user_id *uid);
 
@@ -120,7 +122,8 @@ void tdb_check_or_update (void);
 int tdb_cache_disabled_value (PKT_public_key *pk);
 
 unsigned int tdb_get_validity_core (PKT_public_key *pk, PKT_user_id *uid,
-                                    PKT_public_key *main_pk);
+                                    PKT_public_key *main_pk,
+				    PKT_signature *sig, int may_askx);
 
 void list_trust_path( const char *username );
 int enum_cert_paths( void **context, ulong *lid,
diff --git a/tests/openpgp/Makefile.am b/tests/openpgp/Makefile.am
index 4fdb0a6..6e78e8c 100644
--- a/tests/openpgp/Makefile.am
+++ b/tests/openpgp/Makefile.am
@@ -27,7 +27,7 @@ TESTS_ENVIRONMENT = GNUPGHOME=$(abs_builddir) GPG_AGENT_INFO= LC_ALL=C
 
 # Note: version.test needs to be the first test to run and finish.test
 # the last one
-TESTS = version.test mds.test \
+TESTS = tofu.test version.test mds.test \
 	decrypt.test decrypt-dsa.test \
 	sigs.test sigs-dsa.test \
 	encrypt.test encrypt-dsa.test  \
@@ -46,7 +46,9 @@ TEST_FILES = pubring.asc secring.asc plain-1o.asc plain-2o.asc plain-3o.asc \
 	     pubring.pkr.asc secring.skr.asc secdemo.asc pubdemo.asc \
              gpg.conf.tmpl gpg-agent.conf.tmpl \
 	     bug537-test.data.asc bug894-test.asc \
-	     bug1223-good.asc bug1223-bogus.asc 4gb-packet.asc
+	     bug1223-good.asc bug1223-bogus.asc 4gb-packet.asc \
+	     tofu-keys.asc tofu-keys-secret.asc \
+	     tofu-2183839A-1.txt tofu-BC15C85A-1.txt tofu-EE37CF96-1.txt
 
 data_files = data-500 data-9000 data-32000 data-80000 plain-large
 
@@ -93,10 +95,10 @@ CLEANFILES = prepared.stamp x y yy z out err  $(data_files) \
 	     *.test.log gpg_dearmor gpg.conf gpg-agent.conf S.gpg-agent \
 	     pubring.gpg pubring.gpg~ pubring.kbx pubring.kbx~ \
 	     secring.gpg pubring.pkr secring.skr \
-	     gnupg-test.stop random_seed gpg-agent.log
+	     gnupg-test.stop random_seed gpg-agent.log tofu.db
 
 clean-local:
-	-rm -rf private-keys-v1.d openpgp-revocs.d
+	-rm -rf private-keys-v1.d openpgp-revocs.d tofu.d
 
 
 # We need to depend on a couple of programs so that the tests don't
diff --git a/tests/openpgp/tofu-2183839A-1.txt b/tests/openpgp/tofu-2183839A-1.txt
new file mode 100644
index 0000000..521b3bb
Binary files /dev/null and b/tests/openpgp/tofu-2183839A-1.txt differ
diff --git a/tests/openpgp/tofu-BC15C85A-1.txt b/tests/openpgp/tofu-BC15C85A-1.txt
new file mode 100644
index 0000000..88cc649
--- /dev/null
+++ b/tests/openpgp/tofu-BC15C85A-1.txt
@@ -0,0 +1,9 @@
+-----BEGIN PGP MESSAGE-----
+Version: GnuPG v2
+
+owGbwMvMwMF46tzNaXtET0QxnmZPYgj9/c+Sq2MOCwMjBwMbKxOIy8DFKQBTo/SK
+hWFThVuj19r3R/6VzQkpaZuQx7s3r9BQ46v8KXkjb58dSjmXyr7enlCzb7dg1zE7
+aynbc6YTF+wXZI4IlAgPuLJhUeSXo0+WllxbFXUz39407cv15TcXThLj+3tFkSnZ
+YFXwM9+nfAoHpt6I/ZY96SJT3XFZKzO1jeZNJhZsV4Vfrjp0UmnH3E4A
+=X9WM
+-----END PGP MESSAGE-----
diff --git a/tests/openpgp/tofu-EE37CF96-1.txt b/tests/openpgp/tofu-EE37CF96-1.txt
new file mode 100644
index 0000000..33a38db
--- /dev/null
+++ b/tests/openpgp/tofu-EE37CF96-1.txt
@@ -0,0 +1,9 @@
+-----BEGIN PGP MESSAGE-----
+Version: GnuPG v2
+
+owGbwMvMwMEY0Tqz9J35+WmMp9mTGEJ//xPk6pjDwsDIwcDGygTiMnBxCsDULFZm
+/sk4S36iQ6FuZZPMPdOSe/rZOxNThTmzvJN4l1qe9XGdlLhtpumfzh0uhRnzT2Xc
+jmra+ZdN9+XBhml//i7v6XrfuWu56OuEI/fXH0i3P5HELb+j++6SO85VemLq/tvO
+hNvWtddvuZ7+z2JJaqnP4wiu2t+sEze/MWKZ9zz+u2FV6a3OIyJxjwA=
+=JMtb
+-----END PGP MESSAGE-----
diff --git a/tests/openpgp/tofu-keys-secret.asc b/tests/openpgp/tofu-keys-secret.asc
new file mode 100755
index 0000000..68e0d20
--- /dev/null
+++ b/tests/openpgp/tofu-keys-secret.asc
@@ -0,0 +1,95 @@
+-----BEGIN PGP PRIVATE KEY BLOCK-----
+Version: GnuPG v2
+
+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
+CQ==
+=2SlE
+-----END PGP PRIVATE KEY BLOCK-----
diff --git a/tests/openpgp/tofu-keys.asc b/tests/openpgp/tofu-keys.asc
new file mode 100755
index 0000000..2de1cf7
--- /dev/null
+++ b/tests/openpgp/tofu-keys.asc
@@ -0,0 +1,47 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+Version: GnuPG v2
+
+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
+0v2sv67Ar70GR4vQwMvfCUAbnsVrL05eBjUJ
+=Btw1
+-----END PGP PUBLIC KEY BLOCK-----
diff --git a/tests/openpgp/tofu.test b/tests/openpgp/tofu.test
new file mode 100755
index 0000000..82a1394
--- /dev/null
+++ b/tests/openpgp/tofu.test
@@ -0,0 +1,219 @@
+#!/bin/sh
+
+. $srcdir/defs.inc || exit 3
+
+# set -x
+
+KEYS="2183839A BC15C85A EE37CF96"
+
+# Make sure $srcdir is set.
+if test "x$srcdir" = x
+then
+    echo srcdir environment variable not set!
+    exit 1
+fi
+
+# Make sure $GNUPGHOME is set.
+if test "x$GNUPGHOME" = x
+then
+    echo "GNUPGHOME not set."
+    exit 1
+fi
+
+# Import the test keys.
+$GPG --import $srcdir/tofu-keys.asc
+
+# Make sure the keys are imported.
+for k in $KEYS
+do
+    if ! $GPG --list-keys $k >/dev/null 2>&1
+    then
+	echo Missing key $k
+	exit 1
+    fi
+done
+
+# Carefully remove the TOFU db.
+test -e $GNUPGHOME/tofu.db && rm $GNUPGHOME/tofu.db
+test -e $GNUPGHOME/tofu.d/email && rm -r $GNUPGHOME/tofu.d/email
+test -e $GNUPGHOME/tofu.d/key && rm -r $GNUPGHOME/tofu.d/key
+# This will fail if the directory is not empty.
+test -e $GNUPGHOME/tofu.d && rmdir $GNUPGHOME/tofu.d
+
+debug()
+{
+    echo "$@" >&2
+}
+
+# $1 is the keyid of the policy to lookup.  Any remaining arguments
+# are simply passed to GPG.
+#
+# This function only supports keys with a single user id.
+getpolicy()
+{
+    keyid=$1
+    if test x$keyid = x
+    then
+	echo No keyid supplied!
+	exit 1
+    fi
+    shift
+
+    debug $GPG --trust-model=tofu --with-colons $@ --list-keys "$keyid"
+    policy=$($GPG --trust-model=tofu --with-colons $@ --list-keys "$keyid" \
+		    | awk -F: '/^uid:/ { print $18 }')
+    if test $(echo "$policy" | wc -l) -ne 1
+    then
+	echo "Got: $policy" >&2
+	echo "error"
+    else
+	case $policy in
+	    auto|good|unknown|bad|ask) echo $policy ;;
+	    *) echo "error" ;;
+	esac
+    fi
+}
+
+# $1 is the key id
+# $2 is the expected policy
+# The rest are additional options to pass to gpg.
+checkpolicy()
+{
+    keyid=$1
+    shift
+    expected_policy=$1
+    shift
+    policy=$(getpolicy "$keyid" ${@:+"$@"})
+    if test "x$policy" != "x$expected_policy"
+    then
+	echo "$keyid: Expected policy to be \`$expected_policy', but got \`$policy'."
+	exit 1
+    fi
+}
+
+# $1 is the keyid of the trust level to lookup.  Any remaining
+# arguments are simply passed to GPG.
+#
+# This function only supports keys with a single user id.
+gettrust()
+{
+    keyid=$1
+    if test x$keyid = x
+    then
+	echo No keyid supplied!
+	exit 1
+    fi
+    shift
+
+    $GPG --trust-model=tofu --with-colons $@ --list-keys "$keyid" \
+		    | awk -F: '/^pub:/ { print $2 }' >&2
+    trust=$($GPG --trust-model=tofu --with-colons $@ --list-keys "$keyid" \
+		    | awk -F: '/^pub:/ { print $2 }')
+    if test $(echo "$trust" | wc -l) -ne 1
+    then
+	echo "error"
+    else
+	case $trust in
+	    [oidreqnmfuws-]) echo $trust ;;
+	    *) echo "Bad trust value: $trust" >&2; echo "error" ;;
+	esac
+    fi
+}
+
+# $1 is the key id
+# $2 is the expected trust level
+# The rest are additional options to pass to gpg.
+checktrust()
+{
+    keyid=$1
+    shift
+    expected_trust=$1
+    shift
+    trust=$(gettrust "$keyid" ${@:+"$@"})
+    if test "x$trust" != "x$expected_trust"
+    then
+	echo "$keyid: Expected trust to be \`$expected_trust', but got \`$trust'."
+	exit 1
+    fi
+}
+
+# Set key $1's policy to $2.  Any remaining arguments are passed as
+# options to gpg.
+setpolicy()
+{
+    keyid=$1
+    shift
+    policy=$1
+    shift
+
+    $GPG --trust-model=tofu ${@:+"$@"} --tofu-policy $policy $keyid
+}
+
+# Verify a message.  There should be no conflict and the trust policy
+# should be set to auto.
+$GPG --trust-model=tofu --verify $srcdir/tofu-2183839A-1.txt
+
+checkpolicy 2183839A auto
+
+trust=$(gettrust 2183839A)
+debug "default trust = $trust"
+if test "x$trust" != xf
+then
+    echo "Wrong default trust.  Got: \`$trust', expected \`f'"
+    exit 1
+fi
+
+# Trust should be derived lazily.  Thus, if the policy is set to auto
+# and we change --tofu-default-policy, then the trust should change as
+# well.  Try it.
+checktrust 2183839A f --tofu-default-policy=good
+checktrust 2183839A - --tofu-default-policy=unknown
+checktrust 2183839A n --tofu-default-policy=bad
+
+# Change the policy to something other than auto and make sure the
+# policy and the trust are correct.
+for policy in good unknown bad
+do
+    if test $policy = good
+    then
+	expected_trust='f'
+    elif test $policy = unknown
+    then
+	expected_trust='-'
+    else
+	expected_trust='n'
+    fi
+
+    debug
+    debug "Setting TOFU policy to $policy"
+    setpolicy 2183839A $policy
+
+    # Since we have a fixed policy, the trust level shouldn't change
+    # if we change the default policy.
+    for default_policy in auto good unknown bad ask
+    do
+	checkpolicy 2183839A $policy --tofu-default-policy=$default_policy
+	checktrust 2183839A $expected_trust \
+		   --tofu-default-policy=$default_policy
+    done
+done
+
+# BC15C85A conflicts with 2183839A.  On conflict, this will set
+# BC15C85A to ask.  If 2183839A is auto (it's not, it's bad), then it
+# will be set to ask.
+$GPG --trust-model=tofu --verify $srcdir/tofu-BC15C85A-1.txt
+checkpolicy BC15C85A ask
+checkpolicy 2183839A bad
+
+# EE37CF96 conflicts with 2183839A and BC15C85A.  We change BC15C85A's
+# policy to auto and leave 2183839A's policy at bad.  This conflict
+# should cause BC15C85A's policy to be changed to ask (since it is
+# auto), but not affect 2183839A's policy.
+setpolicy BC15C85A auto
+checkpolicy BC15C85A auto
+$GPG --trust-model=tofu --verify $srcdir/tofu-EE37CF96-1.txt
+checkpolicy BC15C85A ask
+checkpolicy 2183839A bad
+checkpolicy EE37CF96 ask
+
+exit 0

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

Summary of changes:
 common/stringhelp.c                |   2 +-
 doc/DETAILS                        |   4 +
 doc/gpg.texi                       |   3 +-
 g10/gpg.c                          |  14 +-
 g10/gpgv.c                         |  23 +++
 g10/keyedit.c                      |  76 +++++++--
 g10/keylist.c                      |  14 +-
 g10/packet.h                       |   2 +-
 g10/sign.c                         |  23 ++-
 g10/test-stubs.c                   |  23 +++
 g10/tofu.c                         | 312 ++++++++++++++++++++++---------------
 g10/tofu.h                         |  34 ++--
 tests/openpgp/Makefile.am          |  10 +-
 tests/openpgp/tofu-2183839A-1.txt  | Bin 0 -> 191 bytes
 tests/openpgp/tofu-BC15C85A-1.txt  |   9 ++
 tests/openpgp/tofu-EE37CF96-1.txt  |   9 ++
 tests/openpgp/tofu-keys-secret.asc |  95 +++++++++++
 tests/openpgp/tofu-keys.asc        |  47 ++++++
 tests/openpgp/tofu.test            | 219 ++++++++++++++++++++++++++
 19 files changed, 748 insertions(+), 171 deletions(-)
 create mode 100644 tests/openpgp/tofu-2183839A-1.txt
 create mode 100644 tests/openpgp/tofu-BC15C85A-1.txt
 create mode 100644 tests/openpgp/tofu-EE37CF96-1.txt
 create mode 100755 tests/openpgp/tofu-keys-secret.asc
 create mode 100755 tests/openpgp/tofu-keys.asc
 create mode 100755 tests/openpgp/tofu.test


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




More information about the Gnupg-commits mailing list