Coverage Report

Created: 2024-08-21 05:08

/workdir/bitcoin/src/i2p.cpp
Line
Count
Source (jump to first uncovered line)
1
// Copyright (c) 2020-2022 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 <chainparams.h>
6
#include <common/args.h>
7
#include <compat/compat.h>
8
#include <compat/endian.h>
9
#include <crypto/sha256.h>
10
#include <i2p.h>
11
#include <logging.h>
12
#include <netaddress.h>
13
#include <netbase.h>
14
#include <random.h>
15
#include <script/parsing.h>
16
#include <sync.h>
17
#include <tinyformat.h>
18
#include <util/fs.h>
19
#include <util/readwritefile.h>
20
#include <util/sock.h>
21
#include <util/strencodings.h>
22
#include <util/threadinterrupt.h>
23
24
#include <chrono>
25
#include <memory>
26
#include <stdexcept>
27
#include <string>
28
29
using util::Split;
30
31
namespace i2p {
32
33
/**
34
 * Swap Standard Base64 <-> I2P Base64.
35
 * Standard Base64 uses `+` and `/` as last two characters of its alphabet.
36
 * I2P Base64 uses `-` and `~` respectively.
37
 * So it is easy to detect in which one is the input and convert to the other.
38
 * @param[in] from Input to convert.
39
 * @return converted `from`
40
 */
41
static std::string SwapBase64(const std::string& from)
42
0
{
43
0
    std::string to;
44
0
    to.resize(from.size());
45
0
    for (size_t i = 0; i < from.size(); ++i) {
  Branch (45:24): [True: 0, False: 0]
46
0
        switch (from[i]) {
47
0
        case '-':
  Branch (47:9): [True: 0, False: 0]
48
0
            to[i] = '+';
49
0
            break;
50
0
        case '~':
  Branch (50:9): [True: 0, False: 0]
51
0
            to[i] = '/';
52
0
            break;
53
0
        case '+':
  Branch (53:9): [True: 0, False: 0]
54
0
            to[i] = '-';
55
0
            break;
56
0
        case '/':
  Branch (56:9): [True: 0, False: 0]
57
0
            to[i] = '~';
58
0
            break;
59
0
        default:
  Branch (59:9): [True: 0, False: 0]
60
0
            to[i] = from[i];
61
0
            break;
62
0
        }
63
0
    }
64
0
    return to;
65
0
}
66
67
/**
68
 * Decode an I2P-style Base64 string.
69
 * @param[in] i2p_b64 I2P-style Base64 string.
70
 * @return decoded `i2p_b64`
71
 * @throw std::runtime_error if decoding fails
72
 */
73
static Binary DecodeI2PBase64(const std::string& i2p_b64)
74
0
{
75
0
    const std::string& std_b64 = SwapBase64(i2p_b64);
76
0
    auto decoded = DecodeBase64(std_b64);
77
0
    if (!decoded) {
  Branch (77:9): [True: 0, False: 0]
78
0
        throw std::runtime_error(strprintf("Cannot decode Base64: \"%s\"", i2p_b64));
79
0
    }
80
0
    return std::move(*decoded);
81
0
}
82
83
/**
84
 * Derive the .b32.i2p address of an I2P destination (binary).
85
 * @param[in] dest I2P destination.
86
 * @return the address that corresponds to `dest`
87
 * @throw std::runtime_error if conversion fails
88
 */
89
static CNetAddr DestBinToAddr(const Binary& dest)
90
0
{
91
0
    CSHA256 hasher;
92
0
    hasher.Write(dest.data(), dest.size());
93
0
    unsigned char hash[CSHA256::OUTPUT_SIZE];
94
0
    hasher.Finalize(hash);
95
96
0
    CNetAddr addr;
97
0
    const std::string addr_str = EncodeBase32(hash, false) + ".b32.i2p";
98
0
    if (!addr.SetSpecial(addr_str)) {
  Branch (98:9): [True: 0, False: 0]
99
0
        throw std::runtime_error(strprintf("Cannot parse I2P address: \"%s\"", addr_str));
100
0
    }
101
102
0
    return addr;
103
0
}
104
105
/**
106
 * Derive the .b32.i2p address of an I2P destination (I2P-style Base64).
107
 * @param[in] dest I2P destination.
108
 * @return the address that corresponds to `dest`
109
 * @throw std::runtime_error if conversion fails
110
 */
111
static CNetAddr DestB64ToAddr(const std::string& dest)
112
0
{
113
0
    const Binary& decoded = DecodeI2PBase64(dest);
114
0
    return DestBinToAddr(decoded);
115
0
}
116
117
namespace sam {
118
119
Session::Session(const fs::path& private_key_file,
120
                 const Proxy& control_host,
121
                 CThreadInterrupt* interrupt)
122
0
    : m_private_key_file{private_key_file},
123
0
      m_control_host{control_host},
124
0
      m_interrupt{interrupt},
125
0
      m_transient{false}
126
0
{
127
0
}
128
129
Session::Session(const Proxy& control_host, CThreadInterrupt* interrupt)
130
0
    : m_control_host{control_host},
131
0
      m_interrupt{interrupt},
132
0
      m_transient{true}
133
0
{
134
0
}
135
136
Session::~Session()
137
0
{
138
0
    LOCK(m_mutex);
139
0
    Disconnect();
140
0
}
141
142
bool Session::Listen(Connection& conn)
143
0
{
144
0
    try {
145
0
        LOCK(m_mutex);
146
0
        CreateIfNotCreatedAlready();
147
0
        conn.me = m_my_addr;
148
0
        conn.sock = StreamAccept();
149
0
        return true;
150
0
    } catch (const std::runtime_error& e) {
151
0
        LogPrintLevel(BCLog::I2P, BCLog::Level::Error, "Couldn't listen: %s\n", e.what());
152
0
        CheckControlSock();
153
0
    }
154
0
    return false;
155
0
}
156
157
bool Session::Accept(Connection& conn)
158
0
{
159
0
    AssertLockNotHeld(m_mutex);
160
161
0
    std::string errmsg;
162
0
    bool disconnect{false};
163
164
0
    while (!*m_interrupt) {
  Branch (164:12): [True: 0, False: 0]
165
0
        Sock::Event occurred;
166
0
        if (!conn.sock->Wait(MAX_WAIT_FOR_IO, Sock::RECV, &occurred)) {
  Branch (166:13): [True: 0, False: 0]
167
0
            errmsg = "wait on socket failed";
168
0
            break;
169
0
        }
170
171
0
        if (occurred == 0) {
  Branch (171:13): [True: 0, False: 0]
172
            // Timeout, no incoming connections or errors within MAX_WAIT_FOR_IO.
173
0
            continue;
174
0
        }
175
176
0
        std::string peer_dest;
177
0
        try {
178
0
            peer_dest = conn.sock->RecvUntilTerminator('\n', MAX_WAIT_FOR_IO, *m_interrupt, MAX_MSG_SIZE);
179
0
        } catch (const std::runtime_error& e) {
180
0
            errmsg = e.what();
181
0
            break;
182
0
        }
183
184
0
        CNetAddr peer_addr;
185
0
        try {
186
0
            peer_addr = DestB64ToAddr(peer_dest);
187
0
        } catch (const std::runtime_error& e) {
188
            // The I2P router is expected to send the Base64 of the connecting peer,
189
            // but it may happen that something like this is sent instead:
190
            // STREAM STATUS RESULT=I2P_ERROR MESSAGE="Session was closed"
191
            // In that case consider the session damaged and close it right away,
192
            // even if the control socket is alive.
193
0
            if (peer_dest.find("RESULT=I2P_ERROR") != std::string::npos) {
  Branch (193:17): [True: 0, False: 0]
194
0
                errmsg = strprintf("unexpected reply that hints the session is unusable: %s", peer_dest);
195
0
                disconnect = true;
196
0
            } else {
197
0
                errmsg = e.what();
198
0
            }
199
0
            break;
200
0
        }
201
202
0
        conn.peer = CService(peer_addr, I2P_SAM31_PORT);
203
204
0
        return true;
205
0
    }
206
207
0
    if (*m_interrupt) {
  Branch (207:9): [True: 0, False: 0]
208
0
        LogPrintLevel(BCLog::I2P, BCLog::Level::Debug, "Accept was interrupted\n");
209
0
    } else {
210
0
        LogPrintLevel(BCLog::I2P, BCLog::Level::Debug, "Error accepting%s: %s\n", disconnect ? " (will close the session)" : "", errmsg);
211
0
    }
212
0
    if (disconnect) {
  Branch (212:9): [True: 0, False: 0]
213
0
        LOCK(m_mutex);
214
0
        Disconnect();
215
0
    } else {
216
0
        CheckControlSock();
217
0
    }
218
0
    return false;
219
0
}
220
221
bool Session::Connect(const CService& to, Connection& conn, bool& proxy_error)
222
0
{
223
    // Refuse connecting to arbitrary ports. We don't specify any destination port to the SAM proxy
224
    // when connecting (SAM 3.1 does not use ports) and it forces/defaults it to I2P_SAM31_PORT.
225
0
    if (to.GetPort() != I2P_SAM31_PORT) {
  Branch (225:9): [True: 0, False: 0]
226
0
        LogPrintLevel(BCLog::I2P, BCLog::Level::Debug, "Error connecting to %s, connection refused due to arbitrary port %s\n", to.ToStringAddrPort(), to.GetPort());
227
0
        proxy_error = false;
228
0
        return false;
229
0
    }
230
231
0
    proxy_error = true;
232
233
0
    std::string session_id;
234
0
    std::unique_ptr<Sock> sock;
235
0
    conn.peer = to;
236
237
0
    try {
238
0
        {
239
0
            LOCK(m_mutex);
240
0
            CreateIfNotCreatedAlready();
241
0
            session_id = m_session_id;
242
0
            conn.me = m_my_addr;
243
0
            sock = Hello();
244
0
        }
245
246
0
        const Reply& lookup_reply =
247
0
            SendRequestAndGetReply(*sock, strprintf("NAMING LOOKUP NAME=%s", to.ToStringAddr()));
248
249
0
        const std::string& dest = lookup_reply.Get("VALUE");
250
251
0
        const Reply& connect_reply = SendRequestAndGetReply(
252
0
            *sock, strprintf("STREAM CONNECT ID=%s DESTINATION=%s SILENT=false", session_id, dest),
253
0
            false);
254
255
0
        const std::string& result = connect_reply.Get("RESULT");
256
257
0
        if (result == "OK") {
  Branch (257:13): [True: 0, False: 0]
258
0
            conn.sock = std::move(sock);
259
0
            return true;
260
0
        }
261
262
0
        if (result == "INVALID_ID") {
  Branch (262:13): [True: 0, False: 0]
263
0
            LOCK(m_mutex);
264
0
            Disconnect();
265
0
            throw std::runtime_error("Invalid session id");
266
0
        }
267
268
0
        if (result == "CANT_REACH_PEER" || result == "TIMEOUT") {
  Branch (268:13): [True: 0, False: 0]
  Branch (268:44): [True: 0, False: 0]
269
0
            proxy_error = false;
270
0
        }
271
272
0
        throw std::runtime_error(strprintf("\"%s\"", connect_reply.full));
273
0
    } catch (const std::runtime_error& e) {
274
0
        LogPrintLevel(BCLog::I2P, BCLog::Level::Debug, "Error connecting to %s: %s\n", to.ToStringAddrPort(), e.what());
275
0
        CheckControlSock();
276
0
        return false;
277
0
    }
278
0
}
279
280
// Private methods
281
282
std::string Session::Reply::Get(const std::string& key) const
283
0
{
284
0
    const auto& pos = keys.find(key);
285
0
    if (pos == keys.end() || !pos->second.has_value()) {
  Branch (285:9): [True: 0, False: 0]
  Branch (285:9): [True: 0, False: 0]
  Branch (285:30): [True: 0, False: 0]
286
0
        throw std::runtime_error(
287
0
            strprintf("Missing %s= in the reply to \"%s\": \"%s\"", key, request, full));
288
0
    }
289
0
    return pos->second.value();
290
0
}
291
292
Session::Reply Session::SendRequestAndGetReply(const Sock& sock,
293
                                               const std::string& request,
294
                                               bool check_result_ok) const
295
0
{
296
0
    sock.SendComplete(request + "\n", MAX_WAIT_FOR_IO, *m_interrupt);
297
298
0
    Reply reply;
299
300
    // Don't log the full "SESSION CREATE ..." because it contains our private key.
301
0
    reply.request = request.substr(0, 14) == "SESSION CREATE" ? "SESSION CREATE ..." : request;
  Branch (301:21): [True: 0, False: 0]
302
303
    // It could take a few minutes for the I2P router to reply as it is querying the I2P network
304
    // (when doing name lookup, for example). Notice: `RecvUntilTerminator()` is checking
305
    // `m_interrupt` more often, so we would not be stuck here for long if `m_interrupt` is
306
    // signaled.
307
0
    static constexpr auto recv_timeout = 3min;
308
309
0
    reply.full = sock.RecvUntilTerminator('\n', recv_timeout, *m_interrupt, MAX_MSG_SIZE);
310
311
0
    for (const auto& kv : Split(reply.full, ' ')) {
  Branch (311:25): [True: 0, False: 0]
312
0
        const auto& pos = std::find(kv.begin(), kv.end(), '=');
313
0
        if (pos != kv.end()) {
  Branch (313:13): [True: 0, False: 0]
314
0
            reply.keys.emplace(std::string{kv.begin(), pos}, std::string{pos + 1, kv.end()});
315
0
        } else {
316
0
            reply.keys.emplace(std::string{kv.begin(), kv.end()}, std::nullopt);
317
0
        }
318
0
    }
319
320
0
    if (check_result_ok && reply.Get("RESULT") != "OK") {
  Branch (320:9): [True: 0, False: 0]
  Branch (320:9): [True: 0, False: 0]
  Branch (320:28): [True: 0, False: 0]
321
0
        throw std::runtime_error(
322
0
            strprintf("Unexpected reply to \"%s\": \"%s\"", request, reply.full));
323
0
    }
324
325
0
    return reply;
326
0
}
327
328
std::unique_ptr<Sock> Session::Hello() const
329
0
{
330
0
    auto sock = m_control_host.Connect();
331
332
0
    if (!sock) {
  Branch (332:9): [True: 0, False: 0]
333
0
        throw std::runtime_error(strprintf("Cannot connect to %s", m_control_host.ToString()));
334
0
    }
335
336
0
    SendRequestAndGetReply(*sock, "HELLO VERSION MIN=3.1 MAX=3.1");
337
338
0
    return sock;
339
0
}
340
341
void Session::CheckControlSock()
342
0
{
343
0
    LOCK(m_mutex);
344
345
0
    std::string errmsg;
346
0
    if (m_control_sock && !m_control_sock->IsConnected(errmsg)) {
  Branch (346:9): [True: 0, False: 0]
  Branch (346:27): [True: 0, False: 0]
347
0
        LogPrintLevel(BCLog::I2P, BCLog::Level::Debug, "Control socket error: %s\n", errmsg);
348
0
        Disconnect();
349
0
    }
350
0
}
351
352
void Session::DestGenerate(const Sock& sock)
353
0
{
354
    // https://geti2p.net/spec/common-structures#key-certificates
355
    // "7" or "EdDSA_SHA512_Ed25519" - "Recent Router Identities and Destinations".
356
    // Use "7" because i2pd <2.24.0 does not recognize the textual form.
357
    // If SIGNATURE_TYPE is not specified, then the default one is DSA_SHA1.
358
0
    const Reply& reply = SendRequestAndGetReply(sock, "DEST GENERATE SIGNATURE_TYPE=7", false);
359
360
0
    m_private_key = DecodeI2PBase64(reply.Get("PRIV"));
361
0
}
362
363
void Session::GenerateAndSavePrivateKey(const Sock& sock)
364
0
{
365
0
    DestGenerate(sock);
366
367
    // umask is set to 0077 in common/system.cpp, which is ok.
368
0
    if (!WriteBinaryFile(m_private_key_file,
  Branch (368:9): [True: 0, False: 0]
369
0
                         std::string(m_private_key.begin(), m_private_key.end()))) {
370
0
        throw std::runtime_error(
371
0
            strprintf("Cannot save I2P private key to %s", fs::quoted(fs::PathToString(m_private_key_file))));
372
0
    }
373
0
}
374
375
Binary Session::MyDestination() const
376
0
{
377
    // From https://geti2p.net/spec/common-structures#destination:
378
    // "They are 387 bytes plus the certificate length specified at bytes 385-386, which may be
379
    // non-zero"
380
0
    static constexpr size_t DEST_LEN_BASE = 387;
381
0
    static constexpr size_t CERT_LEN_POS = 385;
382
383
0
    uint16_t cert_len;
384
385
0
    if (m_private_key.size() < CERT_LEN_POS + sizeof(cert_len)) {
  Branch (385:9): [True: 0, False: 0]
386
0
        throw std::runtime_error(strprintf("The private key is too short (%d < %d)",
387
0
                                           m_private_key.size(),
388
0
                                           CERT_LEN_POS + sizeof(cert_len)));
389
0
    }
390
391
0
    memcpy(&cert_len, &m_private_key.at(CERT_LEN_POS), sizeof(cert_len));
392
0
    cert_len = be16toh_internal(cert_len);
393
394
0
    const size_t dest_len = DEST_LEN_BASE + cert_len;
395
396
0
    if (dest_len > m_private_key.size()) {
  Branch (396:9): [True: 0, False: 0]
397
0
        throw std::runtime_error(strprintf("Certificate length (%d) designates that the private key should "
398
0
                                           "be %d bytes, but it is only %d bytes",
399
0
                                           cert_len,
400
0
                                           dest_len,
401
0
                                           m_private_key.size()));
402
0
    }
403
404
0
    return Binary{m_private_key.begin(), m_private_key.begin() + dest_len};
405
0
}
406
407
void Session::CreateIfNotCreatedAlready()
408
0
{
409
0
    std::string errmsg;
410
0
    if (m_control_sock && m_control_sock->IsConnected(errmsg)) {
  Branch (410:9): [True: 0, False: 0]
  Branch (410:27): [True: 0, False: 0]
411
0
        return;
412
0
    }
413
414
0
    const auto session_type = m_transient ? "transient" : "persistent";
  Branch (414:31): [True: 0, False: 0]
415
0
    const auto session_id = GetRandHash().GetHex().substr(0, 10); // full is overkill, too verbose in the logs
416
417
0
    LogPrintLevel(BCLog::I2P, BCLog::Level::Debug, "Creating %s SAM session %s with %s\n", session_type, session_id, m_control_host.ToString());
418
419
0
    auto sock = Hello();
420
421
0
    if (m_transient) {
  Branch (421:9): [True: 0, False: 0]
422
        // The destination (private key) is generated upon session creation and returned
423
        // in the reply in DESTINATION=.
424
0
        const Reply& reply = SendRequestAndGetReply(
425
0
            *sock,
426
0
            strprintf("SESSION CREATE STYLE=STREAM ID=%s DESTINATION=TRANSIENT SIGNATURE_TYPE=7 "
427
0
                      "i2cp.leaseSetEncType=4,0 inbound.quantity=1 outbound.quantity=1",
428
0
                      session_id));
429
430
0
        m_private_key = DecodeI2PBase64(reply.Get("DESTINATION"));
431
0
    } else {
432
        // Read our persistent destination (private key) from disk or generate
433
        // one and save it to disk. Then use it when creating the session.
434
0
        const auto& [read_ok, data] = ReadBinaryFile(m_private_key_file);
435
0
        if (read_ok) {
  Branch (435:13): [True: 0, False: 0]
436
0
            m_private_key.assign(data.begin(), data.end());
437
0
        } else {
438
0
            GenerateAndSavePrivateKey(*sock);
439
0
        }
440
441
0
        const std::string& private_key_b64 = SwapBase64(EncodeBase64(m_private_key));
442
443
0
        SendRequestAndGetReply(*sock,
444
0
                               strprintf("SESSION CREATE STYLE=STREAM ID=%s DESTINATION=%s "
445
0
                                         "i2cp.leaseSetEncType=4,0 inbound.quantity=3 outbound.quantity=3",
446
0
                                         session_id,
447
0
                                         private_key_b64));
448
0
    }
449
450
0
    m_my_addr = CService(DestBinToAddr(MyDestination()), I2P_SAM31_PORT);
451
0
    m_session_id = session_id;
452
0
    m_control_sock = std::move(sock);
453
454
0
    LogPrintLevel(BCLog::I2P, BCLog::Level::Info, "%s SAM session %s created, my address=%s\n",
455
0
        Capitalize(session_type),
456
0
        m_session_id,
457
0
        m_my_addr.ToStringAddrPort());
458
0
}
459
460
std::unique_ptr<Sock> Session::StreamAccept()
461
0
{
462
0
    auto sock = Hello();
463
464
0
    const Reply& reply = SendRequestAndGetReply(
465
0
        *sock, strprintf("STREAM ACCEPT ID=%s SILENT=false", m_session_id), false);
466
467
0
    const std::string& result = reply.Get("RESULT");
468
469
0
    if (result == "OK") {
  Branch (469:9): [True: 0, False: 0]
470
0
        return sock;
471
0
    }
472
473
0
    if (result == "INVALID_ID") {
  Branch (473:9): [True: 0, False: 0]
474
        // If our session id is invalid, then force session re-creation on next usage.
475
0
        Disconnect();
476
0
    }
477
478
0
    throw std::runtime_error(strprintf("\"%s\"", reply.full));
479
0
}
480
481
void Session::Disconnect()
482
0
{
483
0
    if (m_control_sock) {
  Branch (483:9): [True: 0, False: 0]
484
0
        if (m_session_id.empty()) {
  Branch (484:13): [True: 0, False: 0]
485
0
            LogPrintLevel(BCLog::I2P, BCLog::Level::Info, "Destroying incomplete SAM session\n");
486
0
        } else {
487
0
            LogPrintLevel(BCLog::I2P, BCLog::Level::Info, "Destroying SAM session %s\n", m_session_id);
488
0
        }
489
0
        m_control_sock.reset();
490
0
    }
491
0
    m_session_id.clear();
492
0
}
493
} // namespace sam
494
} // namespace i2p