Major and incompatible overhaul of key_data representation. Fix leaks.
[u/mdw/catacomb] / keyutil.c
index 0b54df2..1d4bbca 100644 (file)
--- a/keyutil.c
+++ b/keyutil.c
@@ -31,6 +31,7 @@
 
 #include "config.h"
 
+#include <ctype.h>
 #include <errno.h>
 #include <stdio.h>
 #include <stdlib.h>
@@ -46,6 +47,7 @@
 #include <noise.h>
 #include <rand.h>
 
+#include "bintab.h"
 #include "bbs.h"
 #include "dh.h"
 #include "dsa.h"
@@ -55,6 +57,7 @@
 #include "ectab.h"
 #include "fibrand.h"
 #include "getdate.h"
+#include "gfreduce.h"
 #include "key.h"
 #include "mp.h"
 #include "mpmont.h"
@@ -198,7 +201,7 @@ typedef struct keyopts {
 /* --- @dolock@ --- *
  *
  * Arguments:  @keyopts *k@ = key generation options
- *             @key_data *kd@ = pointer to key data to lock
+ *             @key_data **kd@ = pointer to key data to lock
  *             @const char *t@ = tag suffix or null
  *
  * Returns:    ---
@@ -206,35 +209,16 @@ typedef struct keyopts {
  * Use:                Does passphrase locking on new keys.
  */
 
-static void dolock(keyopts *k, key_data *kd, const char *t)
+static void dolock(keyopts *k, key_data **kd, const char *t)
 {
   if (!(k->f & f_lock))
     return;
   if (t)
     dstr_putf(&k->tag, ".%s", t);
-  if (key_plock(k->tag.buf, kd, kd))
+  if (key_plock(kd, 0, k->tag.buf))
     die(EXIT_FAILURE, "couldn't lock key");
 }
 
-/* --- @mpkey@ --- *
- *
- * Arguments:  @key_data *kd@ = pointer to parent key block
- *             @const char *tag@ = pointer to tag string
- *             @mp *m@ = integer to store
- *             @unsigned f@ = flags to set
- *
- * Returns:    ---
- *
- * Use:                Sets a multiprecision integer subkey.
- */
-
-static void mpkey(key_data *kd, const char *tag, mp *m, unsigned f)
-{
-  key_data *kkd = key_structcreate(kd, tag);
-  key_mp(kkd, m);
-  kkd->e |= f;
-}
-
 /* --- @copyparam@ --- *
  *
  * Arguments:  @keyopts *k@ = pointer to key options
@@ -260,7 +244,7 @@ static int copyparam(keyopts *k, const char **pp)
   /* --- Run through the checklist --- */
 
   while (*pp) {
-    key_data *kd = key_structfind(&k->p->k, *pp);
+    key_data *kd = key_structfind(k->p->k, *pp);
     if (!kd)
       die(EXIT_FAILURE, "bad parameter key: parameter `%s' not found", *pp);
     if ((kd->e & KF_CATMASK) != KCAT_SHARE)
@@ -272,8 +256,7 @@ static int copyparam(keyopts *k, const char **pp)
 
   kf.f = KCAT_SHARE;
   kf.m = KF_CATMASK;
-  if (!key_copy(&k->k->k, &k->p->k, &kf))
-    die(EXIT_FAILURE, "unexpected failure while copying parameters");
+  key_setkeydata(k->kf, k->k, k->p->k);
 
   /* --- Copy over attributes --- */
 
@@ -328,7 +311,8 @@ static void keyrand(key_file *kf, const char *id)
     k = key_bytype(kf, "catacomb-rand");
 
   if (k) {
-    key_data *kd = &k->k, kkd;
+    key_data *kd = k->k, *kkd;
+    key_incref(kd);
 
   again:
     switch (kd->e & KF_ENCMASK) {
@@ -337,10 +321,11 @@ static void keyrand(key_file *kf, const char *id)
       case KENC_ENCRYPT: {
        dstr d = DSTR_INIT;
        key_fulltag(k, &d);
-       if (key_punlock(d.buf, kd, &kkd))
+       if (key_punlock(&kkd, kd, d.buf))
          die(EXIT_FAILURE, "error unlocking key `%s'", d.buf);
        dstr_destroy(&d);
-       kd = &kkd;
+       key_drop(kd);
+       kd = kkd;
       } goto again;
       default: {
        dstr d = DSTR_INIT;
@@ -352,8 +337,7 @@ static void keyrand(key_file *kf, const char *id)
     /* --- Key the generator --- */
 
     rand_key(RAND_GLOBAL, kd->u.k.k, kd->u.k.sz);
-    if (kd == &kkd)
-      key_destroy(&kkd);
+    key_drop(kd);
   }
 }
 
@@ -363,6 +347,7 @@ static void alg_binary(keyopts *k)
 {
   unsigned sz;
   unsigned m;
+  key_data *kd;
   octet *p;
 
   if (!k->bits)
@@ -375,17 +360,19 @@ static void alg_binary(keyopts *k)
   m = (1 << (((k->bits - 1) & 7) + 1)) - 1;
   k->r->ops->fill(k->r, p, sz);
   *p &= m;
-  key_binary(&k->k->k, p, sz);
-  k->k->k.e |= KCAT_SYMM | KF_BURN;
+  kd = key_newbinary(KCAT_SYMM | KF_BURN, p, sz);
   memset(p, 0, sz);
+  dolock(k, &kd, 0);
+  key_setkeydata(k->kf, k->k, kd);
+  key_drop(kd);
   sub_free(p, sz);
-  dolock(k, &k->k->k, 0);
 }
 
 static void alg_des(keyopts *k)
 {
   unsigned sz;
   octet *p;
+  key_data *kd;
   int i;
 
   if (!k->bits)
@@ -405,17 +392,18 @@ static void alg_des(keyopts *k)
     x = x ^ (x >> 1);
     p[i] = (p[i] & 0xfe) | (x & 0x01);
   }
-  key_binary(&k->k->k, p, sz);
-  k->k->k.e |= KCAT_SYMM | KF_BURN;
+  kd = key_newbinary(KCAT_SYMM | KF_BURN, p, sz);
   memset(p, 0, sz);
+  dolock(k, &kd, 0);
+  key_setkeydata(k->kf, k->k, kd);
+  key_drop(kd);
   sub_free(p, sz);
-  dolock(k, &k->k->k, 0);
 }
 
 static void alg_rsa(keyopts *k)
 {
   rsa_priv rp;
-  key_data *kd;
+  key_data *kd, *kkd;
 
   /* --- Sanity checking --- */
 
@@ -452,21 +440,21 @@ static void alg_rsa(keyopts *k)
 
   /* --- Allrighty then --- */
 
-  kd = &k->k->k;
-  key_structure(kd);
-  mpkey(kd, "n", rp.n, KCAT_PUB);
-  mpkey(kd, "e", rp.e, KCAT_PUB);
-
-  kd = key_structcreate(kd, "private");
-  key_structure(kd);
-  mpkey(kd, "d", rp.d, KCAT_PRIV | KF_BURN);
-  mpkey(kd, "p", rp.p, KCAT_PRIV | KF_BURN);
-  mpkey(kd, "q", rp.q, KCAT_PRIV | KF_BURN);
-  mpkey(kd, "q-inv", rp.q_inv, KCAT_PRIV | KF_BURN);
-  mpkey(kd, "d-mod-p", rp.dp, KCAT_PRIV | KF_BURN);
-  mpkey(kd, "d-mod-q", rp.dq, KCAT_PRIV | KF_BURN);
-  dolock(k, kd, "private");
-
+  kd = key_newstruct();
+  key_structsteal(kd, "n", key_newmp(KCAT_PUB, rp.n));
+  key_structsteal(kd, "e", key_newmp(KCAT_PUB, rp.e));
+
+  kkd = key_newstruct();
+  key_structsteal(kkd, "d", key_newmp(KCAT_PRIV | KF_BURN, rp.d));
+  key_structsteal(kkd, "p", key_newmp(KCAT_PRIV | KF_BURN, rp.p));
+  key_structsteal(kkd, "q", key_newmp(KCAT_PRIV | KF_BURN, rp.q));
+  key_structsteal(kkd, "q-inv", key_newmp(KCAT_PRIV | KF_BURN, rp.q_inv));
+  key_structsteal(kkd, "d-mod-p", key_newmp(KCAT_PRIV | KF_BURN, rp.dp));
+  key_structsteal(kkd, "d-mod-q", key_newmp(KCAT_PRIV | KF_BURN, rp.dq));
+  dolock(k, &kkd, "private");
+  key_structsteal(kd, "private", kkd);
+  key_setkeydata(k->kf, k->k, kd);
+  key_drop(kd);
   rsa_privfree(&rp);
 }
 
@@ -479,7 +467,7 @@ static void alg_dsaparam(keyopts *k)
     size_t sz;
     dstr d = DSTR_INIT;
     base64_ctx c;
-    key_data *kd = &k->k->k;
+    key_data *kd;
     dsa_seed ds;
 
     /* --- Choose appropriate bit lengths if necessary --- */
@@ -487,7 +475,7 @@ static void alg_dsaparam(keyopts *k)
     if (!k->qbits)
       k->qbits = 160;
     if (!k->bits)
-      k->bits = 768;
+      k->bits = 1024;
 
     /* --- Allocate a seed block --- */
 
@@ -503,13 +491,15 @@ static void alg_dsaparam(keyopts *k)
 
     /* --- Store the parameters --- */
 
-    key_structure(kd);
-    mpkey(kd, "q", dp.q, KCAT_SHARE);
-    mpkey(kd, "p", dp.p, KCAT_SHARE);
-    mpkey(kd, "g", dp.g, KCAT_SHARE);
+    kd = key_newstruct();
+    key_structsteal(kd, "q", key_newmp(KCAT_SHARE, dp.q));
+    key_structsteal(kd, "p", key_newmp(KCAT_SHARE, dp.p));
+    key_structsteal(kd, "g", key_newmp(KCAT_SHARE, dp.g));
     mp_drop(dp.q);
     mp_drop(dp.p);
     mp_drop(dp.g);
+    key_setkeydata(k->kf, k->k, kd);
+    key_drop(kd);
 
     /* --- Store the seed for future verification --- */
 
@@ -518,6 +508,7 @@ static void alg_dsaparam(keyopts *k)
     c.indent = "";
     base64_encode(&c, ds.p, ds.sz, &d);
     base64_encode(&c, 0, 0, &d);
+    DPUTZ(&d);
     key_putattr(k->kf, k->k, "seed", d.buf);
     DRESET(&d);
     dstr_putf(&d, "%u", ds.count);
@@ -533,11 +524,12 @@ static void alg_dsa(keyopts *k)
   mp *q, *p, *g;
   mp *x, *y;
   mpmont mm;
-  key_data *kd = &k->k->k;
+  key_data *kd, *kkd;
 
   /* --- Get the shared parameters --- */
 
   alg_dsaparam(k);
+  key_split(&k->k->k); kd = k->k->k;
   q = getmp(kd, "q");
   p = getmp(kd, "p");
   g = getmp(kd, "g");
@@ -550,12 +542,12 @@ static void alg_dsa(keyopts *k)
 
   /* --- Store everything away --- */
 
-  mpkey(kd, "y", y, KCAT_PUB);
+  key_structsteal(kd, "y", key_newmp(KCAT_PUB, y));
 
-  kd = key_structcreate(kd, "private");
-  key_structure(kd);
-  mpkey(kd, "x", x, KCAT_PRIV | KF_BURN);
-  dolock(k, kd, "private");
+  kkd = key_newstruct();
+  key_structsteal(kkd, "x", key_newmp(KCAT_PRIV | KF_BURN, x));
+  dolock(k, &kkd, "private");
+  key_structsteal(kd, "private", kkd);
 
   mp_drop(x); mp_drop(y);
 }
@@ -563,25 +555,34 @@ static void alg_dsa(keyopts *k)
 static void alg_dhparam(keyopts *k)
 {
   static const char *pl[] = { "p", "q", "g", 0 };
-  key_data *kd = &k->k->k;
+  key_data *kd;
   if (!copyparam(k, pl)) {
     dh_param dp;
     int rc;
 
     if (k->curve) {
       qd_parse qd;
+      group *g;
+      const char *e;
       
       if (strcmp(k->curve, "list") == 0) {
        unsigned i, w;
-       LIST("Built-in prime groups", stdout, ptab[i].name, ptab[i].name);
+       LIST("Built-in prime fields", stdout, ptab[i].name, ptab[i].name);
        exit(0);
       }
       qd.p = k->curve;
       if (dh_parse(&qd, &dp))
-       die(EXIT_FAILURE, "error in group spec: %s", qd.e);
+       die(EXIT_FAILURE, "error in field spec: %s", qd.e);
+      if (!qd_eofp(&qd))
+       die(EXIT_FAILURE, "junk at end of field spec");
+      if ((g = group_prime(&dp)) == 0)
+       die(EXIT_FAILURE, "invalid prime field");
+      if (!(k->f & f_quiet) && (e = G_CHECK(g, &rand_global)) != 0)
+       moan("WARNING!  group check failed: %s", e);
+      G_DESTROYGROUP(g);
       goto done;
     }
-
+    
     if (!k->bits)
       k->bits = 1024;
 
@@ -616,13 +617,15 @@ static void alg_dhparam(keyopts *k)
       die(EXIT_FAILURE, "Diffie-Hellman parameter generation failed");
 
   done:
-    key_structure(kd);
-    mpkey(kd, "p", dp.p, KCAT_SHARE);
-    mpkey(kd, "q", dp.q, KCAT_SHARE);
-    mpkey(kd, "g", dp.g, KCAT_SHARE);
+    kd = key_newstruct();
+    key_structsteal(kd, "p", key_newmp(KCAT_SHARE, dp.p));
+    key_structsteal(kd, "q", key_newmp(KCAT_SHARE, dp.q));
+    key_structsteal(kd, "g", key_newmp(KCAT_SHARE, dp.g));
     mp_drop(dp.q);
     mp_drop(dp.p);
     mp_drop(dp.g);
+    key_setkeydata(k->kf, k->k, kd);
+    key_drop(kd);
   }
 }
 
@@ -631,11 +634,12 @@ static void alg_dh(keyopts *k)
   mp *x, *y;
   mp *p, *q, *g;
   mpmont mm;
-  key_data *kd = &k->k->k;
+  key_data *kd, *kkd;
 
   /* --- Get the shared parameters --- */
 
   alg_dhparam(k);
+  key_split(&k->k->k); kd = k->k->k;
   p = getmp(kd, "p");
   q = getmp(kd, "q");
   g = getmp(kd, "g");
@@ -655,12 +659,12 @@ static void alg_dh(keyopts *k)
 
   /* --- Store everything away --- */
 
-  mpkey(kd, "y", y, KCAT_PUB);
+  key_structsteal(kd, "y", key_newmp(KCAT_PUB, y));
 
-  kd = key_structcreate(kd, "private");
-  key_structure(kd);
-  mpkey(kd, "x", x, KCAT_PRIV | KF_BURN);
-  dolock(k, kd, "private");
+  kkd = key_newstruct();
+  key_structsteal(kkd, "x", key_newmp(KCAT_PRIV | KF_BURN, x));
+  dolock(k, &kkd, "private");
+  key_structsteal(kd, "private", kkd);
 
   mp_drop(x); mp_drop(y);
 }
