Merge branches 'mdw/knock' and 'mdw/ipv6' into bleeding
authorMark Wooding <mdw@distorted.org.uk>
Fri, 25 Jan 2019 12:08:24 +0000 (12:08 +0000)
committerMark Wooding <mdw@distorted.org.uk>
Fri, 25 Jan 2019 12:08:24 +0000 (12:08 +0000)
* mdw/knock:
  Add notion of `ephemeral' associations and a goodbye protocol.
  Add new `knock' protocol.
  server/{keyexch,peer}.c: Maybe key-exchange messages come out of the blue.
  server/keyexch.c (kx_message): Squish vertically.
  server/keyexch.c: Abstract out the common message-handling behaviour.
  server/keymgmt.c: Track and find keys by their 32-bit IDs.
  server/test.c: Add a program to assist unit tests.
  server/servutil.c: Add utilities for plain asymmetric encryption.
  server/servutil.c: Add utilities for simple leaky-bucket rate limiting.
  server/keyexch.c: Rename kx_init => kx_setup.
  server/: Augment challenges to allow a payload.
  server/chal.c: Capture `master->algs.bulk' in a variable.
  server/chal.c: Rename bulk => bchal.
  server/: Expose and enhance the bulk-key-derivation protocol.

* mdw/ipv6: (64 commits)
  contrib/greet.in: Accept IPv6 addresses.
  contrib/tripe-ipif.in: Fixing for IPv6.
  svc/conntrack.in: Add IPv6 support.
  svc/conntrack.in: Split out a base class from `InetAddress'.
  svc/conntrack.in: Contemplate multiple address families.
  svc/conntrack.in: Allow multiple networks in a peer pattern.
  svc/conntrack.in (kickpeers): Refactor and reformat the search loop.
  svc/conntrack.in (kickpeers): Rename `map' variable.
  svc/conntrack.in: Process peer patterns in order.
  svc/conntrack.in: Maintain config groups in a dictionary.
  svc/conntrack.in: Make an `InetAddress' class to do address wrangling.
  svc/conntrack.in: Factor out network parsing.
  svc/conntrack.in: Gather address hacking functions into a new section.
  svc/conntrack.in: Introduce a function for parsing address strings.
  svc/conntrack.in (strmask): Consistently return a string object.
  svc/conntrack.in: Fix netmask parsing.
  svc/conntrack.in: Leave time for network configuration to settle.
  svc/conntrack.in: Hoist `netupdown' above `kickpeers'.
  server/, mon/: Introduce transport of TrIPE over IPv6.
  server/addrmap.c (hash): Visually tighten the arithmetic.
  ...

24 files changed:
common/protocol.h
contrib/README
mon/tripemon.in
peerdb/peers.in
peerdb/peers.in.5.in
py/tripe.py.in
server/Makefile.am
server/admin.c
server/bulkcrypto.c
server/chal.c
server/keyexch.c
server/keymgmt.c
server/keyset.c
server/peer.c
server/servutil.c
server/test.c [new file with mode: 0644]
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 cbf9636..e40cf17 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 --- */
 
@@ -76,6 +79,7 @@
 #define MISC_EPING 3u                  /* Encrypted ping */
 #define MISC_EPONG 4u                  /* Encrypted ping response */
 #define MISC_GREET 5u                  /* A greeting from a NATed peer */
+#define MISC_BYE 6u                    /* Departure notification */
 
 /* --- Symmetric encryption and keysets --- *
  *
index f096a6a..a3138d2 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 1c70d1f..11ee6dc 100644 (file)
@@ -1063,7 +1063,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.l_af = table.labelled('Family', combo_box_text(),
                              newlinep = True, width = 3)
@@ -1106,11 +1106,17 @@ class AddPeerDialog (MyDialog):
     me.c_mobile = G.CheckButton('Mobile')
     table.pack(me.c_mobile, newlinep = True, width = 4, xopt = G.FILL)
 
+    me.c_ephem = G.CheckButton('Ephemeral')
+    table.pack(me.c_ephem, newlinep = True, width = 4, xopt = G.FILL)
+
     me.c_peerkey, me.e_peerkey = \
       optional_entry('Peer 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):
@@ -1127,10 +1133,13 @@ class AddPeerDialog (MyDialog):
                   tunnel = t and me.tuns[t] or None,
                   cork = me.c_cork.get_active() or None,
                   mobile = me.c_mobile.get_active() or None,
+                  ephemeral = me.c_ephem.get_active() or None,
                   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 25a1442..75c039d 100644 (file)
@@ -57,6 +57,10 @@ host = override-me
 ;; the remote peer.
 peer = INET $[$(host)] $(port)
 
+;; ephemeral: whether to send the peer a disconnection notification, or
+;; react to one from the peer.
+ephemeral = nil
+
 ;;;--------------------------------------------------------------------------
 ;;; Temporary association defaults.
 ;;;
@@ -81,9 +85,29 @@ retries = 5
 ;;; The parameters here affect peers to whom dynamic connections are made.
 ;;; The user and connect parameters probably need customizing.
 
-[@DYNAMIC]
+[@EPHEMERAL]
 @inherit = @ACTIVE, @WATCH
 
+;; ephemeral: whether to send the peer a disconnection notification, or
+;; react to one from the peer.
+ephemeral = t
+
+;; every: interval for checking that this connection is alive.
+every = 30s
+
+[@KNOCK]
+@inherit = @EPHEMERAL
+
+;; 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
+
+;; knock: peer-name string to send to the peer.
+knock = $(myhost)
+
+[@DYNAMIC]
+@inherit = @EPHEMERAL
+
 ;; cork: whether to wait for a key-exchange packet from the peer before
 ;; sending one of our own.
 cork = t
@@ -102,9 +126,6 @@ disconnect = ssh -q $(ssh-user)@$[$(host)] goodbye
 ;; least in the minds of intermediate stateful firewalls and NAT routers.
 keepalive = 2m
 
-;; every: interval for checking that this connection is alive.
-every = 30s
-
 ;;;--------------------------------------------------------------------------
 ;;; Passive-peers defaults.
 ;;;
index 554a8d1..d638b07 100644 (file)
@@ -194,6 +194,12 @@ Don't initiate immediate key exchange.  Used by
 Shell command for closing down connection to this peer.  Used by
 .BR connect (8).
 .TP
+.B ephemeral
+Mark the peer as ephemeral: see
+.BR tripe-admin (5)
+for what this means.  Used by
+.BR connect (8).
+.TP
 .B every
 Interval for checking that the peer is still alive and well.  Used by
 .BR connect (8).
@@ -226,6 +232,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 29911b0..a9be668 100644 (file)
@@ -838,7 +838,8 @@ class TripeCommandDispatcher (TripeConnection):
                               *['ADD'] +
                               _kwopts(kw, ['tunnel', 'keepalive',
                                            'key', 'priv', 'cork',
-                                           'mobile']) +
+                                           'mobile', 'knock',
+                                           'ephemeral']) +
                               [peer] +
                               list(addr)))
   def addr(me, peer):
index 1e6d71b..f569b45 100644 (file)
@@ -25,6 +25,7 @@
 include $(top_srcdir)/vars.am
 
 sbin_PROGRAMS           =
+noinst_PROGRAMS                 =
 man_MANS                =
 
 LDADD                   = $(libtripe) $(libpriv) \
@@ -73,4 +74,26 @@ man_MANS             += tripe-service.7tripe
 CLEANFILES             += tripe-service.7tripe
 EXTRA_DIST             += tripe-service.7.in
 
+###--------------------------------------------------------------------------
+### Unit-test program.
+
+noinst_PROGRAMS                += tripe-test
+
+tripe_test_SOURCES      = test.c
+
+tripe_test_SOURCES     += admin.c
+tripe_test_SOURCES     += addrmap.c
+tripe_test_SOURCES     += bulkcrypto.c
+tripe_test_SOURCES     += chal.c
+tripe_test_SOURCES     += dh.c
+tripe_test_SOURCES     += keyexch.c
+tripe_test_SOURCES     += keymgmt.c
+tripe_test_SOURCES     += keyset.c
+tripe_test_SOURCES     += peer.c
+tripe_test_SOURCES     += privsep.c
+tripe_test_SOURCES     += servutil.c
+
+tripe_test_SOURCES     += tun-std.c
+tripe_test_SOURCES     += tun-slip.c
+
 ###----- That's all, folks --------------------------------------------------
index 8a3e62c..87bb905 100644 (file)
@@ -560,7 +560,7 @@ void a_quit(void)
 {
   close(sock.fd);
   unlink(sockname);
-  FOREACH_PEER(p, { p_destroy(p); });
+  FOREACH_PEER(p, { p_destroy(p, 1); });
   ps_quit();
   exit(0);
 }
@@ -1421,6 +1421,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);
 }
 
@@ -1446,6 +1447,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;
@@ -1469,15 +1471,21 @@ static void acmd_add(admin *a, unsigned ac, char *av[])
     })
     OPTTIME("-keepalive", t, { add->peer.t_ka = t; })
     OPT("-cork", { add->peer.f |= KXF_CORK; })
+    OPT("-ephemeral", { add->peer.f |= PSF_EPHEM; })
     OPTARG("-key", arg, {
       if (add->peer.tag) xfree(add->peer.tag);
       add->peer.tag = xstrdup(arg);
     })
-    OPT("-mobile", { add->peer.f |= PSF_MOBILE; })
+    OPT("-mobile", { add->peer.f |= PSF_MOBILE | PSF_EPHEM; })
     OPTARG("-priv", 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);
+      add->peer.f |= PSF_EPHEM;
+    })
   });
 
   /* --- Make sure someone's not got there already --- */
