From 9d3255fa4f5baca2130a0eb46ae2c33e25aa021f Mon Sep 17 00:00:00 2001 From: Vladimir Lebedev Date: Sun, 18 Feb 2024 00:00:38 +0700 Subject: [PATCH] Huge refactoring --- Cargo.toml | 4 -- README.md | 10 ++--- examples/echo_client.rs | 28 +++++++++++++ examples/echo_server.rs | 65 +++++++++++++++++++++++++++++ examples/time.rs | 16 +++---- src/helper_types.rs | 46 ++++++++++++++++---- src/integrations/dalek.rs | 4 +- src/lib.rs | 4 +- src/primitives/handshake.rs | 16 +++---- src/primitives/receive.rs | 6 ++- src/tests.rs | 24 +++++------ src/wrappers/builder.rs | 4 +- src/wrappers/mod.rs | 2 +- src/wrappers/{client.rs => peer.rs} | 50 +++++++++++++++------- 14 files changed, 209 insertions(+), 70 deletions(-) create mode 100644 examples/echo_client.rs create mode 100644 examples/echo_server.rs rename src/wrappers/{client.rs => peer.rs} (64%) diff --git a/Cargo.toml b/Cargo.toml index 64e895f..05ce27f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,11 +23,8 @@ rand = "0.8.5" [dev-dependencies] hex = "0.4.3" -x25519-dalek = "= 2.0.0-pre.1" -curve25519-dalek = "= 4.0.0-pre.2" tokio = { version = "1.36", features = ["rt-multi-thread", "macros"]} base64 = "0.13.0" -anyhow = "1" [features] default = ["dalek"] @@ -35,4 +32,3 @@ dalek = ["x25519-dalek", "curve25519-dalek"] [[example]] name = "time" -required-features = ["dalek"] diff --git a/README.md b/README.md index c197cf6..479aaeb 100644 --- a/README.md +++ b/README.md @@ -16,23 +16,23 @@ Minimal client-server ADNL implementation in Rust. Specification of ADNL is avai Run this example: `cargo run --example time` ```rust -use adnl::AdnlClient; +use adnl::AdnlPeer; use anyhow::{anyhow, Context, Result}; use std::net::SocketAddrV4; - #[tokio::main] async fn main() -> Result<()> { // decode liteserver public key let remote_public: [u8; 32] = base64::decode("JhXt7H1dZTgxQTIyGiYV4f9VUARuDxFl/1kVBjLSMB8=") .context("Error decode base64")? - .try_into().map_err(|_| anyhow!("Bad public key length"))?; + .try_into() + .map_err(|_| anyhow!("Bad public key length"))?; let ls_ip = "65.21.74.140"; let ls_port = 46427; - // create AdnlClient + // act as a client: connect to ADNL server and perform handshake let mut client = - AdnlClient::connect(remote_public, SocketAddrV4::new(ls_ip.parse()?, ls_port)).await?; + AdnlPeer::connect(remote_public, SocketAddrV4::new(ls_ip.parse()?, ls_port)).await?; // already serialized TL with gettime query let mut query = hex::decode("7af98bb435263e6c95d6fecb497dfd0aa5f031e7d412986b5ce720496db512052e8f2d100cdf068c7904345aad16000000000000")?; diff --git a/examples/echo_client.rs b/examples/echo_client.rs new file mode 100644 index 0000000..59d068e --- /dev/null +++ b/examples/echo_client.rs @@ -0,0 +1,28 @@ +use adnl::{AdnlPeer, AdnlRawPublicKey}; +use std::{env, error::Error}; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let addr = env::args() + .nth(1) + .unwrap_or_else(|| "127.0.0.1:8080".to_string()); + + let public_key_hex = env::args() + .nth(2) + .unwrap_or_else(|| "b7d8e88f4033eff806e2f5dff3c785be7dd038c923146e2d9fe80e4fe3cb8805".to_string()); + + let remote_public = AdnlRawPublicKey::try_from(&*hex::decode(public_key_hex)?)?; + + // act as a client: connect to ADNL server and perform handshake + let mut client = AdnlPeer::connect(&remote_public, addr).await?; + + // send over ADNL + client.send(&mut "hello".as_bytes().to_vec()).await?; + + // receive result into vector + let mut result = Vec::::new(); + client.receive(&mut result).await?; + + println!("received: {}", String::from_utf8(result).unwrap()); + Ok(()) +} diff --git a/examples/echo_server.rs b/examples/echo_server.rs new file mode 100644 index 0000000..d2f95f5 --- /dev/null +++ b/examples/echo_server.rs @@ -0,0 +1,65 @@ +//! Adopted from https://github.com/tokio-rs/tokio/blob/b32826bc937a34e4d871c89bb2c3711ed3e20cdc/examples/echo.rs + +use std::{env, error::Error}; + +use adnl::{AdnlPeer, AdnlPrivateKey, AdnlPublicKey}; +use tokio::net::TcpListener; +use x25519_dalek::StaticSecret; + +#[tokio::main] +async fn main() -> Result<(), Box> { + // Allow passing an address to listen on as the first argument of this + // program, but otherwise we'll just set up our TCP listener on + // 127.0.0.1:8080 for connections. + let addr = env::args() + .nth(1) + .unwrap_or_else(|| "127.0.0.1:8080".to_string()); + + // ADNL: get private key from environment variable KEY or use default insecure one + let private_key_hex = env::var("KEY").unwrap_or_else(|_| "69734189c0348245a70eb5335e12bfd75dd4cffc42baf32773e8f994ff5cf7c2".to_string()); + let private_key_bytes: [u8; 32] = hex::decode(private_key_hex)?.try_into().unwrap(); + let private_key = StaticSecret::from(private_key_bytes); + + // Next up we create a TCP listener which will listen for incoming + // connections. This TCP listener is bound to the address we determined + // above and must be associated with an event loop. + let listener = TcpListener::bind(&addr).await?; + println!("Listening on: {}", addr); + + // ADNL: print public key and adnl address associated with given private key + println!("Public key is: {}", hex::encode(private_key.public().as_bytes())); + println!("Address is: {}", hex::encode(private_key.public().address().as_bytes())); + + loop { + // Asynchronously wait for an inbound socket. + let (socket, _) = listener.accept().await?; + + // And this is where much of the magic of this server happens. We + // crucially want all clients to make progress concurrently, rather than + // blocking one on completion of another. To achieve this we use the + // `tokio::spawn` function to execute the work in the background. + // + // Essentially here we're executing a new task to run concurrently, + // which will allow all of our clients to be processed concurrently. + + let private_key = private_key.clone(); + tokio::spawn(async move { + // ADNL: handle handshake + let mut adnl_server = AdnlPeer::handle_handshake(socket, &private_key).await.expect("handshake failed"); + + let mut buf = vec![0; 1024]; + + // In a loop, read data from the socket and write the data back. + loop { + let n = adnl_server.receive(&mut buf) + .await + .expect("failed to read data from socket"); + + adnl_server + .send(&mut buf[..n]) + .await + .expect("failed to write data to socket"); + } + }); + } +} \ No newline at end of file diff --git a/examples/time.rs b/examples/time.rs index bc044cf..a09c146 100644 --- a/examples/time.rs +++ b/examples/time.rs @@ -1,20 +1,16 @@ -use adnl::AdnlClient; -use anyhow::{anyhow, Context, Result}; -use std::net::SocketAddrV4; +use adnl::{AdnlPeer, AdnlRawPublicKey}; +use std::{error::Error, net::SocketAddrV4}; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { // decode liteserver public key - let remote_public: [u8; 32] = base64::decode("JhXt7H1dZTgxQTIyGiYV4f9VUARuDxFl/1kVBjLSMB8=") - .context("Error decode base64")? - .try_into() - .map_err(|_| anyhow!("Bad public key length"))?; + let remote_public = AdnlRawPublicKey::try_from(&*base64::decode("JhXt7H1dZTgxQTIyGiYV4f9VUARuDxFl/1kVBjLSMB8=")?)?; let ls_ip = "65.21.74.140"; let ls_port = 46427; - // create AdnlClient + // act as a client: connect to ADNL server and perform handshake let mut client = - AdnlClient::connect(remote_public, SocketAddrV4::new(ls_ip.parse()?, ls_port)).await?; + AdnlPeer::connect(&remote_public, SocketAddrV4::new(ls_ip.parse()?, ls_port)).await?; // already serialized TL with gettime query let mut query = hex::decode("7af98bb435263e6c95d6fecb497dfd0aa5f031e7d412986b5ce720496db512052e8f2d100cdf068c7904345aad16000000000000")?; diff --git a/src/helper_types.rs b/src/helper_types.rs index b2056a9..f3debc5 100644 --- a/src/helper_types.rs +++ b/src/helper_types.rs @@ -1,6 +1,5 @@ use sha2::{Digest, Sha256}; -use std::io::Error; -use std::net::AddrParseError; +use std::{array::TryFromSliceError, io::Error}; use thiserror::Error; pub trait CryptoRandom: rand_core::RngCore + rand_core::CryptoRng {} @@ -19,9 +18,26 @@ pub trait AdnlPublicKey { } /// Public key can be provided using raw slice -impl AdnlPublicKey for [u8; 32] { +#[derive(Clone)] +pub struct AdnlRawPublicKey([u8; 32]); + +impl AdnlPublicKey for AdnlRawPublicKey { fn to_bytes(&self) -> [u8; 32] { - *self + self.0 + } +} + +impl From<[u8; 32]> for AdnlRawPublicKey { + fn from(value: [u8; 32]) -> Self { + Self(value) + } +} + +impl TryFrom<&[u8]> for AdnlRawPublicKey { + type Error = TryFromSliceError; + + fn try_from(value: &[u8]) -> Result { + Ok(Self(value.try_into()?)) } } @@ -31,26 +47,42 @@ pub trait AdnlPrivateKey { /// Perform key agreement protocol (usually x25519) between our private key /// and their public - fn key_agreement(&self, their_public: P) -> AdnlSecret; + fn key_agreement(&self, their_public: &P) -> AdnlSecret; /// Get public key corresponding to this private fn public(&self) -> Self::PublicKey; } /// Wrapper struct to hold the secret, result of ECDH between peers +#[derive(Clone)] pub struct AdnlSecret([u8; 32]); /// Wrapper struct to hold ADNL address, which is a hash of public key -#[derive(PartialEq, Debug)] +#[derive(PartialEq, Clone)] pub struct AdnlAddress([u8; 32]); +impl std::fmt::Debug for AdnlAddress { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("AdnlAddress").field(&format!("{:02x?}", &self.0)).finish() + } +} + impl From<[u8; 32]> for AdnlAddress { fn from(value: [u8; 32]) -> Self { Self(value) } } +impl TryFrom<&[u8]> for AdnlAddress { + type Error = TryFromSliceError; + + fn try_from(value: &[u8]) -> Result { + Ok(Self(value.try_into()?)) + } +} + /// Session parameters for AES-CTR encryption of datagrams +#[derive(Clone)] pub struct AdnlAesParams { rx_key: [u8; 32], tx_key: [u8; 32], @@ -166,8 +198,6 @@ pub enum AdnlError { IntegrityError, #[error("TooShortPacket error")] TooShortPacket, - #[error("Incorrect ip address")] - IncorrectAddr(AddrParseError), #[error("Receiver ADNL address mismatch")] UnknownAddr(AdnlAddress), #[error(transparent)] diff --git a/src/integrations/dalek.rs b/src/integrations/dalek.rs index c83c2e4..ba37030 100644 --- a/src/integrations/dalek.rs +++ b/src/integrations/dalek.rs @@ -16,7 +16,7 @@ impl AdnlPublicKey for PublicKey { } } -fn edwards_to_montgomery(public_key: P) -> PublicKey { +fn edwards_to_montgomery(public_key: &P) -> PublicKey { PublicKey::from( CompressedEdwardsY::from_slice(&public_key.to_bytes()) .decompress() @@ -29,7 +29,7 @@ fn edwards_to_montgomery(public_key: P) -> PublicKey { impl AdnlPrivateKey for StaticSecret { type PublicKey = PublicKey; - fn key_agreement(&self, their_public: P) -> AdnlSecret { + fn key_agreement(&self, their_public: &P) -> AdnlSecret { AdnlSecret::from( self.diffie_hellman(&edwards_to_montgomery(their_public)) .to_bytes(), diff --git a/src/lib.rs b/src/lib.rs index 9ca6d46..1babafb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,11 +1,11 @@ pub use helper_types::{ - AdnlAddress, AdnlAesParams, AdnlError, AdnlPrivateKey, AdnlPublicKey, AdnlSecret, + AdnlAddress, AdnlAesParams, AdnlError, AdnlPrivateKey, AdnlPublicKey, AdnlSecret, AdnlRawPublicKey, }; pub use primitives::handshake::AdnlHandshake; pub use primitives::receive::AdnlReceiver; pub use primitives::send::AdnlSender; pub use wrappers::builder::AdnlBuilder; -pub use wrappers::client::AdnlClient; +pub use wrappers::peer::AdnlPeer; mod helper_types; mod integrations; diff --git a/src/primitives/handshake.rs b/src/primitives/handshake.rs index b6253c6..d07d0eb 100644 --- a/src/primitives/handshake.rs +++ b/src/primitives/handshake.rs @@ -1,5 +1,6 @@ +use crate::helper_types::AdnlRawPublicKey; use crate::primitives::AdnlAes; -use crate::{AdnlAddress, AdnlAesParams, AdnlClient, AdnlError, AdnlPrivateKey, AdnlPublicKey, AdnlSecret}; +use crate::{AdnlAddress, AdnlAesParams, AdnlPeer, AdnlError, AdnlPrivateKey, AdnlPublicKey, AdnlSecret}; use aes::cipher::KeyIvInit; use ctr::cipher::StreamCipher; use sha2::{Digest, Sha256}; @@ -64,8 +65,8 @@ impl AdnlHandshake

{ pub async fn perform_handshake( &self, transport: T, - ) -> Result, AdnlError> { - AdnlClient::perform_handshake(transport, self).await + ) -> Result, AdnlError> { + AdnlPeer::perform_handshake(transport, self).await } fn initialize_aes(secret: &AdnlSecret, hash: &[u8]) -> AdnlAes { @@ -87,11 +88,10 @@ impl AdnlHandshake

{ } } -impl AdnlHandshake<[u8; 32]> { +impl AdnlHandshake { /// Deserialize and decrypt handshake - pub fn decrypt_from_raw(packet: &[u8], key: &S) -> Result { - let receiver: [u8; 32] = packet[..32].try_into().unwrap(); - let receiver = AdnlAddress::from(receiver); + pub fn decrypt_from_raw(packet: &[u8; 256], key: &S) -> Result { + let receiver = packet[..32].try_into().unwrap(); let sender = packet[32..64].try_into().unwrap(); let hash: [u8; 32] = packet[64..96].try_into().unwrap(); let mut raw_params: [u8; 160] = packet[96..256].try_into().unwrap(); @@ -100,7 +100,7 @@ impl AdnlHandshake<[u8; 32]> { return Err(AdnlError::UnknownAddr(receiver)) } - let secret = key.key_agreement(sender); + let secret = key.key_agreement(&sender); let mut aes = Self::initialize_aes(&secret, &hash); aes.apply_keystream(&mut raw_params); diff --git a/src/primitives/receive.rs b/src/primitives/receive.rs index a4cf14f..253e124 100644 --- a/src/primitives/receive.rs +++ b/src/primitives/receive.rs @@ -22,6 +22,8 @@ impl AdnlReceiver { /// will be sent to `consumer`, which usually can be just `Vec`. Note that /// data can be processed before this function will return, but in case of /// [`AdnlError::IntegrityError`] you must assume that the data was tampered. + /// + /// Returns received packet length. /// /// You can adjust `BUFFER` according to your memory requirements. /// Recommended size is 8192 bytes. @@ -29,7 +31,7 @@ impl AdnlReceiver { &mut self, transport: &mut R, consumer: &mut C, - ) -> Result<(), AdnlError> { + ) -> Result { // read length let mut length = [0u8; 4]; log::debug!("reading length"); @@ -111,6 +113,6 @@ impl AdnlReceiver { log::debug!("receive finished successfully"); - Ok(()) + Ok(length as usize) } } diff --git a/src/tests.rs b/src/tests.rs index 0d5fb55..68c343c 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -51,11 +51,11 @@ fn test_handshake( // test serializing let aes_params_raw: [u8; 160] = aes_params.try_into().unwrap(); let aes_params = AdnlAesParams::from(aes_params_raw); - let remote_public: [u8; 32] = remote_public.try_into().unwrap(); - let local_public: [u8; 32] = local_public.try_into().unwrap(); + let remote_public = AdnlRawPublicKey::try_from(&*remote_public).unwrap(); + let local_public = AdnlRawPublicKey::try_from(&*local_public).unwrap(); let ecdh_raw: [u8; 32] = ecdh.try_into().unwrap(); let ecdh = AdnlSecret::from(ecdh_raw); - let handshake = AdnlHandshake::new(remote_public.address(), local_public, ecdh, aes_params); + let handshake = AdnlHandshake::new(remote_public.address(), local_public.clone(), ecdh.clone(), aes_params); assert_eq!( handshake.to_bytes(), expected_handshake.as_slice(), @@ -64,27 +64,27 @@ fn test_handshake( // test deserializing struct DummyKey { - ecdh: [u8; 32], - public: [u8; 32] + ecdh: AdnlSecret, + public: AdnlRawPublicKey } impl AdnlPrivateKey for DummyKey { - type PublicKey = [u8; 32]; + type PublicKey = AdnlRawPublicKey; - fn key_agreement(&self, _their_public: P) -> AdnlSecret { - AdnlSecret::from(self.ecdh) + fn key_agreement(&self, _their_public: &P) -> AdnlSecret { + self.ecdh.clone() } fn public(&self) -> Self::PublicKey { - self.public + self.public.clone() } } - let key = DummyKey { ecdh: ecdh_raw, public: remote_public }; - let handshake2 = AdnlHandshake::decrypt_from_raw(&expected_handshake, &key).expect("valid handshake"); + let key = DummyKey { ecdh: ecdh, public: remote_public.clone() }; + let handshake2 = AdnlHandshake::decrypt_from_raw(expected_handshake.as_slice().try_into().unwrap(), &key).expect("invalid handshake"); assert_eq!(handshake2.aes_params().to_bytes(), aes_params_raw, "aes_params mismatch"); assert_eq!(handshake2.receiver(), &remote_public.address(), "receiver mismatch"); - assert_eq!(handshake2.sender(), &local_public, "sender mismatch"); + assert_eq!(handshake2.sender().to_bytes(), local_public.to_bytes(), "sender mismatch"); assert_eq!(&handshake2.to_bytes(), expected_handshake.as_slice(), "reencryption failed"); } diff --git a/src/wrappers/builder.rs b/src/wrappers/builder.rs index 4a73fec..dfa7333 100644 --- a/src/wrappers/builder.rs +++ b/src/wrappers/builder.rs @@ -42,8 +42,8 @@ impl AdnlBuilder { /// Perform key agreement using sender private key and receiver public pub fn perform_ecdh( self, - sender_private: S, - receiver_public: P, + sender_private: &S, + receiver_public: &P, ) -> AdnlHandshake<::PublicKey> where S: AdnlPrivateKey, diff --git a/src/wrappers/mod.rs b/src/wrappers/mod.rs index 0c2e48b..1d2536a 100644 --- a/src/wrappers/mod.rs +++ b/src/wrappers/mod.rs @@ -1,2 +1,2 @@ pub mod builder; -pub mod client; +pub mod peer; \ No newline at end of file diff --git a/src/wrappers/client.rs b/src/wrappers/peer.rs similarity index 64% rename from src/wrappers/client.rs rename to src/wrappers/peer.rs index 72c5049..59a3d25 100644 --- a/src/wrappers/client.rs +++ b/src/wrappers/peer.rs @@ -1,22 +1,22 @@ -use crate::{AdnlBuilder, AdnlError, AdnlHandshake, AdnlPublicKey, AdnlReceiver, AdnlSender}; +use crate::{AdnlBuilder, AdnlError, AdnlHandshake, AdnlPrivateKey, AdnlPublicKey, AdnlReceiver, AdnlSender}; use tokio::io::{empty, AsyncReadExt, AsyncWriteExt}; use tokio::net::{TcpStream, ToSocketAddrs}; use x25519_dalek::StaticSecret; /// Abstraction over [`AdnlSender`] and [`AdnlReceiver`] to keep things simple -pub struct AdnlClient { +pub struct AdnlPeer { sender: AdnlSender, receiver: AdnlReceiver, transport: T, } -impl AdnlClient { - /// Create ADNL client use random private key and random AES params +impl AdnlPeer { + /// Create ADNL client using random private key and random AES params #[cfg(feature = "dalek")] pub async fn connect( - ls_public: P, + ls_public: &P, ls_addr: A, - ) -> Result, AdnlError> { + ) -> Result, AdnlError> { // generate private key let local_secret = StaticSecret::new(rand::rngs::OsRng); @@ -26,15 +26,16 @@ impl AdnlClient { // build handshake using random session keys, encrypt it with ECDH(local_secret, remote_public) // then perform handshake over our TcpStream let client = AdnlBuilder::with_random_aes_params(&mut rand::rngs::OsRng) - .perform_ecdh(local_secret, ls_public) + .perform_ecdh(&local_secret, ls_public) .perform_handshake(transport) .await?; Ok(client) } } -impl AdnlClient { - /// Send `handshake` over `transport` and check that handshake was successful +impl AdnlPeer { + /// Act as a client: send `handshake` over `transport` and check that handshake was successful + /// Returns client part of ADNL connection pub async fn perform_handshake( mut transport: T, handshake: &AdnlHandshake

, @@ -53,12 +54,33 @@ impl AdnlClient { }; let mut empty = empty(); client - .receiver - .receive::<_, _, 0>(&mut client.transport, &mut empty) + .receive_with_buffer::<_, 0>(&mut empty) .await?; Ok(client) } + /// Act as a server: receive handshake over transport. + /// Verifies following things: + /// 1) target ADNL address matches associated with provided private key + /// 2) integrity of handshake is not compromised + pub async fn handle_handshake(mut transport: T, private_key: &S) -> Result { + // receive handshake + let mut packet = [0u8; 256]; + transport.read_exact(&mut packet).await.map_err(AdnlError::ReadError)?; + let handshake = AdnlHandshake::decrypt_from_raw(&packet, private_key)?; + + let mut server = Self { + sender: AdnlSender::new(handshake.aes_params()), + receiver: AdnlReceiver::new(handshake.aes_params()), + transport, + }; + + // send empty packet to proof knowledge of AES keys + server.send(&mut []).await?; + + Ok(server) + } + /// Send `data` to another peer with random nonce pub async fn send(&mut self, data: &mut [u8]) -> Result<(), AdnlError> { self.sender @@ -80,7 +102,7 @@ impl AdnlClient { pub async fn receive( &mut self, consumer: &mut C, - ) -> Result<(), AdnlError> { + ) -> Result { self.receiver .receive::<_, _, 8192>(&mut self.transport, consumer) .await @@ -91,9 +113,9 @@ impl AdnlClient { pub async fn receive_with_buffer( &mut self, consumer: &mut C, - ) -> Result<(), AdnlError> { + ) -> Result { self.receiver .receive::<_, _, BUFFER>(&mut self.transport, consumer) .await } -} +} \ No newline at end of file