Skip to content

Instantly share code, notes, and snippets.

@developer-commit
Created January 27, 2025 19:22
Show Gist options
  • Save developer-commit/9e76536902a1497c5af248ff6c995f32 to your computer and use it in GitHub Desktop.
Save developer-commit/9e76536902a1497c5af248ff6c995f32 to your computer and use it in GitHub Desktop.
This Rust code provides a client implementation for HTTPS communication over the Tor network
/*
***warning***
I have not checked with tools like Wireshark whether it was transmitted in an encrypted form.
arti-client = "0.24.0"
tokio = { version = "1", features = ["full"] }
tor-rtcompat = "0.24.0"
tokio-rustls = "0.26.0"
webpki-roots = "=0.26.7"
webpki = "0.22.4"
rustls-pki-types = "1.10.0"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
url = "2.5.4"
*/
use arti_client::{DataStream, TorClient, TorClientConfig};
use serde::Serialize;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio_rustls::client::TlsStream;
use std::collections::HashMap;
use std::sync::Arc;
use tokio_rustls::rustls::{ClientConfig, RootCertStore};
use tokio_rustls::TlsConnector;
use rustls_pki_types::ServerName;
use tor_rtcompat::PreferredRuntime;
struct TorStream{
stream : TlsStream<DataStream>
}
pub struct Client {
tls_connector : TlsConnector, // rustls lib
tor_connector : TorClient<PreferredRuntime>,
stream_map : HashMap<(String, u16), TorStream> // for recycling of connection
}
impl Client {
pub async fn new() -> Result<Self, Box<dyn std::error::Error>>{
let mut root_cert_store = RootCertStore::empty();
root_cert_store.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned());
let config = ClientConfig::builder()
.with_root_certificates(root_cert_store)
.with_no_client_auth();
let tls_connector = TlsConnector::from(Arc::new(config));
let config = TorClientConfig::default();
let tor_connector = TorClient::create_bootstrapped(config).await?;
Ok(
Self {
tls_connector,
tor_connector,
stream_map: HashMap::new()
}
)
}
//TLS Connect
async fn make_stream(
&mut self,
key: &(String, u16)
) -> Result<TorStream, Box<dyn std::error::Error>> {
let (host, port) = (&key.0, key.1);
let dnsname = ServerName::try_from(host.clone())
.map_err(|e| format!("Invalid DNS name: {}", e))?;
let tor_stream = self.tor_connector.connect((host.clone(), port)).await?;
let stream = match port {
443 => {
let tls_stream = self.tls_connector.connect(dnsname, tor_stream).await
.map_err(|e| format!("TLS connection failed: {}", e))?;
tls_stream
},
_ => return Err(format!("Unsupported port: {}", port).into()),
};
Ok(TorStream {
stream,
})
}
/* connection recycling */
async fn load_stream(
&mut self,
key: &(String, u16)
) -> Result<&mut TorStream, Box<dyn std::error::Error>>{
if !self.stream_map.contains_key(key) {
let stream = self.make_stream(key).await?;
self.stream_map.insert(key.clone(), stream);
}
Ok(self.stream_map.get_mut(key).ok_or("err")?)
}
// impl http GET
pub async fn get(
&mut self,
url: &str
) -> Result<String, Box<dyn std::error::Error>>
{
let (host, port, path) = Self::parse_url(url).await?;
let key = (host.clone(), port);
let tor_stream = self.load_stream(&key).await? ;
let request = format!(
"GET {} HTTP/1.1\r\nHost: {}\r\nConnection: close\r\n\r\n",
path, host
);
tor_stream.stream.write_all(request.as_bytes()).await?;
tor_stream.stream.flush().await?;
let mut response = Vec::new();
tor_stream.stream.read_to_end(&mut response).await?;
let res = String::from_utf8(response)?;
Ok(res)
}
// impl http POST
pub async fn post<T: Serialize>(
&mut self,
url: &str,
json_body: &T,
) -> Result<String, Box<dyn std::error::Error>>
{
let (host, port, path) = Self::parse_url(url).await?;
let key = (host.clone(), port);
let tor_stream = self.load_stream(&key).await? ;
let body = serde_json::to_string(json_body)?;
let content_length = body.len();
let request = format!(
"POST {} HTTP/1.1\r\nHost: {}\r\nContent-Type: application/json\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{}",
path, host, content_length, body
);
tor_stream.stream.write_all(request.as_bytes()).await?;
tor_stream.stream.flush().await?;
let mut response = Vec::new();
tor_stream.stream.read_to_end(&mut response).await?;
let res = String::from_utf8(response)?;
Ok(res)
}
pub async fn close(&mut self, key: &(String, u16)) -> Result<(), Box<dyn std::error::Error>>{
let stream = self.load_stream(&key).await?;
stream.stream.shutdown().await?;
Ok(())
}
/// Parses a URL into host, port, and path components.
pub async fn parse_url(url: &str) -> Result<(String, u16, String), Box<dyn std::error::Error>> {
let url = url::Url::parse(url)?;
let host = url.host_str().ok_or("Invalid URL: No host found")?.to_string();
let port = url.port_or_known_default().ok_or("Invalid URL: No port found")?;
let path = if url.path().is_empty() {
"/".to_string()
} else {
url.path().to_string()
};
Ok((host, port, path))
}
}
//main.rs
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut cli = tor_client::client::Client::new().await?;
let res = cli.get("https://api.ip.pe.kr/").await?;
print!("{}", res );
Ok(())
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment