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