X-Git-Url: https://git.distorted.org.uk/~mdw/tripe/blobdiff_plain/0510f26241da5ac18afecf97fa46e0448ae705a4..HEAD:/server/keyexch.c diff --git a/server/keyexch.c b/server/keyexch.c index f1d0a56a..b1411004 100644 --- a/server/keyexch.c +++ b/server/keyexch.c @@ -72,12 +72,29 @@ * * %$\cookie{kx-switch-ok}, E_K(u_A))$% * Switch received. Committed; send data; move to @KXS_SWITCH@. + * + * %$\cookie{kx-token-request}, u, E_L(n)$% + * %$L = H(u, u^\alpha)$%, and %$n$% is a string of the form + * `[PEER.]KEYTAG'. Expect %$\cookie{kx-token}$% by return. + * + * %$\cookie{kx-token}, v, E_{L'}(t)$% + * %$L' = H(v, v^\alpha)$%, and %$t$% is a token associated with %$n$% + * (see %$\cookie{kx-token-request}$% above). + * + * %$\cookie{kx-knock}, u, E_L(n, t), r_A$% + * %$L$%, %$n$% and %$t$% are as %$\cookie{kx-token}$% and + * %$\cookie{kx-token-request}$%; %$r_A$% is as in + * %$\cookie{kx-pre-challenge}$%. If the token %$t$% doesn't match + * %$n$%, then warn and discard. If a peer named PEER (or KEYTAG) + * exists then proceed as for %$\cookie{kx-pre-challenge}$%. Otherwise + * issue a notification `NOTE KNOCK PEER ADDR...' and discard. */ /*----- Static tables -----------------------------------------------------*/ static const char *const pkname[] = { - "pre-challenge", "challenge", "reply", "switch-rq", "switch-ok" + "pre-challenge", "challenge", "reply", "switch-rq", "switch-ok", + "token-rq", "token", "knock" }; /*----- Various utilities -------------------------------------------------*/ @@ -373,6 +390,66 @@ static void rs_time(retry *rs, struct timeval *tv, const struct timeval *now) static void rs_reset(retry *rs) { rs->t = 0; } +/* --- @notice_message@ --- * + * + * Arguments: @keyexch *kx@ = pointer to key-exchange block + * + * Returns: Zero if OK; @-1@ if the public key is in a bad state. + * + * Use: Updates the key-exchange state following a received message. + * Specifically, if there's no currently active key-exchange in + * progress, and we're not in the cooling-off period, then + * commence a new one; reset the retry timers; and if we're + * corked then pop the cork so that we can reply. + */ + +static int checkpub(keyexch *kx); +static void stop(keyexch *kx); +static void start(keyexch *kx, time_t now); + +static int notice_message(keyexch *kx) +{ + struct timeval now, tv; + + gettimeofday(&now, 0); + rs_reset(&kx->rs); + if (kx->f & KXF_CORK) { + start(kx, now.tv_sec); + rs_time(&kx->rs, &tv, &now); + settimer(kx, &tv); + a_notify("KXSTART", "?PEER", kx->p, A_END); + } + if (checkpub(kx)) return (-1); + if (!VALIDP(kx, now.tv_sec)) { + stop(kx); + start(kx, now.tv_sec); + } + return (0); +} + +/* --- @update_stats_tx@, @update_stats_rx@ --- * + * + * Arguments: @keyexch *kx@ = pointer to key-exchange block + * @int ok@ = nonzero if the message was valid (for @rx@) + * @size_t sz@ = size of sent message + * + * Returns: --- + * + * Use: Records that a key-exchange message was sent to, or received + * from, the peer. + */ + +static void update_stats_tx(keyexch *kx, size_t sz) + { stats *st = p_stats(kx->p); st->n_kxout++; st->sz_kxout += sz; } + +static void update_stats_rx(keyexch *kx, int ok, size_t sz) +{ + stats *st = p_stats(kx->p); + + if (!ok) st->n_reject++; + else { st->n_kxin++; st->sz_kxin += sz; } +} + /*----- Challenge management ----------------------------------------------*/ /* --- Notes on challenge management --- * @@ -527,7 +604,6 @@ static void kxc_timer(struct timeval *tv, void *v) static void kxc_answer(keyexch *kx, kxchal *kxc) { - stats *st = p_stats(kx->p); buf *b = p_txstart(kx->p, MSG_KEYEXCH | KX_REPLY); const dhgrp *g = kx->kpriv->grp; struct timeval tv; @@ -545,8 +621,7 @@ static void kxc_answer(keyexch *kx, kxchal *kxc) /* --- Update the statistics --- */ if (BOK(b)) { - st->n_kxout++; - st->sz_kxout += BLEN(b); + update_stats_tx(kx, BLEN(b)); p_txend(kx->p); } @@ -562,6 +637,148 @@ static void kxc_answer(keyexch *kx, kxchal *kxc) /*----- Individual message handlers ---------------------------------------*/ +static ratelim unauth_limit; + +/* --- @dotokenrq@ --- * + * + * Arguments: @const addr *a@ = sender's address + * @buf *b@ = buffer containing the packet + * + * Returns: --- + * + * Use: Processes a token-request message. + */ + +static void dotokenrq(const addr *a, buf *b) +{ + uint32 id; + kdata *kpriv = 0, *kpub = 0; + char *pname; + const char *tag; + size_t sz; + buf bb, bbb; + + /* --- Check if we're in danger of overloading --- */ + + if (ratelim_withdraw(&unauth_limit, 1)) goto done; + + /* --- Start building the reply --- */ + + buf_init(&bbb, buf_o, sizeof(buf_o)); + buf_putu8(&bbb, MSG_KEYEXCH | KX_TOKEN); + + /* --- Fetch and copy the challenge string --- */ + + if (buf_getbuf16(b, &bb)) goto done; + buf_putmem16(&bbb, BBASE(&bb), BSZ(&bb)); + + /* --- Make our own challenge for the response --- */ + + buf_init(&bb, buf_t, sizeof(buf_t)); + c_new(0, 0, &bb); assert(BOK(&bb)); buf_putbuf16(&bbb, &bb); + + /* --- Figure out which private key I'm supposed to use --- */ + + if (buf_getu32(b, &id)) goto done; + if ((kpriv = km_findprivbyid(id)) == 0) goto done; + + /* --- Decrypt the message --- */ + + buf_init(&bb, buf_t, sizeof(buf_t)); + if (ies_decrypt(kpriv, MSG_KEYEXCH | KX_TOKENRQ, b, &bb) || BLEFT(b)) + goto done; + + /* --- Parse the token request and find the sender's public key --- */ + + assert(BOK(&bb)); buf_flip(&bb); + if ((pname = buf_getmem16(&bb, &sz)) == 0 || memchr(pname, 0, sz)) + goto done; + assert(sz < sizeof(buf_t) - ((const octet *)pname - buf_t)); + pname[sz] = 0; + if ((tag = strchr(pname, '.')) != 0) tag++; + else tag = pname; + if ((kpub = km_findpub(tag)) == 0) goto done; + + /* --- Build and encrypt the token --- */ + + buf_init(&bb, buf_i, sizeof(buf_i)); + c_new(pname, sz, &bb); + assert(BOK(&bb)); buf_flip(&bb); + if (ies_encrypt(kpub, MSG_KEYEXCH | KX_TOKEN, &bb, &bbb)) goto done; + assert(BOK(&bbb)); + + /* --- Send the response -- or at least give it a try --- */ + + p_txaddr(a, BBASE(&bbb), BLEN(&bbb)); + + /* --- All done --- */ + +done: + if (kpriv) km_unref(kpriv); + if (kpub) km_unref(kpub); +} + +/* --- @dotoken@ --- * + * + * Arguments: @keyexch *kx@ = pointer to key exchange block + * @buf *b@ = buffer containing the packet + * + * Returns: Zero if OK, nonzero of the packet was rejected. + * + * Use: Processes a token message. + */ + +static int dotoken(keyexch *kx, buf *b) +{ + buf bb; + buf *bbb; + const dhgrp *g = kx->kpriv->grp; + octet *p; + size_t sz; + + /* --- Make sure this is a sensible message to have received --- */ + + if (!kx->p->spec.knock) return (-1); + + /* --- First, collect and verify our challenge --- */ + + if (buf_getbuf16(b, &bb) || c_check(0, 0, &bb) || BLEFT(&bb)) return (-1); + + /* --- Start building the knock message from here --- */ + + bbb = p_txstart(kx->p, MSG_KEYEXCH | KX_KNOCK); + + /* --- Copy the peer's challenge --- */ + + if (buf_getbuf16(b, &bb)) return (-1); + buf_putmem16(bbb, BBASE(&bb), BSZ(&bb)); + + /* --- Add the key indicator --- */ + + buf_putu32(bbb, kx->kpub->id); + + /* --- Building the knock payload --- */ + + buf_init(&bb, buf_t, sizeof(buf_t)); + buf_putstr16(&bb, kx->p->spec.knock); + sz = BLEN(&bb)%64; if (sz) sz = 64 - sz; + if (ies_decrypt(kx->kpriv, MSG_KEYEXCH | KX_TOKEN, b, &bb)) return (-1); + p = buf_get(&bb, sz); assert(p); memset(p, 0, sz); + assert(BOK(&bb)); buf_flip(&bb); + if (ies_encrypt(kx->kpub, MSG_KEYEXCH | KX_KNOCK, &bb, bbb)) return (-1); + + /* --- Finally, the pre-challenge group element --- */ + + g->ops->stge(g, bbb, kx->C, DHFMT_VAR); + + /* --- And we're done --- */ + + if (BBAD(bbb)) return (-1); + update_stats_tx(kx, BLEN(bbb)); + p_txend(kx->p); + return (0); +} + /* --- @doprechallenge@ --- * * * Arguments: @keyexch *kx@ = pointer to key exchange block @@ -574,7 +791,6 @@ static void kxc_answer(keyexch *kx, kxchal *kxc) static int doprechallenge(keyexch *kx, buf *b) { - stats *st = p_stats(kx->p); const dhgrp *g = kx->kpriv->grp; dhge *C = 0; ghash *h; @@ -603,8 +819,7 @@ static int doprechallenge(keyexch *kx, buf *b) hashge(h, g, C); sendchallenge(kx, b, C, GH_DONE(h, 0)); GH_DESTROY(h); - st->n_kxout++; - st->sz_kxout += BLEN(b); + update_stats_tx(kx, BLEN(b)); p_txend(kx->p); /* --- Done --- */ @@ -617,6 +832,73 @@ bad: return (-1); } +/* --- @doknock@ --- * + * + * Arguments: @const addr *a@ = sender's address + * @buf *b@ = buffer containing the packet + * + * Returns: --- + * + * Use: Processes a knock message. + */ + +static void doknock(const addr *a, buf *b) +{ + keyexch *kx; + peer *p; + uint32 id; + kdata *kpriv = 0; + char *pname; + size_t sz, msgsz = BLEN(b); + buf bb; + int rc; + + /* --- Read and check the challenge --- */ + + buf_getbuf16(b, &bb); + if (c_check(0, 0, &bb)) goto done; + + /* --- Figure out which private key I'm supposed to use --- */ + + if (buf_getu32(b, &id)) goto done; + if ((kpriv = km_findprivbyid(id)) == 0) goto done; + + /* --- Decrypt and check the peer's name against the token --- */ + + buf_init(&bb, buf_t, sizeof(buf_t)); + if (ies_decrypt(kpriv, MSG_KEYEXCH | KX_KNOCK, b, &bb)) goto done; + assert(BOK(&bb)); buf_flip(&bb); + if ((pname = buf_getmem16(&bb, &sz)) == 0 || + memchr(pname, 0, sz) || + c_check(pname, sz, &bb)) + goto done; + assert(sz < sizeof(buf_t) - ((const octet *)pname - buf_t)); + pname[sz] = 0; + + /* --- If we can't find the peer, then issue a notification --- */ + + if ((p = p_find(pname)) == 0) { + a_notify("KNOCK", "%s", pname, "?ADDR", a, A_END); + goto done; + } + + /* --- Update the peer's address --- */ + + kx = &p->kx; + p_updateaddr(kx->p, a); + + /* --- Now treat the remainder of the message as a pre-challenge --- */ + + notice_message(kx); + rc = doprechallenge(kx, b); + update_stats_rx(kx, !rc, msgsz); + + /* --- All done: clean up --- */ + +done: + if (kpriv) km_unref(kpriv); +} + /* --- @respond@ --- * * * Arguments: @keyexch *kx@ = pointer to key exchange block @@ -823,17 +1105,37 @@ static void resend(keyexch *kx) { kxchal *kxc; buf bb; - stats *st = p_stats(kx->p); struct timeval tv; const dhgrp *g = kx->kpriv->grp; + octet *p; + size_t sz; buf *b; switch (kx->s) { case KXS_CHAL: - T( trace(T_KEYEXCH, "keyexch: sending prechallenge to `%s'", - p_name(kx->p)); ) - b = p_txstart(kx->p, MSG_KEYEXCH | KX_PRECHAL); - g->ops->stge(g, b, kx->C, DHFMT_VAR); + if (!kx->p->spec.knock) { + T( trace(T_KEYEXCH, "keyexch: sending prechallenge to `%s'", + p_name(kx->p)); ) + b = p_txstart(kx->p, MSG_KEYEXCH | KX_PRECHAL); + g->ops->stge(g, b, kx->C, DHFMT_VAR); + } else { + T( trace(T_KEYEXCH, "keyexch: sending token-request to `%s'", + p_name(kx->p)); ) + b = p_txstart(kx->p, MSG_KEYEXCH | KX_TOKENRQ); + + buf_init(&bb, buf_t, sizeof(buf_t)); + c_new(0, 0, &bb); assert(BOK(&bb)); buf_putbuf16(b, &bb); + + buf_putu32(b, kx->kpub->id); + + buf_init(&bb, buf_t, sizeof(buf_t)); + buf_putstr16(&bb, kx->p->spec.knock); + sz = BLEN(&bb)%64; if (sz) sz = 64 - sz; + p = buf_get(&bb, sz); assert(p); memset(p, 0, sz); + assert(BOK(&bb)); buf_flip(&bb); + if (ies_encrypt(kx->kpub, MSG_KEYEXCH | KX_TOKENRQ, &bb, b)) + buf_break(b); + } break; case KXS_COMMIT: T( trace(T_KEYEXCH, "keyexch: sending switch request to `%s'", @@ -863,8 +1165,7 @@ static void resend(keyexch *kx) } if (BOK(b)) { - st->n_kxout++; - st->sz_kxout += BLEN(b); + update_stats_tx(kx, BLEN(b)); p_txend(kx->p); } @@ -1266,69 +1567,48 @@ void kx_start(keyexch *kx, int forcep) /* --- @kx_message@ --- * * * Arguments: @keyexch *kx@ = pointer to key exchange context + * @const addr *a@ = sender's IP address and port * @unsigned msg@ = the message code * @buf *b@ = pointer to buffer containing the packet * - * Returns: --- + * Returns: Nonzero if the sender's address was unknown. * * Use: Reads a packet containing key exchange messages and handles * it. */ -void kx_message(keyexch *kx, unsigned msg, buf *b) +int kx_message(keyexch *kx, const addr *a, unsigned msg, buf *b) { - struct timeval now, tv; - stats *st = p_stats(kx->p); size_t sz = BSZ(b); int rc; - gettimeofday(&now, 0); - rs_reset(&kx->rs); - if (kx->f & KXF_CORK) { - start(kx, now.tv_sec); - rs_time(&kx->rs, &tv, &now); - settimer(kx, &tv); - a_notify("KXSTART", "?PEER", kx->p, A_END); - } - - if (checkpub(kx)) - return; + 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 (!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@ --- * @@ -1517,4 +1797,16 @@ fail_0: return (-1); } +/* --- @kx_init@ --- * + * + * Arguments: --- + * + * Returns: --- + * + * Use: Initializes the key-exchange logic. + */ + +void kx_init(void) + { ratelim_init(&unauth_limit, 20, 500); } + /*----- That's all, folks -------------------------------------------------*/