Add Functionality for Tor Runner / Torrc Builder
This commit is contained in:
parent
dec3660f46
commit
178f426c7e
|
@ -1,3 +1,6 @@
|
|||
/target
|
||||
Cargo.lock
|
||||
.idea/*
|
||||
examples/client_data_dir
|
||||
examples/server_data_dir
|
||||
*_rc
|
||||
|
|
|
@ -1,14 +1,16 @@
|
|||
[package]
|
||||
name = "tapir-cwtch"
|
||||
version = "0.1.7"
|
||||
version = "0.1.8"
|
||||
authors = ["Sarah Jamie Lewis <sarah@openprivacy.ca>"]
|
||||
edition = "2018"
|
||||
license = "MIT"
|
||||
description = "Tapir is a small library for building p2p applications over anonymous communication systems"
|
||||
repository = "https://git.openprivacy.ca/sarah/tapir-rs"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
|
||||
[features]
|
||||
|
||||
# Compile with support for launching v3 onion services
|
||||
onionv3 = []
|
||||
|
||||
[[test]]
|
||||
|
@ -23,6 +25,7 @@ merlin = "2.0.0"
|
|||
hex = "0.4.2"
|
||||
base32 = "0.4.0"
|
||||
base64 = "0.13.0"
|
||||
sha1 = "0.6.0"
|
||||
sha3 = "0.9.1"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0.61"
|
||||
|
@ -32,3 +35,4 @@ integer-encoding = "2.1.1"
|
|||
secretbox = "0.1.2"
|
||||
subtle = "2.3.0"
|
||||
hashbrown = "0.9.1"
|
||||
which = "4.0.2"
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
use rand::{thread_rng, Rng};
|
||||
use std::sync::Arc;
|
||||
use tapir_cwtch::acns::tor::run::TorRunner;
|
||||
use tapir_cwtch::acns::tor::torrc::TorrcGenerator;
|
||||
use tapir_cwtch::applications::authentication_app::{AuthenicationApp, AUTHENTICATION_CAPABILITY};
|
||||
use tapir_cwtch::connections::service::Service;
|
||||
use tapir_cwtch::connections::{Connection, ConnectionInterface, OutboundConnection};
|
||||
|
@ -9,7 +12,19 @@ fn main() {
|
|||
let identity = Arc::new(Identity::initialize_ephemeral_identity());
|
||||
println!("Setup: {}", identity.hostname());
|
||||
|
||||
let mut service = Service::init(identity.clone());
|
||||
let tor_path = which::which("tor");
|
||||
let mut rng = thread_rng();
|
||||
let socks_port = rng.gen_range(9052, 9100);
|
||||
let mut tor_runner = TorRunner::run(
|
||||
TorrcGenerator::new().with_socks_port(socks_port),
|
||||
"./example_client_rc",
|
||||
tor_path.unwrap().to_str().unwrap(),
|
||||
"./examples/client_data_dir/",
|
||||
)
|
||||
.unwrap();
|
||||
tor_runner.wait_until_bootstrapped();
|
||||
|
||||
let mut service = Service::init(identity.clone(), socks_port);
|
||||
let identity = identity.clone();
|
||||
let outbound_identity = identity.clone();
|
||||
let outbound_service = |conn: Connection<OutboundConnection>| {
|
||||
|
@ -24,7 +39,7 @@ fn main() {
|
|||
}
|
||||
}
|
||||
};
|
||||
match service.connect("qz4o3mqhzw6ye3jjadbi3l7g2rldbnkidf255iwsl4vfvrzsl26drfad", outbound_service.clone()) {
|
||||
match service.connect("srg3w256xrpcs6o25i5qlo6iviwsybnfw66nwelrbah7c5e6knb32xyd", outbound_service.clone()) {
|
||||
_ => {}
|
||||
}
|
||||
loop {}
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
use rand::{thread_rng, Rng};
|
||||
use std::sync::Arc;
|
||||
use tapir_cwtch::acns::tor::authentication::HashedPassword;
|
||||
use tapir_cwtch::acns::tor::run::TorRunner;
|
||||
use tapir_cwtch::acns::tor::torrc::TorrcGenerator;
|
||||
use tapir_cwtch::acns::tor::TorProcess;
|
||||
use tapir_cwtch::applications::authentication_app::AuthenicationApp;
|
||||
use tapir_cwtch::connections::service::Service;
|
||||
|
@ -8,7 +11,23 @@ use tapir_cwtch::primitives::identity::Identity;
|
|||
use tapir_cwtch::primitives::transcript::Transcript;
|
||||
|
||||
fn main() {
|
||||
let mut auth_control_port = TorProcess::connect(9051)
|
||||
let tor_path = which::which("tor");
|
||||
let mut rng = thread_rng();
|
||||
let socks_port = rng.gen_range(10052, 11100);
|
||||
let control_port = rng.gen_range(9052, 9100);
|
||||
let mut tor_runner = TorRunner::run(
|
||||
TorrcGenerator::new()
|
||||
.with_socks_port(socks_port)
|
||||
.with_control_port(control_port)
|
||||
.with_hashed_control_password("examplehashedpassword"),
|
||||
"./example_server_rc",
|
||||
tor_path.unwrap().to_str().unwrap(),
|
||||
"./examples/server_data_dir/",
|
||||
)
|
||||
.unwrap();
|
||||
tor_runner.wait_until_bootstrapped();
|
||||
|
||||
let mut auth_control_port = TorProcess::connect(control_port)
|
||||
.unwrap()
|
||||
.authenticate(Box::new(HashedPassword::new(String::from("examplehashedpassword"))))
|
||||
.unwrap();
|
||||
|
@ -20,7 +39,7 @@ fn main() {
|
|||
println!("Service Id: {}", service_id);
|
||||
println!("Setup: {}", identity.hostname());
|
||||
|
||||
let service = Service::init(identity.clone());
|
||||
let service = Service::init(identity.clone(), 0);
|
||||
|
||||
let inbound_service = |conn: Connection<InboundConnection>| {
|
||||
let mut transcript = Transcript::new_transcript("tapir-transcript");
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
#[cfg(any(feature = "onionv3"))]
|
||||
pub mod tor;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ACNError {
|
||||
ConfigurationError(String),
|
||||
AuthenticationError(String),
|
||||
ServiceSetupError(String),
|
||||
}
|
||||
|
|
|
@ -7,6 +7,8 @@ use std::io::{BufRead, BufReader, Error};
|
|||
use std::net::TcpStream;
|
||||
|
||||
pub mod authentication;
|
||||
pub mod run;
|
||||
pub mod torrc;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TorDisconnected(());
|
||||
|
|
|
@ -0,0 +1,75 @@
|
|||
use crate::acns::tor::torrc::TorrcGenerator;
|
||||
use crate::acns::ACNError;
|
||||
use std::io::{BufRead, BufReader};
|
||||
use std::process::{Child, Command, Stdio};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TorRunner {
|
||||
process: Box<Child>,
|
||||
}
|
||||
|
||||
impl TorRunner {
|
||||
/// run Tor at the given tor_path with the given torrc
|
||||
pub fn run(torrc: TorrcGenerator, torrc_path: &str, tor_path: &str, tor_data_dir: &str) -> Result<TorRunner, ACNError> {
|
||||
match torrc.build(torrc_path) {
|
||||
Ok(()) => {
|
||||
let child = Command::new(tor_path)
|
||||
.arg("-f")
|
||||
.arg(torrc_path)
|
||||
.arg("--DataDirectory")
|
||||
.arg(tor_data_dir)
|
||||
.stdout(Stdio::piped())
|
||||
.spawn()
|
||||
.unwrap();
|
||||
Ok(TorRunner { process: Box::new(child) })
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn wait_until_bootstrapped(&mut self) {
|
||||
let child_stdout = self.process.stdout.as_mut().unwrap();
|
||||
let mut reader = BufReader::new(child_stdout);
|
||||
loop {
|
||||
let mut line = String::new();
|
||||
reader.read_line(&mut line);
|
||||
if line.is_empty() == false {
|
||||
if line.contains("Bootstrapped 100% (done): Done") {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Kill Tor when the TorRunner drops out of scope
|
||||
impl Drop for TorRunner {
|
||||
fn drop(&mut self) {
|
||||
match self.process.kill() {
|
||||
Ok(()) => eprintln!("Killing Tor...{:?}", self.process.wait()),
|
||||
Err(_err) => eprintln!("Could not Kill Tor..."),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::acns::tor::run::TorRunner;
|
||||
use crate::acns::tor::torrc::TorrcGenerator;
|
||||
use std::fs::remove_file;
|
||||
use std::thread::sleep;
|
||||
use std::time::Duration;
|
||||
|
||||
#[test]
|
||||
fn test_run_tor() {
|
||||
match TorRunner::run(TorrcGenerator::new().with_socks_port(9055), "./torrc", "/usr/sbin/tor") {
|
||||
Ok(runner) => {
|
||||
sleep(Duration::new(5, 0));
|
||||
assert!(remove_file("./torrc").is_ok());
|
||||
println!("Runner {:?}", runner)
|
||||
}
|
||||
_ => panic!("tor did not run"),
|
||||
}
|
||||
sleep(Duration::new(10, 0));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,130 @@
|
|||
use crate::acns::ACNError;
|
||||
use crate::acns::ACNError::ConfigurationError;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
|
||||
/// Build a Torrc File with a set of configured options. Note: This is not a complete Torrc builder
|
||||
/// although it might one day grow to be one. As it stands it only allows the configuration options
|
||||
/// most necessary for Tor v3 Onion Service clients/servers as they related to Tapir/Cwtch.
|
||||
/// We use the Builder patter to allow easy programmatic construction for different Tor configurations.
|
||||
pub struct TorrcGenerator {
|
||||
socks_port: u16,
|
||||
control_port: u16,
|
||||
hashed_password: String,
|
||||
}
|
||||
|
||||
impl TorrcGenerator {
|
||||
/// Generate a new, empty Torrc Generator.
|
||||
pub fn new() -> TorrcGenerator {
|
||||
TorrcGenerator {
|
||||
socks_port: 0,
|
||||
control_port: 0,
|
||||
hashed_password: String::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Configure Tor to open a port to connect to external servers over Tor via SOCKS5
|
||||
pub fn with_socks_port(mut self, socks_port: u16) -> Self {
|
||||
self.socks_port = socks_port;
|
||||
self
|
||||
}
|
||||
|
||||
/// Configure Tor to have an open Control Port for configuration.
|
||||
pub fn with_control_port(mut self, control_port: u16) -> Self {
|
||||
self.control_port = control_port;
|
||||
self
|
||||
}
|
||||
|
||||
/// Force the tor control port to accept a hashed password as authentication, the given password
|
||||
/// is hashed with a random salt
|
||||
/// From the Tor Docs: If the 'HashedControlPassword' option is set, it must contain the salted
|
||||
/// hash of a secret password. The salted hash is computed according to the
|
||||
/// S2K algorithm in RFC 2440 (OpenPGP), and prefixed with the s2k specifier.
|
||||
/// This is then encoded in hexadecimal, prefixed by the indicator sequence
|
||||
/// "16:".
|
||||
pub fn with_hashed_control_password(mut self, password: &str) -> Self {
|
||||
let salt: [u8; 8] = rand::random();
|
||||
self.hashed_password = TorrcGenerator::generate_hashed_password(&salt, password);
|
||||
self
|
||||
}
|
||||
|
||||
/// generate_hashed_password calculates a hash in the same way tha tor --hash-password does
|
||||
/// this function takes a salt as input which is not great from an api-misuse perspective, but
|
||||
/// we make it private.
|
||||
fn generate_hashed_password(salt: &[u8; 8], password: &str) -> String {
|
||||
let c = 96;
|
||||
let mut count = (16 + (c & 15)) << ((c >> 4) + 6);
|
||||
let mut tmp = vec![];
|
||||
tmp.extend_from_slice(salt);
|
||||
tmp.extend_from_slice(password.as_bytes());
|
||||
let slen = tmp.len();
|
||||
let mut hash = sha1::Sha1::new();
|
||||
while count != 0 {
|
||||
if count > slen {
|
||||
hash.update(tmp.as_slice());
|
||||
count -= slen
|
||||
} else {
|
||||
let tmp_slice = tmp.as_slice();
|
||||
hash.update(tmp_slice.split_at(count).0);
|
||||
count = 0
|
||||
}
|
||||
}
|
||||
let hashed = hash.digest().bytes();
|
||||
return format!("16:{}{:X}{}", hex::encode_upper(salt), c, hex::encode_upper(hashed));
|
||||
}
|
||||
|
||||
/// Compile the final torrc file and write it to the file system.
|
||||
pub fn build(&self, file_name: &str) -> Result<(), ACNError> {
|
||||
let mut file = match File::create(file_name) {
|
||||
Err(why) => return Err(ConfigurationError(why.to_string())),
|
||||
Ok(file) => file,
|
||||
};
|
||||
let mut lines = vec![];
|
||||
|
||||
if self.socks_port != 0 {
|
||||
lines.push(format!("SocksPort {}", self.socks_port));
|
||||
}
|
||||
|
||||
if self.control_port != 0 {
|
||||
lines.push(format!("ControlPort {}", self.control_port));
|
||||
}
|
||||
|
||||
if self.hashed_password.is_empty() == false {
|
||||
lines.push(format!("HashedControlPassword {}", self.hashed_password));
|
||||
}
|
||||
|
||||
match file.write_all(lines.join("\n").as_bytes()) {
|
||||
Ok(_) => Ok(()),
|
||||
Err(why) => Err(ConfigurationError(why.to_string())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::acns::tor::torrc::TorrcGenerator;
|
||||
use std::fs::{read_to_string, remove_file};
|
||||
use std::path::Path;
|
||||
|
||||
#[test]
|
||||
fn generate_hashed_password() {
|
||||
let salt: [u8; 8] = [0xC1, 0x53, 0x05, 0xF9, 0x77, 0x89, 0x41, 0x4B];
|
||||
assert_eq!(
|
||||
String::from("16:C15305F97789414B601259E3EC5E76B8E55FC56A9F562B713F3D2BA257"),
|
||||
TorrcGenerator::generate_hashed_password(&salt, "examplehashedpassword")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn generate_torrc() {
|
||||
let gen = TorrcGenerator::new().with_control_port(9051).with_hashed_control_password("examplehashedpassword");
|
||||
match gen.build("./torrc") {
|
||||
Ok(()) => {
|
||||
assert!(Path::new("./torrc").exists());
|
||||
println!("{}", read_to_string("./torrc").unwrap());
|
||||
assert!(remove_file("./torrc").is_ok());
|
||||
}
|
||||
_ => assert!(false),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -13,6 +13,7 @@ pub struct ApplicationListenService(());
|
|||
|
||||
pub struct Service<ListenService> {
|
||||
identity: Arc<Identity>,
|
||||
socks_port: u16,
|
||||
listen_service: ListenService,
|
||||
}
|
||||
|
||||
|
@ -21,7 +22,7 @@ impl<ListenService> Service<ListenService> {
|
|||
where
|
||||
F: FnOnce(Connection<OutboundConnection>) + Send + Clone + 'static,
|
||||
{
|
||||
let conn = Socks5Stream::connect(format!("127.0.0.1:9050"), Domain(format!("{}.onion", hostname), 9878));
|
||||
let conn = Socks5Stream::connect(format!("127.0.0.1:{}", self.socks_port), Domain(format!("{}.onion", hostname), 9878));
|
||||
match conn {
|
||||
Ok(conn) => {
|
||||
let application = application.clone();
|
||||
|
@ -34,9 +35,10 @@ impl<ListenService> Service<ListenService> {
|
|||
}
|
||||
|
||||
impl Service<NoListenService> {
|
||||
pub fn init(identity: Arc<Identity>) -> Service<NoListenService> {
|
||||
pub fn init(identity: Arc<Identity>, socks_port: u16) -> Service<NoListenService> {
|
||||
Service {
|
||||
identity,
|
||||
socks_port,
|
||||
listen_service: NoListenService(()),
|
||||
}
|
||||
}
|
||||
|
@ -66,6 +68,7 @@ impl Service<NoListenService> {
|
|||
|
||||
Ok(Service {
|
||||
identity: self.identity,
|
||||
socks_port: self.socks_port,
|
||||
listen_service: jh,
|
||||
})
|
||||
}
|
||||
|
@ -94,7 +97,7 @@ mod tests {
|
|||
let keypair = ed25519_dalek::Keypair::generate(&mut csprng);
|
||||
let _secret_key = SecretKey::from_bytes(&keypair.secret.to_bytes());
|
||||
let identity = Identity::initialize(keypair);
|
||||
let _service = Service::init(identity);
|
||||
let _service = Service::init(identity, 9051);
|
||||
//let mut listen_service = service.listen(10000, TranscriptApp::new_instance()).unwrap_or_else(|_| panic!());
|
||||
// this will not compile! wish we could test that service.connect(Hostname{},TranscriptApp::new_instance());
|
||||
}
|
||||
|
@ -105,7 +108,7 @@ mod tests {
|
|||
let keypair = ed25519_dalek::Keypair::generate(&mut csprng);
|
||||
let _secret_key = SecretKey::from_bytes(&keypair.secret.to_bytes());
|
||||
let identity = Identity::initialize(keypair);
|
||||
let _service = Service::init(identity);
|
||||
let _service = Service::init(identity, 9051);
|
||||
//let listen_service = service.listen(1000, TranscriptApp::new_instance()).unwrap_or_else(|_| panic!());
|
||||
// TODO use trybuild to test that this fails: service.connect(Hostname{},TranscriptApp::new_instance());
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue