chiark / gitweb /
Add new `knock' protocol.
authorMark Wooding <mdw@distorted.org.uk>
Thu, 24 Aug 2017 20:31:43 +0000 (21:31 +0100)
committerMark Wooding <mdw@distorted.org.uk>
Sat, 16 Jun 2018 18:13:41 +0000 (19:13 +0100)
  * Add a new option to the `ADD' command giving the knock string to
    send.

  * Add new protocol messages `token-rq', `token', and `knock', which
    together allow a possibly unknown peer to collect a single-use
    token (`token-rq' and `token') which it can use to identify itself,
    and use this to introduce itself to its (presumably) static
    peer (`knock').  This last message can either provoke a `KNOCK'
    notification to a service, or initiate key-exchange using a
    piggybacked `pre-challenge', possibly after having updated the
    peer's address.

  * Update the `connect' service to collect the necessary knock strings
    from the peer database, and to respond to `KNOCK' notifications.

  * Update the Wireshark dissector to recognize and dissect the new
    messages.  (The `cap.knock' file sneakily includes a message from
    the future, to be dissected in a later modification.)

17 files changed:
common/protocol.h
contrib/README
mon/tripemon.in
peerdb/peers.in
peerdb/peers.in.5.in
py/tripe.py.in
server/admin.c
server/keyexch.c
server/peer.c
server/tests.at
server/tripe-admin.5.in
server/tripe.c
server/tripe.h
svc/connect.8.in
svc/connect.in
wireshark/cap.knock [new file with mode: 0644]
wireshark/tripe.lua

index cbf9636a5e135970f80b307622101ce5c28d6781..2416afcff2cd84135dfa4f2f48d34019f0851ebc 100644 (file)
 #define KX_REPLY 2u
 #define KX_SWITCH 3u
 #define KX_SWITCHOK 4u
-#define KX_NMSG 5u
+#define KX_TOKENRQ 5u
+#define KX_TOKEN 6u
+#define KX_KNOCK 7u
+#define KX_NMSG 8u
 
 /* --- Miscellaneous packets --- */
 
index f096a6ac10c1271ca23325d56a704b48f5aeb82f..a3138d250ec9b7d7e653e660be435e606ab1fa6e 100644 (file)
@@ -23,7 +23,10 @@ greet        A simple tool for stimulating a passive association by sending a
        `greet' packet.
 
 knock  A script which acts as an OpenSSH forced command or login shell for a
-       `tripe' user, estabishing dynamic assocations on demand.
+       `tripe' user, estabishing dynamic assocations on demand.  Not
+       recommended for new deployments: use the `KNOCK' protocol instead
+       (see the `Dynamic connetion' section of connect(8), and the `ADD'
+       command in tripe-admin(5), for details).
 
 sshsvc.conf
        A configuration script for sshsvc-mkauthkeys(1) (part of the
index 24709344da4ce205fe9496a263fca57569adbc95..0d15474ca335d82b8e3001eb586ab09d79dfc606 100644 (file)
@@ -1055,7 +1055,7 @@ class AddPeerDialog (MyDialog):
     table = GridPacker()
     me.vbox.pack_start(table, True, True, 0)
     me.e_name = table.labelled('Name',
-                               ValidatingEntry(r'^[^\s.:]+$', '', 16),
+                               ValidatingEntry(r'^[^\s:]+$', '', 16),
                                width = 3)
     me.e_addr = table.labelled('Address',
                                ValidatingEntry(r'^[a-zA-Z0-9.-]+$', '', 24),
@@ -1098,6 +1098,9 @@ class AddPeerDialog (MyDialog):
     me.c_privkey, me.e_privkey = \
       optional_entry('Private key tag', r'^[^.:\s]+$', 16)
 
+    me.c_knock, me.e_knock = \
+      optional_entry('Knock string', r'^[^:\s]+$', 16)
+
     me.show_all()
 
   def ok(me):
@@ -1115,7 +1118,9 @@ class AddPeerDialog (MyDialog):
                   key = (me.c_peerkey.get_active() and
                          me.e_peerkey.get_text() or None),
                   priv = (me.c_privkey.get_active() and
-                          me.e_privkey.get_text() or None))
+                          me.e_privkey.get_text() or None),
+                  knock = (me.c_knock.get_active() and
+                           me.e_knock.get_text() or None))
     except ValidationError:
       GDK.beep()
       return
