chiark / gitweb /
tripe-admin manpage: Generate a command and message summary.
[tripe] / keyexch.c
index ea503583f976b50cfc536ba7fe6966487583b67f..ea4748e2956ddeaafbbcc0f4d85a83395ebe6d28 100644 (file)
--- a/keyexch.c
+++ b/keyexch.c
@@ -1,6 +1,6 @@
 /* -*-c-*-
  *
- * $Id: keyexch.c,v 1.11 2004/04/03 12:35:13 mdw Exp $
+ * $Id$
  *
  * Key exchange protocol
  *
  * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
  */
 
-/*----- Revision history --------------------------------------------------* 
- *
- * $Log: keyexch.c,v $
- * Revision 1.11  2004/04/03 12:35:13  mdw
- * Support elliptic curve key exchange.
- *
- * Revision 1.10  2003/10/15 09:29:38  mdw
- * Cosmetic fix to changelog comment.
- *
- * Revision 1.9  2003/07/13 11:53:14  mdw
- * Add protocol commentary.
- *
- * Revision 1.8  2003/07/13 11:19:49  mdw
- * Incompatible protocol fix!  Include message type code under MAC tag to
- * prevent cut-and-paste from key-exchange messages to general packet
- * transport.
- *
- * Revision 1.7  2003/05/17 11:01:28  mdw
- * Handle flags on challenge timers correctly to prevent confusing the event
- * list.
- *
- * Revision 1.6  2003/04/06 10:26:35  mdw
- * Report peer name on decrypt errors.
- *
- * Revision 1.5  2002/01/13 14:54:40  mdw
- * Patch up zero-knowledge property by passing an encrypted log with a
- * challenge, so that the prover can verify that the challenge is good.
- *
- * Revision 1.4  2001/06/22 19:40:36  mdw
- * Support expiry of other peers' public keys.
- *
- * Revision 1.3  2001/06/19 22:07:09  mdw
- * Cosmetic fixes.
- *
- * Revision 1.2  2001/02/16 21:24:27  mdw
- * Rewrite for new key exchange protocol.
- *
- * Revision 1.1  2001/02/03 20:26:37  mdw
- * Initial checkin.
- *
- */
-
 /*----- Header files ------------------------------------------------------*/
 
 #include "tripe.h"
  *     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))$%
+ * %$\cookie{kx-switch-rq}, c_A, c_B, E_K(r_B^\alpha, w_A))$%
  *     Reply received: here's my reply.  Committed; send data; move to
  *     @KXS_SWITCH@.
  *
 #define T_VALID MIN(2)                 /* Challenge validity period */
 #define T_RETRY SEC(10)                        /* Challenge retransmit interval */
 
-#define ISVALID(kx, now) ((now) < (kx)->t_valid)
+#define VALIDP(kx, now) ((now) < (kx)->t_valid)
+
+/*----- Static tables -----------------------------------------------------*/
+
+static const char *const pkname[] = {
+  "pre-challenge", "cookie", "challenge",
+  "reply", "switch-rq", "switch-ok"
+};
 
 /*----- Various utilities -------------------------------------------------*/
 
 /* --- @hashge@ --- *
  *
- * Arguments:  @HASH_CTX *r@ = pointer to hash context
+ * Arguments:  @ghash *h@ = pointer to hash context
  *             @ge *x@ = pointer to group element
  *
  * Returns:    ---
  *             @buf_t@.
  */
 
-static void hashge(HASH_CTX *r, ge *x)
+static void hashge(ghash *h, ge *x)
 {
   buf b;
   buf_init(&b, buf_t, sizeof(buf_t));
   G_TOBUF(gg, &b, x);
   assert(BOK(&b));
-  HASH(r, BBASE(&b), BLEN(&b));
+  GH_HASH(h, BBASE(&b), BLEN(&b));
 }
 
-/* --- @mpcrypt@ --- *
+/* --- @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
- *             @size_t ksz@ = size of the key
  *
  * Returns:    The encrypted/decrypted integer.
  *
@@ -167,13 +131,25 @@ static void hashge(HASH_CTX *r, ge *x)
  *             encryption thing.
  */
 
-static mp *mpcrypt(mp *d, mp *x, size_t sz, const octet *k, size_t ksz)
+static mp *mpencrypt(mp *d, mp *x, size_t sz, const octet *k)
 {
-  MGF_CTX m;
+  gcipher *mgf;
 
-  MGF_INIT(&m, k, ksz, 0);
+  mgf = GC_INIT(algs.mgf, k, algs.hashsz);
   mp_storeb(x, buf_t, sz);
-  MGF_CRYPT(&m, buf_t, 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));
 }
 
@@ -192,7 +168,7 @@ static void timer(struct timeval *tv, void *v)
   keyexch *kx = v;
   kx->f &= ~KXF_TIMER;
   T( trace(T_KEYEXCH, "keyexch: timer has popped"); )
-  kx_start(kx);
+  kx_start(kx, 0);
 }
 
 /* --- @settimer@ --- *
@@ -341,7 +317,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);
@@ -379,8 +355,8 @@ static void kxc_answer(keyexch *kx, kxchal *kxc)
   if (!kxc->r)
     G_TOBUF(gg, b, kx->c);
   else
-    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_putmp(b, kxc->ck);
 
   /* --- Maybe send an actual reply, if we have one --- */
@@ -432,28 +408,29 @@ static ge *getreply(keyexch *kx, ge *c, mp *ck)
   ge *r = G_CREATE(gg);
   ge *y = G_CREATE(gg);
   mp *a = MP_NEW;
-  HASH_CTX h;
-  octet buf[HASHSZ];  
+  ghash *h;
+  const octet *hh;
   int ok;
 
   G_EXP(gg, r, c, kpriv);
-  HASH_INIT(&h);
-  HASH_STRING(&h, "tripe-expected-reply");
-  hashge(&h, c);
-  hashge(&h, kx->c);
-  hashge(&h, r);
-  HASH_DONE(&h, buf);
-
-  a = mpcrypt(MP_NEW, ck, mp_octets(gg->r), buf, sizeof(buf));
+  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", gestr(gg, r));
-    trace_block(T_CRYPTO, "crypto: computed reply hash", buf, HASHSZ);
+    trace_block(T_CRYPTO, "crypto: computed reply hash", hh, algs.hashsz);
     trace(T_CRYPTO, "crypto: recovered log = %s", mpstr(a));
   }))
+  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));
+    a_warn("KX", "?PEER", kx->p, "bad-expected-reply-log", A_END);
     IF_TRACING(T_KEYEXCH, IF_TRACING(T_CRYPTO, {
       trace(T_CRYPTO, "crypto: computed challenge = %s", gestr(gg, y));
     }))
@@ -482,29 +459,28 @@ static int dochallenge(keyexch *kx, unsigned msg, buf *b)
   mp *ck = MP_NEW;
   const octet *hc = 0;
   kxchal *kxc;
-  HASH_CTX h;
-  octet buf[HASHSZ];
+  ghash *h;
 
   /* --- Ensure that we're in a sensible state --- */
 
   if (kx->s != KXS_CHAL) {
-    a_warn("unexpected challenge from `%s'", p_name(kx->p));
+    a_warn("KX", "?PEER", kx->p, "unexpected", "%s", pkname[msg], A_END);
     goto bad;
   }
 
   /* --- Unpack the packet --- */
 
   if (G_FROMBUF(gg, b, c) ||
-      (msg >= KX_COOKIE && (hc = buf_get(b, HASHSZ)) == 0) ||
+      (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));
+    a_warn("KX", "?PEER", kx->p, "invalid", "%s", pkname[msg], A_END);
     goto bad;
   }
 
   IF_TRACING(T_KEYEXCH, IF_TRACING(T_CRYPTO, {
     trace(T_CRYPTO, "crypto: challenge = %s", gestr(gg, c));
-    if (hc) trace_block(T_CRYPTO, "crypto: cookie", hc, HASHSZ);
+    if (hc) trace_block(T_CRYPTO, "crypto: cookie", hc, algs.hashsz);
     if (ck) trace(T_CRYPTO, "crypto: check value = %s", mpstr(ck));
   }))
 
@@ -515,20 +491,22 @@ 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"); )
+    a_warn("KX", "?PEER", p_name, "sending-cookie", A_END);
     b = p_txstart(kx->p, MSG_KEYEXCH | KX_COOKIE);
     G_TOBUF(gg, b, kx->c);
