Kevin J. McCarthy wrote:
> The interface issues can be discussed and implemented later.  The
> purpose of this patchset is simply to allow the user to enter a
> fingerprint when searching for a key.
> 
> Putting it in a pgp_uid_t is the easiest approach, but it's not the
> correct approach.  I'd really rather have it done right the first time.

This was a little more involved than just changing the fingerprint field
to an array of length 41, so I thought I'd step up and do what I was
asking for.

Attached are three patches.  The first changes the fingerprint to
"char *" and modifies pgppubring.c as needed.  The second adds fpr
parsing to get_candidates() and stores it in the new fingerprint field
in the key.  The last patch is a folded version of your two patches,
slightly cleaned up with the pgp_uid_t stuff removed.  (I didn't touch
the crypt_get_fingerprint_or_id() function).

If you are okay with my modifications to your patches, I'll push these
after some more testing and giving others a chance to review them.

-Kevin
# HG changeset patch
# User Kevin McCarthy <ke...@8t8.us>
# Date 1423882226 28800
#      Fri Feb 13 18:50:26 2015 -0800
# Node ID 92c80d8bb14487e155a51b5e18646a655c506c28
# Parent  385d7434c9d6f44c732fd12fc76d543f9d5d7233
Convert pgp_key_t fingerprint to a char* (see #3695)

Currently only pgppubring.c is using the fingerprint field, and is using
it to store a binary version of the fingerprint.

Convert the field to store a null-terminated string.
Modify pgppubring.c to use to use the new field.

diff --git a/pgplib.c b/pgplib.c
--- a/pgplib.c
+++ b/pgplib.c
@@ -178,16 +178,17 @@
 
   if (!kpp || !*kpp)
     return;
 
   kp = *kpp;
 
   pgp_free_uid (&kp->address);
   FREE (&kp->keyid);
+  FREE (&kp->fingerprint);
   /* mutt_crypt.h: 'typedef struct pgp_keyinfo *pgp_key_t;' */
   FREE (kpp);          /* __FREE_CHECKED__ */
 }
 
 pgp_key_t pgp_remove_key (pgp_key_t *klist, pgp_key_t key)
 {
   pgp_key_t *last;
   pgp_key_t p, q, r;
diff --git a/pgplib.h b/pgplib.h
--- a/pgplib.h
+++ b/pgplib.h
@@ -29,32 +29,26 @@
   unsigned long sid1;
   unsigned long sid2;
 }
 pgp_sig_t;
 
 struct pgp_keyinfo
 {
   char *keyid;
+  char *fingerprint;
   struct pgp_uid *address;
   int flags;
   short keylen;
   time_t gen_time;
   int numalg;
   const char *algorithm;
   struct pgp_keyinfo *parent;
   struct pgp_signature *sigs;
   struct pgp_keyinfo *next;
-
-  short fp_len;                          /* length of fingerprint.
-                                  * 20 for sha-1, 16 for md5.
-                                  */
-  unsigned char fingerprint[20];  /* large enough to hold SHA-1 and RIPEMD160
-                                     hashes (20 bytes), MD5 hashes just use the
-                                     first 16 bytes */
 };
 /* Note, that pgp_key_t is now pointer and declared in crypt.h */
 
 typedef struct pgp_uid
 {
   char *addr;
   short trust;
   int flags;
diff --git a/pgppubring.c b/pgppubring.c
--- a/pgppubring.c
+++ b/pgppubring.c
@@ -151,16 +151,33 @@
       snprintf (kring, sizeof (kring), "%s/pubring.%s", pgppath, version == 2 
? "pgp" : "pkr");
   }
   
   pgpring_find_candidates (kring, (const char**) argv + optind, argc - optind);
     
   return 0;
 }
 
