chiark / gitweb /
Incopatible protocol fix! Include message type code under MAC tag to prevent
[tripe] / keyexch.c
index 1eb6981aa4e3a524819399dd15cc7b3e0d11188d..eb4fc9c8f538ea2eb37daf64f96b456604674b3c 100644 (file)
--- a/keyexch.c
+++ b/keyexch.c
@@ -1,6 +1,6 @@
 /* -*-c-*-
  *
- * $Id: keyexch.c,v 1.2 2001/02/16 21:24:27 mdw Exp $
+ * $Id: keyexch.c,v 1.8 2003/07/13 11:19:49 mdw Exp $
  *
  * Key exchange protocol
  *
 /*----- Revision history --------------------------------------------------* 
  *
  * $Log: keyexch.c,v $
+ * Revision 1.8  2003/07/13 11:19:49  mdw
+ * Incopatible 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.
  *
@@ -43,8 +64,8 @@
 
 /*----- 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)
 
@@ -70,6 +91,31 @@ static void hashmp(HASH_CTX *r, mp *m)
   HASH(r, BBASE(&b), BLEN(&b));
 }
 
+/* --- @mpcrypt@ --- *
+ *
+ * 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.
+ *
+ * Use:                Encrypts (or decrypts) a multiprecision integer using another
+ *             multiprecision integer as the key.  This is a slightly grotty
+ *             way to do this, but it's easier than the alternatives.
+ */
+
+static mp *mpcrypt(mp *d, mp *x, size_t sz, const octet *k, size_t ksz)
+{
+  MGF_CTX m;
+
+  MGF_INIT(&m, k, ksz, 0);
+  mp_storeb(x, buf_t, sz);
+  MGF_CRYPT(&m, buf_t, buf_t, sz);
+  return (mp_loadb(d, buf_t, sz));
+}
+
 /* --- @timer@ --- *
  *
  * Arguments:  @struct timeval *tv@ = the current time
@@ -139,6 +185,7 @@ static void kxc_destroy(kxchal *kxc)
     sel_rmtimer(&kxc->t);
   mp_drop(kxc->c);
   mp_drop(kxc->r);
+  mp_drop(kxc->ck);
   ks_drop(kxc->ks);
   DESTROY(kxc);
 }
@@ -158,12 +205,12 @@ static void kxc_stoptimer(kxchal *kxc)
 {
   if (kxc->f & KXF_TIMER)
     sel_rmtimer(&kxc->t);
+  kxc->f &= ~KXF_TIMER;
 }
 
 /* --- @kxc_new@ --- *
  *
  * Arguments:  @keyexch *kx@ = pointer to key exchange block
- *             @
  *
  * Returns:    A pointer to the challenge block.
  *
@@ -189,6 +236,7 @@ static kxchal *kxc_new(keyexch *kx)
   kxc = CREATE(kxchal);
   kxc->c = 0;
   kxc->r = 0;
+  kxc->ck = 0;
   kxc->ks = 0;
   kxc->kx = kx;
   kxc->f = 0;
@@ -272,7 +320,7 @@ static void kxc_answer(keyexch *kx, kxchal *kxc)
   else
     buf_put(b, kx->hc, HASHSZ);
   buf_put(b, kxc->hc, HASHSZ);
-  buf_put(b, kxc->hrx, HASHSZ);
+  buf_putmp(b, kxc->ck);
 
   /* --- Maybe send an actual reply, if we have one --- */
 
@@ -284,7 +332,7 @@ static void kxc_answer(keyexch *kx, kxchal *kxc)
     buf_init(&bb, buf_i, sizeof(buf_i));
     buf_putmp(&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 --- */
@@ -311,18 +359,20 @@ static void kxc_answer(keyexch *kx, kxchal *kxc)
  *
  * Arguments:  @keyexch *kx@ = pointer to key exchange context
  *             @mp *c@ = a challenge
- *             @const octet *hrx@ = the supplied expected-reply hash
+ *             @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 mp *getreply(keyexch *kx, mp *c, mp *ck)
 {
   mp *r = mpmont_exp(&mg, MP_NEW, c, kpriv.x);
+  mp *a;
   HASH_CTX h;
-  octet buf[HASHSZ];
+  octet buf[HASHSZ];  
+  int ok;
 
   HASH_INIT(&h);
   HASH_STRING(&h, "tripe-expected-reply");
@@ -330,16 +380,24 @@ static mp *getreply(keyexch *kx, mp *c, const octet *hrx)
   hashmp(&h, kx->c);
   hashmp(&h, r);
   HASH_DONE(&h, buf);
+
+  a = mpcrypt(MP_NEW, ck, mp_octets(kpriv.dp.q), buf, sizeof(buf));
   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: recovered log = %s", mpstr(a));
   }))
-  if (memcmp(buf, hrx, HASHSZ) != 0) {
-    a_warn("invalid expected-reply hash from `%s'", p_name(kx->p));
+  a = mpmont_exp(&mg, a, kpriv.dp.g, a);
+  ok = mp_eq(a, 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", mpstr(a));
+    }))
     mp_drop(r);
