/ src / test / fuzz / crypto_chacha20poly1305.cpp
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  }