From 8362ac1c9b2fbf253b06a50639c57047d43a8fa2 Mon Sep 17 00:00:00 2001 Message-Id: <8362ac1c9b2fbf253b06a50639c57047d43a8fa2.1714921901.git.mdw@distorted.org.uk> From: Mark Wooding Date: Thu, 24 Aug 2017 21:31:43 +0100 Subject: [PATCH] Add new `knock' protocol. Organization: Straylight/Edgeware From: Mark Wooding * Add a new option to the `ADD' command giving the knock string to send. * Add new protocol messages `token-rq', `token', and `knock', which together allow a possibly unknown peer to collect a single-use token (`token-rq' and `token') which it can use to identify itself, and use this to introduce itself to its (presumably) static peer (`knock'). This last message can either provoke a `KNOCK' notification to a service, or initiate key-exchange using a piggybacked `pre-challenge', possibly after having updated the peer's address. * Update the `connect' service to collect the necessary knock strings from the peer database, and to respond to `KNOCK' notifications. * Update the Wireshark dissector to recognize and dissect the new messages. (The `cap.knock' file sneakily includes a message from the future, to be dissected in a later modification.) --- common/protocol.h | 5 +- contrib/README | 5 +- mon/tripemon.in | 9 +- peerdb/peers.in | 13 ++ peerdb/peers.in.5.in | 4 + py/tripe.py.in | 2 +- server/admin.c | 8 ++ server/keyexch.c | 275 +++++++++++++++++++++++++++++++++++++++- server/peer.c | 26 ++++ server/tests.at | 54 ++++++++ server/tripe-admin.5.in | 44 ++++++- server/tripe.c | 1 + server/tripe.h | 26 ++++ svc/connect.8.in | 53 +++++++- svc/connect.in | 23 +++- wireshark/cap.knock | Bin 0 -> 4364 bytes wireshark/tripe.lua | 78 ++++++++++++ 17 files changed, 612 insertions(+), 14 deletions(-) create mode 100644 wireshark/cap.knock diff --git a/common/protocol.h b/common/protocol.h index cbf9636a..2416afcf 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 --- */ 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 24709344..0d15474c 100644 --- a/mon/tripemon.in +++ b/mon/tripemon.in @@ -1055,7 +1055,7 @@ class AddPeerDialog (MyDialog): table = GridPacker() me.vbox.pack_start(table, True, True, 0) me.e_name = table.labelled('Name', - ValidatingEntry(r'^[^\s.:]+$', '', 16), + ValidatingEntry(r'^[^\s:]+$', '', 16), width = 3) me.e_addr = table.labelled('Address', ValidatingEntry(r'^[a-zA-Z0-9.-]+$', '', 24), @@ -1098,6 +1098,9 @@ class AddPeerDialog (MyDialog): me.c_privkey, me.e_privkey = \ optional_entry('Private key tag', r'^[^.:\s]+$', 16) + me.c_knock, me.e_knock = \ + optional_entry('Knock string', r'^[^:\s]+$', 16) + me.show_all() def ok(me): @@ -1115,7 +1118,9 @@ class AddPeerDialog (MyDialog): key = (me.c_peerkey.get_active() and me.e_peerkey.get_text() or None), priv = (me.c_privkey.get_active() and - me.e_privkey.get_text() or None)) + me.e_privkey.get_text() or None), + knock = (me.c_knock.get_active() and + me.e_knock.get_text() or None)) except ValidationError: GDK.beep() return diff --git a/peerdb/peers.in b/peerdb/peers.in index 25a14428..3fb23803 100644 --- a/peerdb/peers.in +++ b/peerdb/peers.in @@ -81,6 +81,19 @@ retries = 5 ;;; The parameters here affect peers to whom dynamic connections are made. ;;; The user and connect parameters probably need customizing. +[@KNOCK] +@inherit = @ACTIVE, @WATCH + +;; keepalive: how often to send NOP packets to keep the connection alive, at +;; least in the minds of intermediate stateful firewalls and NAT routers. +keepalive = 2m + +;; every: interval for checking that this connection is alive. +every = 30s + +;; knock: peer-name string to send to the peer. +knock = $(myhost) + [@DYNAMIC] @inherit = @ACTIVE, @WATCH diff --git a/peerdb/peers.in.5.in b/peerdb/peers.in.5.in index a07240b2..89eb7776 100644 --- a/peerdb/peers.in.5.in +++ b/peerdb/peers.in.5.in @@ -207,6 +207,10 @@ Interval for sending keepalive pings. Used by Key tag to use to authenticate the peer. Used by .BR connect (8). .TP +.B knock +Knock string to send when establishing a dynamic connection. Used by +.BR connect (8). +.TP .B mobile Peer's IP address is highly volatile. Used by .BR connect (8). diff --git a/py/tripe.py.in b/py/tripe.py.in index 5562d7b3..63ae0afc 100644 --- a/py/tripe.py.in +++ b/py/tripe.py.in @@ -838,7 +838,7 @@ class TripeCommandDispatcher (TripeConnection): *['ADD'] + _kwopts(kw, ['tunnel', 'keepalive', 'key', 'priv', 'cork', - 'mobile']) + + 'mobile', 'knock']) + [peer] + list(addr))) def addr(me, peer): diff --git a/server/admin.c b/server/admin.c index d80992cd..886c672c 100644 --- a/server/admin.c +++ b/server/admin.c @@ -1235,6 +1235,7 @@ static void a_doadd(admin_resop *r, int rc) if (add->peer.tag) xfree(add->peer.tag); if (add->peer.privtag) xfree(add->peer.privtag); + if (add->peer.knock) xfree(add->peer.knock); xfree(add->peer.name); } @@ -1260,6 +1261,7 @@ static void acmd_add(admin *a, unsigned ac, char *av[]) add->peer.name = 0; add->peer.tag = 0; add->peer.privtag = 0; + add->peer.knock = 0; add->peer.t_ka = 0; add->peer.tops = tun_default; add->peer.f = 0; @@ -1292,6 +1294,10 @@ static void acmd_add(admin *a, unsigned ac, char *av[]) if (add->peer.privtag) xfree(add->peer.privtag); add->peer.privtag = xstrdup(arg); }) + OPTARG("-knock", arg, { + if (add->peer.knock) xfree(add->peer.knock); + add->peer.knock = xstrdup(arg); + }) }); /* --- Make sure someone's not got there already --- */ @@ -1318,6 +1324,7 @@ fail: if (add->peer.name) xfree(add->peer.name); if (add->peer.tag) xfree(add->peer.tag); if (add->peer.privtag) xfree(add->peer.privtag); + if (add->peer.knock) xfree(add->peer.knock); xfree(add); return; } @@ -1844,6 +1851,7 @@ static void acmd_peerinfo(admin *a, unsigned ac, char *av[]) if ((p = a_findpeer(a, av[0])) != 0) { ps = p_spec(p); a_info(a, "tunnel=%s", ps->tops->name, A_END); + if (ps->knock) a_info(a, "knock=%s", ps->knock, A_END); a_info(a, "key=%s", p_tag(p), "current-key=%s", p->kx.kpub->tag, A_END); if ((ptag = p_privtag(p)) == 0) ptag = "(default)"; diff --git a/server/keyexch.c b/server/keyexch.c index 4a990ee7..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 -------------------------------------------------*/ @@ -620,6 +637,148 @@ static void kxc_answer(keyexch *kx, kxchal *kxc) /*----- Individual message handlers ---------------------------------------*/ +static ratelim unauth_limit; + +/* --- @dotokenrq@ --- * + * + * Arguments: @const addr *a@ = sender's address + * @buf *b@ = buffer containing the packet + * + * Returns: --- + * + * Use: Processes a token-request message. + */ + +static void dotokenrq(const addr *a, buf *b) +{ + uint32 id; + kdata *kpriv = 0, *kpub = 0; + char *pname; + const char *tag; + size_t sz; + buf bb, bbb; + + /* --- Check if we're in danger of overloading --- */ + + if (ratelim_withdraw(&unauth_limit, 1)) goto done; + + /* --- Start building the reply --- */ + + buf_init(&bbb, buf_o, sizeof(buf_o)); + buf_putu8(&bbb, MSG_KEYEXCH | KX_TOKEN); + + /* --- Fetch and copy the challenge string --- */ + + if (buf_getbuf16(b, &bb)) goto done; + buf_putmem16(&bbb, BBASE(&bb), BSZ(&bb)); + + /* --- Make our own challenge for the response --- */ + + buf_init(&bb, buf_t, sizeof(buf_t)); + c_new(0, 0, &bb); assert(BOK(&bb)); buf_putbuf16(&bbb, &bb); + + /* --- Figure out which private key I'm supposed to use --- */ + + if (buf_getu32(b, &id)) goto done; + if ((kpriv = km_findprivbyid(id)) == 0) goto done; + + /* --- Decrypt the message --- */ + + buf_init(&bb, buf_t, sizeof(buf_t)); + if (ies_decrypt(kpriv, MSG_KEYEXCH | KX_TOKENRQ, b, &bb) || BLEFT(b)) + goto done; + + /* --- Parse the token request and find the sender's public key --- */ + + assert(BOK(&bb)); buf_flip(&bb); + if ((pname = buf_getmem16(&bb, &sz)) == 0 || memchr(pname, 0, sz)) + goto done; + assert(sz < sizeof(buf_t) - ((const octet *)pname - buf_t)); + pname[sz] = 0; + if ((tag = strchr(pname, '.')) != 0) tag++; + else tag = pname; + if ((kpub = km_findpub(tag)) == 0) goto done; + + /* --- Build and encrypt the token --- */ + + buf_init(&bb, buf_i, sizeof(buf_i)); + c_new(pname, sz, &bb); + assert(BOK(&bb)); buf_flip(&bb); + if (ies_encrypt(kpub, MSG_KEYEXCH | KX_TOKEN, &bb, &bbb)) goto done; + assert(BOK(&bbb)); + + /* --- Send the response -- or at least give it a try --- */ + + p_txaddr(a, BBASE(&bbb), BLEN(&bbb)); + + /* --- All done --- */ + +done: + if (kpriv) km_unref(kpriv); + if (kpub) km_unref(kpub); +} + +/* --- @dotoken@ --- * + * + * Arguments: @keyexch *kx@ = pointer to key exchange block + * @buf *b@ = buffer containing the packet + * + * Returns: Zero if OK, nonzero of the packet was rejected. + * + * Use: Processes a token message. + */ + +static int dotoken(keyexch *kx, buf *b) +{ + buf bb; + buf *bbb; + const dhgrp *g = kx->kpriv->grp; + octet *p; + size_t sz; + + /* --- Make sure this is a sensible message to have received --- */ + + if (!kx->p->spec.knock) return (-1); + + /* --- First, collect and verify our challenge --- */ + + if (buf_getbuf16(b, &bb) || c_check(0, 0, &bb) || BLEFT(&bb)) return (-1); + + /* --- Start building the knock message from here --- */ + + bbb = p_txstart(kx->p, MSG_KEYEXCH | KX_KNOCK); + + /* --- Copy the peer's challenge --- */ + + if (buf_getbuf16(b, &bb)) return (-1); + buf_putmem16(bbb, BBASE(&bb), BSZ(&bb)); + + /* --- Add the key indicator --- */ + + buf_putu32(bbb, kx->kpub->id); + + /* --- Building the knock payload --- */ + + buf_init(&bb, buf_t, sizeof(buf_t)); + buf_putstr16(&bb, kx->p->spec.knock); + sz = BLEN(&bb)%64; if (sz) sz = 64 - sz; + if (ies_decrypt(kx->kpriv, MSG_KEYEXCH | KX_TOKEN, b, &bb)) return (-1); + p = buf_get(&bb, sz); assert(p); memset(p, 0, sz); + assert(BOK(&bb)); buf_flip(&bb); + if (ies_encrypt(kx->kpub, MSG_KEYEXCH | KX_KNOCK, &bb, bbb)) return (-1); + + /* --- Finally, the pre-challenge group element --- */ + + g->ops->stge(g, bbb, kx->C, DHFMT_VAR); + + /* --- And we're done --- */ + + if (BBAD(bbb)) return (-1); + update_stats_tx(kx, BLEN(bbb)); + p_txend(kx->p); + return (0); +} + /* --- @doprechallenge@ --- * * * Arguments: @keyexch *kx@ = pointer to key exchange block @@ -673,6 +832,73 @@ bad: return (-1); } +/* --- @doknock@ --- * + * + * Arguments: @const addr *a@ = sender's address + * @buf *b@ = buffer containing the packet + * + * Returns: --- + * + * Use: Processes a knock message. + */ + +static void doknock(const addr *a, buf *b) +{ + keyexch *kx; + peer *p; + uint32 id; + kdata *kpriv = 0; + char *pname; + size_t sz, msgsz = BLEN(b); + buf bb; + int rc; + + /* --- Read and check the challenge --- */ + + buf_getbuf16(b, &bb); + if (c_check(0, 0, &bb)) goto done; + + /* --- Figure out which private key I'm supposed to use --- */ + + if (buf_getu32(b, &id)) goto done; + if ((kpriv = km_findprivbyid(id)) == 0) goto done; + + /* --- Decrypt and check the peer's name against the token --- */ + + buf_init(&bb, buf_t, sizeof(buf_t)); + if (ies_decrypt(kpriv, MSG_KEYEXCH | KX_KNOCK, b, &bb)) goto done; + assert(BOK(&bb)); buf_flip(&bb); + if ((pname = buf_getmem16(&bb, &sz)) == 0 || + memchr(pname, 0, sz) || + c_check(pname, sz, &bb)) + goto done; + assert(sz < sizeof(buf_t) - ((const octet *)pname - buf_t)); + pname[sz] = 0; + + /* --- If we can't find the peer, then issue a notification --- */ + + if ((p = p_find(pname)) == 0) { + a_notify("KNOCK", "%s", pname, "?ADDR", a, A_END); + goto done; + } + + /* --- Update the peer's address --- */ + + kx = &p->kx; + p_updateaddr(kx->p, a); + + /* --- Now treat the remainder of the message as a pre-challenge --- */ + + notice_message(kx); + rc = doprechallenge(kx, b); + update_stats_rx(kx, !rc, msgsz); + + /* --- All done: clean up --- */ + +done: + if (kpriv) km_unref(kpriv); +} + /* --- @respond@ --- * * * Arguments: @keyexch *kx@ = pointer to key exchange block @@ -881,14 +1107,35 @@ static void resend(keyexch *kx) buf bb; struct timeval tv; const dhgrp *g = kx->kpriv->grp; + octet *p; + size_t sz; buf *b; switch (kx->s) { case KXS_CHAL: - T( trace(T_KEYEXCH, "keyexch: sending prechallenge to `%s'", - p_name(kx->p)); ) - b = p_txstart(kx->p, MSG_KEYEXCH | KX_PRECHAL); - g->ops->stge(g, b, kx->C, DHFMT_VAR); + if (!kx->p->spec.knock) { + T( trace(T_KEYEXCH, "keyexch: sending prechallenge to `%s'", + p_name(kx->p)); ) + b = p_txstart(kx->p, MSG_KEYEXCH | KX_PRECHAL); + g->ops->stge(g, b, kx->C, DHFMT_VAR); + } else { + T( trace(T_KEYEXCH, "keyexch: sending token-request to `%s'", + p_name(kx->p)); ) + b = p_txstart(kx->p, MSG_KEYEXCH | KX_TOKENRQ); + + buf_init(&bb, buf_t, sizeof(buf_t)); + c_new(0, 0, &bb); assert(BOK(&bb)); buf_putbuf16(b, &bb); + + buf_putu32(b, kx->kpub->id); + + buf_init(&bb, buf_t, sizeof(buf_t)); + buf_putstr16(&bb, kx->p->spec.knock); + sz = BLEN(&bb)%64; if (sz) sz = 64 - sz; + p = buf_get(&bb, sz); assert(p); memset(p, 0, sz); + assert(BOK(&bb)); buf_flip(&bb); + if (ies_encrypt(kx->kpub, MSG_KEYEXCH | KX_TOKENRQ, &bb, b)) + buf_break(b); + } break; case KXS_COMMIT: T( trace(T_KEYEXCH, "keyexch: sending switch request to `%s'", @@ -1339,10 +1586,16 @@ int kx_message(keyexch *kx, const addr *a, unsigned msg, buf *b) msg < KX_NMSG ? pkname[msg] : "unknown", kx ? '`' : '<', kx ? p_name(kx->p) : "nil", kx ? '\'' : '>'); ) + switch (msg) { + case KX_TOKENRQ: dotokenrq(a, b); return (0); + case KX_KNOCK: doknock(a, b); return (0); + } + if (!kx) return (-1); if (notice_message(kx)) return (0); switch (msg) { + case KX_TOKEN: rc = dotoken(kx, b); break; case KX_PRECHAL: rc = doprechallenge(kx, b); break; case KX_CHAL: rc = dochallenge(kx, b); break; case KX_REPLY: rc = doreply(kx, b); break; @@ -1544,4 +1797,16 @@ fail_0: return (-1); } +/* --- @kx_init@ --- * + * + * Arguments: --- + * + * Returns: --- + * + * Use: Initializes the key-exchange logic. + */ + +void kx_init(void) + { ratelim_init(&unauth_limit, 20, 500); } + /*----- That's all, folks -------------------------------------------------*/ diff --git a/server/peer.c b/server/peer.c index 2eb30bf5..f600f99e 100644 --- a/server/peer.c +++ b/server/peer.c @@ -488,6 +488,30 @@ buf *p_txstart(peer *p, unsigned msg) return (&p->b); } +/* --- @p_txaddr@ --- * + * + * Arguments: @const addr *a@ = recipient address + * @const void *p@ = pointer to packet to send + * @size_t sz@ = length of packet + * + * Returns: Zero if successful, nonzero on error. + * + * Use: Sends a packet to an address which (possibly) isn't a current + * peer. + */ + +int p_txaddr(const addr *a, const void *p, size_t sz) +{ + socklen_t sasz = addrsz(a); + + IF_TRACING(T_PEER, trace_block(T_PACKET, "peer: sending packet", p, sz); ) + if (sendto(sock.fd, p, sz, 0, &a->sa, sasz) < 0) { + a_warn("PEER", "?ADDR", a, "socket-write-error", "?ERRNO", A_END); + return (-1); + } + return (0); +} + /* --- @p_txend@ --- * * * Arguments: @peer *p@ = pointer to peer block @@ -894,6 +918,7 @@ peer *p_create(peerspec *spec) p->spec.name = (/*unconst*/ char *)SYM_NAME(p->byname); if (spec->tag) p->spec.tag = xstrdup(spec->tag); if (spec->privtag) p->spec.privtag = xstrdup(spec->privtag); + if (spec->knock) p->spec.knock = xstrdup(spec->knock); p->ks = 0; p->pings = 0; p->ifname = 0; @@ -1037,6 +1062,7 @@ void p_destroy(peer *p) if (p->ifname) xfree(p->ifname); if (p->spec.tag) xfree(p->spec.tag); if (p->spec.privtag) xfree(p->spec.privtag); + if (p->spec.knock) xfree(p->spec.knock); p->t->ops->destroy(p->t); if (p->spec.t_ka) sel_rmtimer(&p->tka); for (pg = p->pings; pg; pg = ppg) { diff --git a/server/tests.at b/server/tests.at index d217054a..8bceb147 100644 --- a/server/tests.at +++ b/server/tests.at @@ -751,4 +751,58 @@ WITH_TRIPE(, [ AT_CLEANUP +###-------------------------------------------------------------------------- +### Knock. + +AT_SETUP([server knock]) +AT_KEYWORDS([knock]) +export TRIPE_SLIPIF=USLIP + +for i in alice bob; do (mkdir $i; cd $i; SETUPDIR([alpha])); done + +WITH_2TRIPES([alice], [bob], [-nslip], [-talice], [-tbob], [ + WITH_MITM([alice], [5311], [bob], [5312], [ + + COPROCESSES([wait-knock], [ + echo WATCH +n + while read line; do + set x $line; shift + echo >&2 ">>> $line" + case "$1:$2:$3" in + OK::) ;; + NOTE:KNOCK:bob) shift 3; echo "$*" >knock-addr; break ;; + NOTE:* | TRACE:* | WARN:*) ;; + *) exit 63 ;; + esac + done + ], [ + TRIPECTL -dalice + ])& waiter=$! + + AT_CHECK([TRIPECTL -dbob ADD -knock bob alice INET 127.0.0.1 5312]) + + wait $waiter; waitrc=$? + AT_CHECK([echo $waitrc],, [0[]nl]) + AT_CHECK([cat knock-addr],, [INET 127.0.0.1 5311[]nl]) + + AWAIT_KXDONE([alice], [alice], [bob], [bob], [ + AT_CHECK([TRIPECTL -dalice ADD bob INET 127.0.0.1 5311]) + ]) + + COMMS_EPING([alice], [alice], [bob], [bob]) + COMMS_SLIP([alice], [alice], [bob], [bob]) + ]) + + WITH_MITM([alice], [5319], [bob], [5312], [ + AWAIT_KXDONE([alice], [alice], [bob], [bob], [ + AT_CHECK([TRIPECTL -dalice FORCEKX bob]) + AT_CHECK([TRIPECTL -dbob FORCEKX alice]) + ]) + AT_CHECK([TRIPECTL -dbob KILL alice]) + AT_CHECK([TRIPECTL -dalice LIST],, []) + ]) +]) + +AT_CLEANUP + ###----- That's all, folks -------------------------------------------------- diff --git a/server/tripe-admin.5.in b/server/tripe-admin.5.in index a737e778..a340f57f 100644 --- a/server/tripe-admin.5.in +++ b/server/tripe-admin.5.in @@ -353,6 +353,25 @@ Use the public key to authenticate the peer. The default is to use the key tagged .IR peer . .TP +.BI "\-knock \fR[" prefix .\fR] tag +Send the string +.RI [ prefix\fB. ] tag +in +.B token-rq +and +.B knock +messages to the peer during key-exchange. The string as a whole should +name the local machine to the peer, and +.I tag +should name its public key. When such messages are received from a +currently unknown peer, +.BR tripe (8) +emits a +.B KNOCK +notification stating the peer's (claimed) name and address. The server +will already have verified that the sender is using the peer's private +key by this point. +.TP .B "\-mobile" The peer is a mobile device, and is likely to change address rapidly. If a packet arrives from an unknown address, the server's usual response @@ -572,6 +591,16 @@ The tunnel driver used for this peer. The keepalive interval, in seconds, or zero if no keepalives are to be sent. .TP +.B knock +If present, the string sent to the peer to set up the association; see +the +.B \-knock +option to +.BR ADD , +and the +.B KNOCK +notification. +.TP .B key The (short) key tag being used for the peer, as passed to the .B ADD @@ -1168,6 +1197,12 @@ The peer .I peer has been killed. .SP +.BI "KNOCK " peer " " address +The currently unknown +.I peer +is attempting to connect from +.IR address . +.SP .BI "KXDONE " peer Key exchange with .I peer @@ -1410,8 +1445,11 @@ is one of the tokens .BR challenge , .BR reply , .BR switch-rq , -or .BR switch-ok . +.BR token-rq , +.BR token , +or +.BR knock . .SP .BI "KX " peer " algorithms-mismatch local-private-key " privtag " peer-public-key " pubtag The algorithms specified in the peer's public key @@ -1526,6 +1564,10 @@ An error occurred trying to read an incoming packet. An error occurred attempting to send a network packet. We lost that one. .SP +.BI "PEER " address\fR... " socket-write-error " ecode " " message +An error occurred attempting to send a network packet. We lost that +one. +.SP .BI "PEER " peer " unexpected-encrypted-ping 0x" id The peer sent an encrypted ping response whose id doesn't match any outstanding ping. Maybe it was delayed for longer than the server was diff --git a/server/tripe.c b/server/tripe.c index 58bf8a18..d50757bc 100644 --- a/server/tripe.c +++ b/server/tripe.c @@ -299,6 +299,7 @@ int main(int argc, char *argv[]) a_init(csock, u, g, csockmode); u_setugid(u, g); km_init(kr_priv, kr_pub, tag_priv); + kx_init(); if (f & f_daemon) { if (daemonize()) die(EXIT_FAILURE, "couldn't become a daemon: %s", strerror(errno)); diff --git a/server/tripe.h b/server/tripe.h index c796d073..2024627a 100644 --- a/server/tripe.h +++ b/server/tripe.h @@ -610,6 +610,7 @@ typedef struct peerspec { char *name; /* Peer's name */ char *privtag; /* Private key tag */ char *tag; /* Public key tag */ + char *knock; /* Knock string, or null */ const tunnel_ops *tops; /* Tunnel operations */ unsigned long t_ka; /* Keep alive interval */ addr sa; /* Socket address to speak to */ @@ -958,6 +959,17 @@ extern void kx_newkeys(keyexch */*kx*/); extern int kx_setup(keyexch */*kx*/, peer */*p*/, keyset **/*ks*/, unsigned /*f*/); +/* --- @kx_init@ --- * + * + * Arguments: --- + * + * Returns: --- + * + * Use: Initializes the key-exchange logic. + */ + +extern void kx_init(void); + /*----- Keysets and symmetric cryptography --------------------------------*/ /* --- @ks_drop@ --- * @@ -1443,6 +1455,20 @@ extern int p_updateaddr(peer */*p*/, const addr */*a*/); extern buf *p_txstart(peer */*p*/, unsigned /*msg*/); +/* --- @p_txaddr@ --- * + * + * Arguments: @const addr *a@ = recipient address + * @const void *p@ = pointer to packet to send + * @size_t sz@ = length of packet + * + * Returns: Zero if successful, nonzero on error. + * + * Use: Sends a packet to an address which (possibly) isn't a current + * peer. + */ + +extern int p_txaddr(const addr */*a*/, const void */*p*/, size_t /*sz*/); + /* --- @p_txend@ --- * * * Arguments: @peer *p@ = pointer to peer block diff --git a/svc/connect.8.in b/svc/connect.8.in index 86945ce4..5a111647 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 @@ -724,6 +752,29 @@ command reported .B FAIL .IR error ... .SP +.BI "USER connect knock-active-peer " name +The server reported a valid +.B knock +message from a peer calling itself +.I name +but this is not a passive peer. +.SP +.BI "USER connect knock-unknown-peer " name +The server reported a valid +.B knock +message from a peer calling itself +.I name +but no such peer is defined in the database. +.SP +.BI "USER connect knock-tag-mismatch peer " name " public-key-tag " tag +The server reported a valid +.B knock +message from a peer calling itself +.I name +but this name doesn't match that peer's recorded public-key tag, which +is +.IR tag . +.SP .BI "USER connect ping-ok " peer A reply was received to a .B PING diff --git a/svc/connect.in b/svc/connect.in index b6ec4b8c..fe8d73b9 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: @@ -731,6 +732,7 @@ def addpeer(peer, addr): key = peer.get('key', default = None), priv = peer.get('priv', default = None), mobile = peer.get('mobile', filter = boolean, default = False), + knock = peer.get('knock', default = None), cork = peer.get('cork', filter = boolean, default = False), *addr) except T.TripeError, exc: @@ -760,6 +762,23 @@ def notify(_, code, *rest): try: cr = chalmap[chal] except KeyError: pass else: cr.switch(rest[1:]) + elif code == 'KNOCK': + try: p = Peer(rest[0]) + except KeyError: + S.warn(['connect', 'knock-unknown-peer', rest[0]]) + return + if p.get('peer') != 'PASSIVE': + S.warn(['connect', 'knock-active-peer', p.name]) + return + dot = p.name.find('.') + if dot >= 0: kname = p.name[dot + 1:] + else: kname = p.name + ktag = p.get('key', p.name) + if kname != ktag: + S.warn(['connect', 'knock-tag-mismatch', + 'peer', pname, 'public-key-tag', ktag]) + return + T.spawn(addpeer, p, rest[1:]) ###-------------------------------------------------------------------------- ### Command implementation. diff --git a/wireshark/cap.knock b/wireshark/cap.knock new file mode 100644 index 0000000000000000000000000000000000000000..2ca353707d22bb3fc08dee7f3909c3469d37b18c GIT binary patch literal 4364 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