diff --git a/README b/README index ce144e8..1dde5c5 100644 --- a/README +++ b/README @@ -33,13 +33,13 @@ Ciphers ------- - [x] ChaCha20 -- [ ] XChaCha20 +- [x] XChaCha20 AEAD ---- - [x] ChaCha20-Poly1305 -- [ ] XChaCha20-Poly1305 +- [x] XChaCha20-Poly1305 ECC --- diff --git a/aead_chacha20_poly1305.c b/aead_chacha20_poly1305.c index ae32dea..75d08fa 100644 --- a/aead_chacha20_poly1305.c +++ b/aead_chacha20_poly1305.c @@ -180,14 +180,174 @@ chacha20_poly1305_open(const uint8_t *key, size_t keylen, const uint8_t *iv, return 1; } +static int +xchacha20_poly1305_seal(const uint8_t *key, size_t keylen, const uint8_t *iv, + size_t ivlen, uint8_t *out, size_t *outlen, const uint8_t *aad, + size_t aadlen, const uint8_t *in, size_t inlen) +{ + struct chacha20_ctx cctx; + struct poly1305_ctx pctx; + uint8_t poly1305_key[LC_POLY1305_KEYLEN]; + uint8_t buf[sizeof(uint64_t) * 2]; + size_t i, olen; + + if (inlen > UINT64_MAX || aadlen > UINT64_MAX || + inlen > SIZE_MAX - LC_POLY1305_TAGLEN || + inlen > SIZE_MAX - CHACHA20_CHUNK + 1 || + (inlen + CHACHA20_CHUNK - 1) / CHACHA20_CHUNK > + CHACHA20_CTRMAX - 1) { + if (out == NULL) + *outlen = 0; + return 0; + } + + if (out == NULL) { + *outlen = inlen + LC_POLY1305_TAGLEN; + return 1; + } + + *outlen = 0; + + for (i = 0; i < LC_POLY1305_KEYLEN; i++) + poly1305_key[i] = 0; + if (!xchacha20_common_init(&cctx, key, keylen, iv, ivlen) || + !chacha20_common_update(&cctx, poly1305_key, &olen, poly1305_key, + LC_POLY1305_KEYLEN)) + return 0; + if (!chacha20_common_final(&cctx, poly1305_key + olen, &olen)) + return 0; + + if (!poly1305_init(&pctx, poly1305_key, LC_POLY1305_KEYLEN) || + !poly1305_update(&pctx, aad, aadlen)) + return 0; + if (aadlen % 16 != 0) + if (!poly1305_update(&pctx, zeropad, 16 - (aadlen % 16))) + return 0; + + if (!xchacha20_common_init_from(&cctx, key, keylen, iv, ivlen, 1)) + return 0; + if (!chacha20_common_update(&cctx, out, &olen, in, inlen)) + return 0; + *outlen = olen; + if (!chacha20_common_final(&cctx, out + olen, &olen)) + return 0; + if (!poly1305_update(&pctx, out, inlen)) + return 0; + if (inlen % 16 != 0) + if (!poly1305_update(&pctx, zeropad, 16 - (inlen % 16))) + return 0; + + store64le(&buf[0], aadlen); + store64le(&buf[sizeof(uint64_t)], inlen); + if (!poly1305_update(&pctx, buf, sizeof(buf)) || + !poly1305_final(&pctx, out + inlen, &olen)) + return 0; + + lc_scrub(buf, sizeof(buf)); + lc_scrub(poly1305_key, sizeof(poly1305_key)); + + *outlen = inlen + LC_POLY1305_TAGLEN; + + return 1; +} + +static int +xchacha20_poly1305_open(const uint8_t *key, size_t keylen, const uint8_t *iv, + size_t ivlen, uint8_t *out, size_t *outlen, const uint8_t *aad, + size_t aadlen, const uint8_t *in, size_t inlen) +{ + const uint8_t *tagp; + struct chacha20_ctx cctx; + struct poly1305_ctx pctx; + uint8_t poly1305_key[LC_POLY1305_KEYLEN]; + uint8_t tag[LC_POLY1305_TAGLEN]; + uint8_t buf[sizeof(uint64_t) * 2]; + size_t i, olen, ctlen; + + if (inlen < LC_POLY1305_TAGLEN || + inlen > UINT64_MAX || aadlen > UINT64_MAX || + inlen > SIZE_MAX - LC_POLY1305_TAGLEN || + inlen > SIZE_MAX - CHACHA20_CHUNK + 1 || + (inlen + CHACHA20_CHUNK - 1) / CHACHA20_CHUNK > + CHACHA20_CTRMAX - 1) { + if (out == NULL) + *outlen = 0; + return 0; + } + + if (out == NULL) { + *outlen = inlen - LC_POLY1305_TAGLEN; + return 1; + } + + *outlen = 0; + ctlen = inlen - LC_POLY1305_TAGLEN; + tagp = in + ctlen; + + for (i = 0; i < LC_POLY1305_KEYLEN; i++) + poly1305_key[i] = 0; + if (!xchacha20_common_init(&cctx, key, keylen, iv, ivlen) || + !chacha20_common_update(&cctx, poly1305_key, &olen, poly1305_key, + LC_POLY1305_KEYLEN)) + return 0; + if (!chacha20_common_final(&cctx, poly1305_key + olen, &olen)) + return 0; + + if (!poly1305_init(&pctx, poly1305_key, LC_POLY1305_KEYLEN) || + !poly1305_update(&pctx, aad, aadlen)) + return 0; + if (aadlen % 16 != 0) + if (!poly1305_update(&pctx, zeropad, 16 - (aadlen % 16))) + return 0; + + if (!poly1305_update(&pctx, in, ctlen)) + return 0; + if (ctlen % 16 != 0) + if (!poly1305_update(&pctx, zeropad, 16 - (ctlen % 16))) + return 0; + + store64le(&buf[0], aadlen); + store64le(&buf[sizeof(uint64_t)], ctlen); + if (!poly1305_update(&pctx, buf, sizeof(buf)) || + !poly1305_final(&pctx, tag, &olen)) + return 0; + + if (!lc_ct_cmp(tag, tagp, LC_POLY1305_TAGLEN)) + return 0; + + lc_scrub(buf, sizeof(buf)); + lc_scrub(poly1305_key, sizeof(poly1305_key)); + + if (!xchacha20_common_init_from(&cctx, key, keylen, iv, ivlen, 1)) + return 0; + if (!chacha20_common_update(&cctx, out, &olen, in, ctlen)) + return 0; + *outlen = olen; + if (!chacha20_common_final(&cctx, out + olen, &olen)) + return 0; + *outlen += olen; + + return 1; +} + static struct lc_aead_impl chacha20_poly1305_impl = { .seal = &chacha20_poly1305_seal, .open = &chacha20_poly1305_open, }; +static struct lc_aead_impl xchacha20_poly1305_impl = { + .seal = &xchacha20_poly1305_seal, + .open = &xchacha20_poly1305_open, +}; + const struct lc_aead_impl * lc_aead_impl_chacha20_poly1305(void) { return &chacha20_poly1305_impl; } +const struct lc_aead_impl * +lc_aead_impl_xchacha20_poly1305(void) +{ + return &xchacha20_poly1305_impl; +} diff --git a/cipher_chacha20.c b/cipher_chacha20.c index cd4ca40..48f0b9a 100644 --- a/cipher_chacha20.c +++ b/cipher_chacha20.c @@ -54,6 +54,49 @@ chacha20_common_init(void *arg, const uint8_t *key, size_t keylen, return chacha20_common_init_from(arg, key, keylen, iv, ivlen, 0); } +int +xchacha20_common_init_from(void *arg, const uint8_t *key, size_t keylen, + const uint8_t *iv, size_t ivlen, uint64_t counter) +{ + struct chacha20_ctx *ctx = arg; + size_t i; + + if (keylen != LC_XCHACHA20_KEYLEN || ivlen != LC_XCHACHA20_IVLEN) + return 0; + + for (i = 0; i < CHACHA20_CHUNK_WORDS; i++) + ctx->s[i] = 0; + for (i = 0; i < CHACHA20_KEY_WORDS; i++) + ctx->k[i] = load32le(&key[i * 4]); + for (i = 0; i < CHACHA20_NONCE_WORDS; i++) + ctx->n[i] = load32le(&iv[i * 4]); + ctx->mlen = 0; + + hchacha20_block(ctx); + + ctx->k[0] = ctx->s[0]; + ctx->k[1] = ctx->s[1]; + ctx->k[2] = ctx->s[2]; + ctx->k[3] = ctx->s[3]; + ctx->k[4] = ctx->s[12]; + ctx->k[5] = ctx->s[13]; + ctx->k[6] = ctx->s[14]; + ctx->k[7] = ctx->s[15]; + ctx->n[0] = counter; + ctx->n[1] = counter >> 32; + ctx->n[2] = load32le(&iv[16]); + ctx->n[3] = load32le(&iv[20]); + + return 1; +} + +int +xchacha20_common_init(void *arg, const uint8_t *key, size_t keylen, + const uint8_t *iv, size_t ivlen) +{ + return xchacha20_common_init_from(arg, key, keylen, iv, ivlen, 0); +} + int chacha20_common_update(void *arg, uint8_t *out, size_t *outlen, const uint8_t *in, size_t inlen) @@ -201,8 +244,29 @@ static struct lc_cipher_impl chacha20_impl = { .ctx_free = NULL, }; +static struct lc_cipher_impl xchacha20_impl = { + .encrypt_init = &xchacha20_common_init, + .encrypt_update = &chacha20_common_update, + .encrypt_final = &chacha20_common_final, + .encrypt = &chacha20_common, + + .decrypt_init = &xchacha20_common_init, + .decrypt_update = &chacha20_common_update, + .decrypt_final = &chacha20_common_final, + .decrypt = &chacha20_common, + + .ctx_new = &chacha20_ctx_new, + .ctx_free = NULL, +}; + const struct lc_cipher_impl * lc_cipher_impl_chacha20(void) { return &chacha20_impl; } + +const struct lc_cipher_impl * +lc_cipher_impl_xchacha20(void) +{ + return &xchacha20_impl; +} diff --git a/cipher_chacha20.h b/cipher_chacha20.h index c0ebda3..8cb3ac2 100644 --- a/cipher_chacha20.h +++ b/cipher_chacha20.h @@ -22,6 +22,10 @@ int chacha20_common_init_from(void *, const uint8_t *, size_t, const uint8_t *, size_t, uint32_t); int chacha20_common_init(void *, const uint8_t *, size_t, const uint8_t *, size_t); +int xchacha20_common_init_from(void *, const uint8_t *, size_t, + const uint8_t *, size_t, uint64_t); +int xchacha20_common_init(void *, const uint8_t *, size_t, const uint8_t *, + size_t); int chacha20_common_update(void *, uint8_t *, size_t *, const uint8_t *, size_t); int chacha20_common_final(void *, uint8_t *, size_t *); diff --git a/impl_chacha20.c b/impl_chacha20.c index b2d28c9..2c2a298 100644 --- a/impl_chacha20.c +++ b/impl_chacha20.c @@ -83,3 +83,42 @@ chacha20_block(struct chacha20_ctx *ctx) for (i = 0; i < CHACHA20_CHUNK_WORDS; i++) ctx->s[i] += x[i]; } + +void +hchacha20_block(struct chacha20_ctx *ctx) +{ + uint32_t x[CHACHA20_CHUNK_WORDS]; + size_t i; + + x[0] = SIGMA0; + x[1] = SIGMA1; + x[2] = SIGMA2; + x[3] = SIGMA3; + x[4] = ctx->k[0]; + x[5] = ctx->k[1]; + x[6] = ctx->k[2]; + x[7] = ctx->k[3]; + x[8] = ctx->k[4]; + x[9] = ctx->k[5]; + x[10] = ctx->k[6]; + x[11] = ctx->k[7]; + x[12] = ctx->n[0]; + x[13] = ctx->n[1]; + x[14] = ctx->n[2]; + x[15] = ctx->n[3]; + + for (i = 0; i < CHACHA20_ROUNDS; i++) { + QUARTERROUND(x[0], x[4], x[8], x[12]); + QUARTERROUND(x[1], x[5], x[9], x[13]); + QUARTERROUND(x[2], x[6], x[10], x[14]); + QUARTERROUND(x[3], x[7], x[11], x[15]); + + QUARTERROUND(x[0], x[5], x[10], x[15]); + QUARTERROUND(x[1], x[6], x[11], x[12]); + QUARTERROUND(x[2], x[7], x[8], x[13]); + QUARTERROUND(x[3], x[4], x[9], x[14]); + } + + for (i = 0; i < CHACHA20_CHUNK_WORDS; i++) + ctx->s[i] = x[i]; +} diff --git a/impl_chacha20.h b/impl_chacha20.h index 6c85ec5..407a467 100644 --- a/impl_chacha20.h +++ b/impl_chacha20.h @@ -38,3 +38,4 @@ struct chacha20_ctx { void chacha20_block(struct chacha20_ctx *); +void hchacha20_block(struct chacha20_ctx *); diff --git a/lilcrypto.h b/lilcrypto.h index 70daec4..0c81bdd 100644 --- a/lilcrypto.h +++ b/lilcrypto.h @@ -34,6 +34,8 @@ /* Ciphers. */ #define LC_CHACHA20_KEYLEN 32 #define LC_CHACHA20_IVLEN 12 +#define LC_XCHACHA20_KEYLEN 32 +#define LC_XCHACHA20_IVLEN 24 /* @@ -115,6 +117,7 @@ struct lc_cipher_ctx *lc_cipher_ctx_new(const struct lc_cipher_impl *); void lc_cipher_ctx_free(struct lc_cipher_ctx *); const struct lc_cipher_impl *lc_cipher_impl_chacha20(void); +const struct lc_cipher_impl *lc_cipher_impl_xchacha20(void); /* @@ -132,6 +135,7 @@ int lc_aead_open(const struct lc_aead_impl *, const uint8_t *, size_t, const uint8_t *, size_t); const struct lc_aead_impl *lc_aead_impl_chacha20_poly1305(void); +const struct lc_aead_impl *lc_aead_impl_xchacha20_poly1305(void); /* diff --git a/wycheproof/Makefile b/wycheproof/Makefile index 3f15ee7..d7b3f98 100644 --- a/wycheproof/Makefile +++ b/wycheproof/Makefile @@ -22,7 +22,9 @@ tests-aead: .for p in ${AEAD} perl ${.CURDIR}/aead.pl ${TESTOPTS} -x ./${p} \ ${WYCHEPROOF_DIR}/testvectors/chacha20_poly1305_test.json \ - ${WYCHEPROOF_DIR}/testvectors_v1/chacha20_poly1305_test.json + ${WYCHEPROOF_DIR}/testvectors_v1/chacha20_poly1305_test.json \ + ${WYCHEPROOF_DIR}/testvectors/xchacha20_poly1305_test.json \ + ${WYCHEPROOF_DIR}/testvectors_v1/xchacha20_poly1305_test.json .endfor tests-mac: diff --git a/wycheproof_aead.c b/wycheproof_aead.c index 9ca386b..66363d2 100644 --- a/wycheproof_aead.c +++ b/wycheproof_aead.c @@ -79,6 +79,7 @@ kw2impl(const char *s) { static const struct kwimpl tbl[] = { { "CHACHA20-POLY1305", &lc_aead_impl_chacha20_poly1305 }, + { "XCHACHA20-POLY1305", &lc_aead_impl_xchacha20_poly1305 }, }; struct kwimpl *match;