progs/key.c: Use `HASH' rather than `HASHALG' to denote a hash-function name.
[catacomb] / progs / key.c
index 4530d00..9c9466b 100644 (file)
 #include <time.h>
 
 #include <mLib/codec.h>
+#include <mLib/base32.h>
 #include <mLib/base64.h>
+#include <mLib/hex.h>
+#include <mLib/macros.h>
 #include <mLib/mdwopt.h>
 #include <mLib/quis.h>
 #include <mLib/report.h>
 #include "gfreduce.h"
 #include "key.h"
 #include "mp.h"
+#include "mpint.h"
 #include "mpmont.h"
 #include "mprand.h"
 #include "mptext.h"
 #include "pgen.h"
 #include "ptab.h"
 #include "rsa.h"
+#include "x25519.h"
+#include "x448.h"
+#include "ed25519.h"
+#include "ed448.h"
 
 #include "cc.h"
 #include "sha-mgf.h"
@@ -191,6 +199,7 @@ typedef struct keyopts {
   unsigned bits, qbits;                        /* Bit length for the new key */
   const char *curve;                   /* Elliptic curve name/info */
   grand *r;                            /* Random number source */
+  mp *e;                               /* Public exponent */
   key *p;                              /* Parameters key-data */
 } keyopts;
 
@@ -226,7 +235,7 @@ static void dolock(keyopts *k, key_data **kd, const char *t)
 /* --- @copyparam@ --- *
  *
  * Arguments:  @keyopts *k@ = pointer to key options
- *             @const char **pp@ = checklist of parameters
+ *             @const char **pp@ = checklist of parameters, or null
  *
  * Returns:    Nonzero if parameters copied; zero if you have to generate
  *             them.
@@ -240,6 +249,7 @@ static int copyparam(keyopts *k, const char **pp)
   key_attriter i;
   key_data *kd;
   const char *n, *v;
+  dstr t = DSTR_INIT;
 
   kf.f = KCAT_SHARE;
   kf.m = KF_CATMASK;
@@ -249,23 +259,35 @@ static int copyparam(keyopts *k, const char **pp)
   if (!k->p)
     return (0);
 
-  /* --- Run through the checklist --- */
+  /* --- Copy the key data if there's anything we want --- */
 
-  while (*pp) {
-    key_data *kd = key_structfind(k->p->k, *pp);
-    if (!kd)
-      die(EXIT_FAILURE, "bad parameter key: parameter `%s' not found", *pp);
-    if (!KEY_MATCH(kd, &kf))
-      die(EXIT_FAILURE, "bad parameter key: subkey `%s' is not shared", *pp);
-    pp++;
-  }
+  if (pp) {
 
-  /* --- Copy over the parameters --- */
+    /* --- Run through the checklist --- */
 
-  kd = key_copydata(k->p->k, &kf);
-  assert(kd);
-  key_setkeydata(k->kf, k->k, kd);
-  key_drop(kd);
+    key_fulltag(k->p, &t);
+    if ((k->p->k->e & KF_ENCMASK) != KENC_STRUCT)
+      die(EXIT_FAILURE, "parameter key `%s' is not structured", t.buf);
+    while (*pp) {
+      key_data *kd = key_structfind(k->p->k, *pp);
+      if (!kd) {
+       die(EXIT_FAILURE,
+           "bad parameter key `%s': parameter `%s' not found", t.buf, *pp);
+      }
+      if (!KEY_MATCH(kd, &kf)) {
+       die(EXIT_FAILURE,
+           "bad parameter key `%s': subkey `%s' is not shared", t.buf, *pp);
+      }
+      pp++;
+    }
+
+    /* --- Copy over the parameters --- */
+
+    kd = key_copydata(k->p->k, &kf);
+    assert(kd);
+    key_setkeydata(k->kf, k->k, kd);
+    key_drop(kd);
+  }
 
   /* --- Copy over attributes --- */
 
@@ -274,6 +296,7 @@ static int copyparam(keyopts *k, const char **pp)
 
   /* --- Done --- */
 
+  dstr_destroy(&t);
   return (1);
 }
 
@@ -352,6 +375,13 @@ static void keyrand(key_file *kf, const char *id)
 
 /* --- Key generation algorithms --- */
 
