[PATCH GnuPG] RFC: g10: Improve and unify key selection.
Justus Winter
justus at g10code.com
Wed Nov 2 12:40:58 CET 2016
* g10/getkey.c (struct pubkey_cmp_cookie): New type.
(key_is_ok, uid_is_ok, subkey_is_ok): New functions.
(pubkey_cmp): Likewise.
(get_best_pubkey_byname): Likewise.
* g10/keydb.h (get_best_pubkey_byname): New prototype.
* g10/keylist.c (locate_one): Use the new function.
* g10/pkclist.c (find_and_check_key): Likewise.
--
When a name resembling a mail address is given to either --locate-key
or --recipient, rank the search results and use only the most relevant
key.
This also lets us query which key will be used for encryption using
--locate-key. XXX: Is this true? B/c if we use --recipient, we only
consider keys usable for encryption. Otoh, --locate-key has no such
constraint.
GnuPG-bug-id: 2359
Signed-off-by: Justus Winter <justus at g10code.com>
---
g10/getkey.c | 211 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
g10/keydb.h | 7 ++
g10/keylist.c | 2 +-
g10/pkclist.c | 2 +-
4 files changed, 220 insertions(+), 2 deletions(-)
diff --git a/g10/getkey.c b/g10/getkey.c
index 5ef5fc3..7da75c0 100644
--- a/g10/getkey.c
+++ b/g10/getkey.c
@@ -1461,6 +1461,217 @@ get_pubkey_byname (ctrl_t ctrl, GETKEY_CTX * retctx, PKT_public_key * pk,
return rc;
}
+
+
+/* Comparison machinery for get_best_pubkey_byname. */
+
+/* First we have a struct to cache computed information about the key
+ in question. */
+struct pubkey_cmp_cookie
+{
+ int valid; /* Are the information valid? */
+ PKT_public_key key; /* The key. */
+ PKT_user_id *uid; /* The matching UID packet. */
+ unsigned int validity; /* Computed validity of (KEY, UID). */
+ u32 creation_time; /* Creation time of the newest subkey
+ capable of encryption. */
+};
+
+/* Then we have a series of helper functions. */
+static int
+key_is_ok (const PKT_public_key *key)
+{
+ return ! key->has_expired && ! key->flags.revoked
+ && key->flags.valid && ! key->flags.disabled;
+}
+
+static int
+uid_is_ok (const PKT_public_key *key, const PKT_user_id *uid)
+{
+ return key_is_ok (key) && ! uid->is_revoked
+#if I_FIGURE_OUT_WHAT_IT_MEANS_FOR_A_UID_TO_BE_INVALID
+ && ! uid->is_invalid
+#endif
+ ;
+}
+
+static int
+subkey_is_ok (const PKT_public_key *sub)
+{
+ return ! sub->flags.revoked && sub->flags.valid && ! sub->flags.disabled;
+}
+
+/* Finally this function compares a NEW key to the former candidate
+ OLD. */
+static int
+pubkey_cmp (ctrl_t ctrl, const char *name, struct pubkey_cmp_cookie *old,
+ struct pubkey_cmp_cookie *new, KBNODE new_keyblock)
+{
+ KBNODE n;
+
+ new->creation_time = 0;
+ for (n = find_next_kbnode (new_keyblock, PKT_PUBLIC_SUBKEY);
+ n; n = find_next_kbnode (n, PKT_PUBLIC_SUBKEY))
+ {
+ PKT_public_key *sub = n->pkt->pkt.public_key;
+
+ if ((sub->pubkey_usage & PUBKEY_USAGE_ENC) == 0)
+ continue;
+
+ if (! subkey_is_ok (sub))
+ continue;
+
+ if (sub->timestamp > new->creation_time)
+ new->creation_time = sub->timestamp;
+ }
+
+ new->uid = NULL;
+ for (n = find_next_kbnode (new_keyblock, PKT_USER_ID);
+ n; n = find_next_kbnode (n, PKT_USER_ID))
+ {
+ PKT_user_id *uid = n->pkt->pkt.user_id;
+ char *mbox = mailbox_from_userid (uid->name);
+ int match = mbox ? strcasecmp (name, mbox) == 0 : 0;
+
+ xfree (mbox);
+ if (! match)
+ continue;
+
+ new->uid = uid;
+ new->validity =
+ get_validity (ctrl, &new->key, uid, NULL, 0) & TRUST_MASK;
+ new->valid = 1;
+
+ if (! old->valid)
+ return -1; /* No OLD key. */
+
+ if (! uid_is_ok (&old->key, old->uid) && uid_is_ok (&new->key, uid))
+ return 1; /* Validity of the NEW key is better. */
+
+ if (old->validity < new->validity)
+ return 1; /* Validity of the NEW key is better. */
+
+ if (old->validity == new->validity && uid_is_ok (&new->key, uid)
+ && old->creation_time < new->creation_time)
+ return 1; /* Both keys are of the same validity, but the
+ NEW key is newer. */
+ }
+
+ /* Stick with the OLD key. */
+ return -1;
+}
+
+
+/* This function works like get_pubkey_byname, but if the name
+ resembles a mail address, the results are ranked and only the best
+ result is returned. */
+int
+get_best_pubkey_byname (ctrl_t ctrl, GETKEY_CTX *retctx, PKT_public_key *pk,
+ const char *name, KBNODE *ret_keyblock,
+ int include_unusable, int no_akl)
+{
+ int rc;
+ struct getkey_ctx_s *ctx = NULL;
+ KBNODE keyblock;
+
+ if (ret_keyblock == NULL)
+ ret_keyblock = &keyblock;
+
+ rc = get_pubkey_byname (ctrl, &ctx, pk, name, ret_keyblock,
+ NULL, include_unusable, no_akl);
+ if (rc)
+ {
+ if (ctx)
+ getkey_end (ctx);
+ if (retctx)
+ *retctx = NULL;
+ return rc;
+ }
+
+ if (is_valid_mailbox (name))
+ {
+ /* Rank results and return only the most relevant key. */
+ struct pubkey_cmp_cookie best = { 0 }, new;
+ while (getkey_next (ctx, &new.key, NULL) == 0)
+ {
+ int diff = pubkey_cmp (ctrl, name, &best, &new, *ret_keyblock);
+ if (diff < 0)
+ {
+ /* New key is better. */
+ release_public_key_parts (&best.key);
+ best = new;
+ }
+ else if (diff > 0)
+ {
+ /* Old key is better. */
+ release_public_key_parts (&new.key);
+ }
+ else
+ {
+ /* A tie. Keep the old key. */
+ release_public_key_parts (&new.key);
+ }
+ }
+ getkey_end (ctx);
+ ctx = NULL;
+
+ if (best.valid)
+ {
+ if (retctx || ret_keyblock)
+ {
+ ctx = xtrycalloc (1, sizeof **retctx);
+ if (! ctx)
+ rc = gpg_error_from_syserror ();
+ else
+ {
+ ctx->kr_handle = keydb_new ();
+ if (! ctx->kr_handle)
+ {
+ xfree (ctx);
+ *retctx = NULL;
+ rc = gpg_error_from_syserror ();
+ }
+ else
+ {
+ u32 *keyid = pk_keyid (&best);
+ ctx->exact = 1;
+ ctx->nitems = 1;
+ ctx->items[0].mode = KEYDB_SEARCH_MODE_LONG_KID;
+ ctx->items[0].u.kid[0] = keyid[0];
+ ctx->items[0].u.kid[1] = keyid[1];
+
+ if (ret_keyblock)
+ {
+ release_kbnode (*ret_keyblock);
+ *ret_keyblock = NULL;
+ rc = getkey_next (ctx, NULL, ret_keyblock);
+ }
+ }
+ }
+ }
+
+ if (pk)
+ *pk = best.key;
+ else
+ release_public_key_parts (&best.key);
+ }
+ }
+
+ if (rc && ctx)
+ {
+ getkey_end (ctx);
+ ctx = NULL;
+ }
+
+ if (retctx && ctx)
+ *retctx = ctx;
+ else
+ getkey_end (ctx);
+
+ return rc;
+}
+
+
/* Get a public key from a file.
*
diff --git a/g10/keydb.h b/g10/keydb.h
index 35512bb..11cc695 100644
--- a/g10/keydb.h
+++ b/g10/keydb.h
@@ -324,6 +324,13 @@ int get_pubkey_byname (ctrl_t ctrl,
KBNODE *ret_keyblock, KEYDB_HANDLE *ret_kdbhd,
int include_unusable, int no_akl );
+/* Likewise, but only return the best match if NAME resembles a mail
+ address. */
+int get_best_pubkey_byname (ctrl_t ctrl,
+ GETKEY_CTX *retctx, PKT_public_key *pk,
+ const char *name, KBNODE *ret_keyblock,
+ int include_unusable, int no_akl);
+
/* Get a public key directly from file FNAME. */
gpg_error_t get_pubkey_fromfile (ctrl_t ctrl,
PKT_public_key *pk, const char *fname);
diff --git a/g10/keylist.c b/g10/keylist.c
index 212d77e..51dc409 100644
--- a/g10/keylist.c
+++ b/g10/keylist.c
@@ -650,7 +650,7 @@ locate_one (ctrl_t ctrl, strlist_t names)
for (sl = names; sl; sl = sl->next)
{
- rc = get_pubkey_byname (ctrl, &ctx, NULL, sl->d, &keyblock, NULL, 1, 0);
+ rc = get_best_pubkey_byname (ctrl, &ctx, NULL, sl->d, &keyblock, 1, 0);
if (rc)
{
if (gpg_err_code (rc) != GPG_ERR_NO_PUBKEY)
diff --git a/g10/pkclist.c b/g10/pkclist.c
index da4cc06..eef3437 100644
--- a/g10/pkclist.c
+++ b/g10/pkclist.c
@@ -838,7 +838,7 @@ find_and_check_key (ctrl_t ctrl, const char *name, unsigned int use,
if (from_file)
rc = get_pubkey_fromfile (ctrl, pk, name);
else
- rc = get_pubkey_byname (ctrl, NULL, pk, name, NULL, NULL, 0, 0);
+ rc = get_best_pubkey_byname (ctrl, NULL, pk, name, NULL, 0, 0);
if (rc)
{
int code;
--
2.10.1
More information about the Gnupg-devel
mailing list