i2p.cpp raw

   1  // Copyright (c) 2020-2022 The Limenka 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  {
  43      std::string to;
  44      to.resize(from.size());
  45      for (size_t i = 0; i < from.size(); ++i) {
  46          switch (from[i]) {
  47          case '-':
  48              to[i] = '+';
  49              break;
  50          case '~':
  51              to[i] = '/';
  52              break;
  53          case '+':
  54              to[i] = '-';
  55              break;
  56          case '/':
  57              to[i] = '~';
  58              break;
  59          default:
  60              to[i] = from[i];
  61              break;
  62          }
  63      }
  64      return to;
  65  }
  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  {
  75      const std::string& std_b64 = SwapBase64(i2p_b64);
  76      auto decoded = DecodeBase64(std_b64);
  77      if (!decoded) {
  78          throw std::runtime_error(strprintf("Cannot decode Base64: \"%s\"", i2p_b64));
  79      }
  80      return std::move(*decoded);
  81  }
  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  {
  91      CSHA256 hasher;
  92      hasher.Write(dest.data(), dest.size());
  93      unsigned char hash[CSHA256::OUTPUT_SIZE];
  94      hasher.Finalize(hash);
  95  
  96      CNetAddr addr;
  97      const std::string addr_str = EncodeBase32(hash, false) + ".b32.i2p";
  98      if (!addr.SetSpecial(addr_str)) {
  99          throw std::runtime_error(strprintf("Cannot parse I2P address: \"%s\"", addr_str));
 100      }
 101  
 102      return addr;
 103  }
 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  {
 113      const Binary& decoded = DecodeI2PBase64(dest);
 114      return DestBinToAddr(decoded);
 115  }
 116  
 117  namespace sam {
 118  
 119  Session::Session(const fs::path& private_key_file,
 120                   const Proxy& control_host,
 121                   CThreadInterrupt* interrupt)
 122      : m_private_key_file{private_key_file},
 123        m_control_host{control_host},
 124        m_interrupt{interrupt},
 125        m_transient{false}
 126  {
 127  }
 128  
 129  Session::Session(const Proxy& control_host, CThreadInterrupt* interrupt)
 130      : m_control_host{control_host},
 131        m_interrupt{interrupt},
 132        m_transient{true}
 133  {
 134  }
 135  
 136  Session::~Session()
 137  {
 138      LOCK(m_mutex);
 139      Disconnect();
 140  }
 141  
 142  bool Session::Listen(Connection& conn)
 143  {
 144      try {
 145          LOCK(m_mutex);
 146          CreateIfNotCreatedAlready();
 147          conn.me = m_my_addr;
 148          conn.sock = StreamAccept();
 149          return true;
 150      } catch (const std::runtime_error& e) {
 151          LogPrintLevel(BCLog::I2P, BCLog::Level::Error, "Couldn't listen: %s\n", e.what());
 152          CheckControlSock();
 153      }
 154      return false;
 155  }
 156  
 157  bool Session::Accept(Connection& conn)
 158  {
 159      AssertLockNotHeld(m_mutex);
 160  
 161      std::string errmsg;
 162      bool disconnect{false};
 163  
 164      while (!*m_interrupt) {
 165          Sock::Event occurred;
 166          if (!conn.sock->Wait(MAX_WAIT_FOR_IO, Sock::RECV, &occurred)) {
 167              errmsg = "wait on socket failed";
 168              break;
 169          }
 170  
 171          if (occurred == 0) {
 172              // Timeout, no incoming connections or errors within MAX_WAIT_FOR_IO.
 173              continue;
 174          }
 175  
 176          std::string peer_dest;
 177          try {
 178              peer_dest = conn.sock->RecvUntilTerminator('\n', MAX_WAIT_FOR_IO, *m_interrupt, MAX_MSG_SIZE);
 179          } catch (const std::runtime_error& e) {
 180              errmsg = e.what();
 181              break;
 182          }
 183  
 184          CNetAddr peer_addr;
 185          try {
 186              peer_addr = DestB64ToAddr(peer_dest);
 187          } 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              if (peer_dest.find("RESULT=I2P_ERROR") != std::string::npos) {
 194                  errmsg = strprintf("unexpected reply that hints the session is unusable: %s", peer_dest);
 195                  disconnect = true;
 196              } else {
 197                  errmsg = e.what();
 198              }
 199              break;
 200          }
 201  
 202          conn.peer = CService(peer_addr, I2P_SAM31_PORT);
 203  
 204          return true;
 205      }
 206  
 207      if (*m_interrupt) {
 208          LogPrintLevel(BCLog::I2P, BCLog::Level::Debug, "Accept was interrupted\n");
 209      } else {
 210          LogPrintLevel(BCLog::I2P, BCLog::Level::Debug, "Error accepting%s: %s\n", disconnect ? " (will close the session)" : "", errmsg);
 211      }
 212      if (disconnect) {
 213          LOCK(m_mutex);
 214          Disconnect();
 215      } else {
 216          CheckControlSock();
 217      }
 218      return false;
 219  }
 220  
 221  bool Session::Connect(const CService& to, Connection& conn, bool& proxy_error)
 222  {
 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      if (to.GetPort() != I2P_SAM31_PORT) {
 226          LogPrintLevel(BCLog::I2P, BCLog::Level::Debug, "Error connecting to %s, connection refused due to arbitrary port %s\n", to.ToStringAddrPort(), to.GetPort());
 227          proxy_error = false;
 228          return false;
 229      }
 230  
 231      proxy_error = true;
 232  
 233      std::string session_id;
 234      std::unique_ptr<Sock> sock;
 235      conn.peer = to;
 236  
 237      try {
 238          {
 239              LOCK(m_mutex);
 240              CreateIfNotCreatedAlready();
 241              session_id = m_session_id;
 242              conn.me = m_my_addr;
 243              sock = Hello();
 244          }
 245  
 246          const Reply& lookup_reply =
 247              SendRequestAndGetReply(*sock, strprintf("NAMING LOOKUP NAME=%s", to.ToStringAddr()));
 248  
 249          const std::string& dest = lookup_reply.Get("VALUE");
 250  
 251          const Reply& connect_reply = SendRequestAndGetReply(
 252              *sock, strprintf("STREAM CONNECT ID=%s DESTINATION=%s SILENT=false", session_id, dest),
 253              false);
 254  
 255          const std::string& result = connect_reply.Get("RESULT");
 256  
 257          if (result == "OK") {
 258              conn.sock = std::move(sock);
 259              return true;
 260          }
 261  
 262          if (result == "INVALID_ID") {
 263              LOCK(m_mutex);
 264              Disconnect();
 265              throw std::runtime_error("Invalid session id");
 266          }
 267  
 268          if (result == "CANT_REACH_PEER" || result == "TIMEOUT") {
 269              proxy_error = false;
 270          }
 271  
 272          throw std::runtime_error(strprintf("\"%s\"", connect_reply.full));
 273      } catch (const std::runtime_error& e) {
 274          LogPrintLevel(BCLog::I2P, BCLog::Level::Debug, "Error connecting to %s: %s\n", to.ToStringAddrPort(), e.what());
 275          CheckControlSock();
 276          return false;
 277      }
 278  }
 279  
 280  // Private methods
 281  
 282  std::string Session::Reply::Get(const std::string& key) const
 283  {
 284      const auto& pos = keys.find(key);
 285      if (pos == keys.end() || !pos->second.has_value()) {
 286          throw std::runtime_error(
 287              strprintf("Missing %s= in the reply to \"%s\": \"%s\"", key, request, full));
 288      }
 289      return pos->second.value();
 290  }
 291  
 292  Session::Reply Session::SendRequestAndGetReply(const Sock& sock,
 293                                                 const std::string& request,
 294                                                 bool check_result_ok) const
 295  {
 296      sock.SendComplete(request + "\n", MAX_WAIT_FOR_IO, *m_interrupt);
 297  
 298      Reply reply;
 299  
 300      // Don't log the full "SESSION CREATE ..." because it contains our private key.
 301      reply.request = request.substr(0, 14) == "SESSION CREATE" ? "SESSION CREATE ..." : request;
 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      static constexpr auto recv_timeout = 3min;
 308  
 309      reply.full = sock.RecvUntilTerminator('\n', recv_timeout, *m_interrupt, MAX_MSG_SIZE);
 310  
 311      for (const auto& kv : Split(reply.full, ' ')) {
 312          const auto& pos = std::find(kv.begin(), kv.end(), '=');
 313          if (pos != kv.end()) {
 314              reply.keys.emplace(std::string{kv.begin(), pos}, std::string{pos + 1, kv.end()});
 315          } else {
 316              reply.keys.emplace(std::string{kv.begin(), kv.end()}, std::nullopt);
 317          }
 318      }
 319  
 320      if (check_result_ok && reply.Get("RESULT") != "OK") {
 321          throw std::runtime_error(
 322              strprintf("Unexpected reply to \"%s\": \"%s\"", reply.request, reply.full));
 323      }
 324  
 325      return reply;
 326  }
 327  
 328  std::unique_ptr<Sock> Session::Hello() const
 329  {
 330      auto sock = m_control_host.Connect();
 331  
 332      if (!sock) {
 333          throw std::runtime_error(strprintf("Cannot connect to %s", m_control_host.ToString()));
 334      }
 335  
 336      SendRequestAndGetReply(*sock, "HELLO VERSION MIN=3.1 MAX=3.1");
 337  
 338      return sock;
 339  }
 340  
 341  void Session::CheckControlSock()
 342  {
 343      LOCK(m_mutex);
 344  
 345      std::string errmsg;
 346      if (m_control_sock && !m_control_sock->IsConnected(errmsg)) {
 347          LogPrintLevel(BCLog::I2P, BCLog::Level::Debug, "Control socket error: %s\n", errmsg);
 348          Disconnect();
 349      }
 350  }
 351  
 352  void Session::DestGenerate(const Sock& sock)
 353  {
 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      const Reply& reply = SendRequestAndGetReply(sock, "DEST GENERATE SIGNATURE_TYPE=7", false);
 359  
 360      m_private_key = DecodeI2PBase64(reply.Get("PRIV"));
 361  }
 362  
 363  void Session::GenerateAndSavePrivateKey(const Sock& sock)
 364  {
 365      DestGenerate(sock);
 366  
 367      // umask is set to 0077 in common/system.cpp, which is ok.
 368      if (!WriteBinaryFile(m_private_key_file,
 369                           std::string(m_private_key.begin(), m_private_key.end()))) {
 370          throw std::runtime_error(
 371              strprintf("Cannot save I2P private key to %s", fs::quoted(fs::PathToString(m_private_key_file))));
 372      }
 373  }
 374  
 375  Binary Session::MyDestination() const
 376  {
 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      static constexpr size_t DEST_LEN_BASE = 387;
 381      static constexpr size_t CERT_LEN_POS = 385;
 382  
 383      uint16_t cert_len;
 384  
 385      if (m_private_key.size() < CERT_LEN_POS + sizeof(cert_len)) {
 386          throw std::runtime_error(strprintf("The private key is too short (%d < %d)",
 387                                             m_private_key.size(),
 388                                             CERT_LEN_POS + sizeof(cert_len)));
 389      }
 390  
 391      memcpy(&cert_len, &m_private_key.at(CERT_LEN_POS), sizeof(cert_len));
 392      cert_len = be16toh_internal(cert_len);
 393  
 394      const size_t dest_len = DEST_LEN_BASE + cert_len;
 395  
 396      if (dest_len > m_private_key.size()) {
 397          throw std::runtime_error(strprintf("Certificate length (%d) designates that the private key should "
 398                                             "be %d bytes, but it is only %d bytes",
 399                                             cert_len,
 400                                             dest_len,
 401                                             m_private_key.size()));
 402      }
 403  
 404      return Binary{m_private_key.begin(), m_private_key.begin() + dest_len};
 405  }
 406  
 407  void Session::CreateIfNotCreatedAlready()
 408  {
 409      std::string errmsg;
 410      if (m_control_sock && m_control_sock->IsConnected(errmsg)) {
 411          return;
 412      }
 413  
 414      const auto session_type = m_transient ? "transient" : "persistent";
 415      const auto session_id = GetRandHash().GetHex().substr(0, 10); // full is overkill, too verbose in the logs
 416  
 417      LogPrintLevel(BCLog::I2P, BCLog::Level::Debug, "Creating %s SAM session %s with %s\n", session_type, session_id, m_control_host.ToString());
 418  
 419      auto sock = Hello();
 420  
 421      if (m_transient) {
 422          // The destination (private key) is generated upon session creation and returned
 423          // in the reply in DESTINATION=.
 424          const Reply& reply = SendRequestAndGetReply(
 425              *sock,
 426              strprintf("SESSION CREATE STYLE=STREAM ID=%s DESTINATION=TRANSIENT SIGNATURE_TYPE=7 "
 427                        "i2cp.leaseSetEncType=4,0 inbound.quantity=1 outbound.quantity=1",
 428                        session_id));
 429  
 430          m_private_key = DecodeI2PBase64(reply.Get("DESTINATION"));
 431      } 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          const auto& [read_ok, data] = ReadBinaryFile(m_private_key_file);
 435          if (read_ok) {
 436              m_private_key.assign(data.begin(), data.end());
 437          } else {
 438              GenerateAndSavePrivateKey(*sock);
 439          }
 440  
 441          const std::string& private_key_b64 = SwapBase64(EncodeBase64(m_private_key));
 442  
 443          SendRequestAndGetReply(*sock,
 444                                 strprintf("SESSION CREATE STYLE=STREAM ID=%s DESTINATION=%s "
 445                                           "i2cp.leaseSetEncType=4,0 inbound.quantity=3 outbound.quantity=3",
 446                                           session_id,
 447                                           private_key_b64));
 448      }
 449  
 450      m_my_addr = CService(DestBinToAddr(MyDestination()), I2P_SAM31_PORT);
 451      m_session_id = session_id;
 452      m_control_sock = std::move(sock);
 453  
 454      LogPrintLevel(BCLog::I2P, BCLog::Level::Info, "%s SAM session %s created, my address=%s\n",
 455          Capitalize(session_type),
 456          m_session_id,
 457          m_my_addr.ToStringAddrPort());
 458  }
 459  
 460  std::unique_ptr<Sock> Session::StreamAccept()
 461  {
 462      auto sock = Hello();
 463  
 464      const Reply& reply = SendRequestAndGetReply(
 465          *sock, strprintf("STREAM ACCEPT ID=%s SILENT=false", m_session_id), false);
 466  
 467      const std::string& result = reply.Get("RESULT");
 468  
 469      if (result == "OK") {
 470          return sock;
 471      }
 472  
 473      if (result == "INVALID_ID") {
 474          // If our session id is invalid, then force session re-creation on next usage.
 475          Disconnect();
 476      }
 477  
 478      throw std::runtime_error(strprintf("\"%s\"", reply.full));
 479  }
 480  
 481  void Session::Disconnect()
 482  {
 483      if (m_control_sock) {
 484          if (m_session_id.empty()) {
 485              LogPrintLevel(BCLog::I2P, BCLog::Level::Info, "Destroying incomplete SAM session\n");
 486          } else {
 487              LogPrintLevel(BCLog::I2P, BCLog::Level::Info, "Destroying SAM session %s\n", m_session_id);
 488          }
 489          m_control_sock.reset();
 490      }
 491      m_session_id.clear();
 492  }
 493  } // namespace sam
 494  } // namespace i2p
 495