+static void alg_empty(keyopts *k)
+{
+  copyparam(k, 0);
+  key_setkeydata(k->kf, k->k,
+                key_newstring(KCAT_SHARE, k->curve ? k->curve : "."));
+}
+
 static void alg_binary(keyopts *k)
 {
   unsigned sz;
@@ -361,8 +391,7 @@ static void alg_binary(keyopts *k)
 
   if (!k->bits)
     k->bits = 128;
-  if (k->p)
-    die(EXIT_FAILURE, "no shared parameters for binary keys");
+  copyparam(k, 0);
 
   sz = (k->bits + 7) >> 3;
   p = sub_alloc(sz);
@@ -386,8 +415,7 @@ static void alg_des(keyopts *k)
 
   if (!k->bits)
     k->bits = 168;
-  if (k->p)
-    die(EXIT_FAILURE, "no shared parameters for DES keys");
+  copyparam(k, 0);
   if (k->bits % 56 || k->bits > 168)
     die(EXIT_FAILURE, "DES keys must be 56, 112 or 168 bits long");
 
@@ -416,15 +444,18 @@ static void alg_rsa(keyopts *k)
 
   /* --- Sanity checking --- */
 
-  if (k->p)
-    die(EXIT_FAILURE, "no shared parameters for RSA keys");
+  copyparam(k, 0);
   if (!k->bits)
     k->bits = 1024;
+  if (k->bits < 64)
+    die(EXIT_FAILURE, "RSA key too tiny");
+  if (!k->e)
+    k->e = mp_fromulong(MP_NEW, 65537);
 
   /* --- Generate the RSA parameters --- */
 
-  if (rsa_gen(&rp, k->bits, k->r, 0,
-             (k->f & f_quiet) ? 0 : pgen_ev, 0))
+  if (rsa_gen_e(&rp, k->bits, k->e, k->r, 0,
+               (k->f & f_quiet) ? 0 : pgen_ev, 0))
     die(EXIT_FAILURE, "RSA key generation failed");
 
   /* --- Run a test encryption --- */
@@ -572,7 +603,7 @@ static void alg_dhparam(keyopts *k)
       group *g;
       const char *e;
 
-      if (strcmp(k->curve, "list") == 0) {
+      if (STRCMP(k->curve, ==, "list")) {
        unsigned i, w;
        LIST("Built-in prime fields", stdout, ptab[i].name, ptab[i].name);
        exit(0);
@@ -701,8 +732,7 @@ static void alg_bbs(keyopts *k)
 
   /* --- Sanity checking --- */
 
-  if (k->p)
-    die(EXIT_FAILURE, "no shared parameters for Blum-Blum-Shub keys");
+  copyparam(k, 0);
   if (!k->bits)
     k->bits = 1024;
 
@@ -741,7 +771,7 @@ static void alg_binparam(keyopts *k)
     /* --- Decide on a field --- */
 
     if (!k->bits) k->bits = 128;
-    if (k->curve && strcmp(k->curve, "list") == 0) {
+    if (k->curve && STRCMP(k->curve, ==, "list")) {
       unsigned i, w;
       LIST("Built-in binary fields", stdout,
           bintab[i].name, bintab[i].name);
@@ -840,7 +870,7 @@ static void alg_ecparam(keyopts *k)
     /* --- Decide on a curve --- */
 
     if (!k->bits) k->bits = 256;
-    if (k->curve && strcmp(k->curve, "list") == 0) {
+    if (k->curve && STRCMP(k->curve, ==, "list")) {
       unsigned i, w;
       LIST("Built-in elliptic curves", stdout,
           ectab[i].name, ectab[i].name);
@@ -917,6 +947,64 @@ static void alg_ec(keyopts *k)
   mp_drop(x);
 }
 
+#define XDHS(_)                                                                \
+  _(x25519, X25519, "X25519")                                          \
+  _(x448, X448, "X448")
+
+#define XDHALG(xdh, XDH, name)                                         \
+                                                                       \
+  static void alg_##xdh(keyopts *k)                                    \
+  {                                                                    \
+    key_data *kd, *kkd;                                                        \
+    octet priv[XDH##_KEYSZ], pub[XDH##_PUBSZ];                         \
+                                                                       \
+    copyparam(k, 0);                                                   \
+    k->r->ops->fill(k->r, priv, sizeof(priv));                         \
+    xdh(pub, priv, xdh##_base);                                                \
+    kkd = key_newstruct();                                             \
+    key_structsteal(kkd, "priv",                                       \
+                   key_newbinary(KCAT_PRIV | KF_BURN,                  \
+                                 priv, sizeof(priv)));                 \
+    kd = key_newstruct();                                              \
+    key_structsteal(kd, "private", kkd);                               \
+    key_structsteal(kd, "pub",                                         \
+                   key_newbinary(KCAT_PUB, pub, sizeof(pub)));         \
+                                                                       \
+    key_setkeydata(k->kf, k->k, kd);                                   \
+  }
+
+XDHS(XDHALG)
+#undef XDHALG
+
+#define EDDSAS(_)                                                      \
+  _(ed25519, ED25519, "Ed25519")                                       \
+  _(ed448, ED448, "Ed448")
+
+#define EDDSAALG(ed, ED, name)                                         \
+                                                                       \
+  static void alg_##ed(keyopts *k)                                     \
+  {                                                                    \
+    key_data *kd, *kkd;                                                        \
+    octet priv[ED##_KEYSZ], pub[ED##_PUBSZ];                           \
+                                                                       \
+    copyparam(k, 0);                                                   \
+    k->r->ops->fill(k->r, priv, sizeof(priv));                         \
+    ed##_pubkey(pub, priv, sizeof(priv));                              \
+    kkd = key_newstruct();                                             \
+    key_structsteal(kkd, "priv",                                       \
+                   key_newbinary(KCAT_PRIV | KF_BURN,                  \
+                                 priv, sizeof(priv)));                 \
+    kd = key_newstruct();                                              \
+    key_structsteal(kd, "private", kkd);                               \
+    key_structsteal(kd, "pub",                                         \
+                   key_newbinary(KCAT_PUB, pub, sizeof(pub)));         \
+                                                                       \
+    key_setkeydata(k->kf, k->k, kd);                                   \
+  }
+
+EDDSAS(EDDSAALG)
+#undef EDDSAALG
+
 /* --- The algorithm tables --- */
 
 typedef struct keyalg {
@@ -938,6 +1026,15 @@ static keyalg algtab[] = {
   { "bindh-param",     alg_binparam,   "Binary-field DH parameters" },
   { "ec-param",                alg_ecparam,    "Elliptic curve parameters" },
   { "ec",              alg_ec,         "Elliptic curve crypto" },
+#define XDHTAB(xdh, XDH, name)                                         \
+  { #xdh,              alg_##xdh,      "" name " key exchange" },
+  XDHS(XDHTAB)
+#undef XDHTAB
+#define EDDSATAB(ed, ED, name)                                         \
+  { #ed,               alg_##ed,       "" name " digital signatures" },
+  EDDSAS(EDDSATAB)
+#undef EDDSATAB
+  { "empty",           alg_empty,      "Empty parametrs-only key" },
   { 0,                 0 }
 };
 
@@ -953,7 +1050,7 @@ static int cmd_add(int argc, char *argv[])
   keyalg *alg = algtab;
   const char *rtag = 0;
   const struct seedalg *sa = SEEDALG_DEFAULT;
-  keyopts k = { 0, 0, DSTR_INIT, 0, 0, 0, 0, 0 };
+  keyopts k = { 0, 0, DSTR_INIT, 0, 0, 0, 0, 0, 0 };
   const char *seed = 0;
   k.r = &rand_global;
 
@@ -974,6 +1071,7 @@ static int cmd_add(int argc, char *argv[])
       { "seedalg",     OPTF_ARGREQ,    0,      'A' },
       { "seed",                OPTF_ARGREQ,    0,      's' },
       { "newseed",     OPTF_ARGREQ,    0,      'n' },
+      { "public-exponent", OPTF_ARGREQ, 0,     'E' },
       { "lock",                0,              0,      'l' },
       { "quiet",       0,              0,      'q' },
       { "lim-lee",     0,              0,      'L' },
@@ -981,7 +1079,7 @@ static int cmd_add(int argc, char *argv[])
       { "kcdsa",       0,              0,      'K' },
       { 0,             0,              0,      0 }
     };
-    int i = mdwopt(argc, argv, "+a:b:B:p:e:c:t:R:I:C:A:s:n:lqrLKS",
+    int i = mdwopt(argc, argv, "+a:b:B:p:e:c:t:R:I:C:A:s:n:E:lqrLKS",
                   opt, 0, 0, 0);
     if (i < 0)
       break;
@@ -996,7 +1094,7 @@ static int cmd_add(int argc, char *argv[])
        keyalg *a;
        size_t sz = strlen(optarg);
 
-       if (strcmp(optarg, "list") == 0) {
+       if (STRCMP(optarg, ==, "list")) {
          for (a = algtab; a->name; a++)
            printf("%-10s %s\n", a->name, a->help);
          return (0);
@@ -1004,7 +1102,7 @@ static int cmd_add(int argc, char *argv[])
 
        alg = 0;
        for (a = algtab; a->name; a++) {
-         if (strncmp(optarg, a->name, sz) == 0) {
+         if (STRNCMP(optarg, ==, a->name, sz)) {
            if (a->name[sz] == 0) {
              alg = a;
              break;
@@ -1043,7 +1141,7 @@ static int cmd_add(int argc, char *argv[])
       /* --- Expiry dates get passed to @get_date@ for parsing --- */
 
       case 'e':
-       if (strcmp(optarg, "forever") == 0)
+       if (STRCMP(optarg, ==, "forever"))
          exp = KEXP_FOREVER;
        else {
          exp = get_date(optarg, 0);
@@ -1081,7 +1179,7 @@ static int cmd_add(int argc, char *argv[])
 
       case 'A': {
        const struct seedalg *ss;
-       if (strcmp(optarg, "list") == 0) {
+       if (STRCMP(optarg, ==, "list")) {
          printf("Seed algorithms:\n");
          for (ss = seedtab; ss->p; ss++)
            printf("  %s\n", ss->p);
@@ -1090,7 +1188,7 @@ static int cmd_add(int argc, char *argv[])
        if (seed) die(EXIT_FAILURE, "seed already set -- put -A first");
        sa = 0;
        for (ss = seedtab; ss->p; ss++) {
-         if (strcmp(optarg, ss->p) == 0)
+         if (STRCMP(optarg, ==, ss->p))
            sa = ss;
        }
        if (!sa)
@@ -1143,6 +1241,15 @@ static int cmd_add(int argc, char *argv[])
        kid = id;
       } break;
 
+      /* --- Public exponent --- */
+
+      case 'E': {
+       char *p;
+       k.e = mp_readstring(k.e, optarg, &p, 0);
+       if (!k.e || *p || MP_CMP(k.e, <, MP_THREE) || MP_EVENP(k.e))
+         die(EXIT_FAILURE, "bad exponent `%s'", optarg);
+      } break;
+
       /* --- Other flags --- */
 
       case 'R':
@@ -1214,7 +1321,7 @@ static int cmd_add(int argc, char *argv[])
     if (k.f & f_retag) {
       if ((kk = key_bytag(&f, tag)) != 0 &&
          kk->tag &&
-         strcmp(kk->tag, tag) == 0)
+         STRCMP(kk->tag, ==, tag))
        key_settag(&f, kk, 0);
     }
     if ((err = key_settag(&f, k.k, tag)) != 0)
@@ -1237,12 +1344,8 @@ static int cmd_add(int argc, char *argv[])
 
   /* --- Find the parameter key --- */
 
-  if (ptag) {
-    if ((k.p = key_bytag(&f, ptag)) == 0)
-      die(EXIT_FAILURE, "parameter key `%s' not found", ptag);
-    if ((k.p->k->e & KF_ENCMASK) != KENC_STRUCT)
-      die(EXIT_FAILURE, "parameter key `%s' is not structured", ptag);
-  }
+  if (ptag && (k.p = key_bytag(&f, ptag)) == 0)
+    die(EXIT_FAILURE, "parameter key `%s' not found", ptag);
 
   /* --- Now generate the actual key data --- */
 
@@ -1682,42 +1785,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)) 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 +1855,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 +1869,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 HASH] [-p STYLE] [-f FILTER] [TAG...]");
+  }
 
   doopen(&f, KOPEN_READ);
 
@@ -1748,7 +1881,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 +1891,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 +1905,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 +1929,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 +1943,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 HASH] [-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))
     die(EXIT_FAILURE, "key fingerprint mismatch");
+  dstr_destroy(&d); dstr_destroy(&dd);
   doclose(&f);
   return (0);
 }
@@ -1911,7 +2029,7 @@ static int cmd_tag(int argc, char *argv[])
     die(EXIT_FAILURE, "Usage: tag [-r] TAG [NEW-TAG]");
   doopen(&f, KOPEN_WRITE);
   if (flags & f_retag) {
-    if ((k = key_bytag(&f, argv[1])) != 0 && strcmp(k->tag, argv[1]) == 0)
+    if ((k = key_bytag(&f, argv[1])) != 0 && STRCMP(k->tag, ==, argv[1]))
       key_settag(&f, k, 0);
   }
   if ((k = key_bytag(&f, argv[0])) == 0)
@@ -2005,7 +2123,7 @@ static int cmd_extract(int argc, char *argv[])
   argv += optind; argc -= optind;
   if (rc || argc < 1)
     die(EXIT_FAILURE, "Usage: extract [-f FILTER] FILE [TAG...]");
-  if (strcmp(*argv, "-") == 0)
+  if (STRCMP(*argv, ==, "-"))
     fp = stdout;
   else {
     outfile = *argv;
@@ -2061,7 +2179,7 @@ static int cmd_merge(int argc, char *argv[])
 
   if (argc != 2)
     die(EXIT_FAILURE, "Usage: merge FILE");
-  if (strcmp(argv[1], "-") == 0)
+  if (STRCMP(argv[1], ==, "-"))
     fp = stdin;
   else if (!(fp = fopen(argv[1], "r"))) {
     die(EXIT_FAILURE, "couldn't open `%s' for reading: %s",
@@ -2090,7 +2208,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 +2233,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 HASH] [-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\
 " },