LCOV - code coverage report
Current view: top level - src - i2p.cpp (source / functions) Hit Total Coverage
Test: fuzz_coverage.info Lines: 1 237 0.4 %
Date: 2023-09-26 12:08:55 Functions: 1 27 3.7 %

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

Generated by: LCOV version 1.14