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