From 6052c147091935fc0321ba24f4a44146df70ef01 Mon Sep 17 00:00:00 2001 From: "Neal H. Walfield" Date: Mon, 29 Aug 2016 16:16:44 +0200 Subject: [PATCH] g10: Change tofu_register & tofu_get_validity to process multiple uids. * g10/tofu.c (tofu_register): Take a list of user ids, not a single user id. Only register the bindings, don't compute the trust. Thus, change return type to an int and remove the may_ask parameter. Update callers. (tofu_get_validity): Take a list of user ids, not a single user id. Update callers. Observe signatures made by expired user ids, but don't include them in the trust calculation. -- Signed-off-by: Neal H. Walfield --- g10/tofu.c | 310 ++++++++++++++++++++++++++++---------------------- g10/tofu.h | 38 ++++--- g10/trustdb.c | 91 ++++++++------- 3 files changed, 240 insertions(+), 199 deletions(-) diff --git a/g10/tofu.c b/g10/tofu.c index 809dac9b8..da09cd5ce 100644 --- a/g10/tofu.c +++ b/g10/tofu.c @@ -2164,8 +2164,9 @@ email_from_user_id (const char *user_id) return email; } -/* Register the signature with the binding . - The fingerprint is taken from the primary key packet PK. +/* Register the signature with the bindings , + for each USER_ID in USER_ID_LIST. The fingerprint is taken from + the primary key packet PK. SIG_DIGEST_BIN is the binary representation of the message's digest. SIG_DIGEST_BIN_LEN is its length. @@ -2181,159 +2182,152 @@ email_from_user_id (const char *user_id) This is necessary if there is a conflict or the binding's policy is TOFU_POLICY_ASK. - This function returns the binding's trust level on return. If an - error occurs, this function returns TRUST_UNKNOWN. */ -int -tofu_register (ctrl_t ctrl, PKT_public_key *pk, const char *user_id, + This function returns 0 on success and an error code if an error + occured. */ +gpg_error_t +tofu_register (ctrl_t ctrl, PKT_public_key *pk, strlist_t user_id_list, const byte *sig_digest_bin, int sig_digest_bin_len, - time_t sig_time, const char *origin, int may_ask) + time_t sig_time, const char *origin) { + gpg_error_t rc; tofu_dbs_t dbs; char *fingerprint = NULL; + strlist_t user_id; char *email = NULL; char *err = NULL; - int rc; - int trust_level = TRUST_UNKNOWN; char *sig_digest; unsigned long c; - int already_verified = 0; - - sig_digest = make_radix64_string (sig_digest_bin, sig_digest_bin_len); dbs = opendbs (ctrl); if (! dbs) { + rc = gpg_error (GPG_ERR_GENERAL); log_error (_("error opening TOFU database: %s\n"), - gpg_strerror (GPG_ERR_GENERAL)); - goto die; - } - - fingerprint = hexfingerprint (pk, NULL, 0); - - if (! *user_id) - { - log_debug ("TOFU: user id is empty. Can't continue.\n"); - goto die; - } - - email = email_from_user_id (user_id); - - if (! origin) - /* The default origin is simply "unknown". */ - origin = "unknown"; - - /* It's necessary to get the trust so that we are certain that the - binding has been registered. */ - trust_level = get_trust (dbs, pk, fingerprint, email, user_id, may_ask); - if (trust_level == _tofu_GET_TRUST_ERROR) - /* An error. */ - { - trust_level = TRUST_UNKNOWN; - goto die; + gpg_strerror (rc)); + return rc; } /* We do a query and then an insert. Make sure they are atomic by wrapping them in a transaction. */ rc = begin_transaction (ctrl, 0); if (rc) - goto die; + return rc; - /* If we've already seen this signature before, then don't add - it again. */ - rc = gpgsql_stepx - (dbs->db, &dbs->s.register_already_seen, - get_single_unsigned_long_cb2, &c, &err, - "select count (*)\n" - " from signatures left join bindings\n" - " on signatures.binding = bindings.oid\n" - " where fingerprint = ? and email = ? and sig_time = ?\n" - " and sig_digest = ?", - SQLITE_ARG_STRING, fingerprint, SQLITE_ARG_STRING, email, - SQLITE_ARG_LONG_LONG, (long long) sig_time, - SQLITE_ARG_STRING, sig_digest, - SQLITE_ARG_END); - if (rc) + sig_digest = make_radix64_string (sig_digest_bin, sig_digest_bin_len); + fingerprint = hexfingerprint (pk, NULL, 0); + + if (! origin) + /* The default origin is simply "unknown". */ + origin = "unknown"; + + for (user_id = user_id_list; user_id; user_id = user_id->next) { - log_error (_("error reading TOFU database: %s\n"), err); - print_further_info ("checking existence"); - sqlite3_free (err); - } - else if (c > 1) - /* Duplicates! This should not happen. In particular, - because is the - primary key! */ - log_debug ("SIGNATURES DB contains duplicate records" - " <%s, %s, 0x%lx, %s, %s>." - " Please report.\n", - fingerprint, email, (unsigned long) sig_time, - sig_digest, origin); - else if (c == 1) - { - already_verified = 1; + email = email_from_user_id (user_id->d); + if (DBG_TRUST) - log_debug ("Already observed the signature" - " <%s, %s, 0x%lx, %s, %s>\n", - fingerprint, email, (unsigned long) sig_time, - sig_digest, origin); - } - else if (opt.dry_run) - { - log_info ("TOFU database update skipped due to --dry-run\n"); - } - else - /* This is the first time that we've seen this signature. - Record it. */ - { - if (DBG_TRUST) - log_debug ("TOFU: Saving signature <%s, %s, %s>\n", - fingerprint, email, sig_digest); + log_debug ("TOFU: Registering signature %s with binding" + " \n", + sig_digest, fingerprint, email); - log_assert (c == 0); + /* Make sure the binding exists and record any TOFU + conflicts. */ + if (get_trust (dbs, pk, fingerprint, email, user_id->d, 0) + == _tofu_GET_TRUST_ERROR) + { + rc = gpg_error (GPG_ERR_GENERAL); + xfree (email); + break; + } + /* If we've already seen this signature before, then don't add + it again. */ rc = gpgsql_stepx - (dbs->db, &dbs->s.register_insert, NULL, NULL, &err, - "insert into signatures\n" - " (binding, sig_digest, origin, sig_time, time)\n" - " values\n" - " ((select oid from bindings\n" - " where fingerprint = ? and email = ?),\n" - " ?, ?, ?, strftime('%s', 'now'));", - SQLITE_ARG_STRING, fingerprint, SQLITE_ARG_STRING, email, - SQLITE_ARG_STRING, sig_digest, SQLITE_ARG_STRING, origin, + (dbs->db, &dbs->s.register_already_seen, + get_single_unsigned_long_cb2, &c, &err, + "select count (*)\n" + " from signatures left join bindings\n" + " on signatures.binding = bindings.oid\n" + " where fingerprint = ? and email = ? and sig_time = ?\n" + " and sig_digest = ?", + SQLITE_ARG_STRING, fingerprint, SQLITE_ARG_STRING, email, SQLITE_ARG_LONG_LONG, (long long) sig_time, + SQLITE_ARG_STRING, sig_digest, SQLITE_ARG_END); if (rc) - { - log_error (_("error updating TOFU database: %s\n"), err); - print_further_info ("insert signatures"); - sqlite3_free (err); - } + { + log_error (_("error reading TOFU database: %s\n"), err); + print_further_info ("checking existence"); + sqlite3_free (err); + } + else if (c > 1) + /* Duplicates! This should not happen. In particular, + because is the + primary key! */ + log_debug ("SIGNATURES DB contains duplicate records" + " ." + " Please report.\n", + fingerprint, email, (unsigned long) sig_time, + sig_digest, origin); + else if (c == 1) + { + if (DBG_TRUST) + log_debug ("Already observed the signature and binding" + " \n", + fingerprint, email, (unsigned long) sig_time, + sig_digest, origin); + } + else if (opt.dry_run) + { + log_info ("TOFU database update skipped due to --dry-run\n"); + } + else + /* This is the first time that we've seen this signature and + binding. Record it. */ + { + if (DBG_TRUST) + log_debug ("TOFU: Saving signature" + " \n", + fingerprint, email, sig_digest); + + log_assert (c == 0); + + rc = gpgsql_stepx + (dbs->db, &dbs->s.register_insert, NULL, NULL, &err, + "insert into signatures\n" + " (binding, sig_digest, origin, sig_time, time)\n" + " values\n" + " ((select oid from bindings\n" + " where fingerprint = ? and email = ?),\n" + " ?, ?, ?, strftime('%s', 'now'));", + SQLITE_ARG_STRING, fingerprint, SQLITE_ARG_STRING, email, + SQLITE_ARG_STRING, sig_digest, SQLITE_ARG_STRING, origin, + SQLITE_ARG_LONG_LONG, (long long) sig_time, + SQLITE_ARG_END); + if (rc) + { + log_error (_("error updating TOFU database: %s\n"), err); + print_further_info ("insert signatures"); + sqlite3_free (err); + } + } + + xfree (email); + + if (rc) + break; } - /* It only matters whether we abort or commit the transaction - (so long as we do something) if we execute the insert. */ if (rc) - rc = rollback_transaction (ctrl); + rollback_transaction (ctrl); else rc = end_transaction (ctrl, 0); - if (rc) - { - sqlite3_free (err); - goto die; - } - die: - if (may_ask && trust_level != TRUST_ULTIMATE) - /* It's only appropriate to show the statistics in an interactive - context. */ - show_statistics (dbs, fingerprint, email, user_id, - already_verified ? NULL : sig_digest, NULL); - - xfree (email); xfree (fingerprint); xfree (sig_digest); - return trust_level; + return rc; } /* Combine a trust level returned from the TOFU trust model with a @@ -2431,8 +2425,9 @@ tofu_write_tfs_record (ctrl_t ctrl, estream_t fp, } -/* Return the validity (TRUST_NEVER, etc.) of the binding - . +/* Return the validity (TRUST_NEVER, etc.) of the bindings + , for each USER_ID in USER_ID_LIST. If + USER_ID_LIST->FLAG is set, then the id is considered to be expired. PK is the primary key packet. @@ -2442,43 +2437,80 @@ tofu_write_tfs_record (ctrl_t ctrl, estream_t fp, Returns TRUST_UNDEFINED if an error occurs. */ int -tofu_get_validity (ctrl_t ctrl, PKT_public_key *pk, const char *user_id, +tofu_get_validity (ctrl_t ctrl, PKT_public_key *pk, strlist_t user_id_list, int may_ask) { tofu_dbs_t dbs; char *fingerprint = NULL; - char *email = NULL; - int trust_level = TRUST_UNDEFINED; + strlist_t user_id; + int trust_level = TRUST_UNKNOWN; dbs = opendbs (ctrl); if (! dbs) { log_error (_("error opening TOFU database: %s\n"), gpg_strerror (GPG_ERR_GENERAL)); - goto die; + return TRUST_UNDEFINED; } fingerprint = hexfingerprint (pk, NULL, 0); - if (! *user_id) + begin_transaction (ctrl, 0); + + for (user_id = user_id_list; user_id; user_id = user_id->next) { - log_debug ("user id is empty." - " Can't get TOFU validity for this binding.\n"); - goto die; + char *email = email_from_user_id (user_id->d); + + /* Always call get_trust to make sure the binding is + registered. */ + int tl = get_trust (dbs, pk, fingerprint, email, user_id->d, may_ask); + if (tl == _tofu_GET_TRUST_ERROR) + { + /* An error. */ + trust_level = TRUST_UNDEFINED; + xfree (email); + goto die; + } + + if (DBG_TRUST) + log_debug ("TOFU: validity for : %s%s.\n", + fingerprint, email, + trust_value_to_string (tl), + user_id->flags ? " (but expired)" : ""); + + if (user_id->flags) + tl = TRUST_EXPIRED; + + if (may_ask && tl != TRUST_ULTIMATE && tl != TRUST_EXPIRED) + show_statistics (dbs, fingerprint, email, user_id->d, NULL, NULL); + + if (tl == TRUST_NEVER) + trust_level = TRUST_NEVER; + else if (tl == TRUST_EXPIRED) + /* Ignore expired bindings in the trust calculation. */ + ; + else if (tl > trust_level) + { + /* The expected values: */ + log_assert (tl == TRUST_UNKNOWN || tl == TRUST_UNDEFINED + || tl == TRUST_MARGINAL || tl == TRUST_FULLY + || tl == TRUST_ULTIMATE); + + /* We assume the following ordering: */ + log_assert (TRUST_UNKNOWN < TRUST_UNDEFINED); + log_assert (TRUST_UNDEFINED < TRUST_MARGINAL); + log_assert (TRUST_MARGINAL < TRUST_FULLY); + log_assert (TRUST_FULLY < TRUST_ULTIMATE); + + trust_level = tl; + } + + xfree (email); } - email = email_from_user_id (user_id); - - trust_level = get_trust (dbs, pk, fingerprint, email, user_id, may_ask); - if (trust_level == _tofu_GET_TRUST_ERROR) - /* An error. */ - trust_level = TRUST_UNDEFINED; - - if (may_ask && trust_level != TRUST_ULTIMATE) - show_statistics (dbs, fingerprint, email, user_id, NULL, NULL); - die: - xfree (email); + end_transaction (ctrl, 0); + xfree (fingerprint); return trust_level; } diff --git a/g10/tofu.h b/g10/tofu.h index d6854e9bf..b9826c9e1 100644 --- a/g10/tofu.h +++ b/g10/tofu.h @@ -59,7 +59,7 @@ enum tofu_policy TOFU_POLICY_ASK = 5, - /* Privat evalue used only within tofu.c. */ + /* Private value used only within tofu.c. */ _tofu_GET_POLICY_ERROR = 100 }; @@ -72,16 +72,19 @@ const char *tofu_policy_str (enum tofu_policy policy); (e.g., TRUST_BAD) in light of the current configuration. */ int tofu_policy_to_trust_level (enum tofu_policy policy); -/* Register the binding and the signature - described by SIGS_DIGEST and SIG_TIME, which it generated. Origin - describes where the signed data came from, e.g., "email:claws" - (default: "unknown"). If MAY_ASK is 1, then this function may - interact with the user in the case of a conflict or if the - binding's policy is ask. This function returns the binding's trust - level. If an error occurs, it returns TRUST_UNKNOWN. */ -int tofu_register (ctrl_t ctrl, PKT_public_key *pk, const char *user_id, - const byte *sigs_digest, int sigs_digest_len, - time_t sig_time, const char *origin, int may_ask); +/* Register the bindings , for each USER_ID in + USER_ID_LIST, and the signature described by SIGS_DIGEST and + SIG_TIME, which it generated. Origin describes where the signed + data came from, e.g., "email:claws" (default: "unknown"). Note: + this function does not interact with the user, If there is a + conflict, or if the binding's policy is ask, the actual interaction + is deferred until tofu_get_validity is called.. Set the string + list FLAG to indicate that a specified user id is expired. This + function returns 0 on success and an error code on failure. */ +gpg_error_t tofu_register (ctrl_t ctrl, PKT_public_key *pk, + strlist_t user_id_list, + const byte *sigs_digest, int sigs_digest_len, + time_t sig_time, const char *origin); /* Combine a trust level returned from the TOFU trust model with a trust level returned by the PGP trust model. This is primarily of @@ -92,12 +95,15 @@ int tofu_wot_trust_combine (int tofu, int wot); gpg_error_t tofu_write_tfs_record (ctrl_t ctrl, estream_t fp, PKT_public_key *pk, const char *user_id); -/* Determine the validity (TRUST_NEVER, etc.) of the binding - . If MAY_ASK is 1, then this function may - interact with the user. If not, TRUST_UNKNOWN is returned. If an - error occurs, TRUST_UNDEFINED is returned. */ +/* Determine the validity (TRUST_NEVER, etc.) of the binding . If MAY_ASK is 1, then this function may interact with + the user. If not, TRUST_UNKNOWN is returned if an interaction is + required. Set the string list FLAGS to indicate that a specified + user id is expired. If an error occurs, TRUST_UNDEFINED is + returned. */ int tofu_get_validity (ctrl_t ctrl, - PKT_public_key *pk, const char *user_id, int may_ask); + PKT_public_key *pk, strlist_t user_id_list, + int may_ask); /* Set the policy for all non-revoked user ids in the keyblock KB to POLICY. */ diff --git a/g10/trustdb.c b/g10/trustdb.c index dd74d187b..4181240b7 100644 --- a/g10/trustdb.c +++ b/g10/trustdb.c @@ -988,7 +988,7 @@ tdb_get_validity_core (ctrl_t ctrl, int may_ask) { TRUSTREC trec, vrec; - gpg_error_t err; + gpg_error_t err = 0; ulong recno; #ifdef USE_TOFU unsigned int tofu_validity = TRUST_UNKNOWN; @@ -1022,21 +1022,18 @@ tdb_get_validity_core (ctrl_t ctrl, #ifdef USE_TOFU if (opt.trust_model == TM_TOFU || opt.trust_model == TM_TOFU_PGP) { - kbnode_t user_id_node = NULL; - kbnode_t n = NULL; /* Silence -Wmaybe-uninitialized. */ - int user_ids = 0; - int user_ids_expired = 0; + kbnode_t kb = NULL; + kbnode_t n = NULL; + strlist_t user_id_list = NULL; - /* If the caller didn't supply a user id then iterate over all - uids. */ + /* If the caller didn't supply a user id then use all uids. */ if (! uid) - user_id_node = n = get_pubkeyblock (main_pk->keyid); + kb = n = get_pubkeyblock (main_pk->keyid); - while (uid - || (n = find_next_kbnode (n, PKT_USER_ID))) + while (uid || (n = find_next_kbnode (n, PKT_USER_ID))) { - unsigned int tl; PKT_user_id *user_id; + int expired = 0; if (uid) user_id = uid; @@ -1044,8 +1041,8 @@ tdb_get_validity_core (ctrl_t ctrl, user_id = n->pkt->pkt.user_id; /* If the user id is revoked or expired, then skip it. */ - if (user_id->is_revoked || user_id->is_expired) - { + if (user_id->is_revoked || user_id->is_expired) + { if (DBG_TRUST) { char *s; @@ -1060,42 +1057,48 @@ tdb_get_validity_core (ctrl_t ctrl, s, user_id->name); } - continue; - } + if (user_id->is_revoked) + continue; - user_ids ++; + expired = 1; + } - if (sig) - tl = tofu_register (ctrl, main_pk, user_id->name, - sig->digest, sig->digest_len, - sig->timestamp, "unknown", - may_ask); - else - tl = tofu_get_validity (ctrl, main_pk, user_id->name, may_ask); + add_to_strlist (&user_id_list, user_id->name); + user_id_list->flags = expired; - if (tl == TRUST_EXPIRED) - user_ids_expired ++; - else if (tl == TRUST_UNDEFINED || tl == TRUST_UNKNOWN) - ; - else if (tl == TRUST_NEVER) - tofu_validity = TRUST_NEVER; - else - { - log_assert (tl == TRUST_MARGINAL - || tl == TRUST_FULLY - || tl == TRUST_ULTIMATE); + if (uid) + /* If the caller specified a user id, then we stop + now. */ + break; + } - if (tl > tofu_validity) - /* XXX: We we really want the max? */ - tofu_validity = tl; - } + /* Process the user ids in the order they appear in the key + block. */ + strlist_rev (&user_id_list); - if (uid) - /* If the caller specified a user id, then we stop - now. */ - break; - } - release_kbnode (user_id_node); + /* It only makes sense to observe any signature before getting + the validity. This is because if the current signature + results in a conflict, then we damn well want to take that + into account. */ + if (sig) + { + err = tofu_register (ctrl, main_pk, user_id_list, + sig->digest, sig->digest_len, + sig->timestamp, "unknown"); + if (err) + { + log_error ("TOFU: error registering signature: %s\n", + gpg_strerror (err)); + + tofu_validity = TRUST_UNKNOWN; + } + } + if (! err) + tofu_validity = tofu_get_validity (ctrl, main_pk, user_id_list, + may_ask); + + free_strlist (user_id_list); + release_kbnode (kb); } #endif /*USE_TOFU*/