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_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 --- */
 
 
 /* --- 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
        `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
 
 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',
     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),
                                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_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):
     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
                   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
     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.
 
 ;;; 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
 
 [@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
 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).
 .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',
                               *['ADD'] +
                               _kwopts(kw, ['tunnel', 'keepalive',
                                            'key', 'priv', 'cork',
-                                           'mobile']) +
+                                           'mobile', 'knock']) +
                               [peer] +
                               list(addr)))
   def addr(me, peer):
                               [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.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);
 }
 
   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.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;
   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);
     })
       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 --- */
   });
 
   /* --- 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.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;
 }
   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 ((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)";
     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-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[] = {
  */
 
 /*----- 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 -------------------------------------------------*/
 };
 
 /*----- Various utilities -------------------------------------------------*/
@@ -620,6 +637,148 @@ static void kxc_answer(keyexch *kx, kxchal *kxc)
 
 /*----- Individual message handlers ---------------------------------------*/
 
 
 /*----- 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
 /* --- @doprechallenge@ --- *
  *
  * Arguments:  @keyexch *kx@ = pointer to key exchange block
@@ -673,6 +832,73 @@ bad:
   return (-1);
 }
 
   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
 /* --- @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;
   buf bb;
   struct timeval tv;
   const dhgrp *g = kx->kpriv->grp;
+  octet *p;
+  size_t sz;
   buf *b;
 
   switch (kx->s) {
     case KXS_CHAL:
   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'",
       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 ? '\'' : '>'); )
 
           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) {
   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;
     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);
 }
 
   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 -------------------------------------------------*/
 /*----- That's all, folks -------------------------------------------------*/
index 2eb30bf5d5a5c86506157553373a889bb0cbe31d..f600f99e8531794845427c65328a2b49fd0e99bd 100644 (file)
@@ -488,6 +488,30 @@ buf *p_txstart(peer *p, unsigned msg)
   return (&p->b);
 }
 
   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
 /* --- @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);
   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;
   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->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) {
   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
 
 
 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 --------------------------------------------------
 ###----- 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
 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
 .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
 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
 .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
 .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
 .BI "KXDONE " peer
 Key exchange with
 .I peer
@@ -1410,8 +1445,11 @@ is one of the tokens
 .BR challenge ,
 .BR reply ,
 .BR switch-rq ,
 .BR challenge ,
 .BR reply ,
 .BR switch-rq ,
-or
 .BR switch-ok .
 .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
 .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
 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
 .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);
   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));
   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 *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 */
   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*/);
 
 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@ --- *
 /*----- 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*/);
 
 
 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
 /* --- @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"
 .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
 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
 .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
 .hP 1.
 The active peer
 .BR ADD s
@@ -724,6 +752,29 @@ command reported
 .B FAIL
 .IR error ...
 .SP
 .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
 .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._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):
     return me
 
   def _ping(me):
@@ -442,10 +443,10 @@ class PingPeer (object):
   def _reconnect(me):
     try:
       peer = Peer(me._peer)
   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)
         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:
         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),
           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:
           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:])
     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.
 
 ###--------------------------------------------------------------------------
 ### 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
 
   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.
 
 -----------------------------------------------------------------------------
 --- The protocol information table.
 
@@ -345,6 +376,19 @@ local PKTINFO = {
                          dissect_switch } },
       [4] = { label = "KX_SWITCHOK", info = "switch-ok",
              dissect = { dissect_switchok } },
                          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.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]
     ["tripe.keyexch.type"] = {
       name = "Key-exchange subcode", type = ftypes.UINT8, base = base.DEC,
       mask = 0x0f, tab = subtab[1]