[git] GnuPG - branch, neal/next, updated. gnupg-2.1.8-43-ge806e4d
by Neal H. Walfield
cvs at cvs.gnupg.org
Tue Sep 22 23:39:42 CEST 2015
This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "The GNU Privacy Guard".
The branch, neal/next has been updated
discards d47cb081f1504ebb7581a8f84d1caf3cf961c968 (commit)
via e806e4dc85a7f5b3d7928853037280b84246c2d0 (commit)
This update added new revisions after undoing existing revisions. That is
to say, the old revision is not a strict subset of the new revision. This
situation occurs when you --force push a change and generate a repository
containing something like this:
* -- * -- B -- O -- O -- O (d47cb081f1504ebb7581a8f84d1caf3cf961c968)
\
N -- N -- N (e806e4dc85a7f5b3d7928853037280b84246c2d0)
When this happens we assume that you've already had alert emails for all
of the O revisions, and so we here report only the revisions in the N
branch from the common base, B.
Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.
- Log -----------------------------------------------------------------
commit e806e4dc85a7f5b3d7928853037280b84246c2d0
Author: Neal H. Walfield <neal at g10code.com>
Date: Sat Sep 19 01:32:51 2015 +0200
TOFU stuff.
diff --git a/configure.ac b/configure.ac
index 27fc9a9..bee69ec 100644
--- a/configure.ac
+++ b/configure.ac
@@ -780,6 +780,12 @@ DL_LIBS=$LIBS
AC_SUBST(DL_LIBS)
LIBS="$gnupg_dlopen_save_libs"
+# Checks for g10
+
+PKG_CHECK_MODULES(SQLITE3, sqlite3)
+AC_SUBST(SQLITE3_CFLAGS)
+AC_SUBST(SQLITE3_LIBS)
+
# Checks for g13
AC_PATH_PROG(ENCFS, encfs, /usr/bin/encfs)
diff --git a/g10/Makefile.am b/g10/Makefile.am
index 2fd52b3..cf5a19f 100644
--- a/g10/Makefile.am
+++ b/g10/Makefile.am
@@ -25,7 +25,7 @@ AM_CPPFLAGS = -I$(top_srcdir)/common
include $(top_srcdir)/am/cmacros.am
-AM_CFLAGS = $(LIBGCRYPT_CFLAGS) \
+AM_CFLAGS = $(SQLITE3_CFLAGS) $(LIBGCRYPT_CFLAGS) \
$(LIBASSUAN_CFLAGS) $(GPG_ERROR_CFLAGS)
needed_libs = ../kbx/libkeybox.a $(libcommon)
@@ -125,7 +125,8 @@ gpg2_SOURCES = gpg.c \
call-agent.c call-agent.h \
trust.c $(trust_source) \
$(card_source) \
- exec.c exec.h
+ exec.c exec.h \
+ tofu.h tofu.c
gpgv2_SOURCES = gpgv.c \
$(common_source) \
@@ -140,7 +141,7 @@ gpgv2_SOURCES = gpgv.c \
LDADD = $(needed_libs) ../common/libgpgrl.a \
$(ZLIBS) $(LIBINTL) $(CAPLIBS) $(NETLIBS)
-gpg2_LDADD = $(LDADD) $(LIBGCRYPT_LIBS) $(LIBREADLINE) \
+gpg2_LDADD = $(LDADD) $(SQLITE3_LIBS) $(LIBGCRYPT_LIBS) $(LIBREADLINE) \
$(LIBASSUAN_LIBS) $(GPG_ERROR_LIBS) \
$(LIBICONV) $(resource_objs) $(extra_sys_libs)
gpg2_LDFLAGS = $(extra_bin_ldflags)
diff --git a/g10/gpg.c b/g10/gpg.c
index 9454b53..dda5c42 100644
--- a/g10/gpg.c
+++ b/g10/gpg.c
@@ -59,6 +59,7 @@
#include "gc-opt-flags.h"
#include "asshelp.h"
#include "call-dirmngr.h"
+#include "tofu.h"
#include "../common/init.h"
#include "../common/shareddefs.h"
@@ -384,6 +385,7 @@ enum cmd_and_opt_values
oFakedSystemTime,
oNoAutostart,
oPrintPKARecords,
+ oTOFUDefaultTrust,
oNoop
};
@@ -669,6 +671,7 @@ static ARGPARSE_OPTS opts[] = {
ARGPARSE_s_i (oDefCertLevel, "default-cert-check-level", "@"), /* old */
ARGPARSE_s_n (oAlwaysTrust, "always-trust", "@"),
ARGPARSE_s_s (oTrustModel, "trust-model", "@"),
+ ARGPARSE_s_s (oTOFUDefaultTrust, "tofu-default-trust", "@"),
ARGPARSE_s_s (oSetFilename, "set-filename", "@"),
ARGPARSE_s_n (oForYourEyesOnly, "for-your-eyes-only", "@"),
ARGPARSE_s_n (oNoForYourEyesOnly, "no-for-your-eyes-only", "@"),
@@ -1937,6 +1940,12 @@ parse_trust_model(const char *model)
opt.trust_model=TM_ALWAYS;
else if(ascii_strcasecmp(model,"direct")==0)
opt.trust_model=TM_DIRECT;
+ else if(ascii_strcasecmp(model,"tofu")==0)
+ opt.trust_model=TM_TOFU;
+ else if(ascii_strcasecmp(model,"tofu+pgp")==0)
+ opt.trust_model=TM_TOFU_PGP;
+ else if(ascii_strcasecmp(model,"tofu+wot")==0)
+ opt.trust_model=TM_TOFU_PGP;
else if(ascii_strcasecmp(model,"auto")==0)
opt.trust_model=TM_AUTO;
else
@@ -1944,6 +1953,22 @@ parse_trust_model(const char *model)
}
#endif /*NO_TRUST_MODELS*/
+static void
+parse_tofu_default_trust (const char *default_trust)
+{
+ if (ascii_strcasecmp (default_trust, "bad") == 0)
+ opt.tofu_default_trust = TOFU_BINDING_BAD;
+ else if (ascii_strcasecmp (default_trust, "ask") == 0)
+ opt.tofu_default_trust = TOFU_BINDING_ASK;
+ else if (ascii_strcasecmp (default_trust, "auto") == 0)
+ opt.tofu_default_trust = TOFU_BINDING_AUTO;
+ else if (ascii_strcasecmp (default_trust, "good") == 0)
+ opt.tofu_default_trust = TOFU_BINDING_GOOD;
+ else
+ log_error("unknown default trust '%s'"
+ " (expected 'bad', 'ask', 'auto' or 'good')\n",
+ default_trust);
+}
/* This fucntion called to initialized a new control object. It is
assumed that this object has been zeroed out before calling this
@@ -2148,6 +2173,7 @@ main (int argc, char **argv)
#else
opt.trust_model = TM_AUTO;
#endif
+ opt.tofu_default_trust = TOFU_BINDING_AUTO;
opt.mangle_dos_filenames = 0;
opt.min_cert_level = 2;
set_screen_dimensions ();
@@ -2551,6 +2577,9 @@ main (int argc, char **argv)
parse_trust_model(pargs.r.ret_str);
break;
#endif /*!NO_TRUST_MODELS*/
+ case oTOFUDefaultTrust:
+ parse_tofu_default_trust (pargs.r.ret_str);
+ break;
case oForceOwnertrust:
log_info(_("Note: %s is not for normal use!\n"),
diff --git a/g10/mainproc.c b/g10/mainproc.c
index f7b7c6b..ef08b3a 100644
--- a/g10/mainproc.c
+++ b/g10/mainproc.c
@@ -835,6 +835,7 @@ do_check_sig (CTX c, kbnode_t node, int *is_selfsig,
PKT_signature *sig;
gcry_md_hd_t md = NULL;
gcry_md_hd_t md2 = NULL;
+ gcry_md_hd_t md_good = NULL;
int algo, rc;
assert (node->pkt->pkttype == PKT_SIGNATURE);
@@ -910,8 +911,21 @@ do_check_sig (CTX c, kbnode_t node, int *is_selfsig,
return GPG_ERR_SIG_CLASS;
rc = signature_check2 (sig, md, NULL, is_expkey, is_revkey, NULL);
- if (gpg_err_code (rc) == GPG_ERR_BAD_SIGNATURE && md2)
- rc = signature_check2 (sig, md2, NULL, is_expkey, is_revkey, NULL);
+ if (! rc)
+ md_good = md;
+ else if (gpg_err_code (rc) == GPG_ERR_BAD_SIGNATURE && md2)
+ {
+ rc = signature_check2 (sig, md2, NULL, is_expkey, is_revkey, NULL);
+ if (! rc)
+ md_good = md2;
+ }
+
+ if (md_good)
+ {
+ unsigned char *buffer = gcry_md_read (md_good, 0);
+ sig->digest_len = gcry_md_get_algo_dlen (map_md_openpgp_to_gcry (algo));
+ memcpy (sig->digest, buffer, sig->digest_len);
+ }
gcry_md_close (md);
gcry_md_close (md2);
diff --git a/g10/options.h b/g10/options.h
index fd2f4a2..7ba2555 100644
--- a/g10/options.h
+++ b/g10/options.h
@@ -117,8 +117,10 @@ struct
we started storing the trust model inside the trustdb. */
enum
{
- TM_CLASSIC=0, TM_PGP=1, TM_EXTERNAL=2, TM_ALWAYS, TM_DIRECT, TM_AUTO
+ TM_CLASSIC=0, TM_PGP=1, TM_EXTERNAL=2,
+ TM_ALWAYS, TM_DIRECT, TM_AUTO, TM_TOFU, TM_TOFU_PGP
} trust_model;
+ int tofu_default_trust;
int force_ownertrust;
enum
{
diff --git a/g10/packet.h b/g10/packet.h
index 1906ec5..1aafbb2 100644
--- a/g10/packet.h
+++ b/g10/packet.h
@@ -175,6 +175,11 @@ typedef struct
subpktarea_t *unhashed; /* Ditto for unhashed data. */
byte digest_start[2]; /* First 2 bytes of the digest. */
gcry_mpi_t data[PUBKEY_MAX_NSIG];
+ /* The message digest and its length (in bytes). Note the maximum
+ digest length is 512 bits (64 bytes). If DIGEST_LEN is 0, then
+ the digest's value has not been saved here. */
+ byte digest[512 / 8];
+ int digest_len;
} PKT_signature;
#define ATTRIB_IMAGE 1
diff --git a/g10/pkclist.c b/g10/pkclist.c
index 9996d18..21d9843 100644
--- a/g10/pkclist.c
+++ b/g10/pkclist.c
@@ -37,6 +37,7 @@
#include "status.h"
#include "photoid.h"
#include "i18n.h"
+#include "tofu.h"
#define CONTROL_D ('D' - 'A' + 1)
@@ -507,13 +508,14 @@ do_we_trust_pre( PKT_public_key *pk, unsigned int trustlevel )
/****************
* Check whether we can trust this signature.
- * Returns: Error if we shall not trust this signatures.
+ * Returns an error code if we should not trust this signature.
*/
int
check_signatures_trust( PKT_signature *sig )
{
PKT_public_key *pk = xmalloc_clear( sizeof *pk );
- unsigned int trustlevel;
+ unsigned int tofu_trustlevel = TRUST_UNKNOWN;
+ unsigned int trustlevel = TRUST_UNKNOWN;
int rc=0;
rc = get_pubkey( pk, sig->keyid );
@@ -537,6 +539,78 @@ check_signatures_trust( PKT_signature *sig )
log_info(_("WARNING: this key might be revoked (revocation key"
" not present)\n"));
+ /* XXX: This isn't exactly the right approach. We do the trust
+ check here, but we repeat it in get_validity. */
+ if (opt.trust_model == TM_TOFU
+ || opt.trust_model == TM_TOFU_PGP)
+ {
+ /* Get the keyblock. We need the user ids. */
+ kbnode_t keyblock = get_pubkeyblock (pk->main_keyid);
+ kbnode_t user_id_node = keyblock;
+ int user_ids = 0;
+ int user_ids_expired = 0;
+
+ char fingerprint[MAX_FINGERPRINT_LEN];
+ size_t fingerprint_len = sizeof (fingerprint);
+
+ fingerprint_from_pk (pk, fingerprint, &fingerprint_len);
+ assert (fingerprint_len == sizeof (fingerprint));
+
+ while ((user_id_node = find_next_kbnode (user_id_node, PKT_USER_ID)))
+ {
+ unsigned int tl;
+ PKT_user_id *user_id = user_id_node->pkt->pkt.user_id;
+
+ if (user_id->is_revoked || user_id->is_expired)
+ /* If the user id is revoked or expired, then skip it. */
+ {
+ char *s;
+ if (user_id->is_revoked && user_id->is_expired)
+ s = "revoked and expired";
+ else if (user_id->is_revoked)
+ s = "revoked";
+ else
+ s = "expire";
+
+ log_info ("TOFU: Ignoring %s user id (%s)\n", s, user_id->name);
+
+ continue;
+ }
+
+ user_ids ++;
+
+ tl = tofu_register (fingerprint, user_id->name,
+ sig->digest, sig->digest_len,
+ sig->timestamp, "unknown");
+ if (tl == TRUST_EXPIRED)
+ user_ids_expired ++;
+ else if (tl == TRUST_UNDEFINED || tl == TRUST_UNKNOWN)
+ ;
+ else if (tl == TRUST_NEVER)
+ tofu_trustlevel = TRUST_NEVER;
+ else
+ {
+ assert (tl == TRUST_MARGINAL
+ || tl == TRUST_FULLY
+ || tl == TRUST_ULTIMATE);
+
+ if (tl > tofu_trustlevel)
+ /* XXX: We we really want the max? */
+ tofu_trustlevel = tl;
+ }
+ }
+
+ if (user_ids_expired == user_ids)
+ /* Only if all of the user ids are expired do we decide that
+ the key is expired. */
+ tofu_trustlevel = TRUST_EXPIRED;
+
+ if (opt.trust_model == TM_TOFU)
+ goto reveal;
+ }
+
+ /* WoT. */
+
trustlevel = get_validity (pk, NULL);
if ( (trustlevel & TRUST_FLAG_REVOKED) )
@@ -612,6 +686,11 @@ check_signatures_trust( PKT_signature *sig )
}
}
+ reveal:
+ /* If TOFU or WoT is not enabled, this still works: the defaults are
+ TRUST_UNDEFINED. */
+ trustlevel = tofu_wot_trust_combine (tofu_trustlevel, trustlevel);
+
/* Now let the user know what up with the trustlevel. */
switch ( (trustlevel & TRUST_MASK) )
{
diff --git a/g10/tofu.c b/g10/tofu.c
new file mode 100644
index 0000000..5b6c823
--- /dev/null
+++ b/g10/tofu.c
@@ -0,0 +1,1183 @@
+/* XXX:
+
+ - Add indices.
+
+ - Add support for multiple DBs?
+
+ - Add some test cases.
+
+ - Make sure encryption works.
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <assert.h>
+#include <sqlite3.h>
+
+#include "gpg.h"
+#include "types.h"
+#include "logging.h"
+#include "stringhelp.h"
+#include "options.h"
+#include "mbox-util.h"
+#include "i18n.h"
+#include "trustdb.h"
+
+#include "tofu.h"
+
+const char *
+tofu_binding_judgment_str (enum tofu_binding_judgment judgment)
+{
+ switch (judgment)
+ {
+ case TOFU_BINDING_BAD: return "bad";
+ case TOFU_BINDING_ASK: return "ask";
+ case TOFU_BINDING_AUTO: return "automatic";
+ case TOFU_BINDING_GOOD: return "good";
+ default: return "unknown";
+ }
+}
+
+int
+tofu_trust_judgment_to_trust (enum tofu_binding_judgment judgment)
+{
+ if (judgment == TOFU_BINDING_AUTO)
+ /* If JUDGMENT is AUTO, fallback to OPT.TOFU_DEFAULT_TRUST. */
+ judgment = opt.tofu_default_trust;
+
+ switch (judgment)
+ {
+ case TOFU_BINDING_BAD:
+ return TRUST_NEVER;
+ case TOFU_BINDING_ASK:
+ return TRUST_ASK;
+ case TOFU_BINDING_AUTO:
+ /* If OPT.TOFU_DEFAULT_TRUST is also AUTO, then trust fully. */
+ return TRUST_FULLY;
+ case TOFU_BINDING_GOOD:
+ return TRUST_FULLY;
+ default:
+ log_bug ("Bad value for trust judgment: %d\n",
+ opt.tofu_default_trust);
+ return 0;
+ }
+}
+
+static int
+sqlite3_exec_printf (sqlite3 *db,
+ int (*callback)(void*,int,char**,char**), void *cookie,
+ char **errmsg,
+ const char *sql, ...)
+{
+ va_list ap;
+ int rc;
+ char *sql2;
+
+ va_start (ap, sql);
+ sql2 = sqlite3_vmprintf (sql, ap);
+ va_end (ap);
+
+#if 0
+ log_debug ("tofo db: executing: `%s'\n", sql2);
+#endif
+
+ rc = sqlite3_exec (db, sql2, callback, cookie, errmsg);
+
+ sqlite3_free (sql2);
+
+ return rc;
+}
+
+static void
+closedb (sqlite3 *db)
+{
+ if (db)
+ sqlite3_close (db);
+}
+
+static int
+version_check_cb (void *cookie, int argc, char **argv, char **azColName)
+{
+ int *version = cookie;
+
+ if (argc != 1)
+ *version = 0;
+ if (strcmp (azColName[0], "version") != 0)
+ *version = 0;
+
+ if (strcmp (argv[0], "1") == 0)
+ *version = 1;
+ else
+ log_error ("tofu: unknown database version: %s\n", argv[0]);
+
+ /* Don't run again. */
+ return 1;
+}
+
+/* Return 1 if the database is okay and 0 otherwise. */
+static int
+initdb (sqlite3 *db)
+{
+ char *err = NULL;
+ int rc;
+ int version = -1;
+
+ rc = sqlite3_exec (db, "select version from version;", version_check_cb,
+ &version, &err);
+ if (rc == SQLITE_ABORT && version == 1)
+ /* Happy, happy, joy, joy. */
+ {
+ sqlite3_free (err);
+ return 1;
+ }
+
+ rc = sqlite3_exec (db, "begin transaction;", NULL, NULL, &err);
+ if (rc)
+ {
+ log_error ("Error beginning transaction on TOFU database: %s\n", err);
+ sqlite3_free (err);
+ return 0;
+ }
+
+ /* If the version table doesn't exist, then this might be a new
+ file and we need to initialize the whole sheband. */
+ rc = sqlite3_exec (db,
+ "create table version (version INTEGER);",
+ NULL, NULL, &err);
+ if (rc)
+ {
+ log_error ("Error initialization TOFU database (version): %s\n",
+ err);
+ sqlite3_free (err);
+ goto out;
+ }
+
+ rc = sqlite3_exec (db,
+ "insert into version values (1);",
+ NULL, NULL, &err);
+ if (rc)
+ {
+ log_error ("Error initialization TOFU database (version init): %s\n",
+ err);
+ sqlite3_free (err);
+ goto out;
+ }
+
+ /* The list of <fingerprint, email> bindings and auxiliary data.
+
+ OID is a unique ID identifying this binding (and used by the
+ signatures table, see below). Note: OIDs will never be
+ reused.
+
+ FINGERPRINT: The key's fingerprint.
+
+ EMAIL: The normalized email address.
+
+ USER_ID: The unmodified user id from which EMAIL was extracted.
+
+ TIME: The time this binding was first observed.
+
+ JUDGMENT: The trust judgment (-1, 0, 1, or 2; see the
+ documentation for TOFU_BINDING_BAD, etc. above). */
+ rc = sqlite3_exec_printf
+ (db, NULL, NULL, &err,
+ "create table bindings\n"
+ " (oid INTEGER PRIMARY KEY AUTOINCREMENT,\n"
+ " fingerprint TEXT, email TEXT, user_id TEXT, time INTEGER,"
+ " judgment BOOLEAN CHECK (judgment in (%d, %d, %d, %d)),\n"
+ " unique (fingerprint, email));\n"
+ "create index bindings_fingerprint_email\n"
+ " on bindings (fingerprint, email);",
+ TOFU_BINDING_BAD, TOFU_BINDING_ASK, TOFU_BINDING_AUTO, TOFU_BINDING_GOOD);
+ if (rc)
+ {
+ log_error ("Error initialization TOFU database (bindings): %s\n",
+ err);
+ sqlite3_free (err);
+ goto out;
+ }
+
+ /* The signatures that we have observed.
+
+ BINDING refers to a record in the bindings table, which describes
+ the binding.
+
+ SIG_DIGEST is the digest stored in the signature.
+
+ SIG_TIME is the timestamp stored in the signature.
+
+ ORIGIN is a free-form string that describes who fed this
+ signature to GnuPG (e.g., email:claws).
+
+ TIME is the time this signature was registered. */
+ rc = sqlite3_exec (db,
+ "create table signatures "
+ " (binding INTEGER, sig_digest TEXT, origin TEXT,"
+ " sig_time INTEGER, time INTEGER,"
+ " primary key (binding, sig_digest, origin));",
+ NULL, NULL, &err);
+ if (rc)
+ {
+ log_error ("Error initialization TOFU database (signatures): %s\n",
+ err);
+ sqlite3_free (err);
+ goto out;
+ }
+
+ out:
+ if (rc)
+ {
+ rc = sqlite3_exec (db, "abort transaction;", NULL, NULL, &err);
+ if (rc)
+ {
+ log_error ("%s: Error aborting transaction: %s\n", __func__, err);
+ sqlite3_free (err);
+ }
+ return 0;
+ }
+ else
+ {
+ rc = sqlite3_exec (db, "commit transaction;", NULL, NULL, &err);
+ if (rc)
+ {
+ log_error ("%s: Error commit transaction: %s\n", __func__, err);
+ sqlite3_free (err);
+ return 0;
+ }
+ return 1;
+ }
+}
+
+/* Opens and initializes the TOFU database. Return's NULL on
+ failure. */
+static sqlite3 *
+opendb (void)
+{
+ char *filename;
+ sqlite3 *db;
+ int rc = 0;
+
+ filename = make_filename (opt.homedir, "tofu.db", NULL);
+ rc = sqlite3_open (filename, &db);
+ if (rc)
+ {
+ log_error ("Can't open TOFU database ('%s'): %s\n",
+ filename, sqlite3_errmsg (db));
+ sqlite3_close (db);
+ db = NULL;
+ }
+
+ xfree (filename);
+
+ if (db && initdb (db) == 0)
+ {
+ closedb (db);
+ db = NULL;
+ }
+
+ return db;
+}
+
+
+/* Record (or update) a trust judgment about a (possibly new)
+ binding. */
+static gpg_error_t
+record_binding (sqlite3 *db, const char *fingerprint, const char *email,
+ const char *user_id, int judgment)
+{
+ int rc;
+ char *err = NULL;
+
+ if (! (judgment == TOFU_BINDING_BAD
+ || judgment == TOFU_BINDING_ASK
+ || judgment == TOFU_BINDING_AUTO
+ || judgment == TOFU_BINDING_GOOD))
+ log_bug ("TOFU: %s: Bad value for judgment (%d)!\n", __func__, judgment);
+
+ log_info ("TOFU: Set trust judgment for binding <%s, %s> to %s (%d).\n",
+ fingerprint, email,
+ tofu_binding_judgment_str (judgment), judgment);
+
+ rc = sqlite3_exec_printf
+ (db, NULL, NULL, &err,
+ "insert or replace into bindings\n"
+ " (oid, fingerprint, email, user_id, time, judgment)\n"
+ " values (\n"
+ /* If we don't explicitly reuse the OID, then SQLite will
+ reallocate a new one. We just need to search for the OID
+ based on the fingerprint and email since they are unique. */
+ " (select oid from bindings where fingerprint = %Q and email = %Q),\n"
+ " %Q, %Q, %Q, strftime('%%s','now'), %d);",
+ fingerprint, email, fingerprint, email, user_id, judgment);
+ if (rc)
+ {
+ log_error ("TOFU: Error updating binding database"
+ " (inserting <%s, %s, %s>): %s\n",
+ fingerprint, email, tofu_binding_judgment_str (judgment),
+ err);
+ sqlite3_free (err);
+ return gpg_error (GPG_ERR_GENERAL);
+ }
+
+ return 0;
+}
+
+
+/* Collect results of a select count (*) ...; style query. Aborts if
+ the argument is not a valid integer (or real of the form X.0). */
+static int
+get_single_integer_cb (void *cookie, int argc, char **argv, char **azColName)
+{
+ unsigned long int *count = cookie;
+ char *tail = NULL;
+
+ (void) azColName;
+
+ assert (argc == 1);
+
+ errno = 0;
+ *count = strtoul (argv[0], &tail, 0);
+ if (errno || *tail != '\0')
+ /* Abort. */
+ return 1;
+ return 0;
+}
+
+/* Collect the strings returned by a query in a simply string list.
+ Any NULL values are converted to the empty string.
+
+ If a result has 3 rows and each row contains two columns, then the
+ results are added to the list as follows (the value is parentheses
+ is the 1-based index in the final list):
+
+ row 1, col 2 (6)
+ row 1, col 1 (5)
+ row 2, col 2 (4)
+ row 2, col 1 (3)
+ row 3, col 2 (2)
+ row 3, col 1 (1)
+
+ This is because add_to_strlist pushes the results onto the front of
+ the list. The end result is that the rows are backwards, but the
+ columns are in the expected order. */
+static int
+strings_collect_cb (void *cookie, int argc, char **argv, char **azColName)
+{
+ int i;
+ strlist_t *strlist = cookie;
+
+ (void) azColName;
+
+ for (i = argc - 1; i >= 0; i --)
+ add_to_strlist (strlist, argv[i] ? argv[i] : "");
+
+ return 0;
+}
+
+/* Auxiliary data structure to collect statistics about
+ signatures. */
+struct signature_stats
+{
+ struct signature_stats *next;
+
+ /* The user-assigned judgment for this binding. */
+ enum tofu_binding_judgment judgment;
+
+ /* How long ago the signature was created (rounded to a multiple of
+ TIME_AGO_UNIT_SMALL, etc.). */
+ long time_ago;
+ /* Number of signatures during this time. */
+ unsigned long count;
+
+ /* The key that generated this signature. */
+ char fingerprint[1];
+};
+
+static void
+signature_stats_free (struct signature_stats *stats)
+{
+ while (stats)
+ {
+ struct signature_stats *next = stats->next;
+ xfree (stats);
+ stats = next;
+ }
+}
+
+/* Process rows that contain the four columns:
+
+ <fingerprint, judgment, time ago, count>. */
+static int
+signature_stats_collect_cb (void *cookie, int argc, char **argv,
+ char **azColName)
+{
+ struct signature_stats **statsp = cookie;
+ struct signature_stats *stats = xmalloc (sizeof (*stats) + strlen (argv[0]));
+ char *tail;
+ int i = 0;
+
+ (void) azColName;
+
+ stats->next = *statsp;
+ *statsp = stats;
+
+ strcpy (stats->fingerprint, argv[i]);
+ i ++;
+
+ tail = NULL;
+ errno = 0;
+ stats->judgment = strtol (argv[i], &tail, 0);
+ if (errno || ! (strcmp (tail, ".0") == 0 || *tail == '\0'))
+ {
+ /* Abort. */
+ log_error ("%s: Error converting %s to an integer (tail = `%s')\n",
+ __func__, argv[i], tail);
+ return 1;
+ }
+ i ++;
+
+ tail = NULL;
+ errno = 0;
+ stats->time_ago = strtol (argv[i], &tail, 0);
+ if (errno || ! (strcmp (tail, ".0") == 0 || *tail == '\0'))
+ {
+ /* Abort. */
+ log_error ("%s: Error converting %s to an integer (tail = `%s')\n",
+ __func__, argv[i], tail);
+ return 1;
+ }
+ i ++;
+
+ tail = NULL;
+ errno = 0;
+ stats->count = strtoul (argv[i], &tail, 0);
+ if (errno || ! (strcmp (tail, ".0") == 0 || *tail == '\0'))
+ {
+ /* Abort. */
+ log_error ("%s: Error converting %s to an integer (tail = `%s')\n",
+ __func__, argv[i], tail);
+ return 1;
+ }
+ i ++;
+
+ assert (argc == i);
+
+ return 0;
+}
+
+/* The grouping parameters when collecting signature statistics. */
+
+/* If a message is signed a couple of hours in the future, just assume
+ some clock skew. */
+#define TIME_AGO_FUTURE_IGNORE (2 * 60 * 60)
+#if 1
+# define TIME_AGO_UNIT_SMALL 60
+# define TIME_AGO_UNIT_SMALL_NAME _("minute")
+# define TIME_AGO_UNIT_SMALL_NAME_PLURAL _("minutes")
+# define TIME_AGO_MEDIUM_THRESHOLD (60 * TIME_AGO_UNIT_SMALL)
+# define TIME_AGO_UNIT_MEDIUM (60 * 60)
+# define TIME_AGO_UNIT_MEDIUM_NAME _("hour")
+# define TIME_AGO_UNIT_MEDIUM_NAME_PLURAL _("hours")
+# define TIME_AGO_LARGE_THRESHOLD (24 * 60 * TIME_AGO_UNIT_SMALL)
+# define TIME_AGO_UNIT_LARGE (24 * 60 * 60)
+# define TIME_AGO_UNIT_LARGE_NAME _("day")
+# define TIME_AGO_UNIT_LARGE_NAME_PLURAL _("days")
+#else
+# define TIME_AGO_UNIT_SMALL (24 * 60 * 60)
+# define TIME_AGO_UNIT_SMALL_NAME _("day")
+# define TIME_AGO_UNIT_SMALL_NAME_PLURAL _("days")
+# define TIME_AGO_MEDIUM_THRESHOLD (4 * TIME_AGO_UNIT_SMALL)
+# define TIME_AGO_UNIT_MEDIUM (7 * 24 * 60 * 60)
+# define TIME_AGO_UNIT_MEDIUM_NAME _("week")
+# define TIME_AGO_UNIT_MEDIUM_NAME_PLURAL _("weeks")
+# define TIME_AGO_LARGE_THRESHOLD (28 * TIME_AGO_UNIT_SMALL)
+# define TIME_AGO_UNIT_LARGE (30 * 24 * 60 * 60)
+# define TIME_AGO_UNIT_LARGE_NAME _("month")
+# define TIME_AGO_UNIT_LARGE_NAME_PLURAL _("months")
+#endif
+
+/* Convert from seconds to time units.
+
+ Note: T should already be a multiple of TIME_AGO_UNIT_SMALL or
+ TIME_AGO_UNIT_MEDIUM or TIME_AGO_UNIT_LARGE. */
+signed long
+time_ago_scale (signed long t)
+{
+ if (t < TIME_AGO_UNIT_MEDIUM)
+ return t / TIME_AGO_UNIT_SMALL;
+ if (t < TIME_AGO_UNIT_LARGE)
+ return t / TIME_AGO_UNIT_MEDIUM;
+ return t / TIME_AGO_UNIT_LARGE;
+}
+
+/* Return the appropriate unit (respecting whether it is plural or
+ singular). */
+const char *
+time_ago_unit (signed long t)
+{
+ signed long t_scaled = time_ago_scale (t);
+
+ if (t < TIME_AGO_UNIT_MEDIUM)
+ {
+ if (t_scaled == 1)
+ return TIME_AGO_UNIT_SMALL_NAME;
+ return TIME_AGO_UNIT_SMALL_NAME_PLURAL;
+ }
+ if (t < TIME_AGO_UNIT_LARGE)
+ {
+ if (t_scaled == 1)
+ return TIME_AGO_UNIT_MEDIUM_NAME;
+ return TIME_AGO_UNIT_MEDIUM_NAME_PLURAL;
+ }
+ if (t_scaled == 1)
+ return TIME_AGO_UNIT_LARGE_NAME;
+ return TIME_AGO_UNIT_LARGE_NAME_PLURAL;
+}
+
+
+#define GET_TRUST_ERROR -1
+#define JUDGMENT_NONE -2
+
+/* Return the trust judgment (TRUST_NEVER, etc.) for the binding
+ <fingerprint, email>. If we don't have a judgment, returns
+ JUDGMENT_NONE. If an error occurs, returns GET_TRUST_ERROR. */
+static int
+get_trust (sqlite3 *db, const char *fingerprint, const char *email,
+ const char *user_id)
+{
+ strlist_t fingerprints = NULL;
+ int judgment;
+ int rc;
+ char *err = NULL;
+ int trust_level = TRUST_UNKNOWN;
+ int count;
+
+ assert (GET_TRUST_ERROR != TRUST_UNKNOWN &&
+ GET_TRUST_ERROR != TRUST_EXPIRED &&
+ GET_TRUST_ERROR != TRUST_UNDEFINED &&
+ GET_TRUST_ERROR != TRUST_NEVER &&
+ GET_TRUST_ERROR != TRUST_MARGINAL &&
+ GET_TRUST_ERROR != TRUST_FULLY &&
+ GET_TRUST_ERROR != TRUST_ULTIMATE);
+
+ /* Check if the <FINGERPRINT, EMAIL> binding is known (JUDGMENT_NONE
+ cannot appear in the DB. Thus, if JUDGMENT is still
+ JUDGMENT_NONE after executing the query, then the result set was
+ empty.) */
+ judgment = JUDGMENT_NONE;
+ assert (judgment != TOFU_BINDING_BAD
+ && judgment != TOFU_BINDING_ASK
+ && judgment != TOFU_BINDING_AUTO
+ && judgment != TOFU_BINDING_GOOD);
+ rc = sqlite3_exec_printf
+ (db, get_single_integer_cb, &judgment, &err,
+ "select judgment from bindings where fingerprint = %Q and email = %Q",
+ fingerprint, email);
+ if (rc)
+ {
+ log_error ("Error reading from TOFU database"
+ " (checking for existing bad bindings): %s (%d)\n",
+ err, rc);
+ sqlite3_free (err);
+ judgment = GET_TRUST_ERROR;
+ goto out;
+ }
+
+ switch (judgment)
+ {
+ case TOFU_BINDING_BAD:
+ /* The saved judgement is bad. We don't need to ask the user
+ anything. */
+ log_info ("TOFO: Bad binding: <%s, %s>\n",
+ fingerprint, email);
+ trust_level = tofu_trust_judgment_to_trust (judgment);
+ goto out;
+
+ case TOFU_BINDING_GOOD:
+ /* The saved judgement is good. We don't need to ask the user
+ anything. */
+ log_info ("TOFO: Good binding: <%s, %s>\n",
+ fingerprint, email);
+ trust_level = tofu_trust_judgment_to_trust (judgment);
+ goto out;
+
+ case TOFU_BINDING_AUTO:
+ /* The saved judgement is auto. If opt.tofu_default_trust is
+ not ask, we don't have to ask the user anything. */
+ log_info ("TOFO: auto (default trust: %s) binding: <%s, %s>\n",
+ tofu_binding_judgment_str (opt.tofu_default_trust),
+ fingerprint, email);
+ if (opt.tofu_default_trust == TOFU_BINDING_ASK)
+ /* We need to ask the user what to do. Case #1 below. */
+ ;
+ else
+ {
+ assert (opt.tofu_default_trust == TOFU_BINDING_BAD
+ || opt.tofu_default_trust == TOFU_BINDING_AUTO
+ || opt.tofu_default_trust == TOFU_BINDING_GOOD);
+ trust_level = tofu_trust_judgment_to_trust (judgment);
+ goto out;
+ }
+
+ break;
+
+ case TOFU_BINDING_ASK:
+ /* We need to ask the user what to do. Case #2 below. */
+ break;
+
+ case JUDGMENT_NONE:
+ /* The binding is new, we need to check for conflicts. Case #3
+ below. */
+ break;
+
+ default:
+ log_bug ("%s: Impossible value for judgment (%d)\n", __func__, judgment);
+ }
+
+
+ /* We get here if:
+
+ 1. The saved judgment is auto and the default trust judgment is
+ ask (need to ask),
+
+ 2. The saved trust judgement is undefined (last time, the user
+ selected accept once or reject once) (need to ask), or,
+
+ 3. We don't yet have a saved trust judgment (judgment == JUDGMENT_NONE)
+ (need to check for a conflict).
+ */
+
+
+ /* Look for conflicts. This is need in all 3 cases.
+
+ Get the fingerprints of any bindings that share the email
+ address. Note: if the binding in question is in the DB, it will
+ also be returned. Thus, if the result set is empty, then this is
+ a new binding. */
+ rc = sqlite3_exec_printf
+ (db, strings_collect_cb, &fingerprints, &err,
+ "select distinct fingerprint from bindings where email = %Q;",
+ email);
+ if (rc)
+ {
+ log_error ("TOFU: Error reading from database"
+ " (listing fingerprints): %s\n",
+ err);
+ sqlite3_free (err);
+ goto out;
+ }
+
+ count = strlist_length (fingerprints);
+ if (count == 0 && opt.tofu_default_trust != TOFU_BINDING_ASK)
+ /* New binding with no conflict and a concrete default judgment.
+
+ We've never observed a binding with this email address (COUNT
+ is 0 and the above query would return the current binding if it
+ were in the DB) and we have a default judgment, which is not to
+ ask the user. */
+ {
+ /* If we've seen this binding, then we've seen this email and
+ judgment couldn't possibly be JUDGMENT_NONE. */
+ assert (judgment == JUDGMENT_NONE);
+
+ log_info ("TOFU: New binding <%s, %s>, no conflict.\n",
+ email, fingerprint);
+
+ if (record_binding (db, fingerprint, email, user_id,
+ TOFU_BINDING_AUTO) != 0)
+ {
+ log_error ("TOFU: Error setting binding's trust level to auto\n");
+ trust_level = GET_TRUST_ERROR;
+ goto out;
+ }
+
+ trust_level = tofu_trust_judgment_to_trust (TOFU_BINDING_AUTO);
+ goto out;
+ }
+
+#if 1
+ /* XXX: Is this behavior desirable? */
+ if (judgment == JUDGMENT_NONE)
+ /* This is a new binding and we have a conflict. Mark any
+ conflicting bindings that have an automatic judgment as now
+ requiring confirmation. */
+ {
+ rc = sqlite3_exec_printf
+ (db, NULL, NULL, &err,
+ "update bindings set judgment = %d"
+ " where email = %Q and judgment = %d;",
+ TOFU_BINDING_ASK, email, TOFU_BINDING_AUTO);
+ if (rc)
+ {
+ log_error ("TOFU: Error changing judgement: %s\n", err);
+ sqlite3_free (err);
+ goto out;
+ }
+ }
+#endif
+
+ /* If we get here, we need to ask the user about the binding. There
+ are three ways we could end up here:
+
+ - This is a new binding and there is a conflict
+ (judgment == JUDGMENT_NONE && length(fingerprints) > 0),
+
+ - This is a new binding and opt.tofu_default_trust is set to
+ ask. (judgment == JUDGMENT_NONE && opt.tofu_default_trust ==
+ TOFU_BINDING_ASK), or,
+
+ - The trust judgment is ask (the user deferred last time)
+ (judgment == TOFU_BINDING_ASK).
+ */
+ {
+ estream_t fp;
+ strlist_t other_user_ids = NULL;
+ struct signature_stats *stats = NULL;
+ struct signature_stats *stats_iter = NULL;
+ char *prompt;
+ char *choices;
+
+ fp = es_fopenmem (0, "rw,samethread");
+ if (! fp)
+ log_fatal ("Error creating memory stream\n");
+
+ if (judgment == JUDGMENT_NONE)
+ es_fprintf (fp, "The binding <%s, %s> is NOT known. ",
+ email, fingerprint);
+ es_fprintf (fp, "Please indicate whether you believe the binding ");
+ if (judgment != JUDGMENT_NONE)
+ es_fprintf (fp, "<%s, %s> ", email, fingerprint);
+ es_fprintf (fp, "is legitimate (good) or a forgery (bad).\n\n");
+
+
+ /* Find other user ids associated with this key and whether the
+ bindings are marked as good or bad. */
+ rc = sqlite3_exec_printf
+ (db, strings_collect_cb, &other_user_ids, &err,
+ "select user_id, judgment from bindings where fingerprint = %Q;",
+ fingerprint);
+ if (rc)
+ {
+ log_error ("TOFU: Error gathering other user ids: %s.\n", err);
+ sqlite3_free (err);
+ err = NULL;
+ }
+
+ if (other_user_ids)
+ {
+ strlist_t strlist_iter;
+
+ es_fprintf (fp, "Known user ids associated with this key:\n");
+ for (strlist_iter = other_user_ids;
+ strlist_iter;
+ strlist_iter = strlist_iter->next)
+ {
+ char *other_user_id = strlist_iter->d;
+ char *other_judgment;
+
+ assert (strlist_iter->next);
+ strlist_iter = strlist_iter->next;
+ other_judgment = strlist_iter->d;
+
+ es_fprintf (fp, " %s (%s)\n",
+ other_user_id,
+ tofu_binding_judgment_str (atoi (other_judgment)));
+ }
+ es_fprintf (fp, "\n");
+
+ free_strlist (other_user_ids);
+ }
+
+ /* Find other keys associated with this email address. */
+ rc = sqlite3_exec_printf
+ (db, signature_stats_collect_cb, &stats, &err,
+ "select fingerprint, judgment, time_ago, count(*)\n"
+ " from (select bindings.*,\n"
+ " case\n"
+ /* From the future (but if its just a couple of hours in the
+ future don't turn it into a warning)? Or should we use
+ small, medium or large units? (Note: whatever we do, we
+ keep the value in seconds. Then when we group, everything
+ that rounds to the same number of seconds is grouped.) */
+ " when delta < -%d then -1\n"
+ " when delta < %d then max(0, round(delta / %d) * %d)\n"
+ " when delta < %d then round(delta / %d) * %d\n"
+ " else round(delta / %d) * %d\n"
+ " end time_ago,\n"
+ " delta time_ago_raw\n"
+ " from (select *,\n"
+ " cast(strftime('%%s','now') - sig_time as real) delta\n"
+ " from signatures) ss\n"
+ " left join bindings on ss.binding = bindings.oid)\n"
+ " where email = %Q\n"
+ " group by fingerprint, time_ago\n"
+ " order by fingerprint = %Q asc, fingerprint desc, time_ago desc;\n",
+ TIME_AGO_FUTURE_IGNORE,
+ TIME_AGO_MEDIUM_THRESHOLD, TIME_AGO_UNIT_SMALL, TIME_AGO_UNIT_SMALL,
+ TIME_AGO_LARGE_THRESHOLD, TIME_AGO_UNIT_MEDIUM, TIME_AGO_UNIT_MEDIUM,
+ TIME_AGO_UNIT_LARGE, TIME_AGO_UNIT_LARGE,
+ email, fingerprint);
+ if (rc)
+ {
+ strlist_t strlist_iter;
+
+ log_error ("TOFU: Error gathering signature stats: %s.\n",
+ err);
+ sqlite3_free (err);
+ err = NULL;
+
+ es_fprintf
+ (fp, "The email address (%s) is associated with %d keys:\n",
+ email, count);
+ for (strlist_iter = fingerprints;
+ strlist_iter;
+ strlist_iter = strlist_iter->next)
+ es_fprintf (fp, " %s\n", strlist_iter->d);
+ }
+ else
+ {
+ char *key = NULL;
+
+ es_fprintf (fp, "Statistics for keys with the email `%s':\n", email);
+ for (stats_iter = stats; stats_iter; stats_iter = stats_iter->next)
+ {
+ if (! key || strcmp (key, stats_iter->fingerprint) != 0)
+ {
+ int this_key;
+ key = stats_iter->fingerprint;
+ this_key = strcmp (key, fingerprint) == 0;
+ es_fprintf (fp, " %s", key);
+ if (this_key)
+ es_fprintf (fp, " (this key):");
+ else
+ es_fprintf (fp, ", which was judged to be %s:",
+ tofu_binding_judgment_str (stats_iter->judgment));
+ es_fprintf (fp, "\n");
+ }
+
+ if (stats_iter->time_ago == -1)
+ es_fprintf (fp, " %ld %s signed in the future.\n",
+ stats_iter->count,
+ stats_iter->count == 1 ? "message" : "messages");
+ else
+ es_fprintf (fp, " %ld %s %ld %s ago.\n",
+ stats_iter->count,
+ stats_iter->count == 1 ? "message" : "messages",
+ time_ago_scale (stats_iter->time_ago),
+ time_ago_unit (stats_iter->time_ago));
+ }
+ }
+
+ es_fputc ('\n', fp);
+ es_fprintf
+ (fp,
+ _("Normally, there is only a single key associated with an email\n"
+ "address. However, people sometimes generate a new key if\n"
+ "their key is too old or they think it might be compromised.\n"
+ "Alternatively, a new key may indicate a man-in-the-middle attack!\n"
+ "Before accepting this key, you should talk to or call the person\n"
+ "to make sure this new key is legitimate.\n"));
+
+ es_fputc ('\n', fp);
+ choices = _("gGaArRbB");
+ es_fprintf (fp, _("(G)ood/(A)ccept once/(R)eject once/(B)ad? "));
+
+ /* Add a NUL terminator. */
+ es_fputc (0, fp);
+ if (es_fclose_snatch (fp, (void **) &prompt, NULL))
+ log_fatal ("error snatching memory stream\n");
+
+ while (1)
+ {
+ char *response;
+
+ if (strlen (choices) != 8)
+ log_bug ("Bad TOFU conflict translation! Please report.");
+
+ response = cpr_get ("tofu conflict", prompt);
+ trim_spaces(response);
+ cpr_kill_prompt();
+ if (strlen (response) == 1)
+ {
+ char *choice = strchr (choices, *response);
+ if (choice)
+ {
+ int c = ((size_t) choice - (size_t) choices) / 2;
+ assert (0 <= c && c <= 3);
+
+ switch (c)
+ {
+ case 0: /* Good. */
+ judgment = TOFU_BINDING_GOOD;
+ trust_level = tofu_trust_judgment_to_trust (judgment);
+ if (record_binding (db, fingerprint, email, user_id,
+ judgment) != 0)
+ /* If there's an error registering the
+ binding, don't save the signature. */
+ trust_level = GET_TRUST_ERROR;
+ break;
+ case 1: /* Accept once. */
+ judgment = TOFU_BINDING_ASK;
+ trust_level =
+ tofu_trust_judgment_to_trust (TOFU_BINDING_GOOD);
+ if (record_binding (db, fingerprint, email, user_id,
+ judgment) != 0)
+ trust_level = GET_TRUST_ERROR;
+ break;
+ case 2: /* Reject once. */
+ judgment = TOFU_BINDING_ASK;
+ trust_level =
+ tofu_trust_judgment_to_trust (TOFU_BINDING_BAD);
+ if (record_binding (db, fingerprint, email, user_id,
+ judgment) != 0)
+ trust_level = GET_TRUST_ERROR;
+ break;
+ case 3: /* Bad. */
+ judgment = TOFU_BINDING_BAD;
+ trust_level = tofu_trust_judgment_to_trust (judgment);
+ if (record_binding (db, fingerprint, email, user_id,
+ judgment) != 0)
+ trust_level = GET_TRUST_ERROR;
+ break;
+ default:
+ log_bug ("c should be between 0 and 3 but it is %d!", c);
+ }
+
+ break;
+ }
+ }
+ xfree (response);
+ }
+
+ xfree (prompt);
+
+ signature_stats_free (stats);
+ }
+
+ out:
+ free_strlist (fingerprints);
+
+ return trust_level;
+}
+
+
+int
+tofu_register (const byte *fingerprint_bin, const char *user_id,
+ const byte *sig_digest_bin, int sig_digest_bin_len,
+ time_t sig_time, const char *origin)
+{
+ sqlite3 *db;
+ char fingerprint[MAX_FINGERPRINT_LEN * 2 + 1];
+ char *email = NULL;
+ char *err = NULL;
+ int rc;
+ int trust_level = TRUST_UNKNOWN;
+ char *sig_digest;
+ unsigned long c;
+
+ db = opendb ();
+ if (! db)
+ {
+ log_info ("TOFU: Error opening DB.\n");
+ goto die;
+ }
+
+ bin2hex (fingerprint_bin, MAX_FINGERPRINT_LEN, fingerprint);
+
+ if (! *user_id)
+ {
+ log_info ("TOFU: user id is empty. Can't continue.\n");
+ goto die;
+ }
+
+ email = mailbox_from_userid (user_id);
+ if (! email)
+ /* Hmm, no email address was provided. Just take the lower-case
+ version of the whole user id. It could be a hostname, for
+ instance. */
+ email = ascii_strlwr (xstrdup (user_id));
+
+ if (! origin)
+ /* The default origin is simply "unknown". */
+ origin = "unknown";
+
+ trust_level = get_trust (db, fingerprint, email, user_id);
+ if (trust_level == GET_TRUST_ERROR)
+ /* An error. */
+ {
+ trust_level = TRUST_UNKNOWN;
+ goto die;
+ }
+
+ /* Save the observed signature in the DB. */
+ sig_digest = make_radix64_string (sig_digest_bin, sig_digest_bin_len);
+
+ /* We do a query and then an insert. Make sure they are atomic
+ by wrapping them in a transaction. */
+ rc = sqlite3_exec (db, "begin transaction;", NULL, NULL, &err);
+ if (rc)
+ {
+ log_error ("Error beginning transaction on TOFU database: %s\n", err);
+ sqlite3_free (err);
+ goto die;
+ }
+
+ /* If we've already seen this signature before, then don't add
+ it again. */
+ rc = sqlite3_exec_printf
+ (db, get_single_integer_cb, &c, &err,
+ "select count (*)\n"
+ " from signatures left join bindings\n"
+ " on signatures.binding = bindings.oid\n"
+ " where fingerprint = %Q and email = %Q and sig_time = 0x%lx\n"
+ " and sig_digest = %Q",
+ fingerprint, email, (unsigned long) sig_time, sig_digest);
+ if (rc)
+ {
+ log_error ("Error reading from SIGNATURES database"
+ " (checking existence): %s\n",
+ err);
+ sqlite3_free (err);
+ }
+ else if (c > 1)
+ /* Duplicates! This should not happen. In particular,
+ because <fingerprint, email, sig_time, sig_digest> is the
+ primary key! */
+ log_debug ("SIGNATURES DB contains duplicate records"
+ " <key: %s, %s, time: 0x%lx, sig: %s, %s>."
+ " Please report.\n",
+ fingerprint, email, (unsigned long) sig_time,
+ sig_digest, origin);
+ else if (c == 1)
+ log_debug ("Already observed the signature"
+ " <key: %s, %s, time: 0x%lx, sig: %s, %s>\n",
+ fingerprint, email, (unsigned long) sig_time,
+ sig_digest, origin);
+ else
+ /* This is the first time that we've seen this signature.
+ Record it. */
+ {
+ log_debug ("TOFU: Saving signature <%s, %s, %s>\n",
+ fingerprint, email, sig_digest);
+
+ assert (c == 0);
+
+ rc = sqlite3_exec_printf
+ (db, NULL, NULL, &err,
+ "insert into signatures\n"
+ " (binding, sig_digest, origin, sig_time, time)\n"
+ " values\n"
+ " ((select oid from bindings\n"
+ " where fingerprint = %Q and email = %Q),\n"
+ " %Q, %Q, 0x%lx, strftime('%%s', 'now'));",
+ fingerprint, email, sig_digest, origin, (unsigned long) sig_time);
+ if (rc)
+ {
+ log_error ("TOFU: Error updating DB"
+ " (inserting into signatures table): %s\n",
+ err);
+ sqlite3_free (err);
+ }
+ }
+
+ /* It only matters whether we abort or commit the transaction
+ (so long as we do something) if we execute the insert. */
+ if (rc)
+ rc = sqlite3_exec (db, "abort transaction;", NULL, NULL, &err);
+ else
+ rc = sqlite3_exec (db, "commit transaction;", NULL, NULL, &err);
+ if (rc)
+ {
+ log_error ("Error ending transaction on TOFU database: %s\n", err);
+ sqlite3_free (err);
+ goto die;
+ }
+
+ die:
+ xfree (email);
+ closedb (db);
+
+ return trust_level;
+}
+
+int
+tofu_wot_trust_combine (int tofu, int wot)
+{
+ assert (tofu == TRUST_UNKNOWN
+ || tofu == TRUST_EXPIRED
+ || tofu == TRUST_UNDEFINED
+ || tofu == TRUST_NEVER
+ || tofu == TRUST_MARGINAL
+ || tofu == TRUST_FULLY
+ || tofu == TRUST_ULTIMATE);
+ assert (wot == TRUST_UNKNOWN
+ || wot == TRUST_EXPIRED
+ || wot == TRUST_UNDEFINED
+ || wot == TRUST_NEVER
+ || wot == TRUST_MARGINAL
+ || wot == TRUST_FULLY
+ || wot == TRUST_ULTIMATE);
+
+ /* We first consider negative trust judgments. These trump positive
+ trust judgments. */
+ if (tofu == TRUST_NEVER || wot == TRUST_NEVER)
+ /* TRUST_NEVER trumps everything else. */
+ return TRUST_NEVER;
+ if (tofu == TRUST_EXPIRED || wot == TRUST_EXPIRED)
+ /* TRUST_EXPIRED trumps everything but TRUST_NEVER. */
+ return TRUST_EXPIRED;
+
+ /* Now we only have positive or neutral trust judgments. We take
+ the max. */
+ if (tofu == TRUST_ULTIMATE || wot == TRUST_ULTIMATE)
+ return TRUST_ULTIMATE;
+ if (tofu == TRUST_FULLY || wot == TRUST_FULLY)
+ return TRUST_FULLY;
+ if (tofu == TRUST_MARGINAL || wot == TRUST_MARGINAL)
+ return TRUST_MARGINAL;
+ if (tofu == TRUST_UNDEFINED || wot == TRUST_UNDEFINED)
+ return TRUST_UNDEFINED;
+ return TRUST_UNKNOWN;
+}
+
+int
+tofu_get_validity (const byte *fingerprint_bin, const char *user_id)
+{
+ sqlite3 *db;
+ char fingerprint[MAX_FINGERPRINT_LEN * 2 + 1];
+ char *email = NULL;
+ int trust_level = TRUST_UNDEFINED;
+
+ db = opendb ();
+ if (! db)
+ {
+ log_info ("TOFU: Error opening DB.\n");
+ goto die;
+ }
+
+ bin2hex (fingerprint_bin, MAX_FINGERPRINT_LEN, fingerprint);
+
+ if (! *user_id)
+ {
+ log_info ("TOFU: user id is empty. Can't continue.\n");
+ goto die;
+ }
+
+ email = mailbox_from_userid (user_id);
+ if (! email)
+ /* Hmm, no email address was provided. Just take the lower-case
+ version of the whole user id. It could be a hostname, for
+ instance. */
+ email = ascii_strlwr (xstrdup (user_id));
+
+ trust_level = get_trust (db, fingerprint, email, user_id);
+ if (trust_level == GET_TRUST_ERROR)
+ /* An error. */
+ trust_level = TRUST_UNKNOWN;
+
+ die:
+ xfree (email);
+ closedb (db);
+
+ return trust_level;
+}
diff --git a/g10/tofu.h b/g10/tofu.h
new file mode 100644
index 0000000..eff9e26
--- /dev/null
+++ b/g10/tofu.h
@@ -0,0 +1,57 @@
+#ifndef G10_TOFU_H
+#define G10_TOFU_H
+
+#include <config.h>
+
+/* For each binding, we have a trust judgment. There are four
+ possible trust judgments. */
+enum tofu_binding_judgment
+ {
+ /* The user explicitly marked the binding as bad. In this case,
+ we always return TRUST_NEVER. */
+ TOFU_BINDING_BAD = -1,
+
+ /* The user deferred a definitive judgment about the binding
+ (accept once / reject once) and the next time we see this
+ binding, we should ask the user what to do. */
+ TOFU_BINDING_ASK = 0,
+
+ /* We made a default judgment. This is only done if there is no
+ conflict with another binding (that is, the email address is
+ not part of another known key). The default judgment is
+ configurable (and specified using: --tofu-default-trust).
+
+ Note: when we make a default judgment, we still save
+ BINDING_AUTO with the binding, not the default judgment. This
+ way, if the user invokes gpg again, but with a different value
+ for --tofu-default-trust, a different decision is made. */
+ TOFU_BINDING_AUTO = 1,
+
+ /* The user explicitly marked the binding as good. In this case,
+ we return TRUST_FULLY. */
+ TOFU_BINDING_GOOD = 2
+ };
+
+/* Return a string representation of a trust judgment. Returns
+ "unknown" if JUDGMENT is not valid. */
+const char *tofu_binding_judgment_str (enum tofu_binding_judgment judgment);
+
+/* Convert a trust judgment (e.g., TOFU_BINDING_BAD) to a trust value
+ (e.g., TRUST_BAD) in light of the current configuration. */
+int tofu_trust_judgment_to_trust (enum tofu_binding_judgment judgment);
+
+
+/* PRIMARY_KEY_FINGERPRINT should be MAX_FINGERPRINT_LEN bytes
+ long. */
+int tofu_register (const byte *primary_key_fingerprint, const char *name,
+ const byte *sigs_digest, int sigs_digest_len,
+ time_t sig_time, const char *origin);
+
+/* Determine the validity (TRUST_NEVER, etc.) of a given binding. */
+int tofu_get_validity (const byte *fingerprint_bin, const char *user_id);
+
+/* When in PGP+WOT mode, we need to combine TOFU and WOT trust
+ judgments. */
+int tofu_wot_trust_combine (int tofu, int wot);
+
+#endif
diff --git a/g10/trustdb.c b/g10/trustdb.c
index b16682d..dc50486 100644
--- a/g10/trustdb.c
+++ b/g10/trustdb.c
@@ -40,6 +40,7 @@
#include "i18n.h"
#include "tdbio.h"
#include "trustdb.h"
+#include "tofu.h"
typedef struct key_item **KeyHashTable; /* see new_key_hash_table() */
@@ -379,6 +380,8 @@ trust_model_string(void)
case TM_CLASSIC: return "classic";
case TM_PGP: return "PGP";
case TM_EXTERNAL: return "external";
+ case TM_TOFU: return "TOFU";
+ case TM_TOFU_PGP: return "TOFU+PGP";
case TM_ALWAYS: return "always";
case TM_DIRECT: return "direct";
default: return "unknown";
@@ -972,7 +975,8 @@ tdb_get_validity_core (PKT_public_key *pk, PKT_user_id *uid,
TRUSTREC trec, vrec;
gpg_error_t err;
ulong recno;
- unsigned int validity;
+ unsigned int tofu_validity = TRUST_UNKNOWN;
+ unsigned int validity = TRUST_UNKNOWN;
init_trustdb ();
@@ -993,6 +997,78 @@ tdb_get_validity_core (PKT_public_key *pk, PKT_user_id *uid,
goto leave;
}
+ if (opt.trust_model == TM_TOFU || opt.trust_model == TM_TOFU_PGP)
+ {
+ kbnode_t user_id_node;
+ int user_ids = 0;
+ int user_ids_expired = 0;
+
+ char fingerprint[MAX_FINGERPRINT_LEN];
+ size_t fingerprint_len = sizeof (fingerprint);
+
+ fingerprint_from_pk (pk, fingerprint, &fingerprint_len);
+ assert (fingerprint_len == sizeof (fingerprint));
+
+ if (! uid)
+ user_id_node = get_pubkeyblock (pk->main_keyid);
+
+ while (uid
+ || (user_id_node = find_next_kbnode (user_id_node, PKT_USER_ID)))
+ {
+ unsigned int tl;
+ PKT_user_id *user_id;
+
+ if (uid)
+ user_id = uid;
+ else
+ user_id = user_id_node->pkt->pkt.user_id;
+
+ if (user_id->is_revoked || user_id->is_expired)
+ /* If the user id is revoked or expired, then skip it. */
+ {
+ char *s;
+ if (user_id->is_revoked && user_id->is_expired)
+ s = "revoked and expired";
+ else if (user_id->is_revoked)
+ s = "revoked";
+ else
+ s = "expire";
+
+ log_info ("TOFU: Ignoring %s user id (%s)\n", s, user_id->name);
+
+ continue;
+ }
+
+ user_ids ++;
+
+ tl = tofu_get_validity (fingerprint, user_id->name);
+ if (tl == TRUST_EXPIRED)
+ user_ids_expired ++;
+ else if (tl == TRUST_UNDEFINED || tl == TRUST_UNKNOWN)
+ ;
+ else if (tl == TRUST_NEVER)
+ tofu_validity = TRUST_NEVER;
+ else
+ {
+ assert (tl == TRUST_MARGINAL
+ || tl == TRUST_FULLY
+ || tl == TRUST_ULTIMATE);
+
+ if (tl > tofu_validity)
+ /* XXX: We we really want the max? */
+ tofu_validity = tl;
+ }
+
+ if (uid)
+ /* If the caller specified a user id, then we stop
+ now. */
+ break;
+ }
+
+ if (opt.trust_model == TM_TOFU)
+ goto leave;
+ }
+
err = read_trust_record (main_pk, &trec);
if (err && gpg_err_code (err) != GPG_ERR_NOT_FOUND)
{
@@ -1046,7 +1122,10 @@ tdb_get_validity_core (PKT_public_key *pk, PKT_user_id *uid,
pk->flags.disabled_valid = 1;
leave:
- if (pending_check_trustdb)
+ validity = tofu_wot_trust_combine (tofu_validity, validity);
+
+ if (opt.trust_model != TM_TOFU
+ && pending_check_trustdb)
validity |= TRUST_FLAG_PENDING_CHECK;
return validity;
diff --git a/g10/trustdb.h b/g10/trustdb.h
index 771a821..fc9bdee 100644
--- a/g10/trustdb.h
+++ b/g10/trustdb.h
@@ -30,6 +30,7 @@
#define TRUST_MARGINAL 4 /* m: marginally trusted */
#define TRUST_FULLY 5 /* f: fully trusted */
#define TRUST_ULTIMATE 6 /* u: ultimately trusted */
+#define TRUST_ASK 31 /* This is only used for TOFU. */
/* trust values not covered by the mask */
#define TRUST_FLAG_REVOKED 32 /* r: revoked */
#define TRUST_FLAG_SUB_REVOKED 64 /* r: revoked but for subkeys */
-----------------------------------------------------------------------
Summary of changes:
g10/pkclist.c | 65 ++++----
g10/tofu.c | 472 ++++++++++++++++++++++++++++++++++++----------------------
g10/tofu.h | 13 +-
g10/trustdb.c | 81 +++++++++-
4 files changed, 417 insertions(+), 214 deletions(-)
hooks/post-receive
--
The GNU Privacy Guard
http://git.gnupg.org
More information about the Gnupg-commits
mailing list