diff options
author | Neal H. Walfield <[email protected]> | 2016-09-02 20:33:47 +0000 |
---|---|---|
committer | Neal H. Walfield <[email protected]> | 2016-09-05 13:14:27 +0000 |
commit | 1f1f56e606c1cb28eec68c60bd8bcb7ab30805de (patch) | |
tree | 7d9da2fcad30ad38d452f67de711ab8c05dba3a4 | |
parent | tests: Update README. (diff) | |
download | gnupg-1f1f56e606c1cb28eec68c60bd8bcb7ab30805de.tar.gz gnupg-1f1f56e606c1cb28eec68c60bd8bcb7ab30805de.zip |
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 <[email protected]>
-rw-r--r-- | g10/tofu.c | 743 |
1 files changed, 434 insertions, 309 deletions
diff --git a/g10/tofu.c b/g10/tofu.c index 75df30af6..d4f687660 100644 --- a/g10/tofu.c +++ b/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); + begin_transaction (ctrl, 0); + in_transaction = 1; } - else + + /* 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, fingerprint, - TOFU_POLICY_AUTO); + " 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); } - - if (rc) - { - log_error (_("error changing TOFU policy: %s\n"), sqerr); - sqlite3_free (sqerr); - sqerr = NULL; - } } - xfree (conflict); - free_strlist (bindings_with_this_email); + if (in_transaction) + end_transaction (ctrl, 0); + + free_strlist (conflict_set); return trust_level; } |