1 // Copyright (c) 2020-2020 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 <compat.h>
7 #include <compat/endian.h>
8 #include <crypto/sha256.h>
9 #include <fs.h>
10 #include <i2p.h>
11 #include <logging.h>
12 #include <netaddress.h>
13 #include <netbase.h>
14 #include <random.h>
15 #include <util/strencodings.h>
16 #include <tinyformat.h>
17 #include <util/readwritefile.h>
18 #include <util/sock.h>
19 #include <util/spanparsing.h>
20 #include <util/system.h>
21 
22 #include <chrono>
23 #include <memory>
24 #include <stdexcept>
25 #include <string>
26 
27 namespace i2p {
28 
29 /**
30  * Swap Standard Base64 <-> I2P Base64.
31  * Standard Base64 uses `+` and `/` as last two characters of its alphabet.
32  * I2P Base64 uses `-` and `~` respectively.
33  * So it is easy to detect in which one is the input and convert to the other.
34  * @param[in] from Input to convert.
35  * @return converted `from`
36  */
SwapBase64(const std::string & from)37 static std::string SwapBase64(const std::string& from)
38 {
39     std::string to;
40     to.resize(from.size());
41     for (size_t i = 0; i < from.size(); ++i) {
42         switch (from[i]) {
43         case '-':
44             to[i] = '+';
45             break;
46         case '~':
47             to[i] = '/';
48             break;
49         case '+':
50             to[i] = '-';
51             break;
52         case '/':
53             to[i] = '~';
54             break;
55         default:
56             to[i] = from[i];
57             break;
58         }
59     }
60     return to;
61 }
62 
63 /**
64  * Decode an I2P-style Base64 string.
65  * @param[in] i2p_b64 I2P-style Base64 string.
66  * @return decoded `i2p_b64`
67  * @throw std::runtime_error if decoding fails
68  */
DecodeI2PBase64(const std::string & i2p_b64)69 static Binary DecodeI2PBase64(const std::string& i2p_b64)
70 {
71     const std::string& std_b64 = SwapBase64(i2p_b64);
72     bool invalid;
73     Binary decoded = DecodeBase64(std_b64.c_str(), &invalid);
74     if (invalid) {
75         throw std::runtime_error(strprintf("Cannot decode Base64: \"%s\"", i2p_b64));
76     }
77     return decoded;
78 }
79 
80 /**
81  * Derive the .b32.i2p address of an I2P destination (binary).
82  * @param[in] dest I2P destination.
83  * @return the address that corresponds to `dest`
84  * @throw std::runtime_error if conversion fails
85  */
DestBinToAddr(const Binary & dest)86 static CNetAddr DestBinToAddr(const Binary& dest)
87 {
88     CSHA256 hasher;
89     hasher.Write(dest.data(), dest.size());
90     unsigned char hash[CSHA256::OUTPUT_SIZE];
91     hasher.Finalize(hash);
92 
93     CNetAddr addr;
94     const std::string addr_str = EncodeBase32(hash, false) + ".b32.i2p";
95     if (!addr.SetSpecial(addr_str)) {
96         throw std::runtime_error(strprintf("Cannot parse I2P address: \"%s\"", addr_str));
97     }
98 
99     return addr;
100 }
101 
102 /**
103  * Derive the .b32.i2p address of an I2P destination (I2P-style Base64).
104  * @param[in] dest I2P destination.
105  * @return the address that corresponds to `dest`
106  * @throw std::runtime_error if conversion fails
107  */
DestB64ToAddr(const std::string & dest)108 static CNetAddr DestB64ToAddr(const std::string& dest)
109 {
110     const Binary& decoded = DecodeI2PBase64(dest);
111     return DestBinToAddr(decoded);
112 }
113 
114 namespace sam {
115 
Session(const fs::path & private_key_file,const CService & control_host,CThreadInterrupt * interrupt)116 Session::Session(const fs::path& private_key_file,
117                  const CService& control_host,
118                  CThreadInterrupt* interrupt)
119     : m_private_key_file(private_key_file), m_control_host(control_host), m_interrupt(interrupt),
120       m_control_sock(std::make_unique<Sock>(INVALID_SOCKET))
121 {
122 }
123 
~Session()124 Session::~Session()
125 {
126     LOCK(m_mutex);
127     Disconnect();
128 }
129 
Listen(Connection & conn)130 bool Session::Listen(Connection& conn)
131 {
132     try {
133         LOCK(m_mutex);
134         CreateIfNotCreatedAlready();
135         conn.me = m_my_addr;
136         conn.sock = StreamAccept();
137         return true;
138     } catch (const std::runtime_error& e) {
139         Log("Error listening: %s", e.what());
140         CheckControlSock();
141     }
142     return false;
143 }
144 
Accept(Connection & conn)145 bool Session::Accept(Connection& conn)
146 {
147     try {
148         while (!*m_interrupt) {
149             Sock::Event occurred;
150             if (!conn.sock->Wait(MAX_WAIT_FOR_IO, Sock::RECV, &occurred)) {
151                 throw std::runtime_error("wait on socket failed");
152             }
153 
154             if ((occurred & Sock::RECV) == 0) {
155                 // Timeout, no incoming connections within MAX_WAIT_FOR_IO.
156                 continue;
157             }
158 
159             const std::string& peer_dest =
160                 conn.sock->RecvUntilTerminator('\n', MAX_WAIT_FOR_IO, *m_interrupt, MAX_MSG_SIZE);
161 
162             conn.peer = CService(DestB64ToAddr(peer_dest), I2P_SAM31_PORT);
163 
164             return true;
165         }
166     } catch (const std::runtime_error& e) {
167         Log("Error accepting: %s", e.what());
168         CheckControlSock();
169     }
170     return false;
171 }
172 
Connect(const CService & to,Connection & conn,bool & proxy_error)173 bool Session::Connect(const CService& to, Connection& conn, bool& proxy_error)
174 {
175     // Refuse connecting to arbitrary ports. We don't specify any destination port to the SAM proxy
176     // when connecting (SAM 3.1 does not use ports) and it forces/defaults it to I2P_SAM31_PORT.
177     if (to.GetPort() != I2P_SAM31_PORT) {
178         proxy_error = false;
179         return false;
180     }
181 
182     proxy_error = true;
183 
184     std::string session_id;
185     std::unique_ptr<Sock> sock;
186     conn.peer = to;
187 
188     try {
189         {
190             LOCK(m_mutex);
191             CreateIfNotCreatedAlready();
192             session_id = m_session_id;
193             conn.me = m_my_addr;
194             sock = Hello();
195         }
196 
197         const Reply& lookup_reply =
198             SendRequestAndGetReply(*sock, strprintf("NAMING LOOKUP NAME=%s", to.ToStringIP()));
199 
200         const std::string& dest = lookup_reply.Get("VALUE");
201 
202         const Reply& connect_reply = SendRequestAndGetReply(
203             *sock, strprintf("STREAM CONNECT ID=%s DESTINATION=%s SILENT=false", session_id, dest),
204             false);
205 
206         const std::string& result = connect_reply.Get("RESULT");
207 
208         if (result == "OK") {
209             conn.sock = std::move(sock);
210             return true;
211         }
212 
213         if (result == "INVALID_ID") {
214             LOCK(m_mutex);
215             Disconnect();
216             throw std::runtime_error("Invalid session id");
217         }
218 
219         if (result == "CANT_REACH_PEER" || result == "TIMEOUT") {
220             proxy_error = false;
221         }
222 
223         throw std::runtime_error(strprintf("\"%s\"", connect_reply.full));
224     } catch (const std::runtime_error& e) {
225         Log("Error connecting to %s: %s", to.ToString(), e.what());
226         CheckControlSock();
227         return false;
228     }
229 }
230 
231 // Private methods
232 
Get(const std::string & key) const233 std::string Session::Reply::Get(const std::string& key) const
234 {
235     const auto& pos = keys.find(key);
236     if (pos == keys.end() || !pos->second.has_value()) {
237         throw std::runtime_error(
238             strprintf("Missing %s= in the reply to \"%s\": \"%s\"", key, request, full));
239     }
240     return pos->second.value();
241 }
242 
243 template <typename... Args>
Log(const std::string & fmt,const Args &...args) const244 void Session::Log(const std::string& fmt, const Args&... args) const
245 {
246     LogPrint(BCLog::I2P, "I2P: %s\n", tfm::format(fmt, args...));
247 }
248 
SendRequestAndGetReply(const Sock & sock,const std::string & request,bool check_result_ok) const249 Session::Reply Session::SendRequestAndGetReply(const Sock& sock,
250                                                const std::string& request,
251                                                bool check_result_ok) const
252 {
253     sock.SendComplete(request + "\n", MAX_WAIT_FOR_IO, *m_interrupt);
254 
255     Reply reply;
256 
257     // Don't log the full "SESSION CREATE ..." because it contains our private key.
258     reply.request = request.substr(0, 14) == "SESSION CREATE" ? "SESSION CREATE ..." : request;
259 
260     // It could take a few minutes for the I2P router to reply as it is querying the I2P network
261     // (when doing name lookup, for example). Notice: `RecvUntilTerminator()` is checking
262     // `m_interrupt` more often, so we would not be stuck here for long if `m_interrupt` is
263     // signaled.
264     static constexpr auto recv_timeout = 3min;
265 
266     reply.full = sock.RecvUntilTerminator('\n', recv_timeout, *m_interrupt, MAX_MSG_SIZE);
267 
268     for (const auto& kv : spanparsing::Split(reply.full, ' ')) {
269         const auto& pos = std::find(kv.begin(), kv.end(), '=');
270         if (pos != kv.end()) {
271             reply.keys.emplace(std::string{kv.begin(), pos}, std::string{pos + 1, kv.end()});
272         } else {
273             reply.keys.emplace(std::string{kv.begin(), kv.end()}, std::nullopt);
274         }
275     }
276 
277     if (check_result_ok && reply.Get("RESULT") != "OK") {
278         throw std::runtime_error(
279             strprintf("Unexpected reply to \"%s\": \"%s\"", request, reply.full));
280     }
281 
282     return reply;
283 }
284 
Hello() const285 std::unique_ptr<Sock> Session::Hello() const
286 {
287     auto sock = CreateSock(m_control_host);
288 
289     if (!sock) {
290         throw std::runtime_error("Cannot create socket");
291     }
292 
293     if (!ConnectSocketDirectly(m_control_host, *sock, nConnectTimeout, true)) {
294         throw std::runtime_error(strprintf("Cannot connect to %s", m_control_host.ToString()));
295     }
296 
297     SendRequestAndGetReply(*sock, "HELLO VERSION MIN=3.1 MAX=3.1");
298 
299     return sock;
300 }
301 
CheckControlSock()302 void Session::CheckControlSock()
303 {
304     LOCK(m_mutex);
305 
306     std::string errmsg;
307     if (!m_control_sock->IsConnected(errmsg)) {
308         Log("Control socket error: %s", errmsg);
309         Disconnect();
310     }
311 }
312 
DestGenerate(const Sock & sock)313 void Session::DestGenerate(const Sock& sock)
314 {
315     // https://geti2p.net/spec/common-structures#key-certificates
316     // "7" or "EdDSA_SHA512_Ed25519" - "Recent Router Identities and Destinations".
317     // Use "7" because i2pd <2.24.0 does not recognize the textual form.
318     const Reply& reply = SendRequestAndGetReply(sock, "DEST GENERATE SIGNATURE_TYPE=7", false);
319 
320     m_private_key = DecodeI2PBase64(reply.Get("PRIV"));
321 }
322 
GenerateAndSavePrivateKey(const Sock & sock)323 void Session::GenerateAndSavePrivateKey(const Sock& sock)
324 {
325     DestGenerate(sock);
326 
327     // umask is set to 077 in init.cpp, which is ok (unless -sysperms is given)
328     if (!WriteBinaryFile(m_private_key_file,
329                          std::string(m_private_key.begin(), m_private_key.end()))) {
330         throw std::runtime_error(
331             strprintf("Cannot save I2P private key to %s", m_private_key_file));
332     }
333 }
334 
MyDestination() const335 Binary Session::MyDestination() const
336 {
337     // From https://geti2p.net/spec/common-structures#destination:
338     // "They are 387 bytes plus the certificate length specified at bytes 385-386, which may be
339     // non-zero"
340     static constexpr size_t DEST_LEN_BASE = 387;
341     static constexpr size_t CERT_LEN_POS = 385;
342 
343     uint16_t cert_len;
344     memcpy(&cert_len, &m_private_key.at(CERT_LEN_POS), sizeof(cert_len));
345     cert_len = be16toh(cert_len);
346 
347     const size_t dest_len = DEST_LEN_BASE + cert_len;
348 
349     return Binary{m_private_key.begin(), m_private_key.begin() + dest_len};
350 }
351 
CreateIfNotCreatedAlready()352 void Session::CreateIfNotCreatedAlready()
353 {
354     std::string errmsg;
355     if (m_control_sock->IsConnected(errmsg)) {
356         return;
357     }
358 
359     Log("Creating SAM session with %s", m_control_host.ToString());
360 
361     auto sock = Hello();
362 
363     const auto& [read_ok, data] = ReadBinaryFile(m_private_key_file);
364     if (read_ok) {
365         m_private_key.assign(data.begin(), data.end());
366     } else {
367         GenerateAndSavePrivateKey(*sock);
368     }
369 
370     const std::string& session_id = GetRandHash().GetHex().substr(0, 10); // full is an overkill, too verbose in the logs
371     const std::string& private_key_b64 = SwapBase64(EncodeBase64(m_private_key));
372 
373     SendRequestAndGetReply(*sock, strprintf("SESSION CREATE STYLE=STREAM ID=%s DESTINATION=%s",
374                                             session_id, private_key_b64));
375 
376     m_my_addr = CService(DestBinToAddr(MyDestination()), I2P_SAM31_PORT);
377     m_session_id = session_id;
378     m_control_sock = std::move(sock);
379 
380     LogPrintf("I2P: SAM session created: session id=%s, my address=%s\n", m_session_id,
381               m_my_addr.ToString());
382 }
383 
StreamAccept()384 std::unique_ptr<Sock> Session::StreamAccept()
385 {
386     auto sock = Hello();
387 
388     const Reply& reply = SendRequestAndGetReply(
389         *sock, strprintf("STREAM ACCEPT ID=%s SILENT=false", m_session_id), false);
390 
391     const std::string& result = reply.Get("RESULT");
392 
393     if (result == "OK") {
394         return sock;
395     }
396 
397     if (result == "INVALID_ID") {
398         // If our session id is invalid, then force session re-creation on next usage.
399         Disconnect();
400     }
401 
402     throw std::runtime_error(strprintf("\"%s\"", reply.full));
403 }
404 
Disconnect()405 void Session::Disconnect()
406 {
407     if (m_control_sock->Get() != INVALID_SOCKET) {
408         if (m_session_id.empty()) {
409             Log("Destroying incomplete session");
410         } else {
411             Log("Destroying session %s", m_session_id);
412         }
413     }
414     m_control_sock->Reset();
415     m_session_id.clear();
416 }
417 } // namespace sam
418 } // namespace i2p
419