X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~mdw/git/tripe/blobdiff_plain/410c8acf139e945dce28bbc0c8b17dcfd0815643..13a55605839046f6f42910de713f4a9b6c44dfd4:/keyexch.c diff --git a/keyexch.c b/keyexch.c index 1cf79fc8..ea4748e2 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$ * * Key exchange protocol * @@ -26,79 +26,131 @@ * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ -/*----- Revision history --------------------------------------------------* - * - * $Log: keyexch.c,v $ - * Revision 1.1 2001/02/03 20:26:37 mdw - * Initial checkin. - * - */ - /*----- Header files ------------------------------------------------------*/ #include "tripe.h" +/*----- Brief protocol overview -------------------------------------------* + * + * Let %$G$% be a cyclic group; let %$g$% be a generator of %$G$%, and let + * %$q$% be the order of %$G$%; for a key %$K$%, let %$E_K(\cdot)$% denote + * application of the symmetric packet protocol to a message; let + * %$H(\cdot)$% be the random oracle. Let $\alpha \inr \{0,\ldots,q - 1\}$% + * be Alice's private key; let %$a = g^\alpha$% be her public key; let %$b$% + * be Bob's public key. + * + * At the beginning of the session, Alice chooses + * + * %$\rho_A \inr \{0, \ldots q - 1\}$% + * + * We also have: + * + * %$r_A = g^{\rho_A}$% Alice's challenge + * %$c_A = H(\cookie{cookie}, r_A)$% Alice's cookie + * %$v_A = \rho_A \xor H(\cookie{expected-reply}, r_A, r_B, b^{\rho_A})$% + * Alice's challenge check value + * %$r_B^\alpha = a^{\rho_B}$% Alice's reply + * %$K = r_B^{\rho_A} = r_B^{\rho_A} = g^{\rho_A\rho_B}$% + * Alice and Bob's shared secret key + * %$w_A = H(\cookie{switch-request}, c_A, c_B)$% + * Alice's switch request value + * %$u_A = H(\cookie{switch-confirm}, c_A, c_B)$% + * Alice's switch confirm value + * + * The messages are then: + * + * %$\cookie{kx-pre-challenge}, r_A$% + * Initial greeting. In state @KXS_CHAL@. + * + * %$\cookie{kx-cookie}, r_A, c_B$% + * My table is full but I got your message. + * + * %$\cookie{kx-challenge}, r_A, c_B, v_A$% + * Here's a full challenge for you to answer. + * + * %$\cookie{kx-reply}, c_A, c_B, v_A, E_K(r_B^\alpha))$% + * Challenge accpeted: here's the answer. Commit to my challenge. Move + * to @KXS_COMMIT@. + * + * %$\cookie{kx-switch-rq}, c_A, c_B, E_K(r_B^\alpha, w_A))$% + * Reply received: here's my reply. Committed; send data; move to + * @KXS_SWITCH@. + * + * %$\cookie{kx-switch-ok}, E_K(u_A))$% + * Switch received. Committed; send data; move to @KXS_SWITCH@. + */ + /*----- 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)) +#define T_VALID MIN(2) /* Challenge validity period */ +#define T_RETRY SEC(10) /* Challenge retransmit interval */ -/*----- Main code ---------------------------------------------------------*/ +#define VALIDP(kx, now) ((now) < (kx)->t_valid) + +/*----- Static tables -----------------------------------------------------*/ + +static const char *const pkname[] = { + "pre-challenge", "cookie", "challenge", + "reply", "switch-rq", "switch-ok" +}; -/* --- @hashmp@ --- * +/*----- Various utilities -------------------------------------------------*/ + +/* --- @hashge@ --- * * - * Arguments: @rmd160_ctx *r@ = pointer to hash context - * @mp *m@ = pointer to multiprecision integer + * Arguments: @ghash *h@ = pointer to hash context + * @ge *x@ = pointer to group element * * Returns: --- * - * Use: Adds the hash of a multiprecision integer to the context. - * Corrupts @buf_o@. + * Use: Adds the hash of a group element to the context. Corrupts + * @buf_t@. */ -static void hashmp(rmd160_ctx *r, mp *m) +static void hashge(ghash *h, ge *x) { buf b; - buf_init(&b, buf_o, sizeof(buf_o)); - buf_putmp(&b, m); + buf_init(&b, buf_t, sizeof(buf_t)); + G_TOBUF(gg, &b, x); assert(BOK(&b)); - rmd160_hash(r, BBASE(&b), BLEN(&b)); + GH_HASH(h, BBASE(&b), BLEN(&b)); +} + +/* --- @mpencrypt@, @mpdecrypt@ --- * + * + * Arguments: @mp *d@ = the destination integer + * @mp *x@ = the plaintext/ciphertext integer + * @size_t sz@ = the expected size of the plaintext + * @const octet *k@ = pointer to key material + * + * Returns: The encrypted/decrypted integer. + * + * Use: Encrypts (or decrypts) a multiprecision integer. In fact, + * the title is a bit of a misnomer: we actually compute + * %$x \xor H(k)$%, so it's a random oracle thing rather than an + * encryption thing. + */ + +static mp *mpencrypt(mp *d, mp *x, size_t sz, const octet *k) +{ + gcipher *mgf; + + mgf = GC_INIT(algs.mgf, k, algs.hashsz); + mp_storeb(x, buf_t, sz); + GC_ENCRYPT(mgf, buf_t, buf_t, sz); + GC_DESTROY(mgf); + return (mp_loadb(d, buf_t, sz)); +} + +static mp *mpdecrypt(mp *d, mp *x, size_t sz, const octet *k) +{ + gcipher *mgf; + + mgf = GC_INIT(algs.mgf, k, algs.hashsz); + mp_storeb(x, buf_t, sz); + GC_DECRYPT(mgf, buf_t, buf_t, sz); + GC_DESTROY(mgf); + return (mp_loadb(d, buf_t, sz)); } /* --- @timer@ --- * @@ -116,7 +168,7 @@ static void timer(struct timeval *tv, void *v) keyexch *kx = v; kx->f &= ~KXF_TIMER; T( trace(T_KEYEXCH, "keyexch: timer has popped"); ) - kx_start(kx); + kx_start(kx, 0); } /* --- @settimer@ --- * @@ -140,340 +192,964 @@ 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); + G_DESTROY(gg, kxc->c); + if (kxc->r) G_DESTROY(gg, kxc->r); + mp_drop(kxc->ck); + 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); + kxc->f &= ~KXF_TIMER; +} + +/* --- @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. + */ + +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]); + } + + /* --- Fill in the new structure --- */ + + kxc = CREATE(kxchal); + kxc->c = G_CREATE(gg); + kxc->r = 0; + kxc->ck = MP_NEW; + kxc->ks = 0; + kxc->kx = kx; + kxc->f = 0; + kx->r[i] = kxc; + return (kxc); +} + +/* --- @kxc_bychal@ --- * + * + * Arguments: @keyexch *kx@ = pointer to key exchange block + * @ge *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, ge *c) +{ + unsigned i; + + for (i = 0; i < kx->nr; i++) { + if (G_EQ(gg, 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. + */ + +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, algs.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) + G_TOBUF(gg, b, kx->c); + else + buf_put(b, kx->hc, algs.hashsz); + buf_put(b, kxc->hc, algs.hashsz); + buf_putmp(b, kxc->ck); - /* --- If we've just started, generate a new challenge --- */ + /* --- Maybe send an actual reply, if we have one --- */ - 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 (!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)); + G_TOBUF(gg, &bb, kxc->r); + buf_flip(&bb); + ks_encrypt(kxc->ks, MSG_KEYEXCH | KX_REPLY, &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 + * @ge *c@ = a challenge + * @mp *ck@ = the supplied expected-reply check value + * + * Returns: A pointer to the reply, or null if the reply-hash was wrong. + * + * Use: Computes replies to challenges. + */ + +static ge *getreply(keyexch *kx, ge *c, mp *ck) +{ + ge *r = G_CREATE(gg); + ge *y = G_CREATE(gg); + mp *a = MP_NEW; + ghash *h; + const octet *hh; + int ok; + + G_EXP(gg, r, c, kpriv); + h = GH_INIT(algs.h); + HASH_STRING(h, "tripe-expected-reply"); + hashge(h, c); + hashge(h, kx->c); + hashge(h, r); + hh = GH_DONE(h, 0); + + a = mpdecrypt(MP_NEW, ck, mp_octets(gg->r), hh); + IF_TRACING(T_KEYEXCH, IF_TRACING(T_CRYPTO, { + trace(T_CRYPTO, "crypto: computed reply = %s", gestr(gg, r)); + trace_block(T_CRYPTO, "crypto: computed reply hash", hh, algs.hashsz); + trace(T_CRYPTO, "crypto: recovered log = %s", mpstr(a)); + })) + GH_DESTROY(h); + G_EXP(gg, y, gg->g, a); + ok = G_EQ(gg, y, c); + if (!ok) { + a_warn("KX", "?PEER", kx->p, "bad-expected-reply-log", A_END); 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)); + trace(T_CRYPTO, "crypto: computed challenge = %s", gestr(gg, y)); })) + G_DESTROY(gg, r); + r = 0; } + mp_drop(a); + G_DESTROY(gg, y); + return (r); +} - /* --- If I don't have your challenge, I can't do anything more --- */ +/* --- @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. + */ - if (!kx->your_gx) - return; +static int dochallenge(keyexch *kx, unsigned msg, buf *b) +{ + ge *c = G_CREATE(gg); + mp *ck = MP_NEW; + const octet *hc = 0; + kxchal *kxc; + ghash *h; + + /* --- Ensure that we're in a sensible state --- */ + + if (kx->s != KXS_CHAL) { + a_warn("KX", "?PEER", kx->p, "unexpected", "%s", pkname[msg], A_END); + goto bad; + } + + /* --- Unpack the packet --- */ - /* --- If I've not computed my hash, I should do that --- */ + if (G_FROMBUF(gg, b, c) || + (msg >= KX_COOKIE && (hc = buf_get(b, algs.hashsz)) == 0) || + (msg >= KX_CHAL && (ck = buf_getmp(b)) == 0) || + BLEFT(b)) { + a_warn("KX", "?PEER", kx->p, "invalid", "%s", pkname[msg], A_END); + goto bad; + } + + IF_TRACING(T_KEYEXCH, IF_TRACING(T_CRYPTO, { + trace(T_CRYPTO, "crypto: challenge = %s", gestr(gg, c)); + if (hc) trace_block(T_CRYPTO, "crypto: cookie", hc, algs.hashsz); + if (ck) trace(T_CRYPTO, "crypto: check value = %s", mpstr(ck)); + })) + + /* --- First, handle a bare challenge --- * + * + * If the table is heavily loaded, just emit a cookie and return. + */ - 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; + if (!hc && kx->nr >= KX_THRESH) { + T( trace(T_KEYEXCH, "keyexch: too many challenges -- sending cookie"); ) + a_warn("KX", "?PEER", p_name, "sending-cookie", A_END); + b = p_txstart(kx->p, MSG_KEYEXCH | KX_COOKIE); + G_TOBUF(gg, b, kx->c); + h = GH_INIT(algs.h); + HASH_STRING(h, "tripe-cookie"); + hashge(h, c); + GH_DONE(h, buf_get(b, algs.hashsz)); + GH_DESTROY(h); + p_txend(kx->p); + goto tidy; } - /* --- If I've received a full challenge, answer it --- * + /* --- Discard a packet with an invalid cookie --- */ + + if (hc && memcmp(hc, kx->hc, algs.hashsz) != 0) { + a_warn("KX", "?PEER", "incorrect", "cookie", A_END); + 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; + ge *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 (!ck) + kxc = kxc_new(kx); + else { + if ((r = getreply(kx, c, ck)) == 0) + goto bad; + kxc = kxc_new(kx); + kxc->r = r; } + kxc->c = G_CREATE(gg); + G_COPY(gg, kxc->c, c); + + /* --- Work out the cookie for this challenge --- */ + + h = GH_INIT(algs.h); + HASH_STRING(h, "tripe-cookie"); + hashge(h, kxc->c); + GH_DONE(h, kxc->hc); + GH_DESTROY(h); + + IF_TRACING(T_KEYEXCH, IF_TRACING(T_CRYPTO, { + trace_block(T_CRYPTO, "crypto: computed cookie", kxc->hc, algs.hashsz); + })) + + /* --- Compute the expected-reply hash --- */ + + h = GH_INIT(algs.h); + HASH_STRING(h, "tripe-expected-reply"); + hashge(h, kx->c); + hashge(h, kxc->c); + hashge(h, kx->rx); + hc = GH_DONE(h, 0); + kxc->ck = mpencrypt(MP_NEW, kx->alpha, mp_octets(gg->r), hc); + IF_TRACING(T_KEYEXCH, IF_TRACING(T_CRYPTO, { + trace_block(T_CRYPTO, "crypto: expected-reply hash", hc, algs.hashsz); + trace(T_CRYPTO, "crypto: my reply check = %s", mpstr(kxc->ck)); + })) + GH_DESTROY(h); + + /* --- Work out the shared key --- */ + + r = G_CREATE(gg); + G_EXP(gg, r, c, kx->alpha); + IF_TRACING(T_KEYEXCH, IF_TRACING(T_CRYPTO, { + trace(T_CRYPTO, "crypto: shared secret = %s", gestr(gg, r)); + })) + + /* --- Compute the switch messages --- */ + + h = GH_INIT(algs.h); HASH_STRING(h, "tripe-switch-request"); + hashge(h, kx->c); hashge(h, kxc->c); + GH_DONE(h, kxc->hswrq_out); GH_DESTROY(h); + h = GH_INIT(algs.h); HASH_STRING(h, "tripe-switch-confirm"); + hashge(h, kx->c); hashge(h, kxc->c); + GH_DONE(h, kxc->hswok_out); GH_DESTROY(h); + + h = GH_INIT(algs.h); HASH_STRING(h, "tripe-switch-request"); + hashge(h, kxc->c); hashge(h, kx->c); + GH_DONE(h, kxc->hswrq_in); GH_DESTROY(h); + h = GH_INIT(algs.h); HASH_STRING(h, "tripe-switch-confirm"); + hashge(h, kxc->c); hashge(h, kx->c); + GH_DONE(h, kxc->hswok_in); GH_DESTROY(h); + + IF_TRACING(T_KEYEXCH, IF_TRACING(T_CRYPTO, { + trace_block(T_CRYPTO, "crypto: outbound switch request", + kxc->hswrq_out, algs.hashsz); + trace_block(T_CRYPTO, "crypto: outbound switch confirm", + kxc->hswok_out, algs.hashsz); + trace_block(T_CRYPTO, "crypto: inbound switch request", + kxc->hswrq_in, algs.hashsz); + trace_block(T_CRYPTO, "crypto: inbound switch confirm", + kxc->hswok_in, algs.hashsz); + })) + + /* --- Create a new symmetric keyset --- */ + + buf_init(b, buf_o, sizeof(buf_o)); + G_TOBUF(gg, b, kx->c); x = BLEN(b); + G_TOBUF(gg, b, kxc->c); y = BLEN(b); + G_TOBUF(gg, b, r); z = BLEN(b); + assert(BOK(b)); + + kxc->ks = ks_gen(BBASE(b), x, y, z, kx->p); + G_DESTROY(gg, 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 (ck && !kxc->r) { + ge *r; + if ((r = getreply(kx, c, ck)) == 0) + goto bad; + kxc->r = r; } + + kxc_answer(kx, kxc); + + /* --- Tidy up and go home --- */ + +tidy: + G_DESTROY(gg, c); + mp_drop(ck); + return (0); + +bad: + G_DESTROY(gg, c); + mp_drop(ck); + 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, "keyexch: sending prechallenge to `%s'", + p_name(kx->p)); ) + b = p_txstart(kx->p, MSG_KEYEXCH | KX_PRECHAL); + G_TOBUF(gg, b, kx->c); + break; + case KXS_COMMIT: + T( trace(T_KEYEXCH, "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, algs.hashsz); + buf_put(b, kxc->hc, algs.hashsz); + buf_init(&bb, buf_i, sizeof(buf_i)); + G_TOBUF(gg, &bb, kxc->r); + buf_put(&bb, kxc->hswrq_out, algs.hashsz); + buf_flip(&bb); + ks_encrypt(kxc->ks, MSG_KEYEXCH | KX_SWITCH, &bb, b); + break; + case KXS_SWITCH: + T( trace(T_KEYEXCH, "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, algs.hashsz); + buf_flip(&bb); + ks_encrypt(kxc->ks, MSG_KEYEXCH | KX_SWITCHOK, &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 + * @unsigned ty@ = type of incoming message + * @const octet *hc_in@ = a hash of his challenge + * @const octet *hc_out@ = a hash of my challenge (cookie) + * @mp *ck@ = 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, unsigned ty, const octet *hc_in, + const octet *hc_out, mp *ck, buf *b) { - buf *b; + kxchal *kxc; + buf bb; + ge *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, algs.hashsz); + trace_block(T_CRYPTO, "crypto: cookie", hc_out, algs.hashsz); + if (ck) trace(T_CRYPTO, "crypto: check value = %s", mpstr(ck)); + })) + if (memcmp(hc_out, kx->hc, algs.hashsz) != 0) { + a_warn("KX", "?PEER", kx->p, "incorrect", "cookie", A_END); + goto bad; + } + if ((kxc = kxc_byhc(kx, hc_in)) == 0) { + a_warn("KX", "?PEER", kx->p, "unknown-challenge", A_END); + 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 (!ck) { + a_warn("KX", "?PEER", kx->p, "unexpected", "switch-rq", A_END); + goto bad; + } + if ((r = getreply(kx, kxc->c, ck)) == 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, ty, b, &bb)) { + a_warn("KX", "?PEER", kx->p, "decrypt-failed", "reply", A_END); + goto bad; + } + buf_init(b, BBASE(&bb), BLEN(&bb)); + r = G_CREATE(gg); + if (G_FROMBUF(gg, b, r)) { + a_warn("KX", "?PEER", kx->p, "invalid", "reply", A_END); + goto bad; + } + IF_TRACING(T_KEYEXCH, IF_TRACING(T_CRYPTO, { + trace(T_CRYPTO, "crypto: reply = %s", gestr(gg, r)); + })) + if (!G_EQ(gg, r, kx->rx)) { + a_warn("KX", "?PEER", kx->p, "incorrect", "reply", A_END); + 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; + + /* --- Done --- */ + + G_DESTROY(gg, r); + return (kxc); + +bad: + if (r) G_DESTROY(gg, 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: Zero if OK, nonzero if the packet was rejected. + * + * Use: Handles a reply packet. This doesn't handle the various + * switch packets: they're rather too different. + */ + +static int doreply(keyexch *kx, buf *b) +{ + const octet *hc_in, *hc_out; + mp *ck = 0; + kxchal *kxc; + + if (kx->s != KXS_CHAL && kx->s != KXS_COMMIT) { + a_warn("KX", "?PEER", kx->p, "unexpected", "reply", A_END); + goto bad; + } + if ((hc_in = buf_get(b, algs.hashsz)) == 0 || + (hc_out = buf_get(b, algs.hashsz)) == 0 || + (ck = buf_getmp(b)) == 0) { + a_warn("KX", "?PEER", kx->p, "invalid", "reply", A_END); + goto bad; + } + if ((kxc = matchreply(kx, MSG_KEYEXCH | KX_REPLY, + hc_in, hc_out, ck, b)) == 0) + goto bad; + if (BLEFT(b)) { + a_warn("KX", "?PEER", kx->p, "invalid", "reply", A_END); + goto bad; + } + if (kx->s == KXS_CHAL) { + commit(kx, kxc); + kx->s = KXS_COMMIT; + } + resend(kx); + return (0); + +bad: + mp_drop(ck); + return (-1); +} + +/* --- @kxfinish@ --- * + * + * Arguments: @keyexch *kx@ = pointer to key exchange block * * Returns: --- * - * Use: Common code for handling challenge messages. The caller - * should have successfullly unpacked the challenge structure. + * Use: Sets everything up following a successful key exchange. */ -static void dochallenge(keyexch *kx, time_t now, mp *m, const octet *h) +static void kxfinish(keyexch *kx) { - 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 + kxchal *kxc = kx->r[0]; + ks_activate(kxc->ks); + settimer(kx, ks_tregen(kxc->ks)); + kx->s = KXS_SWITCH; + a_notify("KXDONE", "?PEER", kx->p, A_END); + p_stats(kx->p)->t_kx = time(0); } -/* --- @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: Zero if OK, nonzero if the packet was rejected. + * + * Use: Handles a reply with a switch request bolted onto it. + */ + +static int doswitch(keyexch *kx, buf *b) +{ + const octet *hc_in, *hc_out, *hswrq; + kxchal *kxc; + + if ((hc_in = buf_get(b, algs.hashsz)) == 0 || + (hc_out = buf_get(b, algs.hashsz)) == 0) { + a_warn("KX", "?PEER", kx->p, "invalid", "switch-rq", A_END); + goto bad; + } + if ((kxc = matchreply(kx, MSG_KEYEXCH | KX_SWITCH, + hc_in, hc_out, 0, b)) == 0) + goto bad; + if ((hswrq = buf_get(b, algs.hashsz)) == 0 || BLEFT(b)) { + a_warn("KX", "?PEER", "invalid", "switch-rq", A_END); + goto bad; + } + IF_TRACING(T_KEYEXCH, { + trace_block(T_CRYPTO, "crypto: switch request hash", hswrq, algs.hashsz); + }) + if (memcmp(hswrq, kxc->hswrq_in, algs.hashsz) != 0) { + a_warn("KX", "?PEER", kx->p, "incorrect", "switch-rq", A_END); + goto bad; + } + switch (kx->s) { + case KXS_CHAL: + commit(kx, kxc); + case KXS_COMMIT: + kxfinish(kx); + break; + } + resend(kx); + return (0); + +bad: + return (-1); +} + +/* --- @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) +{ + const octet *hswok; + kxchal *kxc; + buf bb; + + if (kx->s < KXS_COMMIT) { + a_warn("KX", "?PEER", kx->p, "unexpected", "switch-ok", A_END); + goto bad; + } + kxc = kx->r[0]; + buf_init(&bb, buf_o, sizeof(buf_o)); + if (ks_decrypt(kxc->ks, MSG_KEYEXCH | KX_SWITCHOK, b, &bb)) { + a_warn("KX", "?PEER", kx->p, "decrypt-failed", "switch-ok", A_END); + goto bad; + } + buf_init(b, BBASE(&bb), BLEN(&bb)); + if ((hswok = buf_get(b, algs.hashsz)) == 0 || BLEFT(b)) { + a_warn("KX", "?PEER", "invalid", "switch-ok", A_END); + goto bad; + } + IF_TRACING(T_KEYEXCH, { + trace_block(T_CRYPTO, "crypto: switch confirmation hash", + hswok, algs.hashsz); + }) + if (memcmp(hswok, kxc->hswok_in, algs.hashsz) != 0) { + a_warn("KX", "?PEER", kx->p, "incorrect", "switch-ok", A_END); + goto bad; + } + if (kx->s < KXS_SWITCH) + kxfinish(kx); + return (0); + +bad: + return (-1); +} + +/*----- Main code ---------------------------------------------------------*/ + +/* --- @stop@ --- * + * + * Arguments: @keyexch *kx@ = pointer to key exchange context * * Returns: --- * - * Use: Handle prechallenges and challenges. + * 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). */ -void kx_prechallenge(keyexch *kx, buf *b) +static void stop(keyexch *kx) { - time_t now = time(0); - mp *m; + unsigned i; - if ((m = buf_getmp(b, MP_NEW)) == 0 || BLEFT(b)) { - a_warn("malformed prechallenge from `%s'", p_name(kx->p)); - goto tidy; + if (kx->f & KXF_DEAD) + return; + + 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); + G_DESTROY(gg, kx->c); + G_DESTROY(gg, kx->rx); + kx->t_valid = 0; + kx->f |= KXF_DEAD; + kx->f &= ~KXF_TIMER; +} + +/* --- @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) +{ + ghash *h; + + assert(kx->f & KXF_DEAD); + + kx->f &= ~KXF_DEAD; + kx->nr = 0; + kx->alpha = mprand_range(MP_NEW, gg->r, &rand_global, 0); + kx->c = G_CREATE(gg); G_EXP(gg, kx->c, gg->g, kx->alpha); + kx->rx = G_CREATE(gg); G_EXP(gg, kx->rx, kx->kpub, kx->alpha); + kx->s = KXS_CHAL; + kx->t_valid = now + T_VALID; + + h = GH_INIT(algs.h); + HASH_STRING(h, "tripe-cookie"); + hashge(h, kx->c); + GH_DONE(h, kx->hc); + GH_DESTROY(h); + + 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", gestr(gg, kx->c)); + trace(T_CRYPTO, "crypto: expected response = %s", gestr(gg, kx->rx)); + trace_block(T_CRYPTO, "crypto: challenge cookie", kx->hc, algs.hashsz); + }) + }) +} + +/* --- @checkpub@ --- * + * + * Arguments: @keyexch *kx@ = pointer to key exchange context + * + * Returns: Zero if OK, nonzero if the peer's public key has expired. + * + * Use: Deactivates the key-exchange until the peer acquires a new + * public key. + */ + +static int checkpub(keyexch *kx) +{ + time_t now; + if (kx->f & KXF_DEAD) + return (-1); + now = time(0); + if (KEY_EXPIRED(now, kx->texp_kpub)) { + stop(kx); + a_warn("KX", "?PEER", kx->p, "public-key-expired", A_END); + G_COPY(gg, kx->kpub, gg->i); + kx->f &= ~KXF_PUBKEY; + return (-1); } - dochallenge(kx, now, m, 0); -tidy: - mp_drop(m); + return (0); } -void kx_challenge(keyexch *kx, buf *b) +/* --- @kx_start@ --- * + * + * Arguments: @keyexch *kx@ = pointer to key exchange context + * @int forcep@ = nonzero to ignore the quiet timer + * + * 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. + */ + +void kx_start(keyexch *kx, int forcep) { time_t now = time(0); - mp *m = 0; - const octet *h; - 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 (checkpub(kx)) + return; + if (forcep || !VALIDP(kx, now)) { + stop(kx); + start(kx, now); + a_notify("KXSTART", "?PEER", kx->p, A_END); } - dochallenge(kx, now, m, h); - resend_resp(kx, now); -tidy: - mp_drop(m); + resend(kx); } -/* --- @kx_response@ --- * +/* --- @kx_message@ --- * * * Arguments: @keyexch *kx@ = pointer to key exchange context - * @buf *b@ = a buffer containing the packet to read + * @unsigned msg@ = the message code + * @buf *b@ = pointer to buffer containing the packet * * Returns: --- * - * Use: Reads a response from the buffer and handles it. + * Use: Reads a packet containing key exchange messages and handles + * it. */ -void kx_response(keyexch *kx, buf *b) +void kx_message(keyexch *kx, unsigned msg, buf *b) { time_t now = time(0); - mp *m; + stats *st = p_stats(kx->p); + size_t sz = BSZ(b); + int rc; - 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)) - INITCTX(kx, now); - if (!(kx->f & KXF_MYH)) { - a_warn("premature response from `%s'", p_name(kx->p)); - goto tidy; + if (checkpub(kx)) + return; + + if (!VALIDP(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("KX", "?PEER", kx->p, "unknown-message", "0x%02x", msg, A_END); + 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,10 +1163,8 @@ tidy: void kx_free(keyexch *kx) { - if (kx->f & KXF_TIMER) - sel_rmtimer(&kx->t); - FREECTX(kx); - dh_pubfree(&kx->kpub); + stop(kx); + G_DESTROY(gg, kx->kpub); } /* --- @kx_newkeys@ --- * @@ -507,16 +1181,15 @@ void kx_free(keyexch *kx) void kx_newkeys(keyexch *kx) { - dh_pub dp; - - if (km_getpubkey(p_name(kx->p), &dp)) + if (km_getpubkey(p_name(kx->p), kx->kpub, &kx->texp_kpub)) return; - dh_pubfree(&kx->kpub); - kx->kpub = dp; - if (!(kx->f & KXF_DONE)) { + kx->f |= KXF_PUBKEY; + if ((kx->f & KXF_DEAD) || kx->s != KXS_SWITCH) { T( trace(T_KEYEXCH, "keyexch: restarting key negotiation with `%s'", p_name(kx->p)); ) - INITCTX(kx, time(0)); + stop(kx); + start(kx, time(0)); + resend(kx); } } @@ -537,11 +1210,15 @@ 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)) + kx->kpub = G_CREATE(gg); + if (km_getpubkey(p_name(p), kx->kpub, &kx->texp_kpub)) { + G_DESTROY(gg, kx->kpub); return (-1); - kx->t_valid = 0; - kx_start(kx); + } + kx->f = KXF_DEAD | KXF_PUBKEY; + start(kx, time(0)); + resend(kx); + /* Don't notify here: the ADD message hasn't gone out yet. */ return (0); }