chiark / gitweb /
server/: New `implicit-IV' transform (`iiv').
authorMark Wooding <mdw@distorted.org.uk>
Sat, 24 May 2014 13:00:03 +0000 (14:00 +0100)
committerMark Wooding <mdw@distorted.org.uk>
Sun, 20 Jul 2014 00:42:30 +0000 (01:42 +0100)
The new transform has two advantages over the existing `v0' transform:

  * it doesn't need to transmit an explicit IV, so it adds less overhead
    to messages being sent; and

  * it's entirely deterministic, getting variation from the input
    sequence number rather than randomness, which (a) improves
    performance a bit by not exercising the cryptographic random number
    generator, and (b) eliminates a kleptographic channel.

This change triggers a bug in mLib 2.2.1 and earlier: `dstr_putf'
doesn't handle `%.*s' correctly.

server/admin.c
server/bulkcrypto.c
server/keymgmt.c
server/keyset.c
server/tests.at
server/tripe-admin.5.in
server/tripe.8.in
server/tripe.h
t/keyring-beta-new

index f3091be65627d8558f92e4072b4af2517e25dd90..25581f656479b7f9cc680c1d7782e559502a645d 100644 (file)
@@ -1751,6 +1751,13 @@ static void acmd_algs(admin *a, unsigned ac, char *av[])
           "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 faaf8d71b7889536ba77cf57a8051f84a3f2e0fc..144a8301068e9f2811249bb09f5368ab88edf4bd 100644 (file)
@@ -199,6 +199,163 @@ static int v0_decrypt(keyset *ks, unsigned ty, buf *b, buf *bb, uint32 *seq)
   return (0);
 }
 
