crypto_chacha20poly1305.cpp raw

   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  }
 201