Add library to create a Tor service via Tor control port

This commit is contained in:
Philipp Hoenisch 2020-10-20 11:43:13 +11:00
parent ea064c95b4
commit 5e19949d71
No known key found for this signature in database
GPG Key ID: E5F8E74C672BC666
4 changed files with 178 additions and 3 deletions

View File

@ -12,20 +12,26 @@ cross-curve-dleq = { git = "https://github.com/comit-network/cross-curve-dleq",
curve25519-dalek = "2"
ecdsa_fun = { git = "https://github.com/LLFourn/secp256kfun", rev = "510d48ef6a2b19805f7f5c70c598e5b03f668e7a", features = ["libsecp_compat"] }
ed25519-dalek = "1.0.0-pre.4" # Cannot be 1 because they depend on curve25519-dalek version 3
lazy_static = "1.4"
miniscript = "1"
monero = "0.9"
rand = "0.7"
reqwest = { version = "0.10", default-features = false, features = ["socks"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
sha2 = "0.9"
thiserror = "1"
tokio = { version = "0.2", default-features = false, features = ["blocking", "macros", "rt-core", "time", "rt-threaded"] }
torut = "0.1"
tracing = "0.1"
[dev-dependencies]
base64 = "0.12"
bitcoin-harness = { git = "https://github.com/coblox/bitcoin-harness-rs", rev = "d402b36d3d6406150e3bfb71492ff4a0a7cb290e" }
futures = "0.3"
hyper = "0.13"
monero-harness = { path = "../monero-harness" }
reqwest = { version = "0.10", default-features = false }
spectral = "0.6"
testcontainers = "0.10"
tokio = { version = "0.2", default-features = false, features = ["blocking", "macros", "rt-core", "time", "rt-threaded"] }
tracing = "0.1"
tracing-subscriber = "0.2"
tracing-subscriber = "0.2"

View File

@ -49,4 +49,5 @@ pub mod alice;
pub mod bitcoin;
pub mod bob;
pub mod monero;
pub mod tor;
pub mod transport;

104
xmr-btc/src/tor.rs Normal file
View File

@ -0,0 +1,104 @@
use anyhow::{anyhow, bail, Result};
use lazy_static::lazy_static;
use std::{
future::Future,
net::{IpAddr, Ipv4Addr, SocketAddr, SocketAddrV4},
};
use tokio::net::TcpStream;
use torut::{
control::{AsyncEvent, AuthenticatedConn, ConnError, UnauthenticatedConn},
onion::TorSecretKeyV3,
};
lazy_static! {
/// The default TOR socks5 proxy address, `127.0.0.1:9050`.
pub static ref TOR_PROXY_ADDR: SocketAddrV4 = SocketAddrV4::new(Ipv4Addr::LOCALHOST, 9050);
/// The default TOR Controller Protocol address, `127.0.0.1:9051`.
pub static ref TOR_CP_ADDR: SocketAddr = SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::LOCALHOST, 9051));
}
/// checks if tor is running
async fn tor_running() -> Result<()> {
// Make sure you are running tor and this is your socks port
let proxy = reqwest::Proxy::all(format!("socks5h://{}", *TOR_PROXY_ADDR).as_str())
.expect("tor proxy should be there");
let client = reqwest::Client::builder()
.proxy(proxy)
.build()
.expect("should be able to build reqwest client");
let res = client.get("https://check.torproject.org").send().await?;
let text = res.text().await?;
let is_tor = text.contains("Congratulations. This browser is configured to use Tor.");
if is_tor {
Ok(())
} else {
bail!("Tor is currently not running")
}
}
type Handler = fn(AsyncEvent<'_>) -> Box<dyn Future<Output = Result<(), ConnError>> + Unpin>;
#[allow(missing_debug_implementations)]
pub struct AuthenticatedConnection(AuthenticatedConn<TcpStream, Handler>);
impl AuthenticatedConnection {
async fn init_unauthenticated_connection() -> Result<UnauthenticatedConn<TcpStream>> {
// try to connect to local tor service via control port
let sock = TcpStream::connect(*TOR_CP_ADDR).await?;
let unauthenticated_connection = UnauthenticatedConn::new(sock);
Ok(unauthenticated_connection)
}
/// Create a new authenticated connection to your local Tor service
pub async fn new() -> Result<Self> {
tor_running().await?;
let mut unauthenticated_connection = match Self::init_unauthenticated_connection().await {
Err(_) => bail!("Tor instance not running"),
Ok(unauthenticated_connection) => unauthenticated_connection,
};
let tor_info = match unauthenticated_connection.load_protocol_info().await {
Ok(info) => info,
Err(_) => bail!("Failed to load protocol info from Tor."),
};
let tor_auth_data = tor_info
.make_auth_data()?
.expect("Failed to make auth data.");
// Get an authenticated connection to the Tor via the Tor Controller protocol.
if unauthenticated_connection
.authenticate(&tor_auth_data)
.await
.is_err()
{
bail!("Failed to authenticate with Tor")
}
let authenticated_connection = unauthenticated_connection.into_authenticated().await;
Ok(AuthenticatedConnection(authenticated_connection))
}
/// Add an ephemeral tor service on localhost with the provided key
pub async fn add_service(&mut self, port: u16, tor_key: &TorSecretKeyV3) -> Result<()> {
self.0
.add_onion_v3(
tor_key,
false,
false,
false,
None,
&mut [(
port,
SocketAddr::new(IpAddr::from(Ipv4Addr::new(127, 0, 0, 1)), port),
)]
.iter(),
)
.await
.map_err(|_| anyhow!("Could not add onion service."))?;
Ok(())
}
}

64
xmr-btc/tests/tor.rs Normal file
View File

@ -0,0 +1,64 @@
use anyhow::Result;
use hyper::service::{make_service_fn, service_fn};
use reqwest::StatusCode;
use spectral::prelude::*;
use std::convert::Infallible;
use tokio::sync::oneshot::Receiver;
use torut::onion::TorSecretKeyV3;
use xmr_btc::tor::{AuthenticatedConnection, TOR_PROXY_ADDR};
async fn hello_world(
_req: hyper::Request<hyper::Body>,
) -> Result<hyper::Response<hyper::Body>, Infallible> {
Ok(hyper::Response::new("Hello World".into()))
}
fn start_test_service(port: u16, rx: Receiver<()>) {
let make_svc = make_service_fn(|_conn| async { Ok::<_, Infallible>(service_fn(hello_world)) });
let addr = ([127, 0, 0, 1], port).into();
let server = hyper::Server::bind(&addr).serve(make_svc);
let graceful = server.with_graceful_shutdown(async {
rx.await.ok();
});
tokio::spawn(async {
// server.await.unwrap();
if let Err(e) = graceful.await {
eprintln!("server error: {}", e);
}
});
}
#[tokio::test]
async fn test_tor_control_port() -> Result<()> {
// Setup test HTTP Server
let (tx, rx) = tokio::sync::oneshot::channel::<()>();
let port = 8080;
start_test_service(port, rx);
// Connect to local Tor service
let mut authenticated_connection = AuthenticatedConnection::new().await?;
// Expose an onion service that re-directs to the echo server.
let tor_secret_key_v3 = TorSecretKeyV3::generate();
authenticated_connection
.add_service(port, &tor_secret_key_v3)
.await?;
// Test if Tor service forwards to HTTP Server
let proxy = reqwest::Proxy::all(format!("socks5h://{}", *TOR_PROXY_ADDR).as_str())
.expect("tor proxy should be there");
let client = reqwest::Client::builder().proxy(proxy).build().unwrap();
let onion_address = tor_secret_key_v3.public().get_onion_address().to_string();
let onion_url = format!("http://{}:8080", onion_address);
let res = client.get(&onion_url).send().await?;
assert_that(&res.status()).is_equal_to(StatusCode::OK);
let text = res.text().await?;
assert_that!(text).contains("Hello World");
// gracefully shut down server
let _ = tx.send(());
Ok(())
}