-    HASH_INIT(&h);
-    HASH_STRING(&h, "tripe-cookie");
-    hashge(&h, c);
-    HASH_DONE(&h, buf_get(b, HASHSZ));
+    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) {
-    a_warn("incorrect cookie from `%s'", p_name(kx->p));
+  if (hc && memcmp(hc, kx->hc, algs.hashsz) != 0) {
+    a_warn("KX", "?PEER", "incorrect", "cookie", A_END);
     goto bad;
   }
 
@@ -561,57 +539,64 @@ static int dochallenge(keyexch *kx, unsigned msg, buf *b)
 
     /* --- Work out the cookie for this challenge --- */
 
-    HASH_INIT(&h);
-    HASH_STRING(&h, "tripe-cookie");
-    hashge(&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");
-    hashge(&h, kx->c);
-    hashge(&h, kxc->c);
-    hashge(&h, kx->rx);
-    HASH_DONE(&h, buf);
-    kxc->ck = mpcrypt(MP_NEW, kx->alpha, mp_octets(gg->r),
-                     buf, sizeof(buf));
+    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 = 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");
-    hashge(&h, kx->c); hashge(&h, kxc->c);
-    HASH_DONE(&h, kxc->hswrq_out);
-    HASH_INIT(&h); HASH_STRING(&h, "tripe-switch-confirm");
-    hashge(&h, kx->c); hashge(&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");
-    hashge(&h, kxc->c); hashge(&h, kx->c);
-    HASH_DONE(&h, kxc->hswrq_in);
-    HASH_INIT(&h); HASH_STRING(&h, "tripe-switch-confirm");
-    hashge(&h, kxc->c); hashge(&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: expected-reply hash",
-                 buf, HASHSZ);
-      trace(T_CRYPTO, "crypto: my reply check = %s", mpstr(kxc->ck));
-      trace(T_CRYPTO, "crypto: shared secret = %s", gestr(gg, 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 --- */
@@ -678,11 +663,11 @@ static void resend(keyexch *kx)
               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));
       G_TOBUF(gg, &bb, kxc->r);
-      buf_put(&bb, kxc->hswrq_out, HASHSZ);
+      buf_put(&bb, kxc->hswrq_out, algs.hashsz);
       buf_flip(&bb);
       ks_encrypt(kxc->ks, MSG_KEYEXCH | KX_SWITCH, &bb, b);
       break;
@@ -692,7 +677,7 @@ 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, MSG_KEYEXCH | KX_SWITCHOK, &bb, b);
       break;
@@ -737,16 +722,16 @@ static kxchal *matchreply(keyexch *kx, unsigned ty, const octet *hc_in,
   /* --- 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);
+    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) {
-    a_warn("incorrect cookie from `%s'", p_name(kx->p));
+  if (memcmp(hc_out, kx->hc, algs.hashsz) != 0) {
+    a_warn("KX", "?PEER", kx->p, "incorrect", "cookie", A_END);
     goto bad;
   }
   if ((kxc = kxc_byhc(kx, hc_in)) == 0) {
-    a_warn("received reply for unknown challenge from `%s'", p_name(kx->p));
+    a_warn("KX", "?PEER", kx->p, "unknown-challenge", A_END);
     goto bad;
   }
 
@@ -754,7 +739,7 @@ static kxchal *matchreply(keyexch *kx, unsigned ty, const octet *hc_in,
 
   if (!kxc->r) {
     if (!ck) {
-      a_warn("unexpected switch request from `%s'", p_name(kx->p));
+      a_warn("KX", "?PEER", kx->p, "unexpected", "switch-rq", A_END);
       goto bad;
     }
     if ((r = getreply(kx, kxc->c, ck)) == 0)
@@ -767,20 +752,20 @@ static kxchal *matchreply(keyexch *kx, unsigned ty, const octet *hc_in,
 
   buf_init(&bb, buf_o, sizeof(buf_o));
   if (ks_decrypt(kxc->ks, ty, b, &bb)) {
-    a_warn("failed to decrypt reply from `%s'", p_name(kx->p));
+    a_warn("KX", "?PEER", kx->p, "decrypt-failed", "reply", A_END);
     goto bad;
   }
   buf_init(b, BBASE(&bb), BLEN(&bb));
   r = G_CREATE(gg);
   if (G_FROMBUF(gg, b, r)) {
-    a_warn("invalid reply packet from `%s'", p_name(kx->p));
+    a_warn("KX", "?PEER", kx->p, "invalid", "reply", A_END);
     goto bad;
   }
   IF_TRACING(T_KEYEXCH, IF_TRACING(T_CRYPTO, {
     trace(T_CRYPTO, "crypto: reply = %s", gestr(gg, r));
   }))
   if (!G_EQ(gg, r, kx->rx)) {
-    a_warn("incorrect reply from `%s'", p_name(kx->p));
+    a_warn("KX", "?PEER", kx->p, "incorrect", "reply", A_END);
     goto bad;
   }
 
@@ -837,20 +822,20 @@ static int doreply(keyexch *kx, buf *b)
   kxchal *kxc;
 
   if (kx->s != KXS_CHAL && kx->s != KXS_COMMIT) {
-    a_warn("unexpected reply from `%s'", p_name(kx->p));
+    a_warn("KX", "?PEER", kx->p, "unexpected", "reply", A_END);
     goto bad;
   }
-  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 ||
       (ck = buf_getmp(b)) == 0) {
-    a_warn("invalid reply packet from `%s'", p_name(kx->p));
+    a_warn("KX", "?PEER", kx->p, "invalid", "reply", A_END);
     goto bad;
   }
   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));
+    a_warn("KX", "?PEER", kx->p, "invalid", "reply", A_END);
     goto bad;
   }
   if (kx->s == KXS_CHAL) {
@@ -865,6 +850,25 @@ bad:
   return (-1);
 }
 
+/* --- @kxfinish@ --- *
+ *
+ * Arguments:  @keyexch *kx@ = pointer to key exchange block
+ *
+ * Returns:    ---
+ *
+ * Use:                Sets everything up following a successful key exchange.
+ */
+
+static void kxfinish(keyexch *kx)
+{
+  kxchal *kxc = kx->r[0];
+  ks_activate(kxc->ks);
+  settimer(kx, ks_tregen(kxc->ks));
+  kx->s = KXS_SWITCH;
+  a_notify("KXDONE", "?PEER", kx->p, A_END);
+  p_stats(kx->p)->t_kx = time(0);
+}
+
 /* --- @doswitch@ --- *
  *
  * Arguments:  @keyexch *kx@ = pointer to key exchange block
@@ -880,32 +884,30 @@ 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) {
-    a_warn("invalid switch request from `%s'", p_name(kx->p));
+  if ((hc_in = buf_get(b, algs.hashsz)) == 0 ||
+      (hc_out = buf_get(b, algs.hashsz)) == 0) {
+    a_warn("KX", "?PEER", kx->p, "invalid", "switch-rq", A_END);
     goto bad;
   }
   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)) {
-    a_warn("invalid switch request from `%s'", p_name(kx->p));
+  if ((hswrq = buf_get(b, algs.hashsz)) == 0 || BLEFT(b)) {
+    a_warn("KX", "?PEER", "invalid", "switch-rq", A_END);
     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) {
-    a_warn("incorrect switch request hash from `%s'", p_name(kx->p));
+  if (memcmp(hswrq, kxc->hswrq_in, algs.hashsz) != 0) {
+    a_warn("KX", "?PEER", kx->p, "incorrect", "switch-rq", A_END);
     goto bad;
   }
   switch (kx->s) {
     case KXS_CHAL:
       commit(kx, kxc);
     case KXS_COMMIT:
-      ks_activate(kxc->ks);
-      settimer(kx, ks_tregen(kxc->ks));
-      kx->s = KXS_SWITCH;
+      kxfinish(kx);
       break;
   }
   resend(kx);
@@ -932,32 +934,30 @@ static int doswitchok(keyexch *kx, buf *b)
   buf bb;
 
   if (kx->s < KXS_COMMIT) {
-    a_warn("unexpected switch confirmation from `%s'", p_name(kx->p));
+    a_warn("KX", "?PEER", kx->p, "unexpected", "switch-ok", A_END);
     goto bad;
   }
   kxc = kx->r[0];
   buf_init(&bb, buf_o, sizeof(buf_o));
   if (ks_decrypt(kxc->ks, MSG_KEYEXCH | KX_SWITCHOK, b, &bb)) {
-    a_warn("failed to decrypt switch confirmation from `%s'", p_name(kx->p));
+    a_warn("KX", "?PEER", kx->p, "decrypt-failed", "switch-ok", A_END);
     goto bad;
   }
   buf_init(b, BBASE(&bb), BLEN(&bb));
-  if ((hswok = buf_get(b, HASHSZ)) == 0 || BLEFT(b)) {
-    a_warn("invalid switch confirmation from `%s'", p_name(kx->p));
+  if ((hswok = buf_get(b, algs.hashsz)) == 0 || BLEFT(b)) {
+    a_warn("KX", "?PEER", "invalid", "switch-ok", A_END);
     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) {
-    a_warn("incorrect switch confirmation hash from `%s'", p_name(kx->p));
+  if (memcmp(hswok, kxc->hswok_in, algs.hashsz) != 0) {
+    a_warn("KX", "?PEER", kx->p, "incorrect", "switch-ok", A_END);
     goto bad;
   }
-  if (kx->s < KXS_SWITCH) {
-    ks_activate(kxc->ks);
-    settimer(kx, ks_tregen(kxc->ks));
-    kx->s = KXS_SWITCH;
-  }
+  if (kx->s < KXS_SWITCH)
+    kxfinish(kx);
   return (0);
 
 bad:
@@ -1012,7 +1012,7 @@ static void stop(keyexch *kx)
 
 static void start(keyexch *kx, time_t now)
 {
-  HASH_CTX h;
+  ghash *h;
 
   assert(kx->f & KXF_DEAD);
 
@@ -1024,10 +1024,11 @@ static void start(keyexch *kx, time_t now)
   kx->s = KXS_CHAL;
   kx->t_valid = now + T_VALID;
 
-  HASH_INIT(&h);
-  HASH_STRING(&h, "tripe-cookie");
-  hashge(&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");
@@ -1035,7 +1036,7 @@ static void start(keyexch *kx, time_t now)
       trace(T_CRYPTO, "crypto: secret = %s", mpstr(kx->alpha));
       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, HASHSZ);
+      trace_block(T_CRYPTO, "crypto: challenge cookie", kx->hc, algs.hashsz);
     })
   })
 }
@@ -1058,7 +1059,7 @@ static int checkpub(keyexch *kx)
   now = time(0);
   if (KEY_EXPIRED(now, kx->texp_kpub)) {
     stop(kx);
-    a_warn("public key for `%s' has expired", p_name(kx->p));
+    a_warn("KX", "?PEER", kx->p, "public-key-expired", A_END);
     G_COPY(gg, kx->kpub, gg->i);
     kx->f &= ~KXF_PUBKEY;
     return (-1);
@@ -1069,6 +1070,7 @@ static int checkpub(keyexch *kx)
 /* --- @kx_start@ --- *
  *
  * Arguments:  @keyexch *kx@ = pointer to key exchange context
+ *             @int forcep@ = nonzero to ignore the quiet timer
  *
  * Returns:    ---
  *
@@ -1077,15 +1079,16 @@ static int checkpub(keyexch *kx)
  *             this); if no exchange is in progress, one is commenced.
  */
 
-void kx_start(keyexch *kx)
+void kx_start(keyexch *kx, int forcep)
 {
   time_t now = time(0);
 
   if (checkpub(kx))
     return;
-  if (!ISVALID(kx, now)) {
+  if (forcep || !VALIDP(kx, now)) {
     stop(kx);
     start(kx, now);
+    a_notify("KXSTART", "?PEER", kx->p, A_END);
   }
   resend(kx);
 }
@@ -1109,17 +1112,10 @@ void kx_message(keyexch *kx, unsigned msg, buf *b)
   size_t sz = BSZ(b);
   int rc;
 
-#ifndef NTRACE
-  static const char *const pkname[] = {
-    "prechallenge", "cookie", "challenge",
-    "reply", "switch request", "switch confirmation"
-  };
-#endif
-
   if (checkpub(kx))
     return;
 
-  if (!ISVALID(kx, now)) {
+  if (!VALIDP(kx, now)) {
     stop(kx);
     start(kx, now);
   }
@@ -1143,8 +1139,7 @@ void kx_message(keyexch *kx, unsigned msg, buf *b)
       rc = doswitchok(kx, b);
       break;
     default:
-      a_warn("unexpected key exchange message type %u from `%p'",
-            p_name(kx->p));
+      a_warn("KX", "?PEER", kx->p, "unknown-message", "0x%02x", msg, A_END);
       rc = -1;
       break;
   }
@@ -1223,6 +1218,7 @@ int kx_init(keyexch *kx, peer *p, keyset **ks)
   kx->f = KXF_DEAD | KXF_PUBKEY;
   start(kx, time(0));
   resend(kx);
+  /* Don't notify here: the ADD message hasn't gone out yet. */
   return (0);
 }