index 25a144280695f3ae8cfbd3a3f56cd38b77e62f9b..3fb23803c8fc0e79b24d39a8d3e9193f04741bdf 100644 (file)
@@ -81,6 +81,19 @@ retries = 5
 ;;; The parameters here affect peers to whom dynamic connections are made.
 ;;; The user and connect parameters probably need customizing.
 
+[@KNOCK]
+@inherit = @ACTIVE, @WATCH
+
+;; keepalive: how often to send NOP packets to keep the connection alive, at
+;; least in the minds of intermediate stateful firewalls and NAT routers.
+keepalive = 2m
+
+;; every: interval for checking that this connection is alive.
+every = 30s
+
+;; knock: peer-name string to send to the peer.
+knock = $(myhost)
+
 [@DYNAMIC]
 @inherit = @ACTIVE, @WATCH
 
index a07240b2fba10ed87535bc15d6d8dd3138410644..89eb77769cb325a223b67e9c6227dbef3f228a75 100644 (file)
@@ -207,6 +207,10 @@ Interval for sending keepalive pings.  Used by
 Key tag to use to authenticate the peer.  Used by
 .BR connect (8).
 .TP
+.B knock
+Knock string to send when establishing a dynamic connection.  Used by
+.BR connect (8).
+.TP
 .B mobile
 Peer's IP address is highly volatile.  Used by
 .BR connect (8).
index 5562d7b30b50c0b26d158bf9134d09b2783282e0..63ae0afc4c72ed0f4be4f828c4fe81bcaa4e4de4 100644 (file)
@@ -838,7 +838,7 @@ class TripeCommandDispatcher (TripeConnection):
                               *['ADD'] +
                               _kwopts(kw, ['tunnel', 'keepalive',
                                            'key', 'priv', 'cork',
-                                           'mobile']) +
+                                           'mobile', 'knock']) +
                               [peer] +
                               list(addr)))
   def addr(me, peer):
index d80992cd368ab4ce0a32afca26a31d3bfb91614c..886c672c9c30071b46cce2bb48be8a3f5573ce3b 100644 (file)
@@ -1235,6 +1235,7 @@ static void a_doadd(admin_resop *r, int rc)
 
   if (add->peer.tag) xfree(add->peer.tag);
   if (add->peer.privtag) xfree(add->peer.privtag);
+  if (add->peer.knock) xfree(add->peer.knock);
   xfree(add->peer.name);
 }
 
@@ -1260,6 +1261,7 @@ static void acmd_add(admin *a, unsigned ac, char *av[])
   add->peer.name = 0;
   add->peer.tag = 0;
   add->peer.privtag = 0;
+  add->peer.knock = 0;
   add->peer.t_ka = 0;
   add->peer.tops = tun_default;
   add->peer.f = 0;
@@ -1292,6 +1294,10 @@ static void acmd_add(admin *a, unsigned ac, char *av[])
       if (add->peer.privtag) xfree(add->peer.privtag);
       add->peer.privtag = xstrdup(arg);
     })
+    OPTARG("-knock", arg, {
+      if (add->peer.knock) xfree(add->peer.knock);
+      add->peer.knock = xstrdup(arg);
+    })
   });
 
   /* --- Make sure someone's not got there already --- */
@@ -1318,6 +1324,7 @@ fail:
   if (add->peer.name) xfree(add->peer.name);
   if (add->peer.tag) xfree(add->peer.tag);
   if (add->peer.privtag) xfree(add->peer.privtag);
+  if (add->peer.knock) xfree(add->peer.knock);
   xfree(add);
   return;
 }
