mirror of
git://git.gnupg.org/gnupg.git
synced 2024-12-22 10:19:57 +01:00
g10: Refactor cross sig check code.
* g10/tofu.c (BINDING_NEW): New enum value. (BINDING_CONFLICT): Likewise. (BINDING_EXPIRED): Likewise. (BINDING_REVOKED): Likewise. (ask_about_binding): Move cross sig check from here... (get_trust): ... and the conflict set building from here... (build_conflict_set): ... to this new function. (format_conflict_msg_part1): Replace parameter conflict with conflict_set. Drop parameter fingerprint. Update callers. (ask_about_binding): Drop unused parameter conflict and redundant parameter bindings_with_this_email_count. Rename parameter bindings_with_this_email to conflict_set. Update callers. -- Signed-off-by: Neal H. Walfield <neal@g10code.com>
This commit is contained in:
parent
65a7563edb
commit
1f1f56e606
751
g10/tofu.c
751
g10/tofu.c
@ -1146,14 +1146,18 @@ get_policy (tofu_dbs_t dbs, const char *fingerprint, const char *email,
|
||||
/* Format the first part of a conflict message and return that as a
|
||||
* malloced string. */
|
||||
static char *
|
||||
format_conflict_msg_part1 (int policy, const char *conflict,
|
||||
const char *fingerprint, const char *email)
|
||||
format_conflict_msg_part1 (int policy, strlist_t conflict_set,
|
||||
const char *email)
|
||||
{
|
||||
estream_t fp;
|
||||
char *fingerprint;
|
||||
char *binding;
|
||||
int binding_shown = 0;
|
||||
char *tmpstr, *text;
|
||||
|
||||
log_assert (conflict_set);
|
||||
|
||||
fingerprint = conflict_set->d;
|
||||
binding = xasprintf ("<%s, %s>", fingerprint, email);
|
||||
|
||||
fp = es_fopenmem (0, "rw,samethread");
|
||||
@ -1167,17 +1171,18 @@ format_conflict_msg_part1 (int policy, const char *conflict,
|
||||
es_fputs (" ", fp);
|
||||
binding_shown = 1;
|
||||
}
|
||||
else if (policy == TOFU_POLICY_ASK
|
||||
/* If there the conflict is with itself, then don't
|
||||
* display this message. */
|
||||
&& conflict && strcmp (conflict, fingerprint))
|
||||
else if (policy == TOFU_POLICY_ASK && conflict_set->next)
|
||||
{
|
||||
int conflicts = strlist_length (conflict_set) - 1;
|
||||
es_fprintf (fp,
|
||||
_("The key with fingerprint %s raised a conflict "
|
||||
"with the binding %s."
|
||||
" Since this binding's policy was 'auto', it was "
|
||||
"changed to 'ask'."),
|
||||
conflict, binding);
|
||||
ngettext("The binding <key: %s, user id: %s> raised a "
|
||||
"conflict with %d other binding.",
|
||||
"The binding <key: %s, user id: %s> raised a "
|
||||
"conflict with %d other bindings.", conflicts),
|
||||
fingerprint, email, conflicts);
|
||||
es_fprintf (fp,
|
||||
_(" Since this binding's policy was 'auto', it has been "
|
||||
"changed to 'ask'."));
|
||||
es_fputs (" ", fp);
|
||||
binding_shown = 1;
|
||||
}
|
||||
@ -1219,9 +1224,9 @@ cross_sigs (kbnode_t a, kbnode_t b)
|
||||
if (DBG_TRUST)
|
||||
{
|
||||
format_keyid (pk_main_keyid (a_pk),
|
||||
KF_DEFAULT, a_keyid, sizeof (a_keyid));
|
||||
KF_LONG, a_keyid, sizeof (a_keyid));
|
||||
format_keyid (pk_main_keyid (b_pk),
|
||||
KF_DEFAULT, b_keyid, sizeof (b_keyid));
|
||||
KF_LONG, b_keyid, sizeof (b_keyid));
|
||||
}
|
||||
|
||||
for (i = 0; i < 2; i ++)
|
||||
@ -1263,26 +1268,35 @@ cross_sigs (kbnode_t a, kbnode_t b)
|
||||
/* We didn't find a signature from signer over signee. */
|
||||
{
|
||||
if (DBG_TRUST)
|
||||
log_info ("No cross sig between %s and %s\n",
|
||||
a_keyid, b_keyid);
|
||||
log_debug ("No cross sig between %s and %s\n",
|
||||
a_keyid, b_keyid);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* A signed B and B signed A. */
|
||||
if (DBG_TRUST)
|
||||
log_info ("Cross sig between %s and %s\n",
|
||||
a_keyid, b_keyid);
|
||||
log_debug ("Cross sig between %s and %s\n",
|
||||
a_keyid, b_keyid);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
enum
|
||||
{
|
||||
BINDING_NEW = 1 << 0,
|
||||
BINDING_CONFLICT = 1 << 1,
|
||||
BINDING_EXPIRED = 1 << 2,
|
||||
BINDING_REVOKED = 1 << 3
|
||||
};
|
||||
|
||||
|
||||
/* 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),
|
||||
* (policy == TOFU_POLICY_NONE && conflict_set_count > 1),
|
||||
*
|
||||
* - This is a new binding and opt.tofu_default_policy is set to
|
||||
* ask. (policy == TOFU_POLICY_NONE && opt.tofu_default_policy ==
|
||||
@ -1292,19 +1306,23 @@ cross_sigs (kbnode_t a, kbnode_t b)
|
||||
* TOFU_POLICY_ASK).
|
||||
*
|
||||
* Note: this function must not be called while in a transaction!
|
||||
*
|
||||
* CONFLICT_SET includes all of the conflicting bindings
|
||||
* with FINGERPRINT first. FLAGS is a bit-wise or of
|
||||
* BINDING_NEW, etc.
|
||||
*/
|
||||
static void
|
||||
ask_about_binding (ctrl_t ctrl,
|
||||
enum tofu_policy *policy,
|
||||
int *trust_level,
|
||||
int bindings_with_this_email_count,
|
||||
strlist_t bindings_with_this_email,
|
||||
char *conflict,
|
||||
strlist_t conflict_set,
|
||||
const char *fingerprint,
|
||||
const char *email,
|
||||
const char *user_id)
|
||||
{
|
||||
tofu_dbs_t dbs;
|
||||
strlist_t iter;
|
||||
int conflict_set_count = strlist_length (conflict_set);
|
||||
char *sqerr = NULL;
|
||||
int rc;
|
||||
estream_t fp;
|
||||
@ -1324,8 +1342,7 @@ ask_about_binding (ctrl_t ctrl,
|
||||
gpg_strerror (gpg_error_from_syserror()));
|
||||
|
||||
{
|
||||
char *text = format_conflict_msg_part1 (*policy, conflict,
|
||||
fingerprint, email);
|
||||
char *text = format_conflict_msg_part1 (*policy, conflict_set, email);
|
||||
es_fputs (text, fp);
|
||||
es_fputc ('\n', fp);
|
||||
xfree (text);
|
||||
@ -1375,46 +1392,59 @@ ask_about_binding (ctrl_t ctrl,
|
||||
free_strlist (other_user_ids);
|
||||
}
|
||||
|
||||
/* Find other keys associated with this email address. */
|
||||
/* Get the stats for all the keys in CONFLICT_SET. */
|
||||
/* FIXME: 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 = gpgsql_stepx
|
||||
(dbs->db, &dbs->s.get_trust_gather_other_keys,
|
||||
signature_stats_collect_cb, &stats, &sqerr,
|
||||
"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 < -("STRINGIFY (TIME_AGO_FUTURE_IGNORE)") then -1\n"
|
||||
" when delta < ("STRINGIFY (TIME_AGO_MEDIUM_THRESHOLD)")\n"
|
||||
" then max(0,\n"
|
||||
" round(delta / ("STRINGIFY (TIME_AGO_UNIT_SMALL)"))\n"
|
||||
" * ("STRINGIFY (TIME_AGO_UNIT_SMALL)"))\n"
|
||||
" when delta < ("STRINGIFY (TIME_AGO_LARGE_THRESHOLD)")\n"
|
||||
" then round(delta / ("STRINGIFY (TIME_AGO_UNIT_MEDIUM)"))\n"
|
||||
" * ("STRINGIFY (TIME_AGO_UNIT_MEDIUM)")\n"
|
||||
" else round(delta / ("STRINGIFY (TIME_AGO_UNIT_LARGE)"))\n"
|
||||
" * ("STRINGIFY (TIME_AGO_UNIT_LARGE)")\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 = ?\n"
|
||||
" group by fingerprint, time_ago\n"
|
||||
/* Make sure the current key is first. */
|
||||
" order by fingerprint = ? asc, fingerprint desc, time_ago desc;\n",
|
||||
GPGSQL_ARG_STRING, email, GPGSQL_ARG_STRING, fingerprint,
|
||||
GPGSQL_ARG_END);
|
||||
strlist_rev (&conflict_set);
|
||||
for (iter = conflict_set; iter && ! rc; iter = iter->next)
|
||||
{
|
||||
rc = gpgsql_stepx
|
||||
(dbs->db, &dbs->s.get_trust_gather_other_keys,
|
||||
signature_stats_collect_cb, &stats, &sqerr,
|
||||
"select fingerprint, policy, time_ago, count(*)\n"
|
||||
" from\n"
|
||||
" (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 < -("STRINGIFY (TIME_AGO_FUTURE_IGNORE)") then -1\n"
|
||||
" when delta < ("STRINGIFY (TIME_AGO_MEDIUM_THRESHOLD)")\n"
|
||||
" then max(0,\n"
|
||||
" round(delta / ("STRINGIFY (TIME_AGO_UNIT_SMALL)"))\n"
|
||||
" * ("STRINGIFY (TIME_AGO_UNIT_SMALL)"))\n"
|
||||
" when delta < ("STRINGIFY (TIME_AGO_LARGE_THRESHOLD)")\n"
|
||||
" then round(delta / ("STRINGIFY (TIME_AGO_UNIT_MEDIUM)"))\n"
|
||||
" * ("STRINGIFY (TIME_AGO_UNIT_MEDIUM)")\n"
|
||||
" else round(delta / ("STRINGIFY (TIME_AGO_UNIT_LARGE)"))\n"
|
||||
" * ("STRINGIFY (TIME_AGO_UNIT_LARGE)")\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, iter->d,
|
||||
GPGSQL_ARG_END);
|
||||
if (rc)
|
||||
break;
|
||||
|
||||
if (!stats || strcmp (iter->d, stats->fingerprint) != 0)
|
||||
/* No stats for this binding. Add a dummy entry. */
|
||||
signature_stats_prepend (&stats, iter->d, TOFU_POLICY_AUTO, 0, 0);
|
||||
}
|
||||
end_transaction (ctrl, 0);
|
||||
strlist_rev (&conflict_set);
|
||||
if (rc)
|
||||
{
|
||||
strlist_t strlist_iter;
|
||||
@ -1427,193 +1457,19 @@ ask_about_binding (ctrl_t ctrl,
|
||||
" associated with %d key:\n",
|
||||
"The email address \"%s\" is"
|
||||
" associated with %d keys:\n",
|
||||
bindings_with_this_email_count),
|
||||
email, bindings_with_this_email_count);
|
||||
for (strlist_iter = bindings_with_this_email;
|
||||
conflict_set_count),
|
||||
email, conflict_set_count);
|
||||
for (strlist_iter = conflict_set;
|
||||
strlist_iter;
|
||||
strlist_iter = strlist_iter->next)
|
||||
es_fprintf (fp, " %s\n", strlist_iter->d);
|
||||
}
|
||||
else
|
||||
{
|
||||
int stats_count = 0;
|
||||
kbnode_t *kb_all;
|
||||
KEYDB_HANDLE hd;
|
||||
int i;
|
||||
char *key = NULL;
|
||||
strlist_t binding;
|
||||
|
||||
/* Get the keyblock for each key. */
|
||||
for (stats_iter = stats; stats_iter; stats_iter = stats_iter->next)
|
||||
stats_count ++;
|
||||
kb_all = xcalloc (sizeof (kb_all[0]), stats_count);
|
||||
|
||||
if (! stats || strcmp (stats->fingerprint, fingerprint))
|
||||
{
|
||||
/* 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);
|
||||
}
|
||||
|
||||
/* Figure out which user ids are revoked or expired. */
|
||||
hd = keydb_new ();
|
||||
for (stats_iter = stats, i = 0;
|
||||
stats_iter;
|
||||
stats_iter = stats_iter->next, i ++)
|
||||
{
|
||||
KEYDB_SEARCH_DESC desc;
|
||||
kbnode_t kb;
|
||||
PKT_public_key *pk;
|
||||
kbnode_t n;
|
||||
int found_user_id;
|
||||
|
||||
rc = keydb_search_reset (hd);
|
||||
if (rc)
|
||||
{
|
||||
log_error (_("resetting keydb: %s\n"),
|
||||
gpg_strerror (rc));
|
||||
continue;
|
||||
}
|
||||
|
||||
rc = classify_user_id (stats_iter->fingerprint, &desc, 0);
|
||||
if (rc)
|
||||
{
|
||||
log_error (_("error parsing key specification '%s': %s\n"),
|
||||
stats_iter->fingerprint, gpg_strerror (rc));
|
||||
continue;
|
||||
}
|
||||
|
||||
rc = keydb_search (hd, &desc, 1, NULL);
|
||||
if (rc)
|
||||
{
|
||||
log_error (_("key \"%s\" not found: %s\n"),
|
||||
stats_iter->fingerprint,
|
||||
gpg_strerror (rc));
|
||||
continue;
|
||||
}
|
||||
|
||||
rc = keydb_get_keyblock (hd, &kb);
|
||||
if (rc)
|
||||
{
|
||||
log_error (_("error reading keyblock: %s\n"),
|
||||
gpg_strerror (rc));
|
||||
print_further_info ("fingerprint: %s", stats_iter->fingerprint);
|
||||
continue;
|
||||
}
|
||||
|
||||
merge_keys_and_selfsig (kb);
|
||||
|
||||
log_assert (kb->pkt->pkttype == PKT_PUBLIC_KEY);
|
||||
|
||||
kb_all[i] = kb;
|
||||
|
||||
pk = kb->pkt->pkt.public_key;
|
||||
|
||||
if (pk->has_expired)
|
||||
stats_iter->is_expired = 1;
|
||||
if (pk->flags.revoked)
|
||||
stats_iter->is_revoked = 1;
|
||||
|
||||
n = kb;
|
||||
found_user_id = 0;
|
||||
while ((n = find_next_kbnode (n, PKT_USER_ID)) && ! found_user_id)
|
||||
{
|
||||
PKT_user_id *user_id2 = n->pkt->pkt.user_id;
|
||||
char *email2;
|
||||
|
||||
if (user_id2->attrib_data)
|
||||
continue;
|
||||
|
||||
email2 = email_from_user_id (user_id2->name);
|
||||
|
||||
if (strcmp (email, email2) == 0)
|
||||
{
|
||||
found_user_id = 1;
|
||||
|
||||
if (user_id2->is_revoked)
|
||||
stats_iter->is_revoked = 1;
|
||||
if (user_id2->is_expired)
|
||||
stats_iter->is_expired = 1;
|
||||
}
|
||||
|
||||
xfree (email2);
|
||||
}
|
||||
|
||||
if (! found_user_id)
|
||||
log_info (_("TOFU db may be corrupted: user id (%s)"
|
||||
" not on key block (%s)\n"),
|
||||
email, fingerprint);
|
||||
}
|
||||
keydb_release (hd);
|
||||
|
||||
{
|
||||
int j;
|
||||
struct signature_stats **stats_prevp;
|
||||
struct signature_stats *stats_iter_next;
|
||||
int die[stats_count];
|
||||
|
||||
memset (die, 0, sizeof (die));
|
||||
|
||||
for (i = 0; i < stats_count; i ++)
|
||||
{
|
||||
/* i or a key that has cross sigs with i (possible
|
||||
indirectly)? */
|
||||
if (! (i == 0 || die[i]))
|
||||
continue;
|
||||
|
||||
for (j = i + 1; j < stats_count; j ++)
|
||||
if (cross_sigs (kb_all[i], kb_all[j]))
|
||||
die[j] = 1;
|
||||
}
|
||||
|
||||
/* Free the dead stat structures. */
|
||||
for (stats_iter = stats, stats_prevp = &stats, i = 0;
|
||||
stats_iter;
|
||||
stats_iter = stats_iter_next, i ++)
|
||||
{
|
||||
stats_iter_next = stats_iter->next;
|
||||
|
||||
release_kbnode (kb_all[i]);
|
||||
|
||||
if (die[i])
|
||||
{
|
||||
*stats_prevp = stats_iter_next;
|
||||
stats_iter->next = NULL;
|
||||
signature_stats_free (stats_iter);
|
||||
|
||||
bindings_with_this_email_count --;
|
||||
}
|
||||
else
|
||||
{
|
||||
stats_prevp = &stats_iter->next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log_assert (stats);
|
||||
log_assert (bindings_with_this_email_count >= 1);
|
||||
|
||||
if ((*policy == TOFU_POLICY_NONE && bindings_with_this_email_count == 1)
|
||||
|| (*policy == TOFU_POLICY_ASK && conflict))
|
||||
if (bindings_with_this_email_count == 1)
|
||||
{
|
||||
/* All "conflicts" were not really conflicts. */
|
||||
log_assert (! stats->next);
|
||||
|
||||
if (DBG_TRUST)
|
||||
log_debug ("%s: all apparent TOFU conflicts are legitimate "
|
||||
"(cross sigs), setting policy to auto.\n",
|
||||
stats_iter->fingerprint);
|
||||
|
||||
*policy = TOFU_POLICY_AUTO;
|
||||
record_binding (dbs, fingerprint, email, user_id, *policy, 0);
|
||||
*trust_level = tofu_policy_to_trust_level (*policy);
|
||||
|
||||
goto out;
|
||||
}
|
||||
|
||||
es_fprintf (fp, _("Statistics for potentially conflicting keys"
|
||||
es_fprintf (fp, _("Statistics for keys"
|
||||
" with the email address \"%s\":\n"),
|
||||
email);
|
||||
for (stats_iter = stats; stats_iter; stats_iter = stats_iter->next)
|
||||
@ -1628,12 +1484,20 @@ ask_about_binding (ctrl_t ctrl,
|
||||
key_pp = format_hexfingerprint (key, NULL, 0);
|
||||
es_fprintf (fp, " %s (", key_pp);
|
||||
|
||||
if (stats_iter->is_revoked)
|
||||
/* Find the associated binding. */
|
||||
for (binding = conflict_set;
|
||||
binding;
|
||||
binding = binding->next)
|
||||
if (strcmp (key, binding->d) == 0)
|
||||
break;
|
||||
log_assert (binding);
|
||||
|
||||
if ((binding->flags & BINDING_REVOKED))
|
||||
{
|
||||
es_fprintf (fp, _("revoked"));
|
||||
es_fprintf (fp, _(", "));
|
||||
}
|
||||
else if (stats_iter->is_expired)
|
||||
else if ((binding->flags & BINDING_EXPIRED))
|
||||
{
|
||||
es_fprintf (fp, _("expired"));
|
||||
es_fprintf (fp, _(", "));
|
||||
@ -1681,9 +1545,7 @@ ask_about_binding (ctrl_t ctrl,
|
||||
}
|
||||
}
|
||||
|
||||
if ((*policy == TOFU_POLICY_NONE && bindings_with_this_email_count > 0)
|
||||
|| (*policy == TOFU_POLICY_ASK
|
||||
&& (conflict || bindings_with_this_email_count > 0)))
|
||||
if (conflict_set_count > 1 || (conflict_set->flags & BINDING_CONFLICT))
|
||||
{
|
||||
/* This is a conflict. */
|
||||
|
||||
@ -1796,7 +1658,7 @@ ask_about_binding (ctrl_t ctrl,
|
||||
}
|
||||
xfree (response);
|
||||
}
|
||||
out:
|
||||
|
||||
tofu_resume_batch_transaction (ctrl);
|
||||
|
||||
xfree (prompt);
|
||||
@ -1804,6 +1666,258 @@ ask_about_binding (ctrl_t ctrl,
|
||||
signature_stats_free (stats);
|
||||
}
|
||||
|
||||
/* Return the set of keys that conflict with the binding <fingerprint,
|
||||
email> (including the binding itself, which will be first in the
|
||||
list). For each returned key also sets BINDING_NEW, etc. */
|
||||
static strlist_t
|
||||
build_conflict_set (tofu_dbs_t dbs, const char *fingerprint, const char *email)
|
||||
{
|
||||
gpg_error_t rc;
|
||||
char *sqerr;
|
||||
strlist_t conflict_set = NULL;
|
||||
int conflict_set_count;
|
||||
strlist_t iter;
|
||||
kbnode_t *kb_all;
|
||||
KEYDB_HANDLE hd;
|
||||
int i;
|
||||
|
||||
/* Get the fingerprints of any bindings that share the email address
|
||||
* and whether the bindings have a known conflict.
|
||||
*
|
||||
* Note: if the binding in question is in the DB, it will also be
|
||||
* returned. Thus, if the result set is empty, then <email,
|
||||
* fingerprint> is a new binding. */
|
||||
rc = gpgsql_stepx
|
||||
(dbs->db, &dbs->s.get_trust_bindings_with_this_email,
|
||||
strings_collect_cb2, &conflict_set, &sqerr,
|
||||
"select"
|
||||
/* A binding should only appear once, but try not to break in the
|
||||
* case of corruption. */
|
||||
" fingerprint || case sum(conflict ISNULL) when 0 then '' else '!' end"
|
||||
" from bindings where email = ?"
|
||||
" group by fingerprint"
|
||||
/* Make sure the current key comes first in the result list (if
|
||||
it is present). */
|
||||
" order by fingerprint = ? asc, fingerprint desc;",
|
||||
GPGSQL_ARG_STRING, email,
|
||||
GPGSQL_ARG_STRING, fingerprint,
|
||||
GPGSQL_ARG_END);
|
||||
if (rc)
|
||||
{
|
||||
log_error (_("error reading TOFU database: %s\n"), sqerr);
|
||||
print_further_info ("listing fingerprints");
|
||||
sqlite3_free (sqerr);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* If the current binding has not yet been recorded, add it to the
|
||||
* list. (The order by above ensures that if it is present, it will
|
||||
* be first.) */
|
||||
if (! (conflict_set && strcmp (conflict_set->d, fingerprint) == 0))
|
||||
{
|
||||
add_to_strlist (&conflict_set, fingerprint);
|
||||
conflict_set->flags |= BINDING_NEW;
|
||||
}
|
||||
|
||||
/* Set BINDING_CONFLICT if the binding has a known conflict. This
|
||||
* allows us to distinguish between bindings where the user
|
||||
* explicitly set the policy to ask and bindings where we set the
|
||||
* policy to ask due to a conflict. */
|
||||
for (iter = conflict_set; iter; iter = iter->next)
|
||||
{
|
||||
int l = strlen (iter->d);
|
||||
if (!(l == 2 * MAX_FINGERPRINT_LEN
|
||||
|| l == 2 * MAX_FINGERPRINT_LEN + 1))
|
||||
{
|
||||
log_error (_("TOFU db corruption detected.\n"));
|
||||
print_further_info ("fingerprint '%s' is not %d characters long",
|
||||
iter->d, 2 * MAX_FINGERPRINT_LEN);
|
||||
}
|
||||
|
||||
if (l >= 1 && iter->d[l - 1] == '!')
|
||||
{
|
||||
iter->flags |= BINDING_CONFLICT;
|
||||
/* Remove the !. */
|
||||
iter->d[l - 1] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
conflict_set_count = strlist_length (conflict_set);
|
||||
|
||||
/* Eliminate false conflicts. */
|
||||
|
||||
/* If two keys have cross signatures, then they are controlled by
|
||||
* the same person and thus are not in conflict. */
|
||||
kb_all = xcalloc (sizeof (kb_all[0]), conflict_set_count);
|
||||
hd = keydb_new ();
|
||||
for (i = 0, iter = conflict_set;
|
||||
i < conflict_set_count;
|
||||
i ++, iter = iter->next)
|
||||
{
|
||||
char *fp = iter->d;
|
||||
KEYDB_SEARCH_DESC desc;
|
||||
kbnode_t kb;
|
||||
PKT_public_key *binding_pk;
|
||||
kbnode_t n;
|
||||
int found_user_id;
|
||||
|
||||
rc = keydb_search_reset (hd);
|
||||
if (rc)
|
||||
{
|
||||
log_error (_("resetting keydb: %s\n"),
|
||||
gpg_strerror (rc));
|
||||
continue;
|
||||
}
|
||||
|
||||
rc = classify_user_id (fp, &desc, 0);
|
||||
if (rc)
|
||||
{
|
||||
log_error (_("error parsing key specification '%s': %s\n"),
|
||||
fp, gpg_strerror (rc));
|
||||
continue;
|
||||
}
|
||||
|
||||
rc = keydb_search (hd, &desc, 1, NULL);
|
||||
if (rc)
|
||||
{
|
||||
/* Note: it is entirely possible that we don't have the key
|
||||
corresponding to an entry in the TOFU DB. This can
|
||||
happen if we merge two TOFU DBs, but not the key
|
||||
rings. */
|
||||
log_info (_("key \"%s\" not found: %s\n"),
|
||||
fp, gpg_strerror (rc));
|
||||
continue;
|
||||
}
|
||||
|
||||
rc = keydb_get_keyblock (hd, &kb);
|
||||
if (rc)
|
||||
{
|
||||
log_error (_("error reading keyblock: %s\n"),
|
||||
gpg_strerror (rc));
|
||||
print_further_info ("fingerprint: %s", fp);
|
||||
continue;
|
||||
}
|
||||
|
||||
merge_keys_and_selfsig (kb);
|
||||
|
||||
log_assert (kb->pkt->pkttype == PKT_PUBLIC_KEY);
|
||||
|
||||
kb_all[i] = kb;
|
||||
|
||||
/* Since we have the key block, use this opportunity to figure
|
||||
* out if the binding is expired or revoked. */
|
||||
binding_pk = kb->pkt->pkt.public_key;
|
||||
|
||||
/* The binding is always expired/revoked if the key is
|
||||
* expired/revoked. */
|
||||
if (binding_pk->has_expired)
|
||||
iter->flags &= BINDING_EXPIRED;
|
||||
if (binding_pk->flags.revoked)
|
||||
iter->flags &= BINDING_REVOKED;
|
||||
|
||||
/* The binding is also expired/revoked if the user id is
|
||||
* expired/revoked. */
|
||||
n = kb;
|
||||
found_user_id = 0;
|
||||
while ((n = find_next_kbnode (n, PKT_USER_ID)) && ! found_user_id)
|
||||
{
|
||||
PKT_user_id *user_id2 = n->pkt->pkt.user_id;
|
||||
char *email2;
|
||||
|
||||
if (user_id2->attrib_data)
|
||||
continue;
|
||||
|
||||
email2 = email_from_user_id (user_id2->name);
|
||||
|
||||
if (strcmp (email, email2) == 0)
|
||||
{
|
||||
found_user_id = 1;
|
||||
|
||||
if (user_id2->is_revoked)
|
||||
iter->flags &= BINDING_REVOKED;
|
||||
if (user_id2->is_expired)
|
||||
iter->flags &= BINDING_EXPIRED;
|
||||
}
|
||||
|
||||
xfree (email2);
|
||||
}
|
||||
|
||||
if (! found_user_id)
|
||||
{
|
||||
log_info (_("TOFU db corruption detected.\n"));
|
||||
print_further_info ("user id '%s' not on key block '%s'",
|
||||
email, fingerprint);
|
||||
}
|
||||
}
|
||||
keydb_release (hd);
|
||||
|
||||
/* Now that we have the key blocks, check for cross sigs. */
|
||||
{
|
||||
int j;
|
||||
strlist_t *prevp;
|
||||
strlist_t iter_next;
|
||||
int die[conflict_set_count];
|
||||
|
||||
memset (die, 0, sizeof (die));
|
||||
|
||||
for (i = 0; i < conflict_set_count; i ++)
|
||||
{
|
||||
/* Look for cross sigs between this key (i == 0) or a key
|
||||
* that has cross sigs with i == 0 (i.e., transitively) */
|
||||
if (! (i == 0 || die[i]))
|
||||
continue;
|
||||
|
||||
for (j = i + 1; j < conflict_set_count; j ++)
|
||||
/* Be careful: we might not have a key block for a key. */
|
||||
if (kb_all[i] && kb_all[j] && cross_sigs (kb_all[i], kb_all[j]))
|
||||
die[j] = 1;
|
||||
}
|
||||
|
||||
/* Free unconflicting bindings (and all of the key blocks). */
|
||||
for (iter = conflict_set, prevp = &conflict_set, i = 0;
|
||||
iter;
|
||||
iter = iter_next, i ++)
|
||||
{
|
||||
iter_next = iter->next;
|
||||
|
||||
release_kbnode (kb_all[i]);
|
||||
|
||||
if (die[i])
|
||||
{
|
||||
*prevp = iter_next;
|
||||
iter->next = NULL;
|
||||
free_strlist (iter);
|
||||
conflict_set_count --;
|
||||
}
|
||||
else
|
||||
{
|
||||
prevp = &iter->next;
|
||||
}
|
||||
}
|
||||
|
||||
/* We shouldn't have removed the head. */
|
||||
log_assert (conflict_set);
|
||||
log_assert (conflict_set_count >= 1);
|
||||
}
|
||||
|
||||
if (DBG_TRUST)
|
||||
{
|
||||
log_debug ("binding <key: %s, email: %s> conflicts:\n",
|
||||
fingerprint, email);
|
||||
for (iter = conflict_set; iter; iter = iter->next)
|
||||
{
|
||||
log_debug (" %s:%s%s%s%s\n",
|
||||
iter->d,
|
||||
(iter->flags & BINDING_NEW) ? " new" : "",
|
||||
(iter->flags & BINDING_CONFLICT) ? " known_conflict" : "",
|
||||
(iter->flags & BINDING_EXPIRED) ? " expired" : "",
|
||||
(iter->flags & BINDING_REVOKED) ? " revoked" : "");
|
||||
}
|
||||
}
|
||||
|
||||
return conflict_set;
|
||||
}
|
||||
|
||||
|
||||
/* Return the trust level (TRUST_NEVER, etc.) for the binding
|
||||
* <FINGERPRINT, EMAIL> (email is already normalized). If no policy
|
||||
@ -1828,13 +1942,13 @@ get_trust (ctrl_t ctrl, PKT_public_key *pk,
|
||||
tofu_dbs_t dbs = ctrl->tofu.dbs;
|
||||
int in_transaction = 0;
|
||||
enum tofu_policy policy;
|
||||
char *conflict = NULL;
|
||||
int rc;
|
||||
char *sqerr = NULL;
|
||||
strlist_t bindings_with_this_email = NULL;
|
||||
int bindings_with_this_email_count;
|
||||
int change_conflicting_to_ask = 0;
|
||||
strlist_t conflict_set = NULL;
|
||||
int conflict_set_count;
|
||||
int trust_level = TRUST_UNKNOWN;
|
||||
strlist_t iter;
|
||||
|
||||
log_assert (dbs);
|
||||
|
||||
@ -1857,7 +1971,7 @@ get_trust (ctrl_t ctrl, PKT_public_key *pk,
|
||||
begin_transaction (ctrl, 0);
|
||||
in_transaction = 1;
|
||||
|
||||
policy = get_policy (dbs, fingerprint, email, &conflict);
|
||||
policy = get_policy (dbs, fingerprint, email, NULL);
|
||||
{
|
||||
/* See if the key is ultimately trusted. If so, we're done. */
|
||||
u32 kid[2];
|
||||
@ -1887,7 +2001,7 @@ get_trust (ctrl_t ctrl, PKT_public_key *pk,
|
||||
{
|
||||
policy = opt.tofu_default_policy;
|
||||
if (DBG_TRUST)
|
||||
log_debug ("TOFU: binding <key: %s, user id: %s>'s policy is "
|
||||
log_debug ("TOFU: binding <key: %s, user id: %s>'s policy is"
|
||||
" auto (default: %s).\n",
|
||||
fingerprint, email,
|
||||
tofu_policy_str (opt.tofu_default_policy));
|
||||
@ -1943,41 +2057,29 @@ get_trust (ctrl_t ctrl, PKT_public_key *pk,
|
||||
*
|
||||
* 3. We don't have a saved policy (policy == TOFU_POLICY_NONE)
|
||||
* (need to check for a conflict).
|
||||
*
|
||||
* In summary: POLICY is ask or none.
|
||||
*/
|
||||
|
||||
/* Look for conflicts. This is needed 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 = gpgsql_stepx
|
||||
(dbs->db, &dbs->s.get_trust_bindings_with_this_email,
|
||||
strings_collect_cb2, &bindings_with_this_email, &sqerr,
|
||||
"select distinct fingerprint from bindings where email = ?;",
|
||||
GPGSQL_ARG_STRING, email, GPGSQL_ARG_END);
|
||||
if (rc)
|
||||
/* Look for conflicts. This is needed in all 3 cases. */
|
||||
conflict_set = build_conflict_set (dbs, fingerprint, email);
|
||||
conflict_set_count = strlist_length (conflict_set);
|
||||
if (conflict_set_count == 0)
|
||||
{
|
||||
log_error (_("error reading TOFU database: %s\n"), sqerr);
|
||||
print_further_info ("listing fingerprints");
|
||||
sqlite3_free (sqerr);
|
||||
/* We should always at least have the current binding. */
|
||||
trust_level = _tofu_GET_TRUST_ERROR;
|
||||
goto out;
|
||||
}
|
||||
|
||||
bindings_with_this_email_count = strlist_length (bindings_with_this_email);
|
||||
if (bindings_with_this_email_count == 0
|
||||
if (conflict_set_count == 1
|
||||
&& (conflict_set->flags & BINDING_NEW)
|
||||
&& 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.
|
||||
*/
|
||||
/* We've never observed a binding with this email address 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. */
|
||||
* policy couldn't possibly be TOFU_POLICY_NONE. */
|
||||
log_assert (policy == TOFU_POLICY_NONE);
|
||||
|
||||
if (DBG_TRUST)
|
||||
@ -1997,16 +2099,37 @@ get_trust (ctrl_t ctrl, PKT_public_key *pk,
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (policy == TOFU_POLICY_NONE)
|
||||
if (conflict_set_count == 1
|
||||
&& (conflict_set->flags & BINDING_CONFLICT))
|
||||
{
|
||||
/* 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;
|
||||
/* No known conflicts now, but there was a conflict. This means
|
||||
* at somepoint, there was a conflict and we changed this
|
||||
* binding's policy to ask and set the conflicting key. The
|
||||
* conflict can go away if there is not a cross sig between the
|
||||
* two keys. In this case, just silently clear the conflict and
|
||||
* reset the policy to auto. */
|
||||
|
||||
log_assert (policy == TOFU_POLICY_ASK);
|
||||
|
||||
if (DBG_TRUST)
|
||||
log_debug ("TOFU: binding <key: %s, user id: %s> had a conflict, but it's been resolved (probably via cross sig).\n",
|
||||
fingerprint, email);
|
||||
|
||||
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 = tofu_policy_to_trust_level (TOFU_POLICY_AUTO);
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* 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
|
||||
@ -2031,51 +2154,53 @@ get_trust (ctrl_t ctrl, PKT_public_key *pk,
|
||||
ask_about_binding (ctrl,
|
||||
&policy,
|
||||
&trust_level,
|
||||
bindings_with_this_email_count,
|
||||
bindings_with_this_email,
|
||||
conflict,
|
||||
conflict_set,
|
||||
fingerprint,
|
||||
email,
|
||||
user_id);
|
||||
|
||||
out:
|
||||
if (in_transaction)
|
||||
end_transaction (ctrl, 0);
|
||||
|
||||
if (change_conflicting_to_ask)
|
||||
{
|
||||
if (! may_ask)
|
||||
/* Mark any conflicting bindings that have an automatic policy as
|
||||
* now requiring confirmation. */
|
||||
|
||||
if (! in_transaction)
|
||||
{
|
||||
/* If we weren't allowed to ask, also update this key as
|
||||
conflicting with itself. */
|
||||
rc = gpgsql_exec_printf
|
||||
(dbs->db, NULL, NULL, &sqerr,
|
||||
"update bindings set policy = %d, conflict = %Q"
|
||||
" where email = %Q"
|
||||
" and (policy = %d or (policy = %d and fingerprint = %Q));",
|
||||
TOFU_POLICY_ASK, fingerprint, email, TOFU_POLICY_AUTO,
|
||||
TOFU_POLICY_ASK, fingerprint);
|
||||
}
|
||||
else
|
||||
{
|
||||
rc = gpgsql_exec_printf
|
||||
(dbs->db, NULL, NULL, &sqerr,
|
||||
"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);
|
||||
begin_transaction (ctrl, 0);
|
||||
in_transaction = 1;
|
||||
}
|
||||
|
||||
if (rc)
|
||||
{
|
||||
log_error (_("error changing TOFU policy: %s\n"), sqerr);
|
||||
sqlite3_free (sqerr);
|
||||
sqerr = NULL;
|
||||
}
|
||||
/* If we weren't allowed to ask, also update this key as
|
||||
* conflicting with itself. */
|
||||
for (iter = may_ask ? conflict_set->next : conflict_set;
|
||||
iter; iter = iter->next)
|
||||
{
|
||||
rc = gpgsql_exec_printf
|
||||
(dbs->db, NULL, NULL, &sqerr,
|
||||
"update bindings set policy = %d, conflict = %Q"
|
||||
" where email = %Q and fingerprint = %Q and policy = %d;",
|
||||
TOFU_POLICY_ASK, fingerprint,
|
||||
email, iter->d, TOFU_POLICY_AUTO);
|
||||
if (rc)
|
||||
{
|
||||
log_error (_("error changing TOFU policy: %s\n"), sqerr);
|
||||
print_further_info ("binding: <key: %s, user id: %s>",
|
||||
fingerprint, user_id);
|
||||
sqlite3_free (sqerr);
|
||||
sqerr = NULL;
|
||||
}
|
||||
else if (DBG_TRUST)
|
||||
log_debug ("Set %s to conflict with %s\n",
|
||||
iter->d, fingerprint);
|
||||
}
|
||||
}
|
||||
|
||||
xfree (conflict);
|
||||
free_strlist (bindings_with_this_email);
|
||||
if (in_transaction)
|
||||
end_transaction (ctrl, 0);
|
||||
|
||||
free_strlist (conflict_set);
|
||||
|
||||
return trust_level;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user