@@ -1504,6 +1512,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;
 }
@@ -1982,7 +1991,7 @@ static void acmd_getchal(admin *a, unsigned ac, char *av[])
   buf b;
 
   buf_init(&b, buf_i, PKBUFSZ);
-  c_new(&b);
+  c_new(0, 0, &b);
   a_info(a, "?B64", BBASE(&b), (size_t)BLEN(&b), A_END);
   a_ok(a);
 }
@@ -1999,7 +2008,7 @@ static void acmd_checkchal(admin *a, unsigned ac, char *av[])
     a_fail(a, "bad-base64", "%s", codec_strerror(err), A_END);
   else {
     buf_init(&b, d.buf, d.len);
-    if (c_check(&b) || BBAD(&b) || BLEFT(&b))
+    if (c_check(0, 0, &b) || BBAD(&b) || BLEFT(&b))
       a_fail(a, "invalid-challenge", A_END);
     else
       a_ok(a);
@@ -2049,6 +2058,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)";
@@ -2057,6 +2067,7 @@ static void acmd_peerinfo(admin *a, unsigned ac, char *av[])
     a_info(a, "keepalive=%lu", ps->t_ka, A_END);
     a_info(a, "corked=%s", BOOL(p->kx.f&KXF_CORK),
           "mobile=%s", BOOL(ps->f&PSF_MOBILE),
+          "ephemeral=%s", BOOL(ps->f&PSF_EPHEM),
           A_END);
     a_ok(a);
   }
@@ -2112,7 +2123,7 @@ static void acmd_kill(admin *a, unsigned ac, char *av[])
   peer *p;
 
   if ((p = a_findpeer(a, av[0])) != 0) {
-    p_destroy(p);
+    p_destroy(p, 1);
     a_ok(a);
   }
 }
index 7d754cd..d0e654c 100644 (file)
   trace_block(T_CRYPTO, "crypto: expected MAC", (pmac), (tagsz));      \
 }) } while (0)
 
+/* --- @derivekey@ --- *
+ *
+ * Arguments:  @octet *k@ = pointer to an output buffer of at least
+ *                     @MAXHASHSZ@ bytes
+ *             @size_t ksz@ = actual size wanted (for tracing)
+ *             @const deriveargs@ = derivation parameters, as passed into
+ *                     @genkeys@
+ *             @int dir@ = direction for the key (@DIR_IN@ or @DIR_OUT@)
+ *             @const char *what@ = label for the key (input to derivation)
+ *
+ * Returns:    ---
+ *
+ * Use:                Derives a session key, for use on incoming or outgoing data.
+ */
+
+static void derivekey(octet *k, size_t ksz, const deriveargs *a,
+                     int dir, const char *what)
+{
+  const gchash *hc = a->hc;
+  ghash *h;
+
+  assert(ksz <= hc->hashsz);
+  assert(hc->hashsz <= MAXHASHSZ);
+  h = GH_INIT(hc);
+  GH_HASH(h, a->what, strlen(a->what)); GH_HASH(h, what, strlen(what) + 1);
+  switch (dir) {
+    case DIR_IN:
+      if (a->x) GH_HASH(h, a->k, a->x);
+      if (a->y != a->x) GH_HASH(h, a->k + a->x, a->y - a->x);
+      break;
+    case DIR_OUT:
+      if (a->y != a->x) GH_HASH(h, a->k + a->x, a->y - a->x);
+      if (a->x) GH_HASH(h, a->k, a->x);
+      break;
+    default:
+      abort();
+  }
+  GH_HASH(h, a->k + a->y, a->z - a->y);
+  GH_DONE(h, k);
+  GH_DESTROY(h);
+  IF_TRACING(T_KEYSET, { IF_TRACING(T_CRYPTO, {
+    char _buf[32];
+    sprintf(_buf, "crypto: %s key %s", dir ? "outgoing" : "incoming", what);
+    trace_block(T_CRYPTO, _buf, k, ksz);
+  }) })
+}
+
 /*----- Common functionality for generic-composition transforms -----------*/
 
 #define CHECK_MAC(h, pmac, tagsz) do {                                 \
@@ -204,25 +251,26 @@ static bulkchal *gencomp_genchal(const gencomp_algs *a)
   return (&gc->_b);
 }
 