@@ -668,7 +672,7 @@ static void alg_dh(keyopts *k)
 static void alg_bbs(keyopts *k)
 {
   bbs_priv bp;
-  key_data *kd;
+  key_data *kd, *kkd;
 
   /* --- Sanity checking --- */
 
@@ -685,26 +689,128 @@ static void alg_bbs(keyopts *k)
 
   /* --- Allrighty then --- */
 
-  kd = &k->k->k;
-  key_structure(kd);
-  mpkey(kd, "n", bp.n, KCAT_PUB);
+  kd = key_newstruct();
+  key_structsteal(kd, "n", key_newmp(KCAT_PUB, bp.n));
 
-  kd = key_structcreate(kd, "private");
-  key_structure(kd);
-  mpkey(kd, "p", bp.p, KCAT_PRIV | KF_BURN);
-  mpkey(kd, "q", bp.q, KCAT_PRIV | KF_BURN);
-  dolock(k, kd, "private");
+  kkd = key_newstruct();
+  key_structsteal(kkd, "p", key_newmp(KCAT_PRIV | KF_BURN, bp.p));
+  key_structsteal(kkd, "q", key_newmp(KCAT_PRIV | KF_BURN, bp.q));
+  dolock(k, &kkd, "private");
+  key_structsteal(kd, "private", kkd);
+  key_setkeydata(k->kf, k->k, kd);
+  key_drop(kd);
 
   bbs_privfree(&bp);
 }
 
