fedimint_api_client/api/
tor.rs1#[cfg(all(feature = "tor", not(target_family = "wasm")))]
2use std::fmt;
3use std::sync::Arc;
4
5use anyhow::anyhow;
6use arti_client::{TorAddr, TorClient, TorClientConfig};
7use async_trait::async_trait;
8use base64::Engine as _;
9use fedimint_core::util::SafeUrl;
10use jsonrpsee_ws_client::{HeaderMap, HeaderValue, WsClientBuilder};
11use tokio_rustls::TlsConnector;
12use tokio_rustls::rustls::{ClientConfig as TlsClientConfig, RootCertStore};
13use tracing::debug;
14
15use super::{Connector, DynGuaridianConnection};
16use crate::api::{IGuardianConnection as _, PeerError};
17
18#[derive(Clone)]
19pub struct TorConnector {
20 tor_client: TorClient<tor_rtcompat::PreferredRuntime>,
21}
22
23impl fmt::Debug for TorConnector {
24 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
25 f.debug_struct("TorEndpoint").finish_non_exhaustive()
26 }
27}
28
29impl TorConnector {
30 pub async fn bootstrap() -> anyhow::Result<Self> {
31 use tracing::debug;
32
33 use crate::api::PeerError;
34
35 let tor_config = TorClientConfig::default();
36 let tor_client = TorClient::create_bootstrapped(tor_config)
37 .await
38 .map_err(|err| PeerError::InternalClientError(err.into()))?
39 .isolated_client();
40
41 debug!("Successfully created and bootstrapped the `TorClient`, for given `TorConfig`.");
42
43 Ok(Self { tor_client })
44 }
45}
46
47#[async_trait]
48impl Connector for TorConnector {
49 #[allow(clippy::too_many_lines)]
50 async fn connect_guardian(
51 &self,
52 url: &SafeUrl,
53 api_secret: Option<&str>,
54 ) -> super::PeerResult<DynGuaridianConnection> {
55 let addr = (
56 url.host_str()
57 .ok_or_else(|| PeerError::InvalidEndpoint(anyhow!("Expected host str")))?,
58 url.port_or_known_default()
59 .ok_or_else(|| PeerError::InvalidEndpoint(anyhow!("Expected port number")))?,
60 );
61 let tor_addr = TorAddr::from(addr).map_err(|e| {
62 PeerError::InvalidEndpoint(anyhow!("Invalid endpoint addr: {addr:?}: {e:#}"))
63 })?;
64
65 let tor_addr_clone = tor_addr.clone();
66
67 debug!(
68 ?tor_addr,
69 ?addr,
70 "Successfully created `TorAddr` for given address (i.e. host and port)"
71 );
72
73 let anonymized_stream = if url.is_onion_address() {
76 let mut stream_prefs = arti_client::StreamPrefs::default();
77 stream_prefs.connect_to_onion_services(arti_client::config::BoolOrAuto::Explicit(true));
78
79 let anonymized_stream = self
80 .tor_client
81 .connect_with_prefs(tor_addr, &stream_prefs)
82 .await
83 .map_err(|e| PeerError::Connection(e.into()))?;
84
85 debug!(
86 ?tor_addr_clone,
87 "Successfully connected to onion address `TorAddr`, and established an anonymized `DataStream`"
88 );
89 anonymized_stream
90 } else {
91 let anonymized_stream = self
92 .tor_client
93 .connect(tor_addr)
94 .await
95 .map_err(|e| PeerError::Connection(e.into()))?;
96
97 debug!(
98 ?tor_addr_clone,
99 "Successfully connected to `Hostname`or `Ip` `TorAddr`, and established an anonymized `DataStream`"
100 );
101 anonymized_stream
102 };
103
104 let is_tls = match url.scheme() {
105 "wss" => true,
106 "ws" => false,
107 unexpected_scheme => {
108 return Err(PeerError::InvalidEndpoint(anyhow!(
109 "Unsupported scheme: {unexpected_scheme}"
110 )));
111 }
112 };
113
114 let tls_connector = if is_tls {
115 let webpki_roots = webpki_roots::TLS_SERVER_ROOTS.iter().cloned();
116 let mut root_certs = RootCertStore::empty();
117 root_certs.extend(webpki_roots);
118
119 let tls_config = TlsClientConfig::builder()
120 .with_root_certificates(root_certs)
121 .with_no_client_auth();
122 let tls_connector = TlsConnector::from(Arc::new(tls_config));
123 Some(tls_connector)
124 } else {
125 None
126 };
127
128 let mut ws_client_builder =
129 WsClientBuilder::default().max_concurrent_requests(u16::MAX as usize);
130
131 if let Some(api_secret) = api_secret {
132 let mut headers = HeaderMap::new();
136
137 let auth =
138 base64::engine::general_purpose::STANDARD.encode(format!("fedimint:{api_secret}"));
139
140 headers.insert(
141 "Authorization",
142 HeaderValue::from_str(&format!("Basic {auth}")).expect("Can't fail"),
143 );
144
145 ws_client_builder = ws_client_builder.set_headers(headers);
146 }
147
148 match tls_connector {
149 None => {
150 let client = ws_client_builder
151 .build_with_stream(url.as_str(), anonymized_stream)
152 .await
153 .map_err(|e| PeerError::Connection(e.into()))?;
154
155 Ok(client.into_dyn())
156 }
157 Some(tls_connector) => {
158 let host = url
159 .host_str()
160 .map(ToOwned::to_owned)
161 .ok_or_else(|| PeerError::InvalidEndpoint(anyhow!("Invalid host str")))?;
162
163 let server_name = rustls_pki_types::ServerName::try_from(host)
166 .map_err(|e| PeerError::InvalidEndpoint(e.into()))?;
167
168 let anonymized_tls_stream = tls_connector
169 .connect(server_name, anonymized_stream)
170 .await
171 .map_err(|e| PeerError::Connection(e.into()))?;
172
173 let client = ws_client_builder
174 .build_with_stream(url.as_str(), anonymized_tls_stream)
175 .await
176 .map_err(|e| PeerError::Connection(e.into()))?;
177
178 Ok(client.into_dyn())
179 }
180 }
181 }
182}