@@ -6,6 +6,7 @@ use std::{
66 time:: Duration ,
77} ;
88
9+ use hashes:: sha3_256;
910use tokio:: {
1011 io:: { AsyncReadExt , AsyncWriteExt } ,
1112 net:: TcpStream ,
@@ -21,27 +22,114 @@ const CMD_CONNECT: u8 = 1;
2122const RESPONSE_SUCCESS : u8 = 0 ;
2223const RSV : u8 = 0 ;
2324const ADDR_TYPE_IPV4 : u8 = 1 ;
25+ const ADDR_TYPE_DOMAIN : u8 = 3 ;
2426const ADDR_TYPE_IPV6 : u8 = 4 ;
27+ // Tor constants
28+ const SALT : & [ u8 ] = b".onion checksum" ;
29+ const TOR_VERSION : u8 = 0x03 ;
30+ const ALPHABET : & [ u8 ; 32 ] = b"abcdefghijklmnopqrstuvwxyz234567" ;
31+
32+ fn pubkey_to_service ( ed25519 : [ u8 ; 32 ] ) -> String {
33+ let mut cs_input = Vec :: with_capacity ( 48 ) ;
34+ // SHA3(".onion checksum" + public key + version)
35+ cs_input. extend_from_slice ( SALT ) ;
36+ cs_input. extend_from_slice ( & ed25519) ;
37+ cs_input. push ( TOR_VERSION ) ;
38+ let cs = sha3_256:: hash ( & cs_input) . to_byte_array ( ) ;
39+ // Onion address = public key + 2 byte checksum + version
40+ let mut input_buf = [ 0u8 ; 35 ] ;
41+ input_buf[ ..32 ] . copy_from_slice ( & ed25519) ;
42+ input_buf[ 32 ] = cs[ 0 ] ;
43+ input_buf[ 33 ] = cs[ 1 ] ;
44+ input_buf[ 34 ] = TOR_VERSION ;
45+ let mut encoding = base32_encode ( & input_buf) ;
46+ debug_assert ! ( encoding. len( ) == 56 ) ;
47+ encoding. push_str ( ".onion" ) ;
48+ encoding
49+ }
50+
51+ fn base32_encode ( data : & [ u8 ] ) -> String {
52+ let mut result = String :: with_capacity ( ( data. len ( ) * 8 ) . div_ceil ( 5 ) ) ;
53+ let mut buffer: u64 = 0 ;
54+ let mut bits_left: u32 = 0 ;
55+ for & byte in data {
56+ buffer = ( buffer << 8 ) | byte as u64 ;
57+ bits_left += 8 ;
58+ while bits_left >= 5 {
59+ bits_left -= 5 ;
60+ let index = ( ( buffer >> bits_left) & 0x1f ) as usize ;
61+ result. push ( ALPHABET [ index] as char ) ;
62+ }
63+ // Keep only the unconsumed bits
64+ buffer &= ( 1u64 << bits_left) - 1 ;
65+ }
66+ if bits_left > 0 {
67+ let index = ( ( buffer << ( 5 - bits_left) ) & 0x1f ) as usize ;
68+ result. push ( ALPHABET [ index] as char ) ;
69+ }
70+ result
71+ }
72+
73+ #[ derive( Debug ) ]
74+ pub ( crate ) enum SocksConnection {
75+ ClearNet ( IpAddr ) ,
76+ OnionService ( [ u8 ; 32 ] ) ,
77+ }
78+
79+ impl SocksConnection {
80+ fn encode ( & self ) -> Vec < u8 > {
81+ match self {
82+ Self :: ClearNet ( net) => match net {
83+ IpAddr :: V4 ( ipv4) => ipv4. octets ( ) . to_vec ( ) ,
84+ IpAddr :: V6 ( ipv6) => ipv6. octets ( ) . to_vec ( ) ,
85+ } ,
86+ Self :: OnionService ( onion) => {
87+ let service = pubkey_to_service ( * onion) ;
88+ let enc = service. as_bytes ( ) ;
89+ let mut buf = Vec :: with_capacity ( enc. len ( ) + 1 ) ;
90+ buf. push ( enc. len ( ) as u8 ) ;
91+ buf. extend_from_slice ( enc) ;
92+ buf
93+ }
94+ }
95+ }
96+
97+ fn type_byte ( & self ) -> u8 {
98+ match self {
99+ Self :: ClearNet ( net) => match net {
100+ IpAddr :: V4 ( _) => ADDR_TYPE_IPV4 ,
101+ IpAddr :: V6 ( _) => ADDR_TYPE_IPV6 ,
102+ } ,
103+ Self :: OnionService ( _) => ADDR_TYPE_DOMAIN ,
104+ }
105+ }
106+ }
107+
108+ impl From < IpAddr > for SocksConnection {
109+ fn from ( value : IpAddr ) -> Self {
110+ Self :: ClearNet ( value)
111+ }
112+ }
113+
114+ impl From < [ u8 ; 32 ] > for SocksConnection {
115+ fn from ( value : [ u8 ; 32 ] ) -> Self {
116+ Self :: OnionService ( value)
117+ }
118+ }
25119
26120pub ( crate ) async fn create_socks5 (
27121 proxy : SocketAddr ,
28- ip_addr : IpAddr ,
122+ addr : SocksConnection ,
29123 port : u16 ,
30124) -> Result < TcpStream , Socks5Error > {
31125 // Connect to the proxy, likely a local Tor daemon.
32126 let timeout = tokio:: time:: timeout ( CONNECTION_TIMEOUT , TcpStream :: connect ( proxy) )
33127 . await
34128 . map_err ( |_| Socks5Error :: ConnectionTimeout ) ?;
35129 // Format the destination IP address and port according to the Socks5 spec
36- let dest_ip_bytes = match ip_addr {
37- IpAddr :: V4 ( ipv4) => ipv4. octets ( ) . to_vec ( ) ,
38- IpAddr :: V6 ( ipv6) => ipv6. octets ( ) . to_vec ( ) ,
39- } ;
130+ let dest_ip_bytes = addr. encode ( ) ;
40131 let dest_port_bytes = port. to_be_bytes ( ) ;
41- let ip_type_byte = match ip_addr {
42- IpAddr :: V4 ( _) => ADDR_TYPE_IPV4 ,
43- IpAddr :: V6 ( _) => ADDR_TYPE_IPV6 ,
44- } ;
132+ let ip_type_byte = addr. type_byte ( ) ;
45133 // Begin the handshake by requesting a connection to the proxy.
46134 let mut tcp_stream = timeout. map_err ( |_| Socks5Error :: ConnectionFailed ) ?;
47135 tcp_stream. write_all ( & [ VERSION , METHODS , NOAUTH ] ) . await ?;
@@ -81,9 +169,31 @@ pub(crate) async fn create_socks5(
81169 let mut buf = [ 0_u8 ; 18 ] ;
82170 tcp_stream. read_exact ( & mut buf) . await ?;
83171 }
172+ ADDR_TYPE_DOMAIN => {
173+ let mut len = [ 0_u8 ; 1 ] ;
174+ tcp_stream. read_exact ( & mut len) . await ?;
175+ let mut buf = vec ! [ 0_u8 ; u8 :: from_le_bytes( len) as usize ] ;
176+ tcp_stream. read_exact ( & mut buf) . await ?;
177+ }
84178 _ => return Err ( Socks5Error :: ConnectionFailed ) ,
85179 }
86180
87181 // Proxy handshake is complete, the TCP reader/writer can be returned
88182 Ok ( tcp_stream)
89183}
184+
185+ #[ cfg( test) ]
186+ mod tests {
187+ use super :: pubkey_to_service;
188+
189+ #[ test]
190+ fn public_key_to_service ( ) {
191+ let hex = "d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a" ;
192+ let hsid: [ u8 ; 32 ] = hex:: decode ( hex) . unwrap ( ) . try_into ( ) . unwrap ( ) ;
193+ let service = pubkey_to_service ( hsid) ;
194+ assert_eq ! (
195+ "25njqamcweflpvkl73j4szahhihoc4xt3ktcgjnpaingr5yhkenl5sid.onion" ,
196+ service
197+ ) ;
198+ }
199+ }
0 commit comments