diff --git a/sys/include/libsec.h b/sys/include/libsec.h index 359a8aa61..0b3ba44ac 100644 --- a/sys/include/libsec.h +++ b/sys/include/libsec.h @@ -94,13 +94,18 @@ struct Chachastate }; }; int rounds; + int ivwords; }; -void setupChachastate(Chachastate*, uchar*, ulong, uchar*, int); -void chacha_setblock(Chachastate*, u32int); +void setupChachastate(Chachastate*, uchar*, ulong, uchar*, ulong, int); +void chacha_setiv(Chachastate *, uchar*); +void chacha_setblock(Chachastate*, u64int); void chacha_encrypt(uchar*, ulong, Chachastate*); void chacha_encrypt2(uchar*, uchar*, ulong, Chachastate*); +void ccpoly_encrypt(uchar *dat, ulong ndat, uchar *aad, ulong naad, uchar tag[16], Chachastate *cs); +int ccpoly_decrypt(uchar *dat, ulong ndat, uchar *aad, ulong naad, uchar tag[16], Chachastate *cs); + /* * DES definitions */ @@ -505,3 +510,5 @@ void pbkdf2_x(uchar *p, ulong plen, uchar *s, ulong slen, ulong rounds, uchar *d void hkdf_x(uchar *salt, ulong nsalt, uchar *info, ulong ninfo, uchar *key, ulong nkey, uchar *d, ulong dlen, DigestState* (*x)(uchar*, ulong, uchar*, ulong, uchar*, DigestState*), int xlen); +/* timing safe memcmp() */ +int tsmemcmp(void*, void*, ulong); diff --git a/sys/man/2/chacha b/sys/man/2/chacha index de07d133d..4ccf3834c 100644 --- a/sys/man/2/chacha +++ b/sys/man/2/chacha @@ -1,17 +1,15 @@ .TH CHACHA 2 .SH NAME -setupChachastate, chacha_setblock, chacha_encrypt, chacha_encrypt2 - chacha encryption +setupChachastate, chacha_setblock, chacha_setiv, chacha_encrypt, chacha_encrypt2, ccpoly_encrypt, ccpoly_decrypt \- chacha encryption .SH SYNOPSIS .B #include .br .B #include .br -.B #include -.br .B #include .PP .B -void setupChachastate(Chachastate *s, uchar key[], ulong keylen, uchar *nonce, int rounds) +void setupChachastate(Chachastate *s, uchar key[], ulong keylen, uchar *iv, ulong ivlen, int rounds) .PP .B void chacha_encrypt(uchar *data, ulong len, Chachastate *s) @@ -20,7 +18,16 @@ void chacha_encrypt(uchar *data, ulong len, Chachastate *s) void chacha_encrypt2(uchar *src, uchar *dst, ulong len, Chachastate *s) .PP .B -void chacha_setblock(Chachastate *s, u32int blockno) +void chacha_setblock(Chachastate *s, u64int blockno) +.PP +.B +void chacha_setiv(Chachastate *s, uchar *iv); +.PP +.B +void ccpoly_encrypt(uchar *dat, ulong ndat, uchar *aad, ulong naad, uchar tag[16], Chachastate *cs); +.PP +.B +int ccpoly_decrypt(uchar *dat, ulong ndat, uchar *aad, ulong naad, uchar tag[16], Chachastate *cs); .SH DESCRIPTION .PP Chacha is D J Berstein's symmetric stream cipher, as modified by RFC7539. It supports @@ -38,14 +45,19 @@ of bytes, which should normally be .BR ChachaKeylen , a -.I nonce -or initialisation vector of -.B ChachaIVlen -bytes (set to all zeros if the argument is nil), +.I iv +or nonce of +.I ivlen +bytes (can be +.BR ChachaIVlen =12 +or 8, set to all zeros if the +.I iv +argument is nil), and the number of .I rounds (set to the default of 20 if the argument is zero). -With a keylength of 256 bits (32 bytes) and 20 +With a key length of 256 bits (32 bytes), a nonce of 96 bits (12 bytes) +and 20 .IR rounds , the function implements the Chacha20 encryption function of RFC7539. .PP @@ -77,6 +89,37 @@ without modifying sets the Chacha block counter for the next encryption to .IR blockno , allowing seeking in an encrypted stream. +.PP +.I Chacha_setiv +sets the the initialization vector (nonce) to +.IR iv . +.PP +.I Ccpoly_encrypt +and +.I ccpoly_decrypt +implement authenticated encryption with associated data (AEAD) +using Chacha cipher and Poly1305 message authentication code +as specified in RFC7539. +These routines require a +.I Chachastate +that has been setup with a new (per key unique) initialization +vector (nonce) on each invocation. The referenced data +.IR dat [ ndat ] +is in-place encrypted or decrypted. +.I Ccpoly_encrypt +produces a 16 byte authentication +.IR tag , +while +.I ccpoly_decrypt +verifies the +.IR tag , +returning zero on success or negative on a mismatch. +The +.IR aad [ naad ] +arguments refer to the additional authenticated data +that is included in the +.I tag +calculation, but not encrypted. .SH SOURCE .B /sys/src/libsec .SH SEE ALSO diff --git a/sys/src/libsec/port/ccpoly.c b/sys/src/libsec/port/ccpoly.c new file mode 100644 index 000000000..14a8a9cd4 --- /dev/null +++ b/sys/src/libsec/port/ccpoly.c @@ -0,0 +1,84 @@ +#include +#include +#include + +static void +ccpolyotk(Chachastate *cs, DigestState *ds) +{ + uchar otk[ChachaBsize]; + + memset(ds, 0, sizeof(*ds)); + memset(otk, 0, 32); + chacha_setblock(cs, 0); + chacha_encrypt(otk, ChachaBsize, cs); + poly1305(nil, 0, otk, 32, nil, ds); +} + +static void +ccpolymac(uchar *buf, ulong nbuf, DigestState *ds) +{ + static uchar zeros[16] = {0}; + ulong npad; + + if(nbuf == 0) + return; + poly1305(buf, nbuf, nil, 0, nil, ds); + npad = nbuf % 16; + if(npad == 0) + return; + poly1305(zeros, 16 - npad, nil, 0, nil, ds); +} + +static void +ccpolytag(ulong ndat, ulong naad, uchar tag[16], DigestState *ds) +{ + uchar info[16]; + + info[0] = naad; + info[1] = naad>>8; + info[2] = naad>>16; + info[3] = naad>>24; + info[4] = 0; + info[5] = 0; + info[6] = 0; + info[7] = 0; + + info[8] = ndat; + info[9] = ndat>>8; + info[10] = ndat>>16; + info[11] = ndat>>24; + info[12] = 0; + info[13] = 0; + info[14] = 0; + info[15] = 0; + + poly1305(info, 16, nil, 0, tag, ds); +} + +void +ccpoly_encrypt(uchar *dat, ulong ndat, uchar *aad, ulong naad, uchar tag[16], Chachastate *cs) +{ + DigestState ds; + + ccpolyotk(cs, &ds); + ccpolymac(aad, naad, &ds); + chacha_encrypt(dat, ndat, cs); + ccpolymac(dat, ndat, &ds); + ccpolytag(ndat, naad, tag, &ds); +} + +int +ccpoly_decrypt(uchar *dat, ulong ndat, uchar *aad, ulong naad, uchar tag[16], Chachastate *cs) +{ + DigestState ds; + uchar tmp[16]; + + ccpolyotk(cs, &ds); + ccpolymac(aad, naad, &ds); + ccpolymac(dat, ndat, &ds); + ccpolytag(ndat, naad, tmp, &ds); + if(tsmemcmp(tag, tmp, 16) != 0) + return -1; + chacha_encrypt(dat, ndat, cs); + return 0; +} diff --git a/sys/src/libsec/port/chacha.c b/sys/src/libsec/port/chacha.c index e075c15e5..d3e603a61 100644 --- a/sys/src/libsec/port/chacha.c +++ b/sys/src/libsec/port/chacha.c @@ -54,10 +54,12 @@ load(u32int *d, uchar *s, int nw) } void -setupChachastate(Chachastate *s, uchar *key, ulong keylen, uchar *iv, int rounds) +setupChachastate(Chachastate *s, uchar *key, ulong keylen, uchar *iv, ulong ivlen, int rounds) { if(keylen != 256/8 && keylen != 128/8) sysfatal("invalid chacha key length"); + if(ivlen != 96/8 && ivlen != 64/8) + sysfatal("invalid chacha iv length"); if(rounds == 0) rounds = 20; s->rounds = rounds; @@ -69,19 +71,28 @@ setupChachastate(Chachastate *s, uchar *key, ulong keylen, uchar *iv, int rounds load(&s->input[4], key, 4); load(&s->input[8], key, 4); } + s->ivwords = ivlen/sizeof(u32int); s->input[12] = 0; + s->input[13] = 0; if(iv == nil){ - s->input[13] = 0; s->input[14] = 0; s->input[15] = 0; }else - load(&s->input[13], iv, 3); + chacha_setiv(s, iv); } void -chacha_setblock(Chachastate *s, u32int blockno) +chacha_setiv(Chachastate *s, uchar *iv) +{ + load(&s->input[16 - s->ivwords], iv, s->ivwords); +} + +void +chacha_setblock(Chachastate *s, u64int blockno) { s->input[12] = blockno; + if(s->ivwords == 2) + s->input[13] = blockno>>32; } static void @@ -148,7 +159,8 @@ encryptblock(Chachastate *s, uchar *src, uchar *dst) } #endif - s->input[12]++; + if(++s->input[12] == 0 && s->ivwords == 2) + s->input[13]++; } void diff --git a/sys/src/libsec/port/chachatest.c b/sys/src/libsec/port/chachatest.c index 7949c6f65..e578dec17 100644 --- a/sys/src/libsec/port/chachatest.c +++ b/sys/src/libsec/port/chachatest.c @@ -31,7 +31,7 @@ u32int rfccount = 1; char rfctext[] = "Ladies and Gentlemen of the class of '99: If I could offer you only one tip for the future, " "sunscreen would be it."; uchar rfcout[3*ChachaBsize]; -uchar rfcref[3*ChachaBsize] = { +uchar rfcref[] = { 0x6e, 0x2e, 0x35, 0x9a, 0x25, 0x68, 0xf9, 0x80, 0x41, 0xba, 0x07, 0x28, 0xdd, 0x0d, 0x69, 0x81, 0xe9, 0x7e, 0x7a, 0xec, 0x1d, 0x43, 0x60, 0xc2, 0x0a, 0x27, 0xaf, 0xcc, 0xfd, 0x9f, 0xae, 0x0b, 0xf9, 0x1b, 0x65, 0xc5, 0x52, 0x47, 0x33, 0xab, 0x8f, 0x59, 0x3d, 0xab, 0xcd, 0x62, 0xb3, 0x57, @@ -42,10 +42,26 @@ uchar rfcref[3*ChachaBsize] = { 0x87, 0x4d }; +uchar ccpaad[] = { + 0x50, 0x51, 0x52, 0x53, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, +}; +uchar ccpkey[] = { + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, +}; +uchar ccpiv[] = { + 0x07, 0x00, 0x00, 0x00, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, +}; +uchar ccptag[] = { + 0x1a, 0xe1, 0x0b, 0x59, 0x4f, 0x09, 0xe2, 0x6a, 0x7e, 0x90, 0x2e, 0xcb, 0xd0, 0x60, 0x06, 0x91, +}; + void main(int argc, char **argv) { Chachastate s; + uchar tag[16]; int n; ARGBEGIN{ @@ -54,17 +70,51 @@ main(int argc, char **argv) print("key:\n"); printblock(rfckey, sizeof(rfckey)); n = strlen(rfctext); - setupChachastate(&s, rfckey, sizeof(rfckey), rfcnonce, 0); + setupChachastate(&s, rfckey, sizeof(rfckey), rfcnonce, sizeof(rfcnonce), 0); chacha_setblock(&s, rfccount); print("rfc in:\n"); printblock((uchar*)rfctext, n); chacha_encrypt2((uchar*)rfctext, rfcout, n, &s); print("rfc out:\n"); printblock(rfcout, n); - if(memcmp(rfcout, rfcref, sizeof(rfcout)) != 0){ + if(memcmp(rfcout, rfcref, sizeof(rfcref)) != 0){ print("failure of vision\n"); exits("wrong"); } + print("\n"); + + print("ccpoly key:\n"); + printblock(ccpkey, sizeof(ccpkey)); + + print("ccpoly iv:\n"); + printblock(ccpiv, sizeof(ccpiv)); + + setupChachastate(&s, ccpkey, sizeof(ccpkey), ccpiv, sizeof(ccpiv), 20); + + memmove(rfcout, rfctext, sizeof(rfctext)-1); + ccpoly_encrypt(rfcout, sizeof(rfctext)-1, ccpaad, sizeof(ccpaad), tag, &s); + + print("ccpoly cipher:\n"); + printblock(rfcout, sizeof(rfctext)-1); + + print("ccpoly tag:\n"); + printblock(tag, sizeof(tag)); + + if(memcmp(tag, ccptag, sizeof(tag)) != 0){ + print("bad ccpoly tag\n"); + exits("wrong"); + } + + if(ccpoly_decrypt(rfcout, sizeof(rfctext)-1, ccpaad, sizeof(ccpaad), tag, &s) != 0){ + print("ccpoly decryption failed\n"); + exits("wrong"); + } + + if(memcmp(rfcout, rfctext, sizeof(rfctext)-1) != 0){ + print("ccpoly bad decryption\n"); + exits("wrong"); + } + print("passed\n"); exits(nil); } diff --git a/sys/src/libsec/port/mkfile b/sys/src/libsec/port/mkfile index f7be1abfc..d76e81b91 100644 --- a/sys/src/libsec/port/mkfile +++ b/sys/src/libsec/port/mkfile @@ -27,6 +27,8 @@ CFILES = des.c desmodes.c desECB.c desCBC.c des3ECB.c des3CBC.c\ curve25519_dh.c\ pbkdf2.c\ hkdf.c\ + ccpoly.c\ + tsmemcmp.c\ ALLOFILES=${CFILES:%.c=%.$O} diff --git a/sys/src/libsec/port/tsmemcmp.c b/sys/src/libsec/port/tsmemcmp.c new file mode 100644 index 000000000..6cc75377d --- /dev/null +++ b/sys/src/libsec/port/tsmemcmp.c @@ -0,0 +1,26 @@ +#include +#include +#include + +/* + * timing safe memcmp() + */ +int +tsmemcmp(void *a1, void *a2, ulong n) +{ + int lt, gt, c1, c2, r, m; + uchar *s1, *s2; + + r = m = 0; + s1 = a1; + s2 = a2; + while(n--){ + c1 = *s1++; + c2 = *s2++; + lt = (c1 - c2) >> 8; + gt = (c2 - c1) >> 8; + r |= (lt - gt) & ~m; + m |= lt | gt; + } + return r; +}