@@ -1844,6 +1851,7 @@ static void acmd_peerinfo(admin *a, unsigned ac, char *av[])
   if ((p = a_findpeer(a, av[0])) != 0) {
     ps = p_spec(p);
     a_info(a, "tunnel=%s", ps->tops->name, A_END);
+    if (ps->knock) a_info(a, "knock=%s", ps->knock, A_END);
     a_info(a, "key=%s", p_tag(p),
           "current-key=%s", p->kx.kpub->tag, A_END);
     if ((ptag = p_privtag(p)) == 0) ptag = "(default)";
index 4a990ee708b9328501411b2e029a7d204ce11c67..b141100480464584dbb0d5aa1703a244da27f133 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 -------------------------------------------------*/
@@ -620,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
@@ -673,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
@@ -881,14 +1107,35 @@ static void resend(keyexch *kx)
   buf bb;
   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'",
@@ -1339,10 +1586,16 @@ int kx_message(keyexch *kx, const addr *a, unsigned msg, buf *b)
           msg < KX_NMSG ? pkname[msg] : "unknown",
           kx ? '`' : '<', kx ? p_name(kx->p) : "nil", kx ? '\'' : '>'); )
 
+  switch (msg) {
+    case KX_TOKENRQ: dotokenrq(a, b); return (0);
+    case KX_KNOCK: doknock(a, b); return (0);
+  }
+
   if (!kx) return (-1);
   if (notice_message(kx)) return (0);
 
   switch (msg) {
+    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;
@@ -1544,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 -------------------------------------------------*/
index 2eb30bf5d5a5c86506157553373a889bb0cbe31d..f600f99e8531794845427c65328a2b49fd0e99bd 100644 (file)
@@ -488,6 +488,30 @@ buf *p_txstart(peer *p, unsigned msg)
   return (&p->b);
 }
 
+/* --- @p_txaddr@ --- *
+ *
+ * Arguments:  @const addr *a@ = recipient address
+ *             @const void *p@ = pointer to packet to send
+ *             @size_t sz@ = length of packet
+ *
+ * Returns:    Zero if successful, nonzero on error.
+ *
+ * Use:                Sends a packet to an address which (possibly) isn't a current
+ *             peer.
+ */
+
+int p_txaddr(const addr *a, const void *p, size_t sz)
+{
+  socklen_t sasz = addrsz(a);
+
+  IF_TRACING(T_PEER, trace_block(T_PACKET, "peer: sending packet", p, sz); )
+  if (sendto(sock.fd, p, sz, 0, &a->sa, sasz) < 0) {
+    a_warn("PEER", "?ADDR", a, "socket-write-error", "?ERRNO", A_END);
+    return (-1);
+  }
+  return (0);
+}
+
 /* --- @p_txend@ --- *
  *
  * Arguments:  @peer *p@ = pointer to peer block
@@ -894,6 +918,7 @@ peer *p_create(peerspec *spec)
   p->spec.name = (/*unconst*/ char *)SYM_NAME(p->byname);
   if (spec->tag) p->spec.tag = xstrdup(spec->tag);
   if (spec->privtag) p->spec.privtag = xstrdup(spec->privtag);
+  if (spec->knock) p->spec.knock = xstrdup(spec->knock);
   p->ks = 0;
   p->pings = 0;
   p->ifname = 0;
@@ -1037,6 +1062,7 @@ void p_destroy(peer *p)
   if (p->ifname) xfree(p->ifname);
   if (p->spec.tag) xfree(p->spec.tag);
   if (p->spec.privtag) xfree(p->spec.privtag);
