i2p.cpp raw

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