progs/key.c: Multiple output presentations for fingerprints.
authorMark Wooding <mdw@distorted.org.uk>
Fri, 29 May 2015 17:54:29 +0000 (18:54 +0100)
committerMark Wooding <mdw@distorted.org.uk>
Fri, 29 May 2015 19:02:32 +0000 (20:02 +0100)
Add a new presentation for base32 output, separated into groups of six
characters.

Also document the presentation styles better; include some other missing
options in usage messages; and patch an irrelevant memory leak.

progs/key.1
progs/key.c

index f301391..a2ac337 100644 (file)
@@ -117,6 +117,8 @@ is one of:
 .B fingerprint
 .RB [ \-f
 .IR filter ]
+.RB [ \-p
+.IR style ]
 .RB [ \-a
 .IR hash ]
 .RI [ tag ...]
@@ -124,6 +126,8 @@ is one of:
 .B verify
 .RB [ \-f
 .IR filter ]
+.RB [ \-p
+.IR style ]
 .RB [ \-a
 .IR hash ]
 .I tag
@@ -340,6 +344,13 @@ The pseudorandom generators which are acceptable to the
 option of the
 .B add
 command.
+.TP
+.B fpres
+Fingerprint presentation styles, as used by the
+.B fingerprint
+and
+.B verify
+commands.
 .SS add
 The
 .B add
@@ -919,6 +930,11 @@ Specifies a filter.  Only keys and key components which match the filter
 are fingerprinted.  The default is to only fingerprint nonsecret
 components.
 .TP
+.BI "\-p, \-\-presentation " style
+Write fingerprints in the given
+.IR style .
+See below for a list of presentation styles.
+.TP
 .BI "\-a, \-\-algorithm " hash
 Names the hashing algorithm.  Run
 .B key show hash
@@ -930,6 +946,18 @@ command line arguments.  If no key tags are given, all keys which match
 the filter are fingerprinted.  See
 .BR keyring (5)
 for a description of how key fingerprints are computed.
