// Copyright 2020 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 use crate::node::client_handling::clients_handler::{ ClientsHandlerRequest, ClientsHandlerRequestSender, ClientsHandlerResponse, }; use crate::node::client_handling::websocket::message_receiver::{ MixMessageReceiver, MixMessageSender, }; use crypto::asymmetric::identity; use futures::{ channel::{mpsc, oneshot}, SinkExt, StreamExt, }; use gateway_requests::authentication::encrypted_address::EncryptedAddressBytes; use gateway_requests::iv::IV; use gateway_requests::registration::handshake::error::HandshakeError; use gateway_requests::registration::handshake::{gateway_handshake, SharedKeys}; use gateway_requests::types::{BinaryRequest, ClientControlRequest, ServerResponse}; use gateway_requests::BinaryResponse; use log::*; use mixnet_client::forwarder::MixForwardingSender; use nymsphinx::DestinationAddressBytes; use rand::{CryptoRng, Rng}; use std::convert::TryFrom; use std::sync::Arc; use tokio::io::{AsyncRead, AsyncWrite}; use tokio_tungstenite::{ tungstenite::{protocol::Message, Error as WsError}, WebSocketStream, }; //// TODO: note for my future self to consider the following idea: //// split the socket connection into sink and stream //// stream will be for reading explicit requests //// and sink for pumping responses AND mix traffic //// but as byproduct this might (or might not) break the clean "SocketStream" enum here enum SocketStream { RawTcp(S), UpgradedWebSocket(WebSocketStream), Invalid, } impl SocketStream { fn is_websocket(&self) -> bool { matches!(self, SocketStream::UpgradedWebSocket(_)) } } pub(crate) struct Handle { rng: R, remote_address: Option, shared_key: Option, clients_handler_sender: ClientsHandlerRequestSender, outbound_mix_sender: MixForwardingSender, socket_connection: SocketStream, local_identity: Arc, } impl Handle where R: Rng + CryptoRng, { // for time being we assume handle is always constructed from raw socket. // if we decide we want to change it, that's not too difficult pub(crate) fn new( rng: R, conn: S, clients_handler_sender: ClientsHandlerRequestSender, outbound_mix_sender: MixForwardingSender, local_identity: Arc, ) -> Self { Handle { rng, remote_address: None, shared_key: None, clients_handler_sender, outbound_mix_sender, socket_connection: SocketStream::RawTcp(conn), local_identity, } } async fn perform_websocket_handshake(&mut self) -> Result<(), WsError> where S: AsyncRead + AsyncWrite + Unpin, { self.socket_connection = match std::mem::replace(&mut self.socket_connection, SocketStream::Invalid) { SocketStream::RawTcp(conn) => { // TODO: perhaps in the future, rather than panic here (and uncleanly shut tcp stream) // return a result with an error? let ws_stream = tokio_tungstenite::accept_async(conn).await?; SocketStream::UpgradedWebSocket(ws_stream) } other => other, }; Ok(()) } async fn perform_registration_handshake( &mut self, init_msg: Vec, ) -> Result where S: AsyncRead + AsyncWrite + Unpin + Send, { debug_assert!(self.socket_connection.is_websocket()); match &mut self.socket_connection { SocketStream::UpgradedWebSocket(ws_stream) => { gateway_handshake( &mut self.rng, ws_stream, self.local_identity.as_ref(), init_msg, ) .await } _ => unreachable!(), } } async fn next_websocket_request(&mut self) -> Option> where S: AsyncRead + AsyncWrite + Unpin, { match self.socket_connection { SocketStream::UpgradedWebSocket(ref mut ws_stream) => ws_stream.next().await, _ => panic!("impossible state - websocket handshake was somehow reverted"), } } async fn send_websocket_response(&mut self, msg: Message) -> Result<(), WsError> where S: AsyncRead + AsyncWrite + Unpin, { match self.socket_connection { // TODO: more closely investigate difference between `Sink::send` and `Sink::send_all` // it got something to do with batching and flushing - it might be important if it // turns out somehow we've got a bottleneck here SocketStream::UpgradedWebSocket(ref mut ws_stream) => ws_stream.send(msg).await, _ => panic!("impossible state - websocket handshake was somehow reverted"), } } // Note that it encrypts each message and slaps a MAC on it async fn send_websocket_unwrapped_sphinx_packets( &mut self, packets: Vec>, ) -> Result<(), WsError> where S: AsyncRead + AsyncWrite + Unpin, { let shared_key = self .shared_key .as_ref() .expect("no shared key present even though we authenticated the client!"); // note: into_ws_message encrypts the requests and adds a MAC on it. Perhaps it should // be more explicit in the naming? let messages: Vec> = packets .into_iter() .map(|received_message| { Ok(BinaryResponse::new_pushed_mix_message(received_message) .into_ws_message(shared_key)) }) .collect(); let mut send_stream = futures::stream::iter(messages); match self.socket_connection { SocketStream::UpgradedWebSocket(ref mut ws_stream) => { ws_stream.send_all(&mut send_stream).await } _ => panic!("impossible state - websocket handshake was somehow reverted"), } } fn disconnect(&self) { // if we never established what is the address of the client, its connection was never // announced hence we do not need to send 'disconnect' message if let Some(addr) = self.remote_address.as_ref() { self.clients_handler_sender .unbounded_send(ClientsHandlerRequest::Disconnect(*addr)) .unwrap(); } } async fn handle_binary(&self, bin_msg: Vec) -> Message { trace!("Handling binary message (presumably sphinx packet)"); // this function decrypts the request and checks the MAC match BinaryRequest::try_from_encrypted_tagged_bytes( bin_msg, self.shared_key .as_ref() .expect("no shared key present even though we authenticated the client!"), ) { Err(e) => ServerResponse::new_error(e.to_string()), Ok(request) => match request { // currently only a single type exists BinaryRequest::ForwardSphinx(mix_packet) => { self.outbound_mix_sender.unbounded_send(mix_packet).unwrap(); ServerResponse::Send { status: true } } }, } .into() } async fn handle_authenticate( &mut self, address: String, enc_address: String, iv: String, mix_sender: MixMessageSender, ) -> ServerResponse { let address = match DestinationAddressBytes::try_from_base58_string(address) { Ok(address) => address, Err(e) => { trace!("failed to parse received DestinationAddress: {:?}", e); return ServerResponse::new_error("malformed destination address"); } }; let encrypted_address = match EncryptedAddressBytes::try_from_base58_string(enc_address) { Ok(address) => address, Err(e) => { trace!("failed to parse received encrypted address: {:?}", e); return ServerResponse::new_error("malformed encrypted address"); } }; let iv = match IV::try_from_base58_string(iv) { Ok(iv) => iv, Err(e) => { trace!("failed to parse received IV {:?}", e); return ServerResponse::new_error("malformed iv"); } }; let (res_sender, res_receiver) = oneshot::channel(); let clients_handler_request = ClientsHandlerRequest::Authenticate( address, encrypted_address, iv, mix_sender, res_sender, ); self.clients_handler_sender .unbounded_send(clients_handler_request) .unwrap(); // the receiver MUST BE alive match res_receiver.await.unwrap() { ClientsHandlerResponse::Authenticate(shared_key) => { if shared_key.is_some() { self.remote_address = Some(address); self.shared_key = shared_key; ServerResponse::Authenticate { status: true } } else { ServerResponse::Authenticate { status: false } } } ClientsHandlerResponse::Error(e) => { error!("Authentication unexpectedly failed - {}", e); ServerResponse::Error { message: format!("Authentication failure - {}", e), } } _ => panic!("received response to wrong query!"), // this should NEVER happen } } fn extract_remote_identity_from_register_init(init_data: &[u8]) -> Option { if init_data.len() < identity::PUBLIC_KEY_LENGTH { None } else { identity::PublicKey::from_bytes(&init_data[..identity::PUBLIC_KEY_LENGTH]).ok() } } async fn handle_register( &mut self, init_data: Vec, mix_sender: MixMessageSender, ) -> ServerResponse where S: AsyncRead + AsyncWrite + Unpin + Send, { // not entirely sure how to it more "nicely"... // hopefully, eventually this will go away once client's identity is known beforehand let remote_identity = match Self::extract_remote_identity_from_register_init(&init_data) { Some(address) => address, None => return ServerResponse::new_error("malformed request"), }; let remote_address = remote_identity.derive_destination_address(); let derived_shared_key = match self.perform_registration_handshake(init_data).await { Ok(shared_key) => shared_key, Err(err) => { return ServerResponse::new_error(format!( "failed to perform the handshake - {}", err )) } }; let (res_sender, res_receiver) = oneshot::channel(); let clients_handler_request = ClientsHandlerRequest::Register( remote_address, derived_shared_key.clone(), mix_sender, res_sender, ); self.clients_handler_sender .unbounded_send(clients_handler_request) .unwrap(); // the receiver MUST BE alive match res_receiver.await.unwrap() { // currently register can't fail (as in if all machines are working correctly and you // managed to complete registration handshake) ClientsHandlerResponse::Register(status) => { self.remote_address = Some(remote_address); if status { self.shared_key = Some(derived_shared_key); } ServerResponse::Register { status } } ClientsHandlerResponse::Error(e) => { error!("Post-handshake registration unexpectedly failed - {}", e); ServerResponse::Error { message: format!("Registration failure - {}", e), } } _ => panic!("received response to wrong query!"), // this should NEVER happen } } // currently there are no valid control messages you can send after authentication async fn handle_text(&mut self, _: String) -> Message { trace!("Handling text message (presumably control message)"); error!("Currently there are no text messages besides 'Authenticate' and 'Register' and they were already dealt with!"); ServerResponse::new_error("invalid request").into() } async fn handle_request(&mut self, raw_request: Message) -> Option { // apparently tungstenite auto-handles ping/pong/close messages so for now let's ignore // them and let's test that claim. If that's not the case, just copy code from // desktop nym-client websocket as I've manually handled everything there match raw_request { Message::Binary(bin_msg) => Some(self.handle_binary(bin_msg).await), Message::Text(text_msg) => Some(self.handle_text(text_msg).await), _ => None, } } /// Handles data that resembles request to either start registration handshake or perform /// authentication. async fn handle_initial_authentication_request( &mut self, mix_sender: MixMessageSender, raw_request: String, ) -> ServerResponse where S: AsyncRead + AsyncWrite + Unpin + Send, { if let Ok(request) = ClientControlRequest::try_from(raw_request) { match request { ClientControlRequest::Authenticate { address, enc_address, iv, } => { self.handle_authenticate(address, enc_address, iv, mix_sender) .await } ClientControlRequest::RegisterHandshakeInitRequest { data } => { self.handle_register(data, mix_sender).await } } } else { // TODO: is this a malformed request or rather a network error and // connection should be terminated? ServerResponse::new_error("malformed request") } } /// Listens for only a subset of possible client requests, i.e. for those that can either /// result in client getting registered or authenticated. All other requests, such as forwarding /// sphinx packets are ignored. async fn wait_for_initial_authentication(&mut self) -> Option where S: AsyncRead + AsyncWrite + Unpin + Send, { trace!("Started waiting for authenticate/register request..."); while let Some(msg) = self.next_websocket_request().await { let msg = match msg { Ok(msg) => msg, Err(err) => { error!("failed to obtain message from websocket stream! stopping connection handler: {}", err); break; } }; if msg.is_close() { break; } let (mix_sender, mix_receiver) = mpsc::unbounded(); // ONLY handle 'Authenticate' or 'Register' requests, ignore everything else let response = match msg { Message::Close(_) => break, Message::Text(text_msg) => { self.handle_initial_authentication_request(mix_sender, text_msg) .await } Message::Binary(_) => { // perhaps logging level should be reduced here, let's leave it for now and see what happens // if client is working correctly, this should have never happened warn!("possibly received a sphinx packet without prior authentication. Request is going to be ignored"); ServerResponse::new_error("binary request without prior authentication") } _ => continue, }; let is_done = response.implies_successful_authentication(); if let Err(err) = self.send_websocket_response(response.into()).await { warn!( "Failed to send message over websocket: {}. Assuming the connection is dead.", err ); break; } // it means we successfully managed to perform authentication and announce our // presence to ClientsHandler if is_done { return Some(mix_receiver); } } None } /// Simultaneously listens for incoming client requests, which realistically should only be /// binary requests to forward sphinx packets, and for sphinx packets received from the mix /// network that should be sent back to the client. async fn listen_for_requests(&mut self, mut mix_receiver: MixMessageReceiver) where S: AsyncRead + AsyncWrite + Unpin, { trace!("Started listening for ALL incoming requests..."); loop { tokio::select! { socket_msg = self.next_websocket_request() => { if socket_msg.is_none() { break; } let socket_msg = match socket_msg.unwrap() { Ok(socket_msg) => socket_msg, Err(err) => { error!("failed to obtain message from websocket stream! stopping connection handler: {}", err); break; } }; if socket_msg.is_close() { break; } if let Some(response) = self.handle_request(socket_msg).await { if let Err(err) = self.send_websocket_response(response).await { warn!( "Failed to send message over websocket: {}. Assuming the connection is dead.", err ); break; } } }, mix_messages = mix_receiver.next() => { let mix_messages = mix_messages.expect("sender was unexpectedly closed! this shouldn't have ever happened!"); if let Err(e) = self.send_websocket_unwrapped_sphinx_packets(mix_messages).await { warn!("failed to send the unwrapped sphinx packets back to the client - {:?}, assuming the connection is dead", e); break; } } } } self.disconnect(); trace!("The stream was closed!"); } pub(crate) async fn start_handling(&mut self) where S: AsyncRead + AsyncWrite + Unpin + Send, { if let Err(e) = self.perform_websocket_handshake().await { warn!( "Failed to complete WebSocket handshake - {:?}. Stopping the handler", e ); return; } trace!("Managed to perform websocket handshake!"); let mix_receiver = self.wait_for_initial_authentication().await; trace!("Performed initial authentication"); match mix_receiver { Some(receiver) => self.listen_for_requests(receiver).await, None => trace!("But connection was closed during the process"), } trace!("The handler is done!"); } }