chiark / gitweb /
Merge branch 'mdw/multi-priv'
authorMark Wooding <mdw@distorted.org.uk>
Tue, 18 Sep 2012 02:32:53 +0000 (03:32 +0100)
committerMark Wooding <mdw@distorted.org.uk>
Tue, 18 Sep 2012 02:32:53 +0000 (03:32 +0100)
* mdw/multi-priv:
  server/tests.at, t/keyring-*: New tests for key management.
  Allow different peer associations to use different private keys.
  server: Use the new kdata system.
  server/{keymgmt.c,tripe.h}: Unify public and private key handling.
  server/keyexch.c: Prefix crypto-details trace messages correctly.
  server/{keymgmt.c,tripe-admin.5.in}: Improve key-management errors.
  admin.c (a_format): New function formats token sequences to strings.

Conflicts:
server/tests.at
server/tripe.h

1  2 
server/keyexch.c
server/keyset.c
server/tests.at
server/tripe.h

diff --combined server/keyexch.c
index b1f23d7383a8def11dac4d4b173d82eb58225b10,50a85f6f95c72fc1a868bacdbfea795a81a1ee09..d26ac787378aa6658ca0517d7f83ab73a1822174
   *    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
+  *            @group *g@ = pointer to group
   *            @ge *x@ = pointer to group element
   *
   * Returns:   ---
   *            @buf_t@.
   */
  
- static void hashge(ghash *h, ge *x)
+ static void hashge(ghash *h, group *g, ge *x)
  {
    buf b;
    buf_init(&b, buf_t, sizeof(buf_t));
-   G_TOBUF(gg, &b, x);
+   G_TOBUF(g, &b, x);
    assert(BOK(&b));
    GH_HASH(h, BBASE(&b), BLEN(&b));
  }
   * Arguments: @buf *b@ = output buffer
   *            @mp *x@ = the plaintext integer
   *            @size_t n@ = the expected size of the plaintext
+  *            @gcipher *mgfc@ = mask-generating function to use
   *            @const octet *k@ = pointer to key material
   *            @size_t ksz@ = size of the key
   *
   *            it's a random oracle thing rather than an encryption thing.
   */
  
- static octet *mpmask(buf *b, mp *x, size_t n, const octet *k, size_t ksz)
+ static octet *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);
-   mgf = GC_INIT(algs.mgf, k, ksz);
+   mgf = GC_INIT(mgfc, 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);
+     trace(T_CRYPTO, "crypto: masking index = %s", mpstr(x));
+     trace_block(T_CRYPTO, "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);
+     trace_block(T_CRYPTO, "crypto: index plaintext", buf_t, n);
+     trace_block(T_CRYPTO, "crypto: masked ciphertext", p, n);
    }))
    GC_DESTROY(mgf);
    return (p);
   * Arguments: @mp *d@ = the output integer
   *            @const octet *p@ = pointer to the ciphertext
   *            @size_t n@ = the size of the ciphertext
+  *            @gcipher *mgfc@ = mask-generating function to use
   *            @const octet *k@ = pointer to key material
   *            @size_t ksz@ = size of the key
   *
   */
  
  static mp *mpunmask(mp *d, const octet *p, size_t n,
-                   const octet *k, size_t ksz)
+                   const gccipher *mgfc, const octet *k, size_t ksz)
  {
    gcipher *mgf;
  
-   mgf = GC_INIT(algs.mgf, k, ksz);
+   mgf = GC_INIT(mgfc, 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);
+     trace_block(T_CRYPTO, "crypto: unmasking key", k, ksz);
+     trace_block(T_CRYPTO, "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));
+     trace_block(T_CRYPTO, "crypto: index plaintext", buf_t, n);
+     trace(T_CRYPTO, "crypto: unmasked index = %s", mpstr(d));
    }))
    GC_DESTROY(mgf);
    return (d);
  
  /* --- @hashcheck@ --- *
   *
-  * Arguments: @ge *kpub@ = sender's public key
+  * Arguments: @keyexch *kx@ = pointer to key-exchange block
+  *            @ge *kpub@ = sender's public key
   *            @ge *cc@ = receiver's challenge
   *            @ge *c@ = sender's challenge
   *            @ge *y@ = reply to sender's challenge
   *            key-exchange is deniable.
   */
  
- static const octet *hashcheck(ge *kpub, ge *cc, ge *c, ge *y)
+ static const octet *hashcheck(keyexch *kx, ge *kpub, ge *cc, ge *c, ge *y)
  {
-   ghash *h = GH_INIT(algs.h);
+   ghash *h = GH_INIT(kx->kpriv->algs.h);
+   group *g = kx->kpriv->g;
  
    HASH_STRING(h, "tripe-expected-reply");
-   hashge(h, kpub);
-   hashge(h, cc);
-   hashge(h, c);
-   hashge(h, y);
+   hashge(h, g, kpub);
+   hashge(h, g, cc);
+   hashge(h, g, c);
+   hashge(h, g, 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);
+     trace(T_CRYPTO, "crypto: computing challenge check hash");
+     trace(T_CRYPTO, "crypto: public key = %s", gestr(g, kpub));
+     trace(T_CRYPTO, "crypto: receiver challenge = %s", gestr(g, cc));
+     trace(T_CRYPTO, "crypto: sender challenge = %s", gestr(g, c));
+     trace(T_CRYPTO, "crypto: sender reply = %s", gestr(g, y));
+     trace_block(T_CRYPTO, "crypto: hash output", buf_t, kx->kpriv->algs.hashsz);
    }))
    GH_DESTROY(h);
    return (buf_t);
  
  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);
+   G_TOBUF(kx->kpriv->g, b, kx->c);
+   buf_put(b, hc, kx->kpriv->algs.hashsz);
+   mpmask(b, kx->alpha, kx->kpriv->indexsz, kx->kpriv->algs.mgf,
+        hashcheck(kx, kx->kpriv->kpub, c, kx->c, kx->rx),
+        kx->kpriv->algs.hashsz);
  }
  
  /* --- @timer@ --- *
@@@ -265,102 -269,24 +273,102 @@@ static void timer(struct timeval *tv, v
  /* --- @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 --- *
@@@ -389,8 -315,8 +397,8 @@@ static void kxc_destroy(kxchal *kxc
  {
    if (kxc->f & KXF_TIMER)
      sel_rmtimer(&kxc->t);
-   G_DESTROY(gg, kxc->c);
-   G_DESTROY(gg, kxc->r);
+   G_DESTROY(kxc->kx->kpriv->g, kxc->c);
+   G_DESTROY(kxc->kx->kpriv->g, kxc->r);
    ks_drop(kxc->ks);
    DESTROY(kxc);
  }
@@@ -439,13 -365,12 +447,13 @@@ static kxchal *kxc_new(keyexch *kx
    /* --- Fill in the new structure --- */
  
    kxc = CREATE(kxchal);
