#![warn(clippy::all, clippy::pedantic)]
#![allow(clippy::cast_possible_truncation)]
#![allow(clippy::missing_errors_doc)]
use bincode::{BorrowDecode, Decode, Encode};
use std::io::ErrorKind;
use std::net::SocketAddr;
use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
use tracing::warn;
pub mod wordlist;
pub const PROTOCOL_VERSION: u64 = 2;
#[derive(Debug, Clone, Encode, Decode)]
pub enum Message {
HeartBeat([u8; 32]),
HeartBeatEcho([u8; 32]),
NewClient(u128),
}
#[derive(Debug, Clone, Encode, Decode)]
pub struct DomainAndPubKey {
pub domain: String,
pub public_key: [u8; 32],
pub protocol_version: u64,
}
impl DomainAndPubKey {
#[must_use]
pub fn new(domain: String, public_key: [u8; 32], protocol_version: u64) -> Self {
Self {
domain,
public_key,
protocol_version,
}
}
}
#[derive(Debug, Clone, Encode, Decode)]
pub struct Addr(pub SocketAddr);
#[derive(Debug, Clone, Encode, BorrowDecode)]
pub struct ServerHello<'a>(pub &'a str, pub Option<&'a str>);
impl<'a> BincodeAsync<'a> for ServerHello<'a> {}
impl BincodeAsync<'_> for Message {}
impl BincodeAsync<'_> for DomainAndPubKey {}
impl BincodeAsync<'_> for Addr {}
pub trait BincodeAsync<'a>: BorrowDecode<'a, ()> + Encode + Send {
fn encode<S: AsyncWrite + Unpin + Send>(
self,
r: &mut S,
buf: &mut [u8],
) -> impl Future<Output = Result<usize, std::io::Error>> + Send {
async {
let len = bincode::encode_into_slice(self, buf, bincode::config::standard())
.map_err(|e| std::io::Error::new(ErrorKind::InvalidData, e))?;
r.write_u32(len as u32).await?;
r.write_all(&buf[..len]).await?;
r.flush().await?;
Ok(len + 4)
}
}
fn parse<S: AsyncRead + Unpin + Send>(
r: &mut S,
buf: &'a mut [u8],
) -> impl Future<Output = Result<Self, std::io::Error>> + Send {
async {
let len = r.read_u32().await?;
let len = len as usize;
if len > buf.len() {
return Err(ErrorKind::InvalidData.into());
}
let read = r.read_exact(&mut buf[..len]).await?;
let (decoded, _len) = match bincode::borrow_decode_from_slice::<Self, _>(
&buf[..read],
bincode::config::standard(),
) {
Ok(d) => d,
Err(e) => return Err(std::io::Error::new(ErrorKind::InvalidData, e)),
};
Ok(decoded)
}
}
}
pub fn try_parse_init_packet(
buf: &[u8],
addr: SocketAddr,
) -> Result<Option<&[u8]>, std::io::Error> {
let mut inner_cursor = 0;
let Some((_len, read)) = varint::decode_varint(buf)? else {
return Ok(None);
};
inner_cursor += read;
let Some((packet_id, read)) = varint::decode_varint(&buf[inner_cursor..])? else {
return Ok(None);
};
inner_cursor += read;
if packet_id != 0 {
warn!("client {addr} sent invalid packet protocol ID");
return Err(ErrorKind::InvalidData.into());
}
let Some((_protocol_version, read)) = varint::decode_varint(&buf[inner_cursor..])? else {
return Ok(None);
};
inner_cursor += read;
let Some((strlen, read)) = varint::decode_varint(&buf[inner_cursor..])? else {
return Ok(None);
};
inner_cursor += read;
if strlen > 256 {
warn!("Received packet with hostname len set to {strlen}");
return Err(ErrorKind::InvalidData.into());
}
if buf[inner_cursor..].len() < strlen as usize {
return Ok(None);
}
let hostname = &buf[inner_cursor..inner_cursor + strlen as usize];
Ok(Some(hostname))
}
pub mod varint {
#[must_use]
pub fn encode_varint(mut value: u64) -> ([u8; 10], usize) {
let mut index = 0;
let mut buf = [0u8; 10];
while value >= 0x80 {
buf[index] = (value as u8 & 0x7F) | 0x80;
index += 1;
value >>= 7;
}
buf[index] = value as u8;
index += 1;
(buf, index)
}
pub fn decode_varint(buf: &[u8]) -> Result<Option<(u64, usize)>, std::io::Error> {
let mut result = 0;
for i in 0..10 {
let byte = match buf.get(i) {
Some(b) => *b,
None => return Ok(None),
};
result |= u64::from(byte & 0x7f) << (7 * i);
if byte & 0x80 == 0 {
return Ok(Some((result, i + 1)));
}
}
Err(std::io::ErrorKind::InvalidData.into())
}
}
pub mod dhauth {
use std::sync::atomic::{AtomicU64, Ordering};
use super::BincodeAsync;
use aes_gcm_siv::aead::Aead;
use aes_gcm_siv::{Aes256GcmSiv, KeyInit as _, Nonce};
use bincode::{BorrowDecode, Decode, Encode};
use blake3::Hasher;
use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey};
use rand::Rng as _;
use tokio::io::{AsyncRead, AsyncWrite};
use tracing::error;
use x25519_dalek::{EphemeralSecret, PublicKey};
#[derive(Clone, Debug, Encode, Decode)]
struct BobPublic([u8; 32]);
#[derive(Clone, Debug, Encode, Decode)]
struct AlicePubSignature([u8; 32], [u8; 64]);
#[derive(Clone, Debug, Encode, BorrowDecode)]
struct Id<'a>([u8; 12], &'a [u8]);
impl BincodeAsync<'_> for BobPublic {}
impl BincodeAsync<'_> for AlicePubSignature {}
impl<'a> BincodeAsync<'a> for Id<'a> {}
pub struct AuthenticatorProxy<'a, S: AsyncRead + AsyncWrite + Send + Unpin> {
pub inner: &'a mut S,
pub alice_private_sign_key: SigningKey,
pub counter: &'a AtomicU64,
}
impl<S: AsyncRead + AsyncWrite + Send + Unpin> AuthenticatorProxy<'_, S> {
#[allow(clippy::missing_panics_doc)]
pub async fn get_id(mut self) -> Result<u128, std::io::Error> {
let mut buf = [0u8; 128];
let alice_secret = EphemeralSecret::random();
let alice_public = PublicKey::from(&alice_secret);
let BobPublic(client_public) = BobPublic::parse(&mut self.inner, &mut buf).await?;
let bob_public = PublicKey::from(client_public);
let hashed = sha256(alice_public, bob_public);
let signature = self.alice_private_sign_key.sign(&hashed);
let signature = signature.to_bytes();
let len = AlicePubSignature(alice_public.to_bytes(), signature)
.encode(&mut self.inner, &mut buf)
.await?;
self.counter.fetch_add(len as u64, Ordering::Relaxed);
let Id(nonce, data) = Id::parse(&mut self.inner, &mut buf).await.map_err(|e| {
std::io::Error::other(format!("Connection error OR A MITM OCCURED: {e}"))
})?;
let shared = alice_secret.diffie_hellman(&bob_public).to_bytes();
let key = shared.into();
let aes = Aes256GcmSiv::new(&key);
let decrypted = aes
.decrypt(&Nonce::from(nonce), data)
.map_err(|_e| std::io::Error::other("Error occured when decrypting key"))?;
if decrypted.len() != 16 {
return Err(std::io::Error::other(format!(
"ID is {} bytes, expected 16",
decrypted.len()
)));
}
let id = u128::from_be_bytes(decrypted[..].try_into().unwrap());
Ok(id)
}
}
pub struct AuthenticatorServer<'a, S: AsyncRead + AsyncWrite + Send + Unpin> {
pub inner: &'a mut S,
pub alice_public_sign_key: VerifyingKey,
}
impl<S: AsyncRead + AsyncWrite + Send + Unpin> AuthenticatorServer<'_, S> {
pub async fn send_id(mut self, id: u128) -> Result<(), std::io::Error> {
let mut buf = [0u8; 1024];
let bob_secret = EphemeralSecret::random();
let bob_public = PublicKey::from(&bob_secret);
BobPublic(bob_public.to_bytes())
.encode(&mut self.inner, &mut buf)
.await?;
let AlicePubSignature(alice_public, signature) =
AlicePubSignature::parse(&mut self.inner, &mut buf).await?;
let alice_public = PublicKey::from(alice_public);
let hashed = sha256(alice_public, bob_public);
let res = self
.alice_public_sign_key
.verify(&hashed, &Signature::from(signature));
if let Err(e) = res {
error!("POTENTIAL MITM? Signature error!!! {e}");
return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, e));
}
let shared = bob_secret.diffie_hellman(&alice_public).to_bytes();
let key = shared.into();
let aes = Aes256GcmSiv::new(&key);
let mut nonce = [0u8; 12];
rand::rng().fill(&mut nonce);
let nonce_gen = Nonce::from(nonce);
let id = id.to_be_bytes();
let ciphertext = aes
.encrypt(&nonce_gen, &id as &[u8])
.map_err(|_e| std::io::Error::other("An error occured while encrypting id"))?;
Id(nonce, &ciphertext)
.encode(&mut self.inner, &mut buf)
.await?;
Ok(())
}
}
fn sha256(pub1: PublicKey, pub2: PublicKey) -> [u8; 32] {
let mut hasher = Hasher::new();
hasher.update(b"MINESHARE");
hasher.update(pub1.as_bytes());
hasher.update(pub2.as_bytes());
hasher.finalize().into()
}
}