+static void alg_binparam(keyopts *k)
+{
+  static const char *pl[] = { "p", "q", "g", 0 };
+  if (!copyparam(k, pl)) {
+    gbin_param gb;
+    qd_parse qd;
+    group *g;
+    const char *e;
+    key_data *kd;
+
+    /* --- Decide on a field --- */
+
+    if (!k->bits) k->bits = 128;
+    if (k->curve && strcmp(k->curve, "list") == 0) {
+      unsigned i, w;
+      LIST("Built-in binary fields", stdout,
+          bintab[i].name, bintab[i].name);
+      exit(0);
+    }
+    if (!k->curve) {
+      if (k->bits <= 40) k->curve = "p1363-40";
+      else if (k->bits <= 56) k->curve = "p1363-56";
+      else if (k->bits <= 64) k->curve = "p1363-64";
+      else if (k->bits <= 80) k->curve = "p1363-80";
+      else if (k->bits <= 112) k->curve = "p1363-112";
+      else if (k->bits <= 128) k->curve = "p1363-128";
+      else {
+       die(EXIT_FAILURE,
+           "no built-in binary fields provide %u-bit security",
+           k->bits);
+      }
+    }
+
+    /* --- Check it --- */
+
+    qd.e = 0;
+    qd.p = k->curve;
+    if (dhbin_parse(&qd, &gb))
+      die(EXIT_FAILURE, "error in field spec: %s", qd.e);
+    if (!qd_eofp(&qd))
+      die(EXIT_FAILURE, "junk at end of field spec");
+    if ((g = group_binary(&gb)) == 0)
+      die(EXIT_FAILURE, "invalid binary field");
+    if (!(k->f & f_quiet) && (e = G_CHECK(g, &rand_global)) != 0)
+      moan("WARNING!  group check failed: %s", e);
+    G_DESTROYGROUP(g);
+
+    /* --- Write out the answer --- */
+
+    kd = key_newstruct();
+    key_structsteal(kd, "p", key_newmp(KCAT_SHARE, gb.p));
+    key_structsteal(kd, "q", key_newmp(KCAT_SHARE, gb.q));
+    key_structsteal(kd, "g", key_newmp(KCAT_SHARE, gb.g));
+    mp_drop(gb.q);
+    mp_drop(gb.p);
+    mp_drop(gb.g);
+    key_setkeydata(k->kf, k->k, kd);
+    key_drop(kd);
+  }
+}
+
+static void alg_bin(keyopts *k)
+{
+  mp *x, *y;
+  mp *p, *q, *g;
+  gfreduce r;
+  key_data *kd, *kkd;
+
+  /* --- Get the shared parameters --- */
+
+  alg_binparam(k);
+  key_split(&k->k->k); kd = k->k->k;
+  p = getmp(kd, "p");
+  q = getmp(kd, "q");
+  g = getmp(kd, "g");
+
+  /* --- Choose a suitable private key --- *
+   *
+   * Since %$g$% has order %$q$%, choose %$x < q$%.
+   */
+
+  x = mprand_range(MP_NEWSEC, q, k->r, 0);
+
+  /* --- Compute the public key %$y = g^x \bmod p$% --- */
+
+  gfreduce_create(&r, p);
+  y = gfreduce_exp(&r, MP_NEW, g, x);
+  gfreduce_destroy(&r);
+
+  /* --- Store everything away --- */
+
+  key_structsteal(kd, "y", key_newmp(KCAT_PUB, y));
+
+  kkd = key_newstruct();
+  key_structsteal(kkd, "x", key_newmp(KCAT_PRIV | KF_BURN, x));
+  dolock(k, &kkd, "private");
+  key_structsteal(kd, "private", kkd);
+
+  mp_drop(x); mp_drop(y);
+}
+
 static void alg_ecparam(keyopts *k)
 {
   static const char *pl[] = { "curve", 0 };
   if (!copyparam(k, pl)) {
     ec_info ei;
     const char *e;
-    key_data *kd = &k->k->k;
+    key_data *kd;
 
     /* --- Decide on a curve --- */
 
@@ -739,16 +845,16 @@ static void alg_ecparam(keyopts *k)
 
     /* --- Write out the answer --- */
 
-    key_structure(kd);
-    kd = key_structcreate(kd, "curve");
-    key_string(kd, k->curve);
-    kd->e |= KCAT_SHARE;
+    kd = key_newstruct();
+    key_structsteal(kd, "curve", key_newstring(KCAT_SHARE, k->curve));
+    key_setkeydata(k->kf, k->k, kd);
+    key_drop(kd);
   }
 }
 
 static void alg_ec(keyopts *k)
 {
-  key_data *kd = &k->k->k;
+  key_data *kd;
   key_data *kkd;
   mp *x = MP_NEW;
   ec p = EC_INIT;
@@ -758,6 +864,7 @@ static void alg_ec(keyopts *k)
   /* --- Get the curve --- */
 
   alg_ecparam(k);
+  key_split(&k->k->k); kd = k->k->k;
   if ((kkd = key_structfind(kd, "curve")) == 0)
     die(EXIT_FAILURE, "unexpected failure looking up subkey `curve')");
   if ((kkd->e & KF_ENCMASK) != KENC_STRING)
@@ -772,12 +879,12 @@ static void alg_ec(keyopts *k)
 
   /* --- Store everything away --- */
 
-  kkd = key_structcreate(kd, "p");
-  key_ec(kkd, &p);
-  kkd->e |= KCAT_PUB;
-  kkd = key_structcreate(kd, "private");
-  key_structure(kkd);
-  mpkey(kkd, "x", x, KCAT_PRIV | KF_BURN);
+  key_structsteal(kd, "p", key_newec(KCAT_PUB, &p));
+
+  kkd = key_newstruct();
+  key_structsteal(kkd, "x", key_newmp(KCAT_PRIV | KF_BURN, x));
+  dolock(k, &kkd, "private");
+  key_structsteal(kd, "private", kkd);
 
   /* --- Done --- */
 
@@ -797,11 +904,13 @@ static keyalg algtab[] = {
   { "binary",          alg_binary,     "Plain binary data" },
   { "des",             alg_des,        "Binary with DES-style parity" },
   { "rsa",             alg_rsa,        "RSA public-key encryption" },
+  { "bbs",             alg_bbs,        "Blum-Blum-Shub generator" },
   { "dsa",             alg_dsa,        "DSA digital signatures" },
   { "dsa-param",       alg_dsaparam,   "DSA shared parameters" },
   { "dh",              alg_dh,         "Diffie-Hellman key exchange" },
   { "dh-param",                alg_dhparam,    "Diffie-Hellman parameters" },
-  { "bbs",             alg_bbs,        "Blum-Blum-Shub generator" },
+  { "bindh",           alg_bin,        "DH over a binary field" },
+  { "bindh-param",     alg_binparam,   "Binary-field DH parameters" },
   { "ec-param",                alg_ecparam,    "Elliptic curve parameters" },
   { "ec",              alg_ec,         "Elliptic curve crypto" },
   { 0,                 0 }
@@ -1045,10 +1154,9 @@ static int cmd_add(int argc, char *argv[])
   for (;;) {
     uint32 id = rand_global.ops->word(&rand_global);
     int err;
-    k.k = key_new(&f, id, argv[optind], exp, &err);
-    if (k.k)
+    if ((err = key_new(&f, id, argv[optind], exp, &k.k)) == 0)
       break;
-    if (err != KERR_DUPID)
+    else if (err != KERR_DUPID)
       die(EXIT_FAILURE, "error adding new key: %s", key_strerror(err));
   }
 
@@ -1084,7 +1192,7 @@ static int cmd_add(int argc, char *argv[])
   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)
+    if ((k.p->k->e & KF_ENCMASK) != KENC_STRUCT)
       die(EXIT_FAILURE, "parameter key `%s' is not structured", ptag);
   }
 
