/* -*-c-*-
*
- * $Id: keyexch.c,v 1.13 2004/04/18 18:08:11 mdw Exp $
+ * $Id$
*
* Key exchange protocol
*
*
* %$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})$%
+ * %$v_A = \rho_A \xor H(\cookie{expected-reply}, a, 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}$%
* %$\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))$%
+ * %$\cookie{kx-reply}, r_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}, c_A, c_B, E_K(r_B^\alpha, w_A))$%
+ * %$\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@.
*
#define T_VALID MIN(2) /* Challenge validity period */
#define T_RETRY SEC(10) /* Challenge retransmit interval */
-#define ISVALID(kx, now) ((now) < (kx)->t_valid)
+#define VALIDP(kx, now) ((now) < (kx)->t_valid)
+
+/*----- Static tables -----------------------------------------------------*/
+
+static const char *const pkname[] = {
+ "pre-challenge", "cookie", "challenge",
+ "reply", "switch-rq", "switch-ok"
+};
/*----- Various utilities -------------------------------------------------*/
GH_HASH(h, BBASE(&b), BLEN(&b));
}
-/* --- @mpencrypt@, @mpdecrypt@ --- *
+/* --- @mpmask@ --- *
*
- * Arguments: @mp *d@ = the destination integer
- * @mp *x@ = the plaintext/ciphertext integer
- * @size_t sz@ = the expected size of the plaintext
+ * Arguments: @buf *b@ = output buffer
+ * @mp *x@ = the plaintext integer
+ * @size_t n@ = the expected size of the plaintext
* @const octet *k@ = pointer to key material
+ * @size_t ksz@ = size of the key
*
- * Returns: The encrypted/decrypted integer.
+ * Returns: Pointer to the output.
*
- * 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.
+ * Use: Masks a multiprecision integer: returns %$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)
+static octet *mpmask(buf *b, mp *x, size_t n, const octet *k, size_t ksz)
{
gcipher *mgf;
+ octet *p;
- mgf = GC_INIT(algs.mgf, k, algs.hashsz);
- mp_storeb(x, buf_t, sz);
- GC_ENCRYPT(mgf, buf_t, buf_t, sz);
+ if ((p = buf_get(b, n)) == 0)
+ return (0);
+ mgf = GC_INIT(algs.mgf, k, ksz);
+ IF_TRACING(T_KEYEXCH, IF_TRACING(T_CRYPTO, {
+ trace(T_CRYPTO, "masking index = %s", mpstr(x));
+ trace_block(T_CRYPTO, "masking key", k, ksz);
+ }))
+ mp_storeb(x, buf_t, n);
+ GC_ENCRYPT(mgf, buf_t, p, n);
+ IF_TRACING(T_KEYEXCH, IF_TRACING(T_CRYPTO, {
+ trace_block(T_CRYPTO, "index plaintext", buf_t, n);
+ trace_block(T_CRYPTO, "masked ciphertext", p, n);
+ }))
GC_DESTROY(mgf);
- return (mp_loadb(d, buf_t, sz));
+ return (p);
}
-static mp *mpdecrypt(mp *d, mp *x, size_t sz, const octet *k)
+/* --- @mpunmask@ --- *
+ *
+ * Arguments: @mp *d@ = the output integer
+ * @const octet *p@ = pointer to the ciphertext
+ * @size_t n@ = the size of the ciphertext
+ * @const octet *k@ = pointer to key material
+ * @size_t ksz@ = size of the key
+ *
+ * Returns: The decrypted integer, or null.
+ *
+ * Use: Unmasks a multiprecision integer.
+ */
+
+static mp *mpunmask(mp *d, const octet *p, size_t n,
+ const octet *k, size_t ksz)
{
gcipher *mgf;
- mgf = GC_INIT(algs.mgf, k, algs.hashsz);
- mp_storeb(x, buf_t, sz);
- GC_DECRYPT(mgf, buf_t, buf_t, sz);
+ mgf = GC_INIT(algs.mgf, k, ksz);
+ IF_TRACING(T_KEYEXCH, IF_TRACING(T_CRYPTO, {
+ trace_block(T_CRYPTO, "unmasking key", k, ksz);
+ trace_block(T_CRYPTO, "masked ciphertext", p, n);
+ }))
+ GC_DECRYPT(mgf, p, buf_t, n);
+ d = mp_loadb(d, buf_t, n);
+ IF_TRACING(T_KEYEXCH, IF_TRACING(T_CRYPTO, {
+ trace_block(T_CRYPTO, "index plaintext", buf_t, n);
+ trace(T_CRYPTO, "unmasked index = %s", mpstr(d));
+ }))
GC_DESTROY(mgf);
- return (mp_loadb(d, buf_t, sz));
+ return (d);
+}
+
+/* --- @hashcheck@ --- *
+ *
+ * Arguments: @ge *kpub@ = sender's public key
+ * @ge *cc@ = receiver's challenge
+ * @ge *c@ = sender's challenge
+ * @ge *y@ = reply to sender's challenge
+ *
+ * Returns: Pointer to the hash value (in @buf_t@)
+ *
+ * Use: Computes the check-value hash, used to mask or unmask
+ * indices to prove the validity of challenges. This computes
+ * the masking key used in challenge check values. This is
+ * really the heart of the whole thing, since it ensures that
+ * the index can be recovered from the history of hashing
+ * queries, which gives us (a) a proof that the authentication
+ * process is zero-knowledge, and (b) a proof that the whole
+ * key-exchange is deniable.
+ */
+
+static const octet *hashcheck(ge *kpub, ge *cc, ge *c, ge *y)
+{
+ ghash *h = GH_INIT(algs.h);
+
+ HASH_STRING(h, "tripe-expected-reply");
+ hashge(h, kpub);
+ hashge(h, cc);
+ hashge(h, c);
+ hashge(h, y);
+ GH_DONE(h, buf_t);
+ IF_TRACING(T_KEYEXCH, IF_TRACING(T_CRYPTO, {
+ trace(T_CRYPTO, "computing challenge check hash");
+ trace(T_CRYPTO, "public key = %s", gestr(gg, kpub));
+ trace(T_CRYPTO, "receiver challenge = %s", gestr(gg, cc));
+ trace(T_CRYPTO, "sender challenge = %s", gestr(gg, c));
+ trace(T_CRYPTO, "sender reply = %s", gestr(gg, y));
+ trace_block(T_CRYPTO, "hash output", buf_t, algs.hashsz);
+ }))
+ GH_DESTROY(h);
+ return (buf_t);
+}
+
+/* --- @sendchallenge@ --- *
+ *
+ * Arguments: @keyexch *kx@ = pointer to key exchange block
+ * @buf *b@ = output buffer for challenge
+ * @ge *c@ = peer's actual challenge
+ * @const octet *hc@ = peer's challenge cookie
+ *
+ * Returns: ---
+ *
+ * Use: Writes a full challenge to the message buffer.
+ */
+
+static void sendchallenge(keyexch *kx, buf *b, ge *c, const octet *hc)
+{
+ G_TOBUF(gg, b, kx->c);
+ buf_put(b, hc, algs.hashsz);
+ mpmask(b, kx->alpha, indexsz,
+ hashcheck(kpub, c, kx->c, kx->rx), algs.hashsz);
}
/* --- @timer@ --- *
keyexch *kx = v;
kx->f &= ~KXF_TIMER;
T( trace(T_KEYEXCH, "keyexch: timer has popped"); )
- kx_start(kx);
+ kx_start(kx, 0);
}
/* --- @settimer@ --- *
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);
+ G_DESTROY(gg, kxc->r);
ks_drop(kxc->ks);
DESTROY(kxc);
}
kxc = CREATE(kxchal);
kxc->c = G_CREATE(gg);
- kxc->r = 0;
- kxc->ck = MP_NEW;
+ kxc->r = G_CREATE(gg);
kxc->ks = 0;
kxc->kx = kx;
kxc->f = 0;
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));
+ buf *b = p_txstart(kx->p, MSG_KEYEXCH | KX_REPLY);
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);
- }
+ T( trace(T_KEYEXCH, "keyexch: sending reply to `%s'", p_name(kx->p)); )
+ sendchallenge(kx, b, kxc->c, kxc->hc);
+ buf_init(&bb, buf_i, sizeof(buf_i));
+ G_TORAW(gg, &bb, kxc->r);
+ buf_flip(&bb);
+ ks_encrypt(kxc->ks, MSG_KEYEXCH | KX_REPLY, &bb, b);
/* --- Update the statistics --- */
/*----- Individual message handlers ---------------------------------------*/
-/* --- @getreply@ --- *
+/* --- @doprechallenge@ --- *
*
- * Arguments: @keyexch *kx@ = pointer to key exchange context
- * @ge *c@ = a challenge
- * @mp *ck@ = the supplied expected-reply check value
+ * Arguments: @keyexch *kx@ = pointer to key exchange block
+ * @buf *b@ = buffer containing the packet
*
- * Returns: A pointer to the reply, or null if the reply-hash was wrong.
+ * Returns: Zero if OK, nonzero of the packet was rejected.
*
- * Use: Computes replies to challenges.
+ * Use: Processes a pre-challenge message.
*/
-static ge *getreply(keyexch *kx, ge *c, mp *ck)
+static int doprechallenge(keyexch *kx, buf *b)
{
- ge *r = G_CREATE(gg);
- ge *y = G_CREATE(gg);
- mp *a = MP_NEW;
+ stats *st = p_stats(kx->p);
+ ge *c = G_CREATE(gg);
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);
+ /* --- Ensure that we're in a sensible state --- */
+
+ if (kx->s != KXS_CHAL) {
+ a_warn("KX", "?PEER", kx->p, "unexpected", "pre-challenge", A_END);
+ goto bad;
+ }
+
+ /* --- Unpack the packet --- */
+
+ if (G_FROMBUF(gg, b, c) || BLEFT(b))
+ goto bad;
- 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));
+ trace(T_CRYPTO, "crypto: challenge = %s", gestr(gg, c));
}))
+
+ /* --- Send out a full challenge by return --- */
+
+ b = p_txstart(kx->p, MSG_KEYEXCH | KX_CHAL);
+ h = GH_INIT(algs.h);
+ HASH_STRING(h, "tripe-cookie");
+ hashge(h, c);
+ sendchallenge(kx, b, c, GH_DONE(h, 0));
GH_DESTROY(h);
- G_EXP(gg, y, gg->g, a);
- ok = G_EQ(gg, y, c);
- if (!ok) {
- a_warn("invalid expected-reply check from `%s'", p_name(kx->p));
- IF_TRACING(T_KEYEXCH, IF_TRACING(T_CRYPTO, {
- 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);
+ st->n_kxout++;
+ st->sz_kxout += BLEN(b);
+ p_txend(kx->p);
+
+ /* --- Done --- */
+
+ G_DESTROY(gg, c);
+ return (0);
+
+bad:
+ if (c) G_DESTROY(gg, c);
+ return (-1);
}
-/* --- @dochallenge@ --- *
+/* --- @respond@ --- *
*
* Arguments: @keyexch *kx@ = pointer to key exchange block
- * @unsigned msg@ = message code for the packet
+ * @unsigned msg@ = message code for this packet
* @buf *b@ = buffer containing the packet
*
- * Returns: Zero if OK, nonzero if the packet was rejected.
+ * Returns: Key-exchange challenge block, or null.
*
- * Use: Processes a packet containing a challenge.
+ * Use: Computes a response for the given challenge, entering it into
+ * a challenge block and so on.
*/
-static int dochallenge(keyexch *kx, unsigned msg, buf *b)
+static kxchal *respond(keyexch *kx, unsigned msg, buf *b)
{
ge *c = G_CREATE(gg);
- mp *ck = MP_NEW;
- const octet *hc = 0;
+ ge *r = G_CREATE(gg);
+ ge *cc = G_CREATE(gg);
+ const octet *hc, *ck;
+ size_t x, y, z;
+ mp *cv = 0;
kxchal *kxc;
- ghash *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;
- }
+ ghash *h = 0;
+ buf bb;
+ int ok;
/* --- 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("malformed packet from `%s'", p_name(kx->p));
+ (hc = buf_get(b, algs.hashsz)) == 0 ||
+ (ck = buf_get(b, indexsz)) == 0) {
+ 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));
+ trace_block(T_CRYPTO, "crypto: cookie", hc, algs.hashsz);
+ trace_block(T_CRYPTO, "crypto: check-value", ck, indexsz);
}))
- /* --- 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);
- 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 (hc && memcmp(hc, kx->hc, algs.hashsz) != 0) {
- a_warn("incorrect cookie from `%s'", p_name(kx->p));
+ a_warn("KX", "?PEER", "incorrect", "cookie", A_END);
goto bad;
}
- /* --- Find a challenge block for this packet --- *
+ /* --- Recover the check value and verify it --- *
*
- * If there isn't one already, create a new one.
+ * To avoid recomputation on replays, we store a hash of the `right'
+ * value. The `correct' value is unique, so this is right.
+ *
+ * This will also find a challenge block and, if necessary, populate it.
*/
- 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);
+ if ((kxc = kxc_bychal(kx, c)) != 0) {
+ h = GH_INIT(algs.h);
+ HASH_STRING(h, "tripe-check-hash");
+ GH_HASH(h, ck, indexsz);
+ ok = !memcmp(kxc->ck, GH_DONE(h, 0), algs.hashsz);
+ GH_DESTROY(h);
+ if (!ok) goto badcheck;
+ } else {
+
+ /* --- Compute the reply, and check the magic --- */
+
+ G_EXP(gg, r, c, kpriv);
+ cv = mpunmask(MP_NEW, ck, indexsz,
+ hashcheck(kx->kpub, kx->c, c, r), algs.hashsz);
+ IF_TRACING(T_KEYEXCH, IF_TRACING(T_CRYPTO, {
+ trace(T_CRYPTO, "crypto: computed reply = %s", gestr(gg, r));
+ trace(T_CRYPTO, "crypto: recovered log = %s", mpstr(cv));
+ }))
+ if (MP_CMP(cv, >, gg->r) ||
+ (G_EXP(gg, cc, gg->g, cv), !G_EQ(gg, c, cc)))
+ goto badcheck;
+
+ /* --- Fill in a new challenge block --- */
+
+ kxc = kxc_new(kx);
G_COPY(gg, kxc->c, c);
+ G_COPY(gg, kxc->r, r);
- /* --- Work out the cookie for this challenge --- */
+ h = GH_INIT(algs.h);
+ HASH_STRING(h, "tripe-check-hash");
+ GH_HASH(h, ck, indexsz);
+ GH_DONE(h, kxc->hc);
+ GH_DESTROY(h);
h = GH_INIT(algs.h);
HASH_STRING(h, "tripe-cookie");
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));
/* --- 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));
+ buf_init(&bb, buf_o, sizeof(buf_o));
+ G_TOBUF(gg, &bb, kx->c); x = BLEN(&bb);
+ G_TOBUF(gg, &bb, kxc->c); y = BLEN(&bb);
+ G_TOBUF(gg, &bb, r); z = BLEN(&bb);
+ assert(BOK(&bb));
- kxc->ks = ks_gen(BBASE(b), x, y, z, kx->p);
- G_DESTROY(gg, r);
+ kxc->ks = ks_gen(BBASE(&bb), x, y, z, kx->p);
}
- /* --- Answer the challenge if we need to --- */
+ G_DESTROY(gg, c);
+ G_DESTROY(gg, cc);
+ G_DESTROY(gg, r);
+ mp_drop(cv);
+ return (kxc);
- if (ck && !kxc->r) {
- ge *r;
- if ((r = getreply(kx, c, ck)) == 0)
- goto bad;
- kxc->r = r;
- }
+badcheck:
+ a_warn("KX", "?PEER", kx->p, "bad-expected-reply-log", A_END);
+ goto bad;
+bad:
+ G_DESTROY(gg, c);
+ G_DESTROY(gg, cc);
+ G_DESTROY(gg, r);
+ mp_drop(cv);
+ return (0);
+}
- kxc_answer(kx, kxc);
+/* --- @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.
+ */
- /* --- Tidy up and go home --- */
+static int dochallenge(keyexch *kx, buf *b)
+{
+ kxchal *kxc;
-tidy:
- G_DESTROY(gg, c);
- mp_drop(ck);
+ if (kx->s != KXS_CHAL) {
+ a_warn("KX", "?PEER", kx->p, "unexpected", "challenge", A_END);
+ goto bad;
+ }
+ if ((kxc = respond(kx, KX_CHAL, b)) == 0)
+ goto bad;
+ if (BLEFT(b)) {
+ a_warn("KX", "?PEER", kx->p, "invalid", "challenge", A_END);
+ goto bad;
+ }
+ kxc_answer(kx, kxc);
return (0);
bad:
- G_DESTROY(gg, c);
- mp_drop(ck);
return (-1);
}
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);
+ G_TORAW(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);
settimer(kx, time(0) + T_RETRY);
}
-/* --- @matchreply@ --- *
+/* --- @decryptrest@ --- *
*
* 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)
+ * @kxchal *kxc@ = pointer to challenge block
+ * @unsigned msg@ = type of incoming message
* @buf *b@ = encrypted remainder of the packet
*
- * Returns: A pointer to the challenge block if OK, or null on failure.
+ * Returns: Zero if OK, nonzero on some kind of error.
*
- * 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.
+ * Use: Decrypts the remainder of the packet, and points @b@ at the
+ * recovered plaintext.
*/
-static kxchal *matchreply(keyexch *kx, unsigned ty, const octet *hc_in,
- const octet *hc_out, mp *ck, buf *b)
+static int decryptrest(keyexch *kx, kxchal *kxc, unsigned msg, 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("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;
+ buf_init(&bb, buf_o, sizeof(buf_o));
+ if (ks_decrypt(kxc->ks, MSG_KEYEXCH | msg, b, &bb)) {
+ a_warn("KX", "?PEER", kx->p, "decrypt-failed", "%s", pkname[msg], A_END);
+ return (-1);
}
+ buf_init(b, BBASE(&bb), BLEN(&bb));
+ return (0);
+}
- /* --- Maybe compute a reply for the challenge --- */
-
- if (!kxc->r) {
- if (!ck) {
- a_warn("unexpected switch request from `%s'", p_name(kx->p));
- goto bad;
- }
- if ((r = getreply(kx, kxc->c, ck)) == 0)
- goto bad;
- kxc->r = r;
- r = 0;
- }
+/* --- @checkresponse@ --- *
+ *
+ * Arguments: @keyexch *kx@ = pointer to key exchange context
+ * @unsigned msg@ = type of incoming message
+ * @buf *b@ = decrypted remainder of the packet
+ *
+ * Returns: Zero if OK, nonzero on some kind of error.
+ *
+ * Use: Checks a reply or switch packet, ensuring that its response
+ * is correct.
+ */
- /* --- Decrypt the rest of the packet --- */
+static int checkresponse(keyexch *kx, unsigned msg, buf *b)
+{
+ ge *r = G_CREATE(gg);
- buf_init(&bb, buf_o, sizeof(buf_o));
- if (ks_decrypt(kxc->ks, ty, b, &bb)) {
- a_warn("failed to decrypt reply from `%s'", p_name(kx->p));
- goto bad;
- }
- buf_init(b, BBASE(&bb), BLEN(&bb));
- r = G_CREATE(gg);
- if (G_FROMBUF(gg, b, r)) {
- a_warn("invalid reply packet from `%s'", p_name(kx->p));
+ if (G_FROMRAW(gg, b, r)) {
+ 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: reply = %s", gestr(gg, r));
}))
if (!G_EQ(gg, r, kx->rx)) {
- a_warn("incorrect reply from `%s'", p_name(kx->p));
+ a_warn("KX", "?PEER", kx->p, "incorrect", "response", A_END);
goto bad;
}
- /* --- Done --- */
-
G_DESTROY(gg, r);
- return (kxc);
+ return (0);
bad:
- if (r) G_DESTROY(gg, r);
- return (0);
+ G_DESTROY(gg, r);
+ return (-1);
}
/* --- @commit@ --- *
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("unexpected reply from `%s'", p_name(kx->p));
+ 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("invalid reply packet from `%s'", p_name(kx->p));
- goto bad;
- }
- if ((kxc = matchreply(kx, MSG_KEYEXCH | KX_REPLY,
- hc_in, hc_out, ck, b)) == 0)
+ if ((kxc = respond(kx, KX_REPLY, b)) == 0 ||
+ decryptrest(kx, kxc, KX_REPLY, b) ||
+ checkresponse(kx, KX_REPLY, b))
goto bad;
if (BLEFT(b)) {
- a_warn("invalid reply packet from `%s'", p_name(kx->p));
+ a_warn("KX", "?PEER", kx->p, "invalid", "reply", A_END);
goto bad;
- }
+ }
if (kx->s == KXS_CHAL) {
commit(kx, kxc);
kx->s = KXS_COMMIT;
return (0);
bad:
- mp_drop(ck);
return (-1);
}
+/* --- @kxfinish@ --- *
+ *
+ * Arguments: @keyexch *kx@ = pointer to key exchange block
+ *
+ * Returns: ---
+ *
+ * Use: Sets everything up following a successful key exchange.
+ */
+
+static void kxfinish(keyexch *kx)
+{
+ 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);
+}
+
/* --- @doswitch@ --- *
*
* Arguments: @keyexch *kx@ = pointer to key exchange block
if ((hc_in = buf_get(b, algs.hashsz)) == 0 ||
(hc_out = buf_get(b, algs.hashsz)) == 0) {
- a_warn("invalid switch request from `%s'", p_name(kx->p));
+ a_warn("KX", "?PEER", kx->p, "invalid", "switch-rq", A_END);
+ goto bad;
+ }
+ 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 ((kxc = kxc_byhc(kx, hc_in)) == 0 ||
+ memcmp(hc_out, kx->hc, algs.hashsz) != 0) {
+ a_warn("KX", "?PEER", kx->p, "incorrect", "switch-rq", A_END);
goto bad;
}
- if ((kxc = matchreply(kx, MSG_KEYEXCH | KX_SWITCH,
- hc_in, hc_out, 0, b)) == 0)
+ if (decryptrest(kx, kxc, KX_SWITCH, b) ||
+ checkresponse(kx, KX_SWITCH, b))
goto bad;
if ((hswrq = buf_get(b, algs.hashsz)) == 0 || BLEFT(b)) {
- a_warn("invalid switch request from `%s'", p_name(kx->p));
+ 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("incorrect switch request hash from `%s'", p_name(kx->p));
+ 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:
- ks_activate(kxc->ks);
- settimer(kx, ks_tregen(kxc->ks));
- kx->s = KXS_SWITCH;
- break;
- }
+ if (kx->s == KXS_CHAL)
+ commit(kx, kxc);
+ if (kx->s < KXS_SWITCH)
+ kxfinish(kx);
resend(kx);
return (0);
buf bb;
if (kx->s < KXS_COMMIT) {
- a_warn("unexpected switch confirmation from `%s'", p_name(kx->p));
+ 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("failed to decrypt switch confirmation from `%s'", p_name(kx->p));
+ if (decryptrest(kx, kxc, KX_SWITCHOK, b))
goto bad;
- }
- buf_init(b, BBASE(&bb), BLEN(&bb));
if ((hswok = buf_get(b, algs.hashsz)) == 0 || BLEFT(b)) {
- a_warn("invalid switch confirmation from `%s'", p_name(kx->p));
+ a_warn("KX", "?PEER", "invalid", "switch-ok", A_END);
goto bad;
}
IF_TRACING(T_KEYEXCH, {
hswok, algs.hashsz);
})
if (memcmp(hswok, kxc->hswok_in, algs.hashsz) != 0) {
- a_warn("incorrect switch confirmation hash from `%s'", p_name(kx->p));
+ a_warn("KX", "?PEER", kx->p, "incorrect", "switch-ok", A_END);
goto bad;
}
- if (kx->s < KXS_SWITCH) {
- ks_activate(kxc->ks);
- settimer(kx, ks_tregen(kxc->ks));
- kx->s = KXS_SWITCH;
- }
+ if (kx->s < KXS_SWITCH)
+ kxfinish(kx);
return (0);
bad:
now = time(0);
if (KEY_EXPIRED(now, kx->texp_kpub)) {
stop(kx);
- a_warn("public key for `%s' has expired", p_name(kx->p));
+ a_warn("KX", "?PEER", kx->p, "public-key-expired", A_END);
G_COPY(gg, kx->kpub, gg->i);
kx->f &= ~KXF_PUBKEY;
return (-1);
/* --- @kx_start@ --- *
*
* Arguments: @keyexch *kx@ = pointer to key exchange context
+ * @int forcep@ = nonzero to ignore the quiet timer
*
* Returns: ---
*
* this); if no exchange is in progress, one is commenced.
*/
-void kx_start(keyexch *kx)
+void kx_start(keyexch *kx, int forcep)
{
time_t now = time(0);
if (checkpub(kx))
return;
- if (!ISVALID(kx, now)) {
+ if (forcep || !VALIDP(kx, now)) {
stop(kx);
start(kx, now);
+ a_notify("KXSTART", "?PEER", kx->p, A_END);
}
resend(kx);
}
size_t sz = BSZ(b);
int rc;
-#ifndef NTRACE
- static const char *const pkname[] = {
- "prechallenge", "cookie", "challenge",
- "reply", "switch request", "switch confirmation"
- };
-#endif
-
if (checkpub(kx))
return;
- if (!ISVALID(kx, now)) {
+ if (!VALIDP(kx, now)) {
stop(kx);
start(kx, now);
}
switch (msg) {
case KX_PRECHAL:
- case KX_COOKIE:
+ rc = doprechallenge(kx, b);
+ break;
case KX_CHAL:
- rc = dochallenge(kx, msg, b);
+ rc = dochallenge(kx, b);
break;
case KX_REPLY:
rc = doreply(kx, b);
rc = doswitchok(kx, b);
break;
default:
- a_warn("unexpected key exchange message type %u from `%p'",
- p_name(kx->p));
+ a_warn("KX", "?PEER", kx->p, "unknown-message", "0x%02x", msg, A_END);
rc = -1;
break;
}
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);
}