chiark / gitweb /
keyexch: Simplify key-exchange protocol.
[tripe] / keyexch.c
index f6786e0928b54dd7862f4686d8f9b034655f3645..3ec122c0d24854ff896e6b51206742a0b87fe246 100644 (file)
--- a/keyexch.c
+++ b/keyexch.c
  * %$\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))$%
+ * %$\cookie{kx-reply}, R_A, c_B, v_A, E_K(r_B^\alpha))$%
  *     Challenge accpeted: here's the answer.  Commit to my challenge.  Move
  *     to @KXS_COMMIT@.
  *
@@ -116,41 +113,134 @@ static void hashge(ghash *h, ge *x)
   GH_HASH(h, BBASE(&b), BLEN(&b));
 }
 
-/* --- @mpencrypt@, @mpdecrypt@ --- *
+/* --- @mpmask@ --- *
  *
- * Arguments:  @mp *d@ = the destination integer
- *             @mp *x@ = the plaintext/ciphertext integer
- *             @size_t sz@ = the expected size of the plaintext
+ * Arguments:  @buf *b@ = output buffer
+ *             @mp *x@ = the plaintext integer
+ *             @size_t n@ = 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.
+ * Returns:    Pointer to the output.
  *
- * 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.
+ * Use:                Masks a multiprecision integer: returns %$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)
+static octet *mpmask(buf *b, mp *x, size_t n, const octet *k, size_t ksz)
 {
   gcipher *mgf;
+  octet *p;
 
-  mgf = GC_INIT(algs.mgf, k, algs.hashsz);
-  mp_storeb(x, buf_t, sz);
-  GC_ENCRYPT(mgf, buf_t, buf_t, sz);
+  if ((p = buf_get(b, n)) == 0)
+    return (0);
+  mgf = GC_INIT(algs.mgf, k, ksz);
+  IF_TRACING(T_KEYEXCH, IF_TRACING(T_CRYPTO, {
+    trace(T_CRYPTO, "masking index = %s", mpstr(x));
+    trace_block(T_CRYPTO, "masking key", k, ksz);
+  }))
+  mp_storeb(x, buf_t, n);
+  GC_ENCRYPT(mgf, buf_t, p, n);
+  IF_TRACING(T_KEYEXCH, IF_TRACING(T_CRYPTO, {
+    trace_block(T_CRYPTO, "index plaintext", buf_t, n);
+    trace_block(T_CRYPTO, "masked ciphertext", p, n);
+  }))
   GC_DESTROY(mgf);
-  return (mp_loadb(d, buf_t, sz));
+  return (p);
 }
 
-static mp *mpdecrypt(mp *d, mp *x, size_t sz, const octet *k)
+/* --- @mpunmask@ --- *
+ *
+ * Arguments:  @mp *d@ = the output integer
+ *             @const octet *p@ = pointer to the ciphertext
+ *             @size_t n@ = the size of the ciphertext
+ *             @const octet *k@ = pointer to key material
+ *             @size_t ksz@ = size of the key
+ *
+ * Returns:    The decrypted integer, or null.
+ *
+ * Use:                Unmasks a multiprecision integer.
+ */
+
+static mp *mpunmask(mp *d, const octet *p, size_t n,
+                   const octet *k, size_t ksz)
 {
   gcipher *mgf;
 
-  mgf = GC_INIT(algs.mgf, k, algs.hashsz);
-  mp_storeb(x, buf_t, sz);
-  GC_DECRYPT(mgf, buf_t, buf_t, sz);
+  mgf = GC_INIT(algs.mgf, k, ksz);
+  IF_TRACING(T_KEYEXCH, IF_TRACING(T_CRYPTO, {
+    trace_block(T_CRYPTO, "unmasking key", k, ksz);
+    trace_block(T_CRYPTO, "masked ciphertext", p, n);
+  }))
+  GC_DECRYPT(mgf, p, buf_t, n);
+  d = mp_loadb(d, buf_t, n);
+  IF_TRACING(T_KEYEXCH, IF_TRACING(T_CRYPTO, {
+    trace_block(T_CRYPTO, "index plaintext", buf_t, n);
+    trace(T_CRYPTO, "unmasked index = %s", mpstr(d));
+  }))
   GC_DESTROY(mgf);
-  return (mp_loadb(d, buf_t, sz));
+  return (d);
+}
+
+/* --- @hashcheck@ --- *
+ *
+ * Arguments:  @ge *kpub@ = sender's public key
+ *             @ge *cc@ = receiver's challenge
+ *             @ge *c@ = sender's challenge
+ *             @ge *y@ = reply to sender's challenge
+ *
+ * Returns:    Pointer to the hash value (in @buf_t@)
+ *
+ * Use:                Computes the check-value hash, used to mask or unmask
+ *             indices to prove the validity of challenges.  This computes
+ *             the masking key used in challenge check values.  This is
+ *             really the heart of the whole thing, since it ensures that
+ *             the index can be recovered from the history of hashing
+ *             queries, which gives us (a) a proof that the authentication
+ *             process is zero-knowledge, and (b) a proof that the whole
+ *             key-exchange is deniable.
+ */
+
+static const octet *hashcheck(ge *kpub, ge *cc, ge *c, ge *y)
+{
+  ghash *h = GH_INIT(algs.h);
+
+  HASH_STRING(h, "tripe-expected-reply");
+  hashge(h, kpub);
+  hashge(h, cc);
+  hashge(h, c);
+  hashge(h, y);
+  GH_DONE(h, buf_t);
+  IF_TRACING(T_KEYEXCH, IF_TRACING(T_CRYPTO, {
+    trace(T_CRYPTO, "computing challenge check hash");
+    trace(T_CRYPTO, "public key = %s", gestr(gg, kpub));
+    trace(T_CRYPTO, "receiver challenge = %s", gestr(gg, cc));
+    trace(T_CRYPTO, "sender challenge = %s", gestr(gg, c));
+    trace(T_CRYPTO, "sender reply = %s", gestr(gg, y));
+    trace_block(T_CRYPTO, "hash output", buf_t, algs.hashsz);
+  }))
+  GH_DESTROY(h);
+  return (buf_t);
+}
+
+/* --- @sendchallenge@ --- *
+ *
+ * Arguments:  @keyexch *kx@ = pointer to key exchange block
+ *             @buf *b@ = output buffer for challenge
+ *             @ge *c@ = peer's actual challenge
+ *             @const octet *hc@ = peer's challenge cookie
+ *
+ * Returns:    ---
+ *
+ * Use:                Writes a full challenge to the message buffer.
+ */
+
+static void sendchallenge(keyexch *kx, buf *b, ge *c, const octet *hc)
+{
+  G_TOBUF(gg, b, kx->c);
+  buf_put(b, hc, algs.hashsz);
+  mpmask(b, kx->alpha, indexsz,
+        hashcheck(kpub, c, kx->c, kx->rx), algs.hashsz);
 }
 
 /* --- @timer@ --- *
@@ -221,8 +311,7 @@ static void kxc_destroy(kxchal *kxc)
   if (kxc->f & KXF_TIMER)
     sel_rmtimer(&kxc->t);
   G_DESTROY(gg, kxc->c);
-  if (kxc->r) G_DESTROY(gg, kxc->r);
-  mp_drop(kxc->ck);
+  G_DESTROY(gg, kxc->r);
   ks_drop(kxc->ks);
   DESTROY(kxc);
 }
@@ -272,8 +361,7 @@ static kxchal *kxc_new(keyexch *kx)
 
   kxc = CREATE(kxchal);
   kxc->c = G_CREATE(gg);
-  kxc->r = 0;
-  kxc->ck = MP_NEW;
+  kxc->r = G_CREATE(gg);
   kxc->ks = 0;
   kxc->kx = kx;
   kxc->f = 0;
@@ -346,31 +434,18 @@ static void kxc_timer(struct timeval *tv, void *v)
 static void kxc_answer(keyexch *kx, kxchal *kxc)
 {
   stats *st = p_stats(kx->p);
-  buf *b = p_txstart(kx->p, MSG_KEYEXCH | (kxc->r ? KX_REPLY : KX_CHAL));
+  buf *b = p_txstart(kx->p, MSG_KEYEXCH | KX_REPLY);
   struct timeval tv;
   buf bb;
 
   /* --- Build the reply packet --- */
 
