gosling/context.rs
1// standard
2use std::clone::Clone;
3use std::collections::{BTreeMap, HashMap, VecDeque};
4use std::net::TcpStream;
5use std::time::Duration;
6
7// extern crates
8use honk_rpc::honk_rpc::*;
9use tor_interface::tor_crypto::*;
10use tor_interface::tor_provider::*;
11
12// internal crates
13use crate::ascii_string::*;
14use crate::endpoint_client;
15use crate::endpoint_client::*;
16use crate::endpoint_server;
17use crate::endpoint_server::*;
18use crate::identity_client;
19use crate::identity_client::*;
20use crate::identity_server;
21use crate::identity_server::*;
22
23/// A handle to an in-progres identity or endpoint handshake
24pub type HandshakeHandle = usize;
25const DEFAULT_ENDPOINT_TIMEOUT: Duration = Duration::from_secs(60);
26const DEFAULT_ENDPOINT_MAX_MESSAGE_SIZE: i32 = 384;
27
28/// The error type for the [`Context`] type.
29#[derive(thiserror::Error, Debug)]
30pub enum Error {
31 /// An invalid argument was provided to a function
32 #[error("invalid argument: {0}")]
33 InvalidArgument(String),
34
35 /// Function requiring tor connectivity called before bootstrap
36 #[error(
37 "context is not connected, must call bootstrap() and wait for TorBootstrapCompleted event"
38 )]
39 TorNotConnected(),
40
41 /// Provided handle does not map to an in-flight handshake
42 #[error("handshake handle {0} not found")]
43 HandshakeHandleNotFound(HandshakeHandle),
44
45 /// Requesting an invalid operation
46 #[error("incorrect usage: {0}")]
47 IncorrectUsage(String),
48
49 /// An underlying `std::io::Error`
50 #[error(transparent)]
51 Io(#[from] std::io::Error),
52
53 /// An underlying `honk_rpc::honk_rpc::Error`
54 #[error(transparent)]
55 HonkRpc(#[from] honk_rpc::honk_rpc::Error),
56
57 /// An underlying `tor_interface::tor_crypto::Error`
58 #[error(transparent)]
59 TorCrypto(#[from] tor_interface::tor_crypto::Error),
60
61 /// An underlying `tor_interface::tor_provider::Error`
62 #[error(transparent)]
63 TorProvider(#[from] tor_interface::tor_provider::Error),
64
65 /// Failure ocurred in outgoing identity handshake
66 #[error(transparent)]
67 IdentityClientError(#[from] identity_client::Error),
68
69 /// Failure ocurred in incoming identity handshake
70 #[error(transparent)]
71 IdentityServerError(#[from] identity_server::Error),
72
73 /// Failure ocurred in outgoing endpoint handshake
74 #[error(transparent)]
75 EndpointClientError(#[from] endpoint_client::Error),
76
77 /// Failure ocurred in incoming endpoint handshake
78 #[error(transparent)]
79 EndpointServerError(#[from] endpoint_server::Error),
80}
81
82/// The gosling protocol implementation.
83///
84/// The `Context` object provides various methods for starting and progressing identity and endpoint handshakes. The general usage pattern developers will follow is to construct a `Context` object, connect to the Tor Network using [`Context::bootstrap()`], optionally start an identity or endpoint servers, and listen for and handle incoming identity and endpoint clients using [`Context::update()`] and the various associated methods. Depending on the application's requirements, the developer can also initiate identity and endpoint handshakes as necessary.
85///
86/// The Gosling Protocol specification can be found here:
87/// - [https://gosling.technology/gosling-spec.xhtml](https://gosling.technology/gosling-spec.xhtml)
88pub struct Context {
89 // our tor instance
90 tor_provider: Box<dyn TorProvider>,
91 bootstrap_complete: bool,
92 identity_port: u16,
93 endpoint_port: u16,
94 identity_timeout: Duration,
95 identity_max_message_size: i32,
96 endpoint_timeout: Duration,
97
98 //
99 // Servers and Clients for in-process handshakes
100 //
101 next_handshake_handle: HandshakeHandle,
102 identity_clients: BTreeMap<HandshakeHandle, IdentityClient>,
103 identity_servers: BTreeMap<HandshakeHandle, IdentityServer>,
104 endpoint_clients: BTreeMap<HandshakeHandle, EndpointClient>,
105 endpoint_servers: BTreeMap<HandshakeHandle, EndpointServer>,
106
107 //
108 // Listeners for incoming connections
109 //
110 identity_listener: Option<OnionListener>,
111 identity_server_published: bool,
112 // maps the endpoint service id to the (enpdoint name, alowed client, onion listener tuple, published)
113 endpoint_listeners: HashMap<V3OnionServiceId, (String, V3OnionServiceId, OnionListener, bool)>,
114
115 //
116 // Server Config Data
117 //
118
119 // Private key behind the identity onion service
120 identity_private_key: Ed25519PrivateKey,
121 // Identity server's service id
122 identity_service_id: V3OnionServiceId,
123}
124
125/// Events to signal completion of asynchronous [`Context`] operations
126#[derive(Debug)]
127pub enum ContextEvent {
128 //
129 // Tor Events
130 //
131
132 /// Tor bootstrap progress
133 TorBootstrapStatusReceived {
134 /// Bootstrap percent compeletion
135 progress: u32,
136 /// A short string indicating the completed bootstrap step
137 tag: String,
138 /// A longer human-readable summary of the bootstrap progress
139 summary: String,
140 },
141
142 /// Tor bootstrap completed
143 TorBootstrapCompleted,
144
145 /// Human-readable logs from the [`Context`]'s [`TorProvider`]
146 TorLogReceived {
147 /// Human-readable debug log
148 line: String,
149 },
150
151 //
152 // Identity Client Events
153 //
154
155 /// An identity client has received a challenge request from an identy server
156 ///
157 /// To continue the handshake, the client must call [`Context::identity_client_handle_challenge_received()`]
158 IdentityClientChallengeReceived {
159 /// The handle of the in-progress handshake
160 handle: HandshakeHandle,
161 /// An application specific challenge object used by the identity client to create a challenge response object
162 endpoint_challenge: bson::document::Document,
163 },
164
165 /// An identity client has successfully completed an identity handshake and may now access the requested endpoint server.
166 IdentityClientHandshakeCompleted {
167 /// The handle of the completed handshake
168 handle: HandshakeHandle,
169 /// The onion-service service-id of the identity server the client has completed an identity handshake with
170 identity_service_id: V3OnionServiceId,
171 /// The onion-service service-id of the requested endpoint server
172 endpoint_service_id: V3OnionServiceId,
173 /// The ASCII-encoded name of the requested endpoint server
174 endpoint_name: String,
175 /// The private x25519 client-auth key required to access the requested endpoint server
176 client_auth_private_key: X25519PrivateKey,
177 },
178
179 /// An incoming identit handshake has failed
180 IdentityClientHandshakeFailed {
181 /// The handle of the failed handshake
182 handle: HandshakeHandle,
183 /// The failure reason
184 reason: Error,
185 },
186
187 /// The identity server's onion-service has been published and may be reachable by identity clients
188 IdentityServerPublished,
189
190 /// An identity server has received an incoming connection and the handshake is ready to begin
191 IdentityServerHandshakeStarted {
192 /// The handle of the new handshake
193 handle: HandshakeHandle,
194 },
195
196 /// An identity server has received a request for an endpoint from an identity client.
197 ///
198 /// To continue the handshake, the server must call [`Context::identity_server_handle_endpoint_request_received()`]
199 IdentityServerEndpointRequestReceived {
200 /// The handle of the in-progress handshake
201 handle: HandshakeHandle,
202 /// The alleged onion-service service-id of the connecting client
203 client_service_id: V3OnionServiceId,
204 /// The ASCII-encoded name of the requested endpoint server
205 requested_endpoint: String,
206 },
207
208 /// An identity server has received a challenge response from an identity client.
209 ///
210 /// To continue the handshake, the server must call [`Context::identity_server_handle_challenge_response_received()`]
211 IdentityServerChallengeResponseReceived {
212 /// The handle of the in-progress handshake
213 handle: HandshakeHandle,
214 /// An application specific challenge response object created by the identity client in response to the identity server's challenge object
215 challenge_response: bson::document::Document,
216 },
217
218 /// An identity server's handshake has completed.
219 IdentityServerHandshakeCompleted {
220 /// The handle of the completed handshake
221 handle: HandshakeHandle,
222 /// The ed25519 private key of requested endpoint server
223 endpoint_private_key: Ed25519PrivateKey,
224 /// The ASCII-encoded name of the requested endpoint server
225 endpoint_name: String,
226 /// The onion-service service-id of the authenticated client
227 client_service_id: V3OnionServiceId,
228 /// The public x25519 client-auth key used to encrypt the endpoint server's onion-service descriptor
229 client_auth_public_key: X25519PublicKey,
230 },
231
232 /// An identity server has rejected an identity client's endpoint-request.
233 ///
234 /// There are multiple potential reasons why a handshake may be rejected and this event provides a breakdown on which part(s) failed specifically.
235 IdentityServerHandshakeRejected {
236 /// The handle of the rejected handshake
237 handle: HandshakeHandle,
238 /// `false` if the client was rejected based on their onion-service service-id
239 client_allowed: bool,
240 /// `false` if the requested endpoint name was not understood by the server
241 client_requested_endpoint_valid: bool,
242 /// `false` if the client failed its authentication proof (i.e. potential attempt at identity client impersonation)
243 client_proof_signature_valid: bool,
244 /// `false` if the client fails its x25519 key-ownership proof (i.e. potential attempt at use an x25519 public key not owned by the client)
245 client_auth_signature_valid: bool,
246 /// `false` if the client's challenge response was not suitable
247 challenge_response_valid: bool,
248 },
249
250 /// An incoming identity handshake has failed.
251 IdentityServerHandshakeFailed {
252 /// The handle of the failed handshake
253 handle: HandshakeHandle,
254 /// The failure reason
255 reason: Error,
256 },
257
258 //
259 // Endpoint Client Events
260 //
261
262 /// An endpoint client has successfully completed an endpoint handshake and may now communicate freely with the endpoint server.
263 EndpointClientHandshakeCompleted {
264 /// The handle of the completed handshake
265 handle: HandshakeHandle,
266 /// The onion-service service-id of the endpoint server the client has connected to
267 endpoint_service_id: V3OnionServiceId,
268 /// The ASCII-encoded name of the requested channel on the endpoint server
269 channel_name: String,
270 /// The resulting TCP connection to the endpoint server
271 stream: TcpStream,
272 },
273
274 /// An outgoing endpoint handshake has failed.
275 EndpointClientHandshakeFailed {
276 /// The handle of the failed handshake
277 handle: HandshakeHandle,
278 /// The failure reason
279 reason: Error,
280 },
281
282 //
283 // Endpint Server Events
284 //
285
286 /// The endpoint server’s onion-service has been published and may be reachable by endpoint clients.
287 EndpointServerPublished {
288 /// The onion-service service-id of the published endpoint server
289 endpoint_service_id: V3OnionServiceId,
290 /// The name of the published endpoint server
291 endpoint_name: String,
292 },
293
294 /// An endpoint server has received an incoming connection and the handshake is ready to begin.
295 EndpointServerHandshakeStarted {
296 /// The handle of the new handshake
297 handle: HandshakeHandle,
298 },
299
300 /// An endpoint server has received a request for a channel from an endpoint client.
301 ///
302 /// To continue the handshake, the server must call [`Context::endpoint_server_handle_channel_request_received()`]
303 EndpointServerChannelRequestReceived {
304 /// The handle of the in-progress handshake
305 handle: HandshakeHandle,
306 /// The alleged onion-service service-id of the connecting client
307 client_service_id: V3OnionServiceId,
308 /// The ASCII-encoded name of the requested channel
309 requested_channel: String,
310 },
311
312 /// An endpoint server's handshake has completed
313 EndpointServerHandshakeCompleted {
314 /// The handle of the completed handshake
315 handle: HandshakeHandle,
316 /// The onion-service service-id of the endpoint server which an endpoint client has connected to
317 endpoint_service_id: V3OnionServiceId,
318 /// The onion-service service-id of the connected client
319 client_service_id: V3OnionServiceId,
320 /// The ASCII-encoded name of the client's requested channel
321 channel_name: String,
322 /// The resulting TCP connection to tohe endpoint clientt
323 stream: TcpStream,
324 },
325
326 /// An endpoint server has rejected an endpoint client's channel request.
327 ///
328 /// There are multiple potential reasons why a handshake may be rejected and this event provides a breakdown on which part(s) failed specifically.
329 EndpointServerHandshakeRejected {
330 /// The handle of the rejected handshake
331 handle: HandshakeHandle,
332 /// `false` if the client was rejected based on their onion-service service-id
333 client_allowed: bool,
334 /// `false` if the requested channel name was not understood by the server
335 client_requested_channel_valid: bool,
336 /// `false` if the client failed its authentication proof (i.e. potential attempt at endpoint client impersonation)
337 client_proof_signature_valid: bool,
338 },
339
340 /// An incoming endpoint handshake has failed.
341 EndpointServerHandshakeFailed {
342 /// The handle of the failed handshake
343 handle: HandshakeHandle,
344 /// The failure reason
345 reason: Error,
346 },
347}
348
349impl Context {
350 /// Construct a new `Context` object.
351 ///
352 /// # Parameters
353 /// - `tor_provider`: an implementation of the [`TorProvider`] trait which provides our Tor Network connectivity
354 /// - `identity_port`: the virt-port this `Context`'s identity server's onion-service will listen on for new identity handshakes.
355 /// - `endpoint_port`: the virt-port this `Context`'s endpoint servers' onion-services will listen on for new endpoint handshakes.
356 /// - `identity_timeout`: the maximum amount of time this `Context`' will allow an identity handshake to delay between steps before rejecting the request.
357 /// - `identity_max_message_size`: the maximum size of the underlying Honk-RPC BSON message this `Context`'s identity handshake will send and accept.
358 /// - `endpoint_timeout`: the maximum amount of time this `Context`' will allow an endpoint handshake to delay between steps before rejecting the request.
359 /// - `identity_private_key`: the ed25519 private key used to start this `Context`'s identity server's onion-service
360 /// # Returns
361 /// A newly constructed `Context`.
362 pub fn new(
363 tor_provider: Box<dyn TorProvider>,
364 identity_port: u16,
365 endpoint_port: u16,
366 identity_timeout: Duration,
367 identity_max_message_size: i32,
368 endpoint_timeout: Option<Duration>,
369 identity_private_key: Ed25519PrivateKey,
370 ) -> Result<Self, Error> {
371 let identity_service_id = V3OnionServiceId::from_private_key(&identity_private_key);
372
373 Ok(Self {
374 tor_provider,
375 bootstrap_complete: false,
376 identity_port,
377 identity_max_message_size,
378 endpoint_port,
379 identity_timeout,
380 endpoint_timeout: match endpoint_timeout {
381 Some(timeout) => timeout,
382 None => DEFAULT_ENDPOINT_TIMEOUT,
383 },
384
385 next_handshake_handle: Default::default(),
386 identity_clients: Default::default(),
387 identity_servers: Default::default(),
388 endpoint_clients: Default::default(),
389 endpoint_servers: Default::default(),
390
391 identity_listener: None,
392 identity_server_published: false,
393 endpoint_listeners: Default::default(),
394
395 identity_private_key,
396 identity_service_id,
397 })
398 }
399
400 /// Initiate bootstrap of the `Context`'s owned [`TorProvider`]. Bootstrap status is communicated through [`ContextEvent`]s returned from the [`Context::update()`] method.
401 pub fn bootstrap(&mut self) -> Result<(), Error> {
402 self.tor_provider.bootstrap()?;
403 Ok(())
404 }
405
406 /// Initiate an identity handshake with an identity server. Handshake progression is communicated through [`ContextEvent`]s returned from the [`Context::update()`] method.
407 ///
408 /// # Parameters
409 /// - `identitity_server_id`: the long term identity onion-service service-id of a remote peer
410 /// - `endpoint`: the ASCII-encoded requested endpoint
411 /// # Returns
412 /// A `HandshakeHandle` used to refer to this particular identity handshake.
413 pub fn identity_client_begin_handshake(
414 &mut self,
415 identity_server_id: V3OnionServiceId,
416 endpoint: String,
417 ) -> Result<HandshakeHandle, Error> {
418 let endpoint = match AsciiString::new(endpoint) {
419 Ok(endpoint) => endpoint,
420 Err(_) => {
421 return Err(Error::InvalidArgument(
422 "endpoint must be an ASCII string".to_string(),
423 ))
424 }
425 };
426
427 if !self.bootstrap_complete {
428 return Err(Error::TorNotConnected());
429 }
430
431 // open tcp stream to remove ident server
432 let stream: TcpStream = self
433 .tor_provider
434 .connect(
435 (identity_server_id.clone(), self.identity_port).into(),
436 None,
437 )?
438 .into();
439 stream.set_nonblocking(true)?;
440 let mut client_rpc = Session::new(stream);
441 client_rpc.set_max_wait_time(self.identity_timeout);
442 client_rpc.set_max_message_size(self.identity_max_message_size)?;
443
444 let ident_client = IdentityClient::new(
445 client_rpc,
446 identity_server_id,
447 endpoint,
448 self.identity_private_key.clone(),
449 X25519PrivateKey::generate(),
450 )?;
451
452 let handshake_handle = self.next_handshake_handle;
453 self.next_handshake_handle += 1;
454 self.identity_clients.insert(handshake_handle, ident_client);
455
456 Ok(handshake_handle)
457 }
458
459 /// Abort an in-process outgoing identity handshake.
460 ///
461 /// # Parameters
462 /// - `handle`: the handle of the in-progress outoing identity handshake to abort
463 pub fn identity_client_abort_handshake(
464 &mut self,
465 handle: HandshakeHandle,
466 ) -> Result<(), Error> {
467 if let Some(_identity_client) = self.identity_clients.remove(&handle) {
468 Ok(())
469 } else {
470 Err(Error::HandshakeHandleNotFound(handle))
471 }
472 }
473
474 /// Handle an identity server's endpoint challenge. Callers must construct an identity client's endpoint challenge-response. The particulars of creating and verifying the challenge-response BSON documents are undefined and application-specific.
475 ///
476 /// # Parameters
477 /// - `handle`: the handle of the in-progress outgoing identity handshake
478 /// - `challenge_response`: an application-specific BSON document which somehow responds to an identity server's challenge.
479 pub fn identity_client_handle_challenge_received(
480 &mut self,
481 handle: HandshakeHandle,
482 challenge_response: bson::document::Document,
483 ) -> Result<(), Error> {
484 if let Some(identity_client) = self.identity_clients.get_mut(&handle) {
485 identity_client.send_response(challenge_response)?;
486 Ok(())
487 } else {
488 Err(Error::HandshakeHandleNotFound(handle))
489 }
490 }
491
492 /// Start this `Context`'s identity server. Publish status is communicated through [`ContextEvent`]s returned from the [`Context::update()`] method.
493 pub fn identity_server_start(&mut self) -> Result<(), Error> {
494 if !self.bootstrap_complete {
495 return Err(Error::TorNotConnected());
496 }
497 if self.identity_listener.is_some() {
498 return Err(Error::IncorrectUsage(
499 "identity server already started".to_string(),
500 ));
501 }
502
503 let identity_listener =
504 self.tor_provider
505 .listener(&self.identity_private_key, self.identity_port, None)?;
506 identity_listener.set_nonblocking(true)?;
507
508 self.identity_listener = Some(identity_listener);
509 Ok(())
510 }
511
512 /// Stops this `Context`'s identity server and ends any in-progress incoming identity handshakes.
513 pub fn identity_server_stop(&mut self) -> Result<(), Error> {
514 if self.identity_listener.is_none() {
515 return Err(Error::IncorrectUsage(
516 "identity server is not started".to_string(),
517 ));
518 }
519
520 // clear out current identity listener
521 self.identity_listener = None;
522 // clear out published flag
523 self.identity_server_published = false;
524 // clear out any in-process identity handshakes
525 self.identity_servers = Default::default();
526 Ok(())
527 }
528
529 /// Handle an identity client's incoming endpoint request. Callers must determine whether the connected identity client is allowed to access the requested endpoint, decide whether the requested endpoint is supported by this `Context`, and build an endpoint challenge for the identity client. The particulars of creating the endpoint challenge is undefined and application-specific.
530 ///
531 /// # Parameters
532 /// - `handle`: the handle of the in-progress incoming identity handshake
533 /// - `client_allowed`: whether the connected identity client is allowed to access the requested endpoint
534 /// - `endpoint_supported`: whether the requested endpoint is supported
535 /// - `endpoint_challenge`: an application-specific BSON document which the connected identity client must respond to
536 pub fn identity_server_handle_endpoint_request_received(
537 &mut self,
538 handle: HandshakeHandle,
539 client_allowed: bool,
540 endpoint_supported: bool,
541 endpoint_challenge: bson::document::Document,
542 ) -> Result<(), Error> {
543 if let Some(identity_server) = self.identity_servers.get_mut(&handle) {
544 Ok(identity_server.handle_endpoint_request_received(
545 client_allowed,
546 endpoint_supported,
547 endpoint_challenge,
548 )?)
549 } else {
550 Err(Error::HandshakeHandleNotFound(handle))
551 }
552 }
553
554 // confirm that a received endpoint challenge response is valid
555
556 /// Handle an identity client's incoming endpoint challenge-response. Callers must determine whether the connected identity client's challenge-response is valid. The particulars of verifying the challenge-response is undefined and application-specific.
557 ///
558 /// # Parameters
559 /// - `handle`: the handle of the in-progress incoming identity handshake
560 /// - `challenge_response_valid`: whether the received challenge-response is valid
561 pub fn identity_server_handle_challenge_response_received(
562 &mut self,
563 handle: HandshakeHandle,
564 challenge_response_valid: bool,
565 ) -> Result<(), Error> {
566 if let Some(identity_server) = self.identity_servers.get_mut(&handle) {
567 Ok(identity_server.handle_challenge_response_received(challenge_response_valid)?)
568 } else {
569 Err(Error::HandshakeHandleNotFound(handle))
570 }
571 }
572
573 /// Initiate an endpoint handshake with an identity server. An endpoint client acquires the `endpoint_server_id` and `client_auth_key` by completing an identity handshake or through some other side-channnel. Handshake progression is communicated through [`ContextEvent`]s returned from the [`Context::update()`] method.
574 ///
575 /// # Parameters
576 /// - `endpoint_server_id`: the endpoint onion-service service-id of a remote peer
577 /// - `client_uath_key`: the x25519 private-key required to decrypt the endpoint server's onion-service descriptor
578 /// - `channel`: the ASCII-encoded requested channel
579 pub fn endpoint_client_begin_handshake(
580 &mut self,
581 endpoint_server_id: V3OnionServiceId,
582 client_auth_key: X25519PrivateKey,
583 channel: String,
584 ) -> Result<HandshakeHandle, Error> {
585 let channel = match AsciiString::new(channel) {
586 Ok(channel) => channel,
587 Err(_) => {
588 return Err(Error::InvalidArgument(
589 "channel must be an ASCII string".to_string(),
590 ))
591 }
592 };
593
594 if !self.bootstrap_complete {
595 return Err(Error::TorNotConnected());
596 }
597
598 self.tor_provider
599 .add_client_auth(&endpoint_server_id, &client_auth_key)?;
600 let stream: TcpStream = self
601 .tor_provider
602 .connect(
603 (endpoint_server_id.clone(), self.endpoint_port).into(),
604 None,
605 )?
606 .into();
607 stream.set_nonblocking(true)?;
608
609 let mut session = Session::new(stream);
610 session.set_max_wait_time(self.endpoint_timeout);
611 session.set_max_message_size(DEFAULT_ENDPOINT_MAX_MESSAGE_SIZE)?;
612
613 let endpoint_client = EndpointClient::new(
614 session,
615 endpoint_server_id,
616 channel,
617 self.identity_private_key.clone(),
618 );
619
620 let handshake_handle = self.next_handshake_handle;
621 self.next_handshake_handle += 1;
622 self.endpoint_clients
623 .insert(handshake_handle, endpoint_client);
624 Ok(handshake_handle)
625 }
626
627 /// Abort an in-process outgoing endpoint handshake
628 ///
629 /// # Parameters
630 /// - `handle`: the handle of the in-progress outoing identity handshake to abort
631 pub fn endpoint_client_abort_handshake(
632 &mut self,
633 handle: HandshakeHandle,
634 ) -> Result<(), Error> {
635 if let Some(_endpoint_client) = self.endpoint_clients.remove(&handle) {
636 Ok(())
637 } else {
638 Err(Error::HandshakeHandleNotFound(handle))
639 }
640 }
641
642 /// Start one of this `Context`'s endpoint servers. Publish status is communicated through [`ContextEvent`]s returned from the [`Context::update()`] method.
643 ///
644 /// # Parameters
645 /// - `endpoint_private_key`: the ed25519 private key used to start this endpoint server's onion-service
646 /// - `endpoint_name`: the ASCII-encoded endpoint name
647 /// - `client_identity`: the onion-service service-id of the client which will be connecting to this endpoint server
648 /// - `client_auth`: the x25519 public-key used to encrypt the endpoint server's onion-service descriptor
649 pub fn endpoint_server_start(
650 &mut self,
651 endpoint_private_key: Ed25519PrivateKey,
652 endpoint_name: String,
653 client_identity: V3OnionServiceId,
654 client_auth: X25519PublicKey,
655 ) -> Result<(), Error> {
656 if !self.bootstrap_complete {
657 return Err(Error::TorNotConnected());
658 }
659
660 let endpoint_public_key = Ed25519PublicKey::from_private_key(&endpoint_private_key);
661 let endpoint_service_id = V3OnionServiceId::from_public_key(&endpoint_public_key);
662
663 if endpoint_service_id == self.identity_service_id {
664 return Err(Error::InvalidArgument(
665 "endpoint server must be different from identity server".to_string(),
666 ));
667 }
668
669 if self.endpoint_listeners.contains_key(&endpoint_service_id) {
670 return Err(Error::IncorrectUsage(
671 "endpoint server already started".to_string(),
672 ));
673 }
674
675 let endpoint_listener = self.tor_provider.listener(
676 &endpoint_private_key,
677 self.endpoint_port,
678 Some(&[client_auth]),
679 )?;
680 endpoint_listener.set_nonblocking(true)?;
681
682 self.endpoint_listeners.insert(
683 endpoint_service_id,
684 (endpoint_name, client_identity, endpoint_listener, false),
685 );
686 Ok(())
687 }
688
689 /// Handle an endpoint client's incoming channel request. Callers must determine whether the requested channel is supported by this `Context`. The particulars of making this determination is undefined and application-specific.
690 ///
691 /// # Parameters
692 /// - `handle`: the handle of the in-progress incoming endpoint handshake
693 /// - `channel_supported`: whether the requested channel is supported
694 pub fn endpoint_server_handle_channel_request_received(
695 &mut self,
696 handle: HandshakeHandle,
697 channel_supported: bool,
698 ) -> Result<(), Error> {
699 if let Some(endpoint_server) = self.endpoint_servers.get_mut(&handle) {
700 Ok(endpoint_server.handle_channel_request_received(channel_supported)?)
701 } else {
702 Err(Error::HandshakeHandleNotFound(handle))
703 }
704 }
705
706 /// Stop one of this `Context`'s endpoint servers and ends any of its in-progress incoming endpoint handshakes.
707 ///
708 /// # Parameters
709 /// - `endpoint_identity`: the onion-service service-id of the enpdoint server to stop
710 pub fn endpoint_server_stop(
711 &mut self,
712 endpoint_identity: V3OnionServiceId,
713 ) -> Result<(), Error> {
714 if !self.bootstrap_complete {
715 return Err(Error::TorNotConnected());
716 }
717
718 if let Some(_listener) = self.endpoint_listeners.remove(&endpoint_identity) {
719 Ok(())
720 } else {
721 Err(Error::InvalidArgument(format!(
722 "endpoint server with service id {} not found",
723 endpoint_identity
724 )))
725 }
726 }
727
728 fn identity_server_handle_accept(
729 identity_listener: &OnionListener,
730 identity_timeout: Duration,
731 identity_max_message_size: i32,
732 identity_private_key: &Ed25519PrivateKey,
733 ) -> Result<Option<IdentityServer>, Error> {
734 if let Some(stream) = identity_listener.accept()? {
735 let stream: TcpStream = stream.into();
736 if stream.set_nonblocking(true).is_err() {
737 return Ok(None);
738 }
739
740 let mut server_rpc = Session::new(stream);
741 server_rpc.set_max_wait_time(identity_timeout);
742 server_rpc.set_max_message_size(identity_max_message_size)?;
743 let service_id = V3OnionServiceId::from_private_key(identity_private_key);
744 let identity_server = IdentityServer::new(server_rpc, service_id);
745
746 Ok(Some(identity_server))
747 } else {
748 Ok(None)
749 }
750 }
751
752 fn endpoint_server_handle_accept(
753 endpoint_listener: &OnionListener,
754 endpoint_timeout: Duration,
755 client_service_id: &V3OnionServiceId,
756 endpoint_service_id: &V3OnionServiceId,
757 ) -> Result<Option<EndpointServer>, Error> {
758 if let Some(stream) = endpoint_listener.accept()? {
759 let stream: TcpStream = stream.into();
760 if stream.set_nonblocking(true).is_err() {
761 return Ok(None);
762 }
763
764 let mut server_rpc = Session::new(stream);
765 server_rpc.set_max_wait_time(endpoint_timeout);
766 server_rpc.set_max_message_size(DEFAULT_ENDPOINT_MAX_MESSAGE_SIZE)?;
767
768 let endpoint_server = EndpointServer::new(
769 server_rpc,
770 client_service_id.clone(),
771 endpoint_service_id.clone(),
772 );
773
774 Ok(Some(endpoint_server))
775 } else {
776 Ok(None)
777 }
778 }
779
780 /// A direct pass-through to the underlying [`TorProvider`]'s [`TorProvider::connect()`] method.
781 pub fn connect(
782 &mut self,
783 target_addr: TargetAddr,
784 circuit_token: Option<CircuitToken>,
785 ) -> Result<OnionStream, Error> {
786 Ok(self.tor_provider.connect(target_addr, circuit_token)?)
787 }
788
789 /// A direct pass-through to the underlying [`TorProvider`]'s [`TorProvider::generate_token()`] method.
790 pub fn generate_circuit_token(&mut self) -> CircuitToken {
791 self.tor_provider.generate_token()
792 }
793
794 /// A direct pass-through to the underlying [`TorProvider`]'s [`TorProvider::release_token()`] method.
795 pub fn release_circuit_token(&mut self, circuit_token: CircuitToken) {
796 self.tor_provider.release_token(circuit_token)
797 }
798
799 /// This function updates the `Context`'s underlying [`TorProvider`], handles new handshakes requests, and updates in-progress handshakes. This function needs to be regularly called to process the returned [`ContextEvent`]s.
800 pub fn update(&mut self) -> Result<VecDeque<ContextEvent>, Error> {
801 // events to return
802 let mut events: VecDeque<ContextEvent> = Default::default();
803
804 // first handle new identity connections
805 if let Some(identity_listener) = &self.identity_listener {
806 match Self::identity_server_handle_accept(
807 identity_listener,
808 self.identity_timeout,
809 self.identity_max_message_size,
810 &self.identity_private_key,
811 ) {
812 Ok(Some(identity_server)) => {
813 let handle = self.next_handshake_handle;
814 self.next_handshake_handle += 1;
815 self.identity_servers.insert(handle, identity_server);
816 events.push_back(ContextEvent::IdentityServerHandshakeStarted { handle });
817 }
818 Ok(None) => {}
819 // identity listener failed, remove it
820 // TODO: signal caller identity listener is down
821 Err(_) => self.identity_listener = None,
822 }
823 }
824
825 // next handle new endpoint connections
826 self.endpoint_listeners.retain(
827 |endpoint_service_id, (_endpoint_name, allowed_client, listener, _published)| -> bool {
828 match Self::endpoint_server_handle_accept(
829 listener,
830 self.endpoint_timeout,
831 allowed_client,
832 endpoint_service_id,
833 ) {
834 Ok(Some(endpoint_server)) => {
835 let handle = self.next_handshake_handle;
836 self.next_handshake_handle += 1;
837 self.endpoint_servers.insert(handle, endpoint_server);
838 events.push_back(ContextEvent::EndpointServerHandshakeStarted { handle });
839 true
840 }
841 Ok(None) => true,
842 // endpoint listener failed, remove it
843 // TODO: signal caller endpoint listener is down
844 Err(_) => false,
845 }
846 },
847 );
848
849 // consume tor events
850 // TODO: so curently the only failure mode of this function is a result of the
851 // LegacyTorClient failing; we should probably consider a LegacyTorClient failure fatal, since
852 // reading the LegacyTorClient::update() function it seems the only failure modes are a
853 // failure to DEL_ONION (which realistically speaking could only be due to a logic
854 // error on our part by deleting an onion that doesn't exist, or a parse error of
855 // the response) and a failure to read async events which is either again a parsing
856 // bug on our end or a malformed/buggy tor daemon which we also cannot recover
857 // from.
858 for event in self.tor_provider.update()?.drain(..) {
859 match event {
860 TorEvent::BootstrapStatus {
861 progress,
862 tag,
863 summary,
864 } => {
865 events.push_back(ContextEvent::TorBootstrapStatusReceived {
866 progress,
867 tag,
868 summary,
869 });
870 }
871 TorEvent::BootstrapComplete => {
872 events.push_back(ContextEvent::TorBootstrapCompleted);
873 self.bootstrap_complete = true;
874 }
875 TorEvent::LogReceived { line } => {
876 events.push_back(ContextEvent::TorLogReceived { line });
877 }
878 TorEvent::OnionServicePublished { service_id } => {
879 if service_id == self.identity_service_id {
880 if !self.identity_server_published {
881 events.push_back(ContextEvent::IdentityServerPublished);
882 self.identity_server_published = true;
883 }
884 } else if let Some((endpoint_name, _, _, published)) =
885 self.endpoint_listeners.get_mut(&service_id)
886 {
887 // ingore duplicate publish events
888 if !*published {
889 events.push_back(ContextEvent::EndpointServerPublished {
890 endpoint_service_id: service_id,
891 endpoint_name: endpoint_name.clone(),
892 });
893 *published = true;
894 }
895 }
896 }
897 }
898 }
899
900 // update the ident client handshakes
901 self.identity_clients
902 .retain(|handle, identity_client| -> bool {
903 let handle = *handle;
904 match identity_client.update() {
905 Ok(Some(IdentityClientEvent::ChallengeReceived { endpoint_challenge })) => {
906 events.push_back(ContextEvent::IdentityClientChallengeReceived {
907 handle,
908 endpoint_challenge,
909 });
910 true
911 }
912 Ok(Some(IdentityClientEvent::HandshakeCompleted {
913 identity_service_id,
914 endpoint_service_id,
915 endpoint_name,
916 client_auth_private_key,
917 })) => {
918 events.push_back(ContextEvent::IdentityClientHandshakeCompleted {
919 handle,
920 identity_service_id,
921 endpoint_service_id,
922 endpoint_name,
923 client_auth_private_key,
924 });
925 false
926 }
927 Err(err) => {
928 events.push_back(ContextEvent::IdentityClientHandshakeFailed {
929 handle,
930 reason: err.into(),
931 });
932 false
933 }
934 Ok(None) => true,
935 }
936 });
937
938 // update the ident server handshakes
939 self.identity_servers
940 .retain(|handle, identity_server| -> bool {
941 let handle = *handle;
942 match identity_server.update() {
943 Ok(Some(IdentityServerEvent::EndpointRequestReceived {
944 client_service_id,
945 requested_endpoint,
946 })) => {
947 events.push_back(ContextEvent::IdentityServerEndpointRequestReceived {
948 handle,
949 client_service_id,
950 requested_endpoint: requested_endpoint.to_string(),
951 });
952 true
953 }
954 Ok(Some(IdentityServerEvent::ChallengeResponseReceived {
955 challenge_response,
956 })) => {
957 events.push_back(ContextEvent::IdentityServerChallengeResponseReceived {
958 handle,
959 challenge_response,
960 });
961 true
962 }
963 Ok(Some(IdentityServerEvent::HandshakeCompleted {
964 endpoint_private_key,
965 endpoint_name,
966 client_service_id,
967 client_auth_public_key,
968 })) => {
969 events.push_back(ContextEvent::IdentityServerHandshakeCompleted {
970 handle,
971 endpoint_private_key,
972 endpoint_name: endpoint_name.to_string(),
973 client_service_id,
974 client_auth_public_key,
975 });
976 false
977 }
978 Ok(Some(IdentityServerEvent::HandshakeRejected {
979 client_allowed,
980 client_requested_endpoint_valid,
981 client_proof_signature_valid,
982 client_auth_signature_valid,
983 challenge_response_valid,
984 })) => {
985 events.push_back(ContextEvent::IdentityServerHandshakeRejected {
986 handle,
987 client_allowed,
988 client_requested_endpoint_valid,
989 client_proof_signature_valid,
990 client_auth_signature_valid,
991 challenge_response_valid,
992 });
993 false
994 }
995 Err(err) => {
996 events.push_back(ContextEvent::IdentityServerHandshakeFailed {
997 handle,
998 reason: err.into(),
999 });
1000 false
1001 }
1002 Ok(None) => true,
1003 }
1004 });
1005
1006 // update the endpoint client handshakes
1007 self.endpoint_clients
1008 .retain(|handle, endpoint_client| -> bool {
1009 let handle = *handle;
1010 match endpoint_client.update() {
1011 Ok(Some(EndpointClientEvent::HandshakeCompleted { stream })) => {
1012 events.push_back(ContextEvent::EndpointClientHandshakeCompleted {
1013 handle,
1014 endpoint_service_id: endpoint_client.server_service_id.clone(),
1015 channel_name: endpoint_client.requested_channel.to_string(),
1016 stream,
1017 });
1018 false
1019 }
1020 Err(err) => {
1021 events.push_back(ContextEvent::EndpointClientHandshakeFailed {
1022 handle,
1023 reason: err.into(),
1024 });
1025 false
1026 }
1027 Ok(None) => true,
1028 }
1029 });
1030
1031 // update the endpoint server handshakes
1032 self.endpoint_servers
1033 .retain(|handle, endpoint_server| -> bool {
1034 let handle = *handle;
1035 match endpoint_server.update() {
1036 Ok(Some(EndpointServerEvent::ChannelRequestReceived {
1037 requested_channel,
1038 client_service_id,
1039 })) => {
1040 events.push_back(ContextEvent::EndpointServerChannelRequestReceived {
1041 handle,
1042 client_service_id,
1043 requested_channel: requested_channel.to_string(),
1044 });
1045 true
1046 }
1047 Ok(Some(EndpointServerEvent::HandshakeCompleted {
1048 client_service_id,
1049 channel_name,
1050 stream,
1051 })) => {
1052 events.push_back(ContextEvent::EndpointServerHandshakeCompleted {
1053 handle,
1054 endpoint_service_id: endpoint_server.server_identity.clone(),
1055 client_service_id,
1056 channel_name: channel_name.to_string(),
1057 stream,
1058 });
1059 false
1060 }
1061 Ok(Some(EndpointServerEvent::HandshakeRejected {
1062 client_allowed,
1063 client_requested_channel_valid,
1064 client_proof_signature_valid,
1065 })) => {
1066 events.push_back(ContextEvent::EndpointServerHandshakeRejected {
1067 handle,
1068 client_allowed,
1069 client_requested_channel_valid,
1070 client_proof_signature_valid,
1071 });
1072 false
1073 }
1074 Err(err) => {
1075 events.push_back(ContextEvent::EndpointServerHandshakeFailed {
1076 handle,
1077 reason: err.into(),
1078 });
1079 false
1080 }
1081 Ok(None) => true,
1082 }
1083 });
1084
1085 Ok(events)
1086 }
1087}