use std::
{
time::Duration,
io::{ Error, ErrorKind },
};
use tokio::
{
time,
sync::
{
oneshot,
mpsc::Sender,
},
net::
{
TcpStream,
tcp::{ OwnedReadHalf, OwnedWriteHalf },
},
};
use tokio_socks::tcp::Socks5Stream;
use crate::
{
crypto::kex,
options,
config::
{
self,
keys::{ self, TofuCode },
},
consts::
{
self,
Streams,
SharedKeys,
},
network::
{
self,
schema,
codes::PacketCode,
client::{ ClientEvent, TofuRequest },
},
};
#[derive(PartialEq)]
pub enum Handshake
{
Ready, Reconnect, Failed, }
pub async fn key_exchange
(
streams: &mut Streams<'_>,
keys: &mut SharedKeys,
tx: &Sender<ClientEvent>,
exchange_keys: Option<&SharedKeys>,
) -> Handshake {
let offer = loop
{
let Some(received) = network::receive(streams, exchange_keys, None).await else
{
tx.send(ClientEvent::Quit(false)).await.ok();
return Handshake::Failed;
};
if let PacketCode::KeyExchangeOffer { offer } = received { break offer; }
};
let host = streams.0.peer_addr().unwrap().ip().to_string();
let verdict = if env!("WHY2_SKIP_TOFU") == "false"
{
Some(keys::check(&host, &kex::public_bytes(&offer.static_ecc)))
} else { None };
if !kex::verify_offer(&options::get_obfuscation_key(), &offer.static_ecc, &offer.eph_ecc, &offer.pq, &offer.sig)
{
tx.send(ClientEvent::HandshakeFailed(String::from("Server identity did not sign its exchange keys."))).await.ok();
return Handshake::Failed;
}
let (sk, pk) = kex::generate_ephemeral_keys();
let (pq_ciphertext, pq_secret) = kex::encapsulate_pq(&offer.pq);
network::send(&mut *streams.1.lock().await, PacketCode::KeyExchangeReply
{
reply: Box::new(schema::Reply { eph_ecc: pk, pq: pq_ciphertext }),
}, exchange_keys).await;
*keys = kex::derive_shared_secret(sk, &offer.eph_ecc, pq_secret);
options::set_keys(keys.clone());
let hash = keys::hash(&kex::public_bytes(&offer.static_ecc));
options::set_fingerprint(&hash);
match verdict
{
None => tx.send(ClientEvent::TofuSkip(hash)).await.unwrap(),
Some(TofuCode::Valid) => {},
Some(status) =>
{
let (reply, answer) = oneshot::channel();
tx.send(ClientEvent::TofuPrompt(TofuRequest
{
host: host.clone(),
hash: hash.clone(),
mismatch: matches!(status, TofuCode::Mismatch),
pinned: keys::pinned(&host),
reply,
})).await.unwrap();
if !answer.await.unwrap_or(false)
{
network::send(&mut *streams.1.lock().await, PacketCode::Disconnect, Some(keys)).await;
tx.send(ClientEvent::TofuError).await.unwrap();
return Handshake::Failed;
}
keys::save(&host, &hash);
if exchange_keys.is_none()
{
network::send(&mut *streams.1.lock().await, PacketCode::Disconnect, Some(keys)).await;
return Handshake::Reconnect;
}
},
}
Handshake::Ready
}
pub async fn reconnect(streams: &mut Streams<'_>) -> bool
{
let Ok((read_half, write_half)) = connect(options::get_server_address()).await else { return false };
*streams.0 = read_half;
*streams.1.lock().await = write_half;
options::set_seq(0);
options::set_server_seq(0);
true
}
pub async fn connect(connecting_addr: String) -> Result<(OwnedReadHalf, OwnedWriteHalf), Error> {
let dial = async
{
if !options::socks5_enabled() {
TcpStream::connect(connecting_addr).await
} else {
let proxy_addr = config::read_config::<String>("socks5_addr");
Socks5Stream::connect(proxy_addr.as_str(), connecting_addr.as_str()).await
.map(|s| s.into_inner())
.map_err(Error::other)
}
};
time::timeout(Duration::from_millis(consts::CONNECT_TIMEOUT), dial).await
.unwrap_or_else(|_| Err(Error::new(ErrorKind::TimedOut, "Connection timed out.")))
.and_then(|s|
{
s.set_nodelay(true)?;
Ok(s.into_split())
})
}