1pub use tonic;
3
4pub use error::ConnectError;
5use error::InternalConnectError;
6use hyper::Uri;
7use hyper_util::client::legacy::connect::HttpConnector;
8use std::path::{Path, PathBuf};
9use std::str::FromStr;
10use tonic::codegen::InterceptedService;
11
12type Service = InterceptedService<
13 hyper_util::client::legacy::Client<
14 hyper_rustls::HttpsConnector<HttpConnector>,
15 tonic::body::Body,
16 >,
17 MacaroonInterceptor,
18>;
19
20#[cfg(feature = "lightningrpc")]
22pub type LightningClient = lnrpc::lightning_client::LightningClient<Service>;
23
24#[cfg(feature = "walletrpc")]
26pub type WalletKitClient = walletrpc::wallet_kit_client::WalletKitClient<Service>;
27
28#[cfg(feature = "peersrpc")]
30pub type PeersClient = peersrpc::peers_client::PeersClient<Service>;
31
32#[cfg(feature = "versionrpc")]
34pub type VersionerClient = verrpc::versioner_client::VersionerClient<Service>;
35
36#[cfg(feature = "signrpc")]
38pub type SignerClient = signrpc::signer_client::SignerClient<Service>;
39
40#[cfg(feature = "routerrpc")]
42pub type RouterClient = routerrpc::router_client::RouterClient<Service>;
43
44#[cfg(feature = "invoicesrpc")]
46pub type InvoicesClient = invoicesrpc::invoices_client::InvoicesClient<Service>;
47
48#[cfg(feature = "staterpc")]
50pub type StateClient = staterpc::state_client::StateClient<Service>;
51
52#[derive(Clone)]
56pub struct Client {
57 #[cfg(feature = "lightningrpc")]
58 lightning: LightningClient,
59 #[cfg(feature = "walletrpc")]
60 wallet: WalletKitClient,
61 #[cfg(feature = "signrpc")]
62 signer: SignerClient,
63 #[cfg(feature = "peersrpc")]
64 peers: PeersClient,
65 #[cfg(feature = "versionrpc")]
66 version: VersionerClient,
67 #[cfg(feature = "routerrpc")]
68 router: RouterClient,
69 #[cfg(feature = "invoicesrpc")]
70 invoices: InvoicesClient,
71 #[cfg(feature = "staterpc")]
72 state: StateClient,
73}
74
75impl Client {
76 #[cfg(feature = "lightningrpc")]
78 pub fn lightning(&mut self) -> &mut LightningClient {
79 &mut self.lightning
80 }
81
82 #[cfg(feature = "walletrpc")]
84 pub fn wallet(&mut self) -> &mut WalletKitClient {
85 &mut self.wallet
86 }
87
88 #[cfg(feature = "signrpc")]
90 pub fn signer(&mut self) -> &mut SignerClient {
91 &mut self.signer
92 }
93
94 #[cfg(feature = "versionrpc")]
96 pub fn versioner(&mut self) -> &mut VersionerClient {
97 &mut self.version
98 }
99
100 #[cfg(feature = "peersrpc")]
102 pub fn peers(&mut self) -> &mut PeersClient {
103 &mut self.peers
104 }
105
106 #[cfg(feature = "routerrpc")]
108 pub fn router(&mut self) -> &mut RouterClient {
109 &mut self.router
110 }
111
112 #[cfg(feature = "invoicesrpc")]
114 pub fn invoices(&mut self) -> &mut InvoicesClient {
115 &mut self.invoices
116 }
117
118 #[cfg(feature = "staterpc")]
120 pub fn state(&mut self) -> &mut StateClient {
121 &mut self.state
122 }
123}
124
125pub type Error = tonic::Status;
127
128mod error;
129
130macro_rules! try_map_err {
131 ($result:expr, $mapfn:expr) => {
132 match $result {
133 Ok(value) => value,
134 Err(error) => return Err($mapfn(error).into()),
135 }
136 };
137}
138
139#[cfg(feature = "lightningrpc")]
144pub mod lnrpc {
145 tonic::include_proto!("lnrpc");
146}
147
148#[cfg(feature = "walletrpc")]
149pub mod walletrpc {
150 tonic::include_proto!("walletrpc");
151}
152
153#[cfg(feature = "signrpc")]
154pub mod signrpc {
155 tonic::include_proto!("signrpc");
156}
157
158#[cfg(feature = "peersrpc")]
159pub mod peersrpc {
160 tonic::include_proto!("peersrpc");
161}
162
163#[cfg(feature = "routerrpc")]
164pub mod routerrpc {
165 tonic::include_proto!("routerrpc");
166}
167
168#[cfg(feature = "versionrpc")]
169pub mod verrpc {
170 tonic::include_proto!("verrpc");
171}
172
173#[cfg(feature = "invoicesrpc")]
174pub mod invoicesrpc {
175 tonic::include_proto!("invoicesrpc");
176}
177
178#[cfg(feature = "staterpc")]
179pub mod staterpc {
180 tonic::include_proto!("staterpc");
181}
182
183#[derive(Clone)]
185pub struct MacaroonInterceptor {
186 macaroon: String,
187}
188
189impl tonic::service::Interceptor for MacaroonInterceptor {
190 fn call(&mut self, mut request: tonic::Request<()>) -> Result<tonic::Request<()>, Error> {
191 request.metadata_mut().insert(
192 "macaroon",
193 tonic::metadata::MetadataValue::from_str(&self.macaroon)
194 .expect("hex produced non-ascii"),
195 );
196 Ok(request)
197 }
198}
199
200async fn load_macaroon(
201 path: impl AsRef<Path> + Into<PathBuf>,
202) -> Result<String, InternalConnectError> {
203 let macaroon =
204 tokio::fs::read(&path)
205 .await
206 .map_err(|error| InternalConnectError::ReadFile {
207 file: path.into(),
208 error,
209 })?;
210 Ok(hex::encode(macaroon))
211}
212
213#[cfg_attr(feature = "tracing", tracing::instrument(name = "Connecting to LND"))]
225pub async fn connect<CP, MP>(
226 address: String,
227 cert_file: CP,
228 macaroon_file: MP,
229) -> Result<Client, ConnectError>
230where
231 CP: AsRef<Path> + Into<PathBuf> + std::fmt::Debug,
232 MP: AsRef<Path> + Into<PathBuf> + std::fmt::Debug,
233{
234 let connector = hyper_rustls::HttpsConnectorBuilder::new()
235 .with_tls_config(tls::config(cert_file).await?)
236 .https_or_http()
237 .enable_http2()
238 .build();
239 let client = hyper_util::client::legacy::Client::builder(hyper_util::rt::TokioExecutor::new())
240 .build(connector);
241
242 let macaroon = load_macaroon(macaroon_file).await?;
244
245 let svc = InterceptedService::new(client, MacaroonInterceptor { macaroon });
246 let uri =
247 Uri::from_str(address.as_str()).map_err(|error| InternalConnectError::InvalidAddress {
248 address,
249 error: Box::new(error),
250 })?;
251
252 let client = Client {
253 #[cfg(feature = "lightningrpc")]
254 lightning: lnrpc::lightning_client::LightningClient::with_origin(svc.clone(), uri.clone()),
255 #[cfg(feature = "walletrpc")]
256 wallet: walletrpc::wallet_kit_client::WalletKitClient::with_origin(
257 svc.clone(),
258 uri.clone(),
259 ),
260 #[cfg(feature = "peersrpc")]
261 peers: peersrpc::peers_client::PeersClient::with_origin(svc.clone(), uri.clone()),
262 #[cfg(feature = "signrpc")]
263 signer: signrpc::signer_client::SignerClient::with_origin(svc.clone(), uri.clone()),
264 #[cfg(feature = "versionrpc")]
265 version: verrpc::versioner_client::VersionerClient::with_origin(svc.clone(), uri.clone()),
266 #[cfg(feature = "routerrpc")]
267 router: routerrpc::router_client::RouterClient::with_origin(svc.clone(), uri.clone()),
268 #[cfg(feature = "invoicesrpc")]
269 invoices: invoicesrpc::invoices_client::InvoicesClient::with_origin(
270 svc.clone(),
271 uri.clone(),
272 ),
273 #[cfg(feature = "staterpc")]
274 state: staterpc::state_client::StateClient::with_origin(svc.clone(), uri.clone()),
275 };
276 Ok(client)
277}
278
279mod tls {
280 use crate::error::{ConnectError, InternalConnectError};
281 use rustls::client::danger::ServerCertVerifier;
282 use rustls::client::danger::{HandshakeSignatureValid, ServerCertVerified};
283 use rustls::crypto::CryptoProvider;
284 use rustls::crypto::WebPkiSupportedAlgorithms;
285 use rustls::pki_types::{CertificateDer, ServerName, UnixTime};
286 use rustls::{client::ClientConfig, DigitallySignedStruct, Error as TLSError, SignatureScheme};
287 use std::{
288 path::{Path, PathBuf},
289 sync::Arc,
290 };
291
292 pub(crate) async fn config(
293 path: impl AsRef<Path> + Into<PathBuf>,
294 ) -> Result<ClientConfig, ConnectError> {
295 Ok(ClientConfig::builder()
296 .dangerous()
297 .with_custom_certificate_verifier(Arc::new(CertVerifier::load(path).await?))
298 .with_no_client_auth())
299 }
300
301 #[derive(Debug)]
302 pub(crate) struct CertVerifier {
303 certs: Vec<Vec<u8>>,
304 supported_algs: WebPkiSupportedAlgorithms,
305 }
306
307 impl CertVerifier {
308 pub(crate) async fn load(
309 path: impl AsRef<Path> + Into<PathBuf>,
310 ) -> Result<Self, InternalConnectError> {
311 let contents = try_map_err!(tokio::fs::read(&path).await, |error| {
312 InternalConnectError::ReadFile {
313 file: path.into(),
314 error,
315 }
316 });
317 let mut reader = &*contents;
318
319 let cert_der_vec = rustls_pemfile::certs(&mut reader).map_err(|error| {
320 InternalConnectError::ParseCert {
321 file: path.into(),
322 error,
323 }
324 })?;
325 let certs: Vec<Vec<u8>> = cert_der_vec
326 .into_iter()
327 .map(|cert_der| cert_der.to_vec())
328 .collect();
329
330 let provider = CryptoProvider::get_default()
331 .clone()
332 .expect("Must install default crypto provider");
333
334 Ok(CertVerifier {
335 certs,
336 supported_algs: provider.signature_verification_algorithms,
337 })
338 }
339 }
340
341 impl ServerCertVerifier for CertVerifier {
342 fn verify_server_cert(
343 &self,
344 end_entity: &CertificateDer,
345 intermediates: &[CertificateDer],
346 _server_name: &ServerName,
347 _ocsp_response: &[u8],
348 _now: UnixTime,
349 ) -> Result<ServerCertVerified, TLSError> {
350 let mut certs = intermediates
351 .iter()
352 .map(|c| c.as_ref().to_vec())
353 .collect::<Vec<Vec<u8>>>();
354 certs.push(end_entity.as_ref().to_vec());
355 certs.sort();
356
357 let mut our_certs = self.certs.clone();
358 our_certs.sort();
359
360 if self.certs.len() != certs.len() {
361 return Err(TLSError::General(format!(
362 "Mismatched number of certificates (Expected: {}, Presented: {})",
363 self.certs.len(),
364 certs.len()
365 )));
366 }
367 for (c, p) in our_certs.iter().zip(certs.iter()) {
368 if *p != *c {
369 return Err(TLSError::General(
370 "Server certificates do not match ours".to_string(),
371 ));
372 }
373 }
374 Ok(ServerCertVerified::assertion())
375 }
376
377 fn verify_tls12_signature(
378 &self,
379 message: &[u8],
380 cert: &CertificateDer,
381 dss: &DigitallySignedStruct,
382 ) -> Result<HandshakeSignatureValid, TLSError> {
383 rustls::crypto::verify_tls12_signature(message, cert, dss, &self.supported_algs)
384 }
385
386 fn verify_tls13_signature(
387 &self,
388 message: &[u8],
389 cert: &CertificateDer,
390 dss: &DigitallySignedStruct,
391 ) -> Result<HandshakeSignatureValid, TLSError> {
392 rustls::crypto::verify_tls13_signature(message, cert, dss, &self.supported_algs)
393 }
394
395 fn supported_verify_schemes(&self) -> Vec<SignatureScheme> {
396 self.supported_algs.supported_schemes()
397 }
398 }
399}