-  if (!kxc->r)
-    G_TOBUF(gg, b, kx->c);
-  else
-    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 --- */
-
-  if (!kxc->r) {
-    T( trace(T_KEYEXCH, "keyexch: resending challenge to `%s'",
-            p_name(kx->p)); )
-  } else {
-    T( trace(T_KEYEXCH, "keyexch: sending reply to `%s'", p_name(kx->p)); )
-    buf_init(&bb, buf_i, sizeof(buf_i));
-    G_TORAW(gg, &bb, kxc->r);
-    buf_flip(&bb);
-    ks_encrypt(kxc->ks, MSG_KEYEXCH | KX_REPLY, &bb, b);
-  }
+  T( trace(T_KEYEXCH, "keyexch: sending reply to `%s'", p_name(kx->p)); )
+  sendchallenge(kx, b, kxc->c, kxc->hc);
+  buf_init(&bb, buf_i, sizeof(buf_i));
+  G_TORAW(gg, &bb, kxc->r);
+  buf_flip(&bb);
+  ks_encrypt(kxc->ks, MSG_KEYEXCH | KX_REPLY, &bb, b);
 
   /* --- Update the statistics --- */
 
@@ -392,122 +467,99 @@ static void kxc_answer(keyexch *kx, kxchal *kxc)
 
 /*----- Individual message handlers ---------------------------------------*/
 