+/*----- The implicit-IV transform -----------------------------------------*
+ *
+ * The v0 transform makes everything explicit.  There's an IV because the
+ * cipher needs an IV; there's a sequence number because replay prevention
+ * needs a sequence number.
+ *
+ * This new transform works rather differently.  We make use of a block
+ * cipher to encrypt the sequence number, and use that as the IV.  We
+ * transmit the sequence number in the clear, as before.  This reduces
+ * overhead; and it's not a significant privacy leak because the adversary
+ * can see the order in which the messages are transmitted -- i.e., the
+ * sequence numbers are almost completely predictable anyway.
+ *
+ * So, a MAC is computed over
+ *
+ *             +------+ +------+------...------+
+ *             | type | | seq  |   ciphertext  |
+ *             +------+ +------+------...------+
+ *                32       32         sz
+ *
+ * and we actually transmit the following as the cryptogram.
+ *
+ *             +---...---+------+------...------+
+ *             |   tag   | seq  |   ciphertext  |
+ *             +---...---+------+------...------+
+ *                tagsz     32         sz
+ */
+
+static int iiv_check(const algswitch *a, dstr *e)
+{
+  if (a->b->blksz < a->c->blksz) {
+    a_format(e, "blkc", "%.*s", strlen(a->b->name) - 4, a->b->name,
+            "blksz-insufficient", A_END);
+    return (-1);
+  }
+  return (0);
+}
+
+static size_t iiv_overhead(const algswitch *a)
+  { return a->tagsz + SEQSZ; }
+
+#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)
+{
+  ghash *h;
+  gcipher *c = ks->out.c, *blkc = ks->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;
+  octet t[4];
+
+  /* --- Determine the ciphertext layout --- */
+
+  if (buf_ensure(bb, tagsz + SEQSZ + sz)) return (0);
+  qmac = BCUR(bb); qseq = qmac + tagsz; qpk = qseq + SEQSZ;
+  BSTEP(bb, tagsz + SEQSZ + sz);
+
+  /* --- Store the type --- *
+   *
+   * This isn't transmitted, but it's covered by the MAC.
+   */
+
+  STORE32(t, ty);
+
+  /* --- Store the sequence number --- */
+
+  oseq = ks->oseq++;
+  STORE32(qseq, oseq);
+
+  /* --- Establish an initialization vector if necessary --- */
+
+  if (ivsz) {
+    memset(buf_u, 0, blkcsz - SEQSZ);
+    memcpy(buf_u + blkcsz - SEQSZ, qseq, SEQSZ);
+    TRACE_PRESEQ(buf_u, ivsz);
+    GC_ENCRYPT(blkc, buf_u, buf_u, blkcsz);
+    GC_SETIV(c, buf_u);
+    TRACE_IV(buf_u, ivsz);
+  }
+
+  /* --- Encrypt the packet --- */
+
+  GC_ENCRYPT(c, p, qpk, sz);
+  TRACE_CT(qpk, sz);
+
+  /* --- Compute a MAC over type, sequence number, and ciphertext --- */
+
+  if (tagsz) {
+    h = GM_INIT(ks->out.m);
+    GH_HASH(h, t, sizeof(t));
+    GH_HASH(h, qseq, SEQSZ + sz);
+    memcpy(qmac, GH_DONE(h, 0), tagsz);
+    GH_DESTROY(h);
+    TRACE_MAC(qmac, tagsz);
+  }
+
+  /* --- We're done --- */
+
+  return (0);
+}
+
+static int iiv_decrypt(keyset *ks, unsigned ty, buf *b, buf *bb, uint32 *seq)
+{
+  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;
+  size_t ivsz = GC_CLASS(c)->blksz, blkcsz = GC_CLASS(blkc)->blksz;
+  size_t tagsz = ks->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); )
+    return (KSERR_MALFORMED);
+  }
+  sz = psz - SEQSZ - tagsz;
+  pmac = BCUR(b); pseq = pmac + tagsz; ppk = pseq + SEQSZ;
+  STORE32(t, ty);
+
+  /* --- Verify the MAC on the packet --- */
+
+  if (tagsz) {
+    h = GM_INIT(ks->in.m);
+    GH_HASH(h, t, sizeof(t));
+    GH_HASH(h, pseq, SEQSZ + sz);
+    CHECK_MAC(h, pmac, tagsz);
+  }
+
+  /* --- Decrypt the packet --- */
+
+  if (ivsz) {
+    memset(buf_u, 0, blkcsz - SEQSZ);
+    memcpy(buf_u + blkcsz - SEQSZ, pseq, SEQSZ);
+    TRACE_PRESEQ(buf_u, ivsz);
+    GC_ENCRYPT(blkc, buf_u, buf_u, blkcsz);
+    GC_SETIV(c, buf_u);
+    TRACE_IV(buf_u, ivsz);
+  }
+  GC_DECRYPT(c, ppk, q, sz);
+
+  /* --- Finished --- */
+
+  *seq = LOAD32(pseq);
+  BSTEP(bb, sz);
+  return (0);
+}
+
 /*----- Bulk crypto transform table ---------------------------------------*/
 
 const bulkcrypto bulktab[] = {
@@ -207,6 +364,7 @@ const bulkcrypto bulktab[] = {
   { name, prim, pre##_check, pre##_overhead, pre##_encrypt, pre##_decrypt }
 
   BULK("v0", v0, BCP_CIPHER | BCP_MAC),
+  BULK("iiv", iiv, BCP_CIPHER | BCP_MAC | BCP_BLKC),
 
 #undef BULK
   { 0 }
index d28421fe977e555e3bff1d62b2f1752ad807c050..17325dce2d96e7ca78f029a4fd126d6344c865d9 100644 (file)
@@ -180,7 +180,7 @@ static int algs_get(algswitch *a, dstr *e, key_file *kf, key *k)
   const char *p;
   const bulkcrypto *bulk;
   char *q, *qq;
-  dstr d = DSTR_INIT;
+  dstr d = DSTR_INIT, dd = DSTR_INIT;
   int rc = -1;
 
   /* --- Hash function --- */
@@ -225,6 +225,25 @@ static int algs_get(algswitch *a, dstr *e, key_file *kf, key *k)
     }
   }
 
+  /* --- 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)) {
@@ -270,6 +289,7 @@ static int algs_get(algswitch *a, dstr *e, key_file *kf, key *k)
   rc = 0;
 done:
   dstr_destroy(&d);
+  dstr_destroy(&dd);
   return (rc);
 }
 
@@ -313,6 +333,12 @@ static int algs_check(algswitch *a, dstr *e, const group *g)
             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 --- */
 
@@ -347,7 +373,8 @@ 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->c == aa->c &&
+  return (group_samep(kdx->g, kdy->g) &&
+         a->c == aa->c && a->b == aa->b &&
          a->mgf == aa->mgf && a->h == aa->h &&
          a->m == aa->m && a->tagsz == aa->tagsz);
 }
