chiark / gitweb /
Allow user-specified symmetric crypto algorithms.
[tripe] / keyexch.c
index c17f400..18594cf 100644 (file)
--- a/keyexch.c
+++ b/keyexch.c
@@ -1,6 +1,6 @@
 /* -*-c-*-
  *
- * $Id: keyexch.c,v 1.4 2001/06/22 19:40:36 mdw Exp $
+ * $Id: keyexch.c,v 1.13 2004/04/18 18:08:11 mdw Exp $
  *
  * Key exchange protocol
  *
  * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
  */
 
-/*----- Revision history --------------------------------------------------* 
+/*----- Header files ------------------------------------------------------*/
+
+#include "tripe.h"
+
+/*----- Brief protocol overview -------------------------------------------*
  *
- * $Log: keyexch.c,v $
- * Revision 1.4  2001/06/22 19:40:36  mdw
- * Support expiry of other peers' public keys.
+ * Let %$G$% be a cyclic group; let %$g$% be a generator of %$G$%, and let
+ * %$q$% be the order of %$G$%; for a key %$K$%, let %$E_K(\cdot)$% denote
+ * application of the symmetric packet protocol to a message; let
+ * %$H(\cdot)$% be the random oracle.  Let $\alpha \inr \{0,\ldots,q - 1\}$%
+ * be Alice's private key; let %$a = g^\alpha$% be her public key; let %$b$%
+ * be Bob's public key.
  *
- * Revision 1.3  2001/06/19 22:07:09  mdw
- * Cosmetic fixes.
+ * At the beginning of the session, Alice chooses
  *
- * Revision 1.2  2001/02/16 21:24:27  mdw
- * Rewrite for new key exchange protocol.
+ *   %$\rho_A \inr \{0, \ldots q - 1\}$%
  *
- * Revision 1.1  2001/02/03 20:26:37  mdw
- * Initial checkin.
+ * We also have:
  *
- */
-
-/*----- Header files ------------------------------------------------------*/
-
-#include "tripe.h"
+ * %$r_A = g^{\rho_A}$%                        Alice's challenge
+ * %$c_A = H(\cookie{cookie}, r_A)$%   Alice's cookie
+ * %$v_A = \rho_A \xor H(\cookie{expected-reply}, r_A, r_B, b^{\rho_A})$%
+ *                                     Alice's challenge check value
+ * %$r_B^\alpha = a^{\rho_B}$%         Alice's reply
+ * %$K = r_B^{\rho_A} = r_B^{\rho_A} = g^{\rho_A\rho_B}$%
+ *                                     Alice and Bob's shared secret key
+ * %$w_A = H(\cookie{switch-request}, c_A, c_B)$%
+ *                                     Alice's switch request value
+ * %$u_A = H(\cookie{switch-confirm}, c_A, c_B)$%
+ *                                     Alice's switch confirm value
+ *
+ * The messages are then:
+ *
+ * %$\cookie{kx-pre-challenge}, r_A$%
+ *     Initial greeting.  In state @KXS_CHAL@.
+ *
+ * %$\cookie{kx-cookie}, r_A, c_B$%
+ *     My table is full but I got your message.
+ *
+ * %$\cookie{kx-challenge}, r_A, c_B, v_A$%
+ *     Here's a full challenge for you to answer.
+ *
+ * %$\cookie{kx-reply}, c_A, c_B, v_A, E_K(r_B^\alpha))$%
+ *     Challenge accpeted: here's the answer.  Commit to my challenge.  Move
+ *     to @KXS_COMMIT@.
+ *
+ * %$\cookie{kx-switch}, c_A, c_B, E_K(r_B^\alpha, w_A))$%
+ *     Reply received: here's my reply.  Committed; send data; move to
+ *     @KXS_SWITCH@.
+ *
+ * %$\cookie{kx-switch-ok}, E_K(u_A))$%
+ *     Switch received.  Committed; send data; move to @KXS_SWITCH@.
+ */ 
 
 /*----- Tunable parameters ------------------------------------------------*/
 
-#define T_VALID MIN(2)
-#define T_RETRY SEC(10)
+#define T_VALID MIN(2)                 /* Challenge validity period */
+#define T_RETRY SEC(10)                        /* Challenge retransmit interval */
 
 #define ISVALID(kx, now) ((now) < (kx)->t_valid)
 
 /*----- Various utilities -------------------------------------------------*/
 
-/* --- @hashmp@ --- *
+/* --- @hashge@ --- *
  *
- * Arguments:  @HASH_CTX *r@ = pointer to hash context
- *             @mp *m@ = pointer to multiprecision integer
+ * Arguments:  @ghash *h@ = pointer to hash context
+ *             @ge *x@ = pointer to group element
  *
  * Returns:    ---
  *
- * Use:                Adds the hash of a multiprecision integer to the context.
- *             Corrupts @buf_t@.
+ * Use:                Adds the hash of a group element to the context.  Corrupts
+ *             @buf_t@.
  */
 
