crypto_chacha20poly1305.cpp
1 // Copyright (c) 2020-present 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/chacha20poly1305.h> 6 #include <random.h> 7 #include <span.h> 8 #include <test/fuzz/FuzzedDataProvider.h> 9 #include <test/fuzz/fuzz.h> 10 #include <test/fuzz/util.h> 11 12 #include <cstddef> 13 #include <cstdint> 14 #include <vector> 15 16 constexpr static inline void crypt_till_rekey(FSChaCha20Poly1305& aead, int rekey_interval, bool encrypt) 17 { 18 for (int i = 0; i < rekey_interval; ++i) { 19 std::byte dummy_tag[FSChaCha20Poly1305::EXPANSION] = {{}}; 20 if (encrypt) { 21 aead.Encrypt(std::span{dummy_tag}.first(0), std::span{dummy_tag}.first(0), dummy_tag); 22 } else { 23 aead.Decrypt(dummy_tag, std::span{dummy_tag}.first(0), std::span{dummy_tag}.first(0)); 24 } 25 } 26 } 27 28 FUZZ_TARGET(crypto_aeadchacha20poly1305) 29 { 30 FuzzedDataProvider provider{buffer.data(), buffer.size()}; 31 32 auto key = provider.ConsumeBytes<std::byte>(32); 33 key.resize(32); 34 AEADChaCha20Poly1305 aead(key); 35 36 // Initialize RNG deterministically, to generate contents and AAD. We assume that there are no 37 // (potentially buggy) edge cases triggered by specific values of contents/AAD, so we can avoid 38 // reading the actual data for those from the fuzzer input (which would need large amounts of 39 // data). 40 InsecureRandomContext rng(provider.ConsumeIntegral<uint64_t>()); 41 42 LIMITED_WHILE(provider.ConsumeBool(), 100) 43 { 44 // Mode: 45 // - Bit 0: whether to use single-plain Encrypt/Decrypt; otherwise use a split at prefix. 46 // - Bit 2: whether this ciphertext will be corrupted (making it the last sent one) 47 // - Bit 3-4: controls the maximum aad length (max 511 bytes) 48 // - Bit 5-7: controls the maximum content length (max 16383 bytes, for performance reasons) 49 unsigned mode = provider.ConsumeIntegral<uint8_t>(); 50 bool use_splits = mode & 1; 51 bool damage = mode & 4; 52 unsigned aad_length_bits = 3 * ((mode >> 3) & 3); 53 unsigned aad_length = provider.ConsumeIntegralInRange<unsigned>(0, (1 << aad_length_bits) - 1); 54 unsigned length_bits = 2 * ((mode >> 5) & 7); 55 unsigned length = provider.ConsumeIntegralInRange<unsigned>(0, (1 << length_bits) - 1); 56 // Generate aad and content. 57 auto aad = rng.randbytes<std::byte>(aad_length); 58 auto plain = rng.randbytes<std::byte>(length); 59 std::vector<std::byte> cipher(length + AEADChaCha20Poly1305::EXPANSION); 60 // Generate nonce 61 AEADChaCha20Poly1305::Nonce96 nonce = {(uint32_t)rng(), rng()}; 62 63 if (use_splits && length > 0) { 64 size_t split_index = provider.ConsumeIntegralInRange<size_t>(1, length); 65 aead.Encrypt(std::span{plain}.first(split_index), std::span{plain}.subspan(split_index), aad, nonce, cipher); 66 } else { 67 aead.Encrypt(plain, aad, nonce, cipher); 68 } 69 70 // Test Keystream output 71 std::vector<std::byte> keystream(length); 72 aead.Keystream(nonce, keystream); 73 for (size_t i = 0; i < length; ++i) { 74 assert((plain[i] ^ keystream[i]) == cipher[i]); 75 } 76 77 std::vector<std::byte> decrypted_contents(length); 78 bool ok{false}; 79 80 // damage the key 81 unsigned key_position = provider.ConsumeIntegralInRange<unsigned>(0, 31); 82 std::byte damage_val{(uint8_t)(1U << (key_position & 7))}; 83 std::vector<std::byte> bad_key = key; 84 bad_key[key_position] ^= damage_val; 85 86 AEADChaCha20Poly1305 bad_aead(bad_key); 87 ok = bad_aead.Decrypt(cipher, aad, nonce, decrypted_contents); 88 assert(!ok); 89 90 // Optionally damage 1 bit in either the cipher (corresponding to a change in transit) 91 // or the aad (to make sure that decryption will fail if the AAD mismatches). 92 if (damage) { 93 unsigned damage_bit = provider.ConsumeIntegralInRange<unsigned>(0, (cipher.size() + aad.size()) * 8U - 1U); 94 unsigned damage_pos = damage_bit >> 3; 95 std::byte damage_val{(uint8_t)(1U << (damage_bit & 7))}; 96 if (damage_pos >= cipher.size()) { 97 aad[damage_pos - cipher.size()] ^= damage_val; 98 } else { 99 cipher[damage_pos] ^= damage_val; 100 } 101 } 102 103 if (use_splits && length > 0) { 104 size_t split_index = provider.ConsumeIntegralInRange<size_t>(1, length); 105 ok = aead.Decrypt(cipher, aad, nonce, std::span{decrypted_contents}.first(split_index), std::span{decrypted_contents}.subspan(split_index)); 106 } else { 107 ok = aead.Decrypt(cipher, aad, nonce, decrypted_contents); 108 } 109 110 // Decryption *must* fail if the packet was damaged, and succeed if it wasn't. 111 assert(!ok == damage); 112 if (!ok) break; 113 assert(decrypted_contents == plain); 114 } 115 } 116 117 FUZZ_TARGET(crypto_fschacha20poly1305) 118 { 119 FuzzedDataProvider provider{buffer.data(), buffer.size()}; 120 121 uint32_t rekey_interval = provider.ConsumeIntegralInRange<size_t>(32, 512); 122 auto key = provider.ConsumeBytes<std::byte>(32); 123 key.resize(32); 124 FSChaCha20Poly1305 enc_aead(key, rekey_interval); 125 FSChaCha20Poly1305 dec_aead(key, rekey_interval); 126 127 // Initialize RNG deterministically, to generate contents and AAD. We assume that there are no 128 // (potentially buggy) edge cases triggered by specific values of contents/AAD, so we can avoid 129 // reading the actual data for those from the fuzzer input (which would need large amounts of 130 // data). 131 InsecureRandomContext rng(provider.ConsumeIntegral<uint64_t>()); 132 133 LIMITED_WHILE(provider.ConsumeBool(), 100) 134 { 135 // Mode: 136 // - Bit 0: whether to use single-plain Encrypt/Decrypt; otherwise use a split at prefix. 137 // - Bit 2: whether this ciphertext will be corrupted (making it the last sent one) 138 // - Bit 3-4: controls the maximum aad length (max 511 bytes) 139 // - Bit 5-7: controls the maximum content length (max 16383 bytes, for performance reasons) 140 unsigned mode = provider.ConsumeIntegral<uint8_t>(); 141 bool use_splits = mode & 1; 142 bool damage = mode & 4; 143 unsigned aad_length_bits = 3 * ((mode >> 3) & 3); 144 unsigned aad_length = provider.ConsumeIntegralInRange<unsigned>(0, (1 << aad_length_bits) - 1); 145 unsigned length_bits = 2 * ((mode >> 5) & 7); 146 unsigned length = provider.ConsumeIntegralInRange<unsigned>(0, (1 << length_bits) - 1); 147 // Generate aad and content. 148 auto aad = rng.randbytes<std::byte>(aad_length); 149 auto plain = rng.randbytes<std::byte>(length); 150 std::vector<std::byte> cipher(length + FSChaCha20Poly1305::EXPANSION); 151 152 crypt_till_rekey(enc_aead, rekey_interval, true); 153 if (use_splits && length > 0) { 154 size_t split_index = provider.ConsumeIntegralInRange<size_t>(1, length); 155 enc_aead.Encrypt(std::span{plain}.first(split_index), std::span{plain}.subspan(split_index), aad, cipher); 156 } else { 157 enc_aead.Encrypt(plain, aad, cipher); 158 } 159 160 std::vector<std::byte> decrypted_contents(length); 161 bool ok{false}; 162 163 // damage the key 164 unsigned key_position = provider.ConsumeIntegralInRange<unsigned>(0, 31); 165 std::byte damage_val{(uint8_t)(1U << (key_position & 7))}; 166 std::vector<std::byte> bad_key = key; 167 bad_key[key_position] ^= damage_val; 168 169 FSChaCha20Poly1305 bad_fs_aead(bad_key, rekey_interval); 170 crypt_till_rekey(bad_fs_aead, rekey_interval, false); 171 ok = bad_fs_aead.Decrypt(cipher, aad, decrypted_contents); 172 assert(!ok); 173 174 // Optionally damage 1 bit in either the cipher (corresponding to a change in transit) 175 // or the aad (to make sure that decryption will fail if the AAD mismatches). 176 if (damage) { 177 unsigned damage_bit = provider.ConsumeIntegralInRange<unsigned>(0, (cipher.size() + aad.size()) * 8U - 1U); 178 unsigned damage_pos = damage_bit >> 3; 179 std::byte damage_val{(uint8_t)(1U << (damage_bit & 7))}; 180 if (damage_pos >= cipher.size()) { 181 aad[damage_pos - cipher.size()] ^= damage_val; 182 } else { 183 cipher[damage_pos] ^= damage_val; 184 } 185 } 186 187 crypt_till_rekey(dec_aead, rekey_interval, false); 188 if (use_splits && length > 0) { 189 size_t split_index = provider.ConsumeIntegralInRange<size_t>(1, length); 190 ok = dec_aead.Decrypt(cipher, aad, std::span{decrypted_contents}.first(split_index), std::span{decrypted_contents}.subspan(split_index)); 191 } else { 192 ok = dec_aead.Decrypt(cipher, aad, decrypted_contents); 193 } 194 195 // Decryption *must* fail if the packet was damaged, and succeed if it wasn't. 196 assert(!ok == damage); 197 if (!ok) break; 198 assert(decrypted_contents == plain); 199 } 200 }