-    return (0);
   }
-  return (r);
+  mp_drop(a);
+  return (ok ? r : 0);
 }
 
 /* --- @dochallenge@ --- *
@@ -355,10 +413,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;
+  mp *c = 0, *ck = 0;
+  const octet *hc = 0;
   kxchal *kxc;
   HASH_CTX h;
+  octet buf[HASHSZ];
 
   /* --- Ensure that we're in a sensible state --- */
 
@@ -371,7 +430,7 @@ static int dochallenge(keyexch *kx, unsigned msg, buf *b)
 
   if ((c = buf_getmp(b)) == 0 ||
       (msg >= KX_COOKIE && (hc = buf_get(b, HASHSZ)) == 0) ||
-      (msg >= KX_CHAL && (hrx = buf_get(b, HASHSZ)) == 0) ||
+      (msg >= KX_CHAL && (ck = buf_getmp(b)) == 0) ||
       BLEFT(b)) {
     a_warn("malformed packet from `%s'", p_name(kx->p));
     goto bad;
@@ -380,7 +439,7 @@ static int dochallenge(keyexch *kx, unsigned msg, buf *b)
   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);
+    if (ck) trace(T_CRYPTO, "crypto: check value = %s", mpstr(ck));
   }))
 
   /* --- First, handle a bare challenge --- *
@@ -423,10 +482,10 @@ 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;
@@ -447,11 +506,16 @@ static int dochallenge(keyexch *kx, unsigned msg, buf *b)
     hashmp(&h, kx->c);
     hashmp(&h, kxc->c);
     hashmp(&h, kx->rx);
-    HASH_DONE(&h, kxc->hrx);
+    HASH_DONE(&h, buf);
+    kxc->ck = mpcrypt(MP_NEW, kx->alpha, mp_octets(kpriv.dp.q),
+                     buf, sizeof(buf));
 
     /* --- Work out the shared key --- */
 
+    trace(T_CRYPTO, "debug: c = %s", mpstr(c));
+    trace(T_CRYPTO, "debug: alpha = %s", mpstr(kx->alpha));
     r = mpmont_exp(&mg, MP_NEW, c, kx->alpha);
+    trace(T_CRYPTO, "debug: r = %s", mpstr(r));
 
     /* --- Compute the switch messages --- */
 
@@ -471,7 +535,9 @@ static int dochallenge(keyexch *kx, unsigned msg, buf *b)
 
     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_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", mpstr(r));
       trace_block(T_CRYPTO, "crypto: outbound switch request",
                  kxc->hswrq_out, HASHSZ);
@@ -491,15 +557,15 @@ static int dochallenge(keyexch *kx, unsigned msg, buf *b)
     buf_putmp(b, r); z = BLEN(b);
     assert(BOK(b));
 
-    kxc->ks = ks_gen(BBASE(b), x, y, z);
+    kxc->ks = ks_gen(BBASE(b), x, y, z, kx->p);
     mp_drop(r);
   }
 
   /* --- Answer the challenge if we need to --- */
 
-  if (hrx && !kxc->r) {
+  if (ck && !kxc->r) {
     mp *r;
-    if ((r = getreply(kx, c, hrx)) == 0)
+    if ((r = getreply(kx, c, ck)) == 0)
       goto bad;
     kxc->r = r;
   }
@@ -510,10 +576,12 @@ static int dochallenge(keyexch *kx, unsigned msg, buf *b)
 
 tidy:
   mp_drop(c);
+  mp_drop(ck);
   return (0);
 
 bad:
   mp_drop(c);
+  mp_drop(ck);
   return (-1);
 }
 
@@ -535,12 +603,14 @@ static void resend(keyexch *kx)
 
   switch (kx->s) {
     case KXS_CHAL:
-      T( trace(T_KEYEXCH, "sending prechallenge to `%s'", p_name(kx->p)); )
+      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);
       break;
     case KXS_COMMIT:
-      T( trace(T_KEYEXCH, "sending switch request to `%s'", p_name(kx->p)); )
+      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);
@@ -549,17 +619,17 @@ static void resend(keyexch *kx)
       buf_putmp(&bb, kxc->r);
       buf_put(&bb, kxc->hswrq_out, 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, "sending switch confirmation to `%s'",
+      T( trace(T_KEYEXCH, "keyexch: sending switch confirmation to `%s'",
               p_name(kx->p)); )
       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_flip(&bb);
-      ks_encrypt(kxc->ks, &bb, b);
+      ks_encrypt(kxc->ks, MSG_KEYEXCH | KX_SWITCHOK, &bb, b);
       break;
     default:
       abort();
@@ -578,9 +648,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.
@@ -591,8 +662,8 @@ 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;
@@ -603,7 +674,7 @@ static kxchal *matchreply(keyexch *kx, const octet *hc_in,
   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);
+    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));
@@ -617,11 +688,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;
@@ -630,7 +701,7 @@ 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;
   }