@@ -1094,6 +1202,7 @@ static int cmd_add(int argc, char *argv[])
 
   /* --- Done --- */
 
+  dstr_destroy(&k.tag);
   doclose(&f);
   return (0);
 }
@@ -1176,13 +1285,13 @@ static void showkeydata(key_data *k, int ind, listopts *o, dstr *d)
       if (o->v <= 3)
        fputs(" encrypted\n", stdout);
       else {
-       key_data kd;
-       if (key_punlock(d->buf, k, &kd))
+       key_data *kd;
+       if (key_punlock(&kd, k, d->buf))
          printf(" <failed to unlock %s>\n", d->buf);
        else {
          fputs(" encrypted", stdout);
-         showkeydata(&kd, ind, o, d);
-         key_destroy(&kd);
+         showkeydata(kd, ind, o, d);
+         key_drop(kd);
        }
       }
       break;
@@ -1223,19 +1332,19 @@ static void showkeydata(key_data *k, int ind, listopts *o, dstr *d)
      */
 
     case KENC_STRUCT: {
-      sym_iter i;
-      key_struct *ks;
+      key_subkeyiter i;
+      const char *tag;
       size_t n = d->len;
 
       fputs(" {\n", stdout);
-      for (sym_mkiter(&i, &k->u.s); (ks = sym_next(&i)) != 0; ) {
-       if (!key_match(&ks->k, &o->kf))
+      for (key_mksubkeyiter(&i, k); key_nextsubkey(&i, &tag, &k); ) {
+       if (!key_match(k, &o->kf))
          continue;
        INDENT(ind + 2);
-       printf("%s =", SYM_NAME(ks));
+       printf("%s =", tag);
        d->len = n;
-       dstr_putf(d, ".%s", SYM_NAME(ks));
-       showkeydata(&ks->k, ind + 2, o, d);
+       dstr_putf(d, ".%s", tag);
+       showkeydata(k, ind + 2, o, d);
       }
       INDENT(ind);
       fputs("}\n", stdout);
@@ -1262,7 +1371,7 @@ static void showkey(key *k, listopts *o)
 
   /* --- Skip the key if the filter doesn't match --- */
 
-  if (!key_match(&k->k, &o->kf))
+  if (!key_match(k->k, &o->kf))
     return;
 
   /* --- Sort out the expiry and deletion times --- */
@@ -1334,7 +1443,7 @@ static void showkey(key *k, listopts *o)
     dstr d = DSTR_INIT;
     fputs("key:", stdout);
     key_fulltag(k, &d);
-    showkeydata(&k->k, 0, o, &d);
+    showkeydata(k->k, 0, o, &d);
     dstr_destroy(&d);
   }
     
@@ -1583,6 +1692,101 @@ static int cmd_finger(int argc, char *argv[])
   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;
+  int rc = 0;
+  const gchash *ch = &rmd160;
+  ghash *h;
+  key *k;
+  octet *buf;
+  const octet *fpr;
+  key_filter kf = { KF_NONSECRET, KF_NONSECRET };
+
+  for (;;) {
+    static struct option opt[] = {
+      { "filter",      OPTF_ARGREQ,    0,      'f' },
+      { "algorithm",   OPTF_ARGREQ,    0,      'a' },
+      { 0,             0,              0,      0 }
+    };
+    int i = mdwopt(argc, argv, "+f:a:", opt, 0, 0, 0);
+    if (i < 0)
+      break;
+    switch (i) {
+      case 'f': {
+       char *p;
+       int err = key_readflags(optarg, &p, &kf.f, &kf.m);
+       if (err || *p)
+         die(EXIT_FAILURE, "bad filter string `%s'", optarg);
+      } break;
+      case 'a':
+       if ((ch = ghash_byname(optarg)) == 0)
+         die(EXIT_FAILURE, "unknown hash algorithm `%s'", optarg);
+       break;
+      default:
+       rc = 1;
+       break;
+    }
+  }
+
+  argv += optind; argc -= optind;
+  if (rc || argc != 2)
+    die(EXIT_FAILURE, "Usage: verify [-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);
+  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)
+    die(EXIT_FAILURE, "key fingerprint mismatch");
+  return (0);
+}
+  
 /* --- @cmd_comment@ --- */
 
 static int cmd_comment(int argc, char *argv[])
