Branch data Line data Source code
1 : : // Copyright (c) 2023 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 : :
7 : : #include <crypto/common.h>
8 : : #include <crypto/chacha20.h>
9 : : #include <crypto/poly1305.h>
10 : : #include <span.h>
11 : : #include <support/cleanse.h>
12 : :
13 : : #include <assert.h>
14 : : #include <cstddef>
15 : :
16 : 670 : AEADChaCha20Poly1305::AEADChaCha20Poly1305(Span<const std::byte> key) noexcept : m_chacha20(key)
17 : : {
18 [ + - ]: 670 : assert(key.size() == KEYLEN);
19 : 670 : }
20 : :
21 : 0 : void AEADChaCha20Poly1305::SetKey(Span<const std::byte> key) noexcept
22 : : {
23 [ # # ]: 0 : assert(key.size() == KEYLEN);
24 : 0 : m_chacha20.SetKey(key);
25 : 0 : }
26 : :
27 : : namespace {
28 : :
29 : : #ifndef HAVE_TIMINGSAFE_BCMP
30 : : #define HAVE_TIMINGSAFE_BCMP
31 : :
32 : 0 : int timingsafe_bcmp(const unsigned char* b1, const unsigned char* b2, size_t n) noexcept
33 : : {
34 : 0 : const unsigned char *p1 = b1, *p2 = b2;
35 : 0 : int ret = 0;
36 [ # # ]: 0 : for (; n > 0; n--)
37 : 0 : ret |= *p1++ ^ *p2++;
38 : 0 : return (ret != 0);
39 : : }
40 : :
41 : : #endif
42 : :
43 : : /** Compute poly1305 tag. chacha20 must be set to the right nonce, block 0. Will be at block 1 after. */
44 : 360 : void ComputeTag(ChaCha20& chacha20, Span<const std::byte> aad, Span<const std::byte> cipher, Span<std::byte> tag) noexcept
45 : : {
46 : : static const std::byte PADDING[16] = {{}};
47 : :
48 : : // Get block of keystream (use a full 64 byte buffer to avoid the need for chacha20's own buffering).
49 : : std::byte first_block[ChaCha20Aligned::BLOCKLEN];
50 : 360 : chacha20.Keystream(first_block);
51 : :
52 : : // Use the first 32 bytes of the first keystream block as poly1305 key.
53 : 360 : Poly1305 poly1305{Span{first_block}.first(Poly1305::KEYLEN)};
54 : :
55 : : // Compute tag:
56 : : // - Process the padded AAD with Poly1305.
57 : 360 : const unsigned aad_padding_length = (16 - (aad.size() % 16)) % 16;
58 : 360 : poly1305.Update(aad).Update(Span{PADDING}.first(aad_padding_length));
59 : : // - Process the padded ciphertext with Poly1305.
60 : 360 : const unsigned cipher_padding_length = (16 - (cipher.size() % 16)) % 16;
61 : 360 : poly1305.Update(cipher).Update(Span{PADDING}.first(cipher_padding_length));
62 : : // - Process the AAD and plaintext length with Poly1305.
63 : : std::byte length_desc[Poly1305::TAGLEN];
64 [ + - + - ]: 360 : WriteLE64(UCharCast(length_desc), aad.size());
65 [ + - + - ]: 360 : WriteLE64(UCharCast(length_desc + 8), cipher.size());
66 : 360 : poly1305.Update(length_desc);
67 : :
68 : : // Output tag.
69 : 360 : poly1305.Finalize(tag);
70 : 360 : }
71 : :
72 : : } // namespace
73 : :
74 : 360 : void AEADChaCha20Poly1305::Encrypt(Span<const std::byte> plain1, Span<const std::byte> plain2, Span<const std::byte> aad, Nonce96 nonce, Span<std::byte> cipher) noexcept
75 : : {
76 [ + - ]: 360 : assert(cipher.size() == plain1.size() + plain2.size() + EXPANSION);
77 : :
78 : : // Encrypt using ChaCha20 (starting at block 1).
79 : 360 : m_chacha20.Seek(nonce, 1);
80 : 360 : m_chacha20.Crypt(plain1, cipher.first(plain1.size()));
81 : 360 : m_chacha20.Crypt(plain2, cipher.subspan(plain1.size()).first(plain2.size()));
82 : :
83 : : // Seek to block 0, and compute tag using key drawn from there.
84 : 360 : m_chacha20.Seek(nonce, 0);
85 : 360 : ComputeTag(m_chacha20, aad, cipher.first(cipher.size() - EXPANSION), cipher.last(EXPANSION));
86 : 360 : }
87 : :
88 : 0 : bool AEADChaCha20Poly1305::Decrypt(Span<const std::byte> cipher, Span<const std::byte> aad, Nonce96 nonce, Span<std::byte> plain1, Span<std::byte> plain2) noexcept
89 : : {
90 [ # # ]: 0 : assert(cipher.size() == plain1.size() + plain2.size() + EXPANSION);
91 : :
92 : : // Verify tag (using key drawn from block 0).
93 : 0 : m_chacha20.Seek(nonce, 0);
94 : : std::byte expected_tag[EXPANSION];
95 : 0 : ComputeTag(m_chacha20, aad, cipher.first(cipher.size() - EXPANSION), expected_tag);
96 [ # # # # : 0 : if (timingsafe_bcmp(UCharCast(expected_tag), UCharCast(cipher.last(EXPANSION).data()), EXPANSION)) return false;
# # ]
97 : :
98 : : // Decrypt (starting at block 1).
99 : 0 : m_chacha20.Crypt(cipher.first(plain1.size()), plain1);
100 : 0 : m_chacha20.Crypt(cipher.subspan(plain1.size()).first(plain2.size()), plain2);
101 : 0 : return true;
102 : 0 : }
103 : :
104 : 0 : void AEADChaCha20Poly1305::Keystream(Nonce96 nonce, Span<std::byte> keystream) noexcept
105 : : {
106 : : // Skip the first output block, as it's used for generating the poly1305 key.
107 : 0 : m_chacha20.Seek(nonce, 1);
108 : 0 : m_chacha20.Keystream(keystream);
109 : 0 : }
110 : :
111 : 360 : void FSChaCha20Poly1305::NextPacket() noexcept
112 : : {
113 [ + - ]: 360 : if (++m_packet_counter == m_rekey_interval) {
114 : : // Generate a full block of keystream, to avoid needing the ChaCha20 buffer, even though
115 : : // we only need KEYLEN (32) bytes.
116 : : std::byte one_block[ChaCha20Aligned::BLOCKLEN];
117 [ # # ]: 0 : m_aead.Keystream({0xFFFFFFFF, m_rekey_counter}, one_block);
118 : : // Switch keys.
119 : 0 : m_aead.SetKey(Span{one_block}.first(KEYLEN));
120 : : // Wipe the generated keystream (a copy remains inside m_aead, which will be cleaned up
121 : : // once it cycles again, or is destroyed).
122 [ # # ]: 0 : memory_cleanse(one_block, sizeof(one_block));
123 : : // Update counters.
124 : 0 : m_packet_counter = 0;
125 : 0 : ++m_rekey_counter;
126 : 0 : }
127 : 360 : }
128 : :
129 : 360 : void FSChaCha20Poly1305::Encrypt(Span<const std::byte> plain1, Span<const std::byte> plain2, Span<const std::byte> aad, Span<std::byte> cipher) noexcept
130 : : {
131 [ + - ]: 360 : m_aead.Encrypt(plain1, plain2, aad, {m_packet_counter, m_rekey_counter}, cipher);
132 : 360 : NextPacket();
133 : 360 : }
134 : :
135 : 0 : bool FSChaCha20Poly1305::Decrypt(Span<const std::byte> cipher, Span<const std::byte> aad, Span<std::byte> plain1, Span<std::byte> plain2) noexcept
136 : : {
137 [ # # ]: 0 : bool ret = m_aead.Decrypt(cipher, aad, {m_packet_counter, m_rekey_counter}, plain1, plain2);
138 : 0 : NextPacket();
139 : 0 : return ret;
140 : : }
|