-   kxc->c = G_CREATE(gg);
-   kxc->r = G_CREATE(gg);
+   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);
  }
  
@@@ -464,7 -389,7 +472,7 @@@ static kxchal *kxc_bychal(keyexch *kx, 
    unsigned i;
  
    for (i = 0; i < kx->nr; i++) {
-     if (G_EQ(gg, c, kx->r[i]->c))
+     if (G_EQ(kx->kpriv->g, c, kx->r[i]->c))
        return (kx->r[i]);
    }
    return (0);
@@@ -485,7 -410,7 +493,7 @@@ static kxchal *kxc_byhc(keyexch *kx, co
    unsigned i;
  
    for (i = 0; i < kx->nr; i++) {
-     if (memcmp(hc, kx->r[i]->hc, algs.hashsz) == 0)
+     if (memcmp(hc, kx->r[i]->hc, kx->kpriv->algs.hashsz) == 0)
        return (kx->r[i]);
    }
    return (0);
@@@ -523,7 -448,7 +531,7 @@@ static void kxc_answer(keyexch *kx, kxc
    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);
+   G_TORAW(kx->kpriv->g, &bb, kxc->r);
    buf_flip(&bb);
    ks_encrypt(kxc->ks, MSG_KEYEXCH | KX_REPLY, &bb, b);
  
    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;
  }
  static int doprechallenge(keyexch *kx, buf *b)
  {
    stats *st = p_stats(kx->p);
-   ge *c = G_CREATE(gg);
+   ge *c = G_CREATE(kx->kpriv->g);
    ghash *h;
  
    /* --- Ensure that we're in a sensible state --- */
  
    /* --- Unpack the packet --- */
  
-   if (G_FROMBUF(gg, b, c) || BLEFT(b))
+   if (G_FROMBUF(kx->kpriv->g, b, c) || BLEFT(b))
      goto bad;
  
    IF_TRACING(T_KEYEXCH, IF_TRACING(T_CRYPTO, {
-     trace(T_CRYPTO, "crypto: challenge = %s", gestr(gg, c));
+     trace(T_CRYPTO, "crypto: challenge = %s", gestr(kx->kpriv->g, c));
    }))
  
    /* --- Send out a full challenge by return --- */
  
    b = p_txstart(kx->p, MSG_KEYEXCH | KX_CHAL);
-   h = GH_INIT(algs.h);
+   h = GH_INIT(kx->kpriv->algs.h);
    HASH_STRING(h, "tripe-cookie");
-   hashge(h, c);
+   hashge(h, kx->kpriv->g, c);
    sendchallenge(kx, b, c, GH_DONE(h, 0));
    GH_DESTROY(h);
    st->n_kxout++;
  
    /* --- Done --- */
  
-   G_DESTROY(gg, c);
+   G_DESTROY(kx->kpriv->g, c);
    return (0);
  
  bad:
-   if (c) G_DESTROY(gg, c);
+   if (c) G_DESTROY(kx->kpriv->g, c);
    return (-1);
  }
  
  
  static kxchal *respond(keyexch *kx, unsigned msg, buf *b)
  {
-   ge *c = G_CREATE(gg);
-   ge *r = G_CREATE(gg);
-   ge *cc = G_CREATE(gg);
+   group *g = kx->kpriv->g;
+   const algswitch *algs = &kx->kpriv->algs;
+   size_t ixsz = kx->kpriv->indexsz;
+   ge *c = G_CREATE(g);
+   ge *r = G_CREATE(g);
+   ge *cc = G_CREATE(g);
    const octet *hc, *ck;
    size_t x, y, z;
    mp *cv = 0;
  
    /* --- Unpack the packet --- */
  
-   if (G_FROMBUF(gg, b, c) ||
-       (hc = buf_get(b, algs.hashsz)) == 0 ||
-       (ck = buf_get(b, indexsz)) == 0) {
+   if (G_FROMBUF(g, b, c) ||
+       (hc = buf_get(b, algs->hashsz)) == 0 ||
+       (ck = buf_get(b, ixsz)) == 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));
-     trace_block(T_CRYPTO, "crypto: cookie", hc, algs.hashsz);
-     trace_block(T_CRYPTO, "crypto: check-value", ck, indexsz);
+     trace(T_CRYPTO, "crypto: challenge = %s", gestr(g, c));
+     trace_block(T_CRYPTO, "crypto: cookie", hc, algs->hashsz);
+     trace_block(T_CRYPTO, "crypto: check-value", ck, ixsz);
    }))
  
    /* --- Discard a packet with an invalid cookie --- */
  
-   if (hc && memcmp(hc, kx->hc, algs.hashsz) != 0) {
+   if (hc && memcmp(hc, kx->hc, algs->hashsz) != 0) {
      a_warn("KX", "?PEER", kx->p, "incorrect", "cookie", A_END);
      goto bad;
    }
     */
  
    if ((kxc = kxc_bychal(kx, c)) != 0) {
-     h = GH_INIT(algs.h);
+     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_HASH(h, ck, ixsz);
+     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);
+     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_TRACING(T_KEYEXCH, IF_TRACING(T_CRYPTO, {
-       trace(T_CRYPTO, "crypto: computed reply = %s", gestr(gg, r));
+       trace(T_CRYPTO, "crypto: computed reply = %s", gestr(g, 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)))
+     if (MP_CMP(cv, >, g->r) ||
+       (G_EXP(g, cc, g->g, cv),
+        !G_EQ(g, 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);
+     G_COPY(g, kxc->c, c);
+     G_COPY(g, kxc->r, r);
  
-     h = GH_INIT(algs.h);
-     HASH_STRING(h, "tripe-check-hash");
-     GH_HASH(h, ck, indexsz);
-     GH_DONE(h, kxc->ck);
-     GH_DESTROY(h);
+     h = GH_INIT(algs->h); HASH_STRING(h, "tripe-check-hash");
+     GH_HASH(h, ck, ixsz);
+     GH_DONE(h, kxc->ck); GH_DESTROY(h);
  
-     h = GH_INIT(algs.h);
-     HASH_STRING(h, "tripe-cookie");
-     hashge(h, kxc->c);
-     GH_DONE(h, kxc->hc);
-     GH_DESTROY(h);
+     h = GH_INIT(algs->h); HASH_STRING(h, "tripe-cookie");
+     hashge(h, g, 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);
+       trace_block(T_CRYPTO, "crypto: computed cookie",
+                 kxc->hc, algs->hashsz);
      }))
  
      /* --- Work out the shared key --- */
  
-     G_EXP(gg, r, c, kx->alpha);
+     G_EXP(g, r, c, kx->alpha);
      IF_TRACING(T_KEYEXCH, IF_TRACING(T_CRYPTO, {
-       trace(T_CRYPTO, "crypto: shared secret = %s", gestr(gg, r));
+       trace(T_CRYPTO, "crypto: shared secret = %s", gestr(g, r));
      }))
  
      /* --- Compute the switch messages --- */
  
-     h = GH_INIT(algs.h); HASH_STRING(h, "tripe-switch-request");
-     hashge(h, kx->c); hashge(h, kxc->c);
+     h = GH_INIT(algs->h); HASH_STRING(h, "tripe-switch-request");
+     hashge(h, g, kx->c); hashge(h, g, 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);
+     h = GH_INIT(algs->h); HASH_STRING(h, "tripe-switch-confirm");
+     hashge(h, g, kx->c); hashge(h, g, 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);
+     h = GH_INIT(algs->h); HASH_STRING(h, "tripe-switch-request");
+     hashge(h, g, kxc->c); hashge(h, g, 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);
+     h = GH_INIT(algs->h); HASH_STRING(h, "tripe-switch-confirm");
+     hashge(h, g, kxc->c); hashge(h, g, 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);
+                 kxc->hswrq_out, algs->hashsz);
        trace_block(T_CRYPTO, "crypto: outbound switch confirm",
-                 kxc->hswok_out, algs.hashsz);
+                 kxc->hswok_out, algs->hashsz);
        trace_block(T_CRYPTO, "crypto: inbound switch request",
-                 kxc->hswrq_in, algs.hashsz);
+                 kxc->hswrq_in, algs->hashsz);
        trace_block(T_CRYPTO, "crypto: inbound switch confirm",
-                 kxc->hswok_in, algs.hashsz);
+                 kxc->hswok_in, algs->hashsz);
      }))
  
      /* --- Create a new symmetric keyset --- */
  
      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);
