/* -*-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
*
/*----- 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.
*
/*----- 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@ --- *
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@ --- *
void kx_free(keyexch *kx)
{
- if (kx->f & KXF_TIMER)
- sel_rmtimer(&kx->t);
- FREECTX(kx);
+ stop(kx);
dh_pubfree(&kx->kpub);
}
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);
}
}
{
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);
}