-/* --- @getreply@ --- *
+/* --- @doprechallenge@ --- *
  *
- * Arguments:  @keyexch *kx@ = pointer to key exchange context
- *             @ge *c@ = a challenge
- *             @mp *ck@ = the supplied expected-reply check value
+ * Arguments:  @keyexch *kx@ = pointer to key exchange block
+ *             @buf *b@ = buffer containing the packet
  *
- * Returns:    A pointer to the reply, or null if the reply-hash was wrong.
+ * Returns:    Zero if OK, nonzero of the packet was rejected.
  *
- * Use:                Computes replies to challenges.
+ * Use:                Processes a pre-challenge message.
  */
 
-static ge *getreply(keyexch *kx, ge *c, mp *ck)
+static int doprechallenge(keyexch *kx, buf *b)
 {
-  ge *r = G_CREATE(gg);
-  ge *y = G_CREATE(gg);
-  mp *a = MP_NEW;
+  stats *st = p_stats(kx->p);
+  ge *c = G_CREATE(gg);
   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, kx->kpub);
-  hashge(h, c);
-  hashge(h, kx->c);
-  hashge(h, r);
-  hh = GH_DONE(h, 0);
+  /* --- Ensure that we're in a sensible state --- */
+
+  if (kx->s != KXS_CHAL) {
+    a_warn("KX", "?PEER", kx->p, "unexpected", "pre-challenge", A_END);
+    goto bad;
+  }
+
+  /* --- Unpack the packet --- */
+
+  if (G_FROMBUF(gg, b, c) || BLEFT(b))
+    goto bad;
 
-  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", hh, algs.hashsz);
-    trace(T_CRYPTO, "crypto: recovered log = %s", mpstr(a));
+    trace(T_CRYPTO, "crypto: challenge = %s", gestr(gg, c));
   }))
+
+  /* --- Send out a full challenge by return --- */
+
+  b = p_txstart(kx->p, MSG_KEYEXCH | KX_CHAL);
+  h = GH_INIT(algs.h);
+  HASH_STRING(h, "tripe-cookie");
+  hashge(h, c);
+  sendchallenge(kx, b, c, GH_DONE(h, 0));
   GH_DESTROY(h);
-  if (MP_CMP(a, >=, gg->r))
-    ok = 0;
-  else{
-    G_EXP(gg, y, gg->g, a);
-    ok = G_EQ(gg, y, c);
-  }
-  if (!ok) {
-    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));
-    }))
-    G_DESTROY(gg, r);
-    r = 0;
-  }
-  mp_drop(a);
-  G_DESTROY(gg, y);
-  return (r);
+  st->n_kxout++;
+  st->sz_kxout += BLEN(b);
+  p_txend(kx->p);
+
+  /* --- Done --- */
+
+  G_DESTROY(gg, c);
+  return (0);
+
+bad:
+  if (c) G_DESTROY(gg, c);
+  return (-1);
 }
 