+     G_TOBUF(g, &bb, kx->c); x = BLEN(&bb);
+     G_TOBUF(g, &bb, kxc->c); y = BLEN(&bb);
+     G_TOBUF(g, &bb, r); z = BLEN(&bb);
      assert(BOK(&bb));
  
      kxc->ks = ks_gen(BBASE(&bb), x, y, z, kx->p);
    }
  
-   G_DESTROY(gg, c);
-   G_DESTROY(gg, cc);
-   G_DESTROY(gg, r);
+   G_DESTROY(g, c);
+   G_DESTROY(g, cc);
+   G_DESTROY(g, r);
    mp_drop(cv);
    return (kxc);
  
@@@ -754,9 -681,9 +764,9 @@@ 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);
+   G_DESTROY(g, c);
+   G_DESTROY(g, cc);
+   G_DESTROY(g, r);
    mp_drop(cv);
    return (0);
  }
@@@ -807,7 -734,6 +817,7 @@@ static void resend(keyexch *kx
    kxchal *kxc;
    buf bb;
    stats *st = p_stats(kx->p);
 +  struct timeval tv;
    buf *b;
  
    switch (kx->s) {
        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);
+       G_TOBUF(kx->kpriv->g, 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_put(b, kx->hc, kx->kpriv->algs.hashsz);
+       buf_put(b, kxc->hc, kx->kpriv->algs.hashsz);
        buf_init(&bb, buf_i, sizeof(buf_i));
-       G_TORAW(gg, &bb, kxc->r);
-       buf_put(&bb, kxc->hswrq_out, algs.hashsz);
+       G_TORAW(kx->kpriv->g, &bb, kxc->r);
+       buf_put(&bb, kxc->hswrq_out, kx->kpriv->algs.hashsz);
        buf_flip(&bb);
        ks_encrypt(kxc->ks, MSG_KEYEXCH | KX_SWITCH, &bb, b);
        break;
        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_put(&bb, kxc->hswok_out, kx->kpriv->algs.hashsz);
        buf_flip(&bb);
        ks_encrypt(kxc->ks, MSG_KEYEXCH | KX_SWITCHOK, &bb, b);
        break;
      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@ --- *
@@@ -897,25 -821,26 +907,26 @@@ static int decryptrest(keyexch *kx, kxc
  
  static int checkresponse(keyexch *kx, unsigned msg, buf *b)
  {
-   ge *r = G_CREATE(gg);
+   group *g = kx->kpriv->g;
+   ge *r = G_CREATE(g);
  
-   if (G_FROMRAW(gg, b, r)) {
+   if (G_FROMRAW(g, 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));
+     trace(T_CRYPTO, "crypto: reply = %s", gestr(g, r));
    }))
-   if (!G_EQ(gg, r, kx->rx)) {
+   if (!G_EQ(g, r, kx->rx)) {
      a_warn("KX", "?PEER", kx->p, "incorrect", "response", A_END);
      goto bad;
    }
  
-   G_DESTROY(gg, r);
+   G_DESTROY(g, r);
    return (0);
  
  bad:
-   G_DESTROY(gg, r);
+   G_DESTROY(g, r);
    return (-1);
  }
  
@@@ -994,13 -919,8 +1005,13 @@@ bad
  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);
  
  static int doswitch(keyexch *kx, buf *b)
  {
+   size_t hsz = kx->kpriv->algs.hashsz;
    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) {
+   if ((hc_in = buf_get(b, hsz)) == 0 ||
+       (hc_out = buf_get(b, hsz)) == 0) {
      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);
+     trace_block(T_CRYPTO, "crypto: challenge", hc_in, hsz);
+     trace_block(T_CRYPTO, "crypto: cookie", hc_out, hsz);
    }))
    if ((kxc = kxc_byhc(kx, hc_in)) == 0 ||
-       memcmp(hc_out, kx->hc, algs.hashsz) != 0) {
+       memcmp(hc_out, kx->hc, hsz) != 0) {
      a_warn("KX", "?PEER", kx->p, "incorrect", "switch-rq", A_END);
      goto bad;
    }
    if (decryptrest(kx, kxc, KX_SWITCH, b) ||
        checkresponse(kx, KX_SWITCH, b))
      goto bad;
