/src/libtorrent/src/pe_crypto.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | |
3 | | Copyright (c) 2007, Un Shyam |
4 | | Copyright (c) 2011, 2014-2019, Arvid Norberg |
5 | | Copyright (c) 2016-2018, Alden Torres |
6 | | Copyright (c) 2016, Andrei Kurushin |
7 | | Copyright (c) 2016, 2018, Steven Siloti |
8 | | All rights reserved. |
9 | | |
10 | | Redistribution and use in source and binary forms, with or without |
11 | | modification, are permitted provided that the following conditions |
12 | | are met: |
13 | | |
14 | | * Redistributions of source code must retain the above copyright |
15 | | notice, this list of conditions and the following disclaimer. |
16 | | * Redistributions in binary form must reproduce the above copyright |
17 | | notice, this list of conditions and the following disclaimer in |
18 | | the documentation and/or other materials provided with the distribution. |
19 | | * Neither the name of the author nor the names of its |
20 | | contributors may be used to endorse or promote products derived |
21 | | from this software without specific prior written permission. |
22 | | |
23 | | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
24 | | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
25 | | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
26 | | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE |
27 | | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
28 | | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
29 | | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
30 | | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
31 | | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
32 | | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
33 | | POSSIBILITY OF SUCH DAMAGE. |
34 | | |
35 | | */ |
36 | | |
37 | | #if !defined TORRENT_DISABLE_ENCRYPTION |
38 | | |
39 | | #include <cstdint> |
40 | | #include <algorithm> |
41 | | #include <random> |
42 | | |
43 | | #include "libtorrent/aux_/disable_warnings_push.hpp" |
44 | | |
45 | | #include <boost/multiprecision/integer.hpp> |
46 | | #include <boost/multiprecision/cpp_int.hpp> |
47 | | |
48 | | #include "libtorrent/aux_/disable_warnings_pop.hpp" |
49 | | |
50 | | #include "libtorrent/random.hpp" |
51 | | #include "libtorrent/aux_/alloca.hpp" |
52 | | #include "libtorrent/pe_crypto.hpp" |
53 | | #include "libtorrent/hasher.hpp" |
54 | | |
55 | | namespace libtorrent { |
56 | | |
57 | | namespace mp = boost::multiprecision; |
58 | | |
59 | | namespace { |
60 | | // TODO: it would be nice to get the literal working |
61 | | key_t const dh_prime |
62 | | ("0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A63A36210000000000090563"); |
63 | | } |
64 | | |
65 | | std::array<char, 96> export_key(key_t const& k) |
66 | 0 | { |
67 | 0 | std::array<char, 96> ret; |
68 | 0 | auto* begin = reinterpret_cast<std::uint8_t*>(ret.data()); |
69 | 0 | std::uint8_t* end = mp::export_bits(k, begin, 8); |
70 | | |
71 | | // TODO: it would be nice to be able to export to a fixed width field, so |
72 | | // we wouldn't have to shift it later |
73 | 0 | if (end < begin + 96) |
74 | 0 | { |
75 | 0 | int const len = int(end - begin); |
76 | | #if defined __GNUC__ && __GNUC__ == 12 |
77 | | #pragma GCC diagnostic push |
78 | | #pragma GCC diagnostic ignored "-Wstringop-overflow" |
79 | | #endif |
80 | 0 | std::memmove(begin + 96 - len, begin, aux::numeric_cast<std::size_t>(len)); |
81 | | #if defined __GNUC__ && __GNUC__ == 12 |
82 | | #pragma GCC diagnostic pop |
83 | | #endif |
84 | 0 | std::memset(begin, 0, aux::numeric_cast<std::size_t>(96 - len)); |
85 | 0 | } |
86 | 0 | return ret; |
87 | 0 | } |
88 | | |
89 | | void rc4_init(const unsigned char* in, std::size_t len, rc4 *state); |
90 | | std::size_t rc4_encrypt(unsigned char *out, std::size_t outlen, rc4 *state); |
91 | | |
92 | | // Set the prime P and the generator, generate local public key |
93 | | dh_key_exchange::dh_key_exchange() |
94 | 0 | { |
95 | 0 | aux::array<std::uint8_t, 96> random_key; |
96 | 0 | aux::random_bytes({reinterpret_cast<char*>(random_key.data()) |
97 | 0 | , static_cast<std::ptrdiff_t>(random_key.size())}); |
98 | | |
99 | | // create local key (random) |
100 | 0 | mp::import_bits(m_dh_local_secret, random_key.begin(), random_key.end()); |
101 | | |
102 | | // key = (2 ^ secret) % prime |
103 | 0 | m_dh_local_key = mp::powm(key_t(2), m_dh_local_secret, dh_prime); |
104 | 0 | } |
105 | | |
106 | | // compute shared secret given remote public key |
107 | | void dh_key_exchange::compute_secret(std::uint8_t const* remote_pubkey) |
108 | 0 | { |
109 | 0 | TORRENT_ASSERT(remote_pubkey); |
110 | 0 | key_t key; |
111 | 0 | mp::import_bits(key, remote_pubkey, remote_pubkey + 96); |
112 | 0 | compute_secret(key); |
113 | 0 | } |
114 | | |
115 | | void dh_key_exchange::compute_secret(key_t const& remote_pubkey) |
116 | 0 | { |
117 | | // shared_secret = (remote_pubkey ^ local_secret) % prime |
118 | 0 | m_dh_shared_secret = mp::powm(remote_pubkey, m_dh_local_secret, dh_prime); |
119 | |
|
120 | 0 | std::array<char, 96> buffer; |
121 | 0 | mp::export_bits(m_dh_shared_secret, reinterpret_cast<std::uint8_t*>(buffer.data()), 8); |
122 | |
|
123 | 0 | static char const req3[4] = {'r', 'e', 'q', '3'}; |
124 | | // calculate the xor mask for the obfuscated hash |
125 | 0 | m_xor_mask = hasher(req3).update(buffer).final(); |
126 | 0 | } |
127 | | |
128 | | std::tuple<int, span<span<char const>>> |
129 | | encryption_handler::encrypt( |
130 | | span<span<char>> iovec) |
131 | 0 | { |
132 | 0 | TORRENT_ASSERT(!m_send_barriers.empty()); |
133 | 0 | TORRENT_ASSERT(m_send_barriers.front().enc_handler); |
134 | |
|
135 | 0 | int to_process = m_send_barriers.front().next; |
136 | |
|
137 | 0 | span<span<char>> bufs; |
138 | 0 | bool need_destruct = false; |
139 | 0 | if (to_process != INT_MAX) |
140 | 0 | { |
141 | 0 | TORRENT_ALLOCA(abufs, span<char>, iovec.size()); |
142 | 0 | bufs = abufs; |
143 | 0 | need_destruct = true; |
144 | 0 | int num_bufs = 0; |
145 | 0 | for (int i = 0; to_process > 0 && i < iovec.size(); ++i) |
146 | 0 | { |
147 | 0 | ++num_bufs; |
148 | 0 | int const size = int(iovec[i].size()); |
149 | 0 | if (to_process < size) |
150 | 0 | { |
151 | 0 | new (&bufs[i]) span<char>( |
152 | 0 | iovec[i].data(), to_process); |
153 | 0 | to_process = 0; |
154 | 0 | } |
155 | 0 | else |
156 | 0 | { |
157 | 0 | new (&bufs[i]) span<char>(iovec[i]); |
158 | 0 | to_process -= size; |
159 | 0 | } |
160 | 0 | } |
161 | 0 | bufs = bufs.first(num_bufs); |
162 | 0 | } |
163 | 0 | else |
164 | 0 | { |
165 | 0 | bufs = iovec; |
166 | 0 | } |
167 | |
|
168 | 0 | int next_barrier = 0; |
169 | 0 | span<span<char const>> out_iovec; |
170 | 0 | if (!bufs.empty()) |
171 | 0 | { |
172 | 0 | std::tie(next_barrier, out_iovec) |
173 | 0 | = m_send_barriers.front().enc_handler->encrypt(bufs); |
174 | 0 | } |
175 | |
|
176 | 0 | if (m_send_barriers.front().next != INT_MAX) |
177 | 0 | { |
178 | | // to_process holds the difference between the size of the buffers |
179 | | // and the bytes left to the next barrier |
180 | | // if it's zero then pop the barrier |
181 | | // otherwise update the number of bytes remaining to the next barrier |
182 | 0 | if (to_process == 0) |
183 | 0 | { |
184 | 0 | if (m_send_barriers.size() == 1) |
185 | 0 | { |
186 | | // transitioning back to plaintext |
187 | 0 | next_barrier = INT_MAX; |
188 | 0 | } |
189 | 0 | m_send_barriers.pop_front(); |
190 | 0 | } |
191 | 0 | else |
192 | 0 | { |
193 | 0 | m_send_barriers.front().next = to_process; |
194 | 0 | } |
195 | 0 | } |
196 | |
|
197 | 0 | #if TORRENT_USE_ASSERTS |
198 | 0 | if (next_barrier != INT_MAX && next_barrier != 0) |
199 | 0 | { |
200 | 0 | int payload = 0; |
201 | 0 | for (auto buf : bufs) |
202 | 0 | payload += int(buf.size()); |
203 | |
|
204 | 0 | int overhead = 0; |
205 | 0 | for (auto buf : out_iovec) |
206 | 0 | overhead += int(buf.size()); |
207 | 0 | TORRENT_ASSERT(overhead + payload == next_barrier); |
208 | 0 | } |
209 | 0 | #endif |
210 | 0 | if (need_destruct) |
211 | 0 | { |
212 | 0 | for (auto buf : bufs) |
213 | 0 | buf.~span<char>(); |
214 | 0 | } |
215 | 0 | return std::make_tuple(next_barrier, out_iovec); |
216 | 0 | } |
217 | | |
218 | | int encryption_handler::decrypt(aux::crypto_receive_buffer& recv_buffer |
219 | | , std::size_t& bytes_transferred) |
220 | 0 | { |
221 | 0 | TORRENT_ASSERT(!is_recv_plaintext()); |
222 | 0 | int consume = 0; |
223 | 0 | if (recv_buffer.crypto_packet_finished()) |
224 | 0 | { |
225 | 0 | span<char> wr_buf = recv_buffer.mutable_buffer(int(bytes_transferred)); |
226 | 0 | int produce = 0; |
227 | 0 | int packet_size = 0; |
228 | 0 | std::tie(consume, produce, packet_size) = m_dec_handler->decrypt(wr_buf); |
229 | 0 | TORRENT_ASSERT(packet_size || produce); |
230 | 0 | TORRENT_ASSERT(packet_size >= 0); |
231 | 0 | TORRENT_ASSERT(produce >= 0); |
232 | 0 | bytes_transferred = std::size_t(produce); |
233 | 0 | if (packet_size) |
234 | 0 | recv_buffer.crypto_cut(consume, packet_size); |
235 | 0 | } |
236 | 0 | else |
237 | 0 | bytes_transferred = 0; |
238 | 0 | return consume; |
239 | 0 | } |
240 | | |
241 | | bool encryption_handler::switch_send_crypto(std::shared_ptr<crypto_plugin> crypto |
242 | | , int pending_encryption) |
243 | 0 | { |
244 | 0 | bool place_barrier = false; |
245 | 0 | if (!m_send_barriers.empty()) |
246 | 0 | { |
247 | 0 | auto const end = std::prev(m_send_barriers.end()); |
248 | 0 | for (auto b = m_send_barriers.begin(); b != end; ++b) |
249 | 0 | pending_encryption -= b->next; |
250 | 0 | TORRENT_ASSERT(pending_encryption >= 0); |
251 | 0 | m_send_barriers.back().next = pending_encryption; |
252 | 0 | } |
253 | 0 | else if (crypto) |
254 | 0 | place_barrier = true; |
255 | |
|
256 | 0 | if (crypto) |
257 | 0 | m_send_barriers.push_back(barrier(crypto, INT_MAX)); |
258 | |
|
259 | 0 | return place_barrier; |
260 | 0 | } |
261 | | |
262 | | void encryption_handler::switch_recv_crypto(std::shared_ptr<crypto_plugin> crypto |
263 | | , aux::crypto_receive_buffer& recv_buffer) |
264 | 0 | { |
265 | 0 | m_dec_handler = crypto; |
266 | 0 | int packet_size = 0; |
267 | 0 | if (crypto) |
268 | 0 | { |
269 | 0 | int consume = 0; |
270 | 0 | int produce = 0; |
271 | 0 | std::vector<span<char>> wr_buf; |
272 | 0 | std::tie(consume, produce, packet_size) = crypto->decrypt(wr_buf); |
273 | 0 | TORRENT_ASSERT(wr_buf.empty()); |
274 | 0 | TORRENT_ASSERT(consume == 0); |
275 | 0 | TORRENT_ASSERT(produce == 0); |
276 | 0 | } |
277 | 0 | recv_buffer.crypto_reset(packet_size); |
278 | 0 | } |
279 | | |
280 | | rc4_handler::rc4_handler() |
281 | 0 | : m_encrypt(false) |
282 | 0 | , m_decrypt(false) |
283 | 0 | { |
284 | 0 | m_rc4_incoming.x = 0; |
285 | 0 | m_rc4_incoming.y = 0; |
286 | 0 | m_rc4_outgoing.x = 0; |
287 | 0 | m_rc4_outgoing.y = 0; |
288 | 0 | } |
289 | | |
290 | | void rc4_handler::set_incoming_key(span<char const> key) |
291 | 0 | { |
292 | 0 | m_decrypt = true; |
293 | 0 | rc4_init(reinterpret_cast<unsigned char const*>(key.data()) |
294 | 0 | , std::size_t(key.size()), &m_rc4_incoming); |
295 | | // Discard first 1024 bytes |
296 | 0 | char buf[1024]; |
297 | 0 | span<char> vec(buf, sizeof(buf)); |
298 | 0 | decrypt(vec); |
299 | 0 | } |
300 | | |
301 | | void rc4_handler::set_outgoing_key(span<char const> key) |
302 | 0 | { |
303 | 0 | m_encrypt = true; |
304 | 0 | rc4_init(reinterpret_cast<unsigned char const*>(key.data()) |
305 | 0 | , std::size_t(key.size()), &m_rc4_outgoing); |
306 | | // Discard first 1024 bytes |
307 | 0 | char buf[1024]; |
308 | 0 | span<char> vec(buf, sizeof(buf)); |
309 | 0 | encrypt(vec); |
310 | 0 | } |
311 | | |
312 | | std::tuple<int, span<span<char const>>> |
313 | | rc4_handler::encrypt(span<span<char>> bufs) |
314 | 0 | { |
315 | 0 | span<span<char const>> empty; |
316 | 0 | if (!m_encrypt) return std::make_tuple(0, empty); |
317 | 0 | if (bufs.empty()) return std::make_tuple(0, empty); |
318 | | |
319 | 0 | int bytes_processed = 0; |
320 | 0 | for (auto& buf : bufs) |
321 | 0 | { |
322 | 0 | auto* const pos = reinterpret_cast<unsigned char*>(buf.data()); |
323 | 0 | int const len = int(buf.size()); |
324 | |
|
325 | 0 | TORRENT_ASSERT(len >= 0); |
326 | 0 | TORRENT_ASSERT(pos); |
327 | |
|
328 | 0 | bytes_processed += len; |
329 | 0 | rc4_encrypt(pos, std::uint32_t(len), &m_rc4_outgoing); |
330 | 0 | } |
331 | 0 | return std::make_tuple(bytes_processed, empty); |
332 | 0 | } |
333 | | |
334 | | std::tuple<int, int, int> rc4_handler::decrypt(span<span<char>> bufs) |
335 | 0 | { |
336 | 0 | if (!m_decrypt) return std::make_tuple(0, 0, 0); |
337 | | |
338 | 0 | int bytes_processed = 0; |
339 | 0 | for (auto& buf : bufs) |
340 | 0 | { |
341 | 0 | auto* const pos = reinterpret_cast<unsigned char*>(buf.data()); |
342 | 0 | int const len = int(buf.size()); |
343 | |
|
344 | 0 | TORRENT_ASSERT(len >= 0); |
345 | 0 | TORRENT_ASSERT(pos); |
346 | |
|
347 | 0 | bytes_processed += len; |
348 | 0 | rc4_encrypt(pos, std::uint32_t(len), &m_rc4_incoming); |
349 | 0 | } |
350 | 0 | return std::make_tuple(0, bytes_processed, 0); |
351 | 0 | } |
352 | | |
353 | | // All this code is based on libTomCrypt (http://www.libtomcrypt.com/) |
354 | | // this library is public domain and has been specially |
355 | | // tailored for libtorrent by Arvid Norberg |
356 | | |
357 | | void rc4_init(const unsigned char* in, std::size_t len, rc4 *state) |
358 | 0 | { |
359 | 0 | std::size_t const key_size = sizeof(state->buf); |
360 | 0 | aux::array<std::uint8_t, key_size> key; |
361 | 0 | std::uint8_t tmp, *s; |
362 | 0 | int keylen, x, y, j; |
363 | |
|
364 | 0 | TORRENT_ASSERT(state != nullptr); |
365 | 0 | TORRENT_ASSERT(len <= key_size); |
366 | 0 | if (len > key_size) len = key_size; |
367 | |
|
368 | 0 | state->x = 0; |
369 | 0 | while (len--) { |
370 | 0 | state->buf[state->x++] = *in++; |
371 | 0 | } |
372 | | |
373 | | /* extract the key */ |
374 | 0 | s = state->buf.data(); |
375 | 0 | std::memcpy(key.data(), s, key_size); |
376 | 0 | keylen = state->x; |
377 | | |
378 | | /* make RC4 perm and shuffle */ |
379 | 0 | for (x = 0; x < int(key_size); ++x) { |
380 | 0 | s[x] = x & 0xff; |
381 | 0 | } |
382 | |
|
383 | 0 | for (j = x = y = 0; x < int(key_size); x++) { |
384 | 0 | y = (y + state->buf[x] + key[j++]) & 255; |
385 | 0 | if (j == keylen) { |
386 | 0 | j = 0; |
387 | 0 | } |
388 | 0 | tmp = s[x]; s[x] = s[y]; s[y] = tmp; |
389 | 0 | } |
390 | 0 | state->x = 0; |
391 | 0 | state->y = 0; |
392 | 0 | } |
393 | | |
394 | | std::size_t rc4_encrypt(unsigned char *out, std::size_t outlen, rc4 *state) |
395 | 0 | { |
396 | 0 | std::uint8_t x, y, *s, tmp; |
397 | 0 | std::size_t n; |
398 | |
|
399 | 0 | TORRENT_ASSERT(out != nullptr); |
400 | 0 | TORRENT_ASSERT(state != nullptr); |
401 | |
|
402 | 0 | n = outlen; |
403 | 0 | x = state->x & 0xff; |
404 | 0 | y = state->y & 0xff; |
405 | 0 | s = state->buf.data(); |
406 | 0 | while (outlen--) { |
407 | 0 | x = (x + 1) & 255; |
408 | 0 | y = (y + s[x]) & 255; |
409 | 0 | tmp = s[x]; s[x] = s[y]; s[y] = tmp; |
410 | 0 | tmp = (s[x] + s[y]) & 255; |
411 | 0 | *out++ ^= s[tmp]; |
412 | 0 | } |
413 | 0 | state->x = x; |
414 | 0 | state->y = y; |
415 | 0 | return n; |
416 | 0 | } |
417 | | |
418 | | } // namespace libtorrent |
419 | | |
420 | | #endif // TORRENT_DISABLE_ENCRYPTION |