index 6da6cc0f3f8a33e3f592ca716c53835e50c0f456..94fcae022175a7d242e6b4ee694fcb42d5021bb2 100644 (file)
@@ -236,6 +236,7 @@ static void gen_dir(const algswitch *algs, struct ksdir *ksd,
 
   SETKEY("encryption", c, GC_INIT);
   SETKEY("integrity", m, GM_KEY);
+  SETKEY("blkc", b, GC_INIT);
 
 #undef SETKEY
 }
index e769aaa50869fe953c014e094b1ccd31ec688a12..7a64cb44f2257ec2982f4ebcc34aad5b1757e6de 100644 (file)
@@ -364,12 +364,15 @@ AT_SETUP([server communication])
 AT_KEYWORDS([comm])
 export TRIPE_SLIPIF=USLIP
 
-for p in alice bob; do (mkdir $p; cd $p; SETUPDIR([alpha])); done
-
-WITH_2TRIPES([alice], [bob], [-nslip], [-talice], [-tbob], [
-  ESTABLISH([alice], [not-alice], [-key alice],
-           [bob], [bob], [])
-])
+for k in alpha beta-new; do
+  for p in alice bob; do (
+    rm -rf $p; mkdir $p; cd $p; SETUPDIR([$k])
+  ); done
+  WITH_2TRIPES([alice], [bob], [-nslip], [-talice], [-tbob], [
+    ESTABLISH([alice], [not-alice], [-key alice],
+             [bob], [bob], [])
+  ])
+done
 
 AT_CLEANUP
 
@@ -581,10 +584,11 @@ 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
-bulk-transform=v0 bulk-overhead=22
+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
 ])
 
   cp algs-alpha expout;    AT_CHECK([TRIPECTL -dalice ALGS],,       [expout])
index aeafc63e5b19510111286ae5a287046615e68a75..231aa7644f4dc54c1300401a8cface31a7707098 100644 (file)
@@ -455,6 +455,16 @@ octets.
 .TP
 .B mac-tagsz
 The length of the message authentication tag, in octets.
+.TP
+.B blkc
+The block cipher in use, e.g.,
+.BR blowfish .
+.TP
+.B blkc-keysz
+The length of key used by the block cipher, in octets.
+.TP
+.B blkc-blksz
+The block size of the block cipher.
 .PP
 The various sizes are useful, for example, when computing the MTU for a
 tunnel interface.  If
index ebbfc7904ce0c009704a198fd068d168d704db33..854b3a0a78672ec7febd0acf5e55c3ee564cf310 100644 (file)
@@ -361,8 +361,13 @@ mode, designed by Bellare, Canetti and Krawczyk).  These can all be
 overridden by setting attributes on your private key, as follows.
 .TP
 .B bulk
-Names the bulk-crypto transform to use.  Currently the only choice is
-.BR v0 .
+Names the bulk-crypto transform to use.  See below.
+.TP
+.B blkc
+Names a block cipher, used by some bulk-crypto transforms (e.g.,
+.BR iiv ).  The default is to use the block cipher underlying the chosen
+.BR cipher ,
+if any.
 .TP
 .B cipher
 Names the symmetric encryption scheme to use.  The default is
@@ -384,6 +389,26 @@ at half the underlying hash function's output length.
 A `mask-generation function', used in the key-exchange.  The default is
 .IB hash \-mgf
 and there's no good reason to change it.
+.PP
+The available bulk-crypto transforms are as follows.
+.TP
+.B v0
+Originally this was the only transform available.  It's a standard
+generic composition of a CPA-secure symmetric encryption scheme with a
+MAC; initialization vectors for symmetric encryption are chosen at
+random and included explicitly in the cryptogram.
+.TP
+.B iiv
+A newer `implicit-IV' transform.  Rather than having an explicit random
+IV, the IV is computed from the sequence number using a block cipher.
+This has two advantages over the
+.B v0
+transform.  Firstly, it adds less overhead to encrypted messages
+(because the IV no longer needs to be sent explicitly).  Secondly, and
+more significantly, the transform is entirely deterministic, so (a) it
+doesn't need the (possibly slow) random number generator, and (b) it
+closes a kleptographic channel, over which a compromised implementation
+could leak secret information to a third party.
 .SS "Using SLIP interfaces"
 Though not for the faint of heart, it is possible to get
 .B tripe