-   if ((hswrq = buf_get(b, algs.hashsz)) == 0 || BLEFT(b)) {
+   if ((hswrq = buf_get(b, hsz)) == 0 || BLEFT(b)) {
      a_warn("KX", "?PEER", kx->p, "invalid", "switch-rq", A_END);
      goto bad;
    }
    IF_TRACING(T_KEYEXCH, {
-     trace_block(T_CRYPTO, "crypto: switch request hash", hswrq, algs.hashsz);
+     trace_block(T_CRYPTO, "crypto: switch request hash", hswrq, hsz);
    })
-   if (memcmp(hswrq, kxc->hswrq_in, algs.hashsz) != 0) {
+   if (memcmp(hswrq, kxc->hswrq_in, hsz) != 0) {
      a_warn("KX", "?PEER", kx->p, "incorrect", "switch-rq", A_END);
      goto bad;
    }
@@@ -1072,6 -993,7 +1084,7 @@@ bad
  
  static int doswitchok(keyexch *kx, buf *b)
  {
+   size_t hsz = kx->kpriv->algs.hashsz;
    const octet *hswok;
    kxchal *kxc;
    buf bb;
    buf_init(&bb, buf_o, sizeof(buf_o));
    if (decryptrest(kx, kxc, KX_SWITCHOK, b))
      goto bad;
-   if ((hswok = buf_get(b, algs.hashsz)) == 0 || BLEFT(b)) {
+   if ((hswok = buf_get(b, hsz)) == 0 || BLEFT(b)) {
      a_warn("KX", "?PEER", kx->p, "invalid", "switch-ok", A_END);
      goto bad;
    }
    IF_TRACING(T_KEYEXCH, {
      trace_block(T_CRYPTO, "crypto: switch confirmation hash",
-               hswok, algs.hashsz);
+               hswok, hsz);
    })
-   if (memcmp(hswok, kxc->hswok_in, algs.hashsz) != 0) {
+   if (memcmp(hswok, kxc->hswok_in, hsz) != 0) {
      a_warn("KX", "?PEER", kx->p, "incorrect", "switch-ok", A_END);
      goto bad;
    }
@@@ -1132,8 -1054,8 +1145,8 @@@ static void stop(keyexch *kx
    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);
+   G_DESTROY(kx->kpriv->g, kx->c);
+   G_DESTROY(kx->kpriv->g, kx->rx);
    kx->t_valid = 0;
    kx->f |= KXF_DEAD;
    kx->f &= ~KXF_TIMER;
  
  static void start(keyexch *kx, time_t now)
  {
+   algswitch *algs = &kx->kpriv->algs;
+   group *g = kx->kpriv->g;
    ghash *h;
  
    assert(kx->f & KXF_DEAD);
  
    kx->f &= ~(KXF_DEAD | KXF_CORK);
    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->alpha = mprand_range(MP_NEW, g->r, &rand_global, 0);
+   kx->c = G_CREATE(g); G_EXP(g, kx->c, g->g, kx->alpha);
+   kx->rx = G_CREATE(g); G_EXP(g, kx->rx, kx->kpub->kpub, kx->alpha);
    kx->s = KXS_CHAL;
    kx->t_valid = now + T_VALID;
  
-   h = GH_INIT(algs.h);
+   h = GH_INIT(algs->h);
    HASH_STRING(h, "tripe-cookie");
-   hashge(h, kx->c);
+   hashge(h, g, kx->c);
    GH_DONE(h, kx->hc);
    GH_DESTROY(h);
  
      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);
+       trace(T_CRYPTO, "crypto: challenge = %s", gestr(g, kx->c));
+       trace(T_CRYPTO, "crypto: expected response = %s", gestr(g, kx->rx));
+       trace_block(T_CRYPTO, "crypto: challenge cookie",
+                 kx->hc, algs->hashsz);
      })
    })
  }
  static int checkpub(keyexch *kx)
  {
    time_t now;
+   unsigned f = 0;
    if (kx->f & KXF_DEAD)
      return (-1);
    now = time(0);
-   if (KEY_EXPIRED(now, kx->texp_kpub)) {
+   if (KEY_EXPIRED(now, kx->kpriv->t_exp)) f |= 1;
+   if (KEY_EXPIRED(now, kx->kpub->t_exp)) f |= 2;
+   if (f) {
      stop(kx);
-     a_warn("KX", "?PEER", kx->p, "public-key-expired", A_END);
-     G_COPY(gg, kx->kpub, gg->i);
+     if (f & 1) a_warn("KX", "?PEER", kx->p, "private-key-expired", A_END);
+     if (f & 2) a_warn("KX", "?PEER", kx->p, "public-key-expired", A_END);
      kx->f &= ~KXF_PUBKEY;
      return (-1);
    }
@@@ -1247,26 -1176,23 +1267,26 @@@ void kx_start(keyexch *kx, int forcep
  
  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);
 +    start(kx, now.tv_sec);
 +    rs_time(&kx->rs, &tv, &now);
 +    settimer(kx, &tv);
      a_notify("KXSTART", 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)); )
  void kx_free(keyexch *kx)
  {
    stop(kx);
-   G_DESTROY(gg, kx->kpub);
+   km_unref(kx->kpub);
+   km_unref(kx->kpriv);
  }
  
  /* --- @kx_newkeys@ --- *
  
  void kx_newkeys(keyexch *kx)
  {
-   if (km_getpubkey(p_tag(kx->p), kx->kpub, &kx->texp_kpub))
-     return;
+   kdata *kpriv, *kpub;
+   unsigned i;
+   int switchp;
+   time_t now = time(0);
+   T( trace(T_KEYEXCH, "keyexch: checking new keys for `%s'",
+          p_name(kx->p)); )
+   /* --- Find out whether we can use new keys --- *
+    *
+    * Try each available combination of new and old, public and private,
+    * except both old (which is status quo anyway).  The selection is encoded
+    * in @i@, with bit 0 for the private key and bit 1 for public key; a set
+    * bit means to use the old value, and a clear bit means to use the new
+    * one.
+    *
+    * This means that we currently prefer `old private and new public' over
+    * `new private and old public'.  I'm not sure which way round this should
+    * actually be.
+    */
+   for (i = 0; i < 3; i++) {
+     /* --- Select the keys we're going to examine --- *
+      *
+      * If we're meant to have a new key and don't, then skip this
+      * combination.
+      */
+     T( trace(T_KEYEXCH, "keyexch: checking %s private, %s public",
+            i & 1 ? "old" : "new", i & 2 ? "old" : "new"); )
+     if (i & 1) kpriv = kx->kpriv;
+     else if (kx->kpriv->kn->kd != kx->kpriv) kpriv = kx->kpriv->kn->kd;
+     else {
+       T( trace(T_KEYEXCH, "keyexch: private key unchanged, skipping"); )
+       continue;
+     }
+     if (i & 2) kpub = kx->kpub;
+     else if (kx->kpub->kn->kd != kx->kpub) kpub = kx->kpub->kn->kd;
+     else {
+       T( trace(T_KEYEXCH, "keyexch: public key unchanged, skipping"); )
+       continue;
+     }
+     /* --- Skip if either key is expired --- *
+      *
+      * We're not going to get far with expired keys, and this simplifies the
+      * logic below.
+      */
+     if (KEY_EXPIRED(now, kx->kpriv->t_exp) ||
+       KEY_EXPIRED(now, kx->kpub->t_exp)) {
+       T( trace(T_KEYEXCH, "keyexch: %s expired, skipping",
+              !KEY_EXPIRED(now, kx->kpriv->t_exp) ? "public key" :
+              !KEY_EXPIRED(now, kx->kpub->t_exp) ? "private key" :
+              "both keys"); )
+       continue;
+     }
+     /* --- If the groups don't match then we can't use this pair --- */
+     if (!km_samealgsp(kpriv, kpub)) {
+       T( trace(T_KEYEXCH, "keyexch: peer `%s' group mismatch; "
+              "%s priv `%s' and %s pub `%s'", p_name(kx->p),
+              i & 1 ? "old" : "new", km_tag(kx->kpriv),
+              i & 2 ? "old" : "new", km_tag(kx->kpub)); )
+       continue;
+     }
+     goto newkeys;
+   }
+   T( trace(T_KEYEXCH, "keyexch: peer `%s' continuing with old keys",
+          p_name(kx->p)); )
+   return;
+   /* --- We've chosen new keys --- *
+    *
+    * Switch the new ones into place.  Neither of the keys we're switching to
+    * is expired (we checked that above), so we should just crank everything
+    * up.
+    *
+    * A complication arises: we don't really want to force a new key exchange
+    * unless we have to.  If the group is unchanged, and we're currently
+    * running OK, then we should just let things lie.
+    */
+ newkeys:
+   switchp = ((kx->f & KXF_DEAD) ||
+            kx->s != KXS_SWITCH ||
+            !group_samep(kx->kpriv->g, kpriv->g));
+   T( trace(T_KEYEXCH, "keyexch: peer `%s' adopting "
+          "%s priv `%s' and %s pub `%s'; %sforcing exchange", p_name(kx->p),
+          i & 1 ? "old" : "new", km_tag(kx->kpriv),
+          i & 2 ? "old" : "new", km_tag(kx->kpub),
+          switchp ? "" : "not "); )
+   if (switchp) stop(kx);
+   km_ref(kpriv); km_unref(kx->kpriv); kx->kpriv = kpriv;
+   km_ref(kpub);  km_unref(kx->kpub);  kx->kpub  = kpub;
    kx->f |= KXF_PUBKEY;