-static void hashmp(HASH_CTX *r, mp *m)
+static void hashge(ghash *h, ge *x)
 {
   buf b;
   buf_init(&b, buf_t, sizeof(buf_t));
-  buf_putmp(&b, m);
+  G_TOBUF(gg, &b, x);
   assert(BOK(&b));
-  HASH(r, BBASE(&b), BLEN(&b));
+  GH_HASH(h, BBASE(&b), BLEN(&b));
+}
+
+/* --- @mpencrypt@, @mpdecrypt@ --- *
+ *
+ * Arguments:  @mp *d@ = the destination integer
+ *             @mp *x@ = the plaintext/ciphertext integer
+ *             @size_t sz@ = the expected size of the plaintext
+ *             @const octet *k@ = pointer to key material
+ *
+ * Returns:    The encrypted/decrypted integer.
+ *
+ * Use:                Encrypts (or decrypts) a multiprecision integer.  In fact,
+ *             the title is a bit of a misnomer: we actually compute
+ *             %$x \xor H(k)$%, so it's a random oracle thing rather than an
+ *             encryption thing.
+ */
+
+static mp *mpencrypt(mp *d, mp *x, size_t sz, const octet *k)
+{
+  gcipher *mgf;
+
+  mgf = GC_INIT(algs.mgf, k, algs.hashsz);
+  mp_storeb(x, buf_t, sz);
+  GC_ENCRYPT(mgf, buf_t, buf_t, sz);
+  GC_DESTROY(mgf);
+  return (mp_loadb(d, buf_t, sz));
+}
+
+static mp *mpdecrypt(mp *d, mp *x, size_t sz, const octet *k)
+{
+  gcipher *mgf;
+
+  mgf = GC_INIT(algs.mgf, k, algs.hashsz);
+  mp_storeb(x, buf_t, sz);
+  GC_DECRYPT(mgf, buf_t, buf_t, sz);
+  GC_DESTROY(mgf);
+  return (mp_loadb(d, buf_t, sz));
 }
 
 /* --- @timer@ --- *
@@ -143,8 +213,9 @@ static void kxc_destroy(kxchal *kxc)
 {
   if (kxc->f & KXF_TIMER)
     sel_rmtimer(&kxc->t);
-  mp_drop(kxc->c);
-  mp_drop(kxc->r);
+  G_DESTROY(gg, kxc->c);
+  if (kxc->r) G_DESTROY(gg, kxc->r);
+  mp_drop(kxc->ck);
   ks_drop(kxc->ks);
   DESTROY(kxc);
 }
@@ -164,6 +235,7 @@ static void kxc_stoptimer(kxchal *kxc)
 {
   if (kxc->f & KXF_TIMER)
     sel_rmtimer(&kxc->t);
+  kxc->f &= ~KXF_TIMER;
 }
 
 /* --- @kxc_new@ --- *
@@ -192,8 +264,9 @@ static kxchal *kxc_new(keyexch *kx)
   /* --- Fill in the new structure --- */
 
   kxc = CREATE(kxchal);
-  kxc->c = 0;
+  kxc->c = G_CREATE(gg);
   kxc->r = 0;
+  kxc->ck = MP_NEW;
   kxc->ks = 0;
   kxc->kx = kx;
   kxc->f = 0;
@@ -204,19 +277,19 @@ static kxchal *kxc_new(keyexch *kx)
 /* --- @kxc_bychal@ --- *
  *
  * Arguments:  @keyexch *kx@ = pointer to key exchange block
- *             @mp *c@ = challenge from remote host
+ *             @ge *c@ = challenge from remote host
  *
  * Returns:    Pointer to the challenge block, or null.
  *
  * Use:                Finds a challenge block, given its challenge.
  */
 
-static kxchal *kxc_bychal(keyexch *kx, mp *c)
+static kxchal *kxc_bychal(keyexch *kx, ge *c)
 {
   unsigned i;
 
   for (i = 0; i < kx->nr; i++) {
-    if (MP_EQ(c, kx->r[i]->c))
+    if (G_EQ(gg, c, kx->r[i]->c))
       return (kx->r[i]);
   }
   return (0);
@@ -237,7 +310,7 @@ static kxchal *kxc_byhc(keyexch *kx, const octet *hc)
   unsigned i;
 
   for (i = 0; i < kx->nr; i++) {
-    if (memcmp(hc, kx->r[i]->hc, HASHSZ) == 0)
+    if (memcmp(hc, kx->r[i]->hc, algs.hashsz) == 0)
       return (kx->r[i]);
   }
   return (0);
@@ -273,11 +346,11 @@ static void kxc_answer(keyexch *kx, kxchal *kxc)
   /* --- Build the reply packet --- */
 
   if (!kxc->r)
-    buf_putmp(b, kx->c);
+    G_TOBUF(gg, b, kx->c);
   else
-    buf_put(b, kx->hc, HASHSZ);
-  buf_put(b, kxc->hc, HASHSZ);
-  buf_put(b, kxc->hrx, HASHSZ);
+    buf_put(b, kx->hc, algs.hashsz);
+  buf_put(b, kxc->hc, algs.hashsz);
+  buf_putmp(b, kxc->ck);
 
   /* --- Maybe send an actual reply, if we have one --- */
 
@@ -287,9 +360,9 @@ static void kxc_answer(keyexch *kx, kxchal *kxc)
   } else {
     T( trace(T_KEYEXCH, "keyexch: sending reply to `%s'", p_name(kx->p)); )
     buf_init(&bb, buf_i, sizeof(buf_i));
-    buf_putmp(&bb, kxc->r);
+    G_TOBUF(gg, &bb, kxc->r);
     buf_flip(&bb);
-    ks_encrypt(kxc->ks, &bb, b);
+    ks_encrypt(kxc->ks, MSG_KEYEXCH | KX_REPLY, &bb, b);
   }
 
   /* --- Update the statistics --- */
