Bitcoin Core  28.1.0
P2P Digital Currency
crypto_chacha20poly1305.cpp
Go to the documentation of this file.
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 
6 #include <random.h>
7 #include <span.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(Span{dummy_tag}.first(0), Span{dummy_tag}.first(0), dummy_tag);
22  } else {
23  aead.Decrypt(dummy_tag, Span{dummy_tag}.first(0), 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(), 10000)
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(Span{plain}.first(split_index), 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, Span{decrypted_contents}.first(split_index), 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(), 10000)
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(Span{plain}.first(split_index), 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, Span{decrypted_contents}.first(split_index), 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 }
std::vector< B > randbytes(size_t len) noexcept
Generate random bytes.
Definition: random.h:297
CONSTEXPR_IF_NOT_DEBUG Span< C > first(std::size_t count) const noexcept
Definition: span.h:205
CONSTEXPR_IF_NOT_DEBUG Span< C > subspan(std::size_t offset) const noexcept
Definition: span.h:195
The AEAD_CHACHA20_POLY1305 authenticated encryption algorithm from RFC8439 section 2...
assert(!tx.IsCoinBase())
unsigned int nonce
Definition: miner_tests.cpp:75
void Encrypt(Span< const std::byte > plain, Span< const std::byte > aad, Span< std::byte > cipher) noexcept
Encrypt a message with a specified aad.
Forward-secure wrapper around AEADChaCha20Poly1305.
static constexpr void crypt_till_rekey(FSChaCha20Poly1305 &aead, int rekey_interval, bool encrypt)
#define LIMITED_WHILE(condition, limit)
Can be used to limit a theoretically unbounded loop.
Definition: fuzz.h:22
bool Decrypt(Span< const std::byte > cipher, Span< const std::byte > aad, Nonce96 nonce, Span< std::byte > plain) noexcept
Decrypt a message with a specified 96-bit nonce and aad.
void Encrypt(Span< const std::byte > plain, Span< const std::byte > aad, Nonce96 nonce, Span< std::byte > cipher) noexcept
Encrypt a message with a specified 96-bit nonce and aad.
static constexpr unsigned EXPANSION
Expansion when encrypting.
xoroshiro128++ PRNG.
Definition: random.h:415
std::vector< T > ConsumeBytes(size_t num_bytes)
void Keystream(Nonce96 nonce, Span< std::byte > keystream) noexcept
Get a number of keystream bytes from the underlying stream cipher.
ChaCha20::Nonce96 Nonce96
96-bit nonce type.
static constexpr auto EXPANSION
Expansion when encrypting.
FUZZ_TARGET(crypto_aeadchacha20poly1305)
A Span is an object that can refer to a contiguous sequence of objects.
Definition: solver.h:20
T ConsumeIntegralInRange(T min, T max)
bool Decrypt(Span< const std::byte > cipher, Span< const std::byte > aad, Span< std::byte > plain) noexcept
Decrypt a message with a specified aad.