X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~mdw/git/tripe/blobdiff_plain/aa7f0bd9fd354398adddcf7f20c3715a058fcc6a..0617b6e7bd26c53349e9f457cdf86ccd6a4e8fbf:/keyexch.c diff --git a/keyexch.c b/keyexch.c index 1cf79fc8..1eb6981a 100644 --- a/keyexch.c +++ b/keyexch.c @@ -1,6 +1,6 @@ /* -*-c-*- * - * $Id: keyexch.c,v 1.1 2001/02/03 20:26:37 mdw Exp $ + * $Id: keyexch.c,v 1.2 2001/02/16 21:24:27 mdw Exp $ * * Key exchange protocol * @@ -29,6 +29,9 @@ /*----- Revision history --------------------------------------------------* * * $Log: keyexch.c,v $ + * Revision 1.2 2001/02/16 21:24:27 mdw + * Rewrite for new key exchange protocol. + * * Revision 1.1 2001/02/03 20:26:37 mdw * Initial checkin. * @@ -41,64 +44,30 @@ /*----- Tunable parameters ------------------------------------------------*/ #define T_VALID MIN(2) -#define T_QUIET SEC(5) #define T_RETRY SEC(10) -#define T_NEWCHAL SEC(5) - -/*----- Handy macros ------------------------------------------------------*/ - -#define FREECTX(kx) do { \ - keyexch *_kkx = (kx); \ - if (_kkx->f & KXF_INIT) { \ - mp_drop(_kkx->my_x); mp_drop(_kkx->my_gx); mp_drop(_kkx->my_gxy); \ - mp_drop(_kkx->your_gx); mp_drop(_kkx->your_gxy); \ - } \ -} while (0) - -#define INITCTX(kx, now) do { \ - keyexch *_kx = (kx); \ - time_t _now = (now); \ - FREECTX(_kx); \ - kx->my_x = kx->my_gx = kx->my_gxy = 0; \ - kx->your_gx = kx->your_gxy = 0; \ - kx->t_valid = _now + T_VALID; \ - kx->t_qchal = kx->t_qresp = 0; \ - kx->t_newchal = 0; \ - kx->f = (kx->f | KXF_INIT) & ~(KXF_MYH | KXF_YOURH | \ - KXF_REPLY | KXF_DONE); \ -} while (0) - -#define NEWCHAL(kx) do { \ - kx->f &= ~(KXF_YOURH | KXF_MYH); \ - mp_drop(kx->your_gx); kx->your_gx = 0; \ - mp_drop(kx->your_gxy); kx->your_gxy = 0; \ -} while (0) - -#define ISVALID(kx, now) ((kx)->t_valid > (now)) -#define ISQ_CHAL(kx, now) ((kx)->t_qchal > (now)) -#define ISQ_RESP(kx, now) ((kx)->t_qresp > (now)) -#define ISNEWCHAL(kx, now) ((kx)->t_newchal > (now)) -/*----- Main code ---------------------------------------------------------*/ +#define ISVALID(kx, now) ((now) < (kx)->t_valid) + +/*----- Various utilities -------------------------------------------------*/ /* --- @hashmp@ --- * * - * Arguments: @rmd160_ctx *r@ = pointer to hash context + * Arguments: @HASH_CTX *r@ = pointer to hash context * @mp *m@ = pointer to multiprecision integer * * Returns: --- * * Use: Adds the hash of a multiprecision integer to the context. - * Corrupts @buf_o@. + * Corrupts @buf_t@. */ -static void hashmp(rmd160_ctx *r, mp *m) +static void hashmp(HASH_CTX *r, mp *m) { buf b; - buf_init(&b, buf_o, sizeof(buf_o)); + buf_init(&b, buf_t, sizeof(buf_t)); buf_putmp(&b, m); assert(BOK(&b)); - rmd160_hash(r, BBASE(&b), BLEN(&b)); + HASH(r, BBASE(&b), BLEN(&b)); } /* --- @timer@ --- * @@ -140,340 +109,872 @@ static void settimer(keyexch *kx, time_t t) kx->f |= KXF_TIMER; } -/* --- @update@ --- * +/*----- Challenge management ----------------------------------------------*/ + +/* --- Notes on challenge management --- * * - * Arguments: @keyexch *kx@ = pointer to key exchange context + * We may get multiple different replies to our key exchange; some will be + * correct, some inserted by attackers. Up until @KX_THRESH@, all challenges + * received will be added to the table and given a full response. After + * @KX_THRESH@ distinct challenges are received, we return only a `cookie': + * our existing challenge, followed by a hash of the sender's challenge. We + * do %%\emph{not}%% give a bare challenge a reply slot at this stage. All + * properly-formed cookies are assigned a table slot: if none is spare, a + * used slot is randomly selected and destroyed. A cookie always receives a + * full reply. + */ + +/* --- @kxc_destroy@ --- * + * + * Arguments: @kxchal *kxc@ = pointer to the challenge block * * Returns: --- * - * Use: Updates the information in the key exchange context. Call - * this after new information has arrived. Expects that the - * context is actually valid. Doesn't send any packets. - * Assumes that everything in the context is known to be - * correct. + * Use: Disposes of a challenge block. */ -static void update(keyexch *kx) +static void kxc_destroy(kxchal *kxc) { - rmd160_ctx r; - octet h[RMD160_HASHSZ]; - mp *k_shared; - buf b; + if (kxc->f & KXF_TIMER) + sel_rmtimer(&kxc->t); + mp_drop(kxc->c); + mp_drop(kxc->r); + ks_drop(kxc->ks); + DESTROY(kxc); +} - /* --- Give up if there's nothing more to do --- */ +/* --- @kxc_stoptimer@ --- * + * + * Arguments: @kxchal *kxc@ = pointer to the challenge block + * + * Returns: --- + * + * Use: Stops the challenge's retry timer from sending messages. + * Useful when the state machine is in the endgame of the + * exchange. + */ - if (kx->f & KXF_DONE) - return; +static void kxc_stoptimer(kxchal *kxc) +{ + if (kxc->f & KXF_TIMER) + sel_rmtimer(&kxc->t); +} - /* --- If we've just started, generate a new challenge --- */ +/* --- @kxc_new@ --- * + * + * Arguments: @keyexch *kx@ = pointer to key exchange block + * @ + * + * Returns: A pointer to the challenge block. + * + * Use: Returns a pointer to a new challenge block to fill in. + */ - if (!kx->my_x) { - T( trace(T_KEYEXCH, "keyexch: generating new challenge"); ) - kx->my_x = mprand_range(MP_NEWSEC, kx->kpub.dp.q, &rand_global, 0); - kx->my_gx = mpmont_exp(&mg, MP_NEW, kx->kpub.dp.g, kx->my_x); - kx->my_gxy = mpmont_exp(&mg, MP_NEW, kx->kpub.y, kx->my_x); - IF_TRACING(T_KEYEXCH, IF_TRACING(T_CRYPTO, { - trace(T_CRYPTO, "crypto: secret = %s", mpstr(kx->my_x)); - trace(T_CRYPTO, "crypto: public value = %s", mpstr(kx->my_gx)); - trace(T_CRYPTO, "crypto: expected reply = %s", mpstr(kx->my_gxy)); - })) +static kxchal *kxc_new(keyexch *kx) +{ + kxchal *kxc; + unsigned i; + + /* --- If we're over reply threshold, discard one at random --- */ + + if (kx->nr < KX_NCHAL) + i = kx->nr++; + else { + i = rand_global.ops->range(&rand_global, KX_NCHAL); + kxc_destroy(kx->r[i]); } - /* --- If I don't have your challenge, I can't do anything more --- */ + /* --- Fill in the new structure --- */ - if (!kx->your_gx) - return; + kxc = CREATE(kxchal); + kxc->c = 0; + kxc->r = 0; + kxc->ks = 0; + kxc->kx = kx; + kxc->f = 0; + kx->r[i] = kxc; + return (kxc); +} - /* --- If I've not computed my hash, I should do that --- */ +/* --- @kxc_bychal@ --- * + * + * Arguments: @keyexch *kx@ = pointer to key exchange block + * @mp *c@ = challenge from remote host + * + * Returns: Pointer to the challenge block, or null. + * + * Use: Finds a challenge block, given its challenge. + */ + +static kxchal *kxc_bychal(keyexch *kx, mp *c) +{ + unsigned i; + + for (i = 0; i < kx->nr; i++) { + if (MP_EQ(c, kx->r[i]->c)) + return (kx->r[i]); + } + return (0); +} + +/* --- @kxc_byhc@ --- * + * + * Arguments: @keyexch *kx@ = pointer to key exchange block + * @const octet *hc@ = challenge hash from remote host + * + * Returns: Pointer to the challenge block, or null. + * + * Use: Finds a challenge block, given a hash of its challenge. + */ - if (!(kx->f & KXF_MYH)) { - T( trace(T_KEYEXCH, "keyexch: computing my hash"); ) - rmd160_init(&r); - hashmp(&r, kx->my_gx); - hashmp(&r, kx->your_gx); - hashmp(&r, kx->my_gxy); - rmd160_done(&r, kx->my_h); - IF_TRACING(T_KEYEXCH, trace_block(T_CRYPTO, "crypto: my hash", - kx->my_h, sizeof(kx->my_h)); ) - kx->f |= KXF_MYH; +static kxchal *kxc_byhc(keyexch *kx, const octet *hc) +{ + unsigned i; + + for (i = 0; i < kx->nr; i++) { + if (memcmp(hc, kx->r[i]->hc, HASHSZ) == 0) + return (kx->r[i]); } + return (0); +} + +/* --- @kxc_answer@ --- * + * + * Arguments: @keyexch *kx@ = pointer to key exchange block + * @kxchal *kxc@ = pointer to challenge block + * + * Returns: --- + * + * Use: Sends a reply to the remote host, according to the data in + * this challenge block. + */ + +static void kxc_answer(keyexch *kx, kxchal *kxc); + +static void kxc_timer(struct timeval *tv, void *v) +{ + kxchal *kxc = v; + kxc->f &= ~KXF_TIMER; + kxc_answer(kxc->kx, kxc); +} + +static void kxc_answer(keyexch *kx, kxchal *kxc) +{ + stats *st = p_stats(kx->p); + buf *b = p_txstart(kx->p, MSG_KEYEXCH | (kxc->r ? KX_REPLY : KX_CHAL)); + struct timeval tv; + buf bb; + + /* --- Build the reply packet --- */ + + if (!kxc->r) + buf_putmp(b, kx->c); + else + buf_put(b, kx->hc, HASHSZ); + buf_put(b, kxc->hc, HASHSZ); + buf_put(b, kxc->hrx, HASHSZ); + + /* --- Maybe send an actual reply, if we have one --- */ - /* --- If I've received a full challenge, answer it --- * + if (!kxc->r) { + T( trace(T_KEYEXCH, "keyexch: resending challenge to `%s'", + p_name(kx->p)); ) + } else { + T( trace(T_KEYEXCH, "keyexch: sending reply to `%s'", p_name(kx->p)); ) + buf_init(&bb, buf_i, sizeof(buf_i)); + buf_putmp(&bb, kxc->r); + buf_flip(&bb); + ks_encrypt(kxc->ks, &bb, b); + } + + /* --- Update the statistics --- */ + + if (BOK(b)) { + st->n_kxout++; + st->sz_kxout += BLEN(b); + p_txend(kx->p); + } + + /* --- Schedule another resend --- */ + + if (kxc->f & KXF_TIMER) + sel_rmtimer(&kxc->t); + gettimeofday(&tv, 0); + tv.tv_sec += T_RETRY; + sel_addtimer(&sel, &kxc->t, &tv, kxc_timer, kxc); + kxc->f |= KXF_TIMER; +} + +/*----- Individual message handlers ---------------------------------------*/ + +/* --- @getreply@ --- * + * + * Arguments: @keyexch *kx@ = pointer to key exchange context + * @mp *c@ = a challenge + * @const octet *hrx@ = the supplied expected-reply hash + * + * Returns: A pointer to the reply, or null if the reply-hash was wrong. + * + * Use: Computes replies to challenges. + */ + +static mp *getreply(keyexch *kx, mp *c, const octet *hrx) +{ + mp *r = mpmont_exp(&mg, MP_NEW, c, kpriv.x); + HASH_CTX h; + octet buf[HASHSZ]; + + HASH_INIT(&h); + HASH_STRING(&h, "tripe-expected-reply"); + hashmp(&h, c); + hashmp(&h, kx->c); + hashmp(&h, r); + HASH_DONE(&h, buf); + IF_TRACING(T_KEYEXCH, IF_TRACING(T_CRYPTO, { + trace(T_CRYPTO, "crypto: computed reply = %s", mpstr(r)); + trace_block(T_CRYPTO, "crypto: computed reply hash", buf, HASHSZ); + })) + if (memcmp(buf, hrx, HASHSZ) != 0) { + a_warn("invalid expected-reply hash from `%s'", p_name(kx->p)); + mp_drop(r); + return (0); + } + return (r); +} + +/* --- @dochallenge@ --- * + * + * Arguments: @keyexch *kx@ = pointer to key exchange block + * @unsigned msg@ = message code for the packet + * @buf *b@ = buffer containing the packet + * + * Returns: Zero if OK, nonzero if the packet was rejected. + * + * Use: Processes a packet containing a challenge. + */ + +static int dochallenge(keyexch *kx, unsigned msg, buf *b) +{ + mp *c = 0; + const octet *hc = 0, *hrx = 0; + kxchal *kxc; + HASH_CTX h; + + /* --- Ensure that we're in a sensible state --- */ + + if (kx->s != KXS_CHAL) { + a_warn("unexpected challenge from `%s'", p_name(kx->p)); + goto bad; + } + + /* --- Unpack the packet --- */ + + if ((c = buf_getmp(b)) == 0 || + (msg >= KX_COOKIE && (hc = buf_get(b, HASHSZ)) == 0) || + (msg >= KX_CHAL && (hrx = buf_get(b, HASHSZ)) == 0) || + BLEFT(b)) { + a_warn("malformed packet from `%s'", p_name(kx->p)); + goto bad; + } + + IF_TRACING(T_KEYEXCH, IF_TRACING(T_CRYPTO, { + trace(T_CRYPTO, "crypto: challenge = %s", mpstr(c)); + if (hc) trace_block(T_CRYPTO, "crypto: cookie", hc, HASHSZ); + if (hrx) trace_block(T_CRYPTO, "crypto: response hash", hrx, HASHSZ); + })) + + /* --- First, handle a bare challenge --- * + * + * If the table is heavily loaded, just emit a cookie and return. + */ + + if (!hc && kx->nr >= KX_THRESH) { + T( trace(T_KEYEXCH, "keyexch: too many challenges -- sending cookie"); ) + b = p_txstart(kx->p, MSG_KEYEXCH | KX_COOKIE); + buf_putmp(b, kx->c); + HASH_INIT(&h); + HASH_STRING(&h, "tripe-cookie"); + hashmp(&h, c); + HASH_DONE(&h, buf_get(b, HASHSZ)); + p_txend(kx->p); + goto tidy; + } + + /* --- Discard a packet with an invalid cookie --- */ + + if (hc && memcmp(hc, kx->hc, HASHSZ) != 0) { + a_warn("incorrect cookie from `%s'", p_name(kx->p)); + goto bad; + } + + /* --- Find a challenge block for this packet --- * * - * If it turns out to be wrong, clear the appropriate bits of data. + * If there isn't one already, create a new one. */ - if ((kx->f & KXF_YOURH) && !kx->your_gxy) { - kx->your_gxy = mpmont_exp(&mg, MP_NEW, kx->your_gx, kpriv.x); - rmd160_init(&r); - hashmp(&r, kx->your_gx); - hashmp(&r, kx->my_gx); - hashmp(&r, kx->your_gxy); - rmd160_done(&r, h); - IF_TRACING(T_KEYEXCH, trace_block(T_CRYPTO, "crypto: computed hash", - h, sizeof(h)); ) - if (memcmp(h, kx->your_h, sizeof(h)) != 0) { - IF_TRACING(T_KEYEXCH, { - trace_block(T_CRYPTO, "crypto: expected hash", - kx->your_h, sizeof(kx->your_h)); - trace(T_KEYEXCH, "keyexch: hashes don't match: botched"); - }) - NEWCHAL(kx); - return; + if ((kxc = kxc_bychal(kx, c)) == 0) { + size_t x, y, z; + mp *r; + + /* --- Be careful here --- * + * + * If this is a full challenge, and it's the first time I've seen it, I + * want to be able to throw it away before committing a table entry to + * it. + */ + + if (!hrx) + kxc = kxc_new(kx); + else { + if ((r = getreply(kx, c, hrx)) == 0) + goto bad; + kxc = kxc_new(kx); + kxc->r = r; } + kxc->c = mp_copy(c); + + /* --- Work out the cookie for this challenge --- */ + + HASH_INIT(&h); + HASH_STRING(&h, "tripe-cookie"); + hashmp(&h, kxc->c); + HASH_DONE(&h, kxc->hc); + + /* --- Compute the expected-reply hash --- */ + + HASH_INIT(&h); + HASH_STRING(&h, "tripe-expected-reply"); + hashmp(&h, kx->c); + hashmp(&h, kxc->c); + hashmp(&h, kx->rx); + HASH_DONE(&h, kxc->hrx); + + /* --- Work out the shared key --- */ + + r = mpmont_exp(&mg, MP_NEW, c, kx->alpha); + + /* --- Compute the switch messages --- */ + + HASH_INIT(&h); HASH_STRING(&h, "tripe-switch-request"); + hashmp(&h, kx->c); hashmp(&h, kxc->c); + HASH_DONE(&h, kxc->hswrq_out); + HASH_INIT(&h); HASH_STRING(&h, "tripe-switch-confirm"); + hashmp(&h, kx->c); hashmp(&h, kxc->c); + HASH_DONE(&h, kxc->hswok_out); + + HASH_INIT(&h); HASH_STRING(&h, "tripe-switch-request"); + hashmp(&h, kxc->c); hashmp(&h, kx->c); + HASH_DONE(&h, kxc->hswrq_in); + HASH_INIT(&h); HASH_STRING(&h, "tripe-switch-confirm"); + hashmp(&h, kxc->c); hashmp(&h, kx->c); + HASH_DONE(&h, kxc->hswok_in); + + IF_TRACING(T_KEYEXCH, IF_TRACING(T_CRYPTO, { + trace_block(T_CRYPTO, "crypto: computed cookie", kxc->hc, HASHSZ); + trace_block(T_CRYPTO, "crypto: my reply hash", kxc->hrx, HASHSZ); + trace(T_CRYPTO, "crypto: shared secret = %s", mpstr(r)); + trace_block(T_CRYPTO, "crypto: outbound switch request", + kxc->hswrq_out, HASHSZ); + trace_block(T_CRYPTO, "crypto: outbound switch confirm", + kxc->hswok_out, HASHSZ); + trace_block(T_CRYPTO, "crypto: inbound switch request", + kxc->hswrq_in, HASHSZ); + trace_block(T_CRYPTO, "crypto: inbound switch confirm", + kxc->hswok_in, HASHSZ); + })) + + /* --- Create a new symmetric keyset --- */ + + buf_init(b, buf_o, sizeof(buf_o)); + buf_putmp(b, kx->c); x = BLEN(b); + buf_putmp(b, kxc->c); y = BLEN(b); + buf_putmp(b, r); z = BLEN(b); + assert(BOK(b)); + + kxc->ks = ks_gen(BBASE(b), x, y, z); + mp_drop(r); } - /* --- If I have a good reply, compute a shared key --- */ + /* --- Answer the challenge if we need to --- */ - if ((kx->f & KXF_YOURH) && (kx->f & KXF_REPLY)) { - k_shared = mpmont_exp(&mg, MP_NEW, kx->your_gx, kx->my_x); - IF_TRACING(T_KEYEXCH, { - trace(T_KEYEXCH, "keyexch: computed shared key"); - trace(T_CRYPTO, "crypto: shared key = %s", mpstr(k_shared)); - }) - buf_init(&b, buf_o, sizeof(buf_o)); - buf_putmp(&b, k_shared); assert(BOK(&b)); - settimer(kx, ks_gen(kx->ks, BBASE(&b), BLEN(&b))); - mp_drop(k_shared); - BURN(buf_o); - kx->f |= KXF_DONE; + if (hrx && !kxc->r) { + mp *r; + if ((r = getreply(kx, c, hrx)) == 0) + goto bad; + kxc->r = r; } + + kxc_answer(kx, kxc); + + /* --- Tidy up and go home --- */ + +tidy: + mp_drop(c); + return (0); + +bad: + mp_drop(c); + return (-1); } -/* --- @resend_chal@, @resent_resp@ --- * +/* --- @resend@ --- * * * Arguments: @keyexch *kx@ = pointer to key exchange context - * @time_t now@ = the time right now * * Returns: --- * - * Use: Sends packets to the remote host, according to the various - * timers and information available. + * Use: Sends the next message for a key exchange. */ -void resend_chal(keyexch *kx, time_t now) +static void resend(keyexch *kx) { + kxchal *kxc; + buf bb; + stats *st = p_stats(kx->p); buf *b; - if (kx->f & KXF_DONE) - return; - if (ISQ_CHAL(kx, now)) { - T( trace(T_KEYEXCH, "keyexch: not sending a new challenge yet"); ) - return; + switch (kx->s) { + case KXS_CHAL: + T( trace(T_KEYEXCH, "sending prechallenge to `%s'", p_name(kx->p)); ) + b = p_txstart(kx->p, MSG_KEYEXCH | KX_PRECHAL); + buf_putmp(b, kx->c); + break; + case KXS_COMMIT: + T( trace(T_KEYEXCH, "sending switch request to `%s'", p_name(kx->p)); ) + kxc = kx->r[0]; + b = p_txstart(kx->p, MSG_KEYEXCH | KX_SWITCH); + buf_put(b, kx->hc, HASHSZ); + buf_put(b, kxc->hc, HASHSZ); + buf_init(&bb, buf_i, sizeof(buf_i)); + buf_putmp(&bb, kxc->r); + buf_put(&bb, kxc->hswrq_out, HASHSZ); + buf_flip(&bb); + ks_encrypt(kxc->ks, &bb, b); + break; + case KXS_SWITCH: + T( trace(T_KEYEXCH, "sending switch confirmation to `%s'", + p_name(kx->p)); ) + kxc = kx->r[0]; + b = p_txstart(kx->p, MSG_KEYEXCH | KX_SWITCHOK); + buf_init(&bb, buf_i, sizeof(buf_i)); + buf_put(&bb, kxc->hswok_out, HASHSZ); + buf_flip(&bb); + ks_encrypt(kxc->ks, &bb, b); + break; + default: + abort(); } - if (!kx->your_gx) { - T( trace(T_KEYEXCH, "keyexch: sending prechallenge"); ) - b = p_txstart(kx->p, MSG_PRECHALLENGE); - } else { - T( trace(T_KEYEXCH, "keyexch: sending challenge"); ) - b = p_txstart(kx->p, MSG_CHALLENGE); - buf_put(b, kx->my_h, sizeof(kx->my_h)); - } - buf_putmp(b, kx->my_gx); - p_txend(kx->p); - kx->t_qchal = now + T_QUIET; - settimer(kx, now + T_RETRY); + + if (BOK(b)) { + st->n_kxout++; + st->sz_kxout += BLEN(b); + p_txend(kx->p); + } + + if (kx->s < KXS_SWITCH) + settimer(kx, time(0) + T_RETRY); } -void resend_resp(keyexch *kx, time_t now) +/* --- @matchreply@ --- * + * + * Arguments: @keyexch *kx@ = pointer to key exchange context + * @const octet *hc_in@ = a hash of his challenge + * @const octet *hc_out@ = a hash of my challenge (cookie) + * @const octet *krx@ = his expected-reply hash (optional) + * @buf *b@ = encrypted remainder of the packet + * + * Returns: A pointer to the challenge block if OK, or null on failure. + * + * Use: Checks a reply or switch packet, ensuring that its contents + * are sensible and correct. If they are, @*b@ is set to point + * to the remainder of the encrypted data, and the correct + * challenge is returned. + */ + +static kxchal *matchreply(keyexch *kx, const octet *hc_in, + const octet *hc_out, const octet *hrx, buf *b) { - buf *b; + kxchal *kxc; + buf bb; + mp *r = 0; + + /* --- Check the plaintext portions of the data --- */ + + IF_TRACING(T_KEYEXCH, IF_TRACING(T_CRYPTO, { + trace_block(T_CRYPTO, "crypto: challenge", hc_in, HASHSZ); + trace_block(T_CRYPTO, "crypto: cookie", hc_out, HASHSZ); + if (hrx) trace_block(T_CRYPTO, "crypto: response hash", hrx, HASHSZ); + })) + if (memcmp(hc_out, kx->hc, HASHSZ) != 0) { + a_warn("incorrect cookie from `%s'", p_name(kx->p)); + goto bad; + } + if ((kxc = kxc_byhc(kx, hc_in)) == 0) { + a_warn("received reply for unknown challenge from `%s'", p_name(kx->p)); + goto bad; + } - if (!kx->your_gxy) - return; - if (ISQ_RESP(kx, now)) { - T( trace(T_KEYEXCH, "keyexch: not sending a new response yet"); ) - return; + /* --- Maybe compute a reply for the challenge --- */ + + if (!kxc->r) { + if (!hrx) { + a_warn("unexpected switch request from `%s'", p_name(kx->p)); + goto bad; + } + if ((r = getreply(kx, kxc->c, hrx)) == 0) + goto bad; + kxc->r = r; + r = 0; + } + + /* --- Decrypt the rest of the packet --- */ + + buf_init(&bb, buf_o, sizeof(buf_o)); + if (ks_decrypt(kxc->ks, b, &bb)) { + a_warn("failed to decrypt reply from `%s'", p_name(kx->p)); + goto bad; } - T( trace(T_KEYEXCH, "keyexch: sending response"); ) - b = p_txstart(kx->p, MSG_RESPONSE); - buf_putmp(b, kx->your_gxy); - p_txend(kx->p); - kx->t_qresp = now + T_QUIET; + buf_init(b, BBASE(&bb), BLEN(&bb)); + if ((r = buf_getmp(b)) == 0) { + a_warn("invalid reply packet from `%s'", p_name(kx->p)); + goto bad; + } + IF_TRACING(T_KEYEXCH, IF_TRACING(T_CRYPTO, { + trace(T_CRYPTO, "crypto: reply = %s", mpstr(r)); + })) + if (!mp_eq(r, kx->rx)) { + a_warn("incorrect reply from `%s'", p_name(kx->p)); + goto bad; + } + + /* --- Done --- */ + + mp_drop(r); + return (kxc); + +bad: + mp_drop(r); + return (0); } -/* --- @kx_start@ --- * +/* --- @commit@ --- * * * Arguments: @keyexch *kx@ = pointer to key exchange context + * @kxchal *kxc@ = pointer to challenge to commit to * * Returns: --- * - * Use: Stimulates a key exchange. If a key exchage is in progress, - * a new challenge is sent (unless the quiet timer forbids - * this); if no exchange is in progress, one is commenced. + * Use: Commits to a particular challenge as being the `right' one, + * since a reply has arrived for it. */ -void kx_start(keyexch *kx) +static void commit(keyexch *kx, kxchal *kxc) { - time_t now = time(0); + unsigned i; - if (!ISVALID(kx, now)) - INITCTX(kx, now); - update(kx); - resend_chal(kx, now); + for (i = 0; i < kx->nr; i++) { + if (kx->r[i] != kxc) + kxc_destroy(kx->r[i]); + } + kx->r[0] = kxc; + kx->nr = 1; + kxc_stoptimer(kxc); + ksl_link(kx->ks, kxc->ks); } -/* --- @dochallenge@ --- * +/* --- @doreply@ --- * * * Arguments: @keyexch *kx@ = pointer to key exchange context - * @time_t now@ = the current time - * @mp *m@ = new challenge received - * @const octet *h@ = challenge hash (if any) + * @buf *b@ = buffer containing packet * - * Returns: --- + * Returns: Zero if OK, nonzero if the packet was rejected. * - * Use: Common code for handling challenge messages. The caller - * should have successfullly unpacked the challenge structure. + * Use: Handles a reply packet. This doesn't handle the various + * switch packets: they're rather too different. */ -static void dochallenge(keyexch *kx, time_t now, mp *m, const octet *h) +static int doreply(keyexch *kx, buf *b) { - unsigned f = 0; -#define f_newchal 1u -#define f_newhash 2u -#define f_new (f_newchal | f_newhash) -#define f_match 4u -#define f_good (f_new | f_match) -#define f_ignore 8u -#define f_reset 16u -#define f_change (f_new | f_reset) - - /* --- Restart the process if necessary --- */ - - if (!ISVALID(kx, now)) - INITCTX(kx, now); - if (!ISNEWCHAL(kx, now)) - f |= f_ignore; - - /* --- Sort out what to actually do --- */ - - if (!kx->your_gx) { - f |= f_newchal; - if (h) - f |= f_newhash; - } else if (mp_eq(kx->your_gx, m)) { - if (!h || memcmp(h, kx->your_h, sizeof(kx->your_h)) == 0) - f |= f_match; - else if (!(kx->f & KXF_YOURH)) - f |= f_newhash; - } - - /* --- Update the values in the context --- */ - - if (f & f_good) - f &= ~f_ignore; - else if (!(f & f_ignore)) { - NEWCHAL(kx); - f |= f_reset; - } - if (f & (f_newchal | f_reset)) - kx->your_gx = MP_COPY(m); - if (f & (f_newhash | f_reset)) { - memcpy(kx->your_h, h, sizeof(kx->your_h)); - kx->f |= KXF_YOURH; - } - if (f & f_new) - kx->t_qchal = 0; - - if (!(f & f_good)) { - a_warn("%s nonmatching challenge from `%s'", - (f & f_ignore) ? "rejecting" : "accepting", p_name(kx->p)); - } else { - T( trace(T_KEYEXCH, "keyexch: good challenge (%s, %s) from `%s'", - (f & f_newchal) ? "new gxy" : "match gxy", - (f & f_newhash) ? "new hash" : "match hash", p_name(kx->p)); ) - } - if (f & f_change) - update(kx); - - if (f & f_new) - resend_chal(kx, now); -#undef f_newchal -#undef f_newhash -#undef f_new -#undef f_match -#undef f_good -#undef f_ignore -#undef f_change + const octet *hc_in, *hc_out, *hrx; + kxchal *kxc; + + if (kx->s != KXS_CHAL && kx->s != KXS_COMMIT) { + a_warn("unexpected reply from `%s'", p_name(kx->p)); + goto bad; + } + if ((hc_in = buf_get(b, HASHSZ)) == 0 || + (hc_out = buf_get(b, HASHSZ)) == 0 || + (hrx = buf_get(b, HASHSZ)) == 0) { + a_warn("invalid reply packet from `%s'", p_name(kx->p)); + goto bad; + } + if ((kxc = matchreply(kx, hc_in, hc_out, hrx, b)) == 0) + goto bad; + if (BLEFT(b)) { + a_warn("invalid reply packet from `%s'", p_name(kx->p)); + goto bad; + } + if (kx->s == KXS_CHAL) { + commit(kx, kxc); + kx->s = KXS_COMMIT; + } + resend(kx); + return (0); + +bad: + return (-1); } -/* --- @kx_prechallenge@, @kx_challenge@ --- * +/* --- @doswitch@ --- * * - * Arguments: @keyexch *kx@ = pointer to key exhange context - * @buf *b@ = pointer to buffer containing the packet + * Arguments: @keyexch *kx@ = pointer to key exchange block + * @buf *b@ = pointer to buffer containing packet * - * Returns: --- + * Returns: Zero if OK, nonzero if the packet was rejected. * - * Use: Handle prechallenges and challenges. + * Use: Handles a reply with a switch request bolted onto it. */ -void kx_prechallenge(keyexch *kx, buf *b) +static int doswitch(keyexch *kx, buf *b) { - time_t now = time(0); - mp *m; + const octet *hc_in, *hc_out, *hswrq; + kxchal *kxc; - if ((m = buf_getmp(b, MP_NEW)) == 0 || BLEFT(b)) { - a_warn("malformed prechallenge from `%s'", p_name(kx->p)); - goto tidy; + if ((hc_in = buf_get(b, HASHSZ)) == 0 || + (hc_out = buf_get(b, HASHSZ)) == 0) { + a_warn("invalid switch request from `%s'", p_name(kx->p)); + goto bad; } - dochallenge(kx, now, m, 0); -tidy: - mp_drop(m); + if ((kxc = matchreply(kx, hc_in, hc_out, 0, b)) == 0) + goto bad; + if ((hswrq = buf_get(b, HASHSZ)) == 0 || BLEFT(b)) { + a_warn("invalid switch request from `%s'", p_name(kx->p)); + goto bad; + } + IF_TRACING(T_KEYEXCH, { + trace_block(T_CRYPTO, "crypto: switch request hash", hswrq, HASHSZ); + }) + if (memcmp(hswrq, kxc->hswrq_in, HASHSZ) != 0) { + a_warn("incorrect switch request hash from `%s'", p_name(kx->p)); + goto bad; + } + switch (kx->s) { + case KXS_CHAL: + commit(kx, kxc); + case KXS_COMMIT: + ks_activate(kxc->ks); + settimer(kx, ks_tregen(kxc->ks)); + kx->s = KXS_SWITCH; + break; + } + resend(kx); + return (0); + +bad: + return (-1); } -void kx_challenge(keyexch *kx, buf *b) +/* --- @doswitchok@ --- * + * + * Arguments: @keyexch *kx@ = pointer to key exchange block + * @buf *b@ = pointer to buffer containing packet + * + * Returns: Zero if OK, nonzero if the packet was rejected. + * + * Use: Handles a reply with a switch request bolted onto it. + */ + +static int doswitchok(keyexch *kx, buf *b) { - time_t now = time(0); - mp *m = 0; - const octet *h; + const octet *hswok; + kxchal *kxc; + buf bb; - if (buf_ensure(b, RMD160_HASHSZ) || - (h = BCUR(b), BSTEP(b, RMD160_HASHSZ), - (m = buf_getmp(b, MP_NEW)) == 0 || BLEFT(b))) { - a_warn("malformed challenge from `%s'", p_name(kx->p)); - goto tidy; + if (kx->s < KXS_COMMIT) { + a_warn("unexpected switch confirmation from `%s'", p_name(kx->p)); + goto bad; } - dochallenge(kx, now, m, h); - resend_resp(kx, now); -tidy: - mp_drop(m); + kxc = kx->r[0]; + buf_init(&bb, buf_o, sizeof(buf_o)); + if (ks_decrypt(kxc->ks, b, &bb)) { + a_warn("failed to decrypt switch confirmation from `%s'", p_name(kx->p)); + goto bad; + } + buf_init(b, BBASE(&bb), BLEN(&bb)); + if ((hswok = buf_get(b, HASHSZ)) == 0 || BLEFT(b)) { + a_warn("invalid switch confirmation from `%s'", p_name(kx->p)); + goto bad; + } + IF_TRACING(T_KEYEXCH, { + trace_block(T_CRYPTO, "crypto: switch confirmation hash", hswok, HASHSZ); + }) + if (memcmp(hswok, kxc->hswok_in, HASHSZ) != 0) { + a_warn("incorrect switch confirmation hash from `%s'", p_name(kx->p)); + goto bad; + } + if (kx->s < KXS_SWITCH) { + ks_activate(kxc->ks); + settimer(kx, ks_tregen(kxc->ks)); + kx->s = KXS_SWITCH; + } + return (0); + +bad: + return (-1); +} + +/*----- Main code ---------------------------------------------------------*/ + +/* --- @stop@ --- * + * + * Arguments: @keyexch *kx@ = pointer to key exchange context + * + * Returns: --- + * + * Use: Stops a key exchange dead in its tracks. Throws away all of + * the context information. The context is left in an + * inconsistent state. The only functions which understand this + * state are @kx_free@ and @kx_init@ (which cause it internally + * it), and @start@ (which expects it to be the prevailing + * state). + */ + +static void stop(keyexch *kx) +{ + unsigned i; + + if (kx->f & KXF_TIMER) + sel_rmtimer(&kx->t); + for (i = 0; i < kx->nr; i++) + kxc_destroy(kx->r[i]); + mp_drop(kx->alpha); + mp_drop(kx->c); + mp_drop(kx->rx); +} + +/* --- @start@ --- * + * + * Arguments: @keyexch *kx@ = pointer to key exchange context + * @time_t now@ = the current time + * + * Returns: --- + * + * Use: Starts a new key exchange with the peer. The context must be + * in the bizarre state left by @stop@ or @kx_init@. + */ + +static void start(keyexch *kx, time_t now) +{ + HASH_CTX h; + + kx->nr = 0; + kx->f = 0; + kx->alpha = mprand_range(MP_NEW, kpriv.dp.q, &rand_global, 0); + kx->c = mpmont_exp(&mg, MP_NEW, kpriv.dp.g, kx->alpha); + kx->rx = mpmont_exp(&mg, MP_NEW, kx->kpub.y, kx->alpha); + kx->s = KXS_CHAL; + kx->t_valid = now + T_VALID; + + HASH_INIT(&h); + HASH_STRING(&h, "tripe-cookie"); + hashmp(&h, kx->c); + HASH_DONE(&h, kx->hc); + + IF_TRACING(T_KEYEXCH, { + trace(T_KEYEXCH, "keyexch: creating new challenge"); + IF_TRACING(T_CRYPTO, { + trace(T_CRYPTO, "crypto: secret = %s", mpstr(kx->alpha)); + trace(T_CRYPTO, "crypto: challenge = %s", mpstr(kx->c)); + trace(T_CRYPTO, "crypto: expected response = %s", mpstr(kx->rx)); + trace_block(T_CRYPTO, "crypto: challenge cookie", kx->hc, HASHSZ); + }) + }) } -/* --- @kx_response@ --- * +/* --- @kx_start@ --- * * * Arguments: @keyexch *kx@ = pointer to key exchange context - * @buf *b@ = a buffer containing the packet to read * * Returns: --- * - * Use: Reads a response from the buffer and handles it. + * Use: Stimulates a key exchange. If a key exchage is in progress, + * a new challenge is sent (unless the quiet timer forbids + * this); if no exchange is in progress, one is commenced. */ -void kx_response(keyexch *kx, buf *b) +void kx_start(keyexch *kx) { time_t now = time(0); - mp *m; - if ((m = buf_getmp(b, MP_NEW)) == 0 || BLEFT(b)) { - a_warn("malformed response from `%s'", p_name(kx->p)); - goto tidy; + if (!ISVALID(kx, now)) { + stop(kx); + start(kx, now); } - if (!ISVALID(kx, now)) - INITCTX(kx, now); - if (!(kx->f & KXF_MYH)) { - a_warn("premature response from `%s'", p_name(kx->p)); - goto tidy; + resend(kx); +} + +/* --- @kx_message@ --- * + * + * Arguments: @keyexch *kx@ = pointer to key exchange context + * @unsigned msg@ = the message code + * @buf *b@ = pointer to buffer containing the packet + * + * Returns: --- + * + * Use: Reads a packet containing key exchange messages and handles + * it. + */ + +void kx_message(keyexch *kx, unsigned msg, buf *b) +{ + time_t now = time(0); + stats *st = p_stats(kx->p); + size_t sz = BSZ(b); + int rc; + +#ifndef NTRACE + static const char *const pkname[] = { + "prechallenge", "cookie", "challenge", + "reply", "switch request", "switch confirmation" + }; +#endif + + if (!ISVALID(kx, now)) { + stop(kx); + start(kx, now); } - if (!mp_eq(m, kx->my_gxy)) { - a_warn("incorrect response from `%s'", p_name(kx->p)); - goto tidy; + + T( trace(T_KEYEXCH, "keyexch: processing %s packet from `%s'", + msg < KX_NMSG ? pkname[msg] : "unknown", p_name(kx->p)); ) + + switch (msg) { + case KX_PRECHAL: + case KX_COOKIE: + case KX_CHAL: + rc = dochallenge(kx, msg, 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("unexpected key exchange message type %u from `%p'", + p_name(kx->p)); + rc = -1; + break; } - T( trace(T_KEYEXCH, "keyexch: valid response from `%s'", p_name(kx->p)); ) - kx->f |= KXF_REPLY; - update(kx); -tidy: - mp_drop(m); + if (rc) + st->n_reject++; + else { + st->n_kxin++; + st->sz_kxin += sz; + } } /* --- @kx_free@ --- * @@ -487,9 +988,7 @@ tidy: void kx_free(keyexch *kx) { - if (kx->f & KXF_TIMER) - sel_rmtimer(&kx->t); - FREECTX(kx); + stop(kx); dh_pubfree(&kx->kpub); } @@ -513,10 +1012,11 @@ void kx_newkeys(keyexch *kx) return; dh_pubfree(&kx->kpub); kx->kpub = dp; - if (!(kx->f & KXF_DONE)) { + if (kx->s != KXS_SWITCH) { T( trace(T_KEYEXCH, "keyexch: restarting key negotiation with `%s'", p_name(kx->p)); ) - INITCTX(kx, time(0)); + kx->t_valid = 0; + kx_start(kx); } } @@ -537,11 +1037,10 @@ int kx_init(keyexch *kx, peer *p, keyset **ks) { kx->ks = ks; kx->p = p; - kx->f = 0; if (km_getpubkey(p_name(p), &kx->kpub)) return (-1); - kx->t_valid = 0; - kx_start(kx); + start(kx, time(0)); + resend(kx); return (0); }