-static int gencomp_chaltag(bulkchal *bc, const void *m, size_t msz, void *t)
+static int gencomp_chaltag(bulkchal *bc, const void *m, size_t msz,
+                          uint32 seq, void *t)
 {
   gencomp_chal *gc = (gencomp_chal *)bc;
   ghash *h = GM_INIT(gc->m);
 
-  GH_HASH(h, m, msz);
+  GH_HASHU32(h, seq); if (msz) GH_HASH(h, m, msz);
   memcpy(t, GH_DONE(h, 0), bc->tagsz);
   GH_DESTROY(h);
   return (0);
 }
 
 static int gencomp_chalvrf(bulkchal *bc, const void *m, size_t msz,
-                          const void *t)
+                          uint32 seq, const void *t)
 {
   gencomp_chal *gc = (gencomp_chal *)bc;
   ghash *h = GM_INIT(gc->m);
   int ok;
 
-  GH_HASH(h, m, msz);
+  GH_HASHU32(h, seq); if (msz) GH_HASH(h, m, msz);
   ok = ct_memeq(GH_DONE(h, 0), t, gc->_b.tagsz);
   GH_DESTROY(h);
   return (ok ? 0 : -1);
@@ -309,7 +357,7 @@ static size_t v0_overhead(const bulkalgs *aa)
 static size_t v0_expsz(const bulkalgs *aa)
   { const v0_algs *a = (const v0_algs *)aa; return (gencomp_expsz(&a->ga)); }
 
-static bulkctx *v0_genkeys(const bulkalgs *aa, const struct rawkey *rk)
+static bulkctx *v0_genkeys(const bulkalgs *aa, const deriveargs *da)
 {
   const v0_algs *a = (const v0_algs *)aa;
   v0_ctx *bc = CREATE(v0_ctx);
@@ -318,9 +366,10 @@ static bulkctx *v0_genkeys(const bulkalgs *aa, const struct rawkey *rk)
 
   bc->tagsz = a->ga.tagsz;
   for (i = 0; i < NDIR; i++) {
-    ks_derivekey(k, a->ga.cksz, rk, i, "encryption");
+    if (!(da->f&(1 << i))) { bc->d[i].c = 0; bc->d[i].m = 0; continue; }
+    derivekey(k, a->ga.cksz, da, i, "encryption");
     bc->d[i].c = GC_INIT(a->ga.c, k, a->ga.cksz);
-    ks_derivekey(k, a->ga.mksz, rk, i, "integrity");
+    derivekey(k, a->ga.mksz, da, i, "integrity");
     bc->d[i].m = GM_KEY(a->ga.m, k, a->ga.mksz);
   }
   return (&bc->_b);
@@ -344,8 +393,8 @@ static void v0_freectx(bulkctx *bbc)
   int i;
 
   for (i = 0; i < NDIR; i++) {
-    GC_DESTROY(bc->d[i].c);
-    GM_DESTROY(bc->d[i].m);
+    if (bc->d[i].c) GC_DESTROY(bc->d[i].c);
+    if (bc->d[i].m) GM_DESTROY(bc->d[i].m);
   }
   DESTROY(bc);
 }
@@ -359,10 +408,13 @@ static int v0_encrypt(bulkctx *bbc, unsigned ty,
   const octet *p = BCUR(b);
   size_t sz = BLEFT(b);
   octet *qmac, *qseq, *qiv, *qpk;
-  size_t ivsz = GC_CLASS(c)->blksz;
+  size_t ivsz;
   size_t tagsz = bc->tagsz;
   octet t[4];
 
+  assert(c);
+  ivsz = GC_CLASS(c)->blksz;
+
   /* --- Determine the ciphertext layout --- */
 
   if (buf_ensure(bb, tagsz + SEQSZ + ivsz + sz)) return (0);
@@ -419,10 +471,13 @@ static int v0_decrypt(bulkctx *bbc, unsigned ty,
   octet *q = BCUR(bb);
   ghash *h;
   gcipher *c = bc->d[DIR_IN].c;
-  size_t ivsz = GC_CLASS(c)->blksz;
+  size_t ivsz;
   size_t tagsz = bc->tagsz;
   octet t[4];
 
+  assert(c);
+  ivsz = GC_CLASS(c)->blksz;
+
   /* --- Break up the packet into its components --- */
 
   if (psz < ivsz + SEQSZ + tagsz) {
@@ -587,7 +642,7 @@ static size_t iiv_expsz(const bulkalgs *aa)
   return (gencomp_expsz(&a->ga));
 }
 
-static bulkctx *iiv_genkeys(const bulkalgs *aa, const struct rawkey *rk)
+static bulkctx *iiv_genkeys(const bulkalgs *aa, const deriveargs *da)
 {
   const iiv_algs *a = (const iiv_algs *)aa;
   iiv_ctx *bc = CREATE(iiv_ctx);
@@ -596,11 +651,13 @@ static bulkctx *iiv_genkeys(const bulkalgs *aa, const struct rawkey *rk)
 
   bc->tagsz = a->ga.tagsz;
   for (i = 0; i < NDIR; i++) {
-    ks_derivekey(k, a->ga.cksz, rk, i, "encryption");
+    if (!(da->f&(1 << i)))
+      { bc->d[i].c = 0; bc->d[i].b = 0; bc->d[i].m = 0; continue; }
+    derivekey(k, a->ga.cksz, da, i, "encryption");
     bc->d[i].c = GC_INIT(a->ga.c, k, a->ga.cksz);
-    ks_derivekey(k, a->bksz, rk, i, "blkc");
+    derivekey(k, a->bksz, da, i, "blkc");
     bc->d[i].b = GC_INIT(a->b, k, a->bksz);
-    ks_derivekey(k, a->ga.mksz, rk, i, "integrity");
+    derivekey(k, a->ga.mksz, da, i, "integrity");
     bc->d[i].m = GM_KEY(a->ga.m, k, a->ga.mksz);
   }
   return (&bc->_b);
@@ -624,9 +681,9 @@ static void iiv_freectx(bulkctx *bbc)
   int i;
 
   for (i = 0; i < NDIR; i++) {
-    GC_DESTROY(bc->d[i].c);
-    GC_DESTROY(bc->d[i].b);
-    GM_DESTROY(bc->d[i].m);
+    if (bc->d[i].c) GC_DESTROY(bc->d[i].c);
+    if (bc->d[i].b) GC_DESTROY(bc->d[i].b);
+    if (bc->d[i].m) GM_DESTROY(bc->d[i].m);
   }
   DESTROY(bc);
 }
@@ -644,10 +701,14 @@ static int iiv_encrypt(bulkctx *bbc, unsigned ty,
   const octet *p = BCUR(b);
   size_t sz = BLEFT(b);
   octet *qmac, *qseq, *qpk;
-  size_t ivsz = GC_CLASS(c)->blksz, blkcsz = GC_CLASS(blkc)->blksz;
+  size_t ivsz, blkcsz;
   size_t tagsz = bc->tagsz;
   octet t[4];
 
+  assert(c); assert(blkc);
+  ivsz = GC_CLASS(c)->blksz;
+  blkcsz = GC_CLASS(blkc)->blksz;
+
   /* --- Determine the ciphertext layout --- */
 
   if (buf_ensure(bb, tagsz + SEQSZ + sz)) return (0);
@@ -707,10 +768,14 @@ static int iiv_decrypt(bulkctx *bbc, unsigned ty,
   octet *q = BCUR(bb);
   ghash *h;
   gcipher *c = bc->d[DIR_IN].c, *blkc = bc->d[DIR_IN].b;
-  size_t ivsz = GC_CLASS(c)->blksz, blkcsz = GC_CLASS(blkc)->blksz;
+  size_t ivsz, blkcsz;
   size_t tagsz = bc->tagsz;
   octet t[4];
 
+  assert(c); assert(blkc);
+  ivsz = GC_CLASS(c)->blksz;
+  blkcsz = GC_CLASS(blkc)->blksz;
+
   /* --- Break up the packet into its components --- */
 
   if (psz < SEQSZ + tagsz) {
@@ -893,7 +958,7 @@ static size_t naclbox_overhead(const bulkalgs *aa)
 static size_t naclbox_expsz(const bulkalgs *aa)
   { return (MEG(2048)); }
 
-static bulkctx *naclbox_genkeys(const bulkalgs *aa, const struct rawkey *rk)
+static bulkctx *naclbox_genkeys(const bulkalgs *aa, const deriveargs *da)
 {
   const naclbox_algs *a = (const naclbox_algs *)aa;
   naclbox_ctx *bc = CREATE(naclbox_ctx);
@@ -901,7 +966,8 @@ static bulkctx *naclbox_genkeys(const bulkalgs *aa, const struct rawkey *rk)
   int i;
 
   for (i = 0; i < NDIR; i++) {
-    ks_derivekey(k, a->cksz, rk, i, "encryption");
+    if (!(da->f&(1 << i))) { bc->d[i].c = 0; continue; }
+    derivekey(k, a->cksz, da, i, "encryption");
     bc->d[i].c = GC_INIT(a->c, k, a->cksz);
   }
   return (&bc->_b);
@@ -922,31 +988,44 @@ static bulkchal *naclbox_genchal(const bulkalgs *aa)
     trace(T_CHAL, "chal: generated new challenge key");
     trace_block(T_CRYPTO, "chal: new key", buf_t, a->cksz);
   })
-  c->_b.tagsz = 16;
+  c->_b.tagsz = POLY1305_TAGSZ;
   return (&c->_b);
 }
 
-static int naclbox_chaltag(bulkchal *bc, const void *m, size_t msz, void *t)
+static int naclbox_chaltag(bulkchal *bc, const void *m, size_t msz,
+                          uint32 seq, void *t)
 {
   naclbox_chal *c = (naclbox_chal *)bc;
-  octet b0[SALSA20_NONCESZ];
-  assert(msz <= sizeof(b0));
-  memcpy(b0, m, msz); memset(b0 + msz, 0, sizeof(b0) - msz);
-  GC_SETIV(c->c, b0);
-  GC_ENCRYPT(c->c, 0, t, c->_b.tagsz);
+  poly1305_key pk;
+  poly1305_ctx pm;
+  octet b[POLY1305_KEYSZ + POLY1305_MASKSZ];
+
+  assert(SALSA20_NONCESZ <= sizeof(b));
+  memset(b, 0, SALSA20_NONCESZ - 4); STORE32(b + SALSA20_NONCESZ - 4, seq);
+  GC_SETIV(c->c, b); GC_ENCRYPT(c->c, 0, b, sizeof(b));
+  poly1305_keyinit(&pk, b, POLY1305_KEYSZ);
+  poly1305_macinit(&pm, &pk, b + POLY1305_KEYSZ);
+  if (msz) poly1305_hash(&pm, m, msz);
+  poly1305_done(&pm, t);
   return (0);
 }
 
 static int naclbox_chalvrf(bulkchal *bc, const void *m, size_t msz,
-                          const void *t)
+                          uint32 seq, const void *t)
 {
   naclbox_chal *c = (naclbox_chal *)bc;
-  octet b0[SALSA20_NONCESZ], b1[16];
-  assert(msz <= sizeof(b0)); assert(c->_b.tagsz <= sizeof(b1));
-  memcpy(b0, m, msz); memset(b0 + msz, 0, sizeof(b0) - msz);
-  GC_SETIV(c->c, b0);
-  GC_ENCRYPT(c->c, 0, b1, c->_b.tagsz);
-  return (ct_memeq(t, b1, c->_b.tagsz) ? 0 : -1);
+  poly1305_key pk;
+  poly1305_ctx pm;
+  octet b[POLY1305_KEYSZ + POLY1305_MASKSZ];
+
+  assert(SALSA20_NONCESZ <= sizeof(b));
+  memset(b, 0, SALSA20_NONCESZ - 4); STORE32(b + SALSA20_NONCESZ - 4, seq);
+  GC_SETIV(c->c, b); GC_ENCRYPT(c->c, 0, b, sizeof(b));
+  poly1305_keyinit(&pk, b, POLY1305_KEYSZ);
+  poly1305_macinit(&pm, &pk, b + POLY1305_KEYSZ);
+  if (msz) poly1305_hash(&pm, m, msz);
+  assert(POLY1305_TAGSZ <= sizeof(b)); poly1305_done(&pm, b);
+  return (ct_memeq(t, b, POLY1305_TAGSZ) ? 0 : -1);
 }
 
 static void naclbox_freechal(bulkchal *bc)
@@ -960,7 +1039,7 @@ static void naclbox_freectx(bulkctx *bbc)
   naclbox_ctx *bc = (naclbox_ctx *)bbc;
   int i;
 
-  for (i = 0; i < NDIR; i++) GC_DESTROY(bc->d[i].c);
+  for (i = 0; i < NDIR; i++) { if (bc->d[i].c) GC_DESTROY(bc->d[i].c); }
   DESTROY(bc);
 }
 
@@ -975,6 +1054,8 @@ static int naclbox_encrypt(bulkctx *bbc, unsigned ty,
   size_t sz = BLEFT(b);
   octet *qmac, *qseq, *qpk;
 
+  assert(c);
+
   /* --- Determine the ciphertext layout --- */
 
   if (buf_ensure(bb, POLY1305_TAGSZ + SEQSZ + sz)) return (0);
@@ -1022,6 +1103,8 @@ static int naclbox_decrypt(bulkctx *bbc, unsigned ty,
   size_t sz;
   octet *q = BCUR(bb);
 
+  assert(c);
+
   /* --- Break up the packet into its components --- */
 
   if (psz < SEQSZ + POLY1305_TAGSZ) {
index b463823..68d7f04 100644 (file)
 
 /*----- Static variables --------------------------------------------------*/
 
-static bulkchal *bulk;
+static bulkchal *bchal;
 static uint32 oseq;
 static seqwin iseq;
 
-/*----- Main code ---------------------------------------------------------*/
+/*----- Challenges --------------------------------------------------------*/
 
 /* --- @c_genkey@ --- *
  *
@@ -46,72 +46,84 @@ static seqwin iseq;
 
 static void c_genkey(void)
 {
-  if (bulk && bulk->ops == master->algs.bulk->ops && oseq < 0x07ffffff)
-    return;
-  if (bulk) bulk->ops->freechal(bulk);
-  bulk = master->algs.bulk->ops->genchal(master->algs.bulk);
-  bulk->ops = master->algs.bulk->ops;
+  bulkalgs *bulk = master->algs.bulk;
+  if (bchal && bchal->ops == bulk->ops && oseq < 0x07ffffff) return;
+  if (bchal) bchal->ops->freechal(bchal);
+  bchal = bulk->ops->genchal(bulk);
+  bchal->ops = bulk->ops;
   oseq = 0;
   seq_reset(&iseq);
 }
 
 /* --- @c_new@ --- *
  *
- * Arguments:  @buf *b@ = where to put the challenge
+ * Arguments:  @const void *m@ = pointer to associated message, or null
+ *             @size_t msz@ = length of associated message
+ *             @buf *b@ = where to put the challenge
  *
  * Returns:    Zero if OK, nonzero on error.
  *
  * Use:                Issues a new challenge.
  */
 
-int c_new(buf *b)
+int c_new(const void *m, size_t msz, buf *b)
 {
-  octet *p;
+  const octet *p;
+  octet *t;
+  int rc;
 
   c_genkey();
   p = BCUR(b);
-  if (buf_putu32(b, oseq++) || !buf_get(b, bulk->tagsz)) return (-1);
-  if (bulk->ops->chaltag(bulk, p, 4, p + 4)) return (-1);
+  if (buf_putu32(b, oseq) || (t = buf_get(b, bchal->tagsz)) == 0)
+    { rc = -1; goto done; }
+  if (bchal->ops->chaltag(bchal, m, msz, oseq, t)) { rc = -1; goto done; }
   IF_TRACING(T_CHAL, {
-    trace(T_CHAL, "chal: issuing challenge %lu", (unsigned long)(oseq - 1));
+    trace(T_CHAL, "chal: issuing challenge %lu", (unsigned long)oseq);
+    if (msz) trace_block(T_CRYPTO, "chal: message block", m, msz);
     trace_block(T_CRYPTO, "chal: challenge block", p, BCUR(b) - p);
   })
-  return (0);
+  rc = 0;
+done:
+  oseq++;
+  return (rc);
 }
 
 /* --- @c_check@ --- *
  *
- * Arguments:  @buf *b@ = where to find the challenge
+ * Arguments:  @const void *m@ = pointer to associated message, or null
+ *             @size_t msz@ = length of associated message
+ *             @buf *b@ = where to find the challenge
  *
  * Returns:    Zero if OK, nonzero if it didn't work.
  *
  * Use:                Checks a challenge.  On failure, the buffer is broken.
  */
 
-int c_check(buf *b)
+int c_check(const void *m, size_t msz, buf *b)
 {
-  const octet *p;
-  size_t sz;
+  const octet *p, *t;
   uint32 seq;
 
-  if (!bulk) {
+  if (!bchal) {
     a_warn("CHAL", "impossible-challenge", A_END);
     goto fail;
   }
-  sz = 4 + bulk->tagsz;
-  if ((p = buf_get(b, sz)) == 0) {
+  p = BCUR(b);
+  if (buf_getu32(b, &seq) || (t = buf_get(b, bchal->tagsz)) == 0) {
     a_warn("CHAL", "invalid-challenge", A_END);
     goto fail;
   }
-  IF_TRACING(T_CHAL, trace_block(T_CRYPTO, "chal: check challenge", p, sz); )
-  if (bulk->ops->chalvrf(bulk, p, 4, p + 4)) {
+  IF_TRACING(T_CHAL, {
+    trace(T_CHAL, "chal: checking challenge, seq = %lu", (unsigned long)seq);
+    if (msz) trace_block(T_CRYPTO, "chal: message block", m, msz);
+    trace_block(T_CRYPTO, "chal: check challenge", p, BCUR(b) - p);
+  })
+  if (bchal->ops->chalvrf(bchal, m, msz, seq, t)) {
     a_warn("CHAL", "incorrect-tag", A_END);
     goto fail;
   }
-  seq = LOAD32(p);
-  if (seq_check(&iseq, seq, "CHAL"))
-    goto fail;
-  T( trace(T_CHAL, "chal: checked challenge %lu", (unsigned long)seq); )
+  if (seq_check(&iseq, seq, "CHAL")) goto fail;
+  T( trace(T_CHAL, "chal: challenge ok"); )
   return (0);
 
 fail:
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 -------------------------------------------------*/
index 51a13d7..7386408 100644 (file)
@@ -387,6 +387,7 @@ founddh:
   kd->tag = xstrdup(t.buf);
   kd->ref = 1;
   kd->kn = 0;
+  kd->id = k->id;
   kd->t_exp = k->exp;
 
   IF_TRACING(T_KEYMGMT, {
@@ -616,6 +617,41 @@ kdata *km_findpriv(const char *tag)
   else return (kh_find(&priv, tag ? tag : "tripe-dh", 1));
 }
 
+/* --- @km_findpubbyid@, @km_findprivbyid@ --- *
+ *
+ * Arguments:  @uint32 id@ = key id to load
+ *
+ * Returns:    Pointer to the kdata object if successful, or null on error.
+ *
+ * Use:                Fetches a public or private key from the keyring given its
+ *             numeric id.
+ */
+
+static kdata *findbyid(keyhalf *kh, uint32 id)
+{
+  key *k;
+  kdata *kd;
+
+  k = key_byid(kh->kf, id); if (!k) goto notfound;
+  kd = kh_find(kh, k->tag, 1); if (!kd) goto notfound;
+  if (kd->id != id) { km_unref(kd); goto notfound; }
+  return (kd);
+
+notfound:
+  a_warn("KX", "%s-keyring", kh->kind, "%s", kh->kr,
+        "unknown-key-id", "0x%08lx", (unsigned long)id,
+        A_END);
+  return (0);
+}
+
+kdata *km_findpubbyid(uint32 id) { return (findbyid(&pub, id)); }
+
+kdata *km_findprivbyid(uint32 id)
+{
+  if (id == master->id) { km_ref(master); return (master); }
+  else return findbyid(&priv, id);
+}
+
 /* --- @km_tag@ --- *
  *
  * Arguments:  @kdata *kd@ - pointer to the kdata object
index 9429fa4..a0c4577 100644 (file)
@@ -169,95 +169,33 @@ void ks_drop(keyset *ks)
   DESTROY(ks);
 }
 
-/* --- @ks_derivekey@ --- *
- *
- * Arguments:  @octet *k@ = pointer to an output buffer of at least
- *                     @MAXHASHSZ@ bytes
- *             @size_t ksz@ = actual size wanted (for tracing)
- *             @const struct rawkey *rk@ = a raw key, as passed into
- *                     @genkeys@
- *             @int dir@ = direction for the key (@DIR_IN@ or @DIR_OUT@)
- *             @const char *what@ = label for the key (input to derivation)
- *
- * Returns:    ---
- *
- * Use:                Derives a session key, for use on incoming or outgoing data.
- *             This function is part of a private protocol between @ks_gen@
- *             and the bulk crypto transform @genkeys@ operation.
- */
-
-struct rawkey {
-  const gchash *hc;
-  const octet *k;
-  size_t x, y, z;
-};
-
-void ks_derivekey(octet *k, size_t ksz, const struct rawkey *rk,
-                 int dir, const char *what)
-{
-  const gchash *hc = rk->hc;
-  ghash *h;
-
-  assert(ksz <= hc->hashsz);
-  assert(hc->hashsz <= MAXHASHSZ);
-  h = GH_INIT(hc);
-  GH_HASH(h, "tripe-", 6); GH_HASH(h, what, strlen(what) + 1);
-  switch (dir) {
-    case DIR_IN:
-      GH_HASH(h, rk->k, rk->x);
-      GH_HASH(h, rk->k + rk->x, rk->y - rk->x);
-      break;
-    case DIR_OUT:
-      GH_HASH(h, rk->k + rk->x, rk->y - rk->x);
-      GH_HASH(h, rk->k, rk->x);
-      break;
-    default:
-      abort();
-  }
-  GH_HASH(h, rk->k + rk->y, rk->z - rk->y);
-  GH_DONE(h, k);
-  GH_DESTROY(h);
-  IF_TRACING(T_KEYSET, { IF_TRACING(T_CRYPTO, {
-    char _buf[32];
-    sprintf(_buf, "crypto: %s key %s", dir ? "outgoing" : "incoming", what);
-    trace_block(T_CRYPTO, _buf, k, ksz);
-  }) })
-}
-
 /* --- @ks_gen@ --- *
  *
- * Arguments:  @const void *k@ = pointer to key material
- *             @size_t x, y, z@ = offsets into key material (see below)
+ * Arguments:  @deriveargs *a@ = key derivation parameters (modified)
  *             @peer *p@ = pointer to peer information
  *
  * Returns:    A pointer to the new keyset.
  *
- * Use:                Derives a new keyset from the given key material.  The
- *             offsets @x@, @y@ and @z@ separate the key material into three
- *             parts.  Between the @k@ and @k + x@ is `my' contribution to
- *             the key material; between @k + x@ and @k + y@ is `your'
- *             contribution; and between @k + y@ and @k + z@ is a shared
- *             value we made together.  These are used to construct two
- *             collections of symmetric keys: one for outgoing messages, the
- *             other for incoming messages.
+ * Use:                Derives a new keyset from the given key material.  This will
+ *             set the @what@, @f@, and @hc@ members in @*a@; other members
+ *             must be filled in by the caller.
  *
  *             The new key is marked so that it won't be selected for output
  *             by @ksl_encrypt@.  You can still encrypt data with it by
  *             calling @ks_encrypt@ directly.
  */
 
-keyset *ks_gen(const void *k, size_t x, size_t y, size_t z, peer *p)
+keyset *ks_gen(deriveargs *a, peer *p)
 {
   keyset *ks = CREATE(keyset);
   time_t now = time(0);
   const algswitch *algs = &p->kx.kpriv->algs;
-  struct rawkey rk;
   T( static unsigned seq = 0; )
 
   T( trace(T_KEYSET, "keyset: adding new keyset %u", seq); )
 
-  rk.hc = algs->h; rk.k = k; rk.x = x; rk.y = y; rk.z = z;
-  ks->bulk = algs->bulk->ops->genkeys(algs->bulk, &rk);
+  a->what = "tripe-"; a->f = DF_IN | DF_OUT; a->hc = algs->h;
+  ks->bulk = algs->bulk->ops->genkeys(algs->bulk, a);
   ks->bulk->ops = algs->bulk->ops;
 
   T( ks->seq = seq++; )
index 9462921..a8099e4 100644 (file)
@@ -366,7 +366,7 @@ static void p_read(int fd, unsigned mode, void *v)
     })
     buf_init(&b, buf_i, n);
     buf_getbyte(&b);
-    if (c_check(&b) || BLEFT(&b)) {
+    if (c_check(0, 0, &b) || BLEFT(&b)) {
       a_warn("PEER", "-", "invalid-greeting", A_END);
       return;
     }
@@ -427,9 +427,8 @@ static void p_read(int fd, unsigned mode, void *v)
       }
       break;
     case MSG_KEYEXCH:
-      if (!p) goto unexp;
-      p_rxupdstats(p, n);
-      kx_message(&p->kx, ch & MSG_TYPEMASK, &b);
+      if (p) p_rxupdstats(p, n);
+      if (kx_message(p ? &p->kx : 0, &a, ch & MSG_TYPEMASK, &b)) goto unexp;
       break;
     case MSG_MISC:
       switch (ch & MSG_TYPEMASK) {
@@ -469,6 +468,16 @@ static void p_read(int fd, unsigned mode, void *v)
            p_ponged(p, MISC_EPONG, &bb);
          }
          break;
+       case MISC_BYE:
+         buf_init(&bb, buf_t, sizeof(buf_t));
+         if (p_decrypt(&p, &a, n, ch, &b, &bb)) return;
+         if (!(p->spec.f&PSF_EPHEM)) return;
+         if (BOK(&bb)) {
+           buf_flip(&bb);
+           if (BSZ(&bb)) return;
+           p_destroy(p, 0);
+         }
+         break;
       }
       break;
     default:
@@ -503,6 +512,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
@@ -931,6 +964,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;
@@ -946,7 +980,7 @@ peer *p_create(peerspec *spec)
   T( trace(T_TUNNEL, "peer: attached interface %s to peer `%s'",
           p->ifname, p_name(p)); )
   p_setkatimer(p);
-  if (kx_init(&p->kx, p, &p->ks, p->spec.f & PSF_KXMASK))
+  if (kx_setup(&p->kx, p, &p->ks, p->spec.f & PSF_KXMASK))
     goto tidy_4;
   a_notify("ADD",
           "?PEER", p,
@@ -1057,17 +1091,28 @@ peer *p_find(const char *name)
 /* --- @p_destroy@ --- *
  *
  * Arguments:  @peer *p@ = pointer to a peer
+ *             @int bye@ = say goodbye to the peer?
  *
  * Returns:    ---
  *
  * Use:                Destroys a peer.
  */
 
-void p_destroy(peer *p)
+void p_destroy(peer *p, int bye)
 {
   ping *pg, *ppg;
+  buf *b, bb;
 
   T( trace(T_PEER, "peer: destroying peer `%s'", p->spec.name); )
+
+  if (bye && (p->spec.f&PSF_EPHEM)) {
+    b = p_txstart(p, MSG_MISC | MISC_BYE);
+    buf_init(&bb, buf_t, sizeof(buf_t));
+    assert(BOK(&bb)); buf_flip(&bb);
+    p_encrypt(p, MSG_MISC | MISC_BYE, &bb, b);
+    p_txend(p);
+  }
+
   a_notify("KILL", "%s", p->spec.name, A_END);
   ksl_free(&p->ks);
   kx_free(&p->kx);
@@ -1075,6 +1120,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 f95541a..703e448 100644 (file)
@@ -82,6 +82,180 @@ int seq_check(seqwin *s, uint32 q, const char *service)
   return (0);
 }
 
+/*----- Rate limiting -----------------------------------------------------*/
+
+/* --- @ratelim_init@ --- *
+ *
+ * Arguments:  @ratelim *r@ = rate-limiting state to fill in
+ *             @unsigned persec@ = credit to accumulate per second
+ *             @unsigned max@ = maximum credit to retain
+ *
+ * Returns:    ---
+ *
+ * Use:                Initialize a rate-limiting state.
+ */
+
+void ratelim_init(ratelim *r, unsigned persec, unsigned max)
+{
+  r->n = r->max = max;
+  r->persec = persec;
+  gettimeofday(&r->when, 0);
+}
+
+/* --- @ratelim_withdraw@ --- *
+ *
+ * Arguments:  @ratelim *r@ = rate-limiting state
+ *             @unsigned n@ = credit to withdraw
+ *
+ * Returns:    Zero if successful; @-1@ if there is unsufficient credit
+ *
+ * Use:                Updates the state with any accumulated credit.  Then, if
+ *             there there are more than @n@ credits available, withdraw @n@
+ *             and return successfully; otherwise, report failure.
+ */
+
+int ratelim_withdraw(ratelim *r, unsigned n)
+{
+  struct timeval now, delta;
+  unsigned long d;
+
+  gettimeofday(&now, 0);
+  TV_SUB(&delta, &now, &r->when);
+  d = (unsigned long)r->persec*delta.tv_sec +
+    (unsigned long)r->persec*delta.tv_usec/MILLION;
+  if (d < r->max - r->n) r->n += d;
+  else r->n = r->max;
+  r->when = now;
+
+  if (n > r->n) return (-1);
+  else { r->n -= n; return (0); }
+}
+
+/*----- Crypto ------------------------------------------------------------*/
+
+/* --- @ies_encrypt@ --- *
+ *
+ * Arguments:  @kdata *kpub@ = recipient's public key
+ *             @unsigned ty@ = message type octet
+ *             @buf *b@ = input message buffer
+ *             @buf *bb@ = output buffer for the ciphertext
+ *
+ * Returns:    On error, returns a @KSERR_...@ code or breaks the buffer;
+ *             on success, returns zero and the buffer is good.
+ *
+ * Use:                Encrypts a message for a recipient, given their public key.
+ *             This does not (by itself) provide forward secrecy or sender
+ *             authenticity.  The ciphertext is self-delimiting (unlike
+ *             @ks_encrypt@).
+ */
+
+int ies_encrypt(kdata *kpub, unsigned ty, buf *b, buf *bb)
+{
+  dhgrp *g = kpub->grp;
+  dhsc *u = g->ops->randsc(g);
+  dhge *U = g->ops->mul(g, u, 0), *Z = g->ops->mul(g, u, kpub->K);
+  bulkalgs *algs = kpub->algs.bulk;
+  octet *len;
+  bulkctx *bulk;
+  deriveargs a;
+  size_t n;
+  buf bk;
+  int rc = 0;
+
+  IF_TRACING(T_CRYPTO, {
+    trace(T_CRYPTO,
+         "crypto: encrypting IES message (type 0x%02x) for recipient `%s'",
+         ty, kpub->tag);
+    trace_block(T_CRYPTO, "crypto: plaintext message", BCUR(b), BLEFT(b));
+  })
+
+  a.hc = kpub->algs.h; a.what = "tripe:ecies-"; a.f = DF_OUT;
+  buf_init(&bk, buf_u, sizeof(buf_u)); a.k = BBASE(&bk);
+  g->ops->stge(g, &bk, U, DHFMT_HASH); a.x = a.y = BLEN(&bk);
+  g->ops->stge(g, &bk, Z, DHFMT_HASH); a.z = BLEN(&bk);
+  assert(BOK(&bk));
+  T( trace_block(T_CRYPTO, "crypto: KEM clue", a.k, a.x);
+     trace_block(T_CRYPTO, "crypto: shared secret", a.k + a.y, a.z - a.y); )
+
+  len = BCUR(bb); buf_get(bb, 2);
+  bulk = algs->ops->genkeys(algs, &a);
+  bulk->ops = algs->ops;
+  g->ops->stge(g, bb, U, DHFMT_VAR); if (BBAD(bb)) goto end;
+  rc = bulk->ops->encrypt(bulk, ty, b, bb, 0);
+  if (rc || BBAD(bb)) goto end;
+  n = BCUR(bb) - len - 2; assert(n <= MASK16); STORE16(len, n);
+
+end:
+  bulk->ops->freectx(bulk);
+  g->ops->freesc(g, u);
+  g->ops->freege(g, U);
+  g->ops->freege(g, Z);
+  return (rc);
+}
+
+/* --- @ies_decrypt@ --- *
+ *
+ * Arguments:  @kdata *kpub@ = private key key
+ *             @unsigned ty@ = message type octet
+ *             @buf *b@ = input ciphertext buffer
+ *             @buf *bb@ = output buffer for the message
+ *
+ * Returns:    On error, returns a @KSERR_...@ code; on success, returns
+ *             zero and the buffer is good.
+ *
+ * Use:                Decrypts a message encrypted using @ies_encrypt@, given our
+ *             private key.
+ */
+
+int ies_decrypt(kdata *kpriv, unsigned ty, buf *b, buf *bb)
+{
+  dhgrp *g = kpriv->grp;
+  bulkalgs *algs = kpriv->algs.bulk;
+  bulkctx *bulk = 0;
+  T( const octet *m; )
+  dhge *U = 0, *Z = 0;
+  deriveargs a;
+  uint32 seq;
+  buf bk, bc;
+  int rc;
+
+  IF_TRACING(T_CRYPTO, {
+    trace(T_CRYPTO,
+         "crypto: decrypting IES message (type 0x%02x) to recipient `%s'",
+         ty, kpriv->tag);
+    trace_block(T_CRYPTO, "crypto: ciphertext message", BCUR(b), BLEFT(b));
+  })
+
+  if (buf_getbuf16(b, &bc) ||
+      (U = g->ops->ldge(g, &bc, DHFMT_VAR)) == 0 ||
+      g->ops->checkge(g, U))
+    { rc = KSERR_MALFORMED; goto end; }
+  Z = g->ops->mul(g, kpriv->k, U);
+
+  a.hc = kpriv->algs.h; a.what = "tripe:ecies-"; a.f = DF_IN;
+  buf_init(&bk, buf_u, sizeof(buf_u)); a.k = BBASE(&bk); a.x = 0;
+  g->ops->stge(g, &bk, U, DHFMT_HASH); a.y = BLEN(&bk);
+  g->ops->stge(g, &bk, Z, DHFMT_HASH); a.z = BLEN(&bk);
+  T( trace_block(T_CRYPTO, "crypto: KEM clue", a.k + a.x, a.y - a.x);
+     trace_block(T_CRYPTO, "crypto: shared secret", a.k + a.y, a.z - a.y); )
+  assert(BOK(&bk));
+
+  bulk = algs->ops->genkeys(algs, &a);
+  bulk->ops = algs->ops;
+  T( m = BCUR(bb); )
+  rc = bulk->ops->decrypt(bulk, ty, &bc, bb, &seq);
+  if (rc) goto end;
+  if (seq) { rc = KSERR_SEQ; goto end; }
+  assert(BOK(bb));
+  T( trace_block(T_CRYPTO, "crypto: decrypted message", m, BCUR(bb) - m); )
+
+end:
+  if (bulk) bulk->ops->freectx(bulk);
+  g->ops->freege(g, U);
+  g->ops->freege(g, Z);
+  return (rc);
+}
+
 /*----- Random odds and sods ----------------------------------------------*/
 
 /* --- @timestr@ --- *
diff --git a/server/test.c b/server/test.c
new file mode 100644 (file)
index 0000000..152a6fe
--- /dev/null
@@ -0,0 +1,173 @@
+/* -*-c-*-
+ *
+ * Various unit-level tests
+ *
+ * (c) 2017 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of Trivial IP Encryption (TrIPE).
+ *
+ * TrIPE is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 3 of the License, or (at your
+ * option) any later version.
+ *
+ * TrIPE is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with TrIPE.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include "tripe.h"
+
+/*----- Data structures ---------------------------------------------------*/
+
+/*----- Global variables --------------------------------------------------*/
+
+sel_state sel;
+
+/*----- Static variables --------------------------------------------------*/
+
+static char **args;
+
+/*----- Main code ---------------------------------------------------------*/
+
+static void usage(FILE *fp)
+{
+  pquis(fp,
+"Usage: $ [-k FILE] [-t KEYTAG] [-T TRACE-OPTS] COMMAND [ARGS...]\n");
+}
+
+static void version(FILE *fp)
+  { pquis(fp, "$, TrIPE version " VERSION "\n"); }
+
+static void help(FILE *fp)
+{
+  version(fp); fputc('\n', fp);
+  usage(fp); fputc('\n', fp);
+  fputs("\
+Options in full:\n\
+\n\
+-h, --help             Show this help text.\n\
+-v, --version          Show version number.\n\
+-u, --usage            Show brief usage message.\n\
+\n\
+-k, --keyring=FILE     Get keys from FILE.\n\
+-t, --tag=KEYTAG       Use KEYTAG as master private key.\n\
+-T, --trace=TRACE-OPTS Turn on tracing options.\n\
+\n\
+Commands:\n\
+\n\
+ies-encrypt TY MESSAGE\n\
+ies-decrypt TY CIPHERTEXT\n\
+", fp);
+}
+
+static uint32 parseu32(const char *p)
+{
+  int e = errno;
+  unsigned long i;
+  char *q;
+
+  errno = 0;
+  i = strtoul(p, &q, 0);
+  while (*q && isspace((unsigned char)*q)) q++;
+  if (errno || *q || i > 0xffffffffu) die(2, "bad 32-bit integer `%s'", p);
+  errno = e;
+  return (i);
+}
+
+static const char *getarg(void)
+  { if (!*args) die(2, "missing argument"); else return (*args++); }
+
+static void lastarg(void)
+  { if (*args) die(2, "unexpected argument `%s'", *args); }
+
+int main(int argc, char *argv[])
+{
+  const char *kr = "keyring";
+  const char *tag = "tripe";
+  const char *arg;
+  codec *b64;
+  int i, err;
+  uint32 ty;
+  buf b, bb;
+  dstr d = DSTR_INIT;
+  unsigned f = 0;
+#define f_bogus 1u
+
+  ego(argv[0]);
+  for (;;) {
+    static const struct option opts[] = {
+      { "help",                0,              0,      'h' },
+      { "version",     0,              0,      'v' },
+      { "usage",       0,              0,      'u' },
+      { "keyring",     OPTF_ARGREQ,    0,      'k' },
+      { "tag",         OPTF_ARGREQ,    0,      't' },
+      { "trace",       OPTF_ARGREQ,    0,      'T' },
+      { 0,             0,              0,      0 }
+    };
+
+    i = mdwopt(argc, argv, "hvuk:t:T:", opts, 0, 0, 0); if (i < 0) break;
+    switch (i) {
+      case 'h': help(stdout); exit(0);
+      case 'v': version(stdout); exit(0);
+      case 'u': usage(stdout); exit(0);
+      case 'k': kr = optarg; break;
+      case 't': tag = optarg; break;
+      case 'T':
+       tr_flags = traceopt(tr_opts, optarg, tr_flags, 0);
+       break;
+      default: f |= f_bogus; break;
+    }
+  }
+  if (f&f_bogus) { usage(stderr); exit(2); }
+  args = argv + optind;
+
+  km_init(kr, kr, tag);
+  trace_on(stderr, tr_flags);
+
+  arg = getarg();
+  if (strcmp(arg, "ies-encrypt") == 0) {
+    arg = getarg(); ty = parseu32(arg);
+    arg = getarg(); buf_init(&b, (/*unconst*/ octet *)arg, strlen(arg));
+    buf_init(&bb, buf_t, sizeof(buf_t));
+    lastarg();
+    if ((err = ies_encrypt(master, ty, &b, &bb)) != 0)
+      die(3, "ies_encrypt failed: err = %d", err);
+    b64 = base64_class.encoder(0, "\n", 72);
+    if ((err = b64->ops->code(b64, BBASE(&bb), BLEN(&bb), &d)) != 0 ||
+       (err = b64->ops->code(b64, 0, 0, &d)) != 0)
+      die(3, "base64 encoding failed: %s", codec_strerror(err));
+    b64->ops->destroy(b64);
+    DPUTC(&d, '\n');
+    fwrite(d.buf, 1, d.len, stdout);
+    dstr_destroy(&d);
+  } else if (strcmp(arg, "ies-decrypt") == 0) {
+    arg = getarg(); ty = parseu32(arg);
+    arg = getarg();
+    b64 = base64_class.decoder(CDCF_IGNSPC | CDCF_IGNNEWL);
+    if ((err = b64->ops->code(b64, arg, strlen(arg), &d)) != 0 ||
+       (err = b64->ops->code(b64, 0, 0, &d)) != 0)
+      die(3, "base64 decoding failed: %s", codec_strerror(err));
+    b64->ops->destroy(b64);
+    lastarg();
+    buf_init(&b, d.buf, d.len); buf_init(&bb, buf_t, sizeof(buf_t));
+    if ((err = ies_decrypt(master, ty, &b, &bb)) != 0)
+      die(3, "ies_decrypt failed: err = %d", err);
+    fwrite(BBASE(&bb), 1, BLEN(&bb), stdout);
+    dstr_destroy(&d);
+  } else
+    die(2, "unknown command `%s'", arg);
+
+  return (0);
+}
+
+/*----- That's all, folks -------------------------------------------------*/
index d217054..08bfb13 100644 (file)
@@ -751,4 +751,59 @@ WITH_TRIPE(, [
 
 AT_CLEANUP
 
+###--------------------------------------------------------------------------
+### Knock and bye.
+
+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 -ephemeral 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 81dd570..f066ae6 100644 (file)
@@ -361,6 +361,21 @@ Run the command in the background, using the given
 Don't send an immediate challenge to the peer; instead, wait until it
 sends us something before responding.
 .TP
+.B "\-ephemeral"
+The association with the peer is not intended to persist indefinitely.
+If a peer marked as ephemeral is killed, or the
+.BR tripe (8)
+daemon is shut down, send a
+.B bye
+packet to the peer so that it forgets about us; if a peer marked as
+ephemeral sends us a
+.B bye
+packet then it is killed (but in this case no further
+.B bye
+packet is sent).  Peers not marked as ephemeral exhibit neither of these
+behaviours; each peer must have the other marked as ephemeral for the
+association to be fully torn down if either end kills the other.
+.TP
 .BI "\-keepalive " time
 Send a no-op packet if we've not sent a packet to the peer in the last
 .I time
@@ -382,6 +397,26 @@ 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.  This option implies
+.BR \-ephemeral .
+.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
@@ -390,7 +425,8 @@ peers, however, it will attempt to decrypt the packet using their keys,
 and if one succeeds, the server will update its idea of the peer's
 address and emit an
 .B NEWADDR
-notification.
+notification.  This option implies
+.BR \-ephemeral .
 .TP
 .BI "\-priv " tag
 Use the private key
@@ -605,6 +641,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
@@ -643,6 +689,14 @@ or
 .B nil
 depending on whether or not (respectively) the peer is expected to
 change its address unpredictably.
+.TP
+.B ephemeral
+Either
+.B t
+or
+.B nil
+depending on whether the association with the peer is expected to be
+temporary or persistent (respectively).
 .RE
 .SP
 .BI "PING \fR[" options "\fR] " peer
@@ -1223,6 +1277,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
@@ -1439,6 +1499,11 @@ A key named
 .I tag
 couldn't be found in the keyring.
 .SP
+.BI "KEYMGMT " which "-keyring " file " unknown-key-id 0x" keyid
+A key with the given
+.I keyid
+(in hex) was requested but not found.
+.SP
 .BI "KEYMGMT " which "-keyring " file " line " line " " message
 The contents of the keyring file are invalid.  There may well be a bug
 in the
@@ -1460,8 +1525,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
@@ -1576,6 +1644,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 565c83d..b843885 100644 (file)
@@ -308,6 +308,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 2940399..10a03f5 100644 (file)
@@ -188,6 +188,16 @@ enum {
   DHFMT_VAR                   /* Variable-width-format, mostly a bad idea */
 };
 
+typedef struct deriveargs {
+  const char *what;                    /* Operation name (hashed) */
+  unsigned f;                          /* Flags */
+#define DF_IN 1u                       /*   Make incoming key */
+#define DF_OUT 2u                      /*   Make outgoing key */
+  const gchash *hc;                    /* Hash class */
+  const octet *k;                      /* Pointer to contributions */
+  size_t x, y, z;                      /* Markers in contributions */
+} deriveargs;
+
 typedef struct bulkalgs {
   const struct bulkops *ops;
 } bulkalgs;
@@ -201,8 +211,6 @@ typedef struct bulkchal {
   size_t tagsz;
 } bulkchal;
 
-struct rawkey;
-
 typedef struct dhops {
   const char *name;
 
@@ -332,9 +340,17 @@ typedef struct bulkops {
         * after which the keys must no longer be used.
         */
 
-  bulkctx *(*genkeys)(const bulkalgs */*a*/, const struct rawkey */*rk*/);
+  bulkctx *(*genkeys)(const bulkalgs */*a*/, const deriveargs */*a*/);
        /* Generate session keys and construct and return an appropriate
-        * context for using them, by calling @ks_derive@.
+        * context for using them.  The offsets @a->x@, @a->y@ and @a->z@
+        * separate the key material into three parts.  Between @a->k@ and
+        * @a->k + a->x@ is `my' contribution to the key material; between
+        * @a->k + a->x@ and @a->k + a->y@ is `your' contribution; and
+        * between @a->k + a->y@ and @a->k + a->z@ is a shared value we made
+        * together.  These are used to construct (up to) two collections of
+        * symmetric keys: one for outgoing messages, the other for incoming
+        * messages.  If @a->x == 0@ (or @a->y == a->x@) then my (or your)
+        * contribution is omitted.
         */
 
   bulkchal *(*genchal)(const bulkalgs */*a*/);
@@ -367,15 +383,16 @@ typedef struct bulkops {
        /* Release a bulk encryption context and the resources it holds. */
 
   int (*chaltag)(bulkchal */*bc*/, const void */*m*/, size_t /*msz*/,
-                void */*t*/);
-       /* Calculate a tag for the challenge in @m@, @msz@, and write it to
-        * @t@.  Return @-1@ on error, zero on success.
+                uint32 /*seq*/, void */*t*/);
+       /* Calculate a tag for the challenge in @m@, @msz@, with the sequence
+        * number @seq@, and write it to @t@.  Return @-1@ on error, zero on
+        * success.
         */
 
   int (*chalvrf)(bulkchal */*bc*/, const void */*m*/, size_t /*msz*/,
-                const void */*t*/);
-       /* Check the tag @t@ on @m@, @msz@: return zero if the tag is OK,
-        * nonzero if it's bad.
+                uint32 /*seq*/, const void */*t*/);
+       /* Check the tag @t@ on @m@, @msz@ and @seq@: return zero if the tag
+        * is OK, nonzero if it's bad.
         */
 
   void (*freechal)(bulkchal */*bc*/);
@@ -392,6 +409,7 @@ struct algswitch {
 struct kdata {
   unsigned ref;                                /* Reference counter */
   struct knode *kn;                    /* Pointer to cache entry */
+  uint32 id;                           /* The underlying key's id */
   char *tag;                           /* Full tag name of the key */
   dhgrp *grp;                          /* The group we work in */
   dhsc *k;                             /* The private key (or null) */
@@ -621,12 +639,14 @@ 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 */
   unsigned f;                          /* Flags for the peer */
 #define PSF_KXMASK 255u                        /*   Key-exchange flags to set */
 #define PSF_MOBILE 256u                        /*   Address may change rapidly */
+#define PSF_EPHEM 512u                 /*   Association is ephemeral */
 } peerspec;
 
 typedef struct peer_byname {
@@ -846,6 +866,19 @@ extern int km_reload(void);
 extern kdata *km_findpub(const char */*tag*/);
 extern kdata *km_findpriv(const char */*tag*/);
 
+/* --- @km_findpubbyid@, @km_findprivbyid@ --- *
+ *
+ * Arguments:  @uint32 id@ = key id to load
+ *
+ * Returns:    Pointer to the kdata object if successful, or null on error.
+ *
+ * Use:                Fetches a public or private key from the keyring given its
+ *             numeric id.
+ */
+
+extern kdata *km_findpubbyid(uint32 /*id*/);
+extern kdata *km_findprivbyid(uint32 /*id*/);
+
 /* --- @km_samealgsp@ --- *
  *
  * Arguments:  @const kdata *kdx, *kdy@ = two key data objects
@@ -908,16 +941,18 @@ extern 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.
  */
 
-extern void kx_message(keyexch */*kx*/, unsigned /*msg*/, buf */*b*/);
+extern int kx_message(keyexch */*kx*/, const addr */*a*/,
+                     unsigned /*msg*/, buf */*b*/);
 
 /* --- @kx_free@ --- *
  *
@@ -944,7 +979,7 @@ extern void kx_free(keyexch */*kx*/);
 
 extern void kx_newkeys(keyexch */*kx*/);
 
-/* --- @kx_init@ --- *
+/* --- @kx_setup@ --- *
  *
  * Arguments:  @keyexch *kx@ = pointer to key exchange context
  *             @peer *p@ = pointer to peer context
@@ -958,69 +993,51 @@ extern void kx_newkeys(keyexch */*kx*/);
  *             exchange.
  */
 
-extern int kx_init(keyexch */*kx*/, peer */*p*/,
-                  keyset **/*ks*/, unsigned /*f*/);
-
-/*----- Keysets and symmetric cryptography --------------------------------*/
+extern int kx_setup(keyexch */*kx*/, peer */*p*/,
+                   keyset **/*ks*/, unsigned /*f*/);
 
-/* --- @ks_drop@ --- *
+/* --- @kx_init@ --- *
  *
- * Arguments:  @keyset *ks@ = pointer to a keyset
+ * Arguments:  ---
  *
  * Returns:    ---
  *
- * Use:                Decrements a keyset's reference counter.  If the counter hits
- *             zero, the keyset is freed.
+ * Use:                Initializes the key-exchange logic.
  */
 
-extern void ks_drop(keyset */*ks*/);
+extern void kx_init(void);
 
-/* --- @ks_derivekey@ --- *
+/*----- Keysets and symmetric cryptography --------------------------------*/
+
+/* --- @ks_drop@ --- *
  *
- * Arguments:  @octet *k@ = pointer to an output buffer of at least
- *                     @MAXHASHSZ@ bytes
- *             @size_t ksz@ = actual size wanted (for tracing)
- *             @const struct rawkey *rk@ = a raw key, as passed into
- *                     @genkeys@
- *             @int dir@ = direction for the key (@DIR_IN@ or @DIR_OUT@)
- *             @const char *what@ = label for the key (input to derivation)
+ * Arguments:  @keyset *ks@ = pointer to a keyset
  *
  * Returns:    ---
  *
- * Use:                Derives a session key, for use on incoming or outgoing data.
- *             This function is part of a private protocol between @ks_gen@
- *             and the bulk crypto transform @genkeys@ operation.
+ * Use:                Decrements a keyset's reference counter.  If the counter hits
+ *             zero, the keyset is freed.
  */
 
-extern void ks_derivekey(octet */*k*/, size_t /*ksz*/,
-                        const struct rawkey */*rk*/,
-                        int /*dir*/, const char */*what*/);
+extern void ks_drop(keyset */*ks*/);
 
 /* --- @ks_gen@ --- *
  *
- * Arguments:  @const void *k@ = pointer to key material
- *             @size_t x, y, z@ = offsets into key material (see below)
+ * Arguments:  @deriveargs *a@ = key derivation parameters (modified)
  *             @peer *p@ = pointer to peer information
  *
  * Returns:    A pointer to the new keyset.
  *
- * Use:                Derives a new keyset from the given key material.  The
- *             offsets @x@, @y@ and @z@ separate the key material into three
- *             parts.  Between the @k@ and @k + x@ is `my' contribution to
- *             the key material; between @k + x@ and @k + y@ is `your'
- *             contribution; and between @k + y@ and @k + z@ is a shared
- *             value we made together.  These are used to construct two
- *             collections of symmetric keys: one for outgoing messages, the
- *             other for incoming messages.
+ * Use:                Derives a new keyset from the given key material.  This will
+ *             set the @what@, @f@, and @hc@ members in @*a@; other members
+ *             must be filled in by the caller.
  *
  *             The new key is marked so that it won't be selected for output
  *             by @ksl_encrypt@.  You can still encrypt data with it by
  *             calling @ks_encrypt@ directly.
  */
 
-extern keyset *ks_gen(const void */*k*/,
-                     size_t /*x*/, size_t /*y*/, size_t /*z*/,
-                     peer */*p*/);
+extern keyset *ks_gen(deriveargs */*a*/, peer */*p*/);
 
 /* --- @ks_activate@ --- *
  *
@@ -1159,25 +1176,29 @@ extern int ksl_decrypt(keyset **/*ksroot*/, unsigned /*ty*/,
 
 /* --- @c_new@ --- *
  *
- * Arguments:  @buf *b@ = where to put the challenge
+ * Arguments:  @const void *m@ = pointer to associated message, or null
+ *             @size_t msz@ = length of associated message
+ *             @buf *b@ = where to put the challenge
  *
  * Returns:    Zero if OK, nonzero on error.
  *
  * Use:                Issues a new challenge.
  */
 
-extern int c_new(buf */*b*/);
+extern int c_new(const void */*m*/, size_t /*msz*/, buf */*b*/);
 
 /* --- @c_check@ --- *
  *
- * Arguments:  @buf *b@ = where to find the challenge
+ * Arguments:  @const void *m@ = pointer to associated message, or null
+ *             @size_t msz@ = length of associated message
+ *             @buf *b@ = where to find the challenge
  *
  * Returns:    Zero if OK, nonzero if it didn't work.
  *
  * Use:                Checks a challenge.  On failure, the buffer is broken.
  */
 
-extern int c_check(buf */*b*/);
+extern int c_check(const void */*m*/, size_t /*msz*/, buf */*b*/);
 
 /*----- Administration interface ------------------------------------------*/
 
@@ -1471,6 +1492,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
@@ -1694,13 +1729,14 @@ extern peer *p_find(const char */*name*/);
 /* --- @p_destroy@ --- *
  *
  * Arguments:  @peer *p@ = pointer to a peer
+ *             @int bye@ = say goodbye to the peer?
  *
  * Returns:    ---
  *
  * Use:                Destroys a peer.
  */
 
-extern void p_destroy(peer */*p*/);
+extern void p_destroy(peer */*p*/, int /*bye*/);
 
 /* --- @FOREACH_PEER@ --- *
  *
@@ -1833,6 +1869,75 @@ extern void seq_reset(seqwin */*s*/);
 
 extern int seq_check(seqwin */*s*/, uint32 /*q*/, const char */*service*/);
 
+typedef struct ratelim {
+  unsigned n, max, persec;
+  struct timeval when;
+} ratelim;
+
+/* --- @ratelim_init@ --- *
+ *
+ * Arguments:  @ratelim *r@ = rate-limiting state to fill in
+ *             @unsigned persec@ = credit to accumulate per second
+ *             @unsigned max@ = maximum credit to retain
+ *
+ * Returns:    ---
+ *
+ * Use:                Initialize a rate-limiting state.
+ */
+
+extern void ratelim_init(ratelim */*r*/,
+                        unsigned /*persec*/, unsigned /*max*/);
+
+/* --- @ratelim_withdraw@ --- *
+ *
+ * Arguments:  @ratelim *r@ = rate-limiting state
+ *             @unsigned n@ = credit to withdraw
+ *
+ * Returns:    Zero if successful; @-1@ if there is unsufficient credit
+ *
+ * Use:                Updates the state with any accumulated credit.  Then, if
+ *             there there are more than @n@ credits available, withdraw @n@
+ *             and return successfully; otherwise, report failure.
+ */
+
+extern int ratelim_withdraw(ratelim */*r*/, unsigned /*n*/);
+
+/* --- @ies_encrypt@ --- *
+ *
+ * Arguments:  @kdata *kpub@ = recipient's public key
+ *             @unsigned ty@ = message type octet
+ *             @buf *b@ = input message buffer
+ *             @buf *bb@ = output buffer for the ciphertext
+ *
+ * Returns:    On error, returns a @KSERR_...@ code or breaks the buffer;
+ *             on success, returns zero and the buffer is good.
+ *
+ * Use:                Encrypts a message for a recipient, given their public key.
+ *             This does not (by itself) provide forward secrecy or sender
+ *             authenticity.  The ciphertext is self-delimiting (unlike
+ *             @ks_encrypt@).
+ */
+
+extern int ies_encrypt(kdata */*kpub*/, unsigned /*ty*/,
+                      buf */*b*/, buf */*bb*/);
+
+/* --- @ies_decrypt@ --- *
+ *
+ * Arguments:  @kdata *kpub@ = private key key
+ *             @unsigned ty@ = message type octet
+ *             @buf *b@ = input ciphertext buffer
+ *             @buf *bb@ = output buffer for the message
+ *
+ * Returns:    On error, returns a @KSERR_...@ code; on success, returns
+ *             zero and the buffer is good.
+ *
+ * Use:                Decrypts a message encrypted using @ies_encrypt@, given our
+ *             private key.
+ */
+
+extern int ies_decrypt(kdata */*kpriv*/, unsigned /*ty*/,
+                      buf */*b*/, buf */*bb*/);
+
 /*----- That's all, folks -------------------------------------------------*/
 
 #ifdef __cplusplus
index 86945ce..1e4e2d2 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
@@ -439,6 +467,7 @@ The service will submit the command
 .RB [ \-priv
 .IR tag ]
 .RB [ \-mobile ]
+.RB [ \-ephemeral ]
 .RB [ \-tunnel
 .IR driver ]
 .I address
@@ -505,6 +534,19 @@ to the
 .B tunnel
 key.
 .hP \*o
+The option
+.B \-ephemeral
+is provided if the peer's database record assigns the
+.B ephemeral
+key one of the values
+.BR t ,
+.BR true ,
+.BR y ,
+.BR yes,
+or
+.BR on ;
+or if it is absent.
+.hP \*o
 The
 .I address
 is the value assigned to the
@@ -724,6 +766,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 b6ec4b8..99dbd8a 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:
@@ -716,11 +717,12 @@ def disownpeer(peer):
     T.Coroutine(run_ifupdown, name = 'ifdown %s' % peer.name) \
         .switch('ifdown', peer)
 
-def addpeer(peer, addr):
+def addpeer(peer, addr, ephemp):
   """
   Process a connect request from a new peer PEER on address ADDR.
 
-  Any existing peer with this name is disconnected from the server.
+  Any existing peer with this name is disconnected from the server.  EPHEMP
+  is the default ephemeral-ness state for the new peer.
   """
   if peer.name in S.list():
     S.kill(peer.name)
@@ -731,7 +733,10 @@ 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),
+          ephemeral = peer.get('ephemeral', filter = boolean,
+                               default = ephemp),
           *addr)
   except T.TripeError, exc:
     raise T.TripeJobError(*exc.args)
@@ -760,6 +765,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:], True)
 
 ###--------------------------------------------------------------------------
 ### Command implementation.
@@ -794,7 +816,7 @@ def cmd_active(name):
   addr = peer.get('peer')
   if addr == 'PASSIVE':
     raise T.TripeJobError('passive-peer', name)
-  addpeer(peer, M.split(addr, quotep = True)[0])
+  addpeer(peer, M.split(addr, quotep = True)[0], True)
 
 def cmd_listactive():
   """
@@ -856,7 +878,7 @@ def cmd_passive(*args):
     addr = cr.parent.switch()
     if addr is None:
       raise T.TripeJobError('connect-timeout')
-    addpeer(peer, addr)
+    addpeer(peer, addr, True)
   finally:
     del chalmap[chal]
 
@@ -892,7 +914,7 @@ def setup():
     for name in M.split(autos)[0]:
       try:
         peer = Peer(name, cdb)
-        addpeer(peer, M.split(peer.get('peer'), quotep = True)[0])
+        addpeer(peer, M.split(peer.get('peer'), quotep = True)[0], False)
       except T.TripeJobError, err:
         S.warn('connect', 'auto-add-failed', name, *err.args)
 
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 f950aef..5c6d7ba 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 } }
     }
   },
 
@@ -364,6 +408,8 @@ local PKTINFO = {
              dissect = { dissect_misc_ciphertext } },
       [5] = { label = "MISC_GREET", info = "greeting",
              dissect = { dissect_misc_payload } },
+      [6] = { label = "MISC_BYE", info = "disconnect notification",
+             dissect = { dissect_misc_ciphertext } },
     }
   }
 }
@@ -415,6 +461,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]