+static char *binary_fingerprint_to_string (unsigned char *buff, size_t length)
+{
+  int i;
+  char *fingerprint, *pf;
+
+  pf = fingerprint = (char *)safe_malloc ((length * 2) + 1);
+
+  for (i = 0; i < length; i++)
+  {
+    sprintf (pf, "%02X", buff[i]);
+    pf += 2;
+  }
+  *pf = 0;
+
+  return fingerprint;
+}
+
 
 /* The actual key ring parser */
 static void pgp_make_pgp2_fingerprint (unsigned char *buff,
                                        unsigned char *digest)
 {
   struct md5_ctx ctx;
   unsigned int size = 0;
 
@@ -216,22 +233,19 @@
 
   p->numalg = alg;
   p->algorithm = pgp_pkalgbytype (alg);
   p->flags |= pgp_get_abilities (alg);
 
   if (dump_fingerprints)
   {
     /* j now points to the key material, which we need for the fingerprint */
-    p->fp_len = MD5_DIGEST_LENGTH;
     pgp_make_pgp2_fingerprint (&buff[j], digest);
-    memcpy (p->fingerprint, digest, MD5_DIGEST_LENGTH);
+    p->fingerprint = binary_fingerprint_to_string (digest, MD5_DIGEST_LENGTH);
   }
-  else /* just to be usre */
-    memset (p->fingerprint, 0, MD5_DIGEST_LENGTH);
     
   expl = 0;
   for (i = 0; i < 2; i++)
     expl = (expl << 8) + buff[j++];
 
   p->keylen = expl;
 
   expl = (expl + 7) / 8;
@@ -337,17 +351,20 @@
 
 
   if (alg >= 1 && alg <= 3)
     skip_bignum (buff, l, j, &j, 2);
   else if (alg == 17 || alg == 16 || alg == 20)
     skip_bignum (buff, l, j, &j, 1);
 
   pgp_make_pgp3_fingerprint (buff, j, digest);
-  p->fp_len = SHA_DIGEST_LENGTH;
+  if (dump_fingerprints)
+  {
+    p->fingerprint = binary_fingerprint_to_string (digest, SHA_DIGEST_LENGTH);
+  }
   
   for (k = 0; k < 2; k++)
   {
     for (id = 0, i = SHA_DIGEST_LENGTH - 8 + k * 4;
         i < SHA_DIGEST_LENGTH + (k - 1) * 4; i++)
       id = (id << 8) + digest[i];
 
     snprintf ((char *) scratch + k * 8, sizeof (scratch) - k * 8, "%08lX", id);
@@ -824,23 +841,20 @@
       putchar (*id);
     else
       printf ("\\x%02x", (*id) & 0xff);
   }
 }
 
 static void print_fingerprint (pgp_key_t p) 
 {
-  int i = 0;
+  if (!p->fingerprint)
+    return;
 
-  printf ("fpr:::::::::");
-  for (i = 0; i < p->fp_len; i++)
-    printf ("%02X", p->fingerprint[i]);
-  printf (":\n");
-
+  printf ("fpr:::::::::%s:\n", p->fingerprint);
 } /* print_fingerprint() */
 
 
 static void pgpring_dump_signatures (pgp_sig_t *sig)
 {
   for (; sig; sig = sig->next)
   {
     if (sig->sigtype == 0x10 || sig->sigtype == 0x11 ||
# HG changeset patch
# User Kevin McCarthy <ke...@8t8.us>
# Date 1423882239 28800
#      Fri Feb 13 18:50:39 2015 -0800
# Node ID 6213c21607b79b1faab74ce39573c17a08011fb8
# Parent  92c80d8bb14487e155a51b5e18646a655c506c28
Add fingerprint record parsing for pgp list keys. (see #3695)

Modify parse_pub_line to parse fpr records and add the fingerprint to
the pgp_key_t's fingerprint field.

Add "--with-fingerprint --with-fingerprint" to the
pgp_list_pubring_command and pgp_list_secring_command commands in
contrib/gpg.rc.  The second invocation generates fpr records for subkeys
too.

diff --git a/contrib/gpg.rc b/contrib/gpg.rc
--- a/contrib/gpg.rc
+++ b/contrib/gpg.rc
@@ -60,20 +60,20 @@
 
 # export a key from the public key ring
 set pgp_export_command="gpg --no-verbose --export --armor %r"
 
 # verify a key
 set pgp_verify_key_command="gpg --verbose --batch --fingerprint --check-sigs 
%r"
 
 # read in the public key ring
-set pgp_list_pubring_command="gpg --no-verbose --batch --quiet --with-colons 
--list-keys %r" 
+set pgp_list_pubring_command="gpg --no-verbose --batch --quiet --with-colons 
--with-fingerprint --with-fingerprint --list-keys %r"
 
 # read in the secret key ring
-set pgp_list_secring_command="gpg --no-verbose --batch --quiet --with-colons 
--list-secret-keys %r" 
+set pgp_list_secring_command="gpg --no-verbose --batch --quiet --with-colons 
--with-fingerprint --with-fingerprint --list-secret-keys %r"
 
 # fetch keys
 # set pgp_getkeys_command="pkspxycwrap %r"
 
 # pattern for good signature - may need to be adapted to locale!
 
 # set pgp_good_sign="^gpgv?: Good signature from "
 
diff --git a/gnupgparse.c b/gnupgparse.c
--- a/gnupgparse.c
+++ b/gnupgparse.c
@@ -116,16 +116,17 @@
   }
 }
 
 static pgp_key_t parse_pub_line (char *buf, int *is_subkey, pgp_key_t k)
 {
   pgp_uid_t *uid = NULL;
   int field = 0, is_uid = 0;
   int is_pub = 0;
+  int is_fpr = 0;
   char *pend, *p;
   int trust = 0;
   int flags = 0;
   struct pgp_keyinfo tmp;
 
   *is_subkey = 0;
   if (!*buf)
     return NULL;
@@ -143,36 +144,41 @@
   for (p = buf; p; p = pend)
   {
     if ((pend = strchr (p, ':')))
       *pend++ = 0;
     field++;
     if (!*p && (field != 1) && (field != 10))
       continue;
 
+    if (is_fpr && (field != 10))
+      continue;
+
     switch (field)
     {
       case 1:                  /* record type */
       {
        dprint (2, (debugfile, "record type: %s\n", p));
 
        if (!mutt_strcmp (p, "pub"))
          is_pub = 1;
        else if (!mutt_strcmp (p, "sub"))
          *is_subkey = 1;
        else if (!mutt_strcmp (p, "sec"))
          ;
        else if (!mutt_strcmp (p, "ssb"))
          *is_subkey = 1;
        else if (!mutt_strcmp (p, "uid"))
          is_uid = 1;
+       else if (!mutt_strcmp (p, "fpr"))
+         is_fpr = 1;
        else
          return NULL;
 
-       if (!(is_uid || (*is_subkey && option (OPTPGPIGNORESUB))))
+       if (!(is_uid || is_fpr || (*is_subkey && option (OPTPGPIGNORESUB))))
          memset (&tmp, 0, sizeof (tmp));
 
        break;
       }
       case 2:                  /* trust info */
       {
        dprint (2, (debugfile, "trust info: %s\n", p));
 
@@ -285,16 +291,24 @@
          * We allow an empty field for a pub record type because it is
          * possible for a primary uid record to have an empty User-ID
          * field.  Without any address records, it is not possible to
          * use the key in mutt.
          */
         if (!(pend && (*p || is_pub)))
          break;
 
+        if (is_fpr)
+        {
+          /* don't let a subkey fpr overwrite and existing primary key fpr */
+          if (!tmp.fingerprint)
+            tmp.fingerprint = safe_strdup (p);
+          break;
+        }
+
        /* ignore user IDs on subkeys */
        if (!is_uid && (*is_subkey && option (OPTPGPIGNORESUB)))
          break;
 
        dprint (2, (debugfile, "user ID: %s\n", NONULL (p)));
 
        uid = safe_calloc (sizeof (pgp_uid_t), 1);
        fix_uid (p);
@@ -344,17 +358,17 @@
        break;
       
       default:
         break;
     }
   }
 
   /* merge temp key back into real key */
-  if (!(is_uid || (*is_subkey && option (OPTPGPIGNORESUB))))
+  if (!(is_uid || is_fpr || (*is_subkey && option (OPTPGPIGNORESUB))))
     k = safe_malloc (sizeof (*k));
   memcpy (k, &tmp, sizeof (*k));
   /* fixup parentship of uids after mering the temp key into
    * the real key */
   if (tmp.address)
   {
     for (uid = k->address; uid; uid = uid->next)
       uid->parent = k;
# HG changeset patch
# User Eike Rathke <er...@erack.de>
# Date 1423687117 -3600
#      Wed Feb 11 21:38:37 2015 +0100
# Node ID 15394c7694ca6780261e4bae5a2ce61429ba8d55
# Parent  6213c21607b79b1faab74ce39573c17a08011fb8
Allow fingerprint user input for key selection. (see #3695)

Accept and check input of a fingerprint and find the matching key.

Note that for both to work, match against and display of fingerprint, the
pgp_list_pubring_command and pgp_list_secring_command need to contain the
--with-fingerprint option, or have with-fingerprint in ~/.gnupg/gpg.conf.

diff --git a/crypt-gpgme.c b/crypt-gpgme.c
--- a/crypt-gpgme.c
+++ b/crypt-gpgme.c
@@ -4183,64 +4183,57 @@
 static crypt_key_t *crypt_getkeybystr (char *p, short abilities,
                                       unsigned int app, int *forced_valid)
 {
   LIST *hints = NULL;
   crypt_key_t *keys;
   crypt_key_t *matches = NULL;
   crypt_key_t **matches_endp = &matches;
   crypt_key_t *k;
-  const char *ps, *pl;
+  const char *ps, *pl, *pfcopy, *phint;
 
   mutt_message (_("Looking for keys matching \"%s\"..."), p);
 
   *forced_valid = 0;
 
-  hints = crypt_add_string_to_hints (hints, p);
+  pfcopy = crypt_get_fingerprint_or_id (p, &phint, &pl, &ps);
+  hints = crypt_add_string_to_hints (hints, phint);
   keys = get_candidates (hints, app, (abilities & KEYFLAG_CANSIGN));
   mutt_free_list (&hints);
 
   if (!keys)
+  {
+    FREE (&pfcopy);
     return NULL;
-
-  /* User input may be short or long key ID, independent of OPTPGPLONGIDS.
-   * crypt_key_t->keyid should always contain a long key ID without 0x.
-   * Strip leading "0x" before loops so it doesn't have to be done over and
-   * over again, and prepare pl and ps to simplify logic in the loop's inner
-   * condition.
-   */
-  pl = (!mutt_strncasecmp (p, "0x", 2) ? p + 2 : p);
-  ps = (mutt_strlen (pl) == 16 ? pl + 8 : pl);
-
-  /* If ps != pl it means a long ID (or name of 16 characters) was given, do
-   * not attempt to match short IDs then. Also, it is unnecessary to try to
-   * match pl against long IDs if ps == pl as pl could not be a long ID. */
-  
+  }
+
   for (k = keys; k; k = k->next)
     {
       if (abilities && !(k->flags & abilities))
         continue;
 
       dprint (5, (debugfile, "crypt_getkeybystr: matching \"%s\" against "
                   "key %s, \"%s\": ",  p, crypt_long_keyid (k), k->uid));
 
       if (!*p
-          || (ps != pl && mutt_strcasecmp (pl, crypt_long_keyid (k)) == 0)
-          || (ps == pl && mutt_strcasecmp (ps, crypt_short_keyid (k)) == 0)
+          || (pfcopy && mutt_strcasecmp (pfcopy, crypt_fpr (k)) == 0)
+          || (pl && mutt_strcasecmp (pl, crypt_long_keyid (k)) == 0)
+          || (ps && mutt_strcasecmp (ps, crypt_short_keyid (k)) == 0)
           || mutt_stristr (k->uid, p))
         {
           crypt_key_t *tmp;
 
           dprint (5, (debugfile, "match.\n"));
 
           *matches_endp = tmp = crypt_copy_key (k);
           matches_endp = &tmp->next;
         }
     }
   
+  FREE (&pfcopy);
   crypt_free_key (&keys);
   
   if (matches)
     {
       k = crypt_select_key (matches, NULL, p, app, forced_valid);
       crypt_free_key (&matches);
       return k;
     }
diff --git a/crypt.c b/crypt.c
--- a/crypt.c
+++ b/crypt.c
@@ -895,8 +895,86 @@
   
   if (s->flags & M_DISPLAY && sigcnt)
     state_attach_puts (_("\n[-- End of signed data --]\n"), s);
   
   return rc;
 }
 
 
+/* Obtain pointers to fingerprint or short or long key ID, if any.
+ * See mutt_crypt.h for details.
+ */
+const char* crypt_get_fingerprint_or_id (char *p, const char **pphint,
+    const char **ppl, const char **pps)
+{
+  const char *ps, *pl, *phint;
+  char *pfcopy, *pf, *s1, *s2;
+  char c;
+  int isid;
+
+  /* User input may be partial name, fingerprint or short or long key ID,
+   * independent of OPTPGPLONGIDS.
+   * Fingerprint without spaces is 40 hex digits (SHA-1) or 32 hex digits 
(MD5).
+   * Strip leading "0x" for key ID detection and prepare pl and ps to indicate
+   * if an ID was found and to simplify logic in the key loop's inner
+   * condition of the caller. */
+
+  pf = mutt_skip_whitespace (p);
+  if (!mutt_strncasecmp (pf, "0x", 2))
+    pf += 2;
+
+  /* Check if a fingerprint is given, must be hex digits only, blanks
+   * separating groups of 4 hex digits are allowed. Also pre-check for ID. */
+  isid = 2;             /* unknown */
+  s1 = s2 = pf;
+  do
+  {
+    c = *(s2++);
+    if (('0' <= c && c <= '9') || ('A' <= c && c <= 'F') || ('a' <= c && c <= 
'f'))
+    {
+      ++s1;             /* hex digit */
+      if (isid == 2)
+        isid = 1;       /* it is an ID so far */
+    }
+    else if (c)
+    {
+      isid = 0;         /* not an ID */
+      if (c == ' ' && (((s1 - pf) % 4) == 0))
+        ;               /* skip blank before or after 4 hex digits */
+      else
+        break;          /* any other character or position */
+    }
+  } while (c);
+
+  /* If at end of input, check for correct fingerprint length and copy if. */
+  pfcopy = (!c && (((s1 - pf) == 40) || ((s1 - pf) == 32)) ? safe_strdup (pf) 
: NULL);
+
+  if (pfcopy)
+  {
+    /* Use pfcopy to strip all spaces from fingerprint and as hint. */
+    s1 = s2 = pfcopy;
+    do
+    {
+      *(s1++) = *(s2 = mutt_skip_whitespace (s2));
+    } while (*(s2++));
+
+    phint = pfcopy;
+    ps = pl = NULL;
+  }
+  else
+  {
+    phint = p;
+    ps = pl = NULL;
+    if (isid == 1)
+    {
+      if (mutt_strlen (pf) == 16)
+        pl = pf;        /* long key ID */
+      else if (mutt_strlen (pf) == 8)
+        ps = pf;        /* short key ID */
+    }
+  }
+
+  *pphint = phint;
+  *ppl = pl;
+  *pps = ps;
+  return pfcopy;
+}
diff --git a/mutt_crypt.h b/mutt_crypt.h
--- a/mutt_crypt.h
+++ b/mutt_crypt.h
@@ -148,16 +148,26 @@
 
 /* Check that we have a usable passphrase, ask if not. */
 int crypt_valid_passphrase (int);
 
 /* Write the message body/part A described by state S to a the given
    TEMPFILE.  */
 int crypt_write_signed(BODY *a, STATE *s, const char *tempf);
 
+/* Obtain pointers to fingerprint or short or long key ID, if any.
+   Return:  Copy of fingerprint, if any, stripped of all spaces, else NULL.
+            Must be FREE'd by caller.
+   *pphint  Start of string to be passed to pgp_add_string_to_hints() or 
+            crypt_add_string_to_hints().
+   *ppl     Start of long key ID if detected, else NULL.
+   *pps     Start of short key ID if detected, else NULL. */
+const char* crypt_get_fingerprint_or_id (char *p, const char **pphint,
+    const char **ppl, const char **pps);
+
 
 
 /*-- cryptglue.c --*/
 
 /* Show a message that a backend will be invoked. */
 void crypt_invoke_message (int type);
 
 
diff --git a/pgpkey.c b/pgpkey.c
--- a/pgpkey.c
+++ b/pgpkey.c
@@ -927,39 +927,31 @@
   LIST *hints = NULL;
   pgp_key_t keys;
   pgp_key_t matches = NULL;
   pgp_key_t *last = &matches;
   pgp_key_t k, kn;
   pgp_uid_t *a;
   short match;
   size_t l;
-  const char *ps, *pl;
+  const char *ps, *pl, *pfcopy, *phint;
 
   if ((l = mutt_strlen (p)) && p[l-1] == '!')
     p[l-1] = 0;
 
   mutt_message (_("Looking for keys matching \"%s\"..."), p);
 
-  hints = pgp_add_string_to_hints (hints, p);
+  pfcopy = crypt_get_fingerprint_or_id (p, &phint, &pl, &ps);
+  hints = pgp_add_string_to_hints (hints, phint);
   keys = pgp_get_candidates (keyring, hints);
   mutt_free_list (&hints);
 
   if (!keys)
     goto out;
 
-  /* User input may be short or long key ID, independent of OPTPGPLONGIDS.
-   * pgp_key_t->keyid should always contain a long key ID without 0x.
-   * Strip leading "0x" before loops so it doesn't have to be done over and
-   * over again, and prepare pl and ps to simplify logic in the loop's inner
-   * condition.
-   */
-  pl = (!mutt_strncasecmp (p, "0x", 2) ? p + 2 : p);
-  ps = (mutt_strlen (pl) == 16 ? pl + 8 : pl);
-
   for (k = keys; k; k = kn)
   {
     kn = k->next;
     if (abilities && !(k->flags & abilities))
       continue;
 
     /* This shouldn't happen, but keys without any addresses aren't selectable
      * in pgp_select_key().
@@ -967,22 +959,20 @@
     if (!k->address)
       continue;
 
     match = 0;
 
     dprint (5, (debugfile, "pgp_getkeybystr: matching \"%s\" against key 
%s:\n",
                 p, pgp_long_keyid (k)));
 
-    /* If ps != pl it means a long ID (or name of 16 characters) was given, do
-     * not attempt to match short IDs then. Also, it is unnecessary to try to
-     * match pl against long IDs if ps == pl as pl could not be a long ID. */
     if (!*p ||
-        (ps != pl && mutt_strcasecmp (pl, pgp_long_keyid (k)) == 0) ||
-        (ps == pl && mutt_strcasecmp (ps, pgp_short_keyid (k)) == 0))
+        (pfcopy && mutt_strcasecmp (pfcopy, k->fingerprint) == 0) ||
+        (pl && mutt_strcasecmp (pl, pgp_long_keyid (k)) == 0) ||
+        (ps && mutt_strcasecmp (ps, pgp_short_keyid (k)) == 0))
     {
       dprint (5, (debugfile, "\t\tmatch.\n"));
       match = 1;
     }
     else
     {
       for (a = k->address; a; a = a->next)
       {
@@ -1008,20 +998,22 @@
   pgp_free_key (&keys);
 
   if (matches)
   {
     if ((k = pgp_select_key (matches, NULL, p)))
       pgp_remove_key (&matches, k);
 
     pgp_free_key (&matches);
+    FREE (&pfcopy);
     if (l && !p[l-1])
       p[l-1] = '!';
     return k;
   }
 
 out:
+  FREE (&pfcopy);
   if (l && !p[l-1])
     p[l-1] = '!';
   return NULL;
 }
 
 #endif /* CRYPT_BACKEND_CLASSIC_PGP */

Attachment: signature.asc
Description: PGP signature

Reply via email to