-/* --- @dochallenge@ --- *
+/* --- @respond@ --- *
  *
  * Arguments:  @keyexch *kx@ = pointer to key exchange block
- *             @unsigned msg@ = message code for the packet
+ *             @unsigned msg@ = message code for this packet
  *             @buf *b@ = buffer containing the packet
  *
- * Returns:    Zero if OK, nonzero if the packet was rejected.
+ * Returns:    Key-exchange challenge block, or null.
  *
- * Use:                Processes a packet containing a challenge.
+ * Use:                Computes a response for the given challenge, entering it into
+ *             a challenge block and so on.
  */
 
-static int dochallenge(keyexch *kx, unsigned msg, buf *b)
+static kxchal *respond(keyexch *kx, unsigned msg, buf *b)
 {
   ge *c = G_CREATE(gg);
-  mp *ck = MP_NEW;
-  const octet *hc = 0;
+  ge *r = G_CREATE(gg);
+  ge *cc = G_CREATE(gg);
+  const octet *hc, *ck;
+  size_t x, y, z;
+  mp *cv = 0;
   kxchal *kxc;
-  ghash *h;
-
-  /* --- Ensure that we're in a sensible state --- */
-
-  if (kx->s != KXS_CHAL) {
-    a_warn("KX", "?PEER", kx->p, "unexpected", "%s", pkname[msg], A_END);
-    goto bad;
-  }
+  ghash *h = 0;
+  buf bb;
+  int ok;
 
   /* --- Unpack the packet --- */
 
   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)) {
+      (hc = buf_get(b, algs.hashsz)) == 0 ||
+      (ck = buf_get(b, indexsz)) == 0) {
     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, algs.hashsz);
-    if (ck) trace(T_CRYPTO, "crypto: check value = %s", mpstr(ck));
+    trace_block(T_CRYPTO, "crypto: cookie", hc, algs.hashsz);
+    trace_block(T_CRYPTO, "crypto: check-value", ck, indexsz);
   }))
 
-  /* --- First, handle a bare challenge --- *
-   *
-   * If the table is heavily loaded, just emit a cookie and return.
-   */
-
-  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);
-    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, algs.hashsz) != 0) {
@@ -515,34 +567,47 @@ static int dochallenge(keyexch *kx, unsigned msg, buf *b)
     goto bad;
   }
 
-  /* --- Find a challenge block for this packet --- *
+  /* --- Recover the check value and verify it --- *
+   *
+   * To avoid recomputation on replays, we store a hash of the `right'
+   * value.  The `correct' value is unique, so this is right.
    *
-   * If there isn't one already, create a new one.
+   * This will also find a challenge block and, if necessary, populate it.
    */
 
