#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 --- */
`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
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),
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):
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
;;; 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
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).
*['ADD'] +
_kwopts(kw, ['tunnel', 'keepalive',
'key', 'priv', 'cork',
- 'mobile']) +
+ 'mobile', 'knock']) +
[peer] +
list(addr)))
def addr(me, peer):
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);
}
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;
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 --- */
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;
}
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)";
*
* %$\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 -------------------------------------------------*/
/*----- 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
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
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'",
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;
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 -------------------------------------------------*/
return (&p->b);
}
+/* --- @p_txaddr@ --- *
+ *
+ * Arguments: @const addr *a@ = recipient address
+ * @const void *p@ = pointer to packet to send
+ * @size_t sz@ = length of packet
+ *
+ * Returns: Zero if successful, nonzero on error.
+ *
+ * Use: Sends a packet to an address which (possibly) isn't a current
+ * peer.
+ */
+
+int p_txaddr(const addr *a, const void *p, size_t sz)
+{
+ socklen_t sasz = addrsz(a);
+
+ IF_TRACING(T_PEER, trace_block(T_PACKET, "peer: sending packet", p, sz); )
+ if (sendto(sock.fd, p, sz, 0, &a->sa, sasz) < 0) {
+ a_warn("PEER", "?ADDR", a, "socket-write-error", "?ERRNO", A_END);
+ return (-1);
+ }
+ return (0);
+}
+
/* --- @p_txend@ --- *
*
* Arguments: @peer *p@ = pointer to peer block
p->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;
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) {
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 --------------------------------------------------
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
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
.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
.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
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
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));
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 */
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@ --- *
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
.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
.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
.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
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):
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:
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:
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.
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.
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 } }
}
},
["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]