chiark / gitweb /
server/: Make bulk crypto transforms responsible for algorithm selection.
authorMark Wooding <mdw@distorted.org.uk>
Wed, 19 Apr 2017 19:41:18 +0000 (20:41 +0100)
committerMark Wooding <mdw@distorted.org.uk>
Sun, 14 May 2017 17:19:08 +0000 (18:19 +0100)
Move all of the logic around processing symmetric algorithm selections
into the `bulkcrypto' transforms.  There are now three associated object
types:

  * an algorithm selection, which ends up attached to a peer key;

  * an encryption context, which actually performs the bulk transform on
    packets; and

  * a challenge context, which can issue and verify challenge tags.

The important improvement here is that now we can add new bulk crypto
transforms which are parametrized in different ways.

server/admin.c
server/bulkcrypto.c
server/chal.c
server/keymgmt.c
server/keyset.c
server/tests.at
server/tripe.h

index fcf27d3..b12a3be 100644 (file)
@@ -1743,33 +1743,15 @@ static void acmd_algs(admin *a, unsigned ac, char *av[])
         "hash-sz=%lu", (unsigned long)algs->h->hashsz,
         A_END);
   a_info(a,
-        "bulk-transform=%s", algs->bulk->name,
-        "bulk-overhead=%lu", (unsigned long)algs->bulk->overhead(algs),
+        "bulk-transform=%s", algs->bulk->ops->name,
+        "bulk-overhead=%lu",
+        (unsigned long)algs->bulk->ops->overhead(algs->bulk),
         A_END);
-  if (algs->c) {
-    a_info(a,
-          "cipher=%s", algs->c->name,
-          "cipher-keysz=%lu", (unsigned long)algs->cksz,
-          "cipher-blksz=%lu", (unsigned long)algs->c->blksz,
-          A_END);
-  }
+  algs->bulk->ops->alginfo(algs->bulk, a);
   a_info(a,
-        "cipher-data-limit=%lu", (unsigned long)algs->expsz,
+        "cipher-data-limit=%lu",
+        (unsigned long)algs->bulk->ops->expsz(algs->bulk),
         A_END);
-  if (algs->m) {
-    a_info(a,
-          "mac=%s", algs->m->name,
-          "mac-keysz=%lu", (unsigned long)algs->mksz,
-          "mac-tagsz=%lu", (unsigned long)algs->tagsz,
-          A_END);
-  }
-  if (algs->b) {
-    a_info(a,
-          "blkc=%.*s", strlen(algs->b->name) - 4, algs->b->name,
-          "blkc-keysz=%lu", (unsigned long)algs->bksz,
-          "blkc-blksz=%lu", (unsigned long)algs->b->blksz,
-          A_END);
-  }
   a_ok(a);
 }
 
index 26ae9ed..288eed9 100644 (file)
@@ -50,6 +50,8 @@
   trace_block(T_CRYPTO, "crypto: expected MAC", (pmac), (tagsz));      \
 }) } while (0)
 
+/*----- Common functionality for generic-composition transforms -----------*/
+
 #define CHECK_MAC(h, pmac, tagsz) do {                                 \
   ghash *_h = (h);                                                     \
   const octet *_pmac = (pmac);                                         \
   }                                                                    \
 } while (0)
 
+typedef struct gencomp_algs {
+  const gccipher *c; size_t cksz;
+  const gcmac *m; size_t mksz; size_t tagsz;
+} gencomp_algs;
+
+typedef struct gencomp_chal {
+  bulkchal _b;
+  gmac *m; size_t tagsz;
+} gencomp_chal;
+
+static int gencomp_getalgs(gencomp_algs *a, const algswitch *asw,
+                          dstr *e, key_file *kf, key *k)
+{
+  const char *p;
+  char *q, *qq;
+  unsigned long n;
+  dstr d = DSTR_INIT;
+  int rc = -1;
+
+  /* --- Symmetric encryption --- */
+
+  if ((p = key_getattr(kf, k, "cipher")) == 0) p = "blowfish-cbc";
+  if ((a->c = gcipher_byname(p)) == 0) {
+    a_format(e, "unknown-cipher", "%s", p, A_END);
+    goto done;
+  }
+
+  /* --- Message authentication --- */
+
+  if ((p = key_getattr(kf, k, "mac")) != 0) {
+    dstr_reset(&d);
+    dstr_puts(&d, p);
+    if ((q = strchr(d.buf, '/')) != 0)
+      *q++ = 0;
+    if ((a->m = gmac_byname(d.buf)) == 0) {
+      a_format(e, "unknown-mac", "%s", d.buf, A_END);
+      goto done;
+    }
+    if (!q)
+      a->tagsz = a->m->hashsz;
+    else {
+      n = strtoul(q, &qq, 0);
+      if (*qq)  {
+       a_format(e, "bad-tag-length-string", "%s", q, A_END);
+       goto done;
+      }
+      if (n%8 || n/8 > a->m->hashsz) {
+       a_format(e, "bad-tag-length", "%lu", n, A_END);
+       goto done;
+      }
+      a->tagsz = n/8;
+    }
+  } else {
+    dstr_reset(&d);
+    dstr_putf(&d, "%s-hmac", asw->h->name);
+    if ((a->m = gmac_byname(d.buf)) == 0) {
+      a_format(e, "no-hmac-for-hash", "%s", asw->h->name, A_END);
+      goto done;
+    }
+    a->tagsz = asw->h->hashsz/2;
+  }
+
+  rc = 0;
+done:
+  dstr_destroy(&d);
+  return (rc);
+}
+
+#ifndef NTRACE
+static void gencomp_tracealgs(const gencomp_algs *a)
+{
+  trace(T_CRYPTO, "crypto: cipher = %s", a->c->name);
+  trace(T_CRYPTO, "crypto: mac = %s/%lu",
+       a->m->name, (unsigned long)a->tagsz * 8);
+}
+#endif
+
+static int gencomp_checkalgs(gencomp_algs *a, const algswitch *asw, dstr *e)
+{
+  /* --- Derive the key sizes --- *
+   *
+   * Must ensure that we have non-empty keys.  This isn't ideal, but it
+   * provides a handy sanity check.  Also must be based on a 64- or 128-bit
+   * block cipher or we can't do the data expiry properly.
+   */
+
+  if ((a->cksz = keysz(asw->hashsz, a->c->keysz)) == 0) {
+    a_format(e, "cipher", "%s", a->c->name,
+            "no-key-size", "%lu", (unsigned long)asw->hashsz,
+            A_END);
+    return (-1);
+  }
+  if ((a->mksz = keysz(asw->hashsz, a->m->keysz)) == 0) {
+    a_format(e, "mac", "%s", a->m->name,
+            "no-key-size", "%lu", (unsigned long)asw->hashsz,
+            A_END);
+    return (-1);
+  }
+
+  return (0);
+}
+
+static void gencomp_alginfo(const gencomp_algs *a, admin *adm)
+{
+  a_info(adm,
+        "cipher=%s", a->c->name,
+        "cipher-keysz=%lu", (unsigned long)a->cksz,
+        "cipher-blksz=%lu", (unsigned long)a->c->blksz,
+        A_END);
+  a_info(adm,
+        "mac=%s", a->m->name,
+        "mac-keysz=%lu", (unsigned long)a->mksz,
+        "mac-tagsz=%lu", (unsigned long)a->tagsz,
+        A_END);
+}
+
+static int gencomp_samealgsp(const gencomp_algs *a, const gencomp_algs *aa)
+{
+  return (a->c == aa->c &&
+         a->m == aa->m && a->tagsz == aa->tagsz);
+}
+
+static size_t gencomp_expsz(const gencomp_algs *a)
+  { return (a->c->blksz < 16 ? MEG(64) : MEG(2048)); }
+
+static bulkchal *gencomp_genchal(const gencomp_algs *a)
+{
+  gencomp_chal *gc = CREATE(gencomp_chal);
+
+  rand_get(RAND_GLOBAL, buf_t, a->mksz);
+  gc->m = GM_KEY(a->m, buf_t, a->mksz);
+  gc->_b.tagsz = a->tagsz;
+  IF_TRACING(T_CHAL, {
+    trace(T_CHAL, "chal: generated new challenge key");
+    trace_block(T_CRYPTO, "chal: new key", buf_t, a->mksz);
+  })
+  return (&gc->_b);
+}
+
+static int gencomp_chaltag(bulkchal *bc, const void *m, size_t msz, void *t)
+{
+  gencomp_chal *gc = (gencomp_chal *)bc;
+  ghash *h = GM_INIT(gc->m);
+
+  GH_HASH(h, m, msz);
+  memcpy(t, GH_DONE(h, 0), bc->tagsz);
+  GH_DESTROY(h);
+  return (0);
+}
+
+static int gencomp_chalvrf(bulkchal *bc, const void *m, size_t msz,
+                          const void *t)
+{
+  gencomp_chal *gc = (gencomp_chal *)bc;
+  ghash *h = GM_INIT(gc->m);
+  int ok;
+
+  GH_HASH(h, m, msz);
+  ok = ct_memeq(GH_DONE(h, 0), t, gc->_b.tagsz);
+  GH_DESTROY(h);
+  return (ok ? 0 : -1);
+}
+
+static void gencomp_freechal(bulkchal *bc)
+  { gencomp_chal *gc = (gencomp_chal *)bc; GM_DESTROY(gc->m); DESTROY(gc); }
+
 /*----- The original transform --------------------------------------------*
  *
  * We generate a random initialization vector (if the cipher needs one).  We
  * ciphertext and extracts the sequence number.
  */
 
-static int v0_check(const algswitch *a, dstr *e)
-  { return (0); }
+typedef struct v0_algs {
+  bulkalgs _b;
+  gencomp_algs ga;
+} v0_algs;
+
+typedef struct v0_ctx {
+  bulkctx _b;
+  size_t tagsz;
+  struct {
+    gcipher *c;
+    gmac *m;
+  } d[NDIR];
+} v0_ctx;
+
+static bulkalgs *v0_getalgs(const algswitch *asw, dstr *e,
+                           key_file *kf, key *k)
+{
+  v0_algs *a = CREATE(v0_algs);
+  if (gencomp_getalgs(&a->ga, asw, e, kf, k)) { DESTROY(a); return (0); }
+  return (&a->_b);
+}
+
+#ifndef NTRACE
+static void v0_tracealgs(const bulkalgs *aa)
+  { const v0_algs *a = (const v0_algs *)aa; gencomp_tracealgs(&a->ga); }
+#endif
+
+static int v0_checkalgs(bulkalgs *aa, const algswitch *asw, dstr *e)
+{
+  v0_algs *a = (v0_algs *)aa;
+  if (gencomp_checkalgs(&a->ga, asw, e)) return (-1);
+  return (0);
+}
+
+static int v0_samealgsp(const bulkalgs *aa, const bulkalgs *bb)
+{
+  const v0_algs *a = (const v0_algs *)aa, *b = (const v0_algs *)bb;
+  return (gencomp_samealgsp(&a->ga, &b->ga));
+}
+
+static void v0_alginfo(const bulkalgs *aa, admin *adm)
+  { const v0_algs *a = (const v0_algs *)aa; gencomp_alginfo(&a->ga, adm); }
+
+static size_t v0_overhead(const bulkalgs *aa)
+{
+  const v0_algs *a = (const v0_algs *)aa;
+  return (a->ga.tagsz + SEQSZ + a->ga.c->blksz);
+}
 
-static size_t v0_overhead(const algswitch *a)
-  { return a->tagsz + SEQSZ + a->c->blksz; }
+static size_t v0_expsz(const bulkalgs *aa)
+  { const v0_algs *a = (const v0_algs *)aa; return (gencomp_expsz(&a->ga)); }
 
-static int v0_encrypt(keyset *ks, unsigned ty, buf *b, buf *bb)
+static bulkctx *v0_genkeys(const bulkalgs *aa, const struct rawkey *rk)
 {
+  const v0_algs *a = (const v0_algs *)aa;
+  v0_ctx *bc = CREATE(v0_ctx);
+  octet k[MAXHASHSZ];
+  int i;
+
+  bc->tagsz = a->ga.tagsz;
+  for (i = 0; i < NDIR; i++) {
+    ks_derivekey(k, a->ga.cksz, rk, i, "encryption");
+    bc->d[i].c = GC_INIT(a->ga.c, k, a->ga.cksz);
+    ks_derivekey(k, a->ga.mksz, rk, i, "integrity");
+    bc->d[i].m = GM_KEY(a->ga.m, k, a->ga.mksz);
+  }
+  return (&bc->_b);
+}
+
+static bulkchal *v0_genchal(const bulkalgs *aa)
+{
+  const v0_algs *a = (const v0_algs *)aa;
+  return (gencomp_genchal(&a->ga));
+}
+#define v0_chaltag gencomp_chaltag
+#define v0_chalvrf gencomp_chalvrf
+#define v0_freechal gencomp_freechal
+
+static void v0_freealgs(bulkalgs *aa)
+  { v0_algs *a = (v0_algs *)aa; DESTROY(a); }
+
+static void v0_freectx(bulkctx *bbc)
+{
+  v0_ctx *bc = (v0_ctx *)bbc;
+  int i;
+
+  for (i = 0; i < NDIR; i++) {
+    GC_DESTROY(bc->d[i].c);
+    GM_DESTROY(bc->d[i].m);
+  }
+  DESTROY(bc);
+}
+
+static int v0_encrypt(bulkctx *bbc, unsigned ty,
+                     buf *b, buf *bb, uint32 seq)
+{
+  v0_ctx *bc = (v0_ctx *)bbc;
   ghash *h;
-  gcipher *c = ks->out.c;
+  gcipher *c = bc->d[DIR_OUT].c;
   const octet *p = BCUR(b);
   size_t sz = BLEFT(b);
   octet *qmac, *qseq, *qiv, *qpk;
-  uint32 oseq;
   size_t ivsz = GC_CLASS(c)->blksz;
-  size_t tagsz = ks->tagsz;
+  size_t tagsz = bc->tagsz;
   octet t[4];
 
   /* --- Determine the ciphertext layout --- */
@@ -123,8 +379,7 @@ static int v0_encrypt(keyset *ks, unsigned ty, buf *b, buf *bb)
 
   /* --- Store the sequence number --- */
 
-  oseq = ks->oseq++;
-  STORE32(qseq, oseq);
+  STORE32(qseq, seq);
 
   /* --- Establish an initialization vector if necessary --- */
 
@@ -142,7 +397,7 @@ static int v0_encrypt(keyset *ks, unsigned ty, buf *b, buf *bb)
   /* --- Compute a MAC over type, sequence number, IV, and ciphertext --- */
 
   if (tagsz) {
-    h = GM_INIT(ks->out.m);
+    h = GM_INIT(bc->d[DIR_OUT].m);
     GH_HASH(h, t, sizeof(t));
     GH_HASH(h, qseq, SEQSZ + ivsz + sz);
     memcpy(qmac, GH_DONE(h, 0), tagsz);
@@ -155,22 +410,24 @@ static int v0_encrypt(keyset *ks, unsigned ty, buf *b, buf *bb)
   return (0);
 }
 
-static int v0_decrypt(keyset *ks, unsigned ty, buf *b, buf *bb, uint32 *seq)
+static int v0_decrypt(bulkctx *bbc, unsigned ty,
+                     buf *b, buf *bb, uint32 *seq)
 {
+  v0_ctx *bc = (v0_ctx *)bbc;
   const octet *pmac, *piv, *pseq, *ppk;
   size_t psz = BLEFT(b);
   size_t sz;
   octet *q = BCUR(bb);
   ghash *h;
-  gcipher *c = ks->in.c;
+  gcipher *c = bc->d[DIR_IN].c;
   size_t ivsz = GC_CLASS(c)->blksz;
-  size_t tagsz = ks->tagsz;
+  size_t tagsz = bc->tagsz;
   octet t[4];
 
   /* --- Break up the packet into its components --- */
 
   if (psz < ivsz + SEQSZ + tagsz) {
-    T( trace(T_KEYSET, "keyset: block too small for keyset %u", ks->seq); )
+    T( trace(T_KEYSET, "keyset: block too small for keyset"); )
     return (KSERR_MALFORMED);
   }
   sz = psz - ivsz - SEQSZ - tagsz;
@@ -180,7 +437,7 @@ static int v0_decrypt(keyset *ks, unsigned ty, buf *b, buf *bb, uint32 *seq)
   /* --- Verify the MAC on the packet --- */
 
   if (tagsz) {
-    h = GM_INIT(ks->in.m);
+    h = GM_INIT(bc->d[DIR_IN].m);
     GH_HASH(h, t, sizeof(t));
     GH_HASH(h, pseq, SEQSZ + ivsz + sz);
     CHECK_MAC(h, pmac, tagsz);
@@ -229,9 +486,74 @@ static int v0_decrypt(keyset *ks, unsigned ty, buf *b, buf *bb, uint32 *seq)
  *                tagsz     32         sz
  */
 
-static int iiv_check(const algswitch *a, dstr *e)
+typedef struct iiv_algs {
+  bulkalgs _b;
+  gencomp_algs ga;
+  const gccipher *b; size_t bksz;
+} iiv_algs;
+
+typedef struct iiv_ctx {
+  bulkctx _b;
+  size_t tagsz;
+  struct {
+    gcipher *c, *b;
+    gmac *m;
+  } d[NDIR];
+} iiv_ctx;
+
+
+static bulkalgs *iiv_getalgs(const algswitch *asw, dstr *e,
+                           key_file *kf, key *k)
+{
+  iiv_algs *a = CREATE(iiv_algs);
+  dstr d = DSTR_INIT, dd = DSTR_INIT;
+  const char *p;
+  char *q;
+
+  if (gencomp_getalgs(&a->ga, asw, e, kf, k)) goto fail;
+
+  if ((p = key_getattr(kf, k, "blkc")) == 0) {
+    dstr_puts(&dd, a->ga.c->name);
+    if ((q = strrchr(dd.buf, '-')) != 0) *q = 0;
+    p = dd.buf;
+  }
+  dstr_putf(&d, "%s-ecb", p);
+  if ((a->b = gcipher_byname(d.buf)) == 0) {
+    a_format(e, "unknown-blkc", "%s", p, A_END);
+    goto fail;
+  }
+
+  dstr_destroy(&d); dstr_destroy(&dd);
+  return (&a->_b);
+fail:
+  dstr_destroy(&d); dstr_destroy(&dd);
+  DESTROY(a);
+  return (0);
+}
+
+#ifndef NTRACE
+static void iiv_tracealgs(const bulkalgs *aa)
+{
+  const iiv_algs *a = (const iiv_algs *)aa;
+
+  gencomp_tracealgs(&a->ga);
+  trace(T_CRYPTO, "crypto: blkc = %.*s", strlen(a->b->name) - 4, a->b->name);
+}
+#endif
+
+static int iiv_checkalgs(bulkalgs *aa, const algswitch *asw, dstr *e)
 {
-  if (a->b->blksz < a->c->blksz) {
+  iiv_algs *a = (iiv_algs *)aa;
+
+  if (gencomp_checkalgs(&a->ga, asw, e)) return (-1);
+
+  if ((a->bksz = keysz(asw->hashsz, a->b->keysz)) == 0) {
+    a_format(e, "blkc", "%.*s", strlen(a->b->name) - 4, a->b->name,
+            "no-key-size", "%lu", (unsigned long)asw->hashsz,
+            A_END);
+    return (-1);
+  }
+  if (a->b->blksz < a->ga.c->blksz) {
     a_format(e, "blkc", "%.*s", strlen(a->b->name) - 4, a->b->name,
             "blksz-insufficient", A_END);
     return (-1);
@@ -239,23 +561,91 @@ static int iiv_check(const algswitch *a, dstr *e)
   return (0);
 }
 
-static size_t iiv_overhead(const algswitch *a)
-  { return a->tagsz + SEQSZ; }
+static int iiv_samealgsp(const bulkalgs *aa, const bulkalgs *bb)
+{
+  const iiv_algs *a = (const iiv_algs *)aa, *b = (const iiv_algs *)bb;
+  return (gencomp_samealgsp(&a->ga, &b->ga) && a->b == b->b);
+}
+
+static void iiv_alginfo(const bulkalgs *aa, admin *adm)
+{
+  const iiv_algs *a = (const iiv_algs *)aa;
+  gencomp_alginfo(&a->ga, adm);
+  a_info(adm,
+        "blkc=%.*s", strlen(a->b->name) - 4, a->b->name,
+        "blkc-keysz=%lu", (unsigned long)a->bksz,
+        "blkc-blksz=%lu", (unsigned long)a->b->blksz,
+        A_END);
+}
+
+static size_t iiv_overhead(const bulkalgs *aa)
+  { const iiv_algs *a = (const iiv_algs *)aa; return (a->ga.tagsz + SEQSZ); }
+
+static size_t iiv_expsz(const bulkalgs *aa)
+{
+  const iiv_algs *a = (const iiv_algs *)aa;
+  return (gencomp_expsz(&a->ga));
+}
+
+static bulkctx *iiv_genkeys(const bulkalgs *aa, const struct rawkey *rk)
+{
+  const iiv_algs *a = (const iiv_algs *)aa;
+  iiv_ctx *bc = CREATE(iiv_ctx);
+  octet k[MAXHASHSZ];
+  int i;
+
+  bc->tagsz = a->ga.tagsz;
+  for (i = 0; i < NDIR; i++) {
+    ks_derivekey(k, a->ga.cksz, rk, i, "encryption");
+    bc->d[i].c = GC_INIT(a->ga.c, k, a->ga.cksz);
+    ks_derivekey(k, a->bksz, rk, i, "blkc");
+    bc->d[i].b = GC_INIT(a->b, k, a->bksz);
+    ks_derivekey(k, a->ga.mksz, rk, i, "integrity");
+    bc->d[i].m = GM_KEY(a->ga.m, k, a->ga.mksz);
+  }
+  return (&bc->_b);
+}
+
+static bulkchal *iiv_genchal(const bulkalgs *aa)
+{
+  const iiv_algs *a = (const iiv_algs *)aa;
+  return (gencomp_genchal(&a->ga));
+}
+#define iiv_chaltag gencomp_chaltag
+#define iiv_chalvrf gencomp_chalvrf
+#define iiv_freechal gencomp_freechal
+
+static void iiv_freealgs(bulkalgs *aa)
+  { iiv_algs *a = (iiv_algs *)aa; DESTROY(a); }
+
+static void iiv_freectx(bulkctx *bbc)
+{
+  iiv_ctx *bc = (iiv_ctx *)bbc;
+  int i;
+
+  for (i = 0; i < NDIR; i++) {
+    GC_DESTROY(bc->d[i].c);
+    GC_DESTROY(bc->d[i].b);
+    GM_DESTROY(bc->d[i].m);
+  }
+  DESTROY(bc);
+}
 
 #define TRACE_PRESEQ(qseq, ivsz) do { IF_TRACING(T_KEYSET, {           \
   trace_block(T_CRYPTO, "crypto: IV derivation input", (qseq), (ivsz));        \
 }) } while (0)
 
-static int iiv_encrypt(keyset *ks, unsigned ty, buf *b, buf *bb)
+static int iiv_encrypt(bulkctx *bbc, unsigned ty,
+                      buf *b, buf *bb, uint32 seq)
 {
+  iiv_ctx *bc = (iiv_ctx *)bbc;
   ghash *h;
-  gcipher *c = ks->out.c, *blkc = ks->out.b;
+  gcipher *c = bc->d[DIR_OUT].c, *blkc = bc->d[DIR_OUT].b;
   const octet *p = BCUR(b);
   size_t sz = BLEFT(b);
   octet *qmac, *qseq, *qpk;
-  uint32 oseq;
   size_t ivsz = GC_CLASS(c)->blksz, blkcsz = GC_CLASS(blkc)->blksz;
-  size_t tagsz = ks->tagsz;
+  size_t tagsz = bc->tagsz;
   octet t[4];
 
   /* --- Determine the ciphertext layout --- */
@@ -273,8 +663,7 @@ static int iiv_encrypt(keyset *ks, unsigned ty, buf *b, buf *bb)
 
   /* --- Store the sequence number --- */
 
-  oseq = ks->oseq++;
-  STORE32(qseq, oseq);
+  STORE32(qseq, seq);
 
   /* --- Establish an initialization vector if necessary --- */
 
@@ -295,7 +684,7 @@ static int iiv_encrypt(keyset *ks, unsigned ty, buf *b, buf *bb)
   /* --- Compute a MAC over type, sequence number, and ciphertext --- */
 
   if (tagsz) {
-    h = GM_INIT(ks->out.m);
+    h = GM_INIT(bc->d[DIR_OUT].m);
     GH_HASH(h, t, sizeof(t));
     GH_HASH(h, qseq, SEQSZ + sz);
     memcpy(qmac, GH_DONE(h, 0), tagsz);
@@ -308,22 +697,24 @@ static int iiv_encrypt(keyset *ks, unsigned ty, buf *b, buf *bb)
   return (0);
 }
 
-static int iiv_decrypt(keyset *ks, unsigned ty, buf *b, buf *bb, uint32 *seq)
+static int iiv_decrypt(bulkctx *bbc, unsigned ty,
+                      buf *b, buf *bb, uint32 *seq)
 {
+  iiv_ctx *bc = (iiv_ctx *)bbc;
   const octet *pmac, *pseq, *ppk;
   size_t psz = BLEFT(b);
   size_t sz;
   octet *q = BCUR(bb);
   ghash *h;
-  gcipher *c = ks->in.c, *blkc = ks->in.b;
+  gcipher *c = bc->d[DIR_IN].c, *blkc = bc->d[DIR_IN].b;
   size_t ivsz = GC_CLASS(c)->blksz, blkcsz = GC_CLASS(blkc)->blksz;
-  size_t tagsz = ks->tagsz;
+  size_t tagsz = bc->tagsz;
   octet t[4];
 
   /* --- Break up the packet into its components --- */
 
   if (psz < SEQSZ + tagsz) {
-    T( trace(T_KEYSET, "keyset: block too small for keyset %u", ks->seq); )
+    T( trace(T_KEYSET, "keyset: block too small for keyset"); )
     return (KSERR_MALFORMED);
   }
   sz = psz - SEQSZ - tagsz;
@@ -333,7 +724,7 @@ static int iiv_decrypt(keyset *ks, unsigned ty, buf *b, buf *bb, uint32 *seq)
   /* --- Verify the MAC on the packet --- */
 
   if (tagsz) {
-    h = GM_INIT(ks->in.m);
+    h = GM_INIT(bc->d[DIR_IN].m);
     GH_HASH(h, t, sizeof(t));
     GH_HASH(h, pseq, SEQSZ + sz);
     CHECK_MAC(h, pmac, tagsz);
@@ -362,11 +753,18 @@ static int iiv_decrypt(keyset *ks, unsigned ty, buf *b, buf *bb, uint32 *seq)
 
 const bulkops bulktab[] = {
 
-#define BULK(name, pre, prim)                                          \
-  { name, prim, pre##_check, pre##_overhead, pre##_encrypt, pre##_decrypt }
+#define COMMA ,
+
+#define BULK(name, pre)                                                        \
+  { name, pre##_getalgs, T( pre##_tracealgs COMMA )                    \
+    pre##_checkalgs, pre##_samealgsp,                                  \
+    pre##_alginfo, pre##_overhead, pre##_expsz,                                \
+    pre##_genkeys, pre##_genchal, pre##_freealgs,                      \
+    pre##_encrypt, pre##_decrypt, pre##_freectx,                       \
+    pre##_chaltag, pre##_chalvrf, pre##_freechal }
 
-  BULK("v0", v0, BCP_CIPHER | BCP_MAC),
-  BULK("iiv", iiv, BCP_CIPHER | BCP_MAC | BCP_BLKC),
+  BULK("v0", v0),
+  BULK("iiv", iiv),
 
 #undef BULK
   { 0 }
index 387f0db..71fde49 100644 (file)
@@ -30,7 +30,7 @@
 
 /*----- Static variables --------------------------------------------------*/
 
-static gmac *mac;
+static bulkchal *bulk;
 static uint32 oseq;
 static seqwin iseq;
 
@@ -47,17 +47,13 @@ static seqwin iseq;
 
 static void c_genkey(void)
 {
-  if (mac && GM_CLASS(mac) == master->algs.m && oseq < 0x07ffffff) return;
-  if (mac) GM_DESTROY(mac);
-  assert(master->algs.mksz < sizeof(buf_t));
-  rand_get(RAND_GLOBAL, buf_t, master->algs.mksz);
-  mac = GM_KEY(master->algs.m, buf_t, master->algs.mksz);
+  if (bulk && bulk->ops == master->algs.bulk->ops && oseq < 0x07ffffff)
+    return;
+  if (bulk) bulk->ops->freechal(bulk);
+  bulk = master->algs.bulk->ops->genchal(master->algs.bulk);
+  bulk->ops = master->algs.bulk->ops;
   oseq = 0;
   seq_reset(&iseq);
-  IF_TRACING(T_CHAL, {
-    trace(T_CHAL, "chal: generated new challenge key");
-    trace_block(T_CRYPTO, "chal: new key", buf_t, master->algs.mksz);
-  })
 }
 
 /* --- @c_new@ --- *
@@ -72,16 +68,11 @@ static void c_genkey(void)
 int c_new(buf *b)
 {
   octet *p;
-  ghash *h;
 
   c_genkey();
   p = BCUR(b);
-  if (buf_putu32(b, oseq++)) return (-1);
-  h = GM_INIT(mac);
-  GH_HASH(h, p, BCUR(b) - p);
-  buf_put(b, GH_DONE(h, 0), master->algs.tagsz);
-  GH_DESTROY(h);
-  if (BBAD(b)) return (-1);
+  if (buf_putu32(b, oseq++) || !buf_get(b, bulk->tagsz)) return (-1);
+  if (bulk->ops->chaltag(bulk, p, 4, p + 4)) return (-1);
   IF_TRACING(T_CHAL, {
     trace(T_CHAL, "chal: issuing challenge %lu", (unsigned long)(oseq - 1));
     trace_block(T_CRYPTO, "chal: challenge block", p, BCUR(b) - p);
@@ -101,25 +92,20 @@ int c_new(buf *b)
 int c_check(buf *b)
 {
   const octet *p;
-  size_t sz = 4 + master->algs.tagsz;
+  size_t sz;
   uint32 seq;
-  ghash *h;
-  int ok;
 
+  if (!bulk) {
+    a_warn("CHAL", "impossible-challenge", A_END);
+    goto fail;
+  }
+  sz = 4 + bulk->tagsz;
   if ((p = buf_get(b, sz)) == 0) {
     a_warn("CHAL", "invalid-challenge", A_END);
     goto fail;
   }
   IF_TRACING(T_CHAL, trace_block(T_CRYPTO, "chal: check challenge", p, sz); )
-  if (!mac) {
-    a_warn("CHAL", "impossible-challenge", A_END);
-    goto fail;
-  }
-  h = GM_INIT(mac);
-  GH_HASH(h, p, 4);
-  ok = ct_memeq(GH_DONE(h, 0), p + 4, master->algs.tagsz);
-  GH_DESTROY(h);
-  if (!ok) {
+  if (bulk->ops->chalvrf(bulk, p, 4, p + 4)) {
     a_warn("CHAL", "incorrect-tag", A_END);
     goto fail;
   }
index 837bc6e..2324f15 100644 (file)
@@ -178,8 +178,7 @@ static const kgops *kgtab[] = {
 static int algs_get(algswitch *a, dstr *e, key_file *kf, key *k)
 {
   const char *p;
-  const bulkops *bulk;
-  char *q, *qq;
+  const bulkops *bops;
   dstr d = DSTR_INIT, dd = DSTR_INIT;
   int rc = -1;
 
@@ -206,83 +205,13 @@ static int algs_get(algswitch *a, dstr *e, key_file *kf, key *k)
   /* --- Bulk crypto transform --- */
 
   if ((p = key_getattr(kf, k, "bulk")) == 0) p = "v0";
-  for (bulk = bulktab; bulk->name && strcmp(p, bulk->name) != 0; bulk++);
-  if (!bulk->name) {
+  for (bops = bulktab; bops->name && strcmp(p, bops->name) != 0; bops++);
+  if (!bops->name) {
     a_format(e, "unknown-bulk-transform", "%s", p, A_END);
     goto done;
   }
-  a->bulk = bulk;
-
-  /* --- Symmetric encryption for bulk data --- */
-
-  if (!(a->bulk->prim & BCP_CIPHER))
-    a->c = 0;
-  else {
-    if ((p = key_getattr(kf, k, "cipher")) == 0) p = "blowfish-cbc";
-    if ((a->c = gcipher_byname(p)) == 0) {
-      a_format(e, "unknown-cipher", "%s", p, A_END);
-      goto done;
-    }
-  }
-
-  /* --- Block cipher for miscellaneous use --- */
-
-  if (!(a->bulk->prim & BCP_BLKC))
-    a->b = 0;
-  else {
-    if ((p = key_getattr(kf, k, "blkc")) == 0) {
-      dstr_reset(&dd);
-      dstr_puts(&dd, a->c ? a->c->name : "rijndael-");
-      if ((q = strrchr(dd.buf, '-')) != 0) *q = 0;
-      p = dd.buf;
-    }
-    dstr_reset(&d);
-    dstr_putf(&d, "%s-ecb", p);
-    if ((a->b = gcipher_byname(d.buf)) == 0) {
-      a_format(e, "unknown-blkc", "%s", p, A_END);
-      goto done;
-    }
-  }
-
-  /* --- Message authentication for bulk data --- */
-
-  if (!(a->bulk->prim & BCP_MAC)) {
-    a->m = 0;
-    a->tagsz = 0;
-  } else {
-    if ((p = key_getattr(kf, k, "mac")) != 0) {
-      dstr_reset(&d);
-      dstr_puts(&d, p);
-      if ((q = strchr(d.buf, '/')) != 0)
-       *q++ = 0;
-      if ((a->m = gmac_byname(d.buf)) == 0) {
-       a_format(e, "unknown-mac", "%s", d.buf, A_END);
-       goto done;
-      }
-      if (!q)
-       a->tagsz = a->m->hashsz;
-      else {
-       unsigned long n = strtoul(q, &qq, 0);
-       if (*qq)  {
-         a_format(e, "bad-tag-length-string", "%s", q, A_END);
-         goto done;
-       }
-       if (n%8 || n/8 > a->m->hashsz) {
-         a_format(e, "bad-tag-length", "%lu", n, A_END);
-         goto done;
-       }
-       a->tagsz = n/8;
-      }
-    } else {
-      dstr_reset(&d);
-      dstr_putf(&d, "%s-hmac", a->h->name);
-      if ((a->m = gmac_byname(d.buf)) == 0) {
-       a_format(e, "no-hmac-for-hash", "%s", a->h->name, A_END);
-       goto done;
-      }
-      a->tagsz = a->h->hashsz/2;
-    }
-  }
+  if ((a->bulk = bops->getalgs(a, e, kf, k)) == 0) goto done;
+  a->bulk->ops = bops;
 
   /* --- All done --- */
 
@@ -309,43 +238,7 @@ done:
 
 static int algs_check(algswitch *a, dstr *e, const group *g)
 {
-  /* --- Check the bulk crypto transform --- */
-
-  if (a->bulk->check(a, e)) return (-1);
-
-  /* --- Derive the key sizes --- *
-   *
-   * Must ensure that we have non-empty keys.  This isn't ideal, but it
-   * provides a handy sanity check.  Also must be based on a 64- or 128-bit
-   * block cipher or we can't do the data expiry properly.
-   */
-
   a->hashsz = a->h->hashsz;
-  if (a->c && (a->cksz = keysz(a->hashsz, a->c->keysz)) == 0) {
-    a_format(e, "cipher", "%s", a->c->name,
-            "no-key-size", "%lu", (unsigned long)a->hashsz,
-            A_END);
-    return (-1);
-  }
-  if (a->m && (a->mksz = keysz(a->hashsz, a->m->keysz)) == 0) {
-    a_format(e, "mac", "%s", a->m->name,
-            "no-key-size", "%lu", (unsigned long)a->hashsz,
-            A_END);
-    return (-1);
-  }
-  if (a->b && (a->bksz = keysz(a->hashsz, a->b->keysz)) == 0) {
-    a_format(e, "blkc", "%.*s", strlen(a->b->name) - 4, a->b->name,
-            "no-key-size", "%lu", (unsigned long)a->hashsz,
-            A_END);
-    return (-1);
-  }
-
-  /* --- Derive the data limit --- */
-
-  if (a->c && a->c->blksz < 16) a->expsz = MEG(64);
-  else a->expsz = MEG(2048);
-
-  /* --- Ensure the MGF accepts hashes as keys --- */
 
   if (keysz(a->hashsz, a->mgf->keysz) != a->hashsz) {
     a_format(e, "mgf", "%s", a->mgf->name,
@@ -354,7 +247,7 @@ static int algs_check(algswitch *a, dstr *e, const group *g)
     return (-1);
   }
 
-  /* --- All ship-shape and Bristol-fashion --- */
+  if (a->bulk->ops->checkalgs(a->bulk, a, e)) return (-1);
 
   return (0);
 }
@@ -374,10 +267,9 @@ int km_samealgsp(const kdata *kdx, const kdata *kdy)
   const algswitch *a = &kdx->algs, *aa = &kdy->algs;
 
   return (group_samep(kdx->g, kdy->g) &&
-         a->bulk == aa->bulk &&
-         a->c == aa->c && a->b == aa->b &&
          a->mgf == aa->mgf && a->h == aa->h &&
-         a->m == aa->m && a->tagsz == aa->tagsz);
+         a->bulk->ops == aa->bulk->ops &&
+         a->bulk->ops->samealgsp(a->bulk, aa->bulk));
 }
 
 /*----- Key data and key nodes --------------------------------------------*/
@@ -624,11 +516,7 @@ foundko:
       trace(T_CRYPTO, "crypto: h = %s", mpstr(kd->g->h));
       if (kd->kpriv)
        trace(T_CRYPTO, "crypto: x = %s", mpstr(kd->kpriv));
-      trace(T_CRYPTO, "crypto: cipher = %s", kd->algs.c->name);
-      trace(T_CRYPTO, "crypto: mgf = %s", kd->algs.mgf->name);
-      trace(T_CRYPTO, "crypto: hash = %s", kd->algs.h->name);
-      trace(T_CRYPTO, "crypto: mac = %s/%lu",
-           kd->algs.m->name, (unsigned long)kd->algs.tagsz * 8);
+      kd->algs.bulk->ops->tracealgs(kd->algs.bulk);
     })
   })
 
index 94fcae0..1d7817c 100644 (file)
@@ -90,8 +90,9 @@ static int doencrypt(keyset *ks, unsigned ty, buf *b, buf *bb)
 
   /* --- Apply the bulk-crypto transformation --- */
 
-  rc = ks->bulk->encrypt(ks, ty, b, bb);
+  rc = ks->bulk->ops->encrypt(ks->bulk, ty, b, bb, ks->oseq);
   if (rc || !BOK(bb)) return (rc);
+  ks->oseq++;
 
   /* --- Do the necessary accounting for data volume --- */
 
@@ -139,7 +140,7 @@ static int dodecrypt(keyset *ks, unsigned ty, buf *b, buf *bb, uint32 *seq)
     trace_block(T_CRYPTO, "crypto: ciphertext packet", BCUR(b), BLEFT(b));
   })
 
-  rc = ks->bulk->decrypt(ks, ty, b, bb, seq);
+  rc = ks->bulk->ops->decrypt(ks->bulk, ty, b, bb, seq);
   if (rc) return (rc);
 
   IF_TRACING(T_KEYSET, {
@@ -164,22 +165,64 @@ static int dodecrypt(keyset *ks, unsigned ty, buf *b, buf *bb, uint32 *seq)
 
 void ks_drop(keyset *ks)
 {
-  if (--ks->ref)
-    return;
-
-#define DROP(dir, a, drop) do { if (ks->dir.a) drop(ks->dir.a); } while (0)
-#define DROP_DIR(dir) do {                                             \
-  DROP(dir, c, GC_DESTROY);                                            \
-  DROP(dir, m, GM_DESTROY);                                            \
-} while (0)
+  if (--ks->ref) return;
+  ks->bulk->ops->freectx(ks->bulk);
+  DESTROY(ks);
+}
 
-  DROP_DIR(in);
-  DROP_DIR(out);
+/* --- @ks_derivekey@ --- *
+ *
+ * Arguments:  @octet *k@ = pointer to an output buffer of at least
+ *                     @MAXHASHSZ@ bytes
+ *             @size_t ksz@ = actual size wanted (for tracing)
+ *             @const struct rawkey *rk@ = a raw key, as passed into
+ *                     @genkeys@
+ *             @int dir@ = direction for the key (@DIR_IN@ or @DIR_OUT@)
+ *             @const char *what@ = label for the key (input to derivation)
+ *
+ * Returns:    ---
+ *
+ * Use:                Derives a session key, for use on incoming or outgoing data.
+ *             This function is part of a private protocol between @ks_gen@
+ *             and the bulk crypto transform @genkeys@ operation.
+ */
 
-#undef DROP
-#undef DROP_DIR
+struct rawkey {
+  const gchash *hc;
+  const octet *k;
+  size_t x, y, z;
+};
 
-  DESTROY(ks);
+void ks_derivekey(octet *k, size_t ksz, const struct rawkey *rk,
+                 int dir, const char *what)
+{
+  const gchash *hc = rk->hc;
+  ghash *h;
+
+  assert(ksz <= hc->hashsz);
+  assert(hc->hashsz <= MAXHASHSZ);
+  h = GH_INIT(hc);
+  GH_HASH(h, "tripe-", 6); GH_HASH(h, what, strlen(what) + 1);
+  switch (dir) {
+    case DIR_IN:
+      GH_HASH(h, rk->k, rk->x);
+      GH_HASH(h, rk->k + rk->x, rk->y - rk->x);
+      break;
+    case DIR_OUT:
+      GH_HASH(h, rk->k + rk->x, rk->y - rk->x);
+      GH_HASH(h, rk->k, rk->x);
+      break;
+    default:
+      abort();
+  }
+  GH_HASH(h, rk->k + rk->y, rk->z - rk->y);
+  GH_DONE(h, k);
+  GH_DESTROY(h);
+  IF_TRACING(T_KEYSET, { IF_TRACING(T_CRYPTO, {
+    char _buf[32];
+    sprintf(_buf, "crypto: %s key %s", dir ? "incoming" : "outgoing", what);
+    trace_block(T_CRYPTO, _buf, k, ksz);
+  }) })
 }
 
 /* --- @ks_gen@ --- *
@@ -205,67 +248,30 @@ void ks_drop(keyset *ks)
  *             calling @ks_encrypt@ directly.
  */
 
-static void gen_dir(const algswitch *algs, struct ksdir *ksd,
-                   const char *whichdir,
-                   const octet *from, size_t fromsz,
-                   const octet *to, size_t tosz,
-                   const octet *both, size_t bothsz)
-{
-#define SETKEY(what, a, init) do {                                     \
-  ghash *_h;                                                           \
-  octet *_hh;                                                          \
-                                                                       \
-  if (!algs->a)                                                                \
-    ksd->a = 0;                                                                \
-  else {                                                               \
-    _h = GH_INIT(algs->h);                                             \
-    HASH_STRING(_h, "tripe-" what);                                    \
-    GH_HASH(_h, from, fromsz);                                         \
-    GH_HASH(_h, to, tosz);                                             \
-    GH_HASH(_h, both, bothsz);                                         \
-    _hh = GH_DONE(_h, 0);                                              \
-    IF_TRACING(T_KEYSET, { IF_TRACING(T_CRYPTO, {                      \
-      char _buf[32];                                                   \
-      sprintf(_buf, "crypto: %s key " what, whichdir);                 \
-      trace_block(T_CRYPTO, _buf, _hh, algs->a##ksz);                  \
-    }) })                                                              \
-    ksd->a = init(algs->a, _hh, algs->a##ksz);                         \
-    GH_DESTROY(_h);                                                    \
-  }                                                                    \
-} while (0)
-
-  SETKEY("encryption", c, GC_INIT);
-  SETKEY("integrity", m, GM_KEY);
-  SETKEY("blkc", b, GC_INIT);
-
-#undef SETKEY
-}
-
 keyset *ks_gen(const void *k, size_t x, size_t y, size_t z, peer *p)
 {
   keyset *ks = CREATE(keyset);
   time_t now = time(0);
-  const octet *pp = k;
   const algswitch *algs = &p->kx.kpriv->algs;
+  struct rawkey rk;
   T( static unsigned seq = 0; )
 
   T( trace(T_KEYSET, "keyset: adding new keyset %u", seq); )
 
-  gen_dir(algs, &ks->in, "incoming", pp, x, pp + x, y - x, pp + y, z - y);
-  gen_dir(algs, &ks->out, "outgoing", pp + x, y - x, pp, x, pp + y, z - y);
+  rk.hc = algs->h; rk.k = k; rk.x = x; rk.y = y; rk.z = z;
+  ks->bulk = algs->bulk->ops->genkeys(algs->bulk, &rk);
+  ks->bulk->ops = algs->bulk->ops;
 
   T( ks->seq = seq++; )
-  ks->bulk = algs->bulk;
   ks->ref = 1;
   ks->t_exp = now + T_EXP;
-  ks->sz_exp = algs->expsz;
-  ks->sz_regen = algs->expsz/2;
+  ks->sz_exp = algs->bulk->ops->expsz(algs->bulk);
+  ks->sz_regen = ks->sz_exp/2;
   ks->oseq = 0;
   seq_reset(&ks->iseq);
   ks->next = 0;
   ks->p = p;
   ks->f = KSF_LISTEN;
-  ks->tagsz = algs->tagsz;
   return (ks);
 }
 
index 4311a43..6c5d5e3 100644 (file)
@@ -590,8 +590,8 @@ kx-group=ec kx-group-order-bits=256 kx-group-elt-bits=512
 hash=rmd160 mgf=rmd160-mgf hash-sz=20
 bulk-transform=v0 bulk-overhead=22
 cipher=blowfish-cbc cipher-keysz=20 cipher-blksz=8
-cipher-data-limit=67108864
 mac=rmd160-hmac mac-keysz=20 mac-tagsz=10
+cipher-data-limit=67108864
 ])
 
   AT_DATA([algs-beta-old], [dnl
@@ -599,8 +599,8 @@ kx-group=prime kx-group-order-bits=160 kx-group-elt-bits=1023
 hash=rmd160 mgf=rmd160-mgf hash-sz=20
 bulk-transform=v0 bulk-overhead=22
 cipher=blowfish-cbc cipher-keysz=20 cipher-blksz=8
-cipher-data-limit=67108864
 mac=rmd160-hmac mac-keysz=20 mac-tagsz=10
+cipher-data-limit=67108864
 ])
 
   AT_DATA([algs-beta-new], [dnl
@@ -608,9 +608,9 @@ kx-group=ec kx-group-order-bits=161 kx-group-elt-bits=320
 hash=rmd160 mgf=rmd160-mgf hash-sz=20
 bulk-transform=iiv bulk-overhead=14
 cipher=blowfish-cbc cipher-keysz=20 cipher-blksz=8
-cipher-data-limit=67108864
 mac=rmd160-hmac mac-keysz=20 mac-tagsz=10
 blkc=blowfish blkc-keysz=20 blkc-blksz=8
+cipher-data-limit=67108864
 ])
 
   cp algs-alpha expout;    AT_CHECK([TRIPECTL -dalice ALGS],,       [expout])
index 4669711..24cec43 100644 (file)
 
 typedef struct keyset keyset;
 typedef struct algswitch algswitch;
+typedef struct admin admin;
+
+typedef struct bulkalgs {
+  const struct bulkops *ops;
+} bulkalgs;
+
+typedef struct bulkctx {
+  const struct bulkops *ops;
+} bulkctx;
+
+typedef struct bulkchal {
+  const struct bulkops *ops;
+  size_t tagsz;
+} bulkchal;
+
+struct rawkey;
 
 typedef struct bulkops {
   const char *name;
-  unsigned prim;
-  int (*check)(const algswitch */*a*/, dstr */*e*/);
-  size_t (*overhead)(const algswitch */*a*/);
-  int (*encrypt)(keyset */*ks*/, unsigned /*ty*/, buf */*b*/, buf */*bb*/);
-  int (*decrypt)(keyset */*ks*/, unsigned /*ty*/,
+
+  bulkalgs *(*getalgs)(const algswitch */*asw*/, dstr */*e*/,
+                      key_file */*kf*/, key */*k*/);
+       /* Determine algorithms to use and return a @bulkalgs@ object
+        * representing the decision.  On error, write tokens to @e@ and
+        * return null.
+        */
+
+  T( void (*tracealgs)(const bulkalgs */*a*/); )
+       /* Write trace information about the algorithm selection. */
+
+  int (*checkalgs)(bulkalgs */*a*/, const algswitch */*asw*/, dstr */*e*/);
+       /* Check that the algorithms in @a@ and @asw@ are acceptable.  On
+        * error, write tokens to @e@ and return @-1@; otherwise return zero.
+        */
+
+  int (*samealgsp)(const bulkalgs */*a*/, const bulkalgs */*aa*/);
+       /* If @a@ and @aa@ represent the same algorithm selection, return
+        * nonzero; if not, return zero.
+        */
+
+  void (*alginfo)(const bulkalgs */*a*/, admin */*adm*/);
+       /* Report on the algorithm selection to an admin client: call
+        * @a_info@ with appropriate key-value pairs.
+        */
+
+  size_t (*overhead)(const bulkalgs */*a*/);
+       /* Return the per-packet overhead of the bulk transform, in bytes. */
+
+  size_t (*expsz)(const bulkalgs */*a*/);
+       /* Return the total size limit for the bulk transform, in bytes,
+        * after which the keys must no longer be used.
+        */
+
+  bulkctx *(*genkeys)(const bulkalgs */*a*/, const struct rawkey */*rk*/);
+       /* Generate session keys and construct and return an appropriate
+        * context for using them, by calling @ks_derive@.
+        */
+
+  bulkchal *(*genchal)(const bulkalgs */*a*/);
+       /* Construct and return a challenge issuing and verification
+        * context with a fresh random key.
+        */
+
+  void (*freealgs)(bulkalgs */*a*/);
+       /* Release an algorithm selection object.  (Associated bulk
+        * encryption contexts and challenge contexts may still exist and
+        * must remain valid.)
+        */
+
+  int (*encrypt)(bulkctx */*bc*/, unsigned /*ty*/,
+                buf */*b*/, buf */*bb*/, uint32 /*seq*/);
+       /* Encrypt the packet in @b@, with type @ty@ (which doesn't need
+        * encoding separately) and sequence number @seq@ (which must be
+        * recoverable by @decrypt@), and write the result to @bb@.  On
+        * error, return a @KSERR_...@ code and/or break the output buffer.
+        */
+
+  int (*decrypt)(bulkctx */*bc*/, unsigned /*ty*/,
                 buf */*b*/, buf */*bb*/, uint32 */*seq*/);
-} bulkops;
+       /* Decrypt the packet in @b@, with type @ty@, writing the result to
+        * @bb@ and storing the incoming (claimed) sequence number in @seq@.
+        * On error, return a @KSERR_...@ code.
+        */
+
+  void (*freectx)(bulkctx */*a*/);
+       /* Release a bulk encryption context and the resources it holds. */
+
+  int (*chaltag)(bulkchal */*bc*/, const void */*m*/, size_t /*msz*/,
+                void */*t*/);
+       /* Calculate a tag for the challenge in @m@, @msz@, and write it to
+        * @t@.  Return @-1@ on error, zero on success.
+        */
+
+  int (*chalvrf)(bulkchal */*bc*/, const void */*m*/, size_t /*msz*/,
+                const void */*t*/);
+       /* Check the tag @t@ on @m@, @msz@: return zero if the tag is OK,
+        * nonzero if it's bad.
+        */
+
+  void (*freechal)(bulkchal */*bc*/);
+       /* Release a challenge context and the resources it holds. */
 
-#define BCP_CIPHER 1
-#define BCP_MAC 2
-#define BCP_BLKC 4
+} bulkops;
 
 struct algswitch {
-  const gchash *h;                     /* Hash function */
+  const gchash *h; size_t hashsz;      /* Hash function */
   const gccipher *mgf;                 /* Mask-generation function */
-  const bulkops *bulk;                 /* Bulk crypto transformation */
-  const gccipher *c;                   /* Symmetric encryption scheme */
-  const gcmac *m;                      /* Message authentication code */
-  const gccipher *b;                   /* Block cipher */
-  size_t hashsz;                       /* Hash output size */
-  size_t tagsz;                                /* Length to truncate MAC tags */
-  size_t expsz;                                /* Size of data to process */
-  size_t cksz, mksz, bksz;             /* Key lengths for things */
+  bulkalgs *bulk;                      /* Bulk crypto algorithms */
 };
 
 typedef struct kdata {
@@ -258,6 +340,8 @@ typedef struct seqwin {
  * expiry.
  */
 
+enum { DIR_IN, DIR_OUT, NDIR };
+
 struct keyset {
   struct keyset *next;                 /* Next active keyset in the list */
   unsigned ref;                                /* Reference count for keyset */
@@ -266,13 +350,7 @@ struct keyset {
   unsigned long sz_exp, sz_regen;      /* Data limits for the keyset */
   T( unsigned seq; )                   /* Sequence number for tracing */
   unsigned f;                          /* Various useful flags */
-  const bulkops *bulk;                 /* Bulk crypto transform */
-  size_t tagsz;                                /* Length to truncate MAC tags */
-  struct ksdir {
-    gcipher *c;                                /* Keyset cipher for encryption */
-    gmac *m;                           /* Keyset MAC for integrity */
-    gcipher *b;                                /* Block cipher, just in case */
-  } in, out;
+  bulkctx *bulk;                       /* Bulk crypto transform */
   uint32 oseq;                         /* Outbound sequence number */
   seqwin iseq;                         /* Inbound sequence number */
 };
@@ -529,7 +607,7 @@ typedef struct admin_jobtable {
   admin_jobentry *v;                   /* And the big array of entries */
 } admin_jobtable;
 
-typedef struct admin {
+struct admin {
   struct admin *next, *prev;           /* Links to next and previous */
   unsigned f;                          /* Various useful flags */
   unsigned ref;                                /* Reference counter */
@@ -543,7 +621,7 @@ typedef struct admin {
   admin_jobtable j;                    /* Table of outstanding jobs */
   selbuf b;                            /* Line buffer for commands */
   sel_file w;                          /* Selector for write buffering */
-} admin;
+};
 
 #define AF_DEAD 1u                     /* Destroy this admin block */
 #define AF_CLOSE 2u                    /* Client closed connection */
@@ -748,6 +826,27 @@ extern int kx_init(keyexch */*kx*/, peer */*p*/,
 
 extern void ks_drop(keyset */*ks*/);
 
+/* --- @ks_derivekey@ --- *
+ *
+ * Arguments:  @octet *k@ = pointer to an output buffer of at least
+ *                     @MAXHASHSZ@ bytes
+ *             @size_t ksz@ = actual size wanted (for tracing)
+ *             @const struct rawkey *rk@ = a raw key, as passed into
+ *                     @genkeys@
+ *             @int dir@ = direction for the key (@DIR_IN@ or @DIR_OUT@)
+ *             @const char *what@ = label for the key (input to derivation)
+ *
+ * Returns:    ---
+ *
+ * Use:                Derives a session key, for use on incoming or outgoing data.
+ *             This function is part of a private protocol between @ks_gen@
+ *             and the bulk crypto transform @genkeys@ operation.
+ */
+
+extern void ks_derivekey(octet */*k*/, size_t /*ksz*/,
+                        const struct rawkey */*rk*/,
+                        int /*dir*/, const char */*what*/);
+
 /* --- @ks_gen@ --- *
  *
  * Arguments:  @const void *k@ = pointer to key material