-   if ((kx->f & KXF_DEAD) || kx->s != KXS_SWITCH) {
+   if (switchp) {
      T( trace(T_KEYEXCH, "keyexch: restarting key negotiation with `%s'",
             p_name(kx->p)); )
-     stop(kx);
      start(kx, time(0));
      resend(kx);
    }
  
  int kx_init(keyexch *kx, peer *p, keyset **ks, unsigned f)
  {
+   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",
+          "local-private-key", "%s", p_privtag(p),
+          "peer-public-key", "%s", p_tag(p),
+          A_END);
+     goto fail_2;
+   }
    kx->ks = ks;
    kx->p = p;
-   kx->kpub = G_CREATE(gg);
-   if (km_getpubkey(p_tag(p), kx->kpub, &kx->texp_kpub)) {
-     G_DESTROY(gg, kx->kpub);
-     return (-1);
-   }
    kx->f = KXF_DEAD | KXF_PUBKEY | f;
 +  rs_reset(&kx->rs);
    if (!(kx->f & KXF_CORK)) {
      start(kx, time(0));
      resend(kx);
      /* Don't notify here: the ADD message hasn't gone out yet. */
    }
    return (0);
+ fail_2:
+   km_unref(kx->kpub);
+ fail_1:
+   km_unref(kx->kpriv);
+ fail_0:
+   return (-1);
  }
  
  /*----- That's all, folks -------------------------------------------------*/
diff --combined server/keyset.c
index f21a59a9f7b6232496c23e55d77559fbccfc7575,898c55a4ccd9a8de84d42a4e912bfc6d74c85b78..c54febdc73bde4a38c4d1051d3df2547b346a1a5
  
  #include "tripe.h"
  
 -/*----- Tunable parameters ------------------------------------------------*/
 -
 -#define T_EXP MIN(60)                 /* Expiry time for a key */
 -#define T_REGEN MIN(45)                       /* Regeneration time for a key */
 -
  /*----- Handy macros ------------------------------------------------------*/
  
  #define KEYOK(ks, now) ((ks)->sz_exp > 0 && (ks)->t_exp > now)
