/* -*-c-*-
*
- * $Id: keyexch.c,v 1.3 2001/06/19 22:07:09 mdw Exp $
+ * $Id: keyexch.c,v 1.5 2002/01/13 14:54:40 mdw Exp $
*
* Key exchange protocol
*
/*----- Revision history --------------------------------------------------*
*
* $Log: keyexch.c,v $
+ * Revision 1.5 2002/01/13 14:54:40 mdw
+ * Patch up zero-knowledge property by passing an encrypted log with a
+ * challenge, so that the prover can verify that the challenge is good.
+ *
+ * Revision 1.4 2001/06/22 19:40:36 mdw
+ * Support expiry of other peers' public keys.
+ *
* Revision 1.3 2001/06/19 22:07:09 mdw
* Cosmetic fixes.
*
HASH(r, BBASE(&b), BLEN(&b));
}
+/* --- @mpcrypt@ --- *
+ *
+ * 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
+ * @size_t ksz@ = size of the key
+ *
+ * Returns: The encrypted/decrypted integer.
+ *
+ * Use: Encrypts (or decrypts) a multiprecision integer using another
+ * multiprecision integer as the key. This is a slightly grotty
+ * way to do this, but it's easier than the alternatives.
+ */
+
+static mp *mpcrypt(mp *d, mp *x, size_t sz, const octet *k, size_t ksz)
+{
+ MGF_CTX m;
+
+ MGF_INIT(&m, k, ksz, 0);
+ mp_storeb(x, buf_t, sz);
+ MGF_CRYPT(&m, buf_t, buf_t, sz);
+ return (mp_loadb(d, buf_t, sz));
+}
+
/* --- @timer@ --- *
*
* Arguments: @struct timeval *tv@ = the current time
sel_rmtimer(&kxc->t);
mp_drop(kxc->c);
mp_drop(kxc->r);
+ mp_drop(kxc->ck);
ks_drop(kxc->ks);
DESTROY(kxc);
}
kxc = CREATE(kxchal);
kxc->c = 0;
kxc->r = 0;
+ kxc->ck = 0;
kxc->ks = 0;
kxc->kx = kx;
kxc->f = 0;
else
buf_put(b, kx->hc, HASHSZ);
buf_put(b, kxc->hc, HASHSZ);
- buf_put(b, kxc->hrx, HASHSZ);
+ buf_putmp(b, kxc->ck);
/* --- Maybe send an actual reply, if we have one --- */
*
* Arguments: @keyexch *kx@ = pointer to key exchange context
* @mp *c@ = a challenge
- * @const octet *hrx@ = the supplied expected-reply hash
+ * @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 mp *getreply(keyexch *kx, mp *c, const octet *hrx)
+static mp *getreply(keyexch *kx, mp *c, mp *ck)
{
mp *r = mpmont_exp(&mg, MP_NEW, c, kpriv.x);
+ mp *a;
HASH_CTX h;
- octet buf[HASHSZ];
+ octet buf[HASHSZ];
+ int ok;
HASH_INIT(&h);
HASH_STRING(&h, "tripe-expected-reply");
hashmp(&h, kx->c);
hashmp(&h, r);
HASH_DONE(&h, buf);
+
+ a = mpcrypt(MP_NEW, ck, mp_octets(kpriv.dp.q), buf, sizeof(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);
+ trace(T_CRYPTO, "crypto: recovered log = %s", mpstr(a));
}))
- if (memcmp(buf, hrx, HASHSZ) != 0) {
- a_warn("invalid expected-reply hash from `%s'", p_name(kx->p));
+ a = mpmont_exp(&mg, a, kpriv.dp.g, a);
+ ok = mp_eq(a, 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", mpstr(a));
+ }))
mp_drop(r);
- return (0);
}
- return (r);
+ mp_drop(a);
+ return (ok ? r : 0);
}
/* --- @dochallenge@ --- *
static int dochallenge(keyexch *kx, unsigned msg, buf *b)
{
- mp *c = 0;
- const octet *hc = 0, *hrx = 0;
+ mp *c = 0, *ck = 0;
+ const octet *hc = 0;
kxchal *kxc;
HASH_CTX h;
+ octet buf[HASHSZ];
/* --- Ensure that we're in a sensible state --- */
if ((c = buf_getmp(b)) == 0 ||
(msg >= KX_COOKIE && (hc = buf_get(b, HASHSZ)) == 0) ||
- (msg >= KX_CHAL && (hrx = buf_get(b, HASHSZ)) == 0) ||
+ (msg >= KX_CHAL && (ck = buf_getmp(b)) == 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);
+ if (ck) trace(T_CRYPTO, "crypto: check value = %s", mpstr(ck));
}))
/* --- First, handle a bare challenge --- *
* it.
*/
- if (!hrx)
+ if (!ck)
kxc = kxc_new(kx);
else {
- if ((r = getreply(kx, c, hrx)) == 0)
+ if ((r = getreply(kx, c, ck)) == 0)
goto bad;
kxc = kxc_new(kx);
kxc->r = r;
hashmp(&h, kx->c);
hashmp(&h, kxc->c);
hashmp(&h, kx->rx);
- HASH_DONE(&h, kxc->hrx);
+ HASH_DONE(&h, buf);
+ kxc->ck = mpcrypt(MP_NEW, kx->alpha, mp_octets(kpriv.dp.q),
+ buf, sizeof(buf));
/* --- Work out the shared key --- */
+ trace(T_CRYPTO, "debug: c = %s", mpstr(c));
+ trace(T_CRYPTO, "debug: alpha = %s", mpstr(kx->alpha));
r = mpmont_exp(&mg, MP_NEW, c, kx->alpha);
+ trace(T_CRYPTO, "debug: r = %s", mpstr(r));
/* --- Compute the switch messages --- */
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_block(T_CRYPTO, "crypto: expected-reply hash",
+ buf, HASHSZ);
+ trace(T_CRYPTO, "crypto: my reply check = %s", mpstr(kxc->ck));
trace(T_CRYPTO, "crypto: shared secret = %s", mpstr(r));
trace_block(T_CRYPTO, "crypto: outbound switch request",
kxc->hswrq_out, HASHSZ);
/* --- Answer the challenge if we need to --- */
- if (hrx && !kxc->r) {
+ if (ck && !kxc->r) {
mp *r;
- if ((r = getreply(kx, c, hrx)) == 0)
+ if ((r = getreply(kx, c, ck)) == 0)
goto bad;
kxc->r = r;
}
tidy:
mp_drop(c);
+ mp_drop(ck);
return (0);
bad:
mp_drop(c);
+ mp_drop(ck);
return (-1);
}
switch (kx->s) {
case KXS_CHAL:
- T( trace(T_KEYEXCH, "sending prechallenge to `%s'", p_name(kx->p)); )
+ T( trace(T_KEYEXCH, "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)); )
+ 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, HASHSZ);
ks_encrypt(kxc->ks, &bb, b);
break;
case KXS_SWITCH:
- T( trace(T_KEYEXCH, "sending switch confirmation to `%s'",
+ 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);
* 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)
+ * @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.
*/
static kxchal *matchreply(keyexch *kx, const octet *hc_in,
- const octet *hc_out, const octet *hrx, buf *b)
+ const octet *hc_out, mp *ck, buf *b)
{
kxchal *kxc;
buf bb;
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 (ck) trace(T_CRYPTO, "crypto: check value = %s", mpstr(ck));
}))
if (memcmp(hc_out, kx->hc, HASHSZ) != 0) {
a_warn("incorrect cookie from `%s'", p_name(kx->p));
/* --- Maybe compute a reply for the challenge --- */
if (!kxc->r) {
- if (!hrx) {
+ if (!ck) {
a_warn("unexpected switch request from `%s'", p_name(kx->p));
goto bad;
}
- if ((r = getreply(kx, kxc->c, hrx)) == 0)
+ if ((r = getreply(kx, kxc->c, ck)) == 0)
goto bad;
kxc->r = r;
r = 0;
static int doreply(keyexch *kx, buf *b)
{
- const octet *hc_in, *hc_out, *hrx;
+ const octet *hc_in, *hc_out;
+ mp *ck = 0;
kxchal *kxc;
if (kx->s != KXS_CHAL && kx->s != KXS_COMMIT) {
}
if ((hc_in = buf_get(b, HASHSZ)) == 0 ||
(hc_out = buf_get(b, HASHSZ)) == 0 ||
- (hrx = buf_get(b, HASHSZ)) == 0) {
+ (ck = buf_getmp(b)) == 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)
+ if ((kxc = matchreply(kx, hc_in, hc_out, ck, b)) == 0)
goto bad;
if (BLEFT(b)) {
a_warn("invalid reply packet from `%s'", p_name(kx->p));
return (0);
bad:
+ mp_drop(ck);
return (-1);
}
{
unsigned i;
+ if (kx->f & KXF_DEAD)
+ return;
+
if (kx->f & KXF_TIMER)
sel_rmtimer(&kx->t);
for (i = 0; i < kx->nr; i++)
mp_drop(kx->alpha);
mp_drop(kx->c);
mp_drop(kx->rx);
+ kx->t_valid = 0;
+ kx->f |= KXF_DEAD;
+ kx->f &= ~KXF_TIMER;
}
/* --- @start@ --- *
{
HASH_CTX h;
+ assert(kx->f & KXF_DEAD);
+
+ kx->f &= ~KXF_DEAD;
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);
})
}
+/* --- @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("public key for `%s' has expired", p_name(kx->p));
+ dh_pubfree(&kx->kpub);
+ kx->f &= ~KXF_PUBKEY;
+ return (-1);
+ }
+ return (0);
+}
+
/* --- @kx_start@ --- *
*
* Arguments: @keyexch *kx@ = pointer to key exchange context
{
time_t now = time(0);
+ if (checkpub(kx))
+ return;
if (!ISVALID(kx, now)) {
stop(kx);
start(kx, now);
};
#endif
+ if (checkpub(kx))
+ return;
+
if (!ISVALID(kx, now)) {
stop(kx);
start(kx, now);
void kx_free(keyexch *kx)
{
stop(kx);
- dh_pubfree(&kx->kpub);
+ if (kx->f & KXF_PUBKEY)
+ dh_pubfree(&kx->kpub);
}
/* --- @kx_newkeys@ --- *
{
dh_pub dp;
- if (km_getpubkey(p_name(kx->p), &dp))
+ if (km_getpubkey(p_name(kx->p), &dp, &kx->texp_kpub))
return;
- dh_pubfree(&kx->kpub);
+ if (kx->f & KXF_PUBKEY)
+ dh_pubfree(&kx->kpub);
kx->kpub = dp;
- if (kx->s != KXS_SWITCH) {
+ 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)); )
- kx->t_valid = 0;
- kx_start(kx);
+ stop(kx);
+ start(kx, time(0));
+ resend(kx);
}
}
{
kx->ks = ks;
kx->p = p;
- if (km_getpubkey(p_name(p), &kx->kpub))
+ if (km_getpubkey(p_name(p), &kx->kpub, &kx->texp_kpub))
return (-1);
+ kx->f = KXF_DEAD | KXF_PUBKEY;
start(kx, time(0));
resend(kx);
return (0);