Skip to content

Commit ec81a72

Browse files
committed
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.
1 parent c47f81e commit ec81a72

File tree

3 files changed

+43
-7
lines changed

3 files changed

+43
-7
lines changed

src/netbase.cpp

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -725,6 +725,43 @@ bool IsProxy(const CNetAddr &addr) {
725725
return false;
726726
}
727727

728+
/**
729+
* Generate unique credentials for Tor stream isolation. Tor will create
730+
* separate circuits for SOCKS5 proxy connections with different credentials, which
731+
* makes it harder to correlate the connections.
732+
*/
733+
class TorStreamIsolationCredentialsGenerator
734+
{
735+
public:
736+
TorStreamIsolationCredentialsGenerator():
737+
m_prefix(GenerateUniquePrefix()) {
738+
}
739+
740+
/** Return the next unique proxy credentials. */
741+
ProxyCredentials Generate() {
742+
ProxyCredentials auth;
743+
auth.username = auth.password = strprintf("%s%i", m_prefix, m_counter);
744+
++m_counter;
745+
return auth;
746+
}
747+
748+
/** Size of session prefix in bytes. */
749+
static constexpr size_t PREFIX_BYTE_LENGTH = 8;
750+
private:
751+
const std::string m_prefix;
752+
std::atomic<uint64_t> m_counter;
753+
754+
/** Generate a random prefix for each of the credentials returned by this generator.
755+
* This makes sure that different launches of the application (either successively or in parallel)
756+
* will not share the same circuits, as would be the case with a bare counter.
757+
*/
758+
static std::string GenerateUniquePrefix() {
759+
std::array<uint8_t, PREFIX_BYTE_LENGTH> prefix_bytes;
760+
GetRandBytes(prefix_bytes);
761+
return HexStr(prefix_bytes) + "-";
762+
}
763+
};
764+
728765
std::unique_ptr<Sock> ConnectThroughProxy(const Proxy& proxy,
729766
const std::string& dest,
730767
uint16_t port,
@@ -739,9 +776,8 @@ std::unique_ptr<Sock> ConnectThroughProxy(const Proxy& proxy,
739776

740777
// do socks negotiation
741778
if (proxy.m_tor_stream_isolation) {
742-
ProxyCredentials random_auth;
743-
static std::atomic_int counter(0);
744-
random_auth.username = random_auth.password = strprintf("%i", counter++);
779+
static TorStreamIsolationCredentialsGenerator generator;
780+
ProxyCredentials random_auth{generator.Generate()};
745781
if (!Socks5(dest, port, &random_auth, *sock)) {
746782
return {};
747783
}

src/test/fuzz/i2p.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ FUZZ_TARGET(i2p, .init = initialize_i2p)
3434

3535
const fs::path private_key_path = gArgs.GetDataDirNet() / "fuzzed_i2p_private_key";
3636
const CService addr{in6_addr(IN6ADDR_LOOPBACK_INIT), 7656};
37-
const Proxy sam_proxy{addr, false};
37+
const Proxy sam_proxy{addr, /*tor_stream_isolation=*/false};
3838
CThreadInterrupt interrupt;
3939

4040
i2p::sam::Session session{private_key_path, sam_proxy, &interrupt};

src/test/i2p_tests.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ BOOST_AUTO_TEST_CASE(unlimited_recv)
5252

5353
CThreadInterrupt interrupt;
5454
const std::optional<CService> addr{Lookup("127.0.0.1", 9000, false)};
55-
const Proxy sam_proxy(addr.value(), false);
55+
const Proxy sam_proxy(addr.value(), /*tor_stream_isolation=*/false);
5656
i2p::sam::Session session(gArgs.GetDataDirNet() / "test_i2p_private_key", sam_proxy, &interrupt);
5757

5858
{
@@ -114,7 +114,7 @@ BOOST_AUTO_TEST_CASE(listen_ok_accept_fail)
114114

115115
CThreadInterrupt interrupt;
116116
const CService addr{in6_addr(IN6ADDR_LOOPBACK_INIT), /*port=*/7656};
117-
const Proxy sam_proxy(addr, false);
117+
const Proxy sam_proxy(addr, /*tor_stream_isolation=*/false);
118118
i2p::sam::Session session(gArgs.GetDataDirNet() / "test_i2p_private_key",
119119
sam_proxy,
120120
&interrupt);
@@ -157,7 +157,7 @@ BOOST_AUTO_TEST_CASE(damaged_private_key)
157157

158158
CThreadInterrupt interrupt;
159159
const CService addr{in6_addr(IN6ADDR_LOOPBACK_INIT), /*port=*/7656};
160-
const Proxy sam_proxy{addr, false};
160+
const Proxy sam_proxy{addr, /*tor_stream_isolation=*/false};
161161
i2p::sam::Session session(i2p_private_key_file, sam_proxy, &interrupt);
162162

163163
{

0 commit comments

Comments
 (0)