[Pkg-gnupg-commit] [gnupg2] 06/241: g10: Add TOFU support.

Daniel Kahn Gillmor dkg at fifthhorseman.net
Wed Dec 9 20:31:46 UTC 2015


This is an automated email from the git hooks/post-receive script.

dkg pushed a commit to branch master
in repository gnupg2.

commit f77913e0ff7be4cd9c6337a70ac715e6f4a43572
Author: Neal H. Walfield <neal at g10code.com>
Date:   Sun Oct 18 18:44:05 2015 +0200

    g10: Add TOFU support.
    
    * configure.ac: Check for sqlite3.
    (SQLITE3_CFLAGS): AC_SUBST it.
    (SQLITE3_LIBS): Likewise.
    * g10/Makefile.am (AM_CFLAGS): Add $(SQLITE3_CFLAGS).
    (gpg2_SOURCES): Add tofu.h and tofu.c.
    (gpg2_LDADD): Add $(SQLITE3_LIBS).
    * g10/tofu.c: New file.
    * g10/tofu.h: New file.
    * g10/options.h (trust_model): Define TM_TOFU and TM_TOFU_PGP.
    (tofu_db_format): Define.
    * g10/packet.h (PKT_signature): Add fields digest and digest_len.
    * g10/gpg.c: Include "tofu.h".
    (cmd_and_opt_values): Declare aTOFUPolicy, oTOFUDefaultPolicy,
    oTOFUDBFormat.
    (opts): Add them.
    (parse_trust_model): Recognize the tofu and tofu+pgp trust models.
    (parse_tofu_policy): New function.
    (parse_tofu_db_format): New function.
    (main): Initialize opt.tofu_default_policy and opt.tofu_db_format.
    Handle aTOFUPolicy, oTOFUDefaultPolicy and oTOFUDBFormat.
    * g10/mainproc.c (do_check_sig): If the signature is good, copy the
    hash to SIG->DIGEST and set SIG->DIGEST_LEN appropriately.
    * g10/trustdb.h (get_validity): Add arguments sig and may_ask.  Update
    callers.
    (tdb_get_validity_core): Add arguments sig and may_ask.  Update
    callers.
    * g10/trust.c (get_validity) Add arguments sig and may_ask.  Pass them
    to tdb_get_validity_core.
    * g10/trustdb.c: Include "tofu.h".
    (trust_model_string): Handle TM_TOFU and TM_TOFU_PGP.
    (tdb_get_validity_core): Add arguments sig and may_ask.  If
    OPT.TRUST_MODEL is TM_TOFU or TM_TOFU_PGP, compute the TOFU trust
    level.  Combine it with the computed PGP trust level, if appropriate.
    * g10/keyedit.c: Include "tofu.h".
    (show_key_with_all_names_colon): If the trust mode is tofu or
    tofu+pgp, then show the trust policy.
    * g10/keylist.c: Include "tofu.h".
    (public_key_list): Also show the PGP stats if the trust model is
    TM_TOFU_PGP.
    (list_keyblock_colon): If the trust mode is tofu or
    tofu+pgp, then show the trust policy.
    * g10/pkclist.c: Include "tofu.h".
    * g10/gpgv.c (get_validity): Add arguments sig and may_ask.
    (enum tofu_policy): Define.
    (tofu_get_policy): New stub.
    (tofu_policy_str): Likewise.
    * g10/test-stubs.c (get_validity): Add arguments sig and may_ask.
    (enum tofu_policy): Define.
    (tofu_get_policy): New stub.
    (tofu_policy_str): Likewise.
    * doc/DETAILS: Describe the TOFU Policy field.
    * doc/gpg.texi: Document --tofu-set-policy, --trust-model=tofu,
    --trust-model=tofu+pgp, --tofu-default-policy and --tofu-db-format.
    * tests/openpgp/Makefile.am (TESTS): Add tofu.test.
    (TEST_FILES): Add tofu-keys.asc, tofu-keys-secret.asc,
    tofu-2183839A-1.txt, tofu-BC15C85A-1.txt and tofu-EE37CF96-1.txt.
    (CLEANFILES): Add tofu.db.
    (clean-local): Add tofu.d.
    * tests/openpgp/tofu.test: New file.
    * tests/openpgp/tofu-2183839A-1.txt: New file.
    * tests/openpgp/tofu-BC15C85A-1.txt: New file.
    * tests/openpgp/tofu-EE37CF96-1.txt: New file.
    * tests/openpgp/tofu-keys.asc: New file.
    * tests/openpgp/tofu-keys-secret.asc: New file.
    
    --
    Signed-off-by: Neal H. Walfield <neal at g10code.com>.
---
 configure.ac                       |    6 +
 doc/DETAILS                        |    4 +
 doc/gnupg.texi                     |    3 +-
 doc/gpg.texi                       |   91 +-
 g10/Makefile.am                    |    7 +-
 g10/gpg.c                          |  140 ++
 g10/gpgv.c                         |   28 +-
 g10/keyedit.c                      |   14 +-
 g10/keylist.c                      |   15 +-
 g10/mainproc.c                     |   30 +-
 g10/options.h                      |   10 +-
 g10/packet.h                       |    5 +
 g10/pkclist.c                      |   11 +-
 g10/test-stubs.c                   |   28 +-
 g10/tofu.c                         | 2472 ++++++++++++++++++++++++++++++++++++
 g10/tofu.h                         |  105 ++
 g10/trust.c                        |   11 +-
 g10/trustdb.c                      |  180 ++-
 g10/trustdb.h                      |    6 +-
 tests/openpgp/Makefile.am          |   11 +-
 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            |  245 ++++
 26 files changed, 3505 insertions(+), 77 deletions(-)

diff --git a/configure.ac b/configure.ac
index 289df2a..ddbc065 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/doc/gnupg.texi b/doc/gnupg.texi
index 1fddeb0..42d9dc0 100644
--- a/doc/gnupg.texi
+++ b/doc/gnupg.texi
@@ -35,7 +35,8 @@ Published by The GnuPG Project@*
 @end iftex
 
 @copyright{} 2002, 2004, 2005, 2006, 2007, 2010 Free Software Foundation, Inc.@*
- at copyright{} 2013, 2014, 2015 Werner Koch.
+ at copyright{} 2013, 2014, 2015 Werner Koch.@*
+ at copyright{} 2015 g10code Gmbh.
 
 @quotation
 Permission is granted to copy, distribute and/or modify this document
diff --git a/doc/gpg.texi b/doc/gpg.texi
index 35291a8..a702040 100644
--- a/doc/gpg.texi
+++ b/doc/gpg.texi
@@ -525,6 +525,12 @@ Use the source, Luke :-). The output format is still subject to change.
 Pack or unpack an arbitrary input into/from an OpenPGP ASCII armor.
 This is a GnuPG extension to OpenPGP and in general not very useful.
 
+ at item --tofu-set-policy @code{auto|good|unknown|bad|ask}  @code{key...}
+ at opindex tofu-set-policy
+Set the TOFU policy for all the bindings associated with the specified
+keys.  For more information about the meaning of the policies,
+ at pxref{trust-model-tofu}.  The keys may be specified either by their
+fingerprint (preferred) or their keyid.
 
 @c @item --server
 @c @opindex server
@@ -1408,7 +1414,7 @@ don't want to keep your secret keys (or one of them)
 online but still want to be able to check the validity of a given
 recipient's or signator's key.
 
- at item --trust-model @code{pgp|classic|direct|always|auto}
+ at item --trust-model @code{pgp|classic|tofu|tofu+pgp|direct|always|auto}
 @opindex trust-model
 Set what trust model GnuPG should follow. The models are:
 
@@ -1424,6 +1430,65 @@ Set what trust model GnuPG should follow. The models are:
   @opindex trust-mode:classic
   This is the standard Web of Trust as introduced by PGP 2.
 
+  @item tofu
+  @opindex trust-mode:tofu
+  @anchor{trust-model-tofu}
+  TOFU stands for Trust On First Use.  In this trust model, the first
+  time a key is seen, it is memorized.  If later another key is seen
+  with a user id with the same email address, a warning is displayed
+  indicating that there is a conflict and that the key might be a
+  forgery and an attempt at a man-in-the-middle attack.
+
+  Because a potential attacker is able to control the email address
+  and thereby circumvent the conflict detection algorithm by using an
+  email address that is similar in appearance to a trusted email
+  address, whenever a message is verified, statistics about the number
+  of messages signed with the key are shown.  In this way, a user can
+  easily identify attacks using fake keys for regular correspondents.
+
+  When compared with the Web of Trust, TOFU offers significantly
+  weaker security guarantees.  In particular, TOFU only helps ensure
+  consistency (that is, that the binding between a key and email
+  address doesn't change).  A major advantage of TOFU is that it
+  requires little maintenance to use correctly.  To use the web of
+  trust properly, you need to actively sign keys and mark users as
+  trusted introducers.  This is a time-consuming process and anecdotal
+  evidence suggests that even security-conscious users rarely take the
+  time to do this thoroughly and instead rely on an ad-hoc TOFU
+  process.
+
+  In the TOFU model, policies are associated with bindings between
+  keys and email addresses (which are extracted from user ids and
+  normalized).  There are five policies, which can be set manually
+  using the @option{--tofu-policy} option.  The default policy can be
+  set using the @option{--tofu-default-policy} policy.
+
+  The TOFU policies are: @code{auto}, @code{good}, @code{unknown},
+  @code{bad} and @code{ask}.  The @code{auto} policy is used by
+  default (unless overridden by @option{--tofu-default-policy}) and
+  marks a binding as marginally trusted.  The @code{good},
+  @code{unknown} and @code{bad} policies mark a binding as fully
+  trusted, as having unknown trust or as having trust never,
+  respectively.  The @code{unknown} policy is useful for just using
+  TOFU to detect conflicts, but to never assign positive trust to a
+  binding.  The final policy, @code{ask} prompts the user to indicate
+  the binding's trust.  If batch mode is enabled (or input is
+  inappropriate in the context), then the user is not prompted and the
+  @code{undefined} trust level is returned.
+
+  @item tofu+pgp
+  @opindex trust-mode:tofu+pgp
+  This trust model combines TOFU with the Web of Trust.  This is done
+  by computing the trust level for each model and then taking the
+  maximum trust level where the trust levels are ordered as follows:
+  @code{unknown < undefined < marginal < fully < ultimate < expired <
+  never}.
+
+  By setting @option{--tofu-default-policy=unknown}, this model can be
+  used to implement the web of trust with TOFU's conflict detection
+  algorithm, but without its assignment of positive trust values,
+  which some security-conscious users don't like.
+
   @item direct
   @opindex trust-mode:direct
   Key validity is set directly by the user and not calculated via the