@@ -315,35 +388,50 @@ static void kxc_answer(keyexch *kx, kxchal *kxc)
 /* --- @getreply@ --- *
  *
  * Arguments:  @keyexch *kx@ = pointer to key exchange context
- *             @mp *c@ = a challenge
- *             @const octet *hrx@ = the supplied expected-reply hash
+ *             @ge *c@ = a challenge
+ *             @mp *ck@ = the supplied expected-reply check value
  *
  * Returns:    A pointer to the reply, or null if the reply-hash was wrong.
  *
  * Use:                Computes replies to challenges.
  */
 
-static mp *getreply(keyexch *kx, mp *c, const octet *hrx)
+static ge *getreply(keyexch *kx, ge *c, mp *ck)
 {
-  mp *r = mpmont_exp(&mg, MP_NEW, c, kpriv.x);
-  HASH_CTX h;
-  octet buf[HASHSZ];
-
-  HASH_INIT(&h);
-  HASH_STRING(&h, "tripe-expected-reply");
-  hashmp(&h, c);
-  hashmp(&h, kx->c);
-  hashmp(&h, r);
-  HASH_DONE(&h, buf);
+  ge *r = G_CREATE(gg);
+  ge *y = G_CREATE(gg);
+  mp *a = MP_NEW;
+  ghash *h;
+  const octet *hh;
+  int ok;
+
+  G_EXP(gg, r, c, kpriv);
+  h = GH_INIT(algs.h);
+  HASH_STRING(h, "tripe-expected-reply");
+  hashge(h, c);
+  hashge(h, kx->c);
+  hashge(h, r);
+  hh = GH_DONE(h, 0);
+
+  a = mpdecrypt(MP_NEW, ck, mp_octets(gg->r), hh);
   IF_TRACING(T_KEYEXCH, IF_TRACING(T_CRYPTO, {
-    trace(T_CRYPTO, "crypto: computed reply = %s", mpstr(r));
-    trace_block(T_CRYPTO, "crypto: computed reply hash", buf, HASHSZ);
+    trace(T_CRYPTO, "crypto: computed reply = %s", gestr(gg, r));
+    trace_block(T_CRYPTO, "crypto: computed reply hash", hh, algs.hashsz);
+    trace(T_CRYPTO, "crypto: recovered log = %s", mpstr(a));
   }))
-  if (memcmp(buf, hrx, HASHSZ) != 0) {
-    a_warn("invalid expected-reply hash from `%s'", p_name(kx->p));
-    mp_drop(r);
-    return (0);
+  GH_DESTROY(h);
+  G_EXP(gg, y, gg->g, a);
+  ok = G_EQ(gg, y, c);
+  if (!ok) {
+    a_warn("invalid expected-reply check from `%s'", p_name(kx->p));
+    IF_TRACING(T_KEYEXCH, IF_TRACING(T_CRYPTO, {
+      trace(T_CRYPTO, "crypto: computed challenge = %s", gestr(gg, y));
+    }))
+    G_DESTROY(gg, r);
+    r = 0;
   }
+  mp_drop(a);
+  G_DESTROY(gg, y);
   return (r);
 }
 
@@ -360,10 +448,11 @@ static mp *getreply(keyexch *kx, mp *c, const octet *hrx)
 
 static int dochallenge(keyexch *kx, unsigned msg, buf *b)
 {
-  mp *c = 0;
-  const octet *hc = 0, *hrx = 0;
+  ge *c = G_CREATE(gg);
+  mp *ck = MP_NEW;
+  const octet *hc = 0;
   kxchal *kxc;
-  HASH_CTX h;
+  ghash *h;
 
   /* --- Ensure that we're in a sensible state --- */
 
@@ -374,18 +463,18 @@ static int dochallenge(keyexch *kx, unsigned msg, buf *b)
 
   /* --- Unpack the packet --- */
 
-  if ((c = buf_getmp(b)) == 0 ||
-      (msg >= KX_COOKIE && (hc = buf_get(b, HASHSZ)) == 0) ||
-      (msg >= KX_CHAL && (hrx = buf_get(b, HASHSZ)) == 0) ||
+  if (G_FROMBUF(gg, b, c) ||
+      (msg >= KX_COOKIE && (hc = buf_get(b, algs.hashsz)) == 0) ||
+      (msg >= KX_CHAL && (ck = buf_getmp(b)) == 0) ||
       BLEFT(b)) {
     a_warn("malformed packet from `%s'", p_name(kx->p));
     goto bad;
   }
 
   IF_TRACING(T_KEYEXCH, IF_TRACING(T_CRYPTO, {
-    trace(T_CRYPTO, "crypto: challenge = %s", mpstr(c));
-    if (hc) trace_block(T_CRYPTO, "crypto: cookie", hc, HASHSZ);
-    if (hrx) trace_block(T_CRYPTO, "crypto: response hash", hrx, HASHSZ);
+    trace(T_CRYPTO, "crypto: challenge = %s", gestr(gg, c));
+    if (hc) trace_block(T_CRYPTO, "crypto: cookie", hc, algs.hashsz);
+    if (ck) trace(T_CRYPTO, "crypto: check value = %s", mpstr(ck));
   }))
 
   /* --- First, handle a bare challenge --- *
@@ -396,18 +485,19 @@ static int dochallenge(keyexch *kx, unsigned msg, buf *b)
   if (!hc && kx->nr >= KX_THRESH) {
     T( trace(T_KEYEXCH, "keyexch: too many challenges -- sending cookie"); )
     b = p_txstart(kx->p, MSG_KEYEXCH | KX_COOKIE);
-    buf_putmp(b, kx->c);
-    HASH_INIT(&h);
-    HASH_STRING(&h, "tripe-cookie");
-    hashmp(&h, c);
-    HASH_DONE(&h, buf_get(b, HASHSZ));
+    G_TOBUF(gg, b, kx->c);
+    h = GH_INIT(algs.h);
+    HASH_STRING(h, "tripe-cookie");
+    hashge(h, c);
+    GH_DONE(h, buf_get(b, algs.hashsz));
+    GH_DESTROY(h);
     p_txend(kx->p);
     goto tidy;
   }
 
   /* --- Discard a packet with an invalid cookie --- */
 
-  if (hc && memcmp(hc, kx->hc, HASHSZ) != 0) {
+  if (hc && memcmp(hc, kx->hc, algs.hashsz) != 0) {
     a_warn("incorrect cookie from `%s'", p_name(kx->p));
     goto bad;
   }
@@ -419,7 +509,7 @@ static int dochallenge(keyexch *kx, unsigned msg, buf *b)
 
   if ((kxc = kxc_bychal(kx, c)) == 0) {
     size_t x, y, z;
-    mp *r;
+    ge *r;
 
     /* --- Be careful here --- *
      *
@@ -428,83 +518,96 @@ static int dochallenge(keyexch *kx, unsigned msg, buf *b)
      * it.
      */
 
-    if (!hrx)
+    if (!ck)
       kxc = kxc_new(kx);       
     else {
-      if ((r = getreply(kx, c, hrx)) == 0)
+      if ((r = getreply(kx, c, ck)) == 0)
        goto bad;
       kxc = kxc_new(kx);
       kxc->r = r;
     }
-    kxc->c = mp_copy(c);
+    kxc->c = G_CREATE(gg);
+    G_COPY(gg, kxc->c, c);
 
     /* --- Work out the cookie for this challenge --- */
 
-    HASH_INIT(&h);
-    HASH_STRING(&h, "tripe-cookie");
-    hashmp(&h, kxc->c);
-    HASH_DONE(&h, kxc->hc);    
+    h = GH_INIT(algs.h);
+    HASH_STRING(h, "tripe-cookie");
+    hashge(h, kxc->c);
+    GH_DONE(h, kxc->hc);
+    GH_DESTROY(h);
+
+    IF_TRACING(T_KEYEXCH, IF_TRACING(T_CRYPTO, {
+      trace_block(T_CRYPTO, "crypto: computed cookie", kxc->hc, algs.hashsz);
+    }))
 
     /* --- Compute the expected-reply hash --- */
 
-    HASH_INIT(&h);
-    HASH_STRING(&h, "tripe-expected-reply");
-    hashmp(&h, kx->c);
-    hashmp(&h, kxc->c);
-    hashmp(&h, kx->rx);
-    HASH_DONE(&h, kxc->hrx);
+    h = GH_INIT(algs.h);
+    HASH_STRING(h, "tripe-expected-reply");
+    hashge(h, kx->c);
+    hashge(h, kxc->c);
+    hashge(h, kx->rx);
+    hc = GH_DONE(h, 0);
+    kxc->ck = mpencrypt(MP_NEW, kx->alpha, mp_octets(gg->r), hc);
+    IF_TRACING(T_KEYEXCH, IF_TRACING(T_CRYPTO, {
+      trace_block(T_CRYPTO, "crypto: expected-reply hash", hc, algs.hashsz);
+      trace(T_CRYPTO, "crypto: my reply check = %s", mpstr(kxc->ck));
+    }))
+    GH_DESTROY(h);
 
     /* --- Work out the shared key --- */
 
-    r = mpmont_exp(&mg, MP_NEW, c, kx->alpha);
+    r = G_CREATE(gg);
+    G_EXP(gg, r, c, kx->alpha);
+    IF_TRACING(T_KEYEXCH, IF_TRACING(T_CRYPTO, {
+      trace(T_CRYPTO, "crypto: shared secret = %s", gestr(gg, r));
+    }))
 
     /* --- Compute the switch messages --- */
 
-    HASH_INIT(&h); HASH_STRING(&h, "tripe-switch-request");
-    hashmp(&h, kx->c); hashmp(&h, kxc->c);
-    HASH_DONE(&h, kxc->hswrq_out);
-    HASH_INIT(&h); HASH_STRING(&h, "tripe-switch-confirm");
-    hashmp(&h, kx->c); hashmp(&h, kxc->c);
-    HASH_DONE(&h, kxc->hswok_out);
+    h = GH_INIT(algs.h); HASH_STRING(h, "tripe-switch-request");
+    hashge(h, kx->c); hashge(h, kxc->c);
+    GH_DONE(h, kxc->hswrq_out); GH_DESTROY(h);
+    h = GH_INIT(algs.h); HASH_STRING(h, "tripe-switch-confirm");
+    hashge(h, kx->c); hashge(h, kxc->c);
+    GH_DONE(h, kxc->hswok_out); GH_DESTROY(h);
 
-    HASH_INIT(&h); HASH_STRING(&h, "tripe-switch-request");
-    hashmp(&h, kxc->c); hashmp(&h, kx->c);
-    HASH_DONE(&h, kxc->hswrq_in);
-    HASH_INIT(&h); HASH_STRING(&h, "tripe-switch-confirm");
-    hashmp(&h, kxc->c); hashmp(&h, kx->c);
-    HASH_DONE(&h, kxc->hswok_in);
+    h = GH_INIT(algs.h); HASH_STRING(h, "tripe-switch-request");
+    hashge(h, kxc->c); hashge(h, kx->c);
+    GH_DONE(h, kxc->hswrq_in); GH_DESTROY(h);
+    h = GH_INIT(algs.h); HASH_STRING(h, "tripe-switch-confirm");
+    hashge(h, kxc->c); hashge(h, kx->c);
+    GH_DONE(h, kxc->hswok_in); GH_DESTROY(h);
 
     IF_TRACING(T_KEYEXCH, IF_TRACING(T_CRYPTO, {
-      trace_block(T_CRYPTO, "crypto: computed cookie", kxc->hc, HASHSZ);
-      trace_block(T_CRYPTO, "crypto: my reply hash", kxc->hrx, HASHSZ);
-      trace(T_CRYPTO, "crypto: shared secret = %s", mpstr(r));
       trace_block(T_CRYPTO, "crypto: outbound switch request",
-                 kxc->hswrq_out, HASHSZ);
+                 kxc->hswrq_out, algs.hashsz);
       trace_block(T_CRYPTO, "crypto: outbound switch confirm",
-                 kxc->hswok_out, HASHSZ);
+                 kxc->hswok_out, algs.hashsz);
       trace_block(T_CRYPTO, "crypto: inbound switch request",
-                 kxc->hswrq_in, HASHSZ);
+                 kxc->hswrq_in, algs.hashsz);
       trace_block(T_CRYPTO, "crypto: inbound switch confirm",
-                 kxc->hswok_in, HASHSZ);
+                 kxc->hswok_in, algs.hashsz);
     }))
 
     /* --- Create a new symmetric keyset --- */
 
     buf_init(b, buf_o, sizeof(buf_o));
-    buf_putmp(b, kx->c); x = BLEN(b);
-    buf_putmp(b, kxc->c); y = BLEN(b);
-    buf_putmp(b, r); z = BLEN(b);
+    G_TOBUF(gg, b, kx->c); x = BLEN(b);
+    G_TOBUF(gg, b, kxc->c); y = BLEN(b);
+    G_TOBUF(gg, b, r); z = BLEN(b);
     assert(BOK(b));
 
-    kxc->ks = ks_gen(BBASE(b), x, y, z);
-    mp_drop(r);
+    kxc->ks = ks_gen(BBASE(b), x, y, z, kx->p);
+    G_DESTROY(gg, r);
   }
 
   /* --- Answer the challenge if we need to --- */
 
