From: Mark Wooding Date: Fri, 25 Jan 2019 12:08:24 +0000 (+0000) Subject: Merge branches 'mdw/knock' and 'mdw/ipv6' into bleeding X-Git-Tag: 1.5.0~41 X-Git-Url: https://git.distorted.org.uk/~mdw/tripe/commitdiff_plain/4a3882945f605704ede113a9fe98cd19a92363a7?hp=80a1137431b5f1e52b1edd192a498669325d6ec2 Merge branches 'mdw/knock' and 'mdw/ipv6' into bleeding * 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. ... --- diff --git a/common/protocol.h b/common/protocol.h index cbf9636a..e40cf177 100644 --- a/common/protocol.h +++ b/common/protocol.h @@ -64,7 +64,10 @@ #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 --- * * diff --git a/contrib/README b/contrib/README index f096a6ac..a3138d25 100644 --- a/contrib/README +++ b/contrib/README @@ -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 diff --git a/mon/tripemon.in b/mon/tripemon.in index 1c70d1ff..11ee6dc0 100644 --- a/mon/tripemon.in +++ b/mon/tripemon.in @@ -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 diff --git a/peerdb/peers.in b/peerdb/peers.in index 25a14428..75c039d9 100644 --- a/peerdb/peers.in +++ b/peerdb/peers.in @@ -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. ;;; diff --git a/peerdb/peers.in.5.in b/peerdb/peers.in.5.in index 554a8d19..d638b078 100644 --- a/peerdb/peers.in.5.in +++ b/peerdb/peers.in.5.in @@ -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). diff --git a/py/tripe.py.in b/py/tripe.py.in index 29911b0e..a9be6687 100644 --- a/py/tripe.py.in +++ b/py/tripe.py.in @@ -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): diff --git a/server/Makefile.am b/server/Makefile.am index 1e6d71bc..f569b455 100644 --- a/server/Makefile.am +++ b/server/Makefile.am @@ -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 -------------------------------------------------- diff --git a/server/admin.c b/server/admin.c index 8a3e62ca..87bb9053 100644 --- a/server/admin.c +++ b/server/admin.c @@ -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); } } diff --git a/server/bulkcrypto.c b/server/bulkcrypto.c index 7d754cd7..d0e654cc 100644 --- a/server/bulkcrypto.c +++ b/server/bulkcrypto.c @@ -49,6 +49,53 @@ 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) { diff --git a/server/chal.c b/server/chal.c index b463823c..68d7f048 100644 --- a/server/chal.c +++ b/server/chal.c @@ -29,11 +29,11 @@ /*----- 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: diff --git a/server/keyexch.c b/server/keyexch.c index 9d08bec6..b1411004 100644 --- a/server/keyexch.c +++ b/server/keyexch.c @@ -72,12 +72,29 @@ * * %$\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 -------------------------------------------------*/ diff --git a/server/keymgmt.c b/server/keymgmt.c index 51a13d70..7386408f 100644 --- a/server/keymgmt.c +++ b/server/keymgmt.c @@ -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 diff --git a/server/keyset.c b/server/keyset.c index 9429fa4a..a0c4577d 100644 --- a/server/keyset.c +++ b/server/keyset.c @@ -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++; ) diff --git a/server/peer.c b/server/peer.c index 94629213..a8099e41 100644 --- a/server/peer.c +++ b/server/peer.c @@ -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) { diff --git a/server/servutil.c b/server/servutil.c index f95541a8..703e448e 100644 --- a/server/servutil.c +++ b/server/servutil.c @@ -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 index 00000000..152a6fe6 --- /dev/null +++ b/server/test.c @@ -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 . + */ + +/*----- 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 -------------------------------------------------*/ diff --git a/server/tests.at b/server/tests.at index d217054a..08bfb135 100644 --- a/server/tests.at +++ b/server/tests.at @@ -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 -------------------------------------------------- diff --git a/server/tripe-admin.5.in b/server/tripe-admin.5.in index 81dd570d..f066ae6d 100644 --- a/server/tripe-admin.5.in +++ b/server/tripe-admin.5.in @@ -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 diff --git a/server/tripe.c b/server/tripe.c index 565c83dd..b8438857 100644 --- a/server/tripe.c +++ b/server/tripe.c @@ -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)); diff --git a/server/tripe.h b/server/tripe.h index 29403993..10a03f5f 100644 --- a/server/tripe.h +++ b/server/tripe.h @@ -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 diff --git a/svc/connect.8.in b/svc/connect.8.in index 86945ce4..1e4e2d25 100644 --- a/svc/connect.8.in +++ b/svc/connect.8.in @@ -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 diff --git a/svc/connect.in b/svc/connect.in index b6ec4b8c..99dbd8af 100644 --- a/svc/connect.in +++ b/svc/connect.in @@ -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 index 00000000..2ca35370 Binary files /dev/null and b/wireshark/cap.knock differ diff --git a/wireshark/tripe.lua b/wireshark/tripe.lua index f950aefa..5c6d7ba9 100644 --- a/wireshark/tripe.lua +++ b/wireshark/tripe.lua @@ -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]