tor_interface/
tor_provider.rs

1// standard
2use std::any::Any;
3use std::boxed::Box;
4use std::convert::TryFrom;
5use std::io::{Read, Write};
6use std::net::{SocketAddr, TcpListener, TcpStream};
7use std::ops::{Deref, DerefMut};
8use std::str::FromStr;
9use std::sync::OnceLock;
10
11// extern crates
12use domain::base::name::Name;
13use idna::uts46::{Hyphens, Uts46};
14use idna::{domain_to_ascii_cow, AsciiDenyList};
15use regex::Regex;
16
17// internal crates
18use crate::tor_crypto::*;
19
20
21/// Various `tor_provider` errors.
22#[derive(thiserror::Error, Debug)]
23pub enum Error {
24    #[error("Failed to parse '{0}' as {1}")]
25    /// Failure parsing some string into a type
26    ParseFailure(String, String),
27
28    #[error("{0}")]
29    /// Other miscellaneous error
30    Generic(String),
31
32    #[error("Function not implemented")]
33    /// Placeholder for unimplemented functions
34    NotImplemented,
35}
36
37//
38// OnionAddr
39//
40
41/// A version 3 onion service address.
42///
43/// Version 3 Onion Service addresses const of a [`crate::tor_crypto::V3OnionServiceId`] and a 16-bit port number.
44#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
45pub struct OnionAddrV3 {
46    pub(crate) service_id: V3OnionServiceId,
47    pub(crate) virt_port: u16,
48}
49
50impl OnionAddrV3 {
51    /// Create a new `OnionAddrV3` from a [`crate::tor_crypto::V3OnionServiceId`] and port number.
52    pub fn new(service_id: V3OnionServiceId, virt_port: u16) -> OnionAddrV3 {
53        OnionAddrV3 {
54            service_id,
55            virt_port,
56        }
57    }
58
59    /// Return the service id associated with this onion address.
60    pub fn service_id(&self) -> &V3OnionServiceId {
61        &self.service_id
62    }
63
64    /// Return the port numebr associated with this onion address.
65    pub fn virt_port(&self) -> u16 {
66        self.virt_port
67    }
68}
69
70impl std::fmt::Display for OnionAddrV3 {
71    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
72        write!(f, "{}.onion:{}", self.service_id, self.virt_port)
73    }
74}
75
76/// An onion service address analog to [`std::net::SocketAddr`]
77#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
78pub enum OnionAddr {
79    V3(OnionAddrV3),
80}
81
82impl FromStr for OnionAddr {
83    type Err = Error;
84    fn from_str(s: &str) -> Result<Self, Self::Err> {
85        static ONION_SERVICE_PATTERN: OnceLock<Regex> = OnceLock::new();
86        let onion_service_pattern = ONION_SERVICE_PATTERN.get_or_init(|| {
87            Regex::new(r"(?m)^(?P<service_id>[a-z2-7]{56})\.onion:(?P<port>[1-9][0-9]{0,4})$")
88                .unwrap()
89        });
90
91        if let Some(caps) = onion_service_pattern.captures(s.to_lowercase().as_ref()) {
92            let service_id = caps
93                .name("service_id")
94                .expect("missing service_id group")
95                .as_str()
96                .to_lowercase();
97            let port = caps.name("port").expect("missing port group").as_str();
98            if let (Ok(service_id), Ok(port)) = (
99                V3OnionServiceId::from_string(service_id.as_ref()),
100                u16::from_str(port),
101            ) {
102                return Ok(OnionAddr::V3(OnionAddrV3::new(service_id, port)));
103            }
104        }
105        Err(Self::Err::ParseFailure(s.to_string(), "OnionAddr".to_string()))
106    }
107}
108
109impl std::fmt::Display for OnionAddr {
110    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
111        match self {
112            OnionAddr::V3(onion_addr) => onion_addr.fmt(f),
113        }
114    }
115}
116
117//
118// DomainAddr
119//
120
121/// A domain name analog to `std::net::SocketAddr`
122///
123/// A `DomainAddr` must not end in ".onion"
124#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
125pub struct DomainAddr {
126    domain: String,
127    port: u16,
128}
129
130/// A `DomainAddr` has a domain name (scuh as `www.example.com`) and a port
131impl DomainAddr {
132    /// Returns the domain name associated with this domain address.
133    pub fn domain(&self) -> &str {
134        self.domain.as_ref()
135    }
136
137    /// Returns the port number associated with this domain address.
138    pub fn port(&self) -> u16 {
139        self.port
140    }
141}
142
143impl std::fmt::Display for DomainAddr {
144    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
145        let uts46: Uts46 = Default::default();
146        let (ui_str, _err) = uts46.to_user_interface(
147            self.domain.as_str().as_bytes(),
148            AsciiDenyList::URL,
149            Hyphens::Allow,
150            |_, _, _| -> bool { false },
151        );
152        write!(f, "{}:{}", ui_str, self.port)
153    }
154}
155
156impl TryFrom<(String, u16)> for DomainAddr {
157    type Error = Error;
158
159    fn try_from(value: (String, u16)) -> Result<Self, Self::Error> {
160        let (domain, port) = (&value.0, value.1);
161        if let Ok(domain) = domain_to_ascii_cow(domain.as_bytes(), AsciiDenyList::URL) {
162            let domain = domain.to_string();
163            if let Ok(domain) = Name::<Vec<u8>>::from_str(domain.as_ref()) {
164                let domain = domain.to_string();
165                if !domain.ends_with(".onion") {
166                    return Ok(Self {
167                        domain,
168                        port,
169                    });
170                }
171            }
172        }
173        Err(Self::Error::ParseFailure(format!(
174            "{}:{}",
175            domain, port
176        ), "DomainAddr".to_string()))
177    }
178}
179
180impl FromStr for DomainAddr {
181    type Err = Error;
182    fn from_str(s: &str) -> Result<Self, Self::Err> {
183        static DOMAIN_PATTERN: OnceLock<Regex> = OnceLock::new();
184        let domain_pattern = DOMAIN_PATTERN
185            .get_or_init(|| Regex::new(r"(?m)^(?P<domain>.*):(?P<port>[1-9][0-9]{0,4})$").unwrap());
186        if let Some(caps) = domain_pattern.captures(s) {
187            let domain = caps
188                .name("domain")
189                .expect("missing domain group")
190                .as_str()
191                .to_string();
192            let port = caps.name("port").expect("missing port group").as_str();
193            if let Ok(port) = u16::from_str(port) {
194                return Self::try_from((domain, port));
195            }
196        }
197        Err(Self::Err::ParseFailure(s.to_string(), "DomainAddr".to_string()))
198    }
199}
200
201//
202// TargetAddr
203//
204
205/// An enum representing the various types of addresses a [`TorProvider`] implementation may connect to.
206#[derive(Clone, Debug, PartialEq, Eq)]
207pub enum TargetAddr {
208    /// An ip address and port
209    Socket(std::net::SocketAddr),
210    /// An onion-service id and virtual port
211    OnionService(OnionAddr),
212    /// A domain name and port
213    Domain(DomainAddr),
214}
215
216impl From<(V3OnionServiceId, u16)> for TargetAddr {
217    fn from(target_tuple: (V3OnionServiceId, u16)) -> Self {
218        TargetAddr::OnionService(OnionAddr::V3(OnionAddrV3::new(
219            target_tuple.0,
220            target_tuple.1,
221        )))
222    }
223}
224
225impl FromStr for TargetAddr {
226    type Err = Error;
227    fn from_str(s: &str) -> Result<Self, Self::Err> {
228        if let Ok(socket_addr) = SocketAddr::from_str(s) {
229            return Ok(TargetAddr::Socket(socket_addr));
230        } else if let Ok(onion_addr) = OnionAddr::from_str(s) {
231            return Ok(TargetAddr::OnionService(onion_addr));
232        } else if let Ok(domain_addr) = DomainAddr::from_str(s) {
233            return Ok(TargetAddr::Domain(domain_addr));
234        }
235        Err(Self::Err::ParseFailure(s.to_string(), "TargetAddr".to_string()))
236    }
237}
238
239impl std::fmt::Display for TargetAddr {
240    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
241        match self {
242            TargetAddr::Socket(socket_addr) => socket_addr.fmt(f),
243            TargetAddr::OnionService(onion_addr) => onion_addr.fmt(f),
244            TargetAddr::Domain(domain_addr) => domain_addr.fmt(f),
245        }
246    }
247}
248
249/// Various events possibly returned by a [`TorProvider`] implementation's `update()` method.
250#[derive(Debug)]
251pub enum TorEvent {
252    /// A status update received connecting to the Tor Network.
253    BootstrapStatus {
254        /// A number from 0 to 100 for how through the bootstrap process the `TorProvider` is.
255        progress: u32,
256        /// A short string to identify the current phase of the bootstrap process.
257        tag: String,
258        /// A longer string with a summary of the current phase of the bootstrap process.
259        summary: String,
260    },
261    /// Indicates successful connection to the Tor Network. The [`TorProvider::connect()`] and [`TorProvider::listener()`] methods may now be used.
262    BootstrapComplete,
263    /// Messages which may be useful for troubleshooting.
264    LogReceived {
265        /// A message
266        line: String,
267    },
268    /// An onion-service has been published to the Tor Network and may now be reachable by clients.
269    OnionServicePublished {
270        /// The service-id of the onion-service which has been published.
271        service_id: V3OnionServiceId,
272    },
273    /// TorProvider::connect_async() call completed successfully
274    ConnectComplete {
275        handle: ConnectHandle,
276        stream: OnionStream,
277    },
278    /// TorProvider::connect_async() call failed
279    ConnectFailed {
280        handle: ConnectHandle,
281        error: Error,
282    },
283}
284
285/// A `CircuitToken` is used to specify circuits used to connect to clearnet services.
286pub type CircuitToken = usize;
287
288/// A `ConnectHandle` is used to associate a connect request with returned stream
289pub type ConnectHandle = usize;
290
291//
292// Onion Stream
293//
294
295/// A wrapper around a [`std::net::TcpStream`] with some Tor-specific customisations
296///
297/// An onion-listener can be constructed using the [`TorProvider::connect()`] method.
298#[derive(Debug)]
299pub struct OnionStream {
300    pub(crate) stream: TcpStream,
301    pub(crate) local_addr: Option<OnionAddr>,
302    pub(crate) peer_addr: Option<TargetAddr>,
303}
304
305impl Deref for OnionStream {
306    type Target = TcpStream;
307    fn deref(&self) -> &Self::Target {
308        &self.stream
309    }
310}
311
312impl DerefMut for OnionStream {
313    fn deref_mut(&mut self) -> &mut Self::Target {
314        &mut self.stream
315    }
316}
317
318impl From<OnionStream> for TcpStream {
319    fn from(onion_stream: OnionStream) -> Self {
320        onion_stream.stream
321    }
322}
323
324impl Read for OnionStream {
325    fn read(&mut self, buf: &mut [u8]) -> Result<usize, std::io::Error> {
326        self.stream.read(buf)
327    }
328}
329
330impl Write for OnionStream {
331    fn write(&mut self, buf: &[u8]) -> Result<usize, std::io::Error> {
332        self.stream.write(buf)
333    }
334
335    fn flush(&mut self) -> Result<(), std::io::Error> {
336        self.stream.flush()
337    }
338}
339
340impl OnionStream {
341    /// Returns the target address of the remote peer of this onion connection.
342    pub fn peer_addr(&self) -> Option<TargetAddr> {
343        self.peer_addr.clone()
344    }
345
346    /// Returns the onion address of the local connection for an incoming onion-service connection. Returns `None` for outgoing connections.
347    pub fn local_addr(&self) -> Option<OnionAddr> {
348        self.local_addr.clone()
349    }
350
351    /// Tries to clone the underlying connection and data. A simple pass-through to [`std::net::TcpStream::try_clone()`].
352    pub fn try_clone(&self) -> Result<Self, std::io::Error> {
353        Ok(Self {
354            stream: self.stream.try_clone()?,
355            local_addr: self.local_addr.clone(),
356            peer_addr: self.peer_addr.clone(),
357        })
358    }
359}
360
361//
362// Onion Listener
363//
364
365/// A wrapper around a [`std::net::TcpListener`] with some Tor-specific customisations.
366///
367/// An onion-listener can be constructed using the [`TorProvider::listener()`] method.
368pub struct OnionListener {
369    pub(crate) listener: TcpListener,
370    pub(crate) onion_addr: OnionAddr,
371    pub(crate) data: Option<Box<dyn Any + Send>>,
372    pub(crate) drop: Option<Box<dyn FnMut(Box<dyn Any>) + Send>>,
373}
374
375impl OnionListener {
376    /// Construct an `OnionListener`. The `data` and `drop` parameters are to allow custom `TorProvider` implementations their own data and cleanup procedures.
377    pub(crate) fn new<T: 'static + Send>(
378        listener: TcpListener,
379        onion_addr: OnionAddr,
380        data: T,
381        mut drop: impl FnMut(T) + 'static + Send) -> Self {
382        // marshall our data into an Any
383        let data: Option<Box<dyn Any + Send>> = Some(Box::new(data));
384        // marhsall our drop into a function which takes an Any
385        let drop: Option<Box<dyn FnMut(Box<dyn Any>) + Send>>  = Some(Box::new(move |data: Box<dyn std::any::Any>| {
386            // encapsulate extracting our data from the Any
387            if let Ok(data) = data.downcast::<T>() {
388                // and call our provided drop
389                drop(*data);
390            }
391        }));
392
393        Self{
394            listener,
395            onion_addr,
396            data,
397            drop,
398        }
399    }
400
401    /// Moves the underlying `TcpListener` into or out of nonblocking mode.
402    pub fn set_nonblocking(&self, nonblocking: bool) -> Result<(), std::io::Error> {
403        self.listener.set_nonblocking(nonblocking)
404    }
405
406    /// Accept a new incoming connection from this listener.
407    pub fn accept(&self) -> Result<Option<OnionStream>, std::io::Error> {
408        match self.listener.accept() {
409            Ok((stream, _socket_addr)) => Ok(Some(OnionStream {
410                stream,
411                local_addr: Some(self.onion_addr.clone()),
412                peer_addr: None,
413            })),
414            Err(err) => {
415                if err.kind() == std::io::ErrorKind::WouldBlock {
416                    Ok(None)
417                } else {
418                    Err(err)
419                }
420            }
421        }
422    }
423}
424
425impl Drop for OnionListener {
426    fn drop(&mut self) {
427        if let (Some(data), Some(mut drop)) = (self.data.take(), self.drop.take()) {
428            drop(data)
429        }
430    }
431}
432
433/// The `TorProvider` trait allows for high-level Tor Network functionality. Implementations ay connect to the Tor Network, anonymously connect to both clearnet and onion-service endpoints, and host onion-services.
434pub trait TorProvider: Send {
435    /// Process and return `TorEvent`s handled by this `TorProvider`.
436    fn update(&mut self) -> Result<Vec<TorEvent>, Error>;
437    /// Begin connecting to the Tor Network.
438    fn bootstrap(&mut self) -> Result<(), Error>;
439    /// Add v3 onion-service authorisation credentials, allowing this `TorProvider` to connect to an onion-service whose service-descriptor is encrypted using the assocciated x25519 public key.
440    fn add_client_auth(
441        &mut self,
442        service_id: &V3OnionServiceId,
443        client_auth: &X25519PrivateKey,
444    ) -> Result<(), Error>;
445    /// Remove a previously added client authorisation credential. This `TorProvider` will be unable to connect to the onion-service associated with the removed credentail.
446    fn remove_client_auth(&mut self, service_id: &V3OnionServiceId) -> Result<(), Error>;
447    /// Anonymously connect to the address specified by `target` over the Tor Network and return the associated [`OnionStream`].
448    ///
449    /// When conecting to clearnet targets, an optional [`CircuitToken`] may be used to enforce usage of different circuits through the Tor Network. If `circuit` is `None`, the default circuit is used.
450    ///
451    ///Connections made with different `CircuitToken`s are required to use different circuits through the Tor Network. However, connections made with identical `CircuitToken`s are *not* required to use identical circuits through the Tor Network.
452    ///
453    /// Specifying a circuit token when connecting to an onion-service has no effect on the resulting circuit.
454    fn connect(
455        &mut self,
456        target: TargetAddr,
457        circuit: Option<CircuitToken>,
458    ) -> Result<OnionStream, Error>;
459    /// Anonymously connect to the address specified by `target` over the Tor Network. The resulting [`OnionStream`] (or failure) will be returned as a [`TorEvent`] from [`TorProvider::update()`].
460    ///
461    /// When conecting to clearnet targets, an optional [`CircuitToken`] may be used to enforce usage of different circuits through the Tor Network. If `circuit` is `None`, the default circuit is used.
462    ///
463    ///Connections made with different `CircuitToken`s are required to use different circuits through the Tor Network. However, connections made with identical `CircuitToken`s are *not* required to use identical circuits through the Tor Network.
464    ///
465    /// Specifying a circuit token when connecting to an onion-service has no effect on the resulting circuit.
466    fn connect_async(
467        &mut self,
468        _target: TargetAddr,
469        _circuit: Option<CircuitToken>,
470    ) -> Result<ConnectHandle, Error> {
471        Err(Error::NotImplemented)
472    }
473    /// Anonymously start an onion-service and return the associated [`OnionListener`].
474    ///
475    ///The resulting onion-service will not be reachable by clients until [`TorProvider::update()`] returns a [`TorEvent::OnionServicePublished`] event. The optional `authorised_clients` parameter may be used to require client authorisation keys to connect to resulting onion-service. For further information, see the Tor Project's onion-services [client-auth documentation](https://community.torproject.org/onion-services/advanced/client-auth).
476    fn listener(
477        &mut self,
478        private_key: &Ed25519PrivateKey,
479        virt_port: u16,
480        authorised_clients: Option<&[X25519PublicKey]>,
481    ) -> Result<OnionListener, Error>;
482    /// Create a new [`CircuitToken`].
483    fn generate_token(&mut self) -> CircuitToken;
484    /// Releaes a previously generated [`CircuitToken`].
485    fn release_token(&mut self, token: CircuitToken);
486}