diff options
author | Pieter Wuille <pieter@wuille.net> | 2022-06-13 18:17:28 -0400 |
---|---|---|
committer | Pieter Wuille <pieter@wuille.net> | 2023-01-30 18:12:21 -0500 |
commit | 38eaece67b1bc37b2f502348c5d7537480a34346 (patch) | |
tree | 61e7d0ea983a7bfac11af7b5d3bb0e67cdb34a7e /src/test | |
parent | 5f05b27841af0bed1b6e7de5f46ffe33e5919e4d (diff) |
Add fuzz test for testing that ChaCha20 works as a stream
Diffstat (limited to 'src/test')
-rw-r--r-- | src/test/fuzz/crypto_chacha20.cpp | 108 |
1 files changed, 108 insertions, 0 deletions
diff --git a/src/test/fuzz/crypto_chacha20.cpp b/src/test/fuzz/crypto_chacha20.cpp index a109099394..f1e239bcc8 100644 --- a/src/test/fuzz/crypto_chacha20.cpp +++ b/src/test/fuzz/crypto_chacha20.cpp @@ -6,6 +6,7 @@ #include <test/fuzz/FuzzedDataProvider.h> #include <test/fuzz/fuzz.h> #include <test/fuzz/util.h> +#include <test/util/xoroshiro128plusplus.h> #include <cstdint> #include <vector> @@ -43,3 +44,110 @@ FUZZ_TARGET(crypto_chacha20) }); } } + +namespace +{ + +/** Fuzzer that invokes ChaCha20::Crypt() or ChaCha20::Keystream multiple times: + once for a large block at once, and then the same data in chunks, comparing + the outcome. + + If UseCrypt, seeded Xoroshiro128++ output is used as input to Crypt(). + If not, Keystream() is used directly, or sequences of 0x00 are encrypted. +*/ +template<bool UseCrypt> +void ChaCha20SplitFuzz(FuzzedDataProvider& provider) +{ + // Determine key, iv, start position, length. + unsigned char key[32] = {0}; + auto key_bytes = provider.ConsumeBytes<unsigned char>(32); + std::copy(key_bytes.begin(), key_bytes.end(), key); + uint64_t iv = provider.ConsumeIntegral<uint64_t>(); + uint64_t total_bytes = provider.ConsumeIntegralInRange<uint64_t>(0, 1000000); + /* ~x = 2^64 - 1 - x, so ~(total_bytes >> 6) is the maximal seek position. */ + uint64_t seek = provider.ConsumeIntegralInRange<uint64_t>(0, ~(total_bytes >> 6)); + + // Initialize two ChaCha20 ciphers, with the same key/iv/position. + ChaCha20 crypt1(key, 32); + ChaCha20 crypt2(key, 32); + crypt1.SetIV(iv); + crypt1.Seek64(seek); + crypt2.SetIV(iv); + crypt2.Seek64(seek); + + // Construct vectors with data. + std::vector<unsigned char> data1, data2; + data1.resize(total_bytes); + data2.resize(total_bytes); + + // If using Crypt(), initialize data1 and data2 with the same Xoroshiro128++ based + // stream. + if constexpr (UseCrypt) { + uint64_t seed = provider.ConsumeIntegral<uint64_t>(); + XoRoShiRo128PlusPlus rng(seed); + uint64_t bytes = 0; + while (bytes < (total_bytes & ~uint64_t{7})) { + uint64_t val = rng(); + WriteLE64(data1.data() + bytes, val); + WriteLE64(data2.data() + bytes, val); + bytes += 8; + } + if (bytes < total_bytes) { + unsigned char valbytes[8]; + uint64_t val = rng(); + WriteLE64(valbytes, val); + std::copy(valbytes, valbytes + (total_bytes - bytes), data1.data() + bytes); + std::copy(valbytes, valbytes + (total_bytes - bytes), data2.data() + bytes); + } + } + + // Whether UseCrypt is used or not, the two byte arrays must match. + assert(data1 == data2); + + // Encrypt data1, the whole array at once. + if constexpr (UseCrypt) { + crypt1.Crypt(data1.data(), data1.data(), total_bytes); + } else { + crypt1.Keystream(data1.data(), total_bytes); + } + + // Encrypt data2, in at most 256 chunks. + uint64_t bytes2 = 0; + int iter = 0; + while (true) { + bool is_last = (iter == 255) || (bytes2 == total_bytes) || provider.ConsumeBool(); + ++iter; + // Determine how many bytes to encrypt in this chunk: a fuzzer-determined + // amount for all but the last chunk (which processes all remaining bytes). + uint64_t now = is_last ? total_bytes - bytes2 : + provider.ConsumeIntegralInRange<uint64_t>(0, total_bytes - bytes2); + // For each chunk, consider using Crypt() even when UseCrypt is false. + // This tests that Keystream() has the same behavior as Crypt() applied + // to 0x00 input bytes. + if (UseCrypt || provider.ConsumeBool()) { + crypt2.Crypt(data2.data() + bytes2, data2.data() + bytes2, now); + } else { + crypt2.Keystream(data2.data() + bytes2, now); + } + bytes2 += now; + if (is_last) break; + } + // We should have processed everything now. + assert(bytes2 == total_bytes); + // And the result should match. + assert(data1 == data2); +} + +} // namespace + +FUZZ_TARGET(chacha20_split_crypt) +{ + FuzzedDataProvider provider{buffer.data(), buffer.size()}; + ChaCha20SplitFuzz<true>(provider); +} + +FUZZ_TARGET(chacha20_split_keystream) +{ + FuzzedDataProvider provider{buffer.data(), buffer.size()}; + ChaCha20SplitFuzz<false>(provider); +} |