+  if (p->spec.knock) xfree(p->spec.knock);
   p->t->ops->destroy(p->t);
   if (p->spec.t_ka) sel_rmtimer(&p->tka);
   for (pg = p->pings; pg; pg = ppg) {
index d217054a602cf49527032e7b3d32f32980c909fb..8bceb147703d697dfe8ce0f269d6234955e0a1e1 100644 (file)
@@ -751,4 +751,58 @@ WITH_TRIPE(, [
 
 AT_CLEANUP
 
+###--------------------------------------------------------------------------
+### Knock.
+
+AT_SETUP([server knock])
+AT_KEYWORDS([knock])
+export TRIPE_SLIPIF=USLIP
+
+for i in alice bob; do (mkdir $i; cd $i; SETUPDIR([alpha])); done
+
+WITH_2TRIPES([alice], [bob], [-nslip], [-talice], [-tbob], [
+  WITH_MITM([alice], [5311], [bob], [5312], [
+
+    COPROCESSES([wait-knock], [
+      echo WATCH +n
+      while read line; do
+       set x $line; shift
+       echo >&2 ">>> $line"
+       case "$1:$2:$3" in
+         OK::) ;;
+         NOTE:KNOCK:bob) shift 3; echo "$*" >knock-addr; break ;;
+         NOTE:* | TRACE:* | WARN:*) ;;
+         *) exit 63 ;;
+       esac
+      done
+    ], [
+      TRIPECTL -dalice
+    ])& waiter=$!
+
+    AT_CHECK([TRIPECTL -dbob ADD -knock bob alice INET 127.0.0.1 5312])
+
+    wait $waiter; waitrc=$?
+    AT_CHECK([echo $waitrc],, [0[]nl])
+    AT_CHECK([cat knock-addr],, [INET 127.0.0.1 5311[]nl])
+
+    AWAIT_KXDONE([alice], [alice], [bob], [bob], [
+      AT_CHECK([TRIPECTL -dalice ADD bob INET 127.0.0.1 5311])
+    ])
+
+    COMMS_EPING([alice], [alice], [bob], [bob])
+    COMMS_SLIP([alice], [alice], [bob], [bob])
+  ])
+
+  WITH_MITM([alice], [5319], [bob], [5312], [
+    AWAIT_KXDONE([alice], [alice], [bob], [bob], [
+      AT_CHECK([TRIPECTL -dalice FORCEKX bob])
+      AT_CHECK([TRIPECTL -dbob FORCEKX alice])
+    ])
+    AT_CHECK([TRIPECTL -dbob KILL alice])
+    AT_CHECK([TRIPECTL -dalice LIST],, [])
+  ])
+])
+
+AT_CLEANUP
+
 ###----- That's all, folks --------------------------------------------------
index a737e7782874d706430ead9df5e93a67cc2e2691..a340f57f75997064f3ccdcc02c303e9efc24525a 100644 (file)
@@ -353,6 +353,25 @@ Use the public key
 to authenticate the peer.  The default is to use the key tagged
 .IR peer .
 .TP
+.BI "\-knock \fR[" prefix .\fR] tag
+Send the string
+.RI [ prefix\fB. ] tag
+in
+.B token-rq
+and
+.B knock
+messages to the peer during key-exchange.  The string as a whole should
+name the local machine to the peer, and
+.I tag
+should name its public key.  When such messages are received from a
+currently unknown peer,
+.BR tripe (8)
+emits a
+.B KNOCK
+notification stating the peer's (claimed) name and address.  The server
+will already have verified that the sender is using the peer's private
+key by this point.
+.TP
 .B "\-mobile"
 The peer is a mobile device, and is likely to change address rapidly.
 If a packet arrives from an unknown address, the server's usual response
@@ -572,6 +591,16 @@ The tunnel driver used for this peer.
 The keepalive interval, in seconds, or zero if no keepalives are to be
 sent.
 .TP
+.B knock
+If present, the string sent to the peer to set up the association; see
+the
+.B \-knock
+option to
+.BR ADD ,
+and the
+.B KNOCK
+notification.
+.TP
 .B key
 The (short) key tag being used for the peer, as passed to the
 .B ADD
@@ -1168,6 +1197,12 @@ The peer
 .I peer
 has been killed.
 .SP
+.BI "KNOCK " peer " " address
+The currently unknown
+.I peer
+is attempting to connect from
+.IR address .
+.SP
 .BI "KXDONE " peer
 Key exchange with
 .I peer
@@ -1410,8 +1445,11 @@ is one of the tokens
 .BR challenge ,
 .BR reply ,
 .BR switch-rq ,
-or
 .BR switch-ok .
+.BR token-rq ,
+.BR token ,
+or
+.BR knock .
 .SP
 .BI "KX " peer " algorithms-mismatch local-private-key " privtag " peer-public-key " pubtag
 The algorithms specified in the peer's public key
@@ -1526,6 +1564,10 @@ An error occurred trying to read an incoming packet.
 An error occurred attempting to send a network packet.  We lost that
 one.
 .SP
