[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