crypto_chacha20.cpp
1 // Copyright (c) 2020-2021 The Bitcoin Core developers 2 // Distributed under the MIT software license, see the accompanying 3 // file COPYING or http://www.opensource.org/licenses/mit-license.php. 4 5 #include <crypto/chacha20.h> 6 #include <test/fuzz/FuzzedDataProvider.h> 7 #include <test/fuzz/fuzz.h> 8 #include <test/fuzz/util.h> 9 #include <test/util/xoroshiro128plusplus.h> 10 11 #include <array> 12 #include <cstddef> 13 #include <cstdint> 14 #include <vector> 15 16 FUZZ_TARGET(crypto_chacha20) 17 { 18 FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()}; 19 20 const auto key = ConsumeFixedLengthByteVector<std::byte>(fuzzed_data_provider, ChaCha20::KEYLEN); 21 ChaCha20 chacha20{key}; 22 23 LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 10000) { 24 CallOneOf( 25 fuzzed_data_provider, 26 [&] { 27 auto key = ConsumeFixedLengthByteVector<std::byte>(fuzzed_data_provider, ChaCha20::KEYLEN); 28 chacha20.SetKey(key); 29 }, 30 [&] { 31 chacha20.Seek( 32 { 33 fuzzed_data_provider.ConsumeIntegral<uint32_t>(), 34 fuzzed_data_provider.ConsumeIntegral<uint64_t>() 35 }, fuzzed_data_provider.ConsumeIntegral<uint32_t>()); 36 }, 37 [&] { 38 std::vector<uint8_t> output(fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, 4096)); 39 chacha20.Keystream(MakeWritableByteSpan(output)); 40 }, 41 [&] { 42 std::vector<std::byte> output(fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, 4096)); 43 const auto input = ConsumeFixedLengthByteVector<std::byte>(fuzzed_data_provider, output.size()); 44 chacha20.Crypt(input, output); 45 }); 46 } 47 } 48 49 namespace 50 { 51 52 /** Fuzzer that invokes ChaCha20::Crypt() or ChaCha20::Keystream multiple times: 53 once for a large block at once, and then the same data in chunks, comparing 54 the outcome. 55 56 If UseCrypt, seeded Xoroshiro128++ output is used as input to Crypt(). 57 If not, Keystream() is used directly, or sequences of 0x00 are encrypted. 58 */ 59 template<bool UseCrypt> 60 void ChaCha20SplitFuzz(FuzzedDataProvider& provider) 61 { 62 // Determine key, iv, start position, length. 63 auto key_bytes = ConsumeFixedLengthByteVector<std::byte>(provider, ChaCha20::KEYLEN); 64 uint64_t iv = provider.ConsumeIntegral<uint64_t>(); 65 uint32_t iv_prefix = provider.ConsumeIntegral<uint32_t>(); 66 uint64_t total_bytes = provider.ConsumeIntegralInRange<uint64_t>(0, 1000000); 67 /* ~x = 2^BITS - 1 - x, so ~(total_bytes >> 6) is the maximal seek position. */ 68 uint32_t seek = provider.ConsumeIntegralInRange<uint32_t>(0, ~(uint32_t)(total_bytes >> 6)); 69 70 // Initialize two ChaCha20 ciphers, with the same key/iv/position. 71 ChaCha20 crypt1(key_bytes); 72 ChaCha20 crypt2(key_bytes); 73 crypt1.Seek({iv_prefix, iv}, seek); 74 crypt2.Seek({iv_prefix, iv}, seek); 75 76 // Construct vectors with data. 77 std::vector<std::byte> data1, data2; 78 data1.resize(total_bytes); 79 data2.resize(total_bytes); 80 81 // If using Crypt(), initialize data1 and data2 with the same Xoroshiro128++ based 82 // stream. 83 if constexpr (UseCrypt) { 84 uint64_t seed = provider.ConsumeIntegral<uint64_t>(); 85 XoRoShiRo128PlusPlus rng(seed); 86 uint64_t bytes = 0; 87 while (bytes < (total_bytes & ~uint64_t{7})) { 88 uint64_t val = rng(); 89 WriteLE64(UCharCast(data1.data() + bytes), val); 90 WriteLE64(UCharCast(data2.data() + bytes), val); 91 bytes += 8; 92 } 93 if (bytes < total_bytes) { 94 std::byte valbytes[8]; 95 uint64_t val = rng(); 96 WriteLE64(UCharCast(valbytes), val); 97 std::copy(valbytes, valbytes + (total_bytes - bytes), data1.data() + bytes); 98 std::copy(valbytes, valbytes + (total_bytes - bytes), data2.data() + bytes); 99 } 100 } 101 102 // Whether UseCrypt is used or not, the two byte arrays must match. 103 assert(data1 == data2); 104 105 // Encrypt data1, the whole array at once. 106 if constexpr (UseCrypt) { 107 crypt1.Crypt(data1, data1); 108 } else { 109 crypt1.Keystream(data1); 110 } 111 112 // Encrypt data2, in at most 256 chunks. 113 uint64_t bytes2 = 0; 114 int iter = 0; 115 while (true) { 116 bool is_last = (iter == 255) || (bytes2 == total_bytes) || provider.ConsumeBool(); 117 ++iter; 118 // Determine how many bytes to encrypt in this chunk: a fuzzer-determined 119 // amount for all but the last chunk (which processes all remaining bytes). 120 uint64_t now = is_last ? total_bytes - bytes2 : 121 provider.ConsumeIntegralInRange<uint64_t>(0, total_bytes - bytes2); 122 // For each chunk, consider using Crypt() even when UseCrypt is false. 123 // This tests that Keystream() has the same behavior as Crypt() applied 124 // to 0x00 input bytes. 125 if (UseCrypt || provider.ConsumeBool()) { 126 crypt2.Crypt(Span{data2}.subspan(bytes2, now), Span{data2}.subspan(bytes2, now)); 127 } else { 128 crypt2.Keystream(Span{data2}.subspan(bytes2, now)); 129 } 130 bytes2 += now; 131 if (is_last) break; 132 } 133 // We should have processed everything now. 134 assert(bytes2 == total_bytes); 135 // And the result should match. 136 assert(data1 == data2); 137 } 138 139 } // namespace 140 141 FUZZ_TARGET(chacha20_split_crypt) 142 { 143 FuzzedDataProvider provider{buffer.data(), buffer.size()}; 144 ChaCha20SplitFuzz<true>(provider); 145 } 146 147 FUZZ_TARGET(chacha20_split_keystream) 148 { 149 FuzzedDataProvider provider{buffer.data(), buffer.size()}; 150 ChaCha20SplitFuzz<false>(provider); 151 } 152 153 FUZZ_TARGET(crypto_fschacha20) 154 { 155 FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()}; 156 157 auto key = fuzzed_data_provider.ConsumeBytes<std::byte>(FSChaCha20::KEYLEN); 158 key.resize(FSChaCha20::KEYLEN); 159 160 auto fsc20 = FSChaCha20{key, fuzzed_data_provider.ConsumeIntegralInRange<uint32_t>(1, 1024)}; 161 162 LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 10000) 163 { 164 auto input = fuzzed_data_provider.ConsumeBytes<std::byte>(fuzzed_data_provider.ConsumeIntegralInRange(0, 4096)); 165 std::vector<std::byte> output; 166 output.resize(input.size()); 167 fsc20.Crypt(input, output); 168 } 169 }