From ec81a72b369ab9efe23681ebb6e8fab34ce2e0f2 Mon Sep 17 00:00:00 2001 From: laanwj <126646+laanwj@users.noreply.github.com> Date: Mon, 31 Mar 2025 17:42:00 +0200 Subject: [PATCH] net: Add randomized prefix to Tor stream isolation credentials Add a class TorsStreamIsolationCredentialsGenerator that generates unique credentials based on a randomly generated session prefix and an atomic counter. This makes sure that different launches of the application won't share the same credentials, and thus circuits, even in edge cases. Example with `-debug=proxy`: ``` 2025-03-31T16:30:27Z [proxy] SOCKS5 sending proxy authentication 0afb2da441f5c105-0:0afb2da441f5c105-0 2025-03-31T16:30:31Z [proxy] SOCKS5 sending proxy authentication 0afb2da441f5c105-1:0afb2da441f5c105-1 ``` Thanks to hodlinator for the idea. --- src/netbase.cpp | 42 +++++++++++++++++++++++++++++++++++++++--- src/test/fuzz/i2p.cpp | 2 +- src/test/i2p_tests.cpp | 6 +++--- 3 files changed, 43 insertions(+), 7 deletions(-) diff --git a/src/netbase.cpp b/src/netbase.cpp index 61e4c8eb3e2..1edcd694557 100644 --- a/src/netbase.cpp +++ b/src/netbase.cpp @@ -725,6 +725,43 @@ bool IsProxy(const CNetAddr &addr) { return false; } +/** + * Generate unique credentials for Tor stream isolation. Tor will create + * separate circuits for SOCKS5 proxy connections with different credentials, which + * makes it harder to correlate the connections. + */ +class TorStreamIsolationCredentialsGenerator +{ +public: + TorStreamIsolationCredentialsGenerator(): + m_prefix(GenerateUniquePrefix()) { + } + + /** Return the next unique proxy credentials. */ + ProxyCredentials Generate() { + ProxyCredentials auth; + auth.username = auth.password = strprintf("%s%i", m_prefix, m_counter); + ++m_counter; + return auth; + } + + /** Size of session prefix in bytes. */ + static constexpr size_t PREFIX_BYTE_LENGTH = 8; +private: + const std::string m_prefix; + std::atomic m_counter; + + /** Generate a random prefix for each of the credentials returned by this generator. + * This makes sure that different launches of the application (either successively or in parallel) + * will not share the same circuits, as would be the case with a bare counter. + */ + static std::string GenerateUniquePrefix() { + std::array prefix_bytes; + GetRandBytes(prefix_bytes); + return HexStr(prefix_bytes) + "-"; + } +}; + std::unique_ptr ConnectThroughProxy(const Proxy& proxy, const std::string& dest, uint16_t port, @@ -739,9 +776,8 @@ std::unique_ptr ConnectThroughProxy(const Proxy& proxy, // do socks negotiation if (proxy.m_tor_stream_isolation) { - ProxyCredentials random_auth; - static std::atomic_int counter(0); - random_auth.username = random_auth.password = strprintf("%i", counter++); + static TorStreamIsolationCredentialsGenerator generator; + ProxyCredentials random_auth{generator.Generate()}; if (!Socks5(dest, port, &random_auth, *sock)) { return {}; } diff --git a/src/test/fuzz/i2p.cpp b/src/test/fuzz/i2p.cpp index b8024f7b1c4..29a11123d9a 100644 --- a/src/test/fuzz/i2p.cpp +++ b/src/test/fuzz/i2p.cpp @@ -34,7 +34,7 @@ FUZZ_TARGET(i2p, .init = initialize_i2p) const fs::path private_key_path = gArgs.GetDataDirNet() / "fuzzed_i2p_private_key"; const CService addr{in6_addr(IN6ADDR_LOOPBACK_INIT), 7656}; - const Proxy sam_proxy{addr, false}; + const Proxy sam_proxy{addr, /*tor_stream_isolation=*/false}; CThreadInterrupt interrupt; i2p::sam::Session session{private_key_path, sam_proxy, &interrupt}; diff --git a/src/test/i2p_tests.cpp b/src/test/i2p_tests.cpp index bb9ca88019c..bdb3408b66c 100644 --- a/src/test/i2p_tests.cpp +++ b/src/test/i2p_tests.cpp @@ -52,7 +52,7 @@ BOOST_AUTO_TEST_CASE(unlimited_recv) CThreadInterrupt interrupt; const std::optional addr{Lookup("127.0.0.1", 9000, false)}; - const Proxy sam_proxy(addr.value(), false); + const Proxy sam_proxy(addr.value(), /*tor_stream_isolation=*/false); i2p::sam::Session session(gArgs.GetDataDirNet() / "test_i2p_private_key", sam_proxy, &interrupt); { @@ -114,7 +114,7 @@ BOOST_AUTO_TEST_CASE(listen_ok_accept_fail) CThreadInterrupt interrupt; const CService addr{in6_addr(IN6ADDR_LOOPBACK_INIT), /*port=*/7656}; - const Proxy sam_proxy(addr, false); + const Proxy sam_proxy(addr, /*tor_stream_isolation=*/false); i2p::sam::Session session(gArgs.GetDataDirNet() / "test_i2p_private_key", sam_proxy, &interrupt); @@ -157,7 +157,7 @@ BOOST_AUTO_TEST_CASE(damaged_private_key) CThreadInterrupt interrupt; const CService addr{in6_addr(IN6ADDR_LOOPBACK_INIT), /*port=*/7656}; - const Proxy sam_proxy{addr, false}; + const Proxy sam_proxy{addr, /*tor_stream_isolation=*/false}; i2p::sam::Session session(i2p_private_key_file, sam_proxy, &interrupt); {