Coverage Report

Created: 2024-09-19 18:47

/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(), 10000)
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
}