tor_interface/
censorship_circumvention.rs

1// standard
2use std::net::SocketAddr;
3use std::path::PathBuf;
4use std::str::FromStr;
5use std::sync::OnceLock;
6
7// extern crates
8use regex::Regex;
9
10#[derive(Clone, Debug)]
11/// Configuration for a pluggable-transport
12pub struct PluggableTransportConfig {
13    transports: Vec<String>,
14    path_to_binary: PathBuf,
15    options: Vec<String>,
16}
17
18#[derive(thiserror::Error, Debug)]
19/// Error returned on failure to construct a [`PluggableTransportConfig`]
20pub enum PluggableTransportConfigError {
21    #[error("pluggable transport name '{0}' is invalid")]
22    /// transport names must be a valid C identifier
23    TransportNameInvalid(String),
24    #[error("unable to use '{0}' as pluggable transport binary path, {1}")]
25    /// configuration only allows aboslute paths to binaries
26    BinaryPathInvalid(String, String),
27}
28
29// per the PT spec: https://github.com/Pluggable-Transports/Pluggable-Transports-spec/blob/main/releases/PTSpecV1.0/pt-1_0.txt
30static TRANSPORT_PATTERN: OnceLock<Regex> = OnceLock::new();
31fn init_transport_pattern() -> Regex {
32    Regex::new(r"(?m)^[a-zA-Z_][a-zA-Z0-9_]*$").unwrap()
33}
34
35/// Configuration struct for a pluggable-transport which conforms to the v1.0 pluggable-transport [specification](https://github.com/Pluggable-Transports/Pluggable-Transports-spec/blob/main/releases/PTSpecV1.0/pt-1_0.txt)
36impl PluggableTransportConfig {
37    /// Construct a new `PluggableTransportConfig`. Each `transport` string must be a [valid C identifier](https://github.com/Pluggable-Transports/Pluggable-Transports-spec/blob/c92e59a9fa6ba11c181f4c5ec9d533eaa7d9d7f3/releases/PTSpecV1.0/pt-1_0.txt#L144) while `path_to_binary` must be an absolute path.
38    pub fn new(
39        transports: Vec<String>,
40        path_to_binary: PathBuf,
41    ) -> Result<Self, PluggableTransportConfigError> {
42        let transport_pattern = TRANSPORT_PATTERN.get_or_init(init_transport_pattern);
43        // validate each transport
44        for transport in &transports {
45            if !transport_pattern.is_match(&transport) {
46                return Err(PluggableTransportConfigError::TransportNameInvalid(
47                    transport.clone(),
48                ));
49            }
50        }
51
52        // pluggable transport path must be absolute so we can fix it up for individual
53        // TorProvider implementations
54        if !path_to_binary.is_absolute() {
55            return Err(PluggableTransportConfigError::BinaryPathInvalid(
56                format!("{:?}", path_to_binary.display()),
57                "must be an absolute path".to_string(),
58            ));
59        }
60
61        Ok(Self {
62            transports,
63            path_to_binary,
64            options: Default::default(),
65        })
66    }
67
68    /// Get a reference to this `PluggableTransportConfig`'s list of transports.
69    pub fn transports(&self) -> &Vec<String> {
70        &self.transports
71    }
72
73    /// Get a reference to this `PluggableTransportConfig`'s `PathBuf` containing the absolute path to the pluggable-transport binary.
74    pub fn path_to_binary(&self) -> &PathBuf {
75        &self.path_to_binary
76    }
77
78    /// Get a reference to this `PluggableTransportConfig`'s list of command-line options
79    pub fn options(&self) -> &Vec<String> {
80        &self.options
81    }
82
83    /// Add a command-line option used to invoke this pluggable-transport.
84    pub fn add_option(&mut self, arg: String) {
85        self.options.push(arg);
86    }
87}
88
89/// Configuration for a bridge line to be used with a pluggable-transport
90#[derive(Clone, Debug)]
91pub struct BridgeLine {
92    transport: String,
93    address: SocketAddr,
94    fingerprint: String,
95    keyvalues: Vec<(String, String)>,
96}
97
98#[derive(thiserror::Error, Debug)]
99/// Error returned on failure to construct a [`BridgeLine`]
100pub enum BridgeLineError {
101    #[error("bridge line '{0}' missing transport")]
102    /// Provided bridge line missing transport
103    TransportMissing(String),
104
105    #[error("bridge line '{0}' missing address")]
106    /// Provided bridge line missing address
107    AddressMissing(String),
108
109    #[error("bridge line '{0}' missing fingerprint")]
110    /// Provided bridge line missing fingerprint
111    FingerprintMissing(String),
112
113    #[error("transport name '{0}' is invalid")]
114    /// Invalid transport name (must be a valid C identifier)
115    TransportNameInvalid(String),
116
117    #[error("address '{0}' cannot be parsed as IP:PORT")]
118    /// Provided bridge line's address not parseable
119    AddressParseFailed(String),
120
121    #[error("key=value '{0}' is invalid")]
122    /// A key/value pair in invalid format
123    KeyValueInvalid(String),
124
125    #[error("bridge address port must not be 0")]
126    /// Invalid bridge address port
127    AddressPortInvalid,
128
129    #[error("fingerprint '{0}' is invalid")]
130    /// Fingerprint is not parseable (must be length 40 base16 string)
131    FingerprintInvalid(String),
132}
133
134/// A `BridgeLine` contains the information required to connect to a bridge through the means of a particular pluggable-transport (defined in a `PluggableTransportConfi`). For more information, see:
135/// - [https://tb-manual.torproject.org/bridges/](https://tb-manual.torproject.org/bridges/)
136impl BridgeLine {
137    /// Construct a new `BridgeLine` from its constiuent parts. The `transport` argument must be a valid C identifier and must have an associated `transport` defined in an associated `PluggableTransportConfig`. The `address` must have a non-zero port. The `fingerprint` is a length 40 base16-encoded string. Finally, the keys in the `keyvalues` list must not contain space (` `) or equal (`=`) characters.
138    ///
139    /// In practice, bridge lines are distributed as entire strings so most consumers of these APIs are not likely to need this particular function.
140    pub fn new(
141        transport: String,
142        address: SocketAddr,
143        fingerprint: String,
144        keyvalues: Vec<(String, String)>,
145    ) -> Result<BridgeLine, BridgeLineError> {
146        let transport_pattern = TRANSPORT_PATTERN.get_or_init(init_transport_pattern);
147
148        // transports have a particular pattern
149        if !transport_pattern.is_match(&transport) {
150            return Err(BridgeLineError::TransportNameInvalid(transport));
151        }
152
153        // port can't be 0
154        if address.port() == 0 {
155            return Err(BridgeLineError::AddressPortInvalid);
156        }
157
158        static BRIDGE_FINGERPRINT_PATTERN: OnceLock<Regex> = OnceLock::new();
159        let bridge_fingerprint_pattern = BRIDGE_FINGERPRINT_PATTERN
160            .get_or_init(|| Regex::new(r"(?m)^[0-9a-fA-F]{40}$").unwrap());
161
162        // fingerprint should be a sha1 hash
163        if !bridge_fingerprint_pattern.is_match(&fingerprint) {
164            return Err(BridgeLineError::FingerprintInvalid(fingerprint));
165        }
166
167        // validate key-values
168        for (key, value) in &keyvalues {
169            if key.contains(' ') || key.contains('=') || key.len() == 0 {
170                return Err(BridgeLineError::KeyValueInvalid(format!("{key}={value}")));
171            }
172        }
173
174        Ok(Self {
175            transport,
176            address,
177            fingerprint,
178            keyvalues,
179        })
180    }
181
182    /// Get a reference to this `BridgeLine`'s transport field.
183    pub fn transport(&self) -> &String {
184        &self.transport
185    }
186
187    /// Get a reference to this `BridgeLine`'s address field.
188    pub fn address(&self) -> &SocketAddr {
189        &self.address
190    }
191
192    /// Get a reference to this `BridgeLine`'s fingerprint field.
193    pub fn fingerprint(&self) -> &String {
194        &self.fingerprint
195    }
196
197    /// Get a reference to this `BridgeLine`'s key/values field.
198    pub fn keyvalues(&self) -> &Vec<(String, String)> {
199        &self.keyvalues
200    }
201
202    #[cfg(feature = "legacy-tor-provider")]
203    /// Serialise this `BridgeLine` to the value set via `SETCONF Bridge...` legacy c-tor control-port command.
204    pub fn as_legacy_tor_setconf_value(&self) -> String {
205        let transport = &self.transport;
206        let address = self.address.to_string();
207        let fingerprint = self.fingerprint.to_string();
208        let keyvalues: Vec<String> = self
209            .keyvalues
210            .iter()
211            .map(|(key, value)| format!("{key}={value}"))
212            .collect();
213        let keyvalues = keyvalues.join(" ");
214
215        format!("{transport} {address} {fingerprint} {keyvalues}")
216    }
217}
218
219impl FromStr for BridgeLine {
220    type Err = BridgeLineError;
221    fn from_str(s: &str) -> Result<Self, Self::Err> {
222        let mut tokens = s.split(' ');
223        // get transport name
224        let transport = if let Some(transport) = tokens.next() {
225            transport
226        } else {
227            return Err(BridgeLineError::TransportMissing(s.to_string()));
228        };
229        // get bridge address
230        let address = if let Some(address) = tokens.next() {
231            if let Ok(address) = SocketAddr::from_str(address) {
232                address
233            } else {
234                return Err(BridgeLineError::AddressParseFailed(address.to_string()));
235            }
236        } else {
237            return Err(BridgeLineError::AddressMissing(s.to_string()));
238        };
239        // get the bridge fingerprint
240        let fingerprint = if let Some(fingerprint) = tokens.next() {
241            fingerprint
242        } else {
243            return Err(BridgeLineError::FingerprintMissing(s.to_string()));
244        };
245
246        // get the bridge options
247        static BRIDGE_OPTION_PATTERN: OnceLock<Regex> = OnceLock::new();
248        let bridge_option_pattern = BRIDGE_OPTION_PATTERN
249            .get_or_init(|| Regex::new(r"(?m)^(?<key>[^=]+)=(?<value>.*)$").unwrap());
250
251        let mut keyvalues: Vec<(String, String)> = Default::default();
252        while let Some(keyvalue) = tokens.next() {
253            if let Some(caps) = bridge_option_pattern.captures(&keyvalue) {
254                let key = caps
255                    .name("key")
256                    .expect("missing key group")
257                    .as_str()
258                    .to_string();
259                let value = caps
260                    .name("value")
261                    .expect("missing value group")
262                    .as_str()
263                    .to_string();
264                keyvalues.push((key, value));
265            } else {
266                return Err(BridgeLineError::KeyValueInvalid(keyvalue.to_string()));
267            }
268        }
269
270        BridgeLine::new(
271            transport.to_string(),
272            address,
273            fingerprint.to_string(),
274            keyvalues,
275        )
276    }
277}