| From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 |
| From: Ard Biesheuvel <ardb@kernel.org> |
| Date: Fri, 8 Nov 2019 13:22:40 +0100 |
| Subject: [PATCH] crypto: lib/chacha20poly1305 - reimplement crypt_from_sg() |
| routine |
| |
| commit d95312a3ccc0cd544d374be2fc45aeaa803e5fd9 upstream. |
| |
| Reimplement the library routines to perform chacha20poly1305 en/decryption |
| on scatterlists, without [ab]using the [deprecated] blkcipher interface, |
| which is rather heavyweight and does things we don't really need. |
| |
| Instead, we use the sg_miter API in a novel and clever way, to iterate |
| over the scatterlist in-place (i.e., source == destination, which is the |
| only way this library is expected to be used). That way, we don't have to |
| iterate over two scatterlists in parallel. |
| |
| Another optimization is that, instead of relying on the blkcipher walker |
| to present the input in suitable chunks, we recognize that ChaCha is a |
| streamcipher, and so we can simply deal with partial blocks by keeping a |
| block of cipherstream on the stack and use crypto_xor() to mix it with |
| the in/output. |
| |
| Finally, we omit the scatterwalk_and_copy() call if the last element of |
| the scatterlist covers the MAC as well (which is the common case), |
| avoiding the need to walk the scatterlist and kmap() the page twice. |
| |
| Signed-off-by: Ard Biesheuvel <ardb@kernel.org> |
| Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au> |
| Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com> |
| --- |
| include/crypto/chacha20poly1305.h | 11 ++ |
| lib/crypto/chacha20poly1305-selftest.c | 45 ++++++++ |
| lib/crypto/chacha20poly1305.c | 150 +++++++++++++++++++++++++ |
| 3 files changed, 206 insertions(+) |
| |
| --- a/include/crypto/chacha20poly1305.h |
| +++ b/include/crypto/chacha20poly1305.h |
| @@ -7,6 +7,7 @@ |
| #define __CHACHA20POLY1305_H |
| |
| #include <linux/types.h> |
| +#include <linux/scatterlist.h> |
| |
| enum chacha20poly1305_lengths { |
| XCHACHA20POLY1305_NONCE_SIZE = 24, |
| @@ -34,4 +35,14 @@ bool __must_check xchacha20poly1305_decr |
| const size_t ad_len, const u8 nonce[XCHACHA20POLY1305_NONCE_SIZE], |
| const u8 key[CHACHA20POLY1305_KEY_SIZE]); |
| |
| +bool chacha20poly1305_encrypt_sg_inplace(struct scatterlist *src, size_t src_len, |
| + const u8 *ad, const size_t ad_len, |
| + const u64 nonce, |
| + const u8 key[CHACHA20POLY1305_KEY_SIZE]); |
| + |
| +bool chacha20poly1305_decrypt_sg_inplace(struct scatterlist *src, size_t src_len, |
| + const u8 *ad, const size_t ad_len, |
| + const u64 nonce, |
| + const u8 key[CHACHA20POLY1305_KEY_SIZE]); |
| + |
| #endif /* __CHACHA20POLY1305_H */ |
| --- a/lib/crypto/chacha20poly1305-selftest.c |
| +++ b/lib/crypto/chacha20poly1305-selftest.c |
| @@ -7250,6 +7250,7 @@ bool __init chacha20poly1305_selftest(vo |
| enum { MAXIMUM_TEST_BUFFER_LEN = 1UL << 12 }; |
| size_t i; |
| u8 *computed_output = NULL, *heap_src = NULL; |
| + struct scatterlist sg_src; |
| bool success = true, ret; |
| |
| heap_src = kmalloc(MAXIMUM_TEST_BUFFER_LEN, GFP_KERNEL); |
| @@ -7280,6 +7281,29 @@ bool __init chacha20poly1305_selftest(vo |
| } |
| } |
| |
| + for (i = 0; i < ARRAY_SIZE(chacha20poly1305_enc_vectors); ++i) { |
| + if (chacha20poly1305_enc_vectors[i].nlen != 8) |
| + continue; |
| + memcpy(heap_src, chacha20poly1305_enc_vectors[i].input, |
| + chacha20poly1305_enc_vectors[i].ilen); |
| + sg_init_one(&sg_src, heap_src, |
| + chacha20poly1305_enc_vectors[i].ilen + POLY1305_DIGEST_SIZE); |
| + chacha20poly1305_encrypt_sg_inplace(&sg_src, |
| + chacha20poly1305_enc_vectors[i].ilen, |
| + chacha20poly1305_enc_vectors[i].assoc, |
| + chacha20poly1305_enc_vectors[i].alen, |
| + get_unaligned_le64(chacha20poly1305_enc_vectors[i].nonce), |
| + chacha20poly1305_enc_vectors[i].key); |
| + if (memcmp(heap_src, |
| + chacha20poly1305_enc_vectors[i].output, |
| + chacha20poly1305_enc_vectors[i].ilen + |
| + POLY1305_DIGEST_SIZE)) { |
| + pr_err("chacha20poly1305 sg encryption self-test %zu: FAIL\n", |
| + i + 1); |
| + success = false; |
| + } |
| + } |
| + |
| for (i = 0; i < ARRAY_SIZE(chacha20poly1305_dec_vectors); ++i) { |
| memset(computed_output, 0, MAXIMUM_TEST_BUFFER_LEN); |
| ret = chacha20poly1305_decrypt(computed_output, |
| @@ -7301,6 +7325,27 @@ bool __init chacha20poly1305_selftest(vo |
| } |
| } |
| |
| + for (i = 0; i < ARRAY_SIZE(chacha20poly1305_dec_vectors); ++i) { |
| + memcpy(heap_src, chacha20poly1305_dec_vectors[i].input, |
| + chacha20poly1305_dec_vectors[i].ilen); |
| + sg_init_one(&sg_src, heap_src, |
| + chacha20poly1305_dec_vectors[i].ilen); |
| + ret = chacha20poly1305_decrypt_sg_inplace(&sg_src, |
| + chacha20poly1305_dec_vectors[i].ilen, |
| + chacha20poly1305_dec_vectors[i].assoc, |
| + chacha20poly1305_dec_vectors[i].alen, |
| + get_unaligned_le64(chacha20poly1305_dec_vectors[i].nonce), |
| + chacha20poly1305_dec_vectors[i].key); |
| + if (!decryption_success(ret, |
| + chacha20poly1305_dec_vectors[i].failure, |
| + memcmp(heap_src, chacha20poly1305_dec_vectors[i].output, |
| + chacha20poly1305_dec_vectors[i].ilen - |
| + POLY1305_DIGEST_SIZE))) { |
| + pr_err("chacha20poly1305 sg decryption self-test %zu: FAIL\n", |
| + i + 1); |
| + success = false; |
| + } |
| + } |
| |
| for (i = 0; i < ARRAY_SIZE(xchacha20poly1305_enc_vectors); ++i) { |
| memset(computed_output, 0, MAXIMUM_TEST_BUFFER_LEN); |
| --- a/lib/crypto/chacha20poly1305.c |
| +++ b/lib/crypto/chacha20poly1305.c |
| @@ -11,6 +11,7 @@ |
| #include <crypto/chacha20poly1305.h> |
| #include <crypto/chacha.h> |
| #include <crypto/poly1305.h> |
| +#include <crypto/scatterwalk.h> |
| |
| #include <asm/unaligned.h> |
| #include <linux/kernel.h> |
| @@ -205,6 +206,155 @@ bool xchacha20poly1305_decrypt(u8 *dst, |
| } |
| EXPORT_SYMBOL(xchacha20poly1305_decrypt); |
| |
| +static |
| +bool chacha20poly1305_crypt_sg_inplace(struct scatterlist *src, |
| + const size_t src_len, |
| + const u8 *ad, const size_t ad_len, |
| + const u64 nonce, |
| + const u8 key[CHACHA20POLY1305_KEY_SIZE], |
| + int encrypt) |
| +{ |
| + const u8 *pad0 = page_address(ZERO_PAGE(0)); |
| + struct poly1305_desc_ctx poly1305_state; |
| + u32 chacha_state[CHACHA_STATE_WORDS]; |
| + struct sg_mapping_iter miter; |
| + size_t partial = 0; |
| + unsigned int flags; |
| + bool ret = true; |
| + int sl; |
| + union { |
| + struct { |
| + u32 k[CHACHA_KEY_WORDS]; |
| + __le64 iv[2]; |
| + }; |
| + u8 block0[POLY1305_KEY_SIZE]; |
| + u8 chacha_stream[CHACHA_BLOCK_SIZE]; |
| + struct { |
| + u8 mac[2][POLY1305_DIGEST_SIZE]; |
| + }; |
| + __le64 lens[2]; |
| + } b __aligned(16); |
| + |
| + chacha_load_key(b.k, key); |
| + |
| + b.iv[0] = 0; |
| + b.iv[1] = cpu_to_le64(nonce); |
| + |
| + chacha_init(chacha_state, b.k, (u8 *)b.iv); |
| + chacha_crypt(chacha_state, b.block0, pad0, sizeof(b.block0), 20); |
| + poly1305_init(&poly1305_state, b.block0); |
| + |
| + if (unlikely(ad_len)) { |
| + poly1305_update(&poly1305_state, ad, ad_len); |
| + if (ad_len & 0xf) |
| + poly1305_update(&poly1305_state, pad0, 0x10 - (ad_len & 0xf)); |
| + } |
| + |
| + flags = SG_MITER_TO_SG; |
| + if (!preemptible()) |
| + flags |= SG_MITER_ATOMIC; |
| + |
| + sg_miter_start(&miter, src, sg_nents(src), flags); |
| + |
| + for (sl = src_len; sl > 0 && sg_miter_next(&miter); sl -= miter.length) { |
| + u8 *addr = miter.addr; |
| + size_t length = min_t(size_t, sl, miter.length); |
| + |
| + if (!encrypt) |
| + poly1305_update(&poly1305_state, addr, length); |
| + |
| + if (unlikely(partial)) { |
| + size_t l = min(length, CHACHA_BLOCK_SIZE - partial); |
| + |
| + crypto_xor(addr, b.chacha_stream + partial, l); |
| + partial = (partial + l) & (CHACHA_BLOCK_SIZE - 1); |
| + |
| + addr += l; |
| + length -= l; |
| + } |
| + |
| + if (likely(length >= CHACHA_BLOCK_SIZE || length == sl)) { |
| + size_t l = length; |
| + |
| + if (unlikely(length < sl)) |
| + l &= ~(CHACHA_BLOCK_SIZE - 1); |
| + chacha_crypt(chacha_state, addr, addr, l, 20); |
| + addr += l; |
| + length -= l; |
| + } |
| + |
| + if (unlikely(length > 0)) { |
| + chacha_crypt(chacha_state, b.chacha_stream, pad0, |
| + CHACHA_BLOCK_SIZE, 20); |
| + crypto_xor(addr, b.chacha_stream, length); |
| + partial = length; |
| + } |
| + |
| + if (encrypt) |
| + poly1305_update(&poly1305_state, miter.addr, |
| + min_t(size_t, sl, miter.length)); |
| + } |
| + |
| + if (src_len & 0xf) |
| + poly1305_update(&poly1305_state, pad0, 0x10 - (src_len & 0xf)); |
| + |
| + b.lens[0] = cpu_to_le64(ad_len); |
| + b.lens[1] = cpu_to_le64(src_len); |
| + poly1305_update(&poly1305_state, (u8 *)b.lens, sizeof(b.lens)); |
| + |
| + if (likely(sl <= -POLY1305_DIGEST_SIZE)) { |
| + if (encrypt) { |
| + poly1305_final(&poly1305_state, |
| + miter.addr + miter.length + sl); |
| + ret = true; |
| + } else { |
| + poly1305_final(&poly1305_state, b.mac[0]); |
| + ret = !crypto_memneq(b.mac[0], |
| + miter.addr + miter.length + sl, |
| + POLY1305_DIGEST_SIZE); |
| + } |
| + } |
| + |
| + sg_miter_stop(&miter); |
| + |
| + if (unlikely(sl > -POLY1305_DIGEST_SIZE)) { |
| + poly1305_final(&poly1305_state, b.mac[1]); |
| + scatterwalk_map_and_copy(b.mac[encrypt], src, src_len, |
| + sizeof(b.mac[1]), encrypt); |
| + ret = encrypt || |
| + !crypto_memneq(b.mac[0], b.mac[1], POLY1305_DIGEST_SIZE); |
| + } |
| + |
| + memzero_explicit(chacha_state, sizeof(chacha_state)); |
| + memzero_explicit(&b, sizeof(b)); |
| + |
| + return ret; |
| +} |
| + |
| +bool chacha20poly1305_encrypt_sg_inplace(struct scatterlist *src, size_t src_len, |
| + const u8 *ad, const size_t ad_len, |
| + const u64 nonce, |
| + const u8 key[CHACHA20POLY1305_KEY_SIZE]) |
| +{ |
| + return chacha20poly1305_crypt_sg_inplace(src, src_len, ad, ad_len, |
| + nonce, key, 1); |
| +} |
| +EXPORT_SYMBOL(chacha20poly1305_encrypt_sg_inplace); |
| + |
| +bool chacha20poly1305_decrypt_sg_inplace(struct scatterlist *src, size_t src_len, |
| + const u8 *ad, const size_t ad_len, |
| + const u64 nonce, |
| + const u8 key[CHACHA20POLY1305_KEY_SIZE]) |
| +{ |
| + if (unlikely(src_len < POLY1305_DIGEST_SIZE)) |
| + return false; |
| + |
| + return chacha20poly1305_crypt_sg_inplace(src, |
| + src_len - POLY1305_DIGEST_SIZE, |
| + ad, ad_len, nonce, key, 0); |
| +} |
| +EXPORT_SYMBOL(chacha20poly1305_decrypt_sg_inplace); |
| + |
| static int __init mod_init(void) |
| { |
| if (!IS_ENABLED(CONFIG_CRYPTO_MANAGER_DISABLE_TESTS) && |