+.BI "PEER " address\fR... " socket-write-error " ecode " " message
+An error occurred attempting to send a network packet.  We lost that
+one.
+.SP
 .BI "PEER " peer " unexpected-encrypted-ping 0x" id
 The peer sent an encrypted ping response whose id doesn't match any
 outstanding ping.  Maybe it was delayed for longer than the server was
index 58bf8a18b0af368682a7cfa425d92f01815f667d..d50757bc2baf2d1c2e1ae45ea7d895924ee08ef7 100644 (file)
@@ -299,6 +299,7 @@ int main(int argc, char *argv[])
   a_init(csock, u, g, csockmode);
   u_setugid(u, g);
   km_init(kr_priv, kr_pub, tag_priv);
+  kx_init();
   if (f & f_daemon) {
     if (daemonize())
       die(EXIT_FAILURE, "couldn't become a daemon: %s", strerror(errno));
index c796d07348d895d92ee941544db9c9e04c1eb0be..2024627a10abde931bc1dabc15137fb89875ae35 100644 (file)
@@ -610,6 +610,7 @@ typedef struct peerspec {
   char *name;                          /* Peer's name */
   char *privtag;                       /* Private key tag */
   char *tag;                           /* Public key tag */
+  char *knock;                         /* Knock string, or null */
   const tunnel_ops *tops;              /* Tunnel operations */
   unsigned long t_ka;                  /* Keep alive interval */
   addr sa;                             /* Socket address to speak to */
@@ -958,6 +959,17 @@ extern void kx_newkeys(keyexch */*kx*/);
 extern int kx_setup(keyexch */*kx*/, peer */*p*/,
                    keyset **/*ks*/, unsigned /*f*/);
 
+/* --- @kx_init@ --- *
+ *
+ * Arguments:  ---
+ *
+ * Returns:    ---
+ *
+ * Use:                Initializes the key-exchange logic.
+ */
+
+extern void kx_init(void);
+
 /*----- Keysets and symmetric cryptography --------------------------------*/
 
 /* --- @ks_drop@ --- *
@@ -1443,6 +1455,20 @@ extern int p_updateaddr(peer */*p*/, const addr */*a*/);
 
 extern buf *p_txstart(peer */*p*/, unsigned /*msg*/);
 
+/* --- @p_txaddr@ --- *
+ *
+ * Arguments:  @const addr *a@ = recipient address
+ *             @const void *p@ = pointer to packet to send
+ *             @size_t sz@ = length of packet
+ *
+ * Returns:    Zero if successful, nonzero on error.
+ *
+ * Use:                Sends a packet to an address which (possibly) isn't a current
+ *             peer.
+ */
+
+extern int p_txaddr(const addr */*a*/, const void */*p*/, size_t /*sz*/);
+
 /* --- @p_txend@ --- *
  *
  * Arguments:  @peer *p@ = pointer to peer block
index 86945ce47d17ce94dab1b763f39251bc83e45773..5a11164763eda16df987c994775121f24e0ea2d8 100644 (file)
@@ -246,6 +246,33 @@ becomes
 .BR A_CIPHER_BLKSZ .
 .
 .SS "Dynamic connection"
+The
+.B connect
+service supports two kinds of dynamic connection.
+.PP
+The new kind of dynamic association uses the built-in
+.B knock
+protocol.  If the peer's database record assigns a value to the
+.B knock
+key, then the new connection protocol is used: this value is sent to the
+peer during key-exchange, which should (if the peer is properly
+configured) automatically establish the other end of the connection.
+The string should have the form
+.RI [ prefix\fB. ] tag ,
+where the whole string names this host as it is known by the remote
+host, and the
+.I tag
+names this host's public key.  The passive server receives this string,
+verifies that the sender has access to the claimed private key, and
+emits a
+.B KNOCK
+notification which
+.B connect
+notices, causing it to establish the passive peer.  While the internals
+are somewhat complex, configuration is pretty easy.
+.PP
+The older kind of dynamic association is rather more complicated to set
+up, and involves running shell commands, and probably configuring SSH.
 If a peer's database record assigns a value to the
 .B connect
 key, then the
@@ -294,7 +321,8 @@ key is invoked as a Bourne shell command.  This ought to result in a
 .B KILL
 command being issued to the peer's server.
 .PP
-In detail, the protocol for passive connection works as follows.
+In detail, the protocol for old-style dynamic connection works as
+follows.
 .hP 1.
 The active peer
 .BR ADD s
@@ -724,6 +752,29 @@ command reported
 .B FAIL
 .IR error ...
 .SP
+.BI "USER connect knock-active-peer " name
+The server reported a valid
+.B knock
+message from a peer calling itself
+.I name
+but this is not a passive peer.
+.SP
+.BI "USER connect knock-unknown-peer " name
+The server reported a valid
+.B knock
+message from a peer calling itself
+.I name
+but no such peer is defined in the database.
+.SP
+.BI "USER connect knock-tag-mismatch peer " name " public-key-tag " tag
+The server reported a valid
+.B knock
+message from a peer calling itself
+.I name
+but this name doesn't match that peer's recorded public-key tag, which
+is
+.IR tag .
+.SP
 .BI "USER connect ping-ok " peer
 A reply was received to a
 .B PING
index b6ec4b8ca7ef09a07573df94ed82bd2932fcca2e..fe8d73b98e592862cabe2f8ee31b4722d871a126 100644 (file)
@@ -425,6 +425,7 @@ class PingPeer (object):
     me._timeout = peer.get('timeout', filter = T.timespec, default = 10)
     me._retries = peer.get('retries', filter = int, default = 5)
     me._connectp = peer.has('connect')
+    me._knockp = peer.has('knock')
     return me
 
   def _ping(me):
@@ -442,10 +443,10 @@ class PingPeer (object):
   def _reconnect(me):
     try:
       peer = Peer(me._peer)
-      if me._connectp:
+      if me._connectp or me._knockp:
         S.warn('connect', 'reconnecting', me._peer)
         S.forcekx(me._peer)
-        T.spawn(run_connect, peer, peer.get('connect'))
+        if me._connectp: T.spawn(run_connect, peer, peer.get('connect'))
         me._timer = M.SelTimer(time() + me._every, me._time)
         me._sabotage = False
       else:
@@ -731,6 +732,7 @@ def addpeer(peer, addr):
           key = peer.get('key', default = None),
           priv = peer.get('priv', default = None),
           mobile = peer.get('mobile', filter = boolean, default = False),
+          knock = peer.get('knock', default = None),
           cork = peer.get('cork', filter = boolean, default = False),
           *addr)
   except T.TripeError, exc:
@@ -760,6 +762,23 @@ def notify(_, code, *rest):
     try: cr = chalmap[chal]
     except KeyError: pass
     else: cr.switch(rest[1:])
+  elif code == 'KNOCK':
+    try: p = Peer(rest[0])
+    except KeyError:
+      S.warn(['connect', 'knock-unknown-peer', rest[0]])
+      return
+    if p.get('peer') != 'PASSIVE':
+      S.warn(['connect', 'knock-active-peer', p.name])
+      return
+    dot = p.name.find('.')
+    if dot >= 0: kname = p.name[dot + 1:]
+    else: kname = p.name
+    ktag = p.get('key', p.name)
+    if kname != ktag:
+      S.warn(['connect', 'knock-tag-mismatch',
+              'peer', pname, 'public-key-tag', ktag])
+      return
+    T.spawn(addpeer, p, rest[1:])
 
 ###--------------------------------------------------------------------------
 ### Command implementation.
diff --git a/wireshark/cap.knock b/wireshark/cap.knock
new file mode 100644 (file)
index 0000000..2ca3537
Binary files /dev/null and b/wireshark/cap.knock differ
index f950aefad11ef200e5d3be2a47d13175e65fa961..aab8e7983832b8dfc56dc2397228d0072f3e9e48 100644 (file)
@@ -289,6 +289,37 @@ local function dissect_misc_ciphertext(buf, tree, pos, sz)
   return dissect_ciphertext(buf, tree, "tripe.misc.ciphertext", pos, sz)
 end
 
+local function dissect_chal(buf, tree, label, pos, sz)
+  local len = buf(pos, 2):uint()
+  local t = tree:add(PF[label], buf(pos, len + 2))
+  t:add(PF["tripe.chal.len"], buf(pos, 2)); pos = pos + 2
+  t:add(PF["tripe.chal.sequence"], buf(pos, 4)); pos = pos + 4; len = len - 4
+  t:add(PF["tripe.chal.tag"], buf(pos, len))
+  return pos + len
+end
+
+local function dissect_my_chal(buf, tree, pos, sz)
+  return dissect_chal(buf, tree, "tripe.knock.mychal", pos, sz)
+end
+
+local function dissect_your_chal(buf, tree, pos, sz)
+  return dissect_chal(buf, tree, "tripe.knock.yourchal", pos, sz)
+end
+
+local function dissect_keyid(buf, tree, pos, sz)
+  tree:add(PF["tripe.knock.keyid"], buf(pos, 4))
+  return pos + 4
+end
+
+local function dissect_ies(buf, tree, pos, sz)
+  local len = buf(pos, 2):uint()
+  local lim = pos + len + 2
+  local t = tree:add(PF["tripe.knock.ies"], buf(pos, len + 2))
+  t:add(PF["tripe.ies.len"], buf(pos, 2)); pos = pos + 2
+  pos = dissect_ge[C.kx](buf, t, pos, sz)
+  return dissect_ciphertext(buf, t, "tripe.ies.ciphertext", pos, lim)
+end
+
 -----------------------------------------------------------------------------
 --- The protocol information table.
 
@@ -345,6 +376,19 @@ local PKTINFO = {
                          dissect_switch } },
       [4] = { label = "KX_SWITCHOK", info = "switch-ok",
              dissect = { dissect_switchok } },
+      [5] = { label = "KX_TOKENRQ", info = "token-rq",
+             dissect = { dissect_my_chal,
+                         dissect_keyid,
+                         dissect_ies } },
+      [6] = { label = "KX_TOKEN", info = "token",
+             dissect = { dissect_your_chal,
+                         dissect_my_chal,
+                         dissect_ies } },
+      [7] = { label = "KX_KNOCK", info = "knock",
+             dissect = { dissect_your_chal,
+                         dissect_keyid,
+                         dissect_ies,
+                         dissect_my_challenge } }
     }
   },
 
@@ -415,6 +459,40 @@ do
     ["tripe.packet.payload"] = {
       name = "Encrypted packet", type = ftypes.NONE
     },
+    ["tripe.knock.keyid"] = {
+      name = "Short key indicator", type = ftypes.UINT32, base = base.HEX
+    },
+    ["tripe.knock.mychal"] = {
+      name = "Sender's one-time challenge", type = ftypes.NONE
+    },
+    ["tripe.knock.yourchal"] = {
+      name = "Recipient's one-time challenge", type = ftypes.NONE
+    },
+    ["tripe.chal.len"] = {
+      name = "Challenge length", type = ftypes.UINT16, base = base.DEC
+    },
+    ["tripe.chal.sequence"] = {
+      name = "Challenge sequence number",
+      type = ftypes.UINT32, base = base.DEC
+    },
+    ["tripe.chal.tag"] = {
+      name = "Challenge tag", type = ftypes.BYTES, base = base.SPACE
+    },
+    ["tripe.knock.ies"] = {
+      name = "Encrypted message", type = ftypes.NONE
+    },
+    ["tripe.ies.len"] = {
+      name = "Encrypted message length",
+      type = ftypes.UINT16, base = base.DEC
+    },
+    ["tripe.ies.clue"] = {
+      name = "Encrypted message KEM clue",
+      type = ftypes.BYTES, base = base.SPACE
+    },
+    ["tripe.ies.ciphertext"] = {
+      name = "Encrypted message ciphertext",
+      type = ftypes.BYTES, base = base.SPACE
+    },
     ["tripe.keyexch.type"] = {
       name = "Key-exchange subcode", type = ftypes.UINT8, base = base.DEC,
       mask = 0x0f, tab = subtab[1]