g10: Record and show statistics for encrypted messages when using TOFU

* g10/tofu.c: Include "sqrtu32.h".
(struct tofu_dbs_s.s): Rename get_trust_gather_other_keys to
get_trust_gather_signature_stats.  Add new field
get_trust_gather_encryption_stats.
(initdb): Create the encryptions table.
(ask_about_binding): Show the encryption statistics too.
(tofu_register): Rename from this...
(tofu_register_signature): ... to this and update callers.
(tofu_register_encryption): New function.
(write_stats_status): Add parameters encryption_count,
encryption_first_done and encryption_most_recent.  Update callers.
Compute the trust using the euclidean distance of the signature and
signature count.  Compare with twice the threshold.  Include
encryption count information in the TFS and TOFU_STATS lines.
(show_statistics): Also get information about the encrypted messages.
* g10/trustdb.c (tdb_get_validity_core): Use it.

--
Signed-off-by: Neal H. Walfield <neal@g10code.com>
This commit is contained in:
Neal H. Walfield 2016-09-06 15:45:38 +02:00
parent a9e6db6c7e
commit 875ac9216f
6 changed files with 474 additions and 229 deletions

View File

@ -238,8 +238,10 @@ pkd:0:1024:B665B1435F4C2 .... FF26ABB:
- Field 4 :: signcount - The number of signatures seen. - Field 4 :: signcount - The number of signatures seen.
- Field 5 :: encrcount - The number of encryptions done. - Field 5 :: encrcount - The number of encryptions done.
- Field 6 :: policy - A string with the policy - Field 6 :: policy - A string with the policy
- Field 7 :: first-seen - a timestamp or 0 if not known. - Field 7 :: signture-first-seen - a timestamp or 0 if not known.
- Field 8 :: most-recent-seen - a timestamp or 0 if not known. - Field 8 :: signature-most-recent-seen - a timestamp or 0 if not known.
- Field 9 :: encryption-first-done - a timestamp or 0 if not known.
- Field 10 :: encryption-most-recent-done - a timestamp or 0 if not known.
*** TRU - Trust database information *** TRU - Trust database information
Example for a "tru" trust base record: Example for a "tru" trust base record:
@ -715,7 +717,7 @@ pkd:0:1024:B665B1435F4C2 .... FF26ABB:
userid encoded in UTF-8 and percent escaped. The fingerprint is userid encoded in UTF-8 and percent escaped. The fingerprint is
indentical for all TOFU_USER lines up to a NEWSIG line. indentical for all TOFU_USER lines up to a NEWSIG line.
*** TOFU_STATS <validity> <sign-count> 0 [<policy> [<tm1> <tm2>]] *** TOFU_STATS <validity> <sign-count> 0 [<policy> [<tm1> <tm2> <tm3> <tm4>]]
Statistics for the current user id. Statistics for the current user id.
@ -734,9 +736,11 @@ pkd:0:1024:B665B1435F4C2 .... FF26ABB:
- ask :: Policy is "ask" - ask :: Policy is "ask"
- unknown :: Policy is not known. - unknown :: Policy is not known.
TM1 ist the time the first messages was verified. TM2 is the time TM1 ist the time the first message was verified. TM2 is the time
the most recent message was verified. Both may either be seconds the most recent message was verified. TM3 is the time the first
since Epoch or an ISO time string (yyyymmddThhmmss). message was encrypted. TM4 is the most recent encryption. All may
either be seconds since Epoch or an ISO time string
(yyyymmddThhmmss).
*** TOFU_STATS_SHORT <long_string> *** TOFU_STATS_SHORT <long_string>

View File

@ -74,7 +74,7 @@ trust_source = trustdb.c trustdb.h tdbdump.c tdbio.c tdbio.h
endif endif
if USE_TOFU if USE_TOFU
tofu_source = tofu.h tofu.c gpgsql.c gpgsql.h tofu_source = tofu.h tofu.c gpgsql.c gpgsql.h sqrtu32.c sqrtu32.h
else else
tofu_source = tofu_source =
endif endif

View File

@ -1314,6 +1314,29 @@ build_pk_list (ctrl_t ctrl, strlist_t rcpts, PK_LIST *ret_pk_list)
rc = GPG_ERR_NO_USER_ID; rc = GPG_ERR_NO_USER_ID;
} }
#ifdef USE_TOFU
if (! rc && (opt.trust_model == TM_TOFU_PGP || opt.trust_model == TM_TOFU))
{
PK_LIST iter;
for (iter = pk_list; iter; iter = iter->next)
{
int rc2;
/* Note: we already resolved any conflict when looking up
the key. Don't annoy the user again if she selected
accept once. */
rc2 = tofu_register_encryption (ctrl, iter->pk, NULL, 0);
if (rc2)
log_info ("WARNING: Failed to register encryption to %s"
" with TOFU engine\n",
keystr (pk_main_keyid (iter->pk)));
else if (DBG_TRUST)
log_debug ("Registered encryption to %s with TOFU DB.\n",
keystr (pk_main_keyid (iter->pk)));
}
}
#endif /*USE_TOFU*/
fail: fail:
if ( rc ) if ( rc )

