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 <neal@g10code.com>
This commit is contained in:
Neal H. Walfield 2016-08-29 16:16:44 +02:00
parent 33e97813d7
commit 6052c14709
3 changed files with 240 additions and 199 deletions

View File

@ -2164,8 +2164,9 @@ email_from_user_id (const char *user_id)
return email;
}
/* Register the signature with the binding <fingerprint, USER_ID>.
The fingerprint is taken from the primary key packet PK.
/* Register the signature with the bindings <fingerprint, USER_ID>,
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 <fingerprint, email, sig_time, sig_digest> 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"
" <key: %s, user id: %s>\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 <fingerprint, email, sig_time, sig_digest> is the
primary key! */
log_debug ("SIGNATURES DB contains duplicate records"
" <key: %s, fingerprint: %s, time: 0x%lx, sig: %s,"
" origin: %s>."
" 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"
" <key: %s, user id: %s, time: 0x%lx, sig: %s,"
" origin: %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 and
binding. Record it. */
{
if (DBG_TRUST)
log_debug ("TOFU: Saving signature"
" <key: %s, user id: %s, sig: %s>\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
<FINGERPRINT, USER_ID>.
/* Return the validity (TRUST_NEVER, etc.) of the bindings
<FINGERPRINT, USER_ID>, 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 <key: %s, user id: %s>: %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;
}

View File

@ -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 <PK, USER_ID> 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 <PK, USER_ID>, 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
<PK, USER_ID>. 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 <PK,
USER_ID>. 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. */

View File

@ -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*/