-  if ((kxc = kxc_bychal(kx, c)) == 0) {
-    size_t x, y, z;
-    ge *r;
-
-    /* --- Be careful here --- *
-     *
-     * If this is a full challenge, and it's the first time I've seen it, I
-     * want to be able to throw it away before committing a table entry to
-     * it.
-     */
-
-    if (!ck)
-      kxc = kxc_new(kx);       
-    else {
-      if ((r = getreply(kx, c, ck)) == 0)
-       goto bad;
-      kxc = kxc_new(kx);
-      kxc->r = r;
-    }
-    kxc->c = G_CREATE(gg);
+  if ((kxc = kxc_bychal(kx, c)) != 0) {
+    h = GH_INIT(algs.h);
+    HASH_STRING(h, "tripe-check-hash");
+    GH_HASH(h, ck, indexsz);
+    ok = !memcmp(kxc->ck, GH_DONE(h, 0), algs.hashsz);
+    GH_DESTROY(h);
+    if (!ok) goto badcheck;
+  } else {
+
+    /* --- Compute the reply, and check the magic --- */
+
+    G_EXP(gg, r, c, kpriv);
+    cv = mpunmask(MP_NEW, ck, indexsz,
+                 hashcheck(kx->kpub, kx->c, c, r), algs.hashsz);
+    IF_TRACING(T_KEYEXCH, IF_TRACING(T_CRYPTO, {
+      trace(T_CRYPTO, "crypto: computed reply = %s", gestr(gg, r));
+      trace(T_CRYPTO, "crypto: recovered log = %s", mpstr(cv));
+    }))
+    if (MP_CMP(cv, >, gg->r) ||
+       (G_EXP(gg, cc, gg->g, cv), !G_EQ(gg, c, cc)))
+      goto badcheck;
+
+    /* --- Fill in a new challenge block --- */
+    
+    kxc = kxc_new(kx);
     G_COPY(gg, kxc->c, c);
+    G_COPY(gg, kxc->r, r);
 
-    /* --- Work out the cookie for this challenge --- */
+    h = GH_INIT(algs.h);
+    HASH_STRING(h, "tripe-check-hash");
+    GH_HASH(h, ck, indexsz);
+    GH_DONE(h, kxc->hc);
+    GH_DESTROY(h);
 
     h = GH_INIT(algs.h);
     HASH_STRING(h, "tripe-cookie");
@@ -554,25 +619,8 @@ static int dochallenge(keyexch *kx, unsigned msg, buf *b)
       trace_block(T_CRYPTO, "crypto: computed cookie", kxc->hc, algs.hashsz);
     }))
 
-    /* --- Compute the expected-reply hash --- */
-
-    h = GH_INIT(algs.h);
-    HASH_STRING(h, "tripe-expected-reply");
-    hashge(h, kpub);
-    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));
@@ -607,37 +655,61 @@ static int dochallenge(keyexch *kx, unsigned msg, buf *b)
 
     /* --- Create a new symmetric keyset --- */
 
-    buf_init(b, buf_o, sizeof(buf_o));
-    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));
+    buf_init(&bb, buf_o, sizeof(buf_o));
+    G_TOBUF(gg, &bb, kx->c); x = BLEN(&bb);
+    G_TOBUF(gg, &bb, kxc->c); y = BLEN(&bb);
+    G_TOBUF(gg, &bb, r); z = BLEN(&bb);
+    assert(BOK(&bb));
 
-    kxc->ks = ks_gen(BBASE(b), x, y, z, kx->p);
-    G_DESTROY(gg, r);
+    kxc->ks = ks_gen(BBASE(&bb), x, y, z, kx->p);
   }
 
-  /* --- Answer the challenge if we need to --- */
+  G_DESTROY(gg, c);
+  G_DESTROY(gg, cc);
+  G_DESTROY(gg, r);
+  mp_drop(cv);
+  return (kxc);
 
-  if (ck && !kxc->r) {
-    ge *r;
-    if ((r = getreply(kx, c, ck)) == 0)
-      goto bad;
-    kxc->r = r;
-  }
+badcheck:
+  a_warn("KX", "?PEER", kx->p, "bad-expected-reply-log", A_END);
+  goto bad;
+bad:
+  G_DESTROY(gg, c);
+  G_DESTROY(gg, cc);
+  G_DESTROY(gg, r);
+  mp_drop(cv);
+  return (0);  
+}
 
-  kxc_answer(kx, kxc);
+/* --- @dochallenge@ --- *
+ *
+ * Arguments:  @keyexch *kx@ = pointer to key exchange block
+ *             @unsigned msg@ = message code for the packet
+ *             @buf *b@ = buffer containing the packet
+ *
+ * Returns:    Zero if OK, nonzero if the packet was rejected.
+ *
+ * Use:                Processes a packet containing a challenge.
+ */
 
-  /* --- Tidy up and go home --- */
+static int dochallenge(keyexch *kx, buf *b)
+{
+  kxchal *kxc;
 
-tidy:
-  G_DESTROY(gg, c);
-  mp_drop(ck);
+  if (kx->s != KXS_CHAL) {
+    a_warn("KX", "?PEER", kx->p, "unexpected", "challenge", A_END);
+    goto bad;
+  }
+  if ((kxc = respond(kx, KX_CHAL, b)) == 0)
+    goto bad;
+  if (BLEFT(b)) {
+    a_warn("KX", "?PEER", kx->p, "invalid", "challenge", A_END);
+    goto bad;
+  }
+  kxc_answer(kx, kxc);
   return (0);
 
 bad:
-  G_DESTROY(gg, c);
-  mp_drop(ck);
   return (-1);
 }
 