+.PP
+The fingerprint may be shown in the following styles.
+.TP
+.B hex
+Lowercase hexadecimal, with groups of eight digits separated by hyphens
+(`\-').  This is the default presentation style.  (On input, colons are
+also permitted as separators.)
+.TP
+.B base32
+Lowercase Base32 encoding, without `=' padding, with groups of six
+digits separated by colons (`:').  (On input, padding characters are
+ignored.)
 .SS "verify"
 Check a key's fingerprint against a reference copy.  The following
 options are supported:
@@ -939,15 +967,29 @@ Specifies a filter.  Only key components which match the filter are
 hashed.  The default is to only fingerprint nonsecret components.  An
 error is reported if no part of the key matches.
 .TP
+.BI "\-p, \-\-presentation " style
+Expect the
+.I fingerprint
+to be in the given presentation
+.IR style .
+These match the styles produced by the
+.B fingerprint
+command described above.
+.TP
 .BI "\-a, \-\-algorithm " hash
 Names the hashing algorithm.  Run
 .B key show hash
 for a list of hashing algorithms.  The default is
 .BR rmd160 .
 .PP
-The reference fingerprint is given as hex, in upper or lower case.  The
-hash may contain hyphens, colons and whitespace.  Other characters are
-not permitted.
+The fingerprint should be provided in the form printed by the
+.B fingerprint
+command, using the same presentation
+.IR style .
+A little flexibility is permitted: separators may be placed anywhere (or
+not at all) and are ignored; whitespace is permitted and ignored; and
+case is ignored in presentation styles which don't make use of both
+upper- and lower-case characters.
 .SS "tidy"
 Simply reads the keyring from file and writes it back again.  This has
 the effect of removing any deleted keys from the file.
index 4530d00..0549469 100644 (file)
@@ -39,7 +39,9 @@
 #include <time.h>
 
 #include <mLib/codec.h>
+#include <mLib/base32.h>
 #include <mLib/base64.h>
+#include <mLib/hex.h>
 #include <mLib/mdwopt.h>
 #include <mLib/quis.h>
 #include <mLib/report.h>
@@ -1682,42 +1684,67 @@ static int cmd_getattr(int argc, char *argv[])
 
 /* --- @cmd_finger@ --- */
 
-static void fingerprint(key *k, const gchash *ch, const key_filter *kf)
+static const struct fpres {
+  const char *name;
+  const codec_class *cdc;
+  unsigned short ival;
+  const char *sep;
+} fprestab[] = {
+  { "hex", &hex_class, 8, "-:" },
+  { "base32", &base32_class, 6, ":" },
+  { 0, 0 }
+};
+
+static void fingerprint(key *k, const struct fpres *fpres,
+                       const gchash *ch, const key_filter *kf)
 {
   ghash *h;
-  dstr d = DSTR_INIT;
+  dstr d = DSTR_INIT, dd = DSTR_INIT;
   const octet *p;
   size_t i;
+  codec *c;
 
   h = GH_INIT(ch);
   if (key_fingerprint(k, h, kf)) {
     p = GH_DONE(h, 0);
-    key_fulltag(k, &d);
-    for (i = 0; i < ch->hashsz; i++) {
-      if (i && i % 4 == 0)
-       putchar('-');
-      printf("%02x", p[i]);
+    c = fpres->cdc->encoder(CDCF_LOWERC | CDCF_NOEQPAD, "", 0);
+    c->ops->code(c, p, ch->hashsz, &dd); c->ops->code(c, 0, 0, &dd);
+    c->ops->destroy(c);
+    for (i = 0; i < dd.len; i++) {
+      if (i && i%fpres->ival == 0) dstr_putc(&d, fpres->sep[0]);
+      dstr_putc(&d, dd.buf[i]);
     }
-    printf(" %s\n", d.buf);
+    dstr_putc(&d, ' '); key_fulltag(k, &d); dstr_putc(&d, '\n');
+    dstr_write(&d, stdout);
   }
-  dstr_destroy(&d);
+  dstr_destroy(&d); dstr_destroy(&dd);
   GH_DESTROY(h);
 }
 
+static const struct fpres *lookup_fpres(const char *name)
+{
+  const struct fpres *fpres;
+  for (fpres = fprestab; fpres->name; fpres++)
+    if (strcmp(fpres->name, name) == 0) return (fpres);
+  die(EXIT_FAILURE, "unknown presentation syle `%s'", name);
+}
+
 static int cmd_finger(int argc, char *argv[])
 {
   key_file f;
   int rc = 0;
+  const struct fpres *fpres = fprestab;
   const gchash *ch = &rmd160;
   key_filter kf = { KF_NONSECRET, KF_NONSECRET };
 
   for (;;) {
     static struct option opt[] = {
       { "filter",      OPTF_ARGREQ,    0,      'f' },
+      { "presentation",        OPTF_ARGREQ,    0,      'p' },
       { "algorithm",   OPTF_ARGREQ,    0,      'a' },
       { 0,             0,              0,      0 }
     };
-    int i = mdwopt(argc, argv, "+f:a:", opt, 0, 0, 0);
+    int i = mdwopt(argc, argv, "+f:a:p:", opt, 0, 0, 0);
     if (i < 0)
       break;
     switch (i) {
@@ -1727,6 +1754,9 @@ static int cmd_finger(int argc, char *argv[])
        if (err || *p)
          die(EXIT_FAILURE, "bad filter string `%s'", optarg);
       } break;
+      case 'p':
+       fpres = lookup_fpres(optarg);
+       break;
       case 'a':
        if ((ch = ghash_byname(optarg)) == 0)
          die(EXIT_FAILURE, "unknown hash algorithm `%s'", optarg);
@@ -1738,8 +1768,10 @@ static int cmd_finger(int argc, char *argv[])
   }
 
   argv += optind; argc -= optind;
-  if (rc)
-    die(EXIT_FAILURE, "Usage: fingerprint [-f FILTER] [TAG...]");
+  if (rc) {
+    die(EXIT_FAILURE,
+       "Usage: fingerprint [-a HASHALG] [-p STYLE] [-f FILTER] [TAG...]");
+  }
 
   doopen(&f, KOPEN_READ);
 
@@ -1748,7 +1780,7 @@ static int cmd_finger(int argc, char *argv[])
     for (i = 0; i < argc; i++) {
       key *k = key_bytag(&f, argv[i]);
       if (k)
-       fingerprint(k, ch, &kf);
+       fingerprint(k, fpres, ch, &kf);
       else {
        rc = 1;
        moan("key `%s' not found", argv[i]);
@@ -1758,50 +1790,13 @@ static int cmd_finger(int argc, char *argv[])
     key_iter i;
     key *k;
     for (key_mkiter(&i, &f); (k = key_next(&i)) != 0; )
-      fingerprint(k, ch, &kf);
+      fingerprint(k, fpres, ch, &kf);
   }
   return (rc);
 }
 
 /* --- @cmd_verify@ --- */
 
-static unsigned xdigit(char c)
-{
-  if ('A' <= c && c <= 'Z') return (c + 10 - 'A');
-  if ('a' <= c && c <= 'z') return (c + 10 - 'a');
-  if ('0' <= c && c <= '9') return (c - '0');
-  return (~0u);
-}
-
-static void unhexify(octet *q, char *p, size_t n)
-{
-  unsigned a = 0;
-  int i = 0;
-
-  for (;;) {
-    if (*p == '-' || *p == ':' || isspace((unsigned char)*p)) {
-      p++;
-      continue;
-    }
-    if (!n && !*p)
-      break;
-    if (!*p)
-      die(EXIT_FAILURE, "hex string too short");
-    if (!isxdigit((unsigned char)*p))
-      die(EXIT_FAILURE, "bad hex string");
-    if (!n)
-      die(EXIT_FAILURE, "hex string too long");
-    a = (a << 4) | xdigit(*p++);
-    i++;
-    if (i == 2) {
-      *q++ = U8(a);
-      a = 0;
-      i = 0;
-      n--;
-    }
-  }
-}
-
 static int cmd_verify(int argc, char *argv[])
 {
   key_file f;
@@ -1809,17 +1804,21 @@ static int cmd_verify(int argc, char *argv[])
   const gchash *ch = &rmd160;
   ghash *h;
   key *k;
-  octet *buf;
   const octet *fpr;
+  dstr d = DSTR_INIT, dd = DSTR_INIT;
+  codec *c;
+  const char *p;
+  const struct fpres *fpres = fprestab;
   key_filter kf = { KF_NONSECRET, KF_NONSECRET };
 
   for (;;) {
     static struct option opt[] = {
       { "filter",      OPTF_ARGREQ,    0,      'f' },
+      { "presentation",        OPTF_ARGREQ,    0,      'p' },
       { "algorithm",   OPTF_ARGREQ,    0,      'a' },
       { 0,             0,              0,      0 }
     };
-    int i = mdwopt(argc, argv, "+f:a:", opt, 0, 0, 0);
+    int i = mdwopt(argc, argv, "+f:a:p:", opt, 0, 0, 0);
     if (i < 0)
       break;
     switch (i) {
@@ -1829,6 +1828,9 @@ static int cmd_verify(int argc, char *argv[])
        if (err || *p)
          die(EXIT_FAILURE, "bad filter string `%s'", optarg);
       } break;
+      case 'p':
+       fpres = lookup_fpres(optarg);
+       break;
       case 'a':
        if ((ch = ghash_byname(optarg)) == 0)
          die(EXIT_FAILURE, "unknown hash algorithm `%s'", optarg);
@@ -1840,21 +1842,36 @@ static int cmd_verify(int argc, char *argv[])
   }
 
   argv += optind; argc -= optind;
-  if (rc || argc != 2)
-    die(EXIT_FAILURE, "Usage: verify [-f FILTER] TAG FINGERPRINT");
+  if (rc || argc != 2) {
+    die(EXIT_FAILURE,
+       "Usage: verify [-a HASHALG] [-p STYLE] [-f FILTER] TAG FINGERPRINT");
+  }
 
   doopen(&f, KOPEN_READ);
 
   if ((k = key_bytag(&f, argv[0])) == 0)
     die(EXIT_FAILURE, "key `%s' not found", argv[0]);
-  buf = xmalloc(ch->hashsz);
-  unhexify(buf, argv[1], ch->hashsz);
+  for (p = argv[1]; *p; p++) {
+    if (strchr(fpres->sep, *p)) continue;
+    dstr_putc(&dd, *p);
+  }
+  c = fpres->cdc->decoder(CDCF_IGNCASE | CDCF_IGNEQPAD |
+                         CDCF_IGNSPC | CDCF_IGNNEWL);
+  if ((rc = c->ops->code(c, dd.buf, dd.len, &d)) != 0 ||
+      (rc = c->ops->code(c, 0, 0, &d)) != 0)
+    die(EXIT_FAILURE, "invalid fingerprint: %s", codec_strerror(rc));
+  c->ops->destroy(c);
+  if (d.len != ch->hashsz) {
+    die(EXIT_FAILURE, "incorrect fingerprint length (%lu != %lu)",
+       (unsigned long)d.len, (unsigned long)ch->hashsz);
+  }
   h = GH_INIT(ch);
   if (!key_fingerprint(k, h, &kf))
     die(EXIT_FAILURE, "key has no fingerprintable components (as filtered)");
   fpr = GH_DONE(h, 0);
-  if (memcmp(fpr, buf, ch->hashsz) != 0)
+  if (memcmp(fpr, d.buf, ch->hashsz) != 0)
     die(EXIT_FAILURE, "key fingerprint mismatch");
+  dstr_destroy(&d); dstr_destroy(&dd);
   doclose(&f);
   return (0);
 }
@@ -2090,7 +2107,9 @@ static int cmd_merge(int argc, char *argv[])
   LI("Key-generation algorithms", keygen,                              \
      algtab[i].name, algtab[i].name)                                   \
   LI("Random seeding algorithms", seed,                                        \
-     seedtab[i].p, seedtab[i].p)
+     seedtab[i].p, seedtab[i].p)                                       \
+  LI("Fingerprint presentation styles", fpres,                         \
+     fprestab[i].name, fprestab[i].name)
 
 MAKELISTTAB(listtab, LISTS)
 
@@ -2113,17 +2132,21 @@ Options:\n\
 -q, --quiet            Show less information.\n\
 -v, --verbose          Show more information.\n\
 " },
-  { "fingerprint", cmd_finger, "fingerprint [-f FILTER] [TAG...]", "\
+  { "fingerprint", cmd_finger,
+    "fingerprint [-a HASHALG] [-p STYLE] [-f FILTER] [TAG...]", "\
 Options:\n\
 \n\
 -f, --filter=FILT      Only hash key components matching FILT.\n\
+-p, --presentation=STYLE Use STYLE for presenting fingerprints.\n\
 -a, --algorithm=HASH   Use the named HASH algorithm.\n\
                          ($ show hash for list.)\n\
 " },
-  { "verify", cmd_verify, "verify [-f FILTER] TAG FINGERPRINT", "\
+  { "verify", cmd_verify,
+    "verify [-a HASH] [-p STYLE] [-f FILTER] TAG FINGERPRINT", "\
 Options:\n\
 \n\
 -f, --filter=FILT      Only hash key components matching FILT.\n\
+-p, --presentation=STYLE Expect FINGERPRINT in the given STYLE.\n\
 -a, --algorithm=HASH   Use the named HASH algorithm.\n\
                          ($ show hash for list.)\n\
 " },