/root/bitcoin/src/test/fuzz/crypto_chacha20poly1305.cpp
Line | Count | Source (jump to first uncovered line) |
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 | | |
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 | 0 | { |
18 | 0 | for (int i = 0; i < rekey_interval; ++i) { |
19 | 0 | std::byte dummy_tag[FSChaCha20Poly1305::EXPANSION] = {{}}; |
20 | 0 | if (encrypt) { |
21 | 0 | aead.Encrypt(Span{dummy_tag}.first(0), Span{dummy_tag}.first(0), dummy_tag); |
22 | 0 | } else { |
23 | 0 | aead.Decrypt(dummy_tag, Span{dummy_tag}.first(0), Span{dummy_tag}.first(0)); |
24 | 0 | } |
25 | 0 | } |
26 | 0 | } |
27 | | |
28 | | FUZZ_TARGET(crypto_aeadchacha20poly1305) |
29 | 0 | { |
30 | 0 | FuzzedDataProvider provider{buffer.data(), buffer.size()}; |
31 | |
|
32 | 0 | auto key = provider.ConsumeBytes<std::byte>(32); |
33 | 0 | key.resize(32); |
34 | 0 | 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 | 0 | InsecureRandomContext rng(provider.ConsumeIntegral<uint64_t>()); |
41 | |
|
42 | 0 | LIMITED_WHILE(provider.ConsumeBool(), 100) |
43 | 0 | { |
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 | 0 | unsigned mode = provider.ConsumeIntegral<uint8_t>(); |
50 | 0 | bool use_splits = mode & 1; |
51 | 0 | bool damage = mode & 4; |
52 | 0 | unsigned aad_length_bits = 3 * ((mode >> 3) & 3); |
53 | 0 | unsigned aad_length = provider.ConsumeIntegralInRange<unsigned>(0, (1 << aad_length_bits) - 1); |
54 | 0 | unsigned length_bits = 2 * ((mode >> 5) & 7); |
55 | 0 | unsigned length = provider.ConsumeIntegralInRange<unsigned>(0, (1 << length_bits) - 1); |
56 | | // Generate aad and content. |
57 | 0 | auto aad = rng.randbytes<std::byte>(aad_length); |
58 | 0 | auto plain = rng.randbytes<std::byte>(length); |
59 | 0 | std::vector<std::byte> cipher(length + AEADChaCha20Poly1305::EXPANSION); |
60 | | // Generate nonce |
61 | 0 | AEADChaCha20Poly1305::Nonce96 nonce = {(uint32_t)rng(), rng()}; |
62 | |
|
63 | 0 | if (use_splits && length > 0) { |
64 | 0 | size_t split_index = provider.ConsumeIntegralInRange<size_t>(1, length); |
65 | 0 | aead.Encrypt(Span{plain}.first(split_index), Span{plain}.subspan(split_index), aad, nonce, cipher); |
66 | 0 | } else { |
67 | 0 | aead.Encrypt(plain, aad, nonce, cipher); |
68 | 0 | } |
69 | | |
70 | | // Test Keystream output |
71 | 0 | std::vector<std::byte> keystream(length); |
72 | 0 | aead.Keystream(nonce, keystream); |
73 | 0 | for (size_t i = 0; i < length; ++i) { |
74 | 0 | assert((plain[i] ^ keystream[i]) == cipher[i]); |
75 | 0 | } |
76 | | |
77 | 0 | std::vector<std::byte> decrypted_contents(length); |
78 | 0 | bool ok{false}; |
79 | | |
80 | | // damage the key |
81 | 0 | unsigned key_position = provider.ConsumeIntegralInRange<unsigned>(0, 31); |
82 | 0 | std::byte damage_val{(uint8_t)(1U << (key_position & 7))}; |
83 | 0 | std::vector<std::byte> bad_key = key; |
84 | 0 | bad_key[key_position] ^= damage_val; |
85 | |
|
86 | 0 | AEADChaCha20Poly1305 bad_aead(bad_key); |
87 | 0 | ok = bad_aead.Decrypt(cipher, aad, nonce, decrypted_contents); |
88 | 0 | 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 | 0 | if (damage) { |
93 | 0 | unsigned damage_bit = provider.ConsumeIntegralInRange<unsigned>(0, (cipher.size() + aad.size()) * 8U - 1U); |
94 | 0 | unsigned damage_pos = damage_bit >> 3; |
95 | 0 | std::byte damage_val{(uint8_t)(1U << (damage_bit & 7))}; |
96 | 0 | if (damage_pos >= cipher.size()) { |
97 | 0 | aad[damage_pos - cipher.size()] ^= damage_val; |
98 | 0 | } else { |
99 | 0 | cipher[damage_pos] ^= damage_val; |
100 | 0 | } |
101 | 0 | } |
102 | |
|
103 | 0 | if (use_splits && length > 0) { |
104 | 0 | size_t split_index = provider.ConsumeIntegralInRange<size_t>(1, length); |
105 | 0 | ok = aead.Decrypt(cipher, aad, nonce, Span{decrypted_contents}.first(split_index), Span{decrypted_contents}.subspan(split_index)); |
106 | 0 | } else { |
107 | 0 | ok = aead.Decrypt(cipher, aad, nonce, decrypted_contents); |
108 | 0 | } |
109 | | |
110 | | // Decryption *must* fail if the packet was damaged, and succeed if it wasn't. |
111 | 0 | assert(!ok == damage); |
112 | 0 | if (!ok) break; |
113 | 0 | assert(decrypted_contents == plain); |
114 | 0 | } |
115 | 0 | } |
116 | | |
117 | | FUZZ_TARGET(crypto_fschacha20poly1305) |
118 | 0 | { |
119 | 0 | FuzzedDataProvider provider{buffer.data(), buffer.size()}; |
120 | |
|
121 | 0 | uint32_t rekey_interval = provider.ConsumeIntegralInRange<size_t>(32, 512); |
122 | 0 | auto key = provider.ConsumeBytes<std::byte>(32); |
123 | 0 | key.resize(32); |
124 | 0 | FSChaCha20Poly1305 enc_aead(key, rekey_interval); |
125 | 0 | 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 | 0 | InsecureRandomContext rng(provider.ConsumeIntegral<uint64_t>()); |
132 | |
|
133 | 0 | LIMITED_WHILE(provider.ConsumeBool(), 100) |
134 | 0 | { |
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 | 0 | unsigned mode = provider.ConsumeIntegral<uint8_t>(); |
141 | 0 | bool use_splits = mode & 1; |
142 | 0 | bool damage = mode & 4; |
143 | 0 | unsigned aad_length_bits = 3 * ((mode >> 3) & 3); |
144 | 0 | unsigned aad_length = provider.ConsumeIntegralInRange<unsigned>(0, (1 << aad_length_bits) - 1); |
145 | 0 | unsigned length_bits = 2 * ((mode >> 5) & 7); |
146 | 0 | unsigned length = provider.ConsumeIntegralInRange<unsigned>(0, (1 << length_bits) - 1); |
147 | | // Generate aad and content. |
148 | 0 | auto aad = rng.randbytes<std::byte>(aad_length); |
149 | 0 | auto plain = rng.randbytes<std::byte>(length); |
150 | 0 | std::vector<std::byte> cipher(length + FSChaCha20Poly1305::EXPANSION); |
151 | |
|
152 | 0 | crypt_till_rekey(enc_aead, rekey_interval, true); |
153 | 0 | if (use_splits && length > 0) { |
154 | 0 | size_t split_index = provider.ConsumeIntegralInRange<size_t>(1, length); |
155 | 0 | enc_aead.Encrypt(Span{plain}.first(split_index), Span{plain}.subspan(split_index), aad, cipher); |
156 | 0 | } else { |
157 | 0 | enc_aead.Encrypt(plain, aad, cipher); |
158 | 0 | } |
159 | |
|
160 | 0 | std::vector<std::byte> decrypted_contents(length); |
161 | 0 | bool ok{false}; |
162 | | |
163 | | // damage the key |
164 | 0 | unsigned key_position = provider.ConsumeIntegralInRange<unsigned>(0, 31); |
165 | 0 | std::byte damage_val{(uint8_t)(1U << (key_position & 7))}; |
166 | 0 | std::vector<std::byte> bad_key = key; |
167 | 0 | bad_key[key_position] ^= damage_val; |
168 | |
|
169 | 0 | FSChaCha20Poly1305 bad_fs_aead(bad_key, rekey_interval); |
170 | 0 | crypt_till_rekey(bad_fs_aead, rekey_interval, false); |
171 | 0 | ok = bad_fs_aead.Decrypt(cipher, aad, decrypted_contents); |
172 | 0 | 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 | 0 | if (damage) { |
177 | 0 | unsigned damage_bit = provider.ConsumeIntegralInRange<unsigned>(0, (cipher.size() + aad.size()) * 8U - 1U); |
178 | 0 | unsigned damage_pos = damage_bit >> 3; |
179 | 0 | std::byte damage_val{(uint8_t)(1U << (damage_bit & 7))}; |
180 | 0 | if (damage_pos >= cipher.size()) { |
181 | 0 | aad[damage_pos - cipher.size()] ^= damage_val; |
182 | 0 | } else { |
183 | 0 | cipher[damage_pos] ^= damage_val; |
184 | 0 | } |
185 | 0 | } |
186 | |
|
187 | 0 | crypt_till_rekey(dec_aead, rekey_interval, false); |
188 | 0 | if (use_splits && length > 0) { |
189 | 0 | size_t split_index = provider.ConsumeIntegralInRange<size_t>(1, length); |
190 | 0 | ok = dec_aead.Decrypt(cipher, aad, Span{decrypted_contents}.first(split_index), Span{decrypted_contents}.subspan(split_index)); |
191 | 0 | } else { |
192 | 0 | ok = dec_aead.Decrypt(cipher, aad, decrypted_contents); |
193 | 0 | } |
194 | | |
195 | | // Decryption *must* fail if the packet was damaged, and succeed if it wasn't. |
196 | 0 | assert(!ok == damage); |
197 | 0 | if (!ok) break; |
198 | 0 | assert(decrypted_contents == plain); |
199 | 0 | } |
200 | 0 | } |