@@ -672,7 +744,7 @@ static void resend(keyexch *kx)
       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);
+      G_TORAW(gg, &bb, kxc->r);
       buf_put(&bb, kxc->hswrq_out, algs.hashsz);
       buf_flip(&bb);
       ks_encrypt(kxc->ks, MSG_KEYEXCH | KX_SWITCH, &bb, b);
@@ -701,88 +773,66 @@ static void resend(keyexch *kx)
     settimer(kx, time(0) + T_RETRY);
 }
 
-/* --- @matchreply@ --- *
+/* --- @decryptrest@ --- *
  *
  * 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)
- *             @mp *ck@ = his expected-reply hash (optional)
+ *             @kxchal *kxc@ = pointer to challenge block
+ *             @unsigned msg@ = type of incoming message
  *             @buf *b@ = encrypted remainder of the packet
  *
- * Returns:    A pointer to the challenge block if OK, or null on failure.
+ * Returns:    Zero if OK, nonzero on some kind of error.
  *
- * Use:                Checks a reply or switch packet, ensuring that its contents
- *             are sensible and correct.  If they are, @*b@ is set to point
- *             to the remainder of the encrypted data, and the correct
- *             challenge is returned.
+ * Use:                Decrypts the remainder of the packet, and points @b@ at the
+ *             recovered plaintext.
  */
 
-static kxchal *matchreply(keyexch *kx, unsigned ty, const octet *hc_in,
-                         const octet *hc_out, mp *ck, buf *b)
+static int decryptrest(keyexch *kx, kxchal *kxc, unsigned msg, buf *b)
 {
-  kxchal *kxc;
   buf bb;
-  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, 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, 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("KX", "?PEER", kx->p, "unknown-challenge", A_END);
-    goto bad;
+  buf_init(&bb, buf_o, sizeof(buf_o));
+  if (ks_decrypt(kxc->ks, MSG_KEYEXCH | msg, b, &bb)) {
+    a_warn("KX", "?PEER", kx->p, "decrypt-failed", "%s", pkname[msg], A_END);
+    return (-1);
   }
+  buf_init(b, BBASE(&bb), BLEN(&bb));
+  return (0);
+}
 
-  /* --- Maybe compute a reply for the challenge --- */
-
-  if (!kxc->r) {
-    if (!ck) {
-      a_warn("KX", "?PEER", kx->p, "unexpected", "switch-rq", A_END);
-      goto bad;
-    }
-    if ((r = getreply(kx, kxc->c, ck)) == 0)
-      goto bad;
-    kxc->r = r;
-    r = 0;
-  }
+/* --- @checkresponse@ --- *
+ *
+ * Arguments:  @keyexch *kx@ = pointer to key exchange context
+ *             @unsigned msg@ = type of incoming message
+ *             @buf *b@ = decrypted remainder of the packet
+ *
+ * Returns:    Zero if OK, nonzero on some kind of error.
+ *
+ * Use:                Checks a reply or switch packet, ensuring that its response
+ *             is correct.
+ */
 
-  /* --- Decrypt the rest of the packet --- */
+static int checkresponse(keyexch *kx, unsigned msg, buf *b)
+{
+  ge *r = G_CREATE(gg);
 
-  buf_init(&bb, buf_o, sizeof(buf_o));
-  if (ks_decrypt(kxc->ks, ty, b, &bb)) {
-    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_FROMRAW(gg, b, r)) {
-    a_warn("KX", "?PEER", kx->p, "invalid", "reply", A_END);
+    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: reply = %s", gestr(gg, r));
   }))
   if (!G_EQ(gg, r, kx->rx)) {
-    a_warn("KX", "?PEER", kx->p, "incorrect", "reply", A_END);
+    a_warn("KX", "?PEER", kx->p, "incorrect", "response", A_END);
     goto bad;
   }
 