@@@ -288,6 -293,7 +288,7 @@@ keyset *ks_gen(const void *k, size_t x
    keyset *ks = CREATE(keyset);
    time_t now = time(0);
    const octet *pp = k;
+   const algswitch *algs = &p->kx.kpriv->algs;
    T( static unsigned seq = 0; )
  
    T( trace(T_KEYSET, "keyset: adding new keyset %u", seq); )
  
  #define HASH_in MINE; YOURS; OURS
  #define HASH_out YOURS; MINE; OURS
- #define INIT_c(k) GC_INIT(algs.c, (k), algs.cksz)
- #define INIT_m(k) GM_KEY(algs.m, (k), algs.mksz)
+ #define INIT_c(k) GC_INIT(algs->c, (k), algs->cksz)
+ #define INIT_m(k) GM_KEY(algs->m, (k), algs->mksz)
  #define STR_c "encryption"
  #define STR_m "integrity"
  #define STR_in "incoming"
  #define STR_out "outgoing"
  
  #define SETKEY(a, dir) do {                                           \
-   h = GH_INIT(algs.h);                                                        \
+   h = GH_INIT(algs->h);                                                       \
    HASH_STRING(h, "tripe-" STR_##a);                                   \
    HASH_##dir;                                                         \
    hh = GH_DONE(h, 0);                                                 \
    IF_TRACING(T_KEYSET, {                                              \
      trace_block(T_CRYPTO, "crypto: " STR_##dir " key " STR_##a,               \
-               hh, algs.a##ksz);                                       \
+               hh, algs->a##ksz);                                      \
    })                                                                  \
    ks->a##dir = INIT_##a(hh);                                          \
    GH_DESTROY(h);                                                      \
    T( ks->seq = seq++; )
    ks->ref = 1;
    ks->t_exp = now + T_EXP;
-   ks->sz_exp = algs.expsz;
-   ks->sz_regen = algs.expsz/2;
+   ks->sz_exp = algs->expsz;
+   ks->sz_regen = algs->expsz/2;
    ks->oseq = 0;
    seq_reset(&ks->iseq);
    ks->next = 0;
    ks->p = p;
    ks->f = KSF_LISTEN;
-   ks->tagsz = algs.tagsz;
+   ks->tagsz = algs->tagsz;
    return (ks);
  }
  
 -/* --- @ks_tregen@ --- *
 - *
 - * Arguments: @keyset *ks@ = pointer to a keyset
 - *
 - * Returns:   The time at which moves ought to be made to replace this key.
 - */
 -
 -time_t ks_tregen(keyset *ks) { return (ks->t_exp - T_EXP + T_REGEN); }
 -
  /* --- @ks_activate@ --- *
   *
   * Arguments: @keyset *ks@ = pointer to a keyset
diff --combined server/tests.at
index e0eaa05b08b0c30fcf081ac5bf59f6aa438e213f,a326709ec085f8792109c724962a37f0ba9fdb6b..1171fa221fcde53121dd8580fffe854f8153c1e6
@@@ -40,7 -40,6 +40,7 @@@ m4_define([TRIPECTL], [$abs_top_builddi
  m4_define([USLIP], [$abs_top_builddir/uslip/tripe-uslip])
  m4_define([PKSTREAM],
    [$abs_top_builddir/pkstream/pkstream -b127.0.0.1 -p127.0.0.1])
 +m4_define([MITM], [$abs_top_builddir/proxy/tripe-mitm])
  
  ## Sequences.  (These are used for testing the replay protection machinery.)
  m4_define([R32], [ 0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15   dnl
@@@ -182,24 -181,10 +182,24 @@@ m4_define([WITH_3TRIPES]
          [WITH_TRIPEX([$3], [$4 $7],
          [$8])])])])
  
 -## COMMS_EPING(adir, aname, bdir, bname)
 +## RETRY(n, body)
 +m4_define([RETRY], [
 +  n=0 rc=1
 +  while test $n -lt $1; do
 +    if $2
 +    then rc=0; break
 +    fi
 +    n=$(( $n + 1 ))
 +  done
 +  exit $rc
 +])
 +
 +## COMMS_EPING(adir, aname, bdir, bname, [n])
  m4_define([COMMS_EPING], [
 -  AT_CHECK([TRIPECTL -d$1 EPING $4],, [ignore])
 -  AT_CHECK([TRIPECTL -d$3 EPING $2],, [ignore])
 +  AT_CHECK([RETRY([m4_default([$5], [1])],
 +    [TRIPECTL -d$1 EPING $4])],, [ignore])
 +  AT_CHECK([RETRY([m4_default([$5], [1])],
 +    [TRIPECTL -d$3 EPING $2])],, [ignore])
  ])
  
  ## COMMS_SLIP(adir, aname, bdir, bname)
@@@ -438,44 -423,143 +438,181 @@@ WITH_3TRIPES([alice], [bob], [carol], [
  
  AT_CLEANUP
  
 +###--------------------------------------------------------------------------
 +### Adverse communication.
 +
 +AT_SETUP([server retry])
 +AT_KEYWORDS([backoff])
 +export TRIPE_SLIPIF=USLIP
 +
 +for i in alice bob; do (mkdir $i; cd $i; SETUPDIR([beta])); done
 +
 +WITH_2TRIPES([alice], [bob], [-nslip], [-talice], [-tbob], [
 +
 +  ## Set up the evil proxy.
 +  alicemitm=24516 bobmitm=14016
 +  MITM -kalice/keyring.pub >mitm.out 2>mitm.err \
 +    peer:alice:$alicemitm:127.0.0.1:$(cat alice/port) \
 +    peer:bob:$bobmitm:127.0.0.1:$(cat bob/port) \
 +    filt:drop:5 filt:send& mitmpid=$!
 +  strace -omitm.trace -p$mitmpid& mitmtrace=$!
 +  trap 'kill $mitmpid $mitmtrace; exit 127' EXIT INT QUIT TERM HUP
 +
 +  ## Try to establish keys anyway.
 +  AWAIT_KXDONE([alice], [alice], [bob], [bob], [
 +    AT_CHECK([TRIPECTL -dalice ADD -cork bob   INET 127.0.0.1 $alicemitm])
 +    AT_CHECK([TRIPECTL -dbob   ADD       alice INET 127.0.0.1 $bobmitm])
 +  ])
 +
 +  ## Check pinging.
 +  COMMS_EPING([alice], [alice], [bob], [bob], [10])
 +  COMMS_EPING([bob], [bob], [alice], [alice], [10])
 +
 +  ## Tear down the MITM proxy.
 +  kill $mitmpid
 +  wait $mitmpid
 +  wait $mitmtrace
 +])
 +
 +AT_CLEANUP
 +
+ ###--------------------------------------------------------------------------
+ ### Key management.
+ AT_SETUP([server key-management])
+ AT_KEYWORDS([keymgmt])
+ export TRIPE_SLIPIF=USLIP
+ ## Determine all of the nets and the principals.
+ princs=""
+ nets=" "
+ while read princ pnets; do
+   princs="$princs $princ"
+   for n in $pnets; do
+     case " $nets " in *" $n "*) ;; *) nets="$nets$n " ;; esac
+   done
+ done <<PRINC
+ alice alpha   beta
+ bob   alpha   beta
+ carol beta
+ PRINC
+ ## Build the master keyring.  All key tags here are of the form PRINC/NET.
+ for n in $nets; do
+   key -k$abs_top_srcdir/t/keyring-$n extract keyring-$n $princs
+   for p in $princs; do key -kkeyring-$n tag $p $p/$n; done
+   key merge keyring-$n
+   rm keyring-$n
+ done
+ key extract -f-secret keyring.pub
+ ## Set up the principals' directories.
+ for p in $princs; do
+   mkdir $p
+   cp keyring keyring.pub $p/
+ done
+ WITH_3TRIPES([alice], [bob], [carol], [-nslip -Tmx],
+       [-talice/alpha], [-tbob/alpha], [-tcarol/beta], [
+   ## Establish this little merry-go-round.
+   ESTABLISH([alice], [alice], [-key alice/alpha],
+       [bob], [bob], [-key bob/alpha])
+   ESTABLISH([alice], [alice], [-key alice/beta],
+       [carol], [carol], [-priv alice/beta -key carol/beta])
+   ESTABLISH([bob], [bob], [-key bob/beta],
+       [carol], [carol], [-priv bob/beta -key carol/beta])
+   ## Tweak Bob's alpha key.
+   for p in $princs; do
+     TRIPECTL -d$p WARN test COMMENT tweak bob/alpha
+   done
+   key -k$abs_top_srcdir/t/keyring-alpha extract keyring-bob-new bob-new
+   key merge keyring-bob-new
+   key tag -r bob-new bob/alpha
+   key extract -f-secret keyring.pub
+   for p in alice bob; do cp keyring keyring.pub $p/; done
+   ## Kick the peers to see whether they update.
+   AWAIT_KXDONE([alice], [alice], [bob], [bob], [
+     TRIPECTL -dalice RELOAD
+     TRIPECTL -dbob RELOAD
+     TRIPECTL -dalice FORCEKX bob
+     TRIPECTL -dbob FORCEKX alice
+   ])
+   COMMS_EPING([alice], [alice], [bob], [bob])
+   COMMS_EPING([bob], [bob], [alice], [alice])
+   ## Update the beta ring.
+   key merge $abs_top_srcdir/t/keyring-beta-new
+   for p in $princs; do key tag -r $p $p/beta; done
+   key extract -f-secret keyring.pub
+   ## Update alice's and carol's private keys, bob's public.  This should be
+   ## insufficient for them to switch, but the results could be interesting.
+   for p in $princs; do
+     TRIPECTL -d$p WARN test COMMENT tweak beta step 1
+   done
+   for p in alice carol; do cp keyring $p/; done
+   cp keyring.pub bob/
+   for p in $princs; do TRIPECTL -d$p RELOAD; done
+   AT_DATA([algs-alpha], [dnl
+ kx-group=ec kx-group-order-bits=256 kx-group-elt-bits=512
+ hash=rmd160 mgf=rmd160-mgf hash-sz=20
+ cipher=blowfish-cbc cipher-keysz=20 cipher-blksz=8
+ cipher-data-limit=67108864
+ mac=rmd160-hmac mac-keysz=20 mac-tagsz=10
+ ])
+   AT_DATA([algs-beta-old], [dnl
+ kx-group=prime kx-group-order-bits=160 kx-group-elt-bits=1023
+ hash=rmd160 mgf=rmd160-mgf hash-sz=20
+ cipher=blowfish-cbc cipher-keysz=20 cipher-blksz=8
+ cipher-data-limit=67108864
+ mac=rmd160-hmac mac-keysz=20 mac-tagsz=10
+ ])
+   AT_DATA([algs-beta-new], [dnl
+ kx-group=ec kx-group-order-bits=161 kx-group-elt-bits=320
+ hash=rmd160 mgf=rmd160-mgf hash-sz=20
+ cipher=blowfish-cbc cipher-keysz=20 cipher-blksz=8
+ cipher-data-limit=67108864
+ mac=rmd160-hmac mac-keysz=20 mac-tagsz=10
+ ])
+   cp algs-alpha expout;    AT_CHECK([TRIPECTL -dalice ALGS],,       [expout])
+   cp algs-beta-old expout; AT_CHECK([TRIPECTL -dalice ALGS carol],, [expout])
+   cp algs-beta-old expout; AT_CHECK([TRIPECTL -dbob   ALGS carol],, [expout])
+   cp algs-beta-new expout; AT_CHECK([TRIPECTL -dcarol ALGS],,       [expout])
+   cp algs-beta-old expout; AT_CHECK([TRIPECTL -dcarol ALGS alice],, [expout])
+   ## Now copy the full keys.  We expect this to provoke key exchange.
+   for p in $princs; do
+     TRIPECTL -d$p WARN test COMMENT tweak beta step 2
+   done
+   for p in $princs; do cp keyring keyring.pub $p/; done
+   AWAIT_KXDONE([alice], [alice], [carol], [carol], [
+     TRIPECTL -dalice RELOAD
+     AWAIT_KXDONE([bob], [bob], [carol], [carol], [
+       TRIPECTL -dbob RELOAD
+       TRIPECTL -dcarol RELOAD
+     ])
+   ])
+   cp algs-alpha expout;    AT_CHECK([TRIPECTL -dalice ALGS],,       [expout])
+   cp algs-beta-new expout; AT_CHECK([TRIPECTL -dalice ALGS carol],, [expout])
+   cp algs-beta-new expout; AT_CHECK([TRIPECTL -dbob   ALGS carol],, [expout])
+   cp algs-beta-new expout; AT_CHECK([TRIPECTL -dcarol ALGS],,       [expout])
+ ])
+ AT_CLEANUP
  ###--------------------------------------------------------------------------
  ### Services.
  
diff --combined server/tripe.h
index ad20daa72255dce993051e05f121541e1f0e95df,b4eee1b37c780d58e9c69d7391beec7708a40253..72a47a108b45739de63e1d7d1eb8f600b6edc909
  
  #define SEC(n) (n##u)
  #define MIN(n) (n##u * 60u)
 +#define F_2P32 (65536.0*65536.0)
  #define MEG(n) (n##ul * 1024ul * 1024ul)
  
 +/* --- Timing parameters --- */
 +
 +#define T_EXP MIN(60)                 /* Expiry time for a key */
 +#define T_REGEN MIN(40)                       /* Regeneration time for a key */
 +
 +#define T_VALID SEC(20)                       /* Challenge validity period */
 +#define T_RETRYMIN SEC(2)             /* Minimum retry interval */
 +#define T_RETRYMAX MIN(5)             /* Maximum retry interval */
 +#define T_RETRYGROW (5.0/4.0)         /* Retry interval growth factor */
 +
 +#define T_WOBBLE (1.0/3.0)            /* Relative timer randomness */
 +
  /* --- Other things --- */
  
  #define PKBUFSZ 65536
@@@ -166,7 -153,25 +166,25 @@@ typedef struct algswitch 
    size_t cksz, mksz;                  /* Key lengths for @c@ and @m@ */
  } algswitch;
  
- extern algswitch algs;
+ typedef struct kdata {
+   unsigned ref;                               /* Reference counter */
+   struct knode *kn;                   /* Pointer to cache entry */
+   char *tag;                          /* Full tag name of the key */
+   group *g;                           /* The group we work in */
+   size_t indexsz;                     /* Size of exponent for the group */
+   mp *kpriv;                          /* The private key (or null) */
+   ge *kpub;                           /* The public key */
+   time_t t_exp;                               /* Expiry time of the key */
+   algswitch algs;                     /* Collection of algorithms */
+ } kdata;
+ typedef struct knode {
+   sym_base _b;                                /* Symbol table intrusion */
+   unsigned f;                         /* Various flags */
+ #define KNF_BROKEN 1u                 /*   Don't use this key any more */
+   struct keyhalf *kh;                 /* Pointer to the home keyhalf */
+   kdata *kd;                          /* Pointer to the key data */
+ } knode;
  
  #define MAXHASHSZ 64                  /* Largest possible hash size */
  
@@@ -251,10 -256,6 +269,10 @@@ typedef struct keyset 
   * Clive Jones.
   */
  
 +typedef struct retry {
 +  double t;                           /* Current retry time */
 +} retry;
 +
  #define KX_NCHAL 16u
  
  typedef struct kxchal {
    keyset *ks;                         /* Pointer to temporary keyset */
    unsigned f;                         /* Various useful flags */
    sel_timer t;                                /* Response timer for challenge */
 +  retry rs;                           /* Retry state */
    octet hc[MAXHASHSZ];                        /* Hash of his challenge */
    octet ck[MAXHASHSZ];                        /* His magical check value */
    octet hswrq_in[MAXHASHSZ];          /* Inbound switch request message */
  
  typedef struct keyexch {
    struct peer *p;                     /* Pointer back to the peer */
+   kdata *kpriv;                               /* Private key and related info */
+   kdata *kpub;                                /* Peer's public key */
    keyset **ks;                                /* Peer's list of keysets */
    unsigned f;                         /* Various useful flags */
    unsigned s;                         /* Current state in exchange */
    sel_timer t;                                /* Timer for next exchange */
-   ge *kpub;                           /* Peer's public key */
-   time_t texp_kpub;                   /* Expiry time for public key */
 +  retry rs;                           /* Retry state */
    mp *alpha;                          /* My temporary secret */
    ge *c;                              /* My challenge */
    ge *rx;                             /* The expected response */
@@@ -354,6 -353,7 +372,7 @@@ typedef struct stats 
  
  typedef struct peerspec {
    char *name;                         /* Peer's name */
+   char *privtag;                      /* Private key tag */
    char *tag;                          /* Public key tag */
    const tunnel_ops *tops;             /* Tunnel operations */
    unsigned long t_ka;                 /* Keep alive interval */
@@@ -518,13 -518,11 +537,11 @@@ typedef struct admin 
  /*----- Global variables --------------------------------------------------*/
  
  extern sel_state sel;                 /* Global I/O event state */
- extern group *gg;                     /* The group we work in */
- extern size_t indexsz;                        /* Size of exponent for the group */
- extern mp *kpriv;                     /* Our private key */
- extern ge *kpub;                      /* Our public key */
  extern octet buf_i[PKBUFSZ], buf_o[PKBUFSZ], buf_t[PKBUFSZ], buf_u[PKBUFSZ];
  extern const tunnel_ops *tunnels[];   /* Table of tunnels (0-term) */
  extern const tunnel_ops *tun_default; /* Default tunnel to use */
+ extern kdata *master;                 /* Default private key */
+ extern const char *tag_priv;          /* Default private key tag */
  
  #ifndef NTRACE
  extern const trace_opt tr_opts[];     /* Trace options array */
@@@ -537,6 -535,21 +554,21 @@@ extern unsigned tr_flags;                /* Trace opt
  
  /*----- Key management ----------------------------------------------------*/
  
+ /* --- @km_init@ --- *
+  *
+  * Arguments: @const char *privkr@ = private keyring file
+  *            @const char *pubkr@ = public keyring file
+  *            @const char *ptag@ = default private-key tag
+  *
+  * Returns:   ---
+  *
+  * Use:               Initializes the key-management machinery, loading the
+  *            keyrings and so on.
+  */
+ extern void km_init(const char */*privkr*/, const char */*pubkr*/,
+                   const char */*ptag*/);
  /* --- @km_reload@ --- *
   *
   * Arguments: ---
  
  extern int km_reload(void);
  
- /* --- @km_init@ --- *
+ /* --- @km_findpub@, @km_findpriv@ --- *
+  *
+  * Arguments: @const char *tag@ = key tag to load
+  *
+  * Returns:   Pointer to the kdata object if successful, or null on error.
+  *
+  * Use:               Fetches a public or private key from the keyring.
+  */
+ extern kdata *km_findpub(const char */*tag*/);
+ extern kdata *km_findpriv(const char */*tag*/);
+ /* --- @km_samealgsp@ --- *
+  *
+  * Arguments: @const kdata *kdx, *kdy@ = two key data objects
+  *
+  * Returns:   Nonzero if their two algorithm selections are the same.
+  *
+  * Use:               Checks sameness of algorithm selections: used to ensure that
+  *            peers are using sensible algorithms.
+  */
+ extern int km_samealgsp(const kdata */*kdx*/, const kdata */*kdy*/);
+ /* --- @km_ref@ --- *
   *
-  * Arguments: @const char *kr_priv@ = private keyring file
-  *            @const char *kr_pub@ = public keyring file
-  *            @const char *tag@ = tag to load
+  * Arguments: @kdata *kd@ = pointer to the kdata object
   *
   * Returns:   ---
   *
-  * Use:               Initializes, and loads the private key.
+  * Use:               Claim a new reference to a kdata object.
   */
  
- extern void km_init(const char */*kr_priv*/, const char */*kr_pub*/,
-                   const char */*tag*/);
+ extern void km_ref(kdata */*kd*/);
  
- /* --- @km_getpubkey@ --- *
+ /* --- @km_unref@ --- *
   *
-  * Arguments: @const char *tag@ = public key tag to load
-  *            @ge *kpub@ = where to put the public key
-  *            @time_t *t_exp@ = where to put the expiry time
+  * Arguments: @kdata *kd@ = pointer to the kdata object
   *
-  * Returns:   Zero if OK, nonzero if it failed.
+  * Returns:   ---
   *
-  * Use:               Fetches a public key from the keyring.
+  * Use:               Releases a reference to a kdata object.
   */
  
- extern int km_getpubkey(const char */*tag*/, ge */*kpub*/,
-                       time_t */*t_exp*/);
+ extern void km_unref(kdata */*kd*/);
+ /* --- @km_tag@ --- *
+  *
+  * Arguments: @kdata *kd@ - pointer to the kdata object
+  *
+  * Returns:   A pointer to the short tag by which the kdata was loaded.
+  */
+ extern const char *km_tag(kdata */*kd*/);
  
  /*----- Key exchange ------------------------------------------------------*/
  
@@@ -689,6 -729,15 +748,6 @@@ extern keyset *ks_gen(const void */*k*/
                      size_t /*x*/, size_t /*y*/, size_t /*z*/,
                      peer */*p*/);
  
 -/* --- @ks_tregen@ --- *
 - *
 - * Arguments: @keyset *ks@ = pointer to a keyset
 - *
 - * Returns:   The time at which moves ought to be made to replace this key.
 - */
 -
 -extern time_t ks_tregen(keyset */*ks*/);
 -
  /* --- @ks_activate@ --- *
   *
   * Arguments: @keyset *ks@ = pointer to a keyset
@@@ -871,6 -920,19 +930,19 @@@ extern int c_check(buf */*b*/)
  
  extern void a_vformat(dstr */*d*/, const char */*fmt*/, va_list /*ap*/);
  
+ /* --- @a_format@ --- *
+  *
+  * Arguments: @dstr *d@ = where to leave the formatted message
+  *            @const char *fmt@ = pointer to format string
+  *
+  * Returns:   ---
+  *
+  * Use:               Writes a tokenized message into a string, for later
+  *            presentation.
+  */
+ extern void a_format(dstr */*d*/, const char */*fmt*/, ...);
  /* --- @a_warn@ --- *
   *
   * Arguments: @const char *fmt@ = pointer to format string
@@@ -1269,6 -1331,15 +1341,15 @@@ extern const char *p_name(peer */*p*/)
  
  extern const char *p_tag(peer */*p*/);
  
+ /* --- @p_privtag@ --- *
+  *
+  * Arguments: @peer *p@ = pointer to a peer block
+  *
+  * Returns:   A pointer to the peer's private key tag.
+  */
+ extern const char *p_privtag(peer */*p*/);
  /* --- @p_spec@ --- *
   *
   * Arguments: @peer *p@ = pointer to a peer block