* Switch received. Committed; send data; move to @KXS_SWITCH@.
*/
-/*----- Tunable parameters ------------------------------------------------*/
-
-#define T_VALID SEC(20) /* Challenge validity period */
-#define T_RETRY SEC(10) /* Challenge retransmit interval */
-
-#define VALIDP(kx, now) ((now) < (kx)->t_valid)
-
/*----- Static tables -----------------------------------------------------*/
static const char *const pkname[] = {
/*----- Various utilities -------------------------------------------------*/
+/* --- @VALIDP@ --- *
+ *
+ * Arguments: @const keyexch *kx@ = key exchange state
+ * @time_t now@ = current time in seconds
+ *
+ * Returns: Whether the challenge in the key-exchange state is still
+ * valid or should be regenerated.
+ */
+
+#define VALIDP(kx, now) ((now) < (kx)->t_valid)
+
/* --- @hashge@ --- *
*
* Arguments: @ghash *h@ = pointer to hash context
* @const octet *k@ = pointer to key material
* @size_t ksz@ = size of the key
*
- * Returns: Pointer to the output.
+ * Returns: ---
*
* Use: Masks a multiprecision integer: returns %$x \xor H(k)$%, so
* it's a random oracle thing rather than an encryption thing.
+ * Breaks the output buffer on error.
*/
-static octet *mpmask(buf *b, mp *x, size_t n,
- const gccipher *mgfc, const octet *k, size_t ksz)
+static void mpmask(buf *b, mp *x, size_t n,
+ const gccipher *mgfc, const octet *k, size_t ksz)
{
gcipher *mgf;
octet *p;
- if ((p = buf_get(b, n)) == 0)
- return (0);
+ if ((p = buf_get(b, n)) == 0) return;
mgf = GC_INIT(mgfc, k, ksz);
IF_TRACING(T_KEYEXCH, IF_TRACING(T_CRYPTO, {
trace(T_CRYPTO, "crypto: masking index = %s", mpstr(x));
trace_block(T_CRYPTO, "crypto: masked ciphertext", p, n);
}))
GC_DESTROY(mgf);
- return (p);
}
/* --- @mpunmask@ --- *
/* --- @settimer@ --- *
*
* Arguments: @keyexch *kx@ = pointer to key exchange context
- * @time_t t@ = when to set the timer for
+ * @struct timeval *tv@ = when to set the timer for
*
* Returns: ---
*
* Use: Sets the timer for the next key exchange attempt.
*/
-static void settimer(keyexch *kx, time_t t)
+static void settimer(keyexch *kx, struct timeval *tv)
{
- struct timeval tv;
- if (kx->f & KXF_TIMER)
- sel_rmtimer(&kx->t);
- tv.tv_sec = t;
- tv.tv_usec = 0;
- sel_addtimer(&sel, &kx->t, &tv, timer, kx);
+ if (kx->f & KXF_TIMER) sel_rmtimer(&kx->t);
+ sel_addtimer(&sel, &kx->t, tv, timer, kx);
kx->f |= KXF_TIMER;
}
+/* --- @f2tv@ --- *
+ *
+ * Arguments: @struct timeval *tv@ = where to write the timeval
+ * @double t@ = a time as a floating point number
+ *
+ * Returns: ---
+ *
+ * Use: Converts a floating-point time into a timeval.
+ */
+
+static void f2tv(struct timeval *tv, double t)
+{
+ tv->tv_sec = t;
+ tv->tv_usec = (t - tv->tv_sec)*MILLION;
+}
+
+/* --- @wobble@ --- *
+ *
+ * Arguments: @double t@ = a time interval
+ *
+ * Returns: The same time interval, with a random error applied.
+ */
+
+static double wobble(double t)
+{
+ uint32 r = rand_global.ops->word(&rand_global);
+ double w = (r/F_2P32) - 0.5;
+ return (t + t*w*T_WOBBLE);
+}
+
+/* --- @rs_time@ --- *
+ *
+ * Arguments: @retry *rs@ = current retry state
+ * @struct timeval *tv@ = where to write the result
+ * @const struct timeval *now@ = current time, or null
+ *
+ * Returns: ---
+ *
+ * Use: Computes a time at which to retry sending a key-exchange
+ * packet. This algorithm is subject to change, but it's
+ * currently a capped exponential backoff, slightly randomized
+ * to try to keep clients from hammering a server that's only
+ * just woken up.
+ *
+ * If @now@ is null then the function works out the time for
+ * itself.
+ */
+
+static void rs_time(retry *rs, struct timeval *tv, const struct timeval *now)
+{
+ double t;
+ struct timeval rtv;
+
+ if (!rs->t)
+ t = SEC(2);
+ else {
+ t = (rs->t * 5)/4;
+ if (t > MIN(5)) t = MIN(5);
+ }
+ rs->t = t;
+
+ if (!now) {
+ now = tv;
+ gettimeofday(tv, 0);
+ }
+ f2tv(&rtv, wobble(t));
+ TV_ADD(tv, now, &rtv);
+}
+
+/* --- @retry_reset@ --- *
+ *
+ * Arguments: @retry *rs@ = retry state
+ *
+ * Returns: --
+ *
+ * Use: Resets a retry state to indicate that progress has been
+ * made. Also useful for initializing the state in the first
+ * place.
+ */
+
+static void rs_reset(retry *rs) { rs->t = 0; }
+
/*----- Challenge management ----------------------------------------------*/
/* --- Notes on challenge management --- *
* Returns: A pointer to the challenge block.
*
* Use: Returns a pointer to a new challenge block to fill in.
+ * In particular, the @c@ and @r@ members are left
+ * uninitialized.
*/
static kxchal *kxc_new(keyexch *kx)
/* --- Fill in the new structure --- */
kxc = CREATE(kxchal);
- kxc->c = G_CREATE(kx->kpriv->g);
- kxc->r = G_CREATE(kx->kpriv->g);
kxc->ks = 0;
kxc->kx = kx;
kxc->f = 0;
kx->r[i] = kxc;
+ rs_reset(&kxc->rs);
return (kxc);
}
if (kxc->f & KXF_TIMER)
sel_rmtimer(&kxc->t);
gettimeofday(&tv, 0);
- tv.tv_sec += T_RETRY;
+ rs_time(&kxc->rs, &tv, &tv);
sel_addtimer(&sel, &kxc->t, &tv, kxc_timer, kxc);
kxc->f |= KXF_TIMER;
}
/* --- Compute the reply, and check the magic --- */
G_EXP(g, r, c, kx->kpriv->kpriv);
- cv = mpunmask(MP_NEW, ck, ixsz, algs->mgf,
- hashcheck(kx, kx->kpub->kpub, kx->c, c, r),
- algs->hashsz);
+ if ((cv = mpunmask(MP_NEW, ck, ixsz, algs->mgf,
+ hashcheck(kx, kx->kpub->kpub, kx->c, c, r),
+ algs->hashsz)) == 0)
+ goto badcheck;
IF_TRACING(T_KEYEXCH, IF_TRACING(T_CRYPTO, {
trace(T_CRYPTO, "crypto: computed reply = %s", gestr(g, r));
trace(T_CRYPTO, "crypto: recovered log = %s", mpstr(cv));
/* --- Fill in a new challenge block --- */
kxc = kxc_new(kx);
- G_COPY(g, kxc->c, c);
- G_COPY(g, kxc->r, r);
+ kxc->c = c; c = 0;
+ kxc->r = r; r = G_CREATE(g);
h = GH_INIT(algs->h); HASH_STRING(h, "tripe-check-hash");
GH_HASH(h, ck, ixsz);
/* --- Work out the shared key --- */
- G_EXP(g, r, c, kx->alpha);
+ G_EXP(g, r, kxc->c, kx->alpha);
IF_TRACING(T_KEYEXCH, IF_TRACING(T_CRYPTO, {
trace(T_CRYPTO, "crypto: shared secret = %s", gestr(g, r));
}))
kxc->ks = ks_gen(BBASE(&bb), x, y, z, kx->p);
}
- G_DESTROY(g, c);
+ if (c) G_DESTROY(g, c);
G_DESTROY(g, cc);
G_DESTROY(g, r);
mp_drop(cv);
a_warn("KX", "?PEER", kx->p, "bad-expected-reply-log", A_END);
goto bad;
bad:
- G_DESTROY(g, c);
+ if (c) G_DESTROY(g, c);
G_DESTROY(g, cc);
G_DESTROY(g, r);
mp_drop(cv);
kxchal *kxc;
buf bb;
stats *st = p_stats(kx->p);
+ struct timeval tv;
buf *b;
switch (kx->s) {
p_txend(kx->p);
}
- if (kx->s < KXS_SWITCH)
- settimer(kx, time(0) + T_RETRY);
+ if (kx->s < KXS_SWITCH) {
+ rs_time(&kx->rs, &tv, 0);
+ settimer(kx, &tv);
+ }
}
/* --- @decryptrest@ --- *
static void kxfinish(keyexch *kx)
{
kxchal *kxc = kx->r[0];
+ struct timeval now, tv;
+
ks_activate(kxc->ks);
- settimer(kx, ks_tregen(kxc->ks));
+ gettimeofday(&now, 0);
+ f2tv(&tv, wobble(T_REGEN));
+ TV_ADD(&tv, &now, &tv);
+ settimer(kx, &tv);
kx->s = KXS_SWITCH;
a_notify("KXDONE", "?PEER", kx->p, A_END);
p_stats(kx->p)->t_kx = time(0);
void kx_message(keyexch *kx, unsigned msg, buf *b)
{
- time_t now = time(0);
+ struct timeval now, tv;
stats *st = p_stats(kx->p);
size_t sz = BSZ(b);
int rc;
+ gettimeofday(&now, 0);
+ rs_reset(&kx->rs);
if (kx->f & KXF_CORK) {
- start(kx, now);
- settimer(kx, now + T_RETRY);
- a_notify("KXSTART", A_END);
+ start(kx, now.tv_sec);
+ rs_time(&kx->rs, &tv, &now);
+ settimer(kx, &tv);
+ a_notify("KXSTART", "?PEER", kx->p, A_END);
}
if (checkpub(kx))
return;
- if (!VALIDP(kx, now)) {
+ if (!VALIDP(kx, now.tv_sec)) {
stop(kx);
- start(kx, now);
+ start(kx, now.tv_sec);
}
T( trace(T_KEYEXCH, "keyexch: processing %s packet from `%s'",
msg < KX_NMSG ? pkname[msg] : "unknown", p_name(kx->p)); )
{
if ((kx->kpriv = km_findpriv(p_privtag(p))) == 0) goto fail_0;
if ((kx->kpub = km_findpub(p_tag(p))) == 0) goto fail_1;
- if (!group_samep(kx->kpriv->g, kx->kpub->g)) {
- a_warn("KX", "?PEER", kx->p, "group-mismatch",
+ if (!km_samealgsp(kx->kpriv, kx->kpub)) {
+ a_warn("KX", "?PEER", p, "group-mismatch",
"local-private-key", "%s", p_privtag(p),
"peer-public-key", "%s", p_tag(p),
A_END);
kx->ks = ks;
kx->p = p;
kx->f = KXF_DEAD | KXF_PUBKEY | f;
+ rs_reset(&kx->rs);
if (!(kx->f & KXF_CORK)) {
start(kx, time(0));
resend(kx);