-  /* --- Done --- */
-
   G_DESTROY(gg, r);
-  return (kxc);
+  return (0);
 
 bad:
-  if (r) G_DESTROY(gg, r);
-  return (0);
+  G_DESTROY(gg, r);
+  return (-1);
 }
 
 /* --- @commit@ --- *
@@ -823,27 +873,20 @@ static void commit(keyexch *kx, kxchal *kxc)
 
 static int doreply(keyexch *kx, buf *b)
 {
-  const octet *hc_in, *hc_out;
-  mp *ck = 0;
   kxchal *kxc;
 
   if (kx->s != KXS_CHAL && kx->s != KXS_COMMIT) {
     a_warn("KX", "?PEER", kx->p, "unexpected", "reply", A_END);
     goto bad;
   }
-  if ((hc_in = buf_get(b, algs.hashsz)) == 0 ||
-      (hc_out = buf_get(b, algs.hashsz)) == 0 ||
-      (ck = buf_getmp(b)) == 0) {
-    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)
+  if ((kxc = respond(kx, KX_REPLY, b)) == 0 ||
+      decryptrest(kx, kxc, KX_REPLY, b) ||
+      checkresponse(kx, KX_REPLY, b))
     goto bad;
   if (BLEFT(b)) {
     a_warn("KX", "?PEER", kx->p, "invalid", "reply", A_END);
     goto bad;
-  }
+  }    
   if (kx->s == KXS_CHAL) {
     commit(kx, kxc);
     kx->s = KXS_COMMIT;
@@ -852,7 +895,6 @@ static int doreply(keyexch *kx, buf *b)
   return (0);
 
 bad:
-  mp_drop(ck);
   return (-1);
 }
 
@@ -895,8 +937,17 @@ static int doswitch(keyexch *kx, buf *b)
     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)
+  IF_TRACING(T_KEYEXCH, IF_TRACING(T_CRYPTO, {
+    trace_block(T_CRYPTO, "crypto: challenge", hc_in, algs.hashsz);
+    trace_block(T_CRYPTO, "crypto: cookie", hc_out, algs.hashsz);
+  }))
+  if ((kxc = kxc_byhc(kx, hc_in)) == 0 ||
+      memcmp(hc_out, kx->hc, algs.hashsz) != 0) {
+    a_warn("KX", "?PEER", kx->p, "incorrect", "switch-rq", A_END);
+    goto bad;
+  }
+  if (decryptrest(kx, kxc, KX_SWITCH, b) ||
+      checkresponse(kx, KX_SWITCH, b))
     goto bad;
   if ((hswrq = buf_get(b, algs.hashsz)) == 0 || BLEFT(b)) {
     a_warn("KX", "?PEER", "invalid", "switch-rq", A_END);
@@ -909,13 +960,10 @@ static int doswitch(keyexch *kx, buf *b)
     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:
-      kxfinish(kx);
-      break;
-  }
+  if (kx->s == KXS_CHAL)
+    commit(kx, kxc);
+  if (kx->s < KXS_SWITCH)
+    kxfinish(kx);
   resend(kx);
   return (0);
 
@@ -945,11 +993,8 @@ static int doswitchok(keyexch *kx, buf *b)
   }
   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("KX", "?PEER", kx->p, "decrypt-failed", "switch-ok", A_END);
+  if (decryptrest(kx, kxc, KX_SWITCHOK, b))
     goto bad;
-  }
-  buf_init(b, BBASE(&bb), BLEN(&bb));
   if ((hswok = buf_get(b, algs.hashsz)) == 0 || BLEFT(b)) {
     a_warn("KX", "?PEER", "invalid", "switch-ok", A_END);
     goto bad;
@@ -1131,9 +1176,10 @@ void kx_message(keyexch *kx, unsigned msg, buf *b)
 
   switch (msg) {
     case KX_PRECHAL:
-    case KX_COOKIE:
+      rc = doprechallenge(kx, b);
+      break;
     case KX_CHAL:
-      rc = dochallenge(kx, msg, b);
+      rc = dochallenge(kx, b);
       break;
     case KX_REPLY:
       rc = doreply(kx, b);