/* -*-c-*-
*
- * $Id: keyexch.c,v 1.1 2001/02/03 20:26:37 mdw Exp $
+ * $Id$
*
* Key exchange protocol
*
* 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@ --- *
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]);
+ }
- /* --- If we've just started, generate a new challenge --- */
+ /* --- 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.
+ */
- 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);
+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);
+
+ /* --- Maybe send an actual reply, if we have one --- */
+
+ 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 %s bad-expected-reply-log", p_name(kx->p));
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 %s unexpected %s", p_name(kx->p), pkname[msg]);
+ goto bad;
+ }
+
+ /* --- Unpack the packet --- */
+
+ 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 %s invalid %s", p_name(kx->p), pkname[msg]);
+ 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 I've not computed my hash, I should do that --- */
+ if (!hc && kx->nr >= KX_THRESH) {
+ T( trace(T_KEYEXCH, "keyexch: too many challenges -- sending cookie"); )
+ a_warn("KX %s sending-cookie", p_name(kx->p));
+ 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;
+ }
+
+ /* --- Discard a packet with an invalid cookie --- */
- 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 && memcmp(hc, kx->hc, algs.hashsz) != 0) {
+ a_warn("KX %s incorrect cookie", p_name(kx->p));
+ goto bad;
}
- /* --- If I've received a full challenge, answer it --- *
+ /* --- 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 %s incorrect cookie", p_name(kx->p));
+ goto bad;
+ }
+ if ((kxc = kxc_byhc(kx, hc_in)) == 0) {
+ a_warn("KX %s unknown-challenge", 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 (!ck) {
+ a_warn("KX %s unexpected switch-rq", p_name(kx->p));
+ 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 %s decrypt-failed reply", 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));
+ r = G_CREATE(gg);
+ if (G_FROMBUF(gg, b, r)) {
+ a_warn("KX %s invalid reply", p_name(kx->p));
+ 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 %s incorrect reply", p_name(kx->p));
+ goto bad;
+ }
+
+ /* --- 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 %s unexpected-reply", p_name(kx->p));
+ 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 %s invalid reply", p_name(kx->p));
+ 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 %s invalid reply", p_name(kx->p));
+ 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 %s", p_name(kx->p));
+ 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 %s invalid switch-rq", p_name(kx->p));
+ 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 %s invalid switch-rq", p_name(kx->p));
+ 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 %s incorrect switch-rq", p_name(kx->p));
+ 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 %s unexpected switch-ok", p_name(kx->p));
+ 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 %s decrypt-failed switch-ok", p_name(kx->p));
+ goto bad;
+ }
+ buf_init(b, BBASE(&bb), BLEN(&bb));
+ if ((hswok = buf_get(b, algs.hashsz)) == 0 || BLEFT(b)) {
+ a_warn("KX %s invalid switch-ok", p_name(kx->p));
+ 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 %s incorrect switch-ok", p_name(kx->p));
+ 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 %s public-key-expired", p_name(kx->p));
+ 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
+ *
+ * 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)
{
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 (!VALIDP(kx, now)) {
+ stop(kx);
+ start(kx, now);
+ a_notify("KXSTART %s", p_name(kx->p));
}
- 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 %s unknown-message 0x%02x", p_name(kx->p), msg);
+ 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@ --- *
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@ --- *
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);
}
}
{
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);
}