View File

@ -41,6 +41,7 @@
#include "mkdir_p.h" #include "mkdir_p.h"
#include "gpgsql.h" #include "gpgsql.h"
#include "status.h" #include "status.h"
#include "sqrtu32.h"
#include "tofu.h" #include "tofu.h"
@ -76,7 +77,8 @@ struct tofu_dbs_s
sqlite3_stmt *get_policy_select_policy_and_conflict; sqlite3_stmt *get_policy_select_policy_and_conflict;
sqlite3_stmt *get_trust_bindings_with_this_email; sqlite3_stmt *get_trust_bindings_with_this_email;
sqlite3_stmt *get_trust_gather_other_user_ids; sqlite3_stmt *get_trust_gather_other_user_ids;
sqlite3_stmt *get_trust_gather_other_keys; sqlite3_stmt *get_trust_gather_signature_stats;
sqlite3_stmt *get_trust_gather_encryption_stats;
sqlite3_stmt *register_already_seen; sqlite3_stmt *register_already_seen;
sqlite3_stmt *register_insert; sqlite3_stmt *register_insert;
} s; } s;
@ -649,6 +651,19 @@ initdb (sqlite3 *db)
} }
out: out:
if (! rc)
{
/* Early version of the v1 format did not include the encryption
table. Add it. */
sqlite3_exec (db,
"create table if not exists encryptions"
" (binding INTEGER NOT NULL,"
" time INTEGER);"
"create index if not exists encryptions_binding"
" on encryptions (binding);\n",
NULL, NULL, &err);
}
if (rc) if (rc)
{ {
rc = sqlite3_exec (db, "rollback;", NULL, NULL, &err); rc = sqlite3_exec (db, "rollback;", NULL, NULL, &err);
@ -1384,39 +1399,42 @@ ask_about_binding (ctrl_t ctrl,
strlist_rev (&conflict_set); strlist_rev (&conflict_set);
for (iter = conflict_set; iter && ! rc; iter = iter->next) for (iter = conflict_set; iter && ! rc; iter = iter->next)
{ {
#define STATS_SQL(table, time, sign) \
"select fingerprint, policy, time_ago, count(*)\n" \
" from\n" \
" (select bindings.*,\n" \
" "sign" case\n" \
" when delta ISNULL then 1\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 < -("STRINGIFY (TIME_AGO_FUTURE_IGNORE)") then 2\n" \
" when delta < ("STRINGIFY (TIME_AGO_SMALL_THRESHOLD)")\n" \
" then 3\n" \
" when delta < ("STRINGIFY (TIME_AGO_MEDIUM_THRESHOLD)")\n" \
" then 4\n" \
" when delta < ("STRINGIFY (TIME_AGO_LARGE_THRESHOLD)")\n" \
" then 5\n" \
" else 6\n" \
" end time_ago,\n" \
" delta time_ago_raw\n" \
" from bindings\n" \
" left join\n" \
" (select *,\n" \
" cast(strftime('%s','now') - " time " as real) delta\n" \
" from " table ") ss\n" \
" on ss.binding = bindings.oid)\n" \
" where email = ? and fingerprint = ?\n" \
" group by time_ago\n" \
/* Make sure the current key is first. */ \
" order by time_ago desc;\n"
rc = gpgsql_stepx rc = gpgsql_stepx
(dbs->db, &dbs->s.get_trust_gather_other_keys, (dbs->db, &dbs->s.get_trust_gather_signature_stats,
signature_stats_collect_cb, &stats, &sqerr, signature_stats_collect_cb, &stats, &sqerr,
"select fingerprint, policy, time_ago, count(*)\n" STATS_SQL ("signatures", "sig_time", ""),
" from\n"
" (select bindings.*,\n"
" case\n"
" when delta ISNULL then 1\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 < -("STRINGIFY (TIME_AGO_FUTURE_IGNORE)") then 2\n"
" when delta < ("STRINGIFY (TIME_AGO_SMALL_THRESHOLD)")\n"
" then 3\n"
" when delta < ("STRINGIFY (TIME_AGO_MEDIUM_THRESHOLD)")\n"
" then 4\n"
" when delta < ("STRINGIFY (TIME_AGO_LARGE_THRESHOLD)")\n"
" then 5\n"
" else 6\n"
" end time_ago,\n"
" delta time_ago_raw\n"
" from bindings\n"
" left join\n"
" (select *,\n"
" cast(strftime('%s','now') - sig_time as real) delta\n"
" from signatures) ss\n"
" on ss.binding = bindings.oid)\n"
" where email = ? and fingerprint = ?\n"
" group by time_ago\n"
/* Make sure the current key is first. */
" order by time_ago desc;\n",
GPGSQL_ARG_STRING, email, GPGSQL_ARG_STRING, email,
GPGSQL_ARG_STRING, iter->d, GPGSQL_ARG_STRING, iter->d,
GPGSQL_ARG_END); GPGSQL_ARG_END);
@ -1426,6 +1444,23 @@ ask_about_binding (ctrl_t ctrl,
if (!stats || strcmp (iter->d, stats->fingerprint) != 0) if (!stats || strcmp (iter->d, stats->fingerprint) != 0)
/* No stats for this binding. Add a dummy entry. */ /* No stats for this binding. Add a dummy entry. */
signature_stats_prepend (&stats, iter->d, TOFU_POLICY_AUTO, 1, 1); signature_stats_prepend (&stats, iter->d, TOFU_POLICY_AUTO, 1, 1);
rc = gpgsql_stepx
(dbs->db, &dbs->s.get_trust_gather_encryption_stats,
signature_stats_collect_cb, &stats, &sqerr,
STATS_SQL ("encryptions", "time", "-"),
GPGSQL_ARG_STRING, email,
GPGSQL_ARG_STRING, iter->d,
GPGSQL_ARG_END);
if (rc)
break;
#undef STATS_SQL
if (!stats || strcmp (iter->d, stats->fingerprint) != 0
|| stats->time_ago > 0)
/* No stats for this binding. Add a dummy entry. */
signature_stats_prepend (&stats, iter->d, TOFU_POLICY_AUTO, -1, 1);
} }
end_transaction (ctrl, 0); end_transaction (ctrl, 0);
strlist_rev (&conflict_set); strlist_rev (&conflict_set);
@ -1459,6 +1494,13 @@ ask_about_binding (ctrl_t ctrl,
email); email);
for (stats_iter = stats; stats_iter; stats_iter = stats_iter->next) for (stats_iter = stats; stats_iter; stats_iter = stats_iter->next)
{ {
#if 0
log_debug ("%s: time_ago: %ld; count: %ld\n",
stats_iter->fingerprint,
stats_iter->time_ago,
stats_iter->count);
#endif
if (! key || strcmp (key, stats_iter->fingerprint)) if (! key || strcmp (key, stats_iter->fingerprint))
{ {
int this_key; int this_key;
@ -1499,7 +1541,7 @@ ask_about_binding (ctrl_t ctrl,
seen_in_past = 0; seen_in_past = 0;
} }
if (stats_iter->time_ago == 1) if (abs(stats_iter->time_ago) == 1)
{ {
/* The 1 in this case is the NULL entry. */ /* The 1 in this case is the NULL entry. */
log_assert (stats_iter->count == 1); log_assert (stats_iter->count == 1);
@ -1510,12 +1552,18 @@ ask_about_binding (ctrl_t ctrl,
es_fputs (" ", fp); es_fputs (" ", fp);
/* TANSLATORS: This string is concatenated with one of /* TANSLATORS: This string is concatenated with one of
* the day/week/month strings to form one sentence. */ * the day/week/month strings to form one sentence. */
es_fprintf (fp, ngettext("Verified %d message", if (stats_iter->time_ago > 0)
"Verified %d messages", es_fprintf (fp, ngettext("Verified %d message",
seen_in_past), seen_in_past); "Verified %d messages",
seen_in_past), seen_in_past);
else
es_fprintf (fp, ngettext("Encrypted %d message",
"Encrypted %d messages",
seen_in_past), seen_in_past);
if (!stats_iter->count) if (!stats_iter->count)
es_fputs (".", fp); es_fputs (".", fp);
else if (stats_iter->time_ago == 2) else if (abs(stats_iter->time_ago) == 2)
{ {
es_fprintf (fp, "in the future."); es_fprintf (fp, "in the future.");
/* Reset it. */ /* Reset it. */
@ -1523,25 +1571,25 @@ ask_about_binding (ctrl_t ctrl,
} }
else else
{ {
if (stats_iter->time_ago == 3) if (abs(stats_iter->time_ago) == 3)
es_fprintf (fp, ngettext(" over the past days.", es_fprintf (fp, ngettext(" over the past days.",
" over the past %d days.", " over the past %d days.",
seen_in_past), seen_in_past),
TIME_AGO_SMALL_THRESHOLD TIME_AGO_SMALL_THRESHOLD
/ TIME_AGO_UNIT_SMALL); / TIME_AGO_UNIT_SMALL);
else if (stats_iter->time_ago == 4) else if (abs(stats_iter->time_ago) == 4)
es_fprintf (fp, ngettext(" over the past month.", es_fprintf (fp, ngettext(" over the past month.",
" over the past %d months.", " over the past %d months.",
seen_in_past), seen_in_past),
TIME_AGO_MEDIUM_THRESHOLD TIME_AGO_MEDIUM_THRESHOLD
/ TIME_AGO_UNIT_MEDIUM); / TIME_AGO_UNIT_MEDIUM);
else if (stats_iter->time_ago == 5) else if (abs(stats_iter->time_ago) == 5)
es_fprintf (fp, ngettext(" over the past year.", es_fprintf (fp, ngettext(" over the past year.",
" over the past %d years.", " over the past %d years.",
seen_in_past), seen_in_past),
TIME_AGO_LARGE_THRESHOLD TIME_AGO_LARGE_THRESHOLD
/ TIME_AGO_UNIT_LARGE); / TIME_AGO_UNIT_LARGE);
else if (stats_iter->time_ago == 6) else if (abs(stats_iter->time_ago) == 6)
es_fprintf (fp, _(" in the past.")); es_fprintf (fp, _(" in the past."));
else else
log_assert (! "Broken SQL.\n"); log_assert (! "Broken SQL.\n");
@ -2349,46 +2397,59 @@ time_ago_str (long long int t)
/* If FP is NULL, write TOFU_STATS status line. If FP is not NULL /* If FP is NULL, write TOFU_STATS status line. If FP is not NULL
* write a "tfs" record to that stream. */ * write a "tfs" record to that stream. */
static void static void
write_stats_status (estream_t fp, long messages, enum tofu_policy policy, write_stats_status (estream_t fp,
unsigned long first_seen, enum tofu_policy policy,
unsigned long most_recent_seen) unsigned long signature_count,
unsigned long signature_first_seen,
unsigned long signature_most_recent,
unsigned long encryption_count,
unsigned long encryption_first_done,
unsigned long encryption_most_recent)
{ {
const char *validity; const char *validity;
/* Use the euclidean distance rather then the sum of the magnitudes
to ensure a balance between verified signatures and encrypted
messages. */
float messages = sqrtu32 (signature_count) + sqrtu32 (encryption_count);
if (messages < 1) if (messages < 1)
validity = "1"; /* Key without history. */ validity = "1"; /* Key without history. */
else if (messages < BASIC_TRUST_THRESHOLD) else if (messages < sqrtu32 (2 * BASIC_TRUST_THRESHOLD))
validity = "2"; /* Key with too little history. */ validity = "2"; /* Key with too little history. */
else if (messages < FULL_TRUST_THRESHOLD) else if (messages < sqrtu32 (2 * FULL_TRUST_THRESHOLD))
validity = "3"; /* Key with enough history for basic trust. */ validity = "3"; /* Key with enough history for basic trust. */
else else
validity = "4"; /* Key with a lot of history. */ validity = "4"; /* Key with a lot of history. */
if (fp) if (fp)
{ {
es_fprintf (fp, "tfs:1:%s:%ld:0:%s:%lu:%lu:\n", es_fprintf (fp, "tfs:1:%s:%ld:%ld:%s:%lu:%lu:%lu:%lu:\n",
validity, messages, validity, signature_count, encryption_count,
tofu_policy_str (policy), tofu_policy_str (policy),
first_seen, most_recent_seen); signature_first_seen, signature_most_recent,
encryption_first_done, encryption_most_recent);
} }
else else
{ {
char numbuf1[35]; char numbuf1[35];
char numbuf2[35]; char numbuf2[35];
char numbuf3[35]; char numbuf3[35];
char numbuf4[35];
char numbuf5[35];
char numbuf6[35];
snprintf (numbuf1, sizeof numbuf1, " %ld", messages); snprintf (numbuf1, sizeof numbuf1, " %ld", signature_count);
*numbuf2 = *numbuf3 = 0; snprintf (numbuf2, sizeof numbuf2, " %ld", encryption_count);
if (first_seen && most_recent_seen) snprintf (numbuf3, sizeof numbuf3, " %lu", signature_first_seen);
{ snprintf (numbuf4, sizeof numbuf4, " %lu", signature_most_recent);
snprintf (numbuf2, sizeof numbuf2, " %lu", first_seen); snprintf (numbuf5, sizeof numbuf5, " %lu", encryption_first_done);
snprintf (numbuf3, sizeof numbuf3, " %lu", most_recent_seen); snprintf (numbuf6, sizeof numbuf6, " %lu", encryption_most_recent);
}
write_status_strings (STATUS_TOFU_STATS, write_status_strings (STATUS_TOFU_STATS,
validity, numbuf1, " 0", validity, numbuf1, numbuf2,
" ", tofu_policy_str (policy), " ", tofu_policy_str (policy),
numbuf2, numbuf3, numbuf3, numbuf4, numbuf5, numbuf6,
NULL); NULL);
} }
} }
@ -2401,13 +2462,24 @@ show_statistics (tofu_dbs_t dbs, const char *fingerprint,
const char *email, const char *user_id, const char *email, const char *user_id,
estream_t outfp) estream_t outfp)
{ {
unsigned long now = gnupg_get_time ();
enum tofu_policy policy = get_policy (dbs, fingerprint, email, NULL);
char *fingerprint_pp; char *fingerprint_pp;
int rc; int rc;
strlist_t strlist = NULL; strlist_t strlist = NULL;
char *err = NULL; char *err = NULL;
unsigned long signature_first_seen = 0;
unsigned long signature_most_recent = 0;
unsigned long signature_count = 0;
unsigned long encryption_first_done = 0;
unsigned long encryption_most_recent = 0;
unsigned long encryption_count = 0;
fingerprint_pp = format_hexfingerprint (fingerprint, NULL, 0); fingerprint_pp = format_hexfingerprint (fingerprint, NULL, 0);
/* Get the signature stats. */
rc = gpgsql_exec_printf rc = gpgsql_exec_printf
(dbs->db, strings_collect_cb, &strlist, &err, (dbs->db, strings_collect_cb, &strlist, &err,
"select count (*), min (signatures.time), max (signatures.time)\n" "select count (*), min (signatures.time), max (signatures.time)\n"
@ -2423,191 +2495,217 @@ show_statistics (tofu_dbs_t dbs, const char *fingerprint,
goto out; goto out;
} }
if (strlist)
{
log_assert (strlist->next);
log_assert (strlist->next->next);
log_assert (! strlist->next->next->next);
string_to_long (&signature_count, strlist->d, -1, __LINE__);
string_to_long (&signature_first_seen, strlist->next->d, -1, __LINE__);
string_to_long (&signature_most_recent,
strlist->next->next->d, -1, __LINE__);
free_strlist (strlist);
strlist = NULL;
}
/* Get the encryption stats. */
rc = gpgsql_exec_printf
(dbs->db, strings_collect_cb, &strlist, &err,
"select count (*), min (encryptions.time), max (encryptions.time)\n"
" from encryptions\n"
" left join bindings on encryptions.binding = bindings.oid\n"
" where fingerprint = %Q and email = %Q;",
fingerprint, email);
if (rc)
{
log_error (_("error reading TOFU database: %s\n"), err);
print_further_info ("getting statistics");
sqlite3_free (err);
goto out;
}
if (strlist)
{
log_assert (strlist->next);
log_assert (strlist->next->next);
log_assert (! strlist->next->next->next);
string_to_long (&encryption_count, strlist->d, -1, __LINE__);
string_to_long (&encryption_first_done, strlist->next->d, -1, __LINE__);
string_to_long (&encryption_most_recent,
strlist->next->next->d, -1, __LINE__);
free_strlist (strlist);
strlist = NULL;
}
if (!outfp) if (!outfp)
write_status_text_and_buffer (STATUS_TOFU_USER, fingerprint, write_status_text_and_buffer (STATUS_TOFU_USER, fingerprint,
email, strlen (email), 0); email, strlen (email), 0);
if (! strlist) write_stats_status (outfp, policy,
signature_count,
signature_first_seen,
signature_most_recent,
encryption_count,
encryption_first_done,
encryption_most_recent);
if (!outfp)
{ {
if (!outfp) estream_t fp;
log_info (_("Have never verified a message signed by key %s!\n"), char *msg;
fingerprint_pp);
write_stats_status (outfp, 0, TOFU_POLICY_NONE, 0, 0);
}
else
{
unsigned long now = gnupg_get_time ();
signed long messages;
unsigned long first_seen;
unsigned long most_recent_seen;
log_assert (strlist_length (strlist) == 3); fp = es_fopenmem (0, "rw,samethread");
if (! fp)
log_fatal ("error creating memory stream: %s\n",
gpg_strerror (gpg_error_from_syserror()));
string_to_long (&messages, strlist->d, -1, __LINE__); if (signature_count == 0)
{
if (messages == 0 && *strlist->next->d == '\0') es_fprintf (fp, _("Verified %ld messages signed by \"%s\"."),
{ /* min(NULL) => NULL => "". */ 0L, user_id);
first_seen = 0; es_fputc ('\n', fp);
most_recent_seen = 0;
} }
else else
{
string_to_ulong (&first_seen, strlist->next->d, -1, __LINE__);
if (first_seen > now)
{
log_debug ("time-warp - tofu DB has a future value (%lu, %lu)\n",
first_seen, now);
first_seen = now;
}
string_to_ulong (&most_recent_seen, strlist->next->next->d, -1,
__LINE__);
if (most_recent_seen > now)
{
log_debug ("time-warp - tofu DB has a future value (%lu, %lu)\n",
most_recent_seen, now);
most_recent_seen = now;
}
}
if (messages == -1 || first_seen == -1)
{ {
write_stats_status (outfp, 0, TOFU_POLICY_NONE, 0, 0); char *first_seen_ago_str = time_ago_str (now - signature_first_seen);
if (!outfp)
log_info (_("Failed to collect signature statistics for \"%s\"\n" /* TRANSLATORS: The final %s is replaced by a string like
"(key %s)\n"), "7 months, 1 day, 5 minutes, 0 seconds". */
user_id, fingerprint_pp); es_fprintf (fp,
ngettext("Verified %ld message signed by \"%s\"\n"
"in the past %s.",
"Verified %ld messages signed by \"%s\"\n"
"in the past %s.",
signature_count),
signature_count, user_id, first_seen_ago_str);
if (signature_count > 1)
{
char *tmpstr = time_ago_str (now - signature_most_recent);
es_fputs (" ", fp);
es_fprintf (fp, _("The most recent message was"
" verified %s ago."), tmpstr);
xfree (tmpstr);
}
xfree (first_seen_ago_str);
} }
else if (outfp)
es_fprintf (fp, " ");
if (encryption_count == 0)
{ {
write_stats_status (outfp, messages, es_fprintf (fp, _("Encrypted %ld messages to \"%s\"."),
get_policy (dbs, fingerprint, email, NULL), 0L, user_id);
first_seen, most_recent_seen); es_fputc ('\n', fp);
} }
else else
{ {
enum tofu_policy policy = get_policy (dbs, fingerprint, email, NULL); char *first_done_ago_str = time_ago_str (now - encryption_first_done);
estream_t fp;
char *msg;
write_stats_status (NULL, messages, /* TRANSLATORS: The final %s is replaced by a string like
policy, "7 months, 1 day, 5 minutes, 0 seconds". */
first_seen, most_recent_seen); es_fprintf (fp,
ngettext("Encrypted %ld message to \"%s\"\n"
"in the past %s.",
"Encrypted %ld messages to \"%s\"\n"
"in the past %s.",
encryption_count),
encryption_count, user_id, first_done_ago_str);
fp = es_fopenmem (0, "rw,samethread"); if (encryption_count > 1)
if (! fp)
log_fatal ("error creating memory stream: %s\n",
gpg_strerror (gpg_error_from_syserror()));
if (messages == 0)
{ {
es_fprintf (fp, _("Verified %ld messages signed by \"%s\"."), char *tmpstr = time_ago_str (now - encryption_most_recent);
0L, user_id); es_fputs (" ", fp);
es_fputc ('\n', fp); es_fprintf (fp, _("The most recent message was"
" verified %s ago."), tmpstr);
xfree (tmpstr);
} }
else xfree (first_done_ago_str);
{ }
char *first_seen_ago_str = time_ago_str (now - first_seen);
/* TRANSLATORS: The final %s is replaced by a string like if (opt.verbose)
"7 months, 1 day, 5 minutes, 0 seconds". */ {
es_fprintf (fp, es_fputs (" ", fp);
ngettext("Verified %ld message signed by \"%s\"\n" es_fputc ('(', fp);
"in the past %s.", es_fprintf (fp, _("policy: %s"), tofu_policy_str (policy));
"Verified %ld messages signed by \"%s\"\n" es_fputs (")\n", fp);
"in the past %s.", }
messages), else
messages, user_id, first_seen_ago_str); es_fputs ("\n", fp);
if (messages > 1)
{
char *tmpstr = time_ago_str (now - most_recent_seen);
es_fputs (" ", fp);
es_fprintf (fp, _("The most recent message was"
" verified %s ago."), tmpstr);
xfree (tmpstr);
}
xfree (first_seen_ago_str);
if (opt.verbose) {
{ char *tmpmsg, *p;
es_fputs (" ", fp); es_fputc (0, fp);
es_fputc ('(', fp); if (es_fclose_snatch (fp, (void **) &tmpmsg, NULL))
es_fprintf (fp, _("policy: %s"), tofu_policy_str (policy)); log_fatal ("error snatching memory stream\n");
es_fputs (")\n", fp); msg = format_text (tmpmsg, 0, 72, 80);
} es_free (tmpmsg);
else
es_fputs ("\n", fp);
}
{ /* Print a status line but suppress the trailing LF.
char *tmpmsg, *p; * Spaces are not percent escaped. */
es_fputc (0, fp); if (*msg)
if (es_fclose_snatch (fp, (void **) &tmpmsg, NULL)) write_status_buffer (STATUS_TOFU_STATS_LONG,
log_fatal ("error snatching memory stream\n"); msg, strlen (msg)-1, -1);
msg = format_text (tmpmsg, 0, 72, 80);
es_free (tmpmsg);
/* Print a status line but suppress the trailing LF. /* Remove the non-breaking space markers. */
* Spaces are not percent escaped. */ for (p=msg; *p; p++)
if (*msg) if (*p == '~')
write_status_buffer (STATUS_TOFU_STATS_LONG, *p = ' ';
msg, strlen (msg)-1, -1); }
/* Remove the non-breaking space markers. */ log_string (GPGRT_LOG_INFO, msg);
for (p=msg; *p; p++) xfree (msg);
if (*p == '~')
*p = ' ';
} if (policy == TOFU_POLICY_AUTO
/* Cf. write_stats_status */
&& (sqrtu32 (encryption_count) + sqrtu32 (signature_count)
< sqrtu32 (2 * BASIC_TRUST_THRESHOLD)))
{
char *set_policy_command;
char *text;
char *tmpmsg;
log_string (GPGRT_LOG_INFO, msg); if (signature_count == 0)
xfree (msg); log_info (_("Warning: we have yet to see"
" a message signed by this key and user id!\n"));
else if (signature_count == 1)
log_info (_("Warning: we've only seen a single message"
" signed by this key and user id!\n"));
if (policy == TOFU_POLICY_AUTO && messages < BASIC_TRUST_THRESHOLD) set_policy_command =
{ xasprintf ("gpg --tofu-policy bad %s", fingerprint);
char *set_policy_command;
char *text;
char *tmpmsg;
if (messages == 0) tmpmsg = xasprintf
log_info (_("Warning: we have yet to see" (ngettext
" a message signed by this key and user id!\n")); ("Warning: if you think you've seen more than %ld message "
else if (messages == 1) "signed by this key and user id, then this key might be a "
log_info (_("Warning: we've only seen a single message" "forgery! Carefully examine the email address for small "
" signed by this key and user id!\n")); "variations. If the key is suspect, then use\n"
" %s\n"
"to mark it as being bad.\n",
"Warning: if you think you've seen more than %ld messages "
"signed by this key, then this key might be a forgery! "
"Carefully examine the email address for small "
"variations. If the key is suspect, then use\n"
" %s\n"
"to mark it as being bad.\n",
signature_count),
signature_count, set_policy_command);
text = format_text (tmpmsg, 0, 72, 80);
xfree (tmpmsg);
log_string (GPGRT_LOG_INFO, text);
xfree (text);
set_policy_command = es_free (set_policy_command);
xasprintf ("gpg --tofu-policy bad %s", fingerprint); }
tmpmsg = xasprintf
(ngettext
("Warning: if you think you've seen more than %ld message "
"signed by this key and user id, then this key might be a "
"forgery! Carefully examine the email address for small "
"variations. If the key is suspect, then use\n"
" %s\n"
"to mark it as being bad.\n",
"Warning: if you think you've seen more than %ld messages "
"signed by this key, then this key might be a forgery! "
"Carefully examine the email address for small "
"variations. If the key is suspect, then use\n"
" %s\n"
"to mark it as being bad.\n",
messages),
messages, set_policy_command);
text = format_text (tmpmsg, 0, 72, 80);
xfree (tmpmsg);
log_string (GPGRT_LOG_INFO, text);
xfree (text);
es_free (set_policy_command);
}
}
} }
out: out:
free_strlist (strlist);
xfree (fingerprint_pp); xfree (fingerprint_pp);
return; return;
@ -2652,9 +2750,10 @@ email_from_user_id (const char *user_id)
This function returns 0 on success and an error code if an error This function returns 0 on success and an error code if an error
occured. */ occured. */
gpg_error_t gpg_error_t
tofu_register (ctrl_t ctrl, PKT_public_key *pk, strlist_t user_id_list, tofu_register_signature (ctrl_t ctrl,
const byte *sig_digest_bin, int sig_digest_bin_len, PKT_public_key *pk, strlist_t user_id_list,
time_t sig_time, const char *origin) const byte *sig_digest_bin, int sig_digest_bin_len,
time_t sig_time, const char *origin)
{ {
gpg_error_t rc; gpg_error_t rc;
tofu_dbs_t dbs; tofu_dbs_t dbs;
@ -2797,6 +2896,114 @@ tofu_register (ctrl_t ctrl, PKT_public_key *pk, strlist_t user_id_list,
return rc; return rc;
} }
gpg_error_t
tofu_register_encryption (ctrl_t ctrl,
PKT_public_key *pk, strlist_t user_id_list,
int may_ask)
{
gpg_error_t rc = 0;
tofu_dbs_t dbs;
kbnode_t kb = NULL;
int free_user_id_list = 0;
char *fingerprint = NULL;
strlist_t user_id;
char *err = NULL;
dbs = opendbs (ctrl);
if (! dbs)
{
rc = gpg_error (GPG_ERR_GENERAL);
log_error (_("error opening TOFU database: %s\n"),
gpg_strerror (rc));
return rc;
}
/* Make sure PK is a primary key. */
if (keyid_cmp (pk_keyid (pk), pk->main_keyid) != 0
|| user_id_list)
kb = get_pubkeyblock (pk->keyid);
if (keyid_cmp (pk_keyid (pk), pk->main_keyid) != 0)
pk = kb->pkt->pkt.public_key;
if (! user_id_list)
{
/* Use all non-revoked user ids. Do use expired user ids. */
kbnode_t n = kb;
while ((n = find_next_kbnode (n, PKT_USER_ID)))
{
PKT_user_id *uid = n->pkt->pkt.user_id;
if (uid->is_revoked)
continue;
add_to_strlist (&user_id_list, uid->name);
}
free_user_id_list = 1;
if (! user_id_list)
log_info ("WARNING: Encrypting to %s, which has no"
"non-revoked user ids.\n",
keystr (pk->keyid));
}
fingerprint = hexfingerprint (pk, NULL, 0);
tofu_begin_batch_update (ctrl);
tofu_resume_batch_transaction (ctrl);
for (user_id = user_id_list; user_id; user_id = user_id->next)
{
char *email = email_from_user_id (user_id->d);
/* Make sure the binding exists and that we recognize any
conflicts. */
int tl = get_trust (ctrl, pk, fingerprint, email, user_id->d,
may_ask);
if (tl == _tofu_GET_TRUST_ERROR)
{
/* An error. */
xfree (email);
goto die;
}
rc = gpgsql_stepx
(dbs->db, &dbs->s.register_insert, NULL, NULL, &err,
"insert into encryptions\n"
" (binding, time)\n"
" values\n"
" ((select oid from bindings\n"
" where fingerprint = ? and email = ?),\n"
" strftime('%s', 'now'));",
GPGSQL_ARG_STRING, fingerprint, GPGSQL_ARG_STRING, email,
GPGSQL_ARG_END);
if (rc)
{
log_error (_("error updating TOFU database: %s\n"), err);
print_further_info ("insert encryption");
sqlite3_free (err);
}
xfree (email);
}
die:
tofu_end_batch_update (ctrl);
if (kb)
release_kbnode (kb);
if (free_user_id_list)
free_strlist (user_id_list);
xfree (fingerprint);
return rc;
}
/* Combine a trust level returned from the TOFU trust model with a /* Combine a trust level returned from the TOFU trust model with a
trust level returned by the PGP trust model. This is primarily of trust level returned by the PGP trust model. This is primarily of
interest when the trust model is tofu+pgp (TM_TOFU_PGP). interest when the trust model is tofu+pgp (TM_TOFU_PGP).

View File

@ -78,13 +78,24 @@ int tofu_policy_to_trust_level (enum tofu_policy policy);
data came from, e.g., "email:claws" (default: "unknown"). Note: data came from, e.g., "email:claws" (default: "unknown"). Note:
this function does not interact with the user, If there is a this function does not interact with the user, If there is a
conflict, or if the binding's policy is ask, the actual interaction conflict, or if the binding's policy is ask, the actual interaction
is deferred until tofu_get_validity is called.. Set the string is deferred until tofu_get_validity is called. Set the string
list FLAG to indicate that a specified user id is expired. This list FLAG to indicate that a specified user id is expired. This
function returns 0 on success and an error code on failure. */ function returns 0 on success and an error code on failure. */
gpg_error_t tofu_register (ctrl_t ctrl, PKT_public_key *pk, gpg_error_t tofu_register_signature (ctrl_t ctrl, PKT_public_key *pk,
strlist_t user_id_list, strlist_t user_id_list,
const byte *sigs_digest, int sigs_digest_len, const byte *sigs_digest,
time_t sig_time, const char *origin); int sigs_digest_len,
time_t sig_time, const char *origin);
/* Note that an encrypted mail was sent to <PK, USER_ID>, for each
USER_ID in USER_ID_LIST. (If USER_ID_LIST is NULL, then all
non-revoked user ids associated with PK are used.) If MAY_ASK is
set, then may interact with the user to resolve a TOFU
conflict. */
gpg_error_t tofu_register_encryption (ctrl_t ctrl,
PKT_public_key *pk,
strlist_t user_id_list,
int may_ask);
/* Combine a trust level returned from the TOFU trust model with a /* Combine a trust level returned from the TOFU trust model with a
trust level returned by the PGP trust model. This is primarily of trust level returned by the PGP trust model. This is primarily of

View File

@ -1090,9 +1090,9 @@ tdb_get_validity_core (ctrl_t ctrl,
into account. */ into account. */
if (sig) if (sig)
{ {
err = tofu_register (ctrl, main_pk, user_id_list, err = tofu_register_signature (ctrl, main_pk, user_id_list,
sig->digest, sig->digest_len, sig->digest, sig->digest_len,
sig->timestamp, "unknown"); sig->timestamp, "unknown");
if (err) if (err)
{ {
log_error ("TOFU: error registering signature: %s\n", log_error ("TOFU: error registering signature: %s\n",