@@ -1625,6 +1690,30 @@ key signer (defaults to 1).
 Number of marginally trusted users to introduce a new
 key signer (defaults to 3)
 
+ at item --tofu-default-policy @code{auto|good|unknown|bad|ask}
+ at opindex tofu-default-policy
+The default TOFU policy (defaults to @code{auto}).  For more
+information about the meaning of this option, @xref{trust-model-tofu}.
+
+ at item --tofu-db-format @code{auto|split|flat}
+ at opindex tofu-default-policy
+The format for the TOFU DB.
+
+The split file format splits the data across many DBs under the
+ at code{tofu.d} directory (one per email address and one per key).  This
+makes it easier to automatically synchronize the data using a tool
+such as Unison (@url{https://www.cis.upenn.edu/~bcpierce/unison/}),
+since the individual files change rarely.
+
+The flat file format keeps all of the data in the single file
+ at code{tofu.db}.  This format results in better performance.
+
+If set to auto (which is the default), GnuPG will first check for the
+existence of @code{tofu.d} and @code{tofu.db}.  If one of these
+exists, the corresponding format is used.  If neither or both of these
+exist, then GnuPG defaults to the @code{split} format.  In the latter
+case, a warning is emitted.
+
 @item --max-cert-depth @code{n}
 @opindex max-cert-depth
 Maximum depth of a certification chain (default is 5).
diff --git a/g10/Makefile.am b/g10/Makefile.am
index cd12183..7357843 100644
--- a/g10/Makefile.am
+++ b/g10/Makefile.am
@@ -26,7 +26,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)
@@ -126,7 +126,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)  \
@@ -141,7 +142,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 39cc2e5..ada913c 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,
@@ -385,6 +387,8 @@ enum cmd_and_opt_values
     oNoAutostart,
     oPrintPKARecords,
     oPrintDANERecords,
+    oTOFUDefaultPolicy,
+    oTOFUDBFormat,
 
     oNoop
   };
@@ -475,6 +479,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 ")),
 
@@ -670,6 +676,8 @@ 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 (oTOFUDBFormat, "tofu-db-format", "@"),
   ARGPARSE_s_s (oSetFilename, "set-filename", "@"),
   ARGPARSE_s_n (oForYourEyesOnly, "for-your-eyes-only", "@"),
   ARGPARSE_s_n (oNoForYourEyesOnly, "no-for-your-eyes-only", "@"),
@@ -1939,6 +1947,10 @@ 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,"auto")==0)
     opt.trust_model=TM_AUTO;
   else
@@ -1946,6 +1958,41 @@ 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_POLICY_AUTO;
+  else if (ascii_strcasecmp (policy, "good") == 0)
+    return TOFU_POLICY_GOOD;
+  else if (ascii_strcasecmp (policy, "unknown") == 0)
+    return TOFU_POLICY_UNKNOWN;
+  else if (ascii_strcasecmp (policy, "bad") == 0)
+    return TOFU_POLICY_BAD;
+  else if (ascii_strcasecmp (policy, "ask") == 0)
+    return TOFU_POLICY_ASK;
+  else
+    {
+      log_error (_("unknown TOFU policy '%s'\n"), policy);
+      g10_exit (1);
+    }
+}
+
+static int
+parse_tofu_db_format (const char *db_format)
+{
+  if (ascii_strcasecmp (db_format, "auto") == 0)
+    return TOFU_DB_AUTO;
+  else if (ascii_strcasecmp (db_format, "split") == 0)
+    return TOFU_DB_SPLIT;
+  else if (ascii_strcasecmp (db_format, "flat") == 0)
+    return TOFU_DB_FLAT;
+  else
+    {
+      log_error (_("unknown TOFU DB format '%s'\n"), db_format);
+      g10_exit (1);
+    }
+}
 
 /* This fucntion called to initialized a new control object.  It is
    assumed that this object has been zeroed out before calling this
@@ -2150,6 +2197,8 @@ main (int argc, char **argv)
 #else
     opt.trust_model = TM_AUTO;
 #endif
+    opt.tofu_default_policy = TOFU_POLICY_AUTO;
+    opt.tofu_db_format = TOFU_DB_AUTO;
     opt.mangle_dos_filenames = 0;
     opt.min_cert_level = 2;
     set_screen_dimensions ();
@@ -2372,6 +2421,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;
@@ -2553,6 +2606,12 @@ 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 oTOFUDBFormat:
+	    opt.tofu_db_format = parse_tofu_db_format (pargs.r.ret_str);
+	    break;
 
 	  case oForceOwnertrust:
 	    log_info(_("Note: %s is not for normal use!\n"),
@@ -4351,6 +4410,87 @@ 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_error (_("Failed to open the keyring DB.\n"));
+	      g10_exit (1);
+	    }
+
+	  for (i = 1; i < argc; i ++)
+	    {
+	      KEYDB_SEARCH_DESC desc;
+	      kbnode_t kb;
+
+	      rc = classify_user_id (argv[i], &desc, 0);
+	      if (rc)
+		{
+		  log_error (_("Failed to parse '%s'.\n"), argv[i]);
+		  g10_exit (1);
+		}
+
+	      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_error (_("'%s' does not appear to be a valid"
+			       " key id, fingerprint or key grip.\n"),
+			     argv[i]);
+		  g10_exit (1);
+		}
+
+	      rc = keydb_search_reset (hd);
+	      if (rc)
+		{
+		  log_error (_("Failed to reset keyring handle.\n"));
+		  g10_exit (1);
+		}
+
+	      rc = keydb_search (hd, &desc, 1, NULL);
+	      if (gpg_err_code (rc) == GPG_ERR_NO_PUBKEY)
+		{
+		  log_error (_("Key '%s' is not available\n"), argv[i]);
+		  g10_exit (1);
+		}
+	      else if (rc)
+		{
+		  log_error (_("Failed to find key '%s'\n"), argv[i]);
+		  g10_exit (1);
+		}
+
+	      rc = keydb_get_keyblock (hd, &kb);
+	      if (rc)
+		{
+		  log_error (_("Failed to read key '%s' from the keyring\n"),
+			     argv[i]);
+		  g10_exit (1);
+		}
+
+	      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 8bb3fc4..0807622 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;
 }
 
@@ -606,3 +609,26 @@ export_pubkey_buffer (ctrl_t ctrl, const char *keyspec, unsigned int options,
   *r_datalen = 0;
   return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
 }
+
+enum tofu_policy
+  {
+    tofu_policy
+  };
+
+gpg_error_t
+tofu_get_policy (PKT_public_key *pk, PKT_user_id *user_id,
+		 enum tofu_policy *policy)
+{
+  (void)pk;
+  (void)user_id;
+  (void)policy;
+  return gpg_error (GPG_ERR_GENERAL);
+}
+
+const char *
+tofu_policy_str (enum tofu_policy policy)
+{
+  (void)policy;
+
+  return "unknown";
+}
diff --git a/g10/keyedit.c b/g10/keyedit.c
index 4803f9e..432ba86 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);
@@ -2927,6 +2928,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_policy policy;
+	      if (! tofu_get_policy (primary, uid, &policy)
+		  && policy != TOFU_POLICY_NONE)
+		es_fprintf (fp, "%s", tofu_policy_str (policy));
+	    }
+	  es_putc (':', fp);
 	  es_putc ('\n', fp);
 	}
     }
@@ -3042,7 +3051,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;
@@ -5334,7 +5344,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 3814f1c..1541697 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 (ctrl_t, int, int);
@@ -99,7 +100,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");
@@ -1067,7 +1069,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
@@ -1438,6 +1440,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);
@@ -1495,6 +1498,14 @@ 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_policy policy;
+	      if (! tofu_get_policy (pk, uid, &policy)
+		  && policy != TOFU_POLICY_NONE)
+		es_fprintf (es_stdout, "%s", tofu_policy_str (policy));
+	    }
 	  es_putc (':', es_stdout);
 	  es_putc ('\n', es_stdout);
 	}
diff --git a/g10/mainproc.c b/g10/mainproc.c
index 9f02b15..af50987 100644
--- a/g10/mainproc.c
+++ b/g10/mainproc.c
@@ -851,6 +851,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);
@@ -926,8 +927,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);
@@ -1848,9 +1862,10 @@ 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.  (Note: we register
+	     the signature later.)  */
+          valid = get_validity (pk, un->pkt->pkt.user_id, NULL, 0);
 
           keyid_str[17] = 0; /* cut off the "[uncertain]" part */
 
@@ -1939,8 +1954,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 d57ab5d..2135aa0 100644
--- a/g10/options.h
+++ b/g10/options.h
@@ -118,8 +118,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_AUTO=0, TOFU_DB_SPLIT, 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 f3155fd..dba6034 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;
 }
 
@@ -425,3 +428,26 @@ export_pubkey_buffer (ctrl_t ctrl, const char *keyspec, unsigned int options,
   *r_datalen = 0;
   return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
 }