-  if (hrx && !kxc->r) {
-    mp *r;
-    if ((r = getreply(kx, c, hrx)) == 0)
+  if (ck && !kxc->r) {
+    ge *r;
+    if ((r = getreply(kx, c, ck)) == 0)
       goto bad;
     kxc->r = r;
   }
@@ -514,11 +617,13 @@ static int dochallenge(keyexch *kx, unsigned msg, buf *b)
   /* --- Tidy up and go home --- */
 
 tidy:
-  mp_drop(c);
+  G_DESTROY(gg, c);
+  mp_drop(ck);
   return (0);
 
 bad:
-  mp_drop(c);
+  G_DESTROY(gg, c);
+  mp_drop(ck);
   return (-1);
 }
 
@@ -543,20 +648,20 @@ static void resend(keyexch *kx)
       T( trace(T_KEYEXCH, "keyexch: sending prechallenge to `%s'",
               p_name(kx->p)); )
       b = p_txstart(kx->p, MSG_KEYEXCH | KX_PRECHAL);
-      buf_putmp(b, kx->c);
+      G_TOBUF(gg, b, kx->c);
       break;
     case KXS_COMMIT:
       T( trace(T_KEYEXCH, "keyexch: sending switch request to `%s'",
               p_name(kx->p)); )
       kxc = kx->r[0];
       b = p_txstart(kx->p, MSG_KEYEXCH | KX_SWITCH);
