#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 --- */
#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 --- *
*
`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.l_af = table.labelled('Family', combo_box_text(),
newlinep = True, width = 3)
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):
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
;; 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.
;;;
;;; 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
;; 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.
;;;
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).
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',
+ 'ephemeral']) +
[peer] +
list(addr)))
def addr(me, peer):
include $(top_srcdir)/vars.am
sbin_PROGRAMS =
+noinst_PROGRAMS =
man_MANS =
LDADD = $(libtripe) $(libpriv) \
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 --------------------------------------------------
{
close(sock.fd);
unlink(sockname);
- FOREACH_PEER(p, { p_destroy(p); });
+ FOREACH_PEER(p, { p_destroy(p, 1); });
ps_quit();
exit(0);
}
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;
})
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 --- */
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;
}
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);
}
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);
if ((p = a_findpeer(a, av[0])) != 0) {
ps = p_spec(p);
a_info(a, "tunnel=%s", ps->tops->name, A_END);
+ if (ps->knock) a_info(a, "knock=%s", ps->knock, A_END);
a_info(a, "key=%s", p_tag(p),
"current-key=%s", p->kx.kpub->tag, A_END);
if ((ptag = p_privtag(p)) == 0) ptag = "(default)";
a_info(a, "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);
}
peer *p;
if ((p = a_findpeer(a, av[0])) != 0) {
- p_destroy(p);
+ p_destroy(p, 1);
a_ok(a);
}
}
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 { \
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);
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);
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);
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);
}
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);
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) {
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);
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);
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);
}
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);
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) {
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);
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);
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)
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);
}
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);
size_t sz;
octet *q = BCUR(bb);
+ assert(c);
+
/* --- Break up the packet into its components --- */
if (psz < SEQSZ + POLY1305_TAGSZ) {
/*----- Static variables --------------------------------------------------*/
-static bulkchal *bulk;
+static bulkchal *bchal;
static uint32 oseq;
static seqwin iseq;
-/*----- Main code ---------------------------------------------------------*/
+/*----- Challenges --------------------------------------------------------*/
/* --- @c_genkey@ --- *
*
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:
*
* %$\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 -------------------------------------------------*/
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 --- *
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;
/* --- Update the statistics --- */
if (BOK(b)) {
- st->n_kxout++;
- st->sz_kxout += BLEN(b);
+ update_stats_tx(kx, BLEN(b));
p_txend(kx->p);
}
/*----- 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
static int doprechallenge(keyexch *kx, buf *b)
{
- stats *st = p_stats(kx->p);
const dhgrp *g = kx->kpriv->grp;
dhge *C = 0;
ghash *h;
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 --- */
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
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;
/* --- 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);
{
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'",
}
if (BOK(b)) {
- st->n_kxout++;
- st->sz_kxout += BLEN(b);
+ update_stats_tx(kx, BLEN(b));
p_txend(kx->p);
}
/* --- @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@ --- *
}
}
-/* --- @kx_init@ --- *
+/* --- @kx_setup@ --- *
*
* Arguments: @keyexch *kx@ = pointer to key exchange context
* @peer *p@ = pointer to peer context
* 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;
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 -------------------------------------------------*/
kd->tag = xstrdup(t.buf);
kd->ref = 1;
kd->kn = 0;
+ kd->id = k->id;
kd->t_exp = k->exp;
IF_TRACING(T_KEYMGMT, {
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
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++; )
})
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;
}
}
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) {
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:
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;
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,
/* --- @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);
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) {
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@ --- *
--- /dev/null
+/* -*-c-*-
+ *
+ * Various unit-level tests
+ *
+ * (c) 2017 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of Trivial IP Encryption (TrIPE).
+ *
+ * TrIPE is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 3 of the License, or (at your
+ * option) any later version.
+ *
+ * TrIPE is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with TrIPE. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include "tripe.h"
+
+/*----- Data structures ---------------------------------------------------*/
+
+/*----- Global variables --------------------------------------------------*/
+
+sel_state sel;
+
+/*----- Static variables --------------------------------------------------*/
+
+static char **args;
+
+/*----- Main code ---------------------------------------------------------*/
+
+static void usage(FILE *fp)
+{
+ pquis(fp,
+"Usage: $ [-k FILE] [-t KEYTAG] [-T TRACE-OPTS] COMMAND [ARGS...]\n");
+}
+
+static void version(FILE *fp)
+ { pquis(fp, "$, TrIPE version " VERSION "\n"); }
+
+static void help(FILE *fp)
+{
+ version(fp); fputc('\n', fp);
+ usage(fp); fputc('\n', fp);
+ fputs("\
+Options in full:\n\
+\n\
+-h, --help Show this help text.\n\
+-v, --version Show version number.\n\
+-u, --usage Show brief usage message.\n\
+\n\
+-k, --keyring=FILE Get keys from FILE.\n\
+-t, --tag=KEYTAG Use KEYTAG as master private key.\n\
+-T, --trace=TRACE-OPTS Turn on tracing options.\n\
+\n\
+Commands:\n\
+\n\
+ies-encrypt TY MESSAGE\n\
+ies-decrypt TY CIPHERTEXT\n\
+", fp);
+}
+
+static uint32 parseu32(const char *p)
+{
+ int e = errno;
+ unsigned long i;
+ char *q;
+
+ errno = 0;
+ i = strtoul(p, &q, 0);
+ while (*q && isspace((unsigned char)*q)) q++;
+ if (errno || *q || i > 0xffffffffu) die(2, "bad 32-bit integer `%s'", p);
+ errno = e;
+ return (i);
+}
+
+static const char *getarg(void)
+ { if (!*args) die(2, "missing argument"); else return (*args++); }
+
+static void lastarg(void)
+ { if (*args) die(2, "unexpected argument `%s'", *args); }
+
+int main(int argc, char *argv[])
+{
+ const char *kr = "keyring";
+ const char *tag = "tripe";
+ const char *arg;
+ codec *b64;
+ int i, err;
+ uint32 ty;
+ buf b, bb;
+ dstr d = DSTR_INIT;
+ unsigned f = 0;
+#define f_bogus 1u
+
+ ego(argv[0]);
+ for (;;) {
+ static const struct option opts[] = {
+ { "help", 0, 0, 'h' },
+ { "version", 0, 0, 'v' },
+ { "usage", 0, 0, 'u' },
+ { "keyring", OPTF_ARGREQ, 0, 'k' },
+ { "tag", OPTF_ARGREQ, 0, 't' },
+ { "trace", OPTF_ARGREQ, 0, 'T' },
+ { 0, 0, 0, 0 }
+ };
+
+ i = mdwopt(argc, argv, "hvuk:t:T:", opts, 0, 0, 0); if (i < 0) break;
+ switch (i) {
+ case 'h': help(stdout); exit(0);
+ case 'v': version(stdout); exit(0);
+ case 'u': usage(stdout); exit(0);
+ case 'k': kr = optarg; break;
+ case 't': tag = optarg; break;
+ case 'T':
+ tr_flags = traceopt(tr_opts, optarg, tr_flags, 0);
+ break;
+ default: f |= f_bogus; break;
+ }
+ }
+ if (f&f_bogus) { usage(stderr); exit(2); }
+ args = argv + optind;
+
+ km_init(kr, kr, tag);
+ trace_on(stderr, tr_flags);
+
+ arg = getarg();
+ if (strcmp(arg, "ies-encrypt") == 0) {
+ arg = getarg(); ty = parseu32(arg);
+ arg = getarg(); buf_init(&b, (/*unconst*/ octet *)arg, strlen(arg));
+ buf_init(&bb, buf_t, sizeof(buf_t));
+ lastarg();
+ if ((err = ies_encrypt(master, ty, &b, &bb)) != 0)
+ die(3, "ies_encrypt failed: err = %d", err);
+ b64 = base64_class.encoder(0, "\n", 72);
+ if ((err = b64->ops->code(b64, BBASE(&bb), BLEN(&bb), &d)) != 0 ||
+ (err = b64->ops->code(b64, 0, 0, &d)) != 0)
+ die(3, "base64 encoding failed: %s", codec_strerror(err));
+ b64->ops->destroy(b64);
+ DPUTC(&d, '\n');
+ fwrite(d.buf, 1, d.len, stdout);
+ dstr_destroy(&d);
+ } else if (strcmp(arg, "ies-decrypt") == 0) {
+ arg = getarg(); ty = parseu32(arg);
+ arg = getarg();
+ b64 = base64_class.decoder(CDCF_IGNSPC | CDCF_IGNNEWL);
+ if ((err = b64->ops->code(b64, arg, strlen(arg), &d)) != 0 ||
+ (err = b64->ops->code(b64, 0, 0, &d)) != 0)
+ die(3, "base64 decoding failed: %s", codec_strerror(err));
+ b64->ops->destroy(b64);
+ lastarg();
+ buf_init(&b, d.buf, d.len); buf_init(&bb, buf_t, sizeof(buf_t));
+ if ((err = ies_decrypt(master, ty, &b, &bb)) != 0)
+ die(3, "ies_decrypt failed: err = %d", err);
+ fwrite(BBASE(&bb), 1, BLEN(&bb), stdout);
+ dstr_destroy(&d);
+ } else
+ die(2, "unknown command `%s'", arg);
+
+ return (0);
+}
+
+/*----- That's all, folks -------------------------------------------------*/
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 --------------------------------------------------
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
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
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
The keepalive interval, in seconds, or zero if no keepalives are to be
sent.
.TP
+.B knock
+If present, the string sent to the peer to set up the association; see
+the
+.B \-knock
+option to
+.BR ADD ,
+and the
+.B KNOCK
+notification.
+.TP
.B key
The (short) key tag being used for the peer, as passed to the
.B ADD
.B 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
.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
.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
.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));
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;
size_t tagsz;
} bulkchal;
-struct rawkey;
-
typedef struct dhops {
const char *name;
* 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*/);
/* 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*/);
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) */
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 {
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
/* --- @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@ --- *
*
extern void kx_newkeys(keyexch */*kx*/);
-/* --- @kx_init@ --- *
+/* --- @kx_setup@ --- *
*
* Arguments: @keyexch *kx@ = pointer to key exchange context
* @peer *p@ = pointer to peer context
* 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@ --- *
*
/* --- @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 ------------------------------------------*/
extern buf *p_txstart(peer */*p*/, unsigned /*msg*/);
+/* --- @p_txaddr@ --- *
+ *
+ * Arguments: @const addr *a@ = recipient address
+ * @const void *p@ = pointer to packet to send
+ * @size_t sz@ = length of packet
+ *
+ * Returns: Zero if successful, nonzero on error.
+ *
+ * Use: Sends a packet to an address which (possibly) isn't a current
+ * peer.
+ */
+
+extern int p_txaddr(const addr */*a*/, const void */*p*/, size_t /*sz*/);
+
/* --- @p_txend@ --- *
*
* Arguments: @peer *p@ = pointer to peer block
/* --- @p_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@ --- *
*
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
.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
.RB [ \-priv
.IR tag ]
.RB [ \-mobile ]
+.RB [ \-ephemeral ]
.RB [ \-tunnel
.IR driver ]
.I address
.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
.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:
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)
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)
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.
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():
"""
addr = cr.parent.switch()
if addr is None:
raise T.TripeJobError('connect-timeout')
- addpeer(peer, addr)
+ addpeer(peer, addr, True)
finally:
del chalmap[chal]
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)
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 } }
}
},
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 } },
}
}
}
["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]