@@ -695,7 +766,8 @@ 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) {
@@ -704,11 +776,12 @@ static int doreply(keyexch *kx, buf *b)
   }
   if ((hc_in = buf_get(b, HASHSZ)) == 0 ||
       (hc_out = buf_get(b, HASHSZ)) == 0 ||
-      (hrx = buf_get(b, 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));
@@ -722,6 +795,7 @@ static int doreply(keyexch *kx, buf *b)
   return (0);
 
 bad:
+  mp_drop(ck);
   return (-1);
 }
 
@@ -745,7 +819,8 @@ static int doswitch(keyexch *kx, buf *b)
     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)) {
     a_warn("invalid switch request from `%s'", p_name(kx->p));
@@ -796,7 +871,7 @@ 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;
   }
@@ -843,6 +918,9 @@ static void stop(keyexch *kx)
 {
   unsigned i;
 
+  if (kx->f & KXF_DEAD)
+    return;
+
   if (kx->f & KXF_TIMER)
     sel_rmtimer(&kx->t);
   for (i = 0; i < kx->nr; i++)
@@ -850,6 +928,9 @@ static void stop(keyexch *kx)
   mp_drop(kx->alpha);
   mp_drop(kx->c);
   mp_drop(kx->rx);
+  kx->t_valid = 0;
+  kx->f |= KXF_DEAD;
+  kx->f &= ~KXF_TIMER;
 }
 
 /* --- @start@ --- *
@@ -867,8 +948,10 @@ static void start(keyexch *kx, time_t now)
 {
   HASH_CTX h;
 
+  assert(kx->f & KXF_DEAD);
+
+  kx->f &= ~KXF_DEAD;
   kx->nr = 0;
-  kx->f = 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);
@@ -891,6 +974,32 @@ static void start(keyexch *kx, time_t now)
   })
 }
 
+/* --- @checkpub@ --- *
+ *
+ * Arguments:  @keyexch *kx@ = pointer to key exchange context
+ *
+ * Returns:    Zero if OK, nonzero if the peer's public key has expired.
+ *
+ * Use:                Deactivates the key-exchange until the peer acquires a new
+ *             public key.
+ */
+
+static int checkpub(keyexch *kx)
+{
+  time_t now;
+  if (kx->f & KXF_DEAD)
+    return (-1);
+  now = time(0);
+  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);
+    kx->f &= ~KXF_PUBKEY;
+    return (-1);
+  }
+  return (0);
+}
+
 /* --- @kx_start@ --- *
  *
  * Arguments:  @keyexch *kx@ = pointer to key exchange context
@@ -906,6 +1015,8 @@ void kx_start(keyexch *kx)
 {
   time_t now = time(0);
 
+  if (checkpub(kx))
+    return;
   if (!ISVALID(kx, now)) {
     stop(kx);
     start(kx, now);
@@ -939,6 +1050,9 @@ void kx_message(keyexch *kx, unsigned msg, buf *b)
   };
 #endif
 
+  if (checkpub(kx))
+    return;
+
   if (!ISVALID(kx, now)) {
     stop(kx);
     start(kx, now);
@@ -989,7 +1103,8 @@ void kx_message(keyexch *kx, unsigned msg, buf *b)
 void kx_free(keyexch *kx)
 {
   stop(kx);
-  dh_pubfree(&kx->kpub);
+  if (kx->f & KXF_PUBKEY)
+    dh_pubfree(&kx->kpub);
 }
 
 /* --- @kx_newkeys@ --- *
@@ -1008,15 +1123,18 @@ void kx_newkeys(keyexch *kx)
 {
   dh_pub dp;
 
-  if (km_getpubkey(p_name(kx->p), &dp))
+  if (km_getpubkey(p_name(kx->p), &dp, &kx->texp_kpub))
     return;
-  dh_pubfree(&kx->kpub);
+  if (kx->f & KXF_PUBKEY)
+    dh_pubfree(&kx->kpub);
   kx->kpub = dp;
-  if (kx->s != KXS_SWITCH) {
+  kx->f |= KXF_PUBKEY;
+  if ((kx->f & KXF_DEAD) || kx->s != KXS_SWITCH) {
     T( trace(T_KEYEXCH, "keyexch: restarting key negotiation with `%s'",
             p_name(kx->p)); )
-    kx->t_valid = 0;
-    kx_start(kx);
+    stop(kx);
+    start(kx, time(0));
+    resend(kx);
   }
 }
 
@@ -1037,8 +1155,9 @@ int kx_init(keyexch *kx, peer *p, keyset **ks)
 {
   kx->ks = ks;
   kx->p = p;
-  if (km_getpubkey(p_name(p), &kx->kpub))
+  if (km_getpubkey(p_name(p), &kx->kpub, &kx->texp_kpub))
     return (-1);
+  kx->f = KXF_DEAD | KXF_PUBKEY;
   start(kx, time(0));
   resend(kx);
   return (0);