From b87bffcb20069b174b2c03721466abdaafd780f2 Mon Sep 17 00:00:00 2001 Message-Id: From: Mark Wooding Date: Sat, 24 May 2014 14:00:03 +0100 Subject: [PATCH] server/: New `implicit-IV' transform (`iiv'). Organization: Straylight/Edgeware From: Mark Wooding 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 | 7 ++ server/bulkcrypto.c | 158 ++++++++++++++++++++++++++++++++++++++++ server/keymgmt.c | 31 +++++++- server/keyset.c | 1 + server/tests.at | 18 +++-- server/tripe-admin.5.in | 10 +++ server/tripe.8.in | 29 +++++++- server/tripe.h | 5 +- t/keyring-beta-new | 8 +- 9 files changed, 251 insertions(+), 16 deletions(-) diff --git a/server/admin.c b/server/admin.c index f3091be6..25581f65 100644 --- a/server/admin.c +++ b/server/admin.c @@ -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); } diff --git a/server/bulkcrypto.c b/server/bulkcrypto.c index faaf8d71..144a8301 100644 --- a/server/bulkcrypto.c +++ b/server/bulkcrypto.c @@ -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 } diff --git a/server/keymgmt.c b/server/keymgmt.c index d28421fe..17325dce 100644 --- a/server/keymgmt.c +++ b/server/keymgmt.c @@ -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); } diff --git a/server/keyset.c b/server/keyset.c index 6da6cc0f..94fcae02 100644 --- a/server/keyset.c +++ b/server/keyset.c @@ -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 } diff --git a/server/tests.at b/server/tests.at index e769aaa5..7a64cb44 100644 --- a/server/tests.at +++ b/server/tests.at @@ -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]) diff --git a/server/tripe-admin.5.in b/server/tripe-admin.5.in index aeafc63e..231aa764 100644 --- a/server/tripe-admin.5.in +++ b/server/tripe-admin.5.in @@ -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 diff --git a/server/tripe.8.in b/server/tripe.8.in index ebbfc790..854b3a0a 100644 --- a/server/tripe.8.in +++ b/server/tripe.8.in @@ -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 diff --git a/server/tripe.h b/server/tripe.h index 8cd66afa..de3e016c 100644 --- a/server/tripe.h +++ b/server/tripe.h @@ -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 */ diff --git a/t/keyring-beta-new b/t/keyring-beta-new index a7a1bd90..cf6f32af 100644 --- a/t/keyring-beta-new +++ b/t/keyring-beta-new @@ -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 -- [mdw]