+
+enum tofu_policy
+  {
+    tofu_policy
+  };
+
+gpg_error_t
+tofu_get_policy (PKT_public_key *pk, PKT_user_id *user_id,
+		 enum tofu_policy *policy)
+{
+  (void)pk;
+  (void)user_id;
+  (void)policy;
+  return gpg_error (GPG_ERR_GENERAL);
+}
+
+const char *
+tofu_policy_str (enum tofu_policy policy)
+{
+  (void)policy;
+
+  return "unknown";
+}
diff --git a/g10/tofu.c b/g10/tofu.c
new file mode 100644
index 0000000..39377cb
--- /dev/null
+++ b/g10/tofu.c
@@ -0,0 +1,2472 @@
+/* tofu.c - TOFU trust model.
+ * Copyright (C) 2015 g10 Code GmbH
+ *
+ * This file is part of GnuPG.
+ *
+ * GnuPG is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GnuPG is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+/* TODO:
+
+   - Format the fingerprints nicely when printing (similar to gpg
+     --list-keys)
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <sys/stat.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"
+
+/* The TOFU data can be saved in two different formats: either in a
+   single combined database (opt.tofu_db_format == TOFU_DB_FLAT) or in
+   a split file format (opt.tofu_db_format == TOFU_DB_SPLIT).  In the
+   split format, there is one database per normalized email address
+   (DB_EMAIL) and one per key (DB_KEY).  */
+enum db_type
+  {
+    DB_COMBINED,
+    DB_EMAIL,
+    DB_KEY
+  };
+
+/* A list of open DBs.
+
+   In the flat format, this consists of a single element with the type
+   DB_COMBINED and whose name is the empty string.
+
+   In the split format, the first element is a dummy element (DB is
+   NULL) whose type is DB_COMBINED and whose name is the empty string.
+   Any following elements describe either DB_EMAIL or DB_KEY DBs.  In
+   theis case, NAME is either the normalized email address or the
+   fingerprint.
+
+   To initialize this data structure, call opendbs().  When you are
+   done, clean it up using closedbs().  To get a handle to a database,
+   use the getdb() function.  This will either return an existing
+   handle or open a new DB connection, as appropriate.  */
+struct db
+{
+  struct db *next;
+
+  enum db_type type;
+
+  sqlite3 *db;
+
+  /* If TYPE is DB_COMBINED, this is "".  Otherwise, it is either the
+     fingerprint (type == DB_KEY) or the normalized email address
+     (type == DB_EMAIL).  */
+  char name[1];
+};
+
+const char *
+tofu_policy_str (enum tofu_policy policy)
+{
+  switch (policy)
+    {
+    case TOFU_POLICY_NONE: return "none";
+    case TOFU_POLICY_AUTO: return "auto";
+    case TOFU_POLICY_GOOD: return "good";
+    case TOFU_POLICY_UNKNOWN: return "unknown";
+    case TOFU_POLICY_BAD: return "bad";
+    case TOFU_POLICY_ASK: return "ask";
+    default: return "???";
+    }
+}
+
+/* Convert a binding policy (e.g., TOFU_POLICY_BAD) to a trust level
+   (e.g., TRUST_BAD) in light of the current configuration.  */
+int
+tofu_policy_to_trust_level (enum tofu_policy policy)
+{
+  if (policy == TOFU_POLICY_AUTO)
+    /* If POLICY is AUTO, fallback to OPT.TOFU_DEFAULT_POLICY.  */
+    policy = opt.tofu_default_policy;
+
+  switch (policy)
+    {
+    case TOFU_POLICY_AUTO:
+      /* If POLICY and OPT.TOFU_DEFAULT_POLICY are both AUTO, default
+	 to marginal trust.  */
+      return TRUST_MARGINAL;
+    case TOFU_POLICY_GOOD:
+      return TRUST_FULLY;
+    case TOFU_POLICY_UNKNOWN:
+      return TRUST_UNKNOWN;
+    case TOFU_POLICY_BAD:
+      return TRUST_NEVER;
+    case TOFU_POLICY_ASK:
+      return TRUST_UNKNOWN;
+    default:
+      log_bug ("Bad value for trust policy: %d\n",
+	       opt.tofu_default_policy);
+      return 0;
+    }
+}
+
+/* This is a convenience function that combines sqlite3_mprintf and
+   sqlite3_exec.  */
+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;
+}
+
+
+/* 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 || ! (strcmp (tail, ".0") == 0 || *tail == '\0'))
+    /* Abort.  */
+    return 1;
+  return 0;
+}
+
+/* We expect a single integer column whose name is "version".  COOKIE
+   must point to an int.  This function always aborts.  On error or a
+   if the version is bad, sets *VERSION to -1.  */
+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;
+      return 1;
+    }
+
+  if (strcmp (argv[0], "1") == 0)
+    *version = 1;
+  else
+    {
+      log_error (_("unsupported TOFU DB version: %s\n"), argv[0]);
+      *version = -1;
+    }
+
+  /* Don't run again.  */
+  return 1;
+}
+
+
+/* If the DB is new, initialize it.  Otherwise, check the DB's
+   version.
+
+   Return 0 if the database is okay and 1 otherwise.  */
+static int
+initdb (sqlite3 *db, enum db_type type)
+{
+  char *err = NULL;
+  int rc;
+  unsigned long int count;
+  int version = -1;
+
+  /* If the DB has no tables, then assume this is a new DB that needs
+     to be initialized.  */
+  rc = sqlite3_exec (db,
+		     "select count(*) from sqlite_master where type='table';",
+		     get_single_unsigned_long_cb, &count, &err);
+  if (rc)
+    {
+      log_error (_("error querying TOFU DB's available tables: %s\n"),
+		 err);
+      sqlite3_free (err);
+      return 1;
+    }
+  else if (count != 0)
+    /* Assume that the DB is already initialized.  Make sure the
+       version is okay.  */
+    {
+      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 0;
+	}
+      else if (rc == SQLITE_ABORT && version == -1)
+	/* Unsupported version.  */
+	{
+	  /* An error message was already displayed.  */
+	  sqlite3_free (err);
+	  return 1;
+	}
+      else if (rc)
+	/* Some error.  */
+	{
+	  log_error (_("error determining TOFU DB's version: %s\n"), err);
+	  sqlite3_free (err);
+	  return 1;
+	}
+      else
+	/* Unexpected success.  This can only happen if there are no
+	   rows.  */
+	{
+	  log_error (_("error determining TOFU DB's version: %s\n"),
+		     "select returned 0, but expected ABORT");
+	  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 1;
+    }
+
+  /* Create the version table.  */
+  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;
+    }
+
+  /* Initialize the version table, which contains a single integer
+     value.  */
+  rc = sqlite3_exec (db,
+		     "insert into version values (1);",
+		     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_POLICY_BAD, etc. above).
+
+       CONFLICT is either NULL or a fingerprint.  Assume that we have
+         a binding <0xdeadbeef, foo at example.com> and then we observe
+         <0xbaddecaf, foo at example.com>.  There two bindings conflict
+         (they have the same email address).  When we observe the
+         latter binding, we warn the user about the conflict and ask
+         for a policy decision about the new binding.  We also change
+         the old binding's policy to ask if it was auto.  So that we
+         know why this occured, we also set conflict to 0xbaddecaf.
+  */
+  if (type == DB_EMAIL || type == DB_COMBINED)
+    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,\n"
+       "  policy BOOLEAN CHECK (policy in (%d, %d, %d, %d, %d)),\n"
+       "  conflict STRING,\n"
+       "  unique (fingerprint, email));\n"
+       "create index bindings_fingerprint_email\n"
+       " on bindings (fingerprint, email);\n"
+       "create index bindings_email on bindings (email);\n",
+       TOFU_POLICY_AUTO, TOFU_POLICY_GOOD, TOFU_POLICY_UNKNOWN,
+       TOFU_POLICY_BAD, TOFU_POLICY_ASK);
+  else
+    /* In the split DB case, the fingerprint DB only contains a subset
+       of the fields.  This reduces the amount of duplicated data.
+
+       Note: since the data is split on the email address, there is no
+       need to index the email column.  */
+    rc = sqlite3_exec_printf
+      (db, NULL, NULL, &err,
+       "create table bindings\n"
+       " (oid INTEGER PRIMARY KEY AUTOINCREMENT,\n"
+       "  fingerprint TEXT, email TEXT, user_id,\n"
+       "  unique (fingerprint, email));\n"
+       "create index bindings_fingerprint\n"
+       " on bindings (fingerprint);\n");
+  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 (i.e., this is a foreign key that
+         references bindings.oid).
+
+	 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 (_("error aborting transaction on TOFU DB: %s\n"),
+		     err);
+	  sqlite3_free (err);
+	}
+      return 1;
+    }
+  else
+    {
+      rc = sqlite3_exec (db, "commit transaction;", NULL, NULL, &err);
+      if (rc)
+	{
+	  log_error (_("error committing transaction on TOFU DB: %s\n"),
+		     err);
+	  sqlite3_free (err);
+	  return 1;
+	}
+      return 0;
+    }
+}
+
+static sqlite3 *combined_db;
+
+/* Open and initialize a low-level TOFU database.  Returns NULL on
+   failure.  This function should not normally be directly called to
+   get a database handle.  Instead, use getdb().  */
+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);
+
+      if (combined_db)
+	return combined_db;
+
+      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 DB ('%s'): %s\n"),
+		 filename, sqlite3_errmsg (db));
+      /* Even if an error occurs, DB is guaranteed to be valid.  */
+      sqlite3_close (db);
+      db = NULL;
+    }
+
+  if (filename_free)
+    xfree (filename);
+
+  if (db && initdb (db, type))
+    {
+      sqlite3_close (db);
+      db = NULL;
+    }
+
+  if (opt.tofu_db_format == TOFU_DB_FLAT)
+    combined_db = db;
+
+  return db;
+}
+
+/* Return a database handle.  <type, name> describes the required
+   database.  If there is a cached handle in DBS, that handle is
+   returned.  Otherwise, the database is opened and cached in DBS.
+
+   NAME is the name of the DB and may not be NULL.
+
+   TYPE must be either DB_MAIL or DB_KEY.  In the combined format, the
+   combined DB is always returned.  */
+static sqlite3 *
+getdb (struct db *dbs, const char *name, enum db_type type)
+{
+  struct db *t = NULL;
+  sqlite3 *sqlitedb = NULL;
+  char *name_sanitized = NULL;
+  char *filename = NULL;
+  int i;
+
+  assert (name);
+  assert (type == DB_EMAIL || type == DB_KEY);
+
+  assert (dbs);
+  /* The first entry is always for the combined DB.  */
+  assert (dbs->type == DB_COMBINED);
+  assert (! dbs->name[0]);
+
+  if (opt.tofu_db_format == TOFU_DB_FLAT)
+    /* When using the flat format, we only have a single combined
+       DB.  */
+    {
+      assert (dbs->db);
+      assert (! dbs->next);
+      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.  */
+
+  /* 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] = '_';
+    }
+
+  /* See if the DB is cached.  */
+  for (t = dbs->next; t; t = t->next)
+    if (type == t->type && strcmp (t->name, name_sanitized) == 0)
+      goto out;
+
+  /* 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 (gnupg_mkdir_p (opt.homedir, "tofu.d", type_str, prefix, NULL) != 0)
+      {
+	log_error (_("unable to create directory %s/%s/%s/%s"),
+		   opt.homedir, "tofu.d", type_str, prefix);
+	g10_exit (1);
+      }
+
+    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);
+
+  /* Insert it immediately after the first element.  */
+  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_AUTO)
+    {
+      char *filename = make_filename (opt.homedir, "tofu.db", NULL);
+      struct stat s;
+      int have_tofu_db = 0;
+      int have_tofu_d = 0;
+
+      if (stat (filename, &s) == 0)
+	{
+	  have_tofu_db = 1;
+	  if (DBG_TRUST)
+	    log_debug ("%s exists.\n", filename);
+	}
+      else
+	{
+	  if (DBG_TRUST)
+	    log_debug ("%s does not exist.\n", filename);
+	}
+
+      /* We now have tofu.d.  */
+      filename[strlen (filename) - 1] = '\0';
+      if (stat (filename, &s) == 0)
+	{
+	  have_tofu_d = 1;
+	  if (DBG_TRUST)
+	    log_debug ("%s exists.\n", filename);
+	}
+      else
+	{
+	  if (DBG_TRUST)
+	    log_debug ("%s does not exist.\n", filename);
+	}
+
+      xfree (filename);
+
+      if (have_tofu_db && have_tofu_d)
+	{
+	  log_info (_("Warning: Home directory contains both tofu.db and tofu.d.  Using split format for TOFU DB.\n"));
+	  opt.tofu_db_format = TOFU_DB_SPLIT;
+	}
+      else if (have_tofu_db)
+	{
+	  opt.tofu_db_format = TOFU_DB_FLAT;
+	  if (DBG_TRUST)
+	    log_debug ("Using flat format for TOFU DB.\n");
+	}
+      else if (have_tofu_d)
+	{
+	  opt.tofu_db_format = TOFU_DB_SPLIT;
+	  if (DBG_TRUST)
+	    log_debug ("Using split format for TOFU DB.\n");
+	}
+      else
+	{
+	  opt.tofu_db_format = TOFU_DB_SPLIT;
+	  if (DBG_TRUST)
+	    log_debug ("Using split format for TOFU DB.\n");
+	}
+    }
+
+  if (opt.tofu_db_format == TOFU_DB_FLAT)
+    {
+      db = opendb (NULL, DB_COMBINED);
+      if (! db)
+	return NULL;
+    }
+  else
+    /* Create a dummy entry so that we have a handle.  */
+    ;
+
+  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 *db;
+  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);
+      assert (dbs->db == combined_db);
+    }
+  else
+    /* In the split format, the combined record is just a place holder
+       so that we have a stable handle.  */
+    assert (! dbs->db);
+
+  for (db = dbs; db; db = n)
+    {
+      n = db->next;
+
+      if (combined_db && db->db == combined_db)
+	{
+	  assert (opt.tofu_db_format == TOFU_DB_FLAT);
+	  assert (dbs == db);
+	  assert (db->type == DB_COMBINED);
+	  assert (! db->name[0]);
+	}
+      else if (db->db)
+	/* Not the dummy entry.  */
+	{
+	  if (dbs == db)
+	    /* The first entry.  */
+	    {
+	      assert (opt.tofu_db_format == TOFU_DB_FLAT);
+	      assert (db->type == DB_COMBINED);
+	      assert (! db->name[0]);
+	    }
+	  else
+	    /* Not the first entry.  */
+	    {
+	      assert (opt.tofu_db_format == TOFU_DB_SPLIT);
+	      assert (db->type != DB_COMBINED);
+	      assert (db->name[0]);
+	    }
+
+	  sqlite3_close (db->db);
+	}
+      else
+	/* The dummy entry.  */
+	{
+	  assert (opt.tofu_db_format == TOFU_DB_SPLIT);
+	  assert (dbs == db);
+	  assert (db->type == DB_COMBINED);
+	  assert (! db->name[0]);
+	}
+
+      xfree (db);
+    }
+}
+
+
+/* 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 || ! (strcmp (tail, ".0") == 0 || *tail == '\0'))
+    /* Abort.  */
+    return 1;
+  return 0;
+}
+
+
+/* Record (or update) a trust policy about a (possibly new)
+   binding.
+
+   If SHOW_OLD is set, the binding's old policy is displayed.  */
+static gpg_error_t
+record_binding (struct db *dbs, const char *fingerprint, const char *email,
+		const char *user_id, enum tofu_policy policy, int show_old)
+{
+  sqlite3 *db_email = NULL, *db_key = NULL;
+  int rc;
+  char *err = NULL;
+  enum tofu_policy policy_old = TOFU_POLICY_NONE;
+
+  if (! (policy == TOFU_POLICY_AUTO
+	 || policy == TOFU_POLICY_GOOD
+	 || policy == TOFU_POLICY_UNKNOWN
+	 || policy == TOFU_POLICY_BAD
+	 || policy == TOFU_POLICY_ASK))
+    log_bug ("%s: Bad value for policy (%d)!\n", __func__, policy);
+
+  db_email = getdb (dbs, email, DB_EMAIL);
+  if (! db_email)
+    return gpg_error (GPG_ERR_GENERAL);
+
+  if (opt.tofu_db_format == TOFU_DB_SPLIT)
+    /* In the split format, we need to update two DBs.  To keep them
+       consistent, we start a transaction on each.  Note: this is the
+       only place where we start two transaction and we always start
+       transaction on the DB_KEY DB first, thus deadlock is not
+       possible.  */
+    {
+      db_key = getdb (dbs, fingerprint, DB_KEY);
+      if (! db_key)
+	return gpg_error (GPG_ERR_GENERAL);
+
+      rc = sqlite3_exec (db_email, "begin transaction;", NULL, NULL, &err);
+      if (rc)
+	{
+	  log_error (_("error beginning transaction on TOFU %s database: %s\n"),
+		     "email", err);
+	  sqlite3_free (err);
+	  return gpg_error (GPG_ERR_GENERAL);
+	}
+
+      rc = sqlite3_exec (db_key, "begin transaction;", NULL, NULL, &err);
+      if (rc)
+	{
+	  log_error (_("error beginning transaction on TOFU %s database: %s\n"),
+		     "key", err);
+	  sqlite3_free (err);
+	  goto out_revert_one;
+	}
+    }
+
+  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_email, 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 (DBG_TRUST)
+    {
+      if (policy_old != TOFU_POLICY_NONE)
+	log_debug ("Changing TOFU trust policy for binding <%s, %s>"
+		   " from %s to %s.\n",
+		   fingerprint, email,
+		   tofu_policy_str (policy_old),
+		   tofu_policy_str (policy));
+      else
+	log_debug ("Set TOFU trust policy for binding <%s, %s> to %s.\n",
+		   fingerprint, email,
+		   tofu_policy_str (policy));
+    }
+
+  if (policy_old == policy)
+    /* Nothing to do.  */
+    goto out;
+
+  rc = sqlite3_exec_printf
+    (db_email, 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 (_("error updating TOFU binding database"
+		   " (inserting <%s, %s> = %s): %s\n"),
+		 fingerprint, email, tofu_policy_str (policy),
+		 err);
+      sqlite3_free (err);
+      goto out;
+    }
+
+  if (db_key)
+    /* We also need to update the key DB.  */
+    {
+      assert (opt.tofu_db_format == TOFU_DB_SPLIT);
+
+      rc = sqlite3_exec_printf
+	(db_key, NULL, NULL, &err,
+	 "insert or replace into bindings\n"
+	 " (oid, fingerprint, email, user_id)\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);",
+	 fingerprint, email, fingerprint, email, user_id);
+      if (rc)
+	{
+	  log_error (_("error updating TOFU binding database"
+		       " (inserting <%s, %s>): %s\n"),
+		     fingerprint, email, err);
+	  sqlite3_free (err);
+	  goto out;
+	}
+    }
+  else
+    assert (opt.tofu_db_format == TOFU_DB_FLAT);
+
+ out:
+  if (opt.tofu_db_format == TOFU_DB_SPLIT)
+    /* We only need a transaction for the split format.  */
+    {
+      int rc2;
+
+      rc2 = sqlite3_exec_printf (db_key, 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_email, NULL, NULL, &err,
+				 rc ? "rollback;" : "end transaction;");
+      if (rc2)
+	{
+	  log_error (_("error ending transaction on TOFU database: %s\n"),
+		     err);
+	  sqlite3_free (err);
+	}
+    }
+
+  if (rc)
+    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 policy for this binding.  */
+  enum tofu_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;
+    }
+}
+
+static void
+signature_stats_prepend (struct signature_stats **statsp,
+			 const char *fingerprint,
+			 enum tofu_policy policy,
+			 long time_ago,
+			 unsigned long count)
+{
+  struct signature_stats *stats =
+    xmalloc (sizeof (*stats) + strlen (fingerprint));
+
+  stats->next = *statsp;
+  *statsp = stats;
+
+  strcpy (stats->fingerprint, fingerprint);
+  stats->policy = policy;
+  stats->time_ago = time_ago;
+  stats->count = count;
+}
+
+
+/* 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;
+  char *tail;
+  int i = 0;
+  enum tofu_policy policy;
+  long time_ago;
+  unsigned long count;
+
+  (void) azColName;
+
+  i ++;
+
+  tail = NULL;
+  errno = 0;
+  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;
+  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;
+  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);
+
+  signature_stats_prepend (statsp, argv[0], policy, time_ago, count);
+
+  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 0
+#  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 100
+
+/* Return the policy for the binding <FINGERPRINT, EMAIL> (email has
+   already been normalized) and any conflict information in *CONFLICT
+   if CONFLICT is not NULL.  Returns GET_POLICY_ERROR if an error
+   occurs.  */
+static enum tofu_policy
+get_policy (struct db *dbs, const char *fingerprint, const char *email,
+	    char **conflict)
+{
+  sqlite3 *db;
+  int rc;
+  char *err = NULL;
+  strlist_t strlist = NULL;
+  char *tail = NULL;
+  enum tofu_policy policy = GET_POLICY_ERROR;
+
+  assert (GET_POLICY_ERROR != TOFU_POLICY_NONE
+	  && GET_POLICY_ERROR != TOFU_POLICY_AUTO
+	  && GET_POLICY_ERROR != TOFU_POLICY_GOOD
+	  && GET_POLICY_ERROR != TOFU_POLICY_UNKNOWN
+	  && GET_POLICY_ERROR != TOFU_POLICY_BAD
+	  && GET_POLICY_ERROR != TOFU_POLICY_ASK);
+
+  db = getdb (dbs, email, DB_EMAIL);
+  if (! db)
+    return GET_POLICY_ERROR;
+
+  /* Check if the <FINGERPRINT, EMAIL> binding is known
+     (TOFU_POLICY_NONE cannot appear in the DB.  Thus, if POLICY is
+     still TOFU_POLICY_NONE after executing the query, then the
+     result set was empty.)  */
+  rc = sqlite3_exec_printf
+    (db, strings_collect_cb, &strlist, &err,
+     "select policy, conflict from bindings\n"
+     " where fingerprint = %Q and email = %Q",
+     fingerprint, email);
+  if (rc)
+    {
+      log_error (_("error reading from TOFU database"
+		   " (checking for existing bad bindings): %s\n"),
+		 err);
+      sqlite3_free (err);
+      goto out;
+    }
+
+  if (strlist_length (strlist) == 0)
+    /* No results.  */
+    {
+      policy = TOFU_POLICY_NONE;
+      goto out;
+    }
+  else if (strlist_length (strlist) != 2)
+    /* The result has the wrong form.  */
+    {
+      log_error (_("error reading from TOFU database"
+		   " (checking for existing bad bindings):"
+		   " expected 2 results, got %d\n"),
+		 strlist_length (strlist));
+      goto out;
+    }
+
+  /* The result has the right form.  */
+
+  errno = 0;
+  policy = strtol (strlist->d, &tail, 0);
+  if (errno || *tail != '\0')
+    {
+      log_error (_("error reading from TOFU database: bad value for policy: %s\n"),
+		 strlist->d);
+      goto out;
+    }
+
+  if (! (policy == TOFU_POLICY_AUTO
+	 || policy == TOFU_POLICY_GOOD
+	 || policy == TOFU_POLICY_UNKNOWN
+	 || policy == TOFU_POLICY_BAD
+	 || policy == TOFU_POLICY_ASK))
+    {
+      log_error (_("TOFU DB is corrupted.  Invalid value for policy (%d).\n"),
+		 policy);
+      policy = GET_POLICY_ERROR;
+      goto out;
+    }
+
+
+  /* If CONFLICT is set, then policy should be TOFU_POLICY_ASK.  But,
+     just in case, we do the check again here and ignore the conflict
+     is POLICY is not TOFU_POLICY_ASK.  */
+  if (conflict)
+    {
+      if (policy == TOFU_POLICY_ASK && *strlist->next->d)
+	*conflict = xstrdup (strlist->next->d);
+      else
+	*conflict = NULL;
+    }
+
+ out:
+  assert (policy == GET_POLICY_ERROR
+	  || policy == TOFU_POLICY_NONE
+	  || policy == TOFU_POLICY_AUTO
+	  || policy == TOFU_POLICY_GOOD
+	  || policy == TOFU_POLICY_UNKNOWN
+	  || policy == TOFU_POLICY_BAD
+	  || policy == TOFU_POLICY_ASK);
+
+  free_strlist (strlist);
+
+  return policy;
+}
+
+#define GET_TRUST_ERROR 100
+
+/* Return the trust level (TRUST_NEVER, etc.) for the binding
+   <FINGERPRINT, EMAIL> (email is already normalized).  If no policy
+   is registered, returns TOFU_POLICY_NONE.  If an error occurs,
+   returns GET_TRUST_ERROR.
+
+   USER_ID is the unadultered user id.
+
+   If MAY_ASK is set, then we may interact with the user.  This is
+   necessary if there is a conflict or the binding's policy is
+   TOFU_POLICY_ASK.  In the case of a conflict, we set the new
+   conflicting binding's policy to TOFU_POLICY_ASK.  In either case,
+   we return TRUST_UNDEFINED.  */
+static enum tofu_policy
+get_trust (struct db *dbs, const char *fingerprint, const char *email,
+	   const char *user_id, int may_ask)
+{
+  sqlite3 *db;
+  enum tofu_policy policy;
+  char *conflict = NULL;
+  int rc;
+  char *err = NULL;
+  strlist_t bindings_with_this_email = NULL;
+  int bindings_with_this_email_count;
+  int change_conflicting_to_ask = 0;
+  int trust_level = TRUST_UNKNOWN;
+
+  if (opt.batch)
+    may_ask = 0;
+
+  /* Make sure GET_TRUST_ERROR isn't equal to any of the trust
+     levels.  */
+  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, &conflict);
+  if (policy == TOFU_POLICY_AUTO)
+    {
+      policy = opt.tofu_default_policy;
+      if (DBG_TRUST)
+	log_debug ("TOFU: binding <%s, %s>'s policy is auto (default: %s).\n",
+		   fingerprint, email,
+		   tofu_policy_str (opt.tofu_default_policy));
+    }
+  switch (policy)
+    {
+    case TOFU_POLICY_AUTO:
+    case TOFU_POLICY_GOOD:
+    case TOFU_POLICY_UNKNOWN:
+    case TOFU_POLICY_BAD:
+      /* The saved judgement is auto -> auto, good, unknown or bad.
+	 We don't need to ask the user anything.  */
+      if (DBG_TRUST)
+	log_debug ("TOFU: Known binding <%s, %s>'s policy: %s\n",
+		   fingerprint, email, tofu_policy_str (policy));
+      trust_level = tofu_policy_to_trust_level (policy);
+      goto out;
+
+    case TOFU_POLICY_ASK:
+      /* We need to ask the user what to do.  Case #1 or #2 below.  */
+      if (! may_ask)
+	{
+	  trust_level = TRUST_UNDEFINED;
+	  goto out;
+	}
+
+      break;
+
+    case TOFU_POLICY_NONE:
+      /* The binding is new, we need to check for conflicts.  Case #3
+	 below.  */
+      break;
+
+    case GET_POLICY_ERROR:
+      trust_level = GET_TRUST_ERROR;
+      goto out;
+
+    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 policy is ask
+          (get_policy() == TOFU_POLICY_AUTO
+           && opt.tofu_default_policy == TOFU_POLICY_ASK)
+
+       2. The saved policy is ask (either last time the user selected
+          accept once or reject once or there was a conflict and this
+          binding's policy was changed from auto to ask)
+	  (policy == TOFU_POLICY_ASK), or,
+
+       3. We don't have a saved policy (policy == TOFU_POLICY_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, &bindings_with_this_email, &err,
+     "select distinct fingerprint from bindings where email = %Q;",
+     email);
+  if (rc)
+    {
+      log_error (_("error reading from TOFU database"
+		   " (listing fingerprints): %s\n"),
+		 err);
+      sqlite3_free (err);
+      goto out;
+    }
+
+  bindings_with_this_email_count = strlist_length (bindings_with_this_email);
+  if (bindings_with_this_email_count == 0
+      && opt.tofu_default_policy != TOFU_POLICY_ASK)
+    /* New binding with no conflict and a concrete default policy.
+
+       We've never observed a binding with this email address
+       (BINDINGS_WITH_THIS_EMAIL_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_POLICY_NONE.  */
+      assert (policy == TOFU_POLICY_NONE);
+
+      if (DBG_TRUST)
+	log_debug ("TOFU: New binding <%s, %s>, no conflict.\n",
+		   email, fingerprint);
+
+      if (record_binding (dbs, fingerprint, email, user_id,
+			  TOFU_POLICY_AUTO, 0) != 0)
+	{
+	  log_error (_("error setting TOFU binding's trust level to %s\n"),
+		       "auto");
+	  trust_level = GET_TRUST_ERROR;
+	  goto out;
+	}
+
+      trust_level = tofu_policy_to_trust_level (TOFU_POLICY_AUTO);
+      goto out;
+    }
+
+  if (policy == TOFU_POLICY_NONE)
+    /* This is a new binding and we have a conflict.  Mark any
+       conflicting bindings that have an automatic policy as now
+       requiring confirmation.  Note: we delay this until after we ask
+       for confirmation so that when the current policy is printed, it
+       is correct.  */
+    change_conflicting_to_ask = 1;
+
+  if (! may_ask)
+    /* We can only get here in the third case (no saved policy) and if
+       there is a conflict.  (If the policy was ask (cases #1 and #2)
+       and we weren't allowed to ask, we'd have already exited).  */
+    {
+      assert (policy == TOFU_POLICY_NONE);
+
+      if (record_binding (dbs, fingerprint, email, user_id,
+			  TOFU_POLICY_ASK, 0) != 0)
+	log_error (_("error setting TOFU binding's trust level to %s\n"),
+		   "ask");
+
+      trust_level = TRUST_UNDEFINED;
+      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_POLICY_NONE && bindings_with_this_email_count > 0),
+
+       - This is a new binding and opt.tofu_default_policy is set to
+         ask.  (policy == TOFU_POLICY_NONE && opt.tofu_default_policy ==
+         TOFU_POLICY_ASK), or,
+
+       - The policy is ask (the user deferred last time) (policy ==
+         TOFU_POLICY_ASK).
+   */
+  {
+    int is_conflict =
+      ((policy == TOFU_POLICY_NONE && bindings_with_this_email_count > 0)
+       || (policy == TOFU_POLICY_ASK && conflict));
+    estream_t fp;
+    char *binding;
+    int binding_shown;
+    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");
+
+    binding = xasprintf ("<%s, %s>", fingerprint, email);
+    binding_shown = 0;
+
+    if (policy == TOFU_POLICY_NONE)
+      {
+	es_fprintf (fp, _("The binding %s is NOT known.  "), binding);
+	binding_shown = 1;
+      }
+    else if (policy == TOFU_POLICY_ASK && conflict)
+      {
+	es_fprintf (fp,
+		    _("%s raised a conflict with this binding.  Since this"
+		      " binding's policy was 'auto', it was changed to 'ask'.  "),
+		    binding);
+	binding_shown = 1;
+      }
+    es_fprintf (fp,
+		_("Please indicate whether you believe the binding %s%s"
+		  "is legitimate (the key belongs to the stated owner) "
+		  "or a forgery (bad).\n\n"),
+		binding_shown ? "" : binding,
+		binding_shown ? "" : " ");
+
+    xfree (binding);
+
+    /* Find other user ids associated with this key and whether the
+       bindings are marked as good or bad.  */
+    {
+      sqlite3 *db_key;
+
+      if (opt.tofu_db_format == TOFU_DB_SPLIT)
+	/* In the split format, we need to search in the fingerprint
+	   DB for all the emails associated with this key, not the
+	   email DB.  */
+	db_key = getdb (dbs, fingerprint, DB_KEY);
+      else
+	db_key = db;
+
+      if (db_key)
+	{
+	  rc = sqlite3_exec_printf
+	    (db_key, strings_collect_cb, &other_user_ids, &err,
+	     "select user_id, %s from bindings where fingerprint = %Q;",
+	     opt.tofu_db_format == TOFU_DB_SPLIT ? "email" : "policy",
+	     fingerprint);
+	  if (rc)
+	    {
+	      log_error (_("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_thing;
+	    enum tofu_policy other_policy;
+
+	    assert (strlist_iter->next);
+	    strlist_iter = strlist_iter->next;
+	    other_thing = strlist_iter->d;
+
+	    if (opt.tofu_db_format == TOFU_DB_SPLIT)
+	      other_policy = get_policy (dbs, fingerprint, other_thing, NULL);
+	    else
+	      other_policy = atoi (other_thing);
+
+	    es_fprintf (fp, _("  %s (policy: %s)\n"),
+			other_user_id,
+			tofu_policy_str (other_policy));
+	  }
+	es_fprintf (fp, "\n");
+
+	free_strlist (other_user_ids);
+      }
+
+    /* Find other keys associated with this email address.  */
+    /* XXX: When generating the statistics, do we want the time
+       embedded in the signature (column 'sig_time') or the time that
+       we first verified the signature (column 'time').  */
+    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"
+       /* Make sure the current key is first.  */
+       " 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 (_("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, bindings_with_this_email_count);
+	for (strlist_iter = bindings_with_this_email;
+	     strlist_iter;
+	     strlist_iter = strlist_iter->next)
+	  es_fprintf (fp, _("  %s\n"), strlist_iter->d);
+      }
+    else
+      {
+	char *key = NULL;
+
+	if (! stats || strcmp (stats->fingerprint, fingerprint) != 0)
+	  /* If we have already added this key to the DB, then it will
+	     be first (see the above select).  Since the first key on
+	     the list is not this key, we must not yet have verified
+	     any messages signed by this key.  Add a dummy entry.  */
+	  signature_stats_prepend (&stats, fingerprint, TOFU_POLICY_AUTO, 0, 0);
+
+	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;
+		if (this_key)
+		  es_fprintf (fp, _("  %s (this key):"), key);
+		else
+		  es_fprintf (fp, _("  %s (policy: %s):"),
+			      key, tofu_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 if (stats_iter->count == 0)
+	      es_fprintf (fp, _("    0 signed messages.\n"));
+	    else
+	      es_fprintf (fp, _("    %ld %s signed over the past %ld %s.\n"),
+			  stats_iter->count,
+			  stats_iter->count == 1
+			  ? _("message") : _("messages"),
+			  time_ago_scale (stats_iter->time_ago),
+			  time_ago_unit (stats_iter->time_ago));
+	  }
+      }
+
+    if (is_conflict)
+      {
+	/* TRANSLATORS: translate the below text.  We don't directly
+	   internationalize that text so that we can tweak it without
+	   breaking translations.  */
+	char *text = _("TOFU detected a binding conflict");
+	if (strcmp (text, "TOFU detected a binding conflict") == 0)
+	  /* No translation.  Use the English text.  */
+	  text =
+	    "Normally, there is only a single key associated with an email"
+	    "address.  However, people sometimes generate a new key if"
+	    "their key is too old or they think it might be compromised."
+	    "Alternatively, a new key may indicate a man-in-the-middle attack!"
+	    "Before accepting this key, you should talk to or call the person"
+	    "to make sure this new key is legitimate.";
+	es_fprintf (fp, "\n%s\n", text);
+      }
+
+    es_fputc ('\n', fp);
+    /* TRANSLATORS: Two letters (normally the lower and upper case
+       version of the hotkey) for each of the five choices.  If there
+       is only one choice in your language, repeat it.  */
+    choices = _("gG" "aA" "uU" "rR" "bB");
+    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_POLICY_GOOD;
+		    trust_level = tofu_policy_to_trust_level (policy);
+		    break;
+		  case 1: /* Accept once.  */
+		    policy = TOFU_POLICY_ASK;
+		    trust_level =
+		      tofu_policy_to_trust_level (TOFU_POLICY_GOOD);
+		    break;
+		  case 2: /* Unknown.  */
+		    policy = TOFU_POLICY_UNKNOWN;
+		    trust_level = tofu_policy_to_trust_level (policy);
+		    break;
+		  case 3: /* Reject once.  */
+		    policy = TOFU_POLICY_ASK;
+		    trust_level =
+		      tofu_policy_to_trust_level (TOFU_POLICY_BAD);
+		    break;
+		  case 4: /* Bad.  */
+		    policy = TOFU_POLICY_BAD;
+		    trust_level = tofu_policy_to_trust_level (policy);
+		    break;
+		  default:
+		    log_bug ("c should be between 0 and 4 but it is %d!", c);
+		  }
+
+		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;
+	      }
+	  }
+	xfree (response);
+      }
+
+    xfree (prompt);
+
+    signature_stats_free (stats);
+  }
+
+ out:
+  if (change_conflicting_to_ask)
+    {
+      rc = sqlite3_exec_printf
+	(db, NULL, NULL, &err,
+	 "update bindings set policy = %d, conflict = %Q"
+	 " where email = %Q and fingerprint != %Q and policy = %d;",
+	 TOFU_POLICY_ASK, fingerprint, email, fingerprint, TOFU_POLICY_AUTO);
+      if (rc)
+	{
+	  log_error (_("error changing TOFU policy: %s\n"), err);
+	  sqlite3_free (err);
+	  goto out;
+	}
+    }
+
+  xfree (conflict);
+  free_strlist (bindings_with_this_email);
+
+  return trust_level;
+}
+
+static void
+show_statistics (struct db *dbs, const char *fingerprint,
+		 const char *email, const char *user_id,
+		 const char *sig_exclude)
+{
+  sqlite3 *db;
+  int rc;
+  strlist_t strlist = NULL;
+  char *err = NULL;
+
+  db = getdb (dbs, email, DB_EMAIL);
+  if (! db)
+    return;
+
+  rc = sqlite3_exec_printf
+    (db, strings_collect_cb, &strlist, &err,
+     "select count (*), strftime('%%s','now') - min (signatures.time)\n"
+     " from signatures\n"
+     " left join bindings on signatures.binding = bindings.oid\n"
+     " where fingerprint = %Q and email = %Q and sig_digest %s%s%s;",
+     fingerprint, email,
+     /* We want either: sig_digest != 'SIG_EXCLUDE' or sig_digest is
+	not NULL.  */
+     sig_exclude ? "!= '" : "is not NULL",
+     sig_exclude ? sig_exclude : "",
+     sig_exclude ? "'" : "");
+  if (rc)
+    {
+      log_error (_("error reading from TOFU database"
+		   " (getting statistics): %s\n"),
+		 err);
+      sqlite3_free (err);
+      goto out;
+    }
+
+  if (! strlist)
+    log_info (_("Have never verified a message signed by key %s!\n"),
+	      fingerprint);
+  else
+    {
+      char *tail = NULL;
+      signed long messages;
+      signed long first_seen_ago;
+
+      assert (strlist_length (strlist) == 2);
+
+      errno = 0;
+      messages = strtol (strlist->d, &tail, 0);
+      if (errno || *tail != '\0')
+	/* Abort.  */
+	{
+	  log_debug ("%s:%d: Couldn't convert %s (messages) to an int: %s.\n",
+		     __func__, __LINE__, strlist->d, strerror (errno));
+	  messages = -1;
+	}
+
+      if (messages == 0 && *strlist->next->d == '\0')
+	/* min(NULL) => NULL => "".  */
+	first_seen_ago = -1;
+      else
+	{
+	  errno = 0;
+	  first_seen_ago = strtol (strlist->next->d, &tail, 0);
+	  if (errno || *tail != '\0')
+	    /* Abort.  */
+	    {
+	      log_debug ("%s:%d: Cound't convert %s (first_seen) to an int: %s.\n",
+			 __func__, __LINE__,
+			 strlist->next->d, strerror (errno));
+	      first_seen_ago = 0;
+	    }
+	}
+
+      if (messages == -1 || first_seen_ago == 0)
+	log_info (_("Failed to collect signature statistics for \"%s\" (key %s)\n"),
+		  user_id, fingerprint);
+      else
+	{
+	  enum tofu_policy policy = get_policy (dbs, fingerprint, email, NULL);
+	  estream_t fp;
+	  char *msg;
+
+	  fp = es_fopenmem (0, "rw,samethread");
+	  if (! fp)
+	    log_fatal ("error creating memory stream\n");
+
+	  if (messages == 0)
+	    es_fprintf (fp,
+			_("Verified 0 messages signed by \"%s\""
+			  " (key: %s, policy %s)."),
+			user_id, fingerprint, tofu_policy_str (policy));
+	  else
+	    {
+	      int years = 0;
+	      int months = 0;
+	      int days = 0;
+	      int hours = 0;
+	      int minutes = 0;
+	      int seconds = 0;
+
+	      /* The number of units that we've printed so far.  */
+	      int count = 0;
+	      /* The first unit that we printed (year = 0, month = 1,
+		 etc.).  */
+	      int first = -1;
+	      /* The current unit.  */
+	      int i = 0;
+
+	      es_fprintf (fp,
+			  _("Verified %ld messages signed by \"%s\""
+			    " (key: %s, policy: %s) in the past "),
+			  messages, user_id,
+			  fingerprint, tofu_policy_str (policy));
+
+	      /* It would be nice to use a macro to do this, but gettext
+		 works on the unpreprocessed code.  */
+#define MIN_SECS (60)
+#define HOUR_SECS (60 * MIN_SECS)
+#define DAY_SECS (24 * HOUR_SECS)
+#define MONTH_SECS (30 * DAY_SECS)
+#define YEAR_SECS (365 * DAY_SECS)
+
+	      if (first_seen_ago > YEAR_SECS)
+		{
+		  years = first_seen_ago / YEAR_SECS;
+		  first_seen_ago -= years * YEAR_SECS;
+		}
+	      if (first_seen_ago > MONTH_SECS)
+		{
+		  months = first_seen_ago / MONTH_SECS;
+		  first_seen_ago -= months * MONTH_SECS;
+		}
+	      if (first_seen_ago > DAY_SECS)
+		{
+		  days = first_seen_ago / DAY_SECS;
+		  first_seen_ago -= days * DAY_SECS;
+		}
+	      if (first_seen_ago > HOUR_SECS)
+		{
+		  hours = first_seen_ago / HOUR_SECS;
+		  first_seen_ago -= hours * HOUR_SECS;
+		}
+	      if (first_seen_ago > MIN_SECS)
+		{
+		  minutes = first_seen_ago / MIN_SECS;
+		  first_seen_ago -= minutes * MIN_SECS;
+		}
+	      seconds = first_seen_ago;
+
+	      if (years)
+		{
+		  if (years > 1)
+		    es_fprintf (fp, _("%d years"), years);
+		  else
+		    es_fprintf (fp, _("%d year"), years);
+		  count ++;
+		  first = i;
+		}
+	      i ++;
+	      if ((first == -1 || i - first <= 3) && months)
+		{
+		  if (count)
+		    es_fprintf (fp, _(", "));
+
+		  if (months > 1)
+		    es_fprintf (fp, _("%d months"), months);
+		  else
+		    es_fprintf (fp, _("%d month"), months);
+		  count ++;
+		  first = i;
+		}
+	      i ++;
+	      if ((first == -1 || i - first <= 3) && count < 2 && days)
+		{
+		  if (count)
+		    es_fprintf (fp, _(", "));
+
+		  if (days > 1)
+		    es_fprintf (fp, _("%d days"), days);
+		  else
+		    es_fprintf (fp, _("%d day"), days);
+		  count ++;
+		  first = i;
+		}
+	      i ++;
+	      if ((first == -1 || i - first <= 3) && count < 2 && hours)
+		{
+		  if (count)
+		    es_fprintf (fp, _(", "));
+
+		  if (hours > 1)
+		    es_fprintf (fp, _("%d hours"), hours);
+		  else
+		    es_fprintf (fp, _("%d hour"), hours);
+		  count ++;
+		  first = i;
+		}
+	      i ++;
+	      if ((first == -1 || i - first <= 3) && count < 2 && minutes)
+		{
+		  if (count)
+		    es_fprintf (fp, _(", "));
+
+		  if (minutes > 1)
+		    es_fprintf (fp, _("%d minutes"), minutes);
+		  else
+		    es_fprintf (fp, _("%d minute"), minutes);
+		  count ++;
+		  first = i;
+		}
+	      i ++;
+	      if ((first == -1 || i - first <= 3) && count < 2)
+		{
+		  if (count)
+		    es_fprintf (fp, _(", "));
+
+		  if (seconds > 1)
+		    es_fprintf (fp, _("%d seconds"), seconds);
+		  else
+		    es_fprintf (fp, _("%d second"), seconds);
+		}
+
+	      es_fprintf (fp, _("."));
+	    }
+
+	  es_fputc (0, fp);
+	  if (es_fclose_snatch (fp, (void **) &msg, NULL))
+	    log_fatal ("error snatching memory stream\n");
+
+	  log_info ("%s\n", msg);
+
+	  if (policy == TOFU_POLICY_AUTO && messages < 10)
+	    {
+	      char *set_policy_command;
+	      const char *text;
+
+	      if (messages == 0)
+		log_info (_("Warning: we've have yet to see a message signed by this key!\n"));
+	      else if (messages == 1)
+		log_info (_("Warning: we've only seen a single message signed by this key!\n"));
+
+	      set_policy_command =
+		xasprintf ("gpg --tofu-policy bad \"%s\"", fingerprint);
+	      /* TRANSLATORS: translate the below text.  We don't
+		 directly internationalize that text so that we can
+		 tweak it without breaking translations.  */
+	      text = _("TOFU: few signatures %s");
+	      if (strcmp (text, "TOFU: few signatures %s") == 0)
+		text =
+		  "Warning: if this value is unexpectedly low, this might "
+		  "indicate that this key is a forgery!  Carefully examine "
+		  "the email address for small variations (e.g., additional "
+		  "white space).  If the key is suspect, then use '%s' to "
+		  "mark the key as being bad.\n";
+	      log_info (text, set_policy_command);
+	      free (set_policy_command);
+	    }
+	}
+    }
+
+ out:
+  free_strlist (strlist);
+
+  return;
+}
+
+/* Extract the email address from a user id and normalize it.  If the
+   user id doesn't contain an email address, then we use the whole
+   user_id and normalize that.  The returned string must be freed.  */
+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;
+}
+
+/* Pretty print a MAX_FINGERPRINT_LEN-byte binary fingerprint into a
+   malloc'd string.  */
+static char *
+fingerprint_pp (const byte *fingerprint_bin)
+{
+  char fingerprint[MAX_FINGERPRINT_LEN * 2 + 1];
+  char *fingerprint_pretty;
+  int space = (/* The characters and the NUL.  */
+	       sizeof (fingerprint)
+	       /* After every fourth character, we add a space (except
+		  the last).  */
+	       + (sizeof (fingerprint) - 1) / 4 - 1
+	       /* Half way through we add a second space.  */
+	       + 1);
+  int i;
+  int j;
+
+  bin2hex (fingerprint_bin, MAX_FINGERPRINT_LEN, fingerprint);
+
+  fingerprint_pretty = xmalloc (space);
+
+  for (i = 0, j = 0; i < MAX_FINGERPRINT_LEN * 2; i ++)
+    {
+      if (i && i % 4 == 0)
+	fingerprint_pretty[j ++] = ' ';
+      if (i == MAX_FINGERPRINT_LEN * 2 / 2)
+	fingerprint_pretty[j ++] = ' ';
+
+      fingerprint_pretty[j ++] = fingerprint[i];
+    }
+  fingerprint_pretty[j ++] = 0;
+  assert (j == space);
+
+  return fingerprint_pretty;
+}
+
+/* Register the signature with the binding <FINGERPRINT_BIN, USER_ID>.
+   FINGERPRINT must be MAX_FINGERPRINT_LEN bytes long.
+
+   SIG_DIGEST_BIN is the binary representation of the message's
+   digest.  SIG_DIGEST_BIN_LEN is its length.
+
+   SIG_TIME is the time that the signature was generated.
+
+   ORIGIN is a free-formed string describing the origin of the
+   signature.  If this was from an email and the Claws MUA was used,
+   then this should be something like: "email:claws".  If this is
+   NULL, the default is simply "unknown".
+
+   If MAY_ASK is 1, then this function may interact with the user.
+   This is necessary if there is a conflict or the binding's policy is
+   TOFU_POLICY_ASK.
+
+   This function returns the binding's trust level on return.  If an
+   error occurs, this function returns TRUST_UNKNOWN.  */
+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 = NULL;
+  char *email = NULL;
+  char *err = NULL;
+  int rc;
+  int trust_level = TRUST_UNKNOWN;
+  char *sig_digest;
+  unsigned long c;
+  int already_verified = 0;
+
+  dbs = opendbs ();
+  if (! dbs)
+    {
+      log_error (_("error opening TOFU DB.\n"));
+      goto die;
+    }
+
+  fingerprint = fingerprint_pp (fingerprint_bin);
+
+  if (! *user_id)
+    {
+      log_debug ("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";
+
+  /* It's necessary to get the trust so that we are certain that the
+     binding has been registered.  */
+  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_error (_("error opening TOFU 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)
+    {
+      already_verified = 1;
+      if (DBG_TRUST)
+	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.  */
+    {
+      if (DBG_TRUST)
+	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 (_("error updating TOFU 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:
+  if (may_ask)
+    /* It's only appropriate to show the statistics in an interactive
+       context.  */
+    show_statistics (dbs, fingerprint, email, user_id,
+		     already_verified ? NULL : sig_digest);
+
+  xfree (email);
+  xfree (fingerprint);
+  if (dbs)
+    closedbs (dbs);
+
+  return trust_level;
+}
+
+/* Combine a trust level returned from the TOFU trust model with a
+   trust level returned by the PGP trust model.  This is primarily of
+   interest when the trust model is tofu+pgp (TM_TOFU_PGP).
+
+   This function ors together the upper bits (the values not covered
+   by TRUST_MASK, i.e., TRUST_FLAG_REVOKED, etc.).  */
+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;
+}
+
+/* Return the validity (TRUST_NEVER, etc.) of the binding
+   <FINGERPRINT, USER_ID>.
+
+   FINGERPRINT must be a MAX_FINGERPRINT_LEN-byte fingerprint.
+
+   If MAY_ASK is 1 and the policy is TOFU_POLICY_ASK, then the user
+   will be prompted to choose a different policy.  If MAY_ASK is 0 and
+   the policy is TOFU_POLICY_ASK, then TRUST_UNKNOWN is returned.
+
+   Returns TRUST_UNDEFINED if an error occurs.  */
+int
+tofu_get_validity (const byte *fingerprint_bin, const char *user_id,
+		   int may_ask)
+{
+  struct db *dbs;
+  char *fingerprint = NULL;
+  char *email = NULL;
+  int trust_level = TRUST_UNDEFINED;
+
+  dbs = opendbs ();
+  if (! dbs)
+    {
+      log_error (_("error opening TOFU DB.\n"));
+      goto die;
+    }
+
+  fingerprint = fingerprint_pp (fingerprint_bin);
+
+  if (! *user_id)
+    {
+      log_debug ("user id is empty.  Can't get TOFU validity for this binding.\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_UNDEFINED;
+
+  if (may_ask)
+    show_statistics (dbs, fingerprint, email, user_id, NULL);
+
+ die:
+  xfree (email);
+  xfree (fingerprint);
+  if (dbs)
+    closedbs (dbs);
+
+  return trust_level;
+}
+
+/* Set the policy for all non-revoked user ids in the keyblock KB to
+   POLICY.
+
+   If no key is available with the specified key id, then this
+   function returns GPG_ERR_NO_PUBKEY.
+
+   Returns 0 on success and an error code otherwise.  */
+gpg_error_t
+tofu_set_policy (kbnode_t kb, enum tofu_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 = NULL;
+
+  assert (kb->pkt->pkttype == PKT_PUBLIC_KEY);
+  pk = kb->pkt->pkt.public_key;
+
+  dbs = opendbs ();
+  if (! dbs)
+    {
+      log_error (_("error opening TOFU DB.\n"));
+      return gpg_error (GPG_ERR_GENERAL);
+    }
+
+  if (DBG_TRUST)
+    log_debug ("Setting TOFU policy for %s to %s\n",
+	       keystr (pk->keyid), tofu_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));
+
+  fingerprint = fingerprint_pp (fingerprint_bin);
+
+  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);
+    }
+
+  xfree (fingerprint);
+  closedbs (dbs);
+
+  return 0;
+}
+
+/* Set the TOFU policy for all non-revoked user ids in the KEY with
+   the key id KEYID to POLICY.
+
+   If no key is available with the specified key id, then this
+   function returns GPG_ERR_NO_PUBKEY.
+
+   Returns 0 on success and an error code otherwise.  */
+gpg_error_t
+tofu_set_policy_by_keyid (u32 *keyid, enum tofu_policy policy)
+{
+  kbnode_t keyblock = get_pubkeyblock (keyid);
+  if (! keyblock)
+    return gpg_error (GPG_ERR_NO_PUBKEY);
+
+  return tofu_set_policy (keyblock, policy);
+}
+
+/* Return the TOFU policy for the specified binding in *POLICY.  If no
+   policy has been set for the binding, sets *POLICY to
+   TOFU_POLICY_NONE.
+
+   PK is a primary public key and USER_ID is a user id.
+
+   Returns 0 on success and an error code otherwise.  */
+gpg_error_t
+tofu_get_policy (PKT_public_key *pk, PKT_user_id *user_id,
+		 enum tofu_policy *policy)
+{
+  struct db *dbs;
+  char fingerprint_bin[MAX_FINGERPRINT_LEN];
+  size_t fingerprint_bin_len = sizeof (fingerprint_bin);
+  char *fingerprint;
+  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_error (_("error opening TOFU DB.\n"));
+      return gpg_error (GPG_ERR_GENERAL);
+    }
+
+  fingerprint_from_pk (pk, fingerprint_bin, &fingerprint_bin_len);
+  assert (fingerprint_bin_len == sizeof (fingerprint_bin));
+
+  fingerprint = fingerprint_pp (fingerprint_bin);
+
+  email = email_from_user_id (user_id->name);
+
+  *policy = get_policy (dbs, fingerprint, email, NULL);
+
+  xfree (email);
+  xfree (fingerprint);
+  closedbs (dbs);
+
+  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..7516684
--- /dev/null
+++ b/g10/tofu.h
@@ -0,0 +1,105 @@
+/* tofu.h - TOFU trust model.
+ * Copyright (C) 2015 g10 Code GmbH
+ *
+ * This file is part of GnuPG.
+ *
+ * GnuPG is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GnuPG is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef G10_TOFU_H
+#define G10_TOFU_H
+
+#include <config.h>
+
+/* For each binding, we have a trust policy.  */
+enum tofu_policy
+  {
+    /* This value can be returned by tofu_get_policy to indicate that
+       there is no policy set for the specified binding.  */
+    TOFU_POLICY_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_POLICY_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_POLICY_AUTO = 1,
+
+    /* The user explicitly marked the binding as good.  In this case,
+       we return TRUST_FULLY.  */
+    TOFU_POLICY_GOOD = 2,
+
+    /* The user explicitly marked the binding as unknown.  In this
+       case, we return TRUST_UNKNOWN.  */
+    TOFU_POLICY_UNKNOWN = 3,
+
+    /* The user explicitly marked the binding as bad.  In this case,
+       we always return TRUST_NEVER.  */
+    TOFU_POLICY_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_POLICY_ASK = 5
+  };
+
+/* Return a string representation of a trust policy.  Returns "???" if
+   POLICY is not valid.  */
+const char *tofu_policy_str (enum tofu_policy policy);
+
+/* Convert a binding policy (e.g., TOFU_POLICY_BAD) to a trust level
+   (e.g., TRUST_BAD) in light of the current configuration.  */
+int tofu_policy_to_trust_level (enum tofu_policy policy);
+
+/* Register the binding <FINGERPRINT, USER_ID> and the signature
+   described by SIGS_DIGEST and SIG_TIME, which it generated.  Origin
+   describes where the signed data came from, e.g., "email:claws"
+   (default: "unknown").  If MAY_ASK is 1, then this function may
+   interact with the user in the case of a conflict or if the
+   binding's policy is ask.  This function returns the binding's trust
+   level.  If an error occurs, it returns TRUST_UNKNOWN.  */
+int tofu_register (const byte *fingerprint, const char *user_id,
+		   const byte *sigs_digest, int sigs_digest_len,
+		   time_t sig_time, const char *origin, int may_ask);
+
+/* Combine a trust level returned from the TOFU trust model with a
+   trust level returned by the PGP trust model.  This is primarily of
+   interest when the trust model is tofu+pgp (TM_TOFU_PGP).  */
+int tofu_wot_trust_combine (int tofu, int wot);
+
+/* Determine the validity (TRUST_NEVER, etc.) of the binding
+   <FINGERPRINT, USER_ID>.  If MAY_ASK is 1, then this function may
+   interact with the user.  If not, TRUST_UNKNOWN is returned.  If an
+   error occurs, TRUST_UNDEFINED is returned.  */
+int tofu_get_validity (const byte *fingerprint, const char *user_id,
+		       int may_ask);
+
+/* Set the policy for all non-revoked user ids in the keyblock KB to
+   POLICY.  */
+gpg_error_t tofu_set_policy (kbnode_t kb, enum tofu_policy policy);
+
+/* Set the TOFU policy for all non-revoked users in the key with the
+   key id KEYID to POLICY.  */
+gpg_error_t tofu_set_policy_by_keyid (u32 *keyid, enum tofu_policy policy);
+
+/* Return the TOFU policy for the specified binding in *POLICY.  */
+gpg_error_t tofu_get_policy (PKT_public_key *pk, PKT_user_id *user_id,
+			     enum tofu_policy *policy);
+
+#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..170c041 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 (main_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 (main_pk->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..2c3f865 100644
--- a/g10/trustdb.h
+++ b/g10/trustdb.h
@@ -86,7 +86,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 +121,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_ask);
 
 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 95bb92c..f82fc1d 100644
--- a/tests/openpgp/Makefile.am
+++ b/tests/openpgp/Makefile.am
@@ -38,7 +38,8 @@ TESTS = version.test mds.test \
 	armdetachm.test detachm.test genkey1024.test \
 	conventional.test conventional-mdc.test \
 	multisig.test verify.test armor.test \
-	import.test ecc.test 4gb-packet.test finish.test
+	import.test ecc.test 4gb-packet.test tofu.test \
+	finish.test
 
 
 TEST_FILES = pubring.asc secring.asc plain-1o.asc plain-2o.asc plain-3o.asc \
@@ -46,7 +47,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
 
@@ -95,10 +98,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..18c1756
--- /dev/null
+++ b/tests/openpgp/tofu.test
@@ -0,0 +1,245 @@
+#!/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
+
+format=auto
+
+debug()
+{
+    echo "$@" >&2
+}
+
+debug_exec()
+{
+    debug "Running GNUPGHOME=$GNUPGHOME $@"
+    ${@:+"$@"}
+}
+
+# $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
+
+    policy=$(debug_exec $GPG --tofu-db-format=$format --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()
+{
+    debug
+    debug "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
+
+    trust=$(debug_exec $GPG --tofu-db-format=$format --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()
+{
+    debug
+    debug "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()
+{
+    debug
+    debug "setpolicy($@)"
+
+    keyid=$1
+    shift
+    policy=$1
+    shift
+
+    debug_exec $GPG --tofu-db-format=$format \
+	 --trust-model=tofu ${@:+"$@"} --tofu-policy $policy $keyid
+}
+
+for format in split flat
+do
+    debug
+    debug "Testing with db format $format"
+
+    # 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
+
+    # Verify a message.  There should be no conflict and the trust policy
+    # should be set to auto.
+    debug_exec $GPG --tofu-db-format=$format --trust-model=tofu \
+	 --verify $srcdir/tofu-2183839A-1.txt
+
+    checkpolicy 2183839A auto
+
+    trust=$(gettrust 2183839A)
+    debug "default trust = $trust"
+    if test "x$trust" != xm
+    then
+	echo "Wrong default trust.  Got: \`$trust', expected \`m'"
+	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.
+    debug_exec $GPG --tofu-db-format=$format --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
+    debug_exec $GPG --tofu-db-format=$format --trust-model=tofu \
+	 --verify $srcdir/tofu-EE37CF96-1.txt
+    checkpolicy BC15C85A ask
+    checkpolicy 2183839A bad
+    checkpolicy EE37CF96 ask
+done
+
+exit 0

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-gnupg/gnupg2.git



More information about the Pkg-gnupg-commit mailing list