index 8cd66aface1ad69bc84d5e96416ea3d6c2010b99..de3e016ce3e555dc933d314c1cfd381ce350689c 100644 (file)
@@ -171,6 +171,7 @@ typedef struct bulkcrypto {
 
 #define BCP_CIPHER 1
 #define BCP_MAC 2
+#define BCP_BLKC 4
 
 struct algswitch {
   const gchash *h;                     /* Hash function */
@@ -178,10 +179,11 @@ struct algswitch {
   const struct bulkcrypto *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;                   /* Key lengths for @c@ and @m@ */
+  size_t cksz, mksz, bksz;             /* Key lengths for things */
 };
 
 typedef struct kdata {
@@ -269,6 +271,7 @@ struct keyset {
   struct ksdir {
     gcipher *c;                                /* Keyset cipher for encryption */
     gmac *m;                           /* Keyset MAC for integrity */
+    gcipher *b;                                /* Block cipher, just in case */
   } in, out;
   uint32 oseq;                         /* Outbound sequence number */
   seqwin iseq;                         /* Inbound sequence number */
index a7a1bd90b693ab4ec70fab406b88fd1e99e63532..cf6f32af826e3ea94a6d4475c48d3f4516868b9d 100644 (file)
@@ -1,4 +1,4 @@
-000f1070:tripe-ec-param struct:[curve=string,shared:secp160r1] forever forever -
-63803f04:tripe-ec:carol struct:[p=ec,public:0x42a11d34a1e19a69222a67c6ec29ff1d421cb021,0xc7d35a6dc9c330a09ee8e30680397b8bf51c29de,private=struct:[x=integer,private,burn:964893088925854502228477969935127891578649410999],curve=string,shared:secp160r1] forever forever -
-4045911b:tripe-ec:bob struct:[p=ec,public:0xcfc0b74fe1c4f30ced726bb70fbe4592ed8456ba,0x351d4dcbc200ccd3f3b6f4ecc112ecbf2043402d,private=struct:[x=integer,private,burn:333869955870933965311262832506583263321304092611],curve=string,shared:secp160r1] forever forever -
-e1a55f0e:tripe-ec:alice struct:[p=ec,public:0xce3bdc518066042b19083e2d27b0ded1915cb6b9,0xadef0e2006072f7bf038ef608e039a47e5767070,private=struct:[x=integer,private,burn:184161721501402669384082492238940360070520035996],curve=string,shared:secp160r1] forever forever -
+000f1070:tripe-ec-param struct:[curve=string,shared:secp160r1] forever forever bulk=iiv
+63803f04:tripe-ec:carol struct:[p=ec,public:0x42a11d34a1e19a69222a67c6ec29ff1d421cb021,0xc7d35a6dc9c330a09ee8e30680397b8bf51c29de,private=struct:[x=integer,private,burn:964893088925854502228477969935127891578649410999],curve=string,shared:secp160r1] forever forever bulk=iiv
+4045911b:tripe-ec:bob struct:[p=ec,public:0xcfc0b74fe1c4f30ced726bb70fbe4592ed8456ba,0x351d4dcbc200ccd3f3b6f4ecc112ecbf2043402d,private=struct:[x=integer,private,burn:333869955870933965311262832506583263321304092611],curve=string,shared:secp160r1] forever forever bulk=iiv
+e1a55f0e:tripe-ec:alice struct:[p=ec,public:0xce3bdc518066042b19083e2d27b0ded1915cb6b9,0xadef0e2006072f7bf038ef608e039a47e5767070,private=struct:[x=integer,private,burn:184161721501402669384082492238940360070520035996],curve=string,shared:secp160r1] forever forever bulk=iiv