server/admin.c: Remove spurious `ping' in usage message.
[tripe] / server / keyexch.c
index 9d08bec..b141100 100644 (file)
  *
  * %$\cookie{kx-switch-ok}, E_K(u_A))$%
  *     Switch received.  Committed; send data; move to @KXS_SWITCH@.
+ *
+ * %$\cookie{kx-token-request}, u, E_L(n)$%
+ *     %$L = H(u, u^\alpha)$%, and %$n$% is a string of the form
+ *     `[PEER.]KEYTAG'.  Expect %$\cookie{kx-token}$% by return.
+ *
+ * %$\cookie{kx-token}, v, E_{L'}(t)$%
+ *     %$L' = H(v, v^\alpha)$%, and %$t$% is a token associated with %$n$%
+ *     (see %$\cookie{kx-token-request}$% above).
+ *
+ * %$\cookie{kx-knock}, u, E_L(n, t), r_A$%
+ *     %$L$%, %$n$% and %$t$% are as %$\cookie{kx-token}$% and
+ *     %$\cookie{kx-token-request}$%; %$r_A$% is as in
+ *     %$\cookie{kx-pre-challenge}$%.  If the token %$t$% doesn't match
+ *     %$n$%, then warn and discard.  If a peer named PEER (or KEYTAG)
+ *     exists then proceed as for %$\cookie{kx-pre-challenge}$%.  Otherwise
+ *     issue a notification `NOTE KNOCK PEER ADDR...' and discard.
  */
 
 /*----- Static tables -----------------------------------------------------*/
 
 static const char *const pkname[] = {
-  "pre-challenge", "challenge", "reply", "switch-rq", "switch-ok"
+  "pre-challenge", "challenge", "reply", "switch-rq", "switch-ok",
+  "token-rq", "token", "knock"
 };
 
 /*----- Various utilities -------------------------------------------------*/
@@ -373,6 +390,66 @@ static void rs_time(retry *rs, struct timeval *tv, const struct timeval *now)
 
 static void rs_reset(retry *rs) { rs->t = 0; }
 
+/* --- @notice_message@ --- *
+ *
+ * Arguments:  @keyexch *kx@ = pointer to key-exchange block
+ *
+ * Returns:    Zero if OK; @-1@ if the public key is in a bad state.
+ *
+ * Use:                Updates the key-exchange state following a received message.
+ *             Specifically, if there's no currently active key-exchange in
+ *             progress, and we're not in the cooling-off period, then
+ *             commence a new one; reset the retry timers; and if we're
+ *             corked then pop the cork so that we can reply.
+ */
+
+static int checkpub(keyexch *kx);
+static void stop(keyexch *kx);
+static void start(keyexch *kx, time_t now);
+
+static int notice_message(keyexch *kx)
+{
+  struct timeval now, tv;
+
+  gettimeofday(&now, 0);
+  rs_reset(&kx->rs);
+  if (kx->f & KXF_CORK) {
+    start(kx, now.tv_sec);
+    rs_time(&kx->rs, &tv, &now);
+    settimer(kx, &tv);
+    a_notify("KXSTART", "?PEER", kx->p, A_END);
+  }
+  if (checkpub(kx)) return (-1);
+  if (!VALIDP(kx, now.tv_sec)) {
+    stop(kx);
+    start(kx, now.tv_sec);
+  }
+  return (0);
+}
+
+/* --- @update_stats_tx@, @update_stats_rx@ --- *
+ *
+ * Arguments:  @keyexch *kx@ = pointer to key-exchange block
+ *             @int ok@ = nonzero if the message was valid (for @rx@)
+ *             @size_t sz@ = size of sent message
+ *
+ * Returns:    ---
+ *
+ * Use:                Records that a key-exchange message was sent to, or received
+ *             from, the peer.
+ */
+
+static void update_stats_tx(keyexch *kx, size_t sz)
+  { stats *st = p_stats(kx->p); st->n_kxout++; st->sz_kxout += sz; }
+
+static void update_stats_rx(keyexch *kx, int ok, size_t sz)
+{
+  stats *st = p_stats(kx->p);
+
+  if (!ok) st->n_reject++;
+  else { st->n_kxin++; st->sz_kxin += sz; }
+}
+
 /*----- Challenge management ----------------------------------------------*/
 
 /* --- Notes on challenge management --- *
@@ -527,7 +604,6 @@ 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 | KX_REPLY);
   const dhgrp *g = kx->kpriv->grp;
   struct timeval tv;
@@ -545,8 +621,7 @@ static void kxc_answer(keyexch *kx, kxchal *kxc)
   /* --- Update the statistics --- */
 
   if (BOK(b)) {
-    st->n_kxout++;
-    st->sz_kxout += BLEN(b);
+    update_stats_tx(kx, BLEN(b));
     p_txend(kx->p);
   }
 
@@ -562,6 +637,148 @@ static void kxc_answer(keyexch *kx, kxchal *kxc)
 
 /*----- Individual message handlers ---------------------------------------*/
 
+static ratelim unauth_limit;
+
+/* --- @dotokenrq@ --- *
+ *
+ * Arguments:  @const addr *a@ = sender's address
+ *             @buf *b@ = buffer containing the packet
+ *
+ * Returns:    ---
+ *
+ * Use:                Processes a token-request message.
+ */
+
+static void dotokenrq(const addr *a, buf *b)
+{
+  uint32 id;
+  kdata *kpriv = 0, *kpub = 0;
+  char *pname;
+  const char *tag;
+  size_t sz;
+  buf bb, bbb;
+
+  /* --- Check if we're in danger of overloading --- */
+
+  if (ratelim_withdraw(&unauth_limit, 1)) goto done;
+
+  /* --- Start building the reply --- */
+
+  buf_init(&bbb, buf_o, sizeof(buf_o));
+  buf_putu8(&bbb, MSG_KEYEXCH | KX_TOKEN);
+
+  /* --- Fetch and copy the challenge string --- */
+
+  if (buf_getbuf16(b, &bb)) goto done;
+  buf_putmem16(&bbb, BBASE(&bb), BSZ(&bb));
+
+  /* --- Make our own challenge for the response --- */
+
+  buf_init(&bb, buf_t, sizeof(buf_t));
+  c_new(0, 0, &bb); assert(BOK(&bb)); buf_putbuf16(&bbb, &bb);
+
+  /* --- Figure out which private key I'm supposed to use --- */
+
+  if (buf_getu32(b, &id)) goto done;
+  if ((kpriv = km_findprivbyid(id)) == 0) goto done;
+
+  /* --- Decrypt the message --- */
+
+  buf_init(&bb, buf_t, sizeof(buf_t));
+  if (ies_decrypt(kpriv, MSG_KEYEXCH | KX_TOKENRQ, b, &bb) || BLEFT(b))
+    goto done;
+
+  /* --- Parse the token request and find the sender's public key --- */
+
+  assert(BOK(&bb)); buf_flip(&bb);
+  if ((pname = buf_getmem16(&bb, &sz)) == 0 || memchr(pname, 0, sz))
+    goto done;
+  assert(sz < sizeof(buf_t) - ((const octet *)pname - buf_t));
+  pname[sz] = 0;
+  if ((tag = strchr(pname, '.')) != 0) tag++;
+  else tag = pname;
+  if ((kpub = km_findpub(tag)) == 0) goto done;
+
+  /* --- Build and encrypt the token --- */
+
+  buf_init(&bb, buf_i, sizeof(buf_i));
+  c_new(pname, sz, &bb);
+  assert(BOK(&bb)); buf_flip(&bb);
+  if (ies_encrypt(kpub, MSG_KEYEXCH | KX_TOKEN, &bb, &bbb)) goto done;
+  assert(BOK(&bbb));
+
+  /* --- Send the response -- or at least give it a try --- */
+
+  p_txaddr(a, BBASE(&bbb), BLEN(&bbb));
+
+  /* --- All done --- */
+
+done:
+  if (kpriv) km_unref(kpriv);
+  if (kpub) km_unref(kpub);
+}
+
+/* --- @dotoken@ --- *
+ *
+ * Arguments:  @keyexch *kx@ = pointer to key exchange block
+ *             @buf *b@ = buffer containing the packet
+ *
+ * Returns:    Zero if OK, nonzero of the packet was rejected.
+ *
+ * Use:                Processes a token message.
+ */
+
+static int dotoken(keyexch *kx, buf *b)
+{
+  buf bb;
+  buf *bbb;
+  const dhgrp *g = kx->kpriv->grp;
+  octet *p;
+  size_t sz;
+
+  /* --- Make sure this is a sensible message to have received --- */
+
+  if (!kx->p->spec.knock) return (-1);
+
+  /* --- First, collect and verify our challenge --- */
+
+  if (buf_getbuf16(b, &bb) || c_check(0, 0, &bb) || BLEFT(&bb)) return (-1);
+
+  /* --- Start building the knock message from here --- */
+
+  bbb = p_txstart(kx->p, MSG_KEYEXCH | KX_KNOCK);
+
+  /* --- Copy the peer's challenge --- */
+
+  if (buf_getbuf16(b, &bb)) return (-1);
+  buf_putmem16(bbb, BBASE(&bb), BSZ(&bb));
+
+  /* --- Add the key indicator --- */
+
+  buf_putu32(bbb, kx->kpub->id);
+
+  /* --- Building the knock payload --- */
+
+  buf_init(&bb, buf_t, sizeof(buf_t));
+  buf_putstr16(&bb, kx->p->spec.knock);
+  sz = BLEN(&bb)%64; if (sz) sz = 64 - sz;
+  if (ies_decrypt(kx->kpriv, MSG_KEYEXCH | KX_TOKEN, b, &bb)) return (-1);
+  p = buf_get(&bb, sz); assert(p); memset(p, 0, sz);
+  assert(BOK(&bb)); buf_flip(&bb);
+  if (ies_encrypt(kx->kpub, MSG_KEYEXCH | KX_KNOCK, &bb, bbb)) return (-1);
+
+  /* --- Finally, the pre-challenge group element --- */
+
+  g->ops->stge(g, bbb, kx->C, DHFMT_VAR);
+
+  /* --- And we're done --- */
+
+  if (BBAD(bbb)) return (-1);
+  update_stats_tx(kx, BLEN(bbb));
+  p_txend(kx->p);
+  return (0);
+}
+
 /* --- @doprechallenge@ --- *
  *
  * Arguments:  @keyexch *kx@ = pointer to key exchange block
@@ -574,7 +791,6 @@ static void kxc_answer(keyexch *kx, kxchal *kxc)
 
 static int doprechallenge(keyexch *kx, buf *b)
 {
-  stats *st = p_stats(kx->p);
   const dhgrp *g = kx->kpriv->grp;
   dhge *C = 0;
   ghash *h;
@@ -603,8 +819,7 @@ static int doprechallenge(keyexch *kx, buf *b)
   hashge(h, g, C);
   sendchallenge(kx, b, C, GH_DONE(h, 0));
   GH_DESTROY(h);
-  st->n_kxout++;
-  st->sz_kxout += BLEN(b);
+  update_stats_tx(kx, BLEN(b));
   p_txend(kx->p);
 
   /* --- Done --- */
@@ -617,6 +832,73 @@ bad:
   return (-1);
 }
 
+/* --- @doknock@ --- *
+ *
+ * Arguments:  @const addr *a@ = sender's address
+ *             @buf *b@ = buffer containing the packet
+ *
+ * Returns:    ---
+ *
+ * Use:                Processes a knock message.
+ */
+
+static void doknock(const addr *a, buf *b)
+{
+  keyexch *kx;
+  peer *p;
+  uint32 id;
+  kdata *kpriv = 0;
+  char *pname;
+  size_t sz, msgsz = BLEN(b);
+  buf bb;
+  int rc;
+
+  /* --- Read and check the challenge --- */
+
+  buf_getbuf16(b, &bb);
+  if (c_check(0, 0, &bb)) goto done;
+
+  /* --- Figure out which private key I'm supposed to use --- */
+
+  if (buf_getu32(b, &id)) goto done;
+  if ((kpriv = km_findprivbyid(id)) == 0) goto done;
+
+  /* --- Decrypt and check the peer's name against the token --- */
+
+  buf_init(&bb, buf_t, sizeof(buf_t));
+  if (ies_decrypt(kpriv, MSG_KEYEXCH | KX_KNOCK, b, &bb)) goto done;
+  assert(BOK(&bb)); buf_flip(&bb);
+  if ((pname = buf_getmem16(&bb, &sz)) == 0 ||
+      memchr(pname, 0, sz) ||
+      c_check(pname, sz, &bb))
+    goto done;
+  assert(sz < sizeof(buf_t) - ((const octet *)pname - buf_t));
+  pname[sz] = 0;
+
+  /* --- If we can't find the peer, then issue a notification --- */
+
+  if ((p = p_find(pname)) == 0) {
+    a_notify("KNOCK", "%s", pname, "?ADDR", a, A_END);
+    goto done;
+  }
+
+  /* --- Update the peer's address --- */
+
+  kx = &p->kx;
+  p_updateaddr(kx->p, a);
+
+  /* --- Now treat the remainder of the message as a pre-challenge --- */
+
+  notice_message(kx);
+  rc = doprechallenge(kx, b);
+  update_stats_rx(kx, !rc, msgsz);
+
+  /* --- All done: clean up --- */
+
+done:
+  if (kpriv) km_unref(kpriv);
+}
+
 /* --- @respond@ --- *
  *
  * Arguments:  @keyexch *kx@ = pointer to key exchange block
@@ -637,8 +919,8 @@ static kxchal *respond(keyexch *kx, unsigned msg, buf *b)
   dhge *C = 0;
   dhge *R = 0;
   dhge *CC = 0;
+  deriveargs a;
   const octet *hc, *ck;
-  size_t x, y, z;
   dhsc *c = 0;
   kxchal *kxc;
   ghash *h = 0;
@@ -752,13 +1034,13 @@ static kxchal *respond(keyexch *kx, unsigned msg, buf *b)
 
     /* --- Create a new symmetric keyset --- */
 
-    buf_init(&bb, buf_o, sizeof(buf_o));
-    g->ops->stge(g, &bb, kx->C, DHFMT_HASH); x = BLEN(&bb);
-    g->ops->stge(g, &bb, kxc->C, DHFMT_HASH); y = BLEN(&bb);
-    g->ops->stge(g, &bb, R, DHFMT_HASH); z = BLEN(&bb);
+    buf_init(&bb, buf_o, sizeof(buf_o)); a.k = BBASE(&bb);
+    g->ops->stge(g, &bb, kx->C, DHFMT_HASH); a.x = BLEN(&bb);
+    g->ops->stge(g, &bb, kxc->C, DHFMT_HASH); a.y = BLEN(&bb);
+    g->ops->stge(g, &bb, R, DHFMT_HASH); a.z = BLEN(&bb);
     assert(BOK(&bb));
 
-    kxc->ks = ks_gen(BBASE(&bb), x, y, z, kx->p);
+    kxc->ks = ks_gen(&a, kx->p);
   }
 
   if (C) g->ops->freege(g, C);
@@ -823,17 +1105,37 @@ static void resend(keyexch *kx)
 {
   kxchal *kxc;
   buf bb;
-  stats *st = p_stats(kx->p);
   struct timeval tv;
   const dhgrp *g = kx->kpriv->grp;
+  octet *p;
+  size_t sz;
   buf *b;
 
   switch (kx->s) {
     case KXS_CHAL:
-      T( trace(T_KEYEXCH, "keyexch: sending prechallenge to `%s'",
-              p_name(kx->p)); )
-      b = p_txstart(kx->p, MSG_KEYEXCH | KX_PRECHAL);
-      g->ops->stge(g, b, kx->C, DHFMT_VAR);
+      if (!kx->p->spec.knock) {
+       T( trace(T_KEYEXCH, "keyexch: sending prechallenge to `%s'",
+                p_name(kx->p)); )
+       b = p_txstart(kx->p, MSG_KEYEXCH | KX_PRECHAL);
+       g->ops->stge(g, b, kx->C, DHFMT_VAR);
+      } else {
+       T( trace(T_KEYEXCH, "keyexch: sending token-request to `%s'",
+                p_name(kx->p)); )
+       b = p_txstart(kx->p, MSG_KEYEXCH | KX_TOKENRQ);
+
+       buf_init(&bb, buf_t, sizeof(buf_t));
+       c_new(0, 0, &bb); assert(BOK(&bb)); buf_putbuf16(b, &bb);
+
+       buf_putu32(b, kx->kpub->id);
+
+       buf_init(&bb, buf_t, sizeof(buf_t));
+       buf_putstr16(&bb, kx->p->spec.knock);
+       sz = BLEN(&bb)%64; if (sz) sz = 64 - sz;
+       p = buf_get(&bb, sz); assert(p); memset(p, 0, sz);
+       assert(BOK(&bb)); buf_flip(&bb);
+       if (ies_encrypt(kx->kpub, MSG_KEYEXCH | KX_TOKENRQ, &bb, b))
+         buf_break(b);
+      }
       break;
     case KXS_COMMIT:
       T( trace(T_KEYEXCH, "keyexch: sending switch request to `%s'",
@@ -863,8 +1165,7 @@ static void resend(keyexch *kx)
   }
 
   if (BOK(b)) {
-    st->n_kxout++;
-    st->sz_kxout += BLEN(b);
+    update_stats_tx(kx, BLEN(b));
     p_txend(kx->p);
   }
 
@@ -1266,69 +1567,48 @@ void kx_start(keyexch *kx, int forcep)
 /* --- @kx_message@ --- *
  *
  * Arguments:  @keyexch *kx@ = pointer to key exchange context
+ *             @const addr *a@ = sender's IP address and port
  *             @unsigned msg@ = the message code
  *             @buf *b@ = pointer to buffer containing the packet
  *
- * Returns:    ---
+ * Returns:    Nonzero if the sender's address was unknown.
  *
  * Use:                Reads a packet containing key exchange messages and handles
  *             it.
  */
 
-void kx_message(keyexch *kx, unsigned msg, buf *b)
+int kx_message(keyexch *kx, const addr *a, unsigned msg, buf *b)
 {
-  struct timeval now, tv;
-  stats *st = p_stats(kx->p);
   size_t sz = BSZ(b);
   int rc;
 
-  gettimeofday(&now, 0);
-  rs_reset(&kx->rs);
-  if (kx->f & KXF_CORK) {
-    start(kx, now.tv_sec);
-    rs_time(&kx->rs, &tv, &now);
-    settimer(kx, &tv);
-    a_notify("KXSTART", "?PEER", kx->p, A_END);
-  }
+  T( trace(T_KEYEXCH, "keyexch: processing %s packet from %c%s%c",
+          msg < KX_NMSG ? pkname[msg] : "unknown",
+          kx ? '`' : '<', kx ? p_name(kx->p) : "nil", kx ? '\'' : '>'); )
 
-  if (checkpub(kx))
-    return;
-
-  if (!VALIDP(kx, now.tv_sec)) {
-    stop(kx);
-    start(kx, now.tv_sec);
+  switch (msg) {
+    case KX_TOKENRQ: dotokenrq(a, b); return (0);
+    case KX_KNOCK: doknock(a, b); return (0);
   }
-  T( trace(T_KEYEXCH, "keyexch: processing %s packet from `%s'",
-          msg < KX_NMSG ? pkname[msg] : "unknown", p_name(kx->p)); )
+
+  if (!kx) return (-1);
+  if (notice_message(kx)) return (0);
 
   switch (msg) {
-    case KX_PRECHAL:
-      rc = doprechallenge(kx, b);
-      break;
-    case KX_CHAL:
-      rc = dochallenge(kx, b);
-      break;
-    case KX_REPLY:
-      rc = doreply(kx, b);
-      break;
-    case KX_SWITCH:
-      rc = doswitch(kx, b);
-      break;
-    case KX_SWITCHOK:
-      rc = doswitchok(kx, b);
-      break;
+    case KX_TOKEN: rc = dotoken(kx, b); break;
+    case KX_PRECHAL: rc = doprechallenge(kx, b); break;
+    case KX_CHAL: rc = dochallenge(kx, b); break;
+    case KX_REPLY: rc = doreply(kx, b); break;
+    case KX_SWITCH: rc = doswitch(kx, b); break;
+    case KX_SWITCHOK: rc = doswitchok(kx, b); break;
     default:
       a_warn("KX", "?PEER", kx->p, "unknown-message", "0x%02x", msg, A_END);
       rc = -1;
       break;
   }
 
-  if (rc)
-    st->n_reject++;
-  else {
-    st->n_kxin++;
-    st->sz_kxin += sz;
-  }
+  update_stats_rx(kx, !rc, sz);
+  return (0);
 }
 
 /* --- @kx_free@ --- *
@@ -1472,7 +1752,7 @@ newkeys:
   }
 }
 
-/* --- @kx_init@ --- *
+/* --- @kx_setup@ --- *
  *
  * Arguments:  @keyexch *kx@ = pointer to key exchange context
  *             @peer *p@ = pointer to peer context
@@ -1486,7 +1766,7 @@ newkeys:
  *             exchange.
  */
 
-int kx_init(keyexch *kx, peer *p, keyset **ks, unsigned f)
+int kx_setup(keyexch *kx, peer *p, keyset **ks, unsigned f)
 {
   if ((kx->kpriv = km_findpriv(p_privtag(p))) == 0) goto fail_0;
   if ((kx->kpub = km_findpub(p_tag(p))) == 0) goto fail_1;
@@ -1517,4 +1797,16 @@ fail_0:
   return (-1);
 }
 
+/* --- @kx_init@ --- *
+ *
+ * Arguments:  ---
+ *
+ * Returns:    ---
+ *
+ * Use:                Initializes the key-exchange logic.
+ */
+
+void kx_init(void)
+  { ratelim_init(&unauth_limit, 20, 500); }
+
 /*----- That's all, folks -------------------------------------------------*/