-      buf_put(b, kx->hc, HASHSZ);
-      buf_put(b, kxc->hc, HASHSZ);
+      buf_put(b, kx->hc, algs.hashsz);
+      buf_put(b, kxc->hc, algs.hashsz);
       buf_init(&bb, buf_i, sizeof(buf_i));
-      buf_putmp(&bb, kxc->r);
-      buf_put(&bb, kxc->hswrq_out, HASHSZ);
+      G_TOBUF(gg, &bb, kxc->r);
+      buf_put(&bb, kxc->hswrq_out, algs.hashsz);
       buf_flip(&bb);
-      ks_encrypt(kxc->ks, &bb, b);
+      ks_encrypt(kxc->ks, MSG_KEYEXCH | KX_SWITCH, &bb, b);
       break;
     case KXS_SWITCH:
       T( trace(T_KEYEXCH, "keyexch: sending switch confirmation to `%s'",
@@ -564,9 +669,9 @@ static void resend(keyexch *kx)
       kxc = kx->r[0];
       b = p_txstart(kx->p, MSG_KEYEXCH | KX_SWITCHOK);
       buf_init(&bb, buf_i, sizeof(buf_i));
-      buf_put(&bb, kxc->hswok_out, HASHSZ);
+      buf_put(&bb, kxc->hswok_out, algs.hashsz);
       buf_flip(&bb);
-      ks_encrypt(kxc->ks, &bb, b);
+      ks_encrypt(kxc->ks, MSG_KEYEXCH | KX_SWITCHOK, &bb, b);
       break;
     default:
       abort();
@@ -585,9 +690,10 @@ static void resend(keyexch *kx)
 /* --- @matchreply@ --- *
  *
  * Arguments:  @keyexch *kx@ = pointer to key exchange context
+ *             @unsigned ty@ = type of incoming message
  *             @const octet *hc_in@ = a hash of his challenge
  *             @const octet *hc_out@ = a hash of my challenge (cookie)
- *             @const octet *krx@ = his expected-reply hash (optional)
+ *             @mp *ck@ = his expected-reply hash (optional)
  *             @buf *b@ = encrypted remainder of the packet
  *
  * Returns:    A pointer to the challenge block if OK, or null on failure.
@@ -598,21 +704,21 @@ static void resend(keyexch *kx)
  *             challenge is returned.
  */
 
-static kxchal *matchreply(keyexch *kx, const octet *hc_in,
-                         const octet *hc_out, const octet *hrx, buf *b)
+static kxchal *matchreply(keyexch *kx, unsigned ty, const octet *hc_in,
+                         const octet *hc_out, mp *ck, buf *b)
 {
   kxchal *kxc;
   buf bb;
-  mp *r = 0;
+  ge *r = 0;
 
   /* --- Check the plaintext portions of the data --- */
 
   IF_TRACING(T_KEYEXCH, IF_TRACING(T_CRYPTO, {
-    trace_block(T_CRYPTO, "crypto: challenge", hc_in, HASHSZ);
-    trace_block(T_CRYPTO, "crypto: cookie", hc_out, HASHSZ);
-    if (hrx) trace_block(T_CRYPTO, "crypto: response hash", hrx, HASHSZ);
+    trace_block(T_CRYPTO, "crypto: challenge", hc_in, algs.hashsz);
+    trace_block(T_CRYPTO, "crypto: cookie", hc_out, algs.hashsz);
+    if (ck) trace(T_CRYPTO, "crypto: check value = %s", mpstr(ck));
   }))
-  if (memcmp(hc_out, kx->hc, HASHSZ) != 0) {
+  if (memcmp(hc_out, kx->hc, algs.hashsz) != 0) {
     a_warn("incorrect cookie from `%s'", p_name(kx->p));
     goto bad;
   }
@@ -624,11 +730,11 @@ static kxchal *matchreply(keyexch *kx, const octet *hc_in,
   /* --- Maybe compute a reply for the challenge --- */
 
   if (!kxc->r) {
-    if (!hrx) {
+    if (!ck) {
       a_warn("unexpected switch request from `%s'", p_name(kx->p));
       goto bad;
     }
-    if ((r = getreply(kx, kxc->c, hrx)) == 0)
+    if ((r = getreply(kx, kxc->c, ck)) == 0)
       goto bad;
     kxc->r = r;
     r = 0;
@@ -637,30 +743,31 @@ static kxchal *matchreply(keyexch *kx, const octet *hc_in,
   /* --- Decrypt the rest of the packet --- */
 
   buf_init(&bb, buf_o, sizeof(buf_o));
-  if (ks_decrypt(kxc->ks, b, &bb)) {
+  if (ks_decrypt(kxc->ks, ty, b, &bb)) {
     a_warn("failed to decrypt reply from `%s'", p_name(kx->p));
     goto bad;
   }
   buf_init(b, BBASE(&bb), BLEN(&bb));
-  if ((r = buf_getmp(b)) == 0) {
+  r = G_CREATE(gg);
+  if (G_FROMBUF(gg, b, r)) {
     a_warn("invalid reply packet from `%s'", p_name(kx->p));
     goto bad;
   }
   IF_TRACING(T_KEYEXCH, IF_TRACING(T_CRYPTO, {
-    trace(T_CRYPTO, "crypto: reply = %s", mpstr(r));
+    trace(T_CRYPTO, "crypto: reply = %s", gestr(gg, r));
   }))
-  if (!mp_eq(r, kx->rx)) {
+  if (!G_EQ(gg, r, kx->rx)) {
     a_warn("incorrect reply from `%s'", p_name(kx->p));
     goto bad;
   }
 
   /* --- Done --- */
 
-  mp_drop(r);
+  G_DESTROY(gg, r);
   return (kxc);
 
 bad:
-  mp_drop(r);
+  if (r) G_DESTROY(gg, r);
   return (0);
 }
 
@@ -702,20 +809,22 @@ static void commit(keyexch *kx, kxchal *kxc)
 
 static int doreply(keyexch *kx, buf *b)
 {
-  const octet *hc_in, *hc_out, *hrx;
+  const octet *hc_in, *hc_out;
+  mp *ck = 0;
   kxchal *kxc;
 
   if (kx->s != KXS_CHAL && kx->s != KXS_COMMIT) {
     a_warn("unexpected reply from `%s'", p_name(kx->p));
     goto bad;
   }
-  if ((hc_in = buf_get(b, HASHSZ)) == 0 ||
-      (hc_out = buf_get(b, HASHSZ)) == 0 ||
-      (hrx = buf_get(b, HASHSZ)) == 0) {
+  if ((hc_in = buf_get(b, algs.hashsz)) == 0 ||
+      (hc_out = buf_get(b, algs.hashsz)) == 0 ||
+      (ck = buf_getmp(b)) == 0) {
     a_warn("invalid reply packet from `%s'", p_name(kx->p));
     goto bad;
   }
-  if ((kxc = matchreply(kx, hc_in, hc_out, hrx, b)) == 0)
+  if ((kxc = matchreply(kx, MSG_KEYEXCH | KX_REPLY,
+                       hc_in, hc_out, ck, b)) == 0)
     goto bad;
   if (BLEFT(b)) {
     a_warn("invalid reply packet from `%s'", p_name(kx->p));
@@ -729,6 +838,7 @@ static int doreply(keyexch *kx, buf *b)
   return (0);
 
 bad:
+  mp_drop(ck);
   return (-1);
 }
 
@@ -747,21 +857,22 @@ static int doswitch(keyexch *kx, buf *b)
   const octet *hc_in, *hc_out, *hswrq;
   kxchal *kxc;
 
-  if ((hc_in = buf_get(b, HASHSZ)) == 0 ||
-      (hc_out = buf_get(b, HASHSZ)) == 0) {
+  if ((hc_in = buf_get(b, algs.hashsz)) == 0 ||
+      (hc_out = buf_get(b, algs.hashsz)) == 0) {
     a_warn("invalid switch request from `%s'", p_name(kx->p));
     goto bad;
   }
-  if ((kxc = matchreply(kx, hc_in, hc_out, 0, b)) == 0)
+  if ((kxc = matchreply(kx, MSG_KEYEXCH | KX_SWITCH,
+                       hc_in, hc_out, 0, b)) == 0)
     goto bad;
-  if ((hswrq = buf_get(b, HASHSZ)) == 0 || BLEFT(b)) {
+  if ((hswrq = buf_get(b, algs.hashsz)) == 0 || BLEFT(b)) {
     a_warn("invalid switch request from `%s'", p_name(kx->p));
     goto bad;
   }
   IF_TRACING(T_KEYEXCH, {
-    trace_block(T_CRYPTO, "crypto: switch request hash", hswrq, HASHSZ);
+    trace_block(T_CRYPTO, "crypto: switch request hash", hswrq, algs.hashsz);
   })
-  if (memcmp(hswrq, kxc->hswrq_in, HASHSZ) != 0) {
+  if (memcmp(hswrq, kxc->hswrq_in, algs.hashsz) != 0) {
     a_warn("incorrect switch request hash from `%s'", p_name(kx->p));
     goto bad;
   }
@@ -803,19 +914,20 @@ static int doswitchok(keyexch *kx, buf *b)
   }
   kxc = kx->r[0];
   buf_init(&bb, buf_o, sizeof(buf_o));
-  if (ks_decrypt(kxc->ks, b, &bb)) {
+  if (ks_decrypt(kxc->ks, MSG_KEYEXCH | KX_SWITCHOK, b, &bb)) {
     a_warn("failed to decrypt switch confirmation from `%s'", p_name(kx->p));
     goto bad;
   }
   buf_init(b, BBASE(&bb), BLEN(&bb));
-  if ((hswok = buf_get(b, HASHSZ)) == 0 || BLEFT(b)) {
+  if ((hswok = buf_get(b, algs.hashsz)) == 0 || BLEFT(b)) {
     a_warn("invalid switch confirmation from `%s'", p_name(kx->p));
     goto bad;
   }
   IF_TRACING(T_KEYEXCH, {
-    trace_block(T_CRYPTO, "crypto: switch confirmation hash", hswok, HASHSZ);
+    trace_block(T_CRYPTO, "crypto: switch confirmation hash",
+               hswok, algs.hashsz);
   })
-  if (memcmp(hswok, kxc->hswok_in, HASHSZ) != 0) {
+  if (memcmp(hswok, kxc->hswok_in, algs.hashsz) != 0) {
     a_warn("incorrect switch confirmation hash from `%s'", p_name(kx->p));
     goto bad;
   }
@@ -858,8 +970,8 @@ static void stop(keyexch *kx)
   for (i = 0; i < kx->nr; i++)
     kxc_destroy(kx->r[i]);
   mp_drop(kx->alpha);
-  mp_drop(kx->c);
-  mp_drop(kx->rx);
+  G_DESTROY(gg, kx->c);
+  G_DESTROY(gg, kx->rx);
   kx->t_valid = 0;
   kx->f |= KXF_DEAD;
   kx->f &= ~KXF_TIMER;
@@ -878,30 +990,31 @@ static void stop(keyexch *kx)
 
 static void start(keyexch *kx, time_t now)
 {
-  HASH_CTX h;
+  ghash *h;
 
   assert(kx->f & KXF_DEAD);
 
   kx->f &= ~KXF_DEAD;
   kx->nr = 0;
-  kx->alpha = mprand_range(MP_NEW, kpriv.dp.q, &rand_global, 0);
-  kx->c = mpmont_exp(&mg, MP_NEW, kpriv.dp.g, kx->alpha);
-  kx->rx = mpmont_exp(&mg, MP_NEW, kx->kpub.y, kx->alpha);
+  kx->alpha = mprand_range(MP_NEW, gg->r, &rand_global, 0);
+  kx->c = G_CREATE(gg); G_EXP(gg, kx->c, gg->g, kx->alpha);
+  kx->rx = G_CREATE(gg); G_EXP(gg, kx->rx, kx->kpub, kx->alpha);
   kx->s = KXS_CHAL;
   kx->t_valid = now + T_VALID;
 
-  HASH_INIT(&h);
-  HASH_STRING(&h, "tripe-cookie");
-  hashmp(&h, kx->c);
-  HASH_DONE(&h, kx->hc);
+  h = GH_INIT(algs.h);
+  HASH_STRING(h, "tripe-cookie");
+  hashge(h, kx->c);
+  GH_DONE(h, kx->hc);
+  GH_DESTROY(h);
 
   IF_TRACING(T_KEYEXCH, {
     trace(T_KEYEXCH, "keyexch: creating new challenge");
     IF_TRACING(T_CRYPTO, {
       trace(T_CRYPTO, "crypto: secret = %s", mpstr(kx->alpha));
-      trace(T_CRYPTO, "crypto: challenge = %s", mpstr(kx->c));
-      trace(T_CRYPTO, "crypto: expected response = %s", mpstr(kx->rx));
-      trace_block(T_CRYPTO, "crypto: challenge cookie", kx->hc, HASHSZ);
+      trace(T_CRYPTO, "crypto: challenge = %s", gestr(gg, kx->c));
+      trace(T_CRYPTO, "crypto: expected response = %s", gestr(gg, kx->rx));
+      trace_block(T_CRYPTO, "crypto: challenge cookie", kx->hc, algs.hashsz);
     })
   })
 }
@@ -925,7 +1038,7 @@ static int checkpub(keyexch *kx)
   if (KEY_EXPIRED(now, kx->texp_kpub)) {
     stop(kx);
     a_warn("public key for `%s' has expired", p_name(kx->p));
-    dh_pubfree(&kx->kpub);
+    G_COPY(gg, kx->kpub, gg->i);
     kx->f &= ~KXF_PUBKEY;
     return (-1);
   }
@@ -1035,8 +1148,7 @@ void kx_message(keyexch *kx, unsigned msg, buf *b)
 void kx_free(keyexch *kx)
 {
   stop(kx);
-  if (kx->f & KXF_PUBKEY)
-    dh_pubfree(&kx->kpub);
+  G_DESTROY(gg, kx->kpub);
 }
 
 /* --- @kx_newkeys@ --- *
@@ -1053,13 +1165,8 @@ void kx_free(keyexch *kx)
 
 void kx_newkeys(keyexch *kx)
 {
-  dh_pub dp;
-
-  if (km_getpubkey(p_name(kx->p), &dp, &kx->texp_kpub))
+  if (km_getpubkey(p_name(kx->p), kx->kpub, &kx->texp_kpub))
     return;
-  if (kx->f & KXF_PUBKEY)
-    dh_pubfree(&kx->kpub);
-  kx->kpub = dp;
   kx->f |= KXF_PUBKEY;
   if ((kx->f & KXF_DEAD) || kx->s != KXS_SWITCH) {
     T( trace(T_KEYEXCH, "keyexch: restarting key negotiation with `%s'",
@@ -1087,8 +1194,11 @@ int kx_init(keyexch *kx, peer *p, keyset **ks)
 {
   kx->ks = ks;
   kx->p = p;
-  if (km_getpubkey(p_name(p), &kx->kpub, &kx->texp_kpub))
+  kx->kpub = G_CREATE(gg);
+  if (km_getpubkey(p_name(p), kx->kpub, &kx->texp_kpub)) {
+    G_DESTROY(gg, kx->kpub);
     return (-1);
+  }
   kx->f = KXF_DEAD | KXF_PUBKEY;
   start(kx, time(0));
   resend(kx);