Branch data Line data Source code
# 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 : : */
# 37 : : static std::string SwapBase64(const std::string& from)
# 38 : 0 : {
# 39 : 0 : std::string to;
# 40 : 0 : to.resize(from.size());
# 41 [ # # ]: 0 : for (size_t i = 0; i < from.size(); ++i) {
# 42 : 0 : switch (from[i]) {
# 43 [ # # ]: 0 : case '-':
# 44 : 0 : to[i] = '+';
# 45 : 0 : break;
# 46 [ # # ]: 0 : case '~':
# 47 : 0 : to[i] = '/';
# 48 : 0 : break;
# 49 [ # # ]: 0 : case '+':
# 50 : 0 : to[i] = '-';
# 51 : 0 : break;
# 52 [ # # ]: 0 : case '/':
# 53 : 0 : to[i] = '~';
# 54 : 0 : break;
# 55 [ # # ]: 0 : default:
# 56 : 0 : to[i] = from[i];
# 57 : 0 : break;
# 58 : 0 : }
# 59 : 0 : }
# 60 : 0 : return to;
# 61 : 0 : }
# 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 : : */
# 69 : : static Binary DecodeI2PBase64(const std::string& i2p_b64)
# 70 : 0 : {
# 71 : 0 : const std::string& std_b64 = SwapBase64(i2p_b64);
# 72 : 0 : bool invalid;
# 73 : 0 : Binary decoded = DecodeBase64(std_b64.c_str(), &invalid);
# 74 [ # # ]: 0 : if (invalid) {
# 75 : 0 : throw std::runtime_error(strprintf("Cannot decode Base64: \"%s\"", i2p_b64));
# 76 : 0 : }
# 77 : 0 : return 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 : : static CNetAddr DestBinToAddr(const Binary& dest)
# 87 : 0 : {
# 88 : 0 : CSHA256 hasher;
# 89 : 0 : hasher.Write(dest.data(), dest.size());
# 90 : 0 : 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 : 0 : }
# 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 : : static CNetAddr DestB64ToAddr(const std::string& dest)
# 109 : 0 : {
# 110 : 0 : const Binary& decoded = DecodeI2PBase64(dest);
# 111 : 0 : return DestBinToAddr(decoded);
# 112 : 0 : }
# 113 : :
# 114 : : namespace sam {
# 115 : :
# 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 : 4 : {
# 122 : 4 : }
# 123 : :
# 124 : : Session::~Session()
# 125 : 4 : {
# 126 : 4 : LOCK(m_mutex);
# 127 : 4 : Disconnect();
# 128 : 4 : }
# 129 : :
# 130 : : bool Session::Listen(Connection& conn)
# 131 : 1 : {
# 132 : 1 : try {
# 133 : 1 : LOCK(m_mutex);
# 134 : 1 : CreateIfNotCreatedAlready();
# 135 : 1 : conn.me = m_my_addr;
# 136 : 1 : conn.sock = StreamAccept();
# 137 : 1 : return true;
# 138 : 1 : } catch (const std::runtime_error& e) {
# 139 : 1 : Log("Error listening: %s", e.what());
# 140 : 1 : CheckControlSock();
# 141 : 1 : }
# 142 : 1 : return false;
# 143 : 1 : }
# 144 : :
# 145 : : bool Session::Accept(Connection& conn)
# 146 : 0 : {
# 147 : 0 : try {
# 148 [ # # ]: 0 : while (!*m_interrupt) {
# 149 : 0 : Sock::Event occurred;
# 150 [ # # ]: 0 : if (!conn.sock->Wait(MAX_WAIT_FOR_IO, Sock::RECV, &occurred)) {
# 151 : 0 : throw std::runtime_error("wait on socket failed");
# 152 : 0 : }
# 153 : :
# 154 [ # # ]: 0 : if ((occurred & Sock::RECV) == 0) {
# 155 : : // Timeout, no incoming connections within MAX_WAIT_FOR_IO.
# 156 : 0 : continue;
# 157 : 0 : }
# 158 : :
# 159 : 0 : const std::string& peer_dest =
# 160 : 0 : conn.sock->RecvUntilTerminator('\n', MAX_WAIT_FOR_IO, *m_interrupt, MAX_MSG_SIZE);
# 161 : :
# 162 : 0 : conn.peer = CService(DestB64ToAddr(peer_dest), PORT_SAM31);
# 163 : :
# 164 : 0 : return true;
# 165 : 0 : }
# 166 : 0 : } catch (const std::runtime_error& e) {
# 167 : 0 : Log("Error accepting: %s", e.what());
# 168 : 0 : CheckControlSock();
# 169 : 0 : }
# 170 : 0 : return false;
# 171 : 0 : }
# 172 : :
# 173 : : bool Session::Connect(const CService& to, Connection& conn, bool& proxy_error)
# 174 : 4 : {
# 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 PORT_SAM31.
# 177 [ + + ]: 4 : if (to.GetPort() != PORT_SAM31) {
# 178 : 1 : proxy_error = false;
# 179 : 1 : return false;
# 180 : 1 : }
# 181 : :
# 182 : 3 : proxy_error = true;
# 183 : :
# 184 : 3 : std::string session_id;
# 185 : 3 : std::unique_ptr<Sock> sock;
# 186 : 3 : conn.peer = to;
# 187 : :
# 188 : 3 : try {
# 189 : 3 : {
# 190 : 3 : LOCK(m_mutex);
# 191 : 3 : CreateIfNotCreatedAlready();
# 192 : 3 : session_id = m_session_id;
# 193 : 3 : conn.me = m_my_addr;
# 194 : 3 : sock = Hello();
# 195 : 3 : }
# 196 : :
# 197 : 3 : const Reply& lookup_reply =
# 198 : 3 : SendRequestAndGetReply(*sock, strprintf("NAMING LOOKUP NAME=%s", to.ToStringIP()));
# 199 : :
# 200 : 3 : const std::string& dest = lookup_reply.Get("VALUE");
# 201 : :
# 202 : 3 : const Reply& connect_reply = SendRequestAndGetReply(
# 203 : 3 : *sock, strprintf("STREAM CONNECT ID=%s DESTINATION=%s SILENT=false", session_id, dest),
# 204 : 3 : false);
# 205 : :
# 206 : 3 : const std::string& result = connect_reply.Get("RESULT");
# 207 : :
# 208 [ - + ]: 3 : if (result == "OK") {
# 209 : 0 : conn.sock = std::move(sock);
# 210 : 0 : return true;
# 211 : 0 : }
# 212 : :
# 213 [ - + ]: 3 : if (result == "INVALID_ID") {
# 214 : 0 : LOCK(m_mutex);
# 215 : 0 : Disconnect();
# 216 : 0 : throw std::runtime_error("Invalid session id");
# 217 : 0 : }
# 218 : :
# 219 [ + - ][ # # ]: 3 : if (result == "CANT_REACH_PEER" || result == "TIMEOUT") {
# 220 : 0 : proxy_error = false;
# 221 : 0 : }
# 222 : :
# 223 : 3 : throw std::runtime_error(strprintf("\"%s\"", connect_reply.full));
# 224 : 3 : } catch (const std::runtime_error& e) {
# 225 : 3 : Log("Error connecting to %s: %s", to.ToString(), e.what());
# 226 : 3 : CheckControlSock();
# 227 : 3 : return false;
# 228 : 3 : }
# 229 : 3 : }
# 230 : :
# 231 : : // Private methods
# 232 : :
# 233 : : std::string Session::Reply::Get(const std::string& key) const
# 234 : 0 : {
# 235 : 0 : const auto& pos = keys.find(key);
# 236 [ # # ][ # # ]: 0 : if (pos == keys.end() || !pos->second.has_value()) {
# [ # # ]
# 237 : 0 : throw std::runtime_error(
# 238 : 0 : strprintf("Missing %s= in the reply to \"%s\": \"%s\"", key, request, full));
# 239 : 0 : }
# 240 : 0 : return pos->second.value();
# 241 : 0 : }
# 242 : :
# 243 : : template <typename... Args>
# 244 : : void Session::Log(const std::string& fmt, const Args&... args) const
# 245 : 12 : {
# 246 [ # # ][ + - ]: 12 : LogPrint(BCLog::I2P, "I2P: %s\n", tfm::format(fmt, args...));
# [ + - ][ + - ]
# 247 : 12 : }
# 248 : :
# 249 : : Session::Reply Session::SendRequestAndGetReply(const Sock& sock,
# 250 : : const std::string& request,
# 251 : : bool check_result_ok) const
# 252 : 2 : {
# 253 : 2 : sock.SendComplete(request + "\n", MAX_WAIT_FOR_IO, *m_interrupt);
# 254 : :
# 255 : 2 : Reply reply;
# 256 : :
# 257 : : // Don't log the full "SESSION CREATE ..." because it contains our private key.
# 258 [ - + ]: 2 : 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 : 2 : static constexpr auto recv_timeout = 3min;
# 265 : :
# 266 : 2 : reply.full = sock.RecvUntilTerminator('\n', recv_timeout, *m_interrupt, MAX_MSG_SIZE);
# 267 : :
# 268 [ - + ]: 2 : for (const auto& kv : spanparsing::Split(reply.full, ' ')) {
# 269 : 0 : const auto& pos = std::find(kv.begin(), kv.end(), '=');
# 270 [ # # ]: 0 : if (pos != kv.end()) {
# 271 : 0 : reply.keys.emplace(std::string{kv.begin(), pos}, std::string{pos + 1, kv.end()});
# 272 : 0 : } else {
# 273 : 0 : reply.keys.emplace(std::string{kv.begin(), kv.end()}, std::nullopt);
# 274 : 0 : }
# 275 : 0 : }
# 276 : :
# 277 [ - + ][ - + ]: 2 : if (check_result_ok && reply.Get("RESULT") != "OK") {
# [ # # ]
# 278 : 0 : throw std::runtime_error(
# 279 : 0 : strprintf("Unexpected reply to \"%s\": \"%s\"", request, reply.full));
# 280 : 0 : }
# 281 : :
# 282 : 2 : return reply;
# 283 : 2 : }
# 284 : :
# 285 : : std::unique_ptr<Sock> Session::Hello() const
# 286 : 4 : {
# 287 : 4 : auto sock = CreateSock(m_control_host);
# 288 : :
# 289 [ - + ]: 4 : if (!sock) {
# 290 : 0 : throw std::runtime_error("Cannot create socket");
# 291 : 0 : }
# 292 : :
# 293 [ + + ]: 4 : if (!ConnectSocketDirectly(m_control_host, *sock, nConnectTimeout, true)) {
# 294 : 2 : throw std::runtime_error(strprintf("Cannot connect to %s", m_control_host.ToString()));
# 295 : 2 : }
# 296 : :
# 297 : 2 : SendRequestAndGetReply(*sock, "HELLO VERSION MIN=3.1 MAX=3.1");
# 298 : :
# 299 : 2 : return sock;
# 300 : 2 : }
# 301 : :
# 302 : : void Session::CheckControlSock()
# 303 : 4 : {
# 304 : 4 : LOCK(m_mutex);
# 305 : :
# 306 : 4 : std::string errmsg;
# 307 [ + - ]: 4 : if (!m_control_sock->IsConnected(errmsg)) {
# 308 : 4 : Log("Control socket error: %s", errmsg);
# 309 : 4 : Disconnect();
# 310 : 4 : }
# 311 : 4 : }
# 312 : :
# 313 : : void Session::DestGenerate(const Sock& sock)
# 314 : 0 : {
# 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 : 0 : const Reply& reply = SendRequestAndGetReply(sock, "DEST GENERATE SIGNATURE_TYPE=7", false);
# 319 : :
# 320 : 0 : m_private_key = DecodeI2PBase64(reply.Get("PRIV"));
# 321 : 0 : }
# 322 : :
# 323 : : void Session::GenerateAndSavePrivateKey(const Sock& sock)
# 324 : 0 : {
# 325 : 0 : DestGenerate(sock);
# 326 : :
# 327 : : // umask is set to 077 in init.cpp, which is ok (unless -sysperms is given)
# 328 [ # # ]: 0 : if (!WriteBinaryFile(m_private_key_file,
# 329 : 0 : std::string(m_private_key.begin(), m_private_key.end()))) {
# 330 : 0 : throw std::runtime_error(
# 331 : 0 : strprintf("Cannot save I2P private key to %s", m_private_key_file));
# 332 : 0 : }
# 333 : 0 : }
# 334 : :
# 335 : : Binary Session::MyDestination() const
# 336 : 0 : {
# 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 : 0 : static constexpr size_t DEST_LEN_BASE = 387;
# 341 : 0 : static constexpr size_t CERT_LEN_POS = 385;
# 342 : :
# 343 : 0 : uint16_t cert_len;
# 344 : 0 : memcpy(&cert_len, &m_private_key.at(CERT_LEN_POS), sizeof(cert_len));
# 345 : 0 : cert_len = be16toh(cert_len);
# 346 : :
# 347 : 0 : const size_t dest_len = DEST_LEN_BASE + cert_len;
# 348 : :
# 349 : 0 : return Binary{m_private_key.begin(), m_private_key.begin() + dest_len};
# 350 : 0 : }
# 351 : :
# 352 : : void Session::CreateIfNotCreatedAlready()
# 353 : 4 : {
# 354 : 4 : std::string errmsg;
# 355 [ - + ]: 4 : if (m_control_sock->IsConnected(errmsg)) {
# 356 : 0 : return;
# 357 : 0 : }
# 358 : :
# 359 : 4 : Log("Creating SAM session with %s", m_control_host.ToString());
# 360 : :
# 361 : 4 : auto sock = Hello();
# 362 : :
# 363 : 4 : const auto& [read_ok, data] = ReadBinaryFile(m_private_key_file);
# 364 [ - + ]: 4 : if (read_ok) {
# 365 : 0 : m_private_key.assign(data.begin(), data.end());
# 366 : 4 : } else {
# 367 : 4 : GenerateAndSavePrivateKey(*sock);
# 368 : 4 : }
# 369 : :
# 370 : 4 : const std::string& session_id = GetRandHash().GetHex().substr(0, 10); // full is an overkill, too verbose in the logs
# 371 : 4 : const std::string& private_key_b64 = SwapBase64(EncodeBase64(m_private_key));
# 372 : :
# 373 : 4 : SendRequestAndGetReply(*sock, strprintf("SESSION CREATE STYLE=STREAM ID=%s DESTINATION=%s",
# 374 : 4 : session_id, private_key_b64));
# 375 : :
# 376 : 4 : m_my_addr = CService(DestBinToAddr(MyDestination()), PORT_SAM31);
# 377 : 4 : m_session_id = session_id;
# 378 : 4 : m_control_sock = std::move(sock);
# 379 : :
# 380 : 4 : LogPrintf("I2P: SAM session created: session id=%s, my address=%s\n", m_session_id,
# 381 : 4 : m_my_addr.ToString());
# 382 : 4 : }
# 383 : :
# 384 : : std::unique_ptr<Sock> Session::StreamAccept()
# 385 : 0 : {
# 386 : 0 : auto sock = Hello();
# 387 : :
# 388 : 0 : const Reply& reply = SendRequestAndGetReply(
# 389 : 0 : *sock, strprintf("STREAM ACCEPT ID=%s SILENT=false", m_session_id), false);
# 390 : :
# 391 : 0 : const std::string& result = reply.Get("RESULT");
# 392 : :
# 393 [ # # ]: 0 : if (result == "OK") {
# 394 : 0 : return sock;
# 395 : 0 : }
# 396 : :
# 397 [ # # ]: 0 : if (result == "INVALID_ID") {
# 398 : : // If our session id is invalid, then force session re-creation on next usage.
# 399 : 0 : Disconnect();
# 400 : 0 : }
# 401 : :
# 402 : 0 : throw std::runtime_error(strprintf("\"%s\"", reply.full));
# 403 : 0 : }
# 404 : :
# 405 : : void Session::Disconnect()
# 406 : 8 : {
# 407 [ - + ]: 8 : if (m_control_sock->Get() != INVALID_SOCKET) {
# 408 [ # # ]: 0 : if (m_session_id.empty()) {
# 409 : 0 : Log("Destroying incomplete session");
# 410 : 0 : } else {
# 411 : 0 : Log("Destroying session %s", m_session_id);
# 412 : 0 : }
# 413 : 0 : }
# 414 : 8 : m_control_sock->Reset();
# 415 : 8 : m_session_id.clear();
# 416 : 8 : }
# 417 : : } // namespace sam
# 418 : : } // namespace i2p
|