Bitcoin Core 31.99.0
P2P Digital Currency
crypto_chacha20poly1305.cpp
Go to the documentation of this file.
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
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
16constexpr 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
28FUZZ_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).
41
43 // Mode:
44 // - Bit 0: whether to use single-plain Encrypt/Decrypt; otherwise use a split at prefix.
45 // - Bit 2: whether this ciphertext will be corrupted (making it the last sent one)
46 // - Bit 3-4: controls the maximum aad length (max 511 bytes)
47 // - Bit 5-7: controls the maximum content length (max 16383 bytes, for performance reasons)
48 unsigned mode = provider.ConsumeIntegral<uint8_t>();
49 bool use_splits = mode & 1;
50 bool damage = mode & 4;
51 unsigned aad_length_bits = 3 * ((mode >> 3) & 3);
52 unsigned aad_length = provider.ConsumeIntegralInRange<unsigned>(0, (1 << aad_length_bits) - 1);
53 unsigned length_bits = 2 * ((mode >> 5) & 7);
54 unsigned length = provider.ConsumeIntegralInRange<unsigned>(0, (1 << length_bits) - 1);
55 // Generate aad and content.
56 auto aad = rng.randbytes<std::byte>(aad_length);
57 auto plain = rng.randbytes<std::byte>(length);
58 std::vector<std::byte> cipher(length + AEADChaCha20Poly1305::EXPANSION);
59 // Generate nonce
60 AEADChaCha20Poly1305::Nonce96 nonce = {(uint32_t)rng(), rng()};
61
62 if (use_splits && length > 0) {
63 size_t split_index = provider.ConsumeIntegralInRange<size_t>(1, length);
64 aead.Encrypt(std::span{plain}.first(split_index), std::span{plain}.subspan(split_index), aad, nonce, cipher);
65 } else {
66 aead.Encrypt(plain, aad, nonce, cipher);
67 }
68
69 // Test Keystream output
70 std::vector<std::byte> keystream(length);
71 aead.Keystream(nonce, keystream);
72 for (size_t i = 0; i < length; ++i) {
73 assert((plain[i] ^ keystream[i]) == cipher[i]);
74 }
75
76 std::vector<std::byte> decrypted_contents(length);
77 bool ok{false};
78
79 // damage the key
80 unsigned key_position = provider.ConsumeIntegralInRange<unsigned>(0, 31);
81 std::byte damage_val{(uint8_t)(1U << (key_position & 7))};
82 std::vector<std::byte> bad_key = key;
83 bad_key[key_position] ^= damage_val;
84
85 AEADChaCha20Poly1305 bad_aead(bad_key);
86 ok = bad_aead.Decrypt(cipher, aad, nonce, decrypted_contents);
87 assert(!ok);
88
89 // Optionally damage 1 bit in either the cipher (corresponding to a change in transit)
90 // or the aad (to make sure that decryption will fail if the AAD mismatches).
91 if (damage) {
92 unsigned damage_bit = provider.ConsumeIntegralInRange<unsigned>(0, (cipher.size() + aad.size()) * 8U - 1U);
93 unsigned damage_pos = damage_bit >> 3;
94 std::byte damage_val{(uint8_t)(1U << (damage_bit & 7))};
95 if (damage_pos >= cipher.size()) {
96 aad[damage_pos - cipher.size()] ^= damage_val;
97 } else {
98 cipher[damage_pos] ^= damage_val;
99 }
100 }
101
102 if (use_splits && length > 0) {
103 size_t split_index = provider.ConsumeIntegralInRange<size_t>(1, length);
104 ok = aead.Decrypt(cipher, aad, nonce, std::span{decrypted_contents}.first(split_index), std::span{decrypted_contents}.subspan(split_index));
105 } else {
106 ok = aead.Decrypt(cipher, aad, nonce, decrypted_contents);
107 }
108
109 // Decryption *must* fail if the packet was damaged, and succeed if it wasn't.
110 assert(!ok == damage);
111 if (!ok) break;
112 assert(decrypted_contents == plain);
113 }
114}
115
116FUZZ_TARGET(crypto_fschacha20poly1305)
117{
118 FuzzedDataProvider provider{buffer.data(), buffer.size()};
119
120 uint32_t rekey_interval = provider.ConsumeIntegralInRange<size_t>(32, 512);
121 auto key = provider.ConsumeBytes<std::byte>(32);
122 key.resize(32);
123 FSChaCha20Poly1305 enc_aead(key, rekey_interval);
124 FSChaCha20Poly1305 dec_aead(key, rekey_interval);
125
126 // Initialize RNG deterministically, to generate contents and AAD. We assume that there are no
127 // (potentially buggy) edge cases triggered by specific values of contents/AAD, so we can avoid
128 // reading the actual data for those from the fuzzer input (which would need large amounts of
129 // data).
131
133 // Mode:
134 // - Bit 0: whether to use single-plain Encrypt/Decrypt; otherwise use a split at prefix.
135 // - Bit 2: whether this ciphertext will be corrupted (making it the last sent one)
136 // - Bit 3-4: controls the maximum aad length (max 511 bytes)
137 // - Bit 5-7: controls the maximum content length (max 16383 bytes, for performance reasons)
138 unsigned mode = provider.ConsumeIntegral<uint8_t>();
139 bool use_splits = mode & 1;
140 bool damage = mode & 4;
141 unsigned aad_length_bits = 3 * ((mode >> 3) & 3);
142 unsigned aad_length = provider.ConsumeIntegralInRange<unsigned>(0, (1 << aad_length_bits) - 1);
143 unsigned length_bits = 2 * ((mode >> 5) & 7);
144 unsigned length = provider.ConsumeIntegralInRange<unsigned>(0, (1 << length_bits) - 1);
145 // Generate aad and content.
146 auto aad = rng.randbytes<std::byte>(aad_length);
147 auto plain = rng.randbytes<std::byte>(length);
148 std::vector<std::byte> cipher(length + FSChaCha20Poly1305::EXPANSION);
149
150 crypt_till_rekey(enc_aead, rekey_interval, true);
151 if (use_splits && length > 0) {
152 size_t split_index = provider.ConsumeIntegralInRange<size_t>(1, length);
153 enc_aead.Encrypt(std::span{plain}.first(split_index), std::span{plain}.subspan(split_index), aad, cipher);
154 } else {
155 enc_aead.Encrypt(plain, aad, cipher);
156 }
157
158 std::vector<std::byte> decrypted_contents(length);
159 bool ok{false};
160
161 // damage the key
162 unsigned key_position = provider.ConsumeIntegralInRange<unsigned>(0, 31);
163 std::byte damage_val{(uint8_t)(1U << (key_position & 7))};
164 std::vector<std::byte> bad_key = key;
165 bad_key[key_position] ^= damage_val;
166
167 FSChaCha20Poly1305 bad_fs_aead(bad_key, rekey_interval);
168 crypt_till_rekey(bad_fs_aead, rekey_interval, false);
169 ok = bad_fs_aead.Decrypt(cipher, aad, decrypted_contents);
170 assert(!ok);
171
172 // Optionally damage 1 bit in either the cipher (corresponding to a change in transit)
173 // or the aad (to make sure that decryption will fail if the AAD mismatches).
174 if (damage) {
175 unsigned damage_bit = provider.ConsumeIntegralInRange<unsigned>(0, (cipher.size() + aad.size()) * 8U - 1U);
176 unsigned damage_pos = damage_bit >> 3;
177 std::byte damage_val{(uint8_t)(1U << (damage_bit & 7))};
178 if (damage_pos >= cipher.size()) {
179 aad[damage_pos - cipher.size()] ^= damage_val;
180 } else {
181 cipher[damage_pos] ^= damage_val;
182 }
183 }
184
185 crypt_till_rekey(dec_aead, rekey_interval, false);
186 if (use_splits && length > 0) {
187 size_t split_index = provider.ConsumeIntegralInRange<size_t>(1, length);
188 ok = dec_aead.Decrypt(cipher, aad, std::span{decrypted_contents}.first(split_index), std::span{decrypted_contents}.subspan(split_index));
189 } else {
190 ok = dec_aead.Decrypt(cipher, aad, decrypted_contents);
191 }
192
193 // Decryption *must* fail if the packet was damaged, and succeed if it wasn't.
194 assert(!ok == damage);
195 if (!ok) break;
196 assert(decrypted_contents == plain);
197 }
198}
The AEAD_CHACHA20_POLY1305 authenticated encryption algorithm from RFC8439 section 2....
ChaCha20::Nonce96 Nonce96
96-bit nonce type.
void Encrypt(std::span< const std::byte > plain, std::span< const std::byte > aad, Nonce96 nonce, std::span< std::byte > cipher) noexcept
Encrypt a message with a specified 96-bit nonce and aad.
bool Decrypt(std::span< const std::byte > cipher, std::span< const std::byte > aad, Nonce96 nonce, std::span< std::byte > plain) noexcept
Decrypt a message with a specified 96-bit nonce and aad.
static constexpr unsigned EXPANSION
Expansion when encrypting.
void Keystream(Nonce96 nonce, std::span< std::byte > keystream) noexcept
Get a number of keystream bytes from the underlying stream cipher.
Forward-secure wrapper around AEADChaCha20Poly1305.
bool Decrypt(std::span< const std::byte > cipher, std::span< const std::byte > aad, std::span< std::byte > plain) noexcept
Decrypt a message with a specified aad.
void Encrypt(std::span< const std::byte > plain, std::span< const std::byte > aad, std::span< std::byte > cipher) noexcept
Encrypt a message with a specified aad.
static constexpr auto EXPANSION
Expansion when encrypting.
std::vector< T > ConsumeBytes(size_t num_bytes)
T ConsumeIntegralInRange(T min, T max)
xoroshiro128++ PRNG.
Definition: random.h:425
std::vector< B > randbytes(size_t len) noexcept
Generate random bytes.
Definition: random.h:297
LIMITED_WHILE(provider.remaining_bytes(), 10000)
static constexpr void crypt_till_rekey(FSChaCha20Poly1305 &aead, int rekey_interval, bool encrypt)
FUZZ_TARGET(crypto_aeadchacha20poly1305)
unsigned int nonce
FastRandomContext rng
Definition: dbwrapper.cpp:413
FuzzedDataProvider provider
Definition: dbwrapper.cpp:366
assert(!tx.IsCoinBase())