@@ -1652,7 +1856,7 @@ static int cmd_lock(int argc, char *argv[])
 {
   key_file f;
   key *k;
-  key_data *kd;
+  key_data **kd;
   dstr d = DSTR_INIT;
 
   if (argc != 2)
@@ -1660,9 +1864,9 @@ static int cmd_lock(int argc, char *argv[])
   doopen(&f, KOPEN_WRITE);
   if (key_qtag(&f, argv[1], &d, &k, &kd))
     die(EXIT_FAILURE, "key `%s' not found", argv[1]);
-  if (kd->e == KENC_ENCRYPT && key_punlock(d.buf, kd, kd))
+  if ((*kd)->e == KENC_ENCRYPT && key_punlock(kd, 0, d.buf))
     die(EXIT_FAILURE, "couldn't unlock key `%s'", d.buf);
-  if (key_plock(d.buf, kd, kd))
+  if (key_plock(kd, 0, d.buf))
     die(EXIT_FAILURE, "failed to lock key `%s'", d.buf);
   f.f |= KF_MODIFIED;
   doclose(&f);
@@ -1675,7 +1879,7 @@ static int cmd_unlock(int argc, char *argv[])
 {
   key_file f;
   key *k;
-  key_data *kd;
+  key_data **kd;
   dstr d = DSTR_INIT;
 
   if (argc != 2)
@@ -1683,9 +1887,9 @@ static int cmd_unlock(int argc, char *argv[])
   doopen(&f, KOPEN_WRITE);
   if (key_qtag(&f, argv[1], &d, &k, &kd))
     die(EXIT_FAILURE, "key `%s' not found", argv[1]);
-  if (kd->e != KENC_ENCRYPT)
+  if ((*kd)->e != KENC_ENCRYPT)
     die(EXIT_FAILURE, "key `%s' is not encrypted", d.buf);
-  if (kd->e == KENC_ENCRYPT && key_punlock(d.buf, kd, kd))
+  if (key_punlock(kd, 0, d.buf))
     die(EXIT_FAILURE, "couldn't unlock key `%s'", d.buf);
   f.f |= KF_MODIFIED;
   doclose(&f);
@@ -1781,7 +1985,7 @@ static int cmd_merge(int argc, char *argv[])
   if (strcmp(argv[1], "-") == 0)
     fp = stdin;
   else if (!(fp = fopen(argv[1], "r"))) {
-    die(EXIT_FAILURE, "couldn't open `%s' for writing: %s",
+    die(EXIT_FAILURE, "couldn't open `%s' for reading: %s",
        argv[1], strerror(errno));
   }
 
@@ -1800,8 +2004,10 @@ static int cmd_merge(int argc, char *argv[])
      ghashtab[i], ghashtab[i]->name)                                   \
   LI("Elliptic curves", ec,                                            \
      ectab[i].name, ectab[i].name)                                     \
-  LI("Diffie-Hellman groups", dh,                                      \
+  LI("Prime Diffie-Hellman groups", dh,                                        \
      ptab[i].name, ptab[i].name)                                       \
+  LI("Binary Diffie-Hellman groups", bindh,                            \
+     bintab[i].name, bintab[i].name)                                   \
   LI("Key-generation algorithms", keygen,                              \
      algtab[i].name, algtab[i].name)                                   \
   LI("Random seeding algorithms", seed,                                        \
@@ -1835,6 +2041,13 @@ Options:\n\
 -a, --algorithm=HASH   Use the named HASH algorithm.\n\
                          ($ show hash for list.)\n\
 " },
+  { "verify", cmd_verify, "verify [-f FILTER] TAG FINGERPRINT", "\
+Options:\n\
+\n\
+-f, --filter=FILT      Only hash key components matching FILT.\n\
+-a, --algorithm=HASH   Use the named HASH algorithm.\n\
+                         ($ show hash for list.)\n\
+" },
   { "extract", cmd_extract, "extract [-f FILTER] FILE [TAG...]", "\
 Options:\n\
 \n\