diff --git a/Cargo.toml b/Cargo.toml index 64e895f..12f234e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ repository = "https://github.com/tonstack/adnl-rs" keywords = ["ton"] categories = ["network-programming"] license = "MIT" -version = "1.0.0" +version = "2.0.0" authors = ["Vladimir Lebedev "] edition = "2021" @@ -15,24 +15,25 @@ ctr = "0.9.1" aes = "0.8.1" log = "0.4.14" rand_core = "0.6.3" -x25519-dalek = { version = "2.0.0-pre.1", optional = true } -curve25519-dalek = { version = "4.0.0-pre.2", optional = true } -tokio = { version = "1.36", features = ["net", "io-util"]} +tokio = { version = "1", features = ["net", "io-util"] } +tokio-util = { version = "0.7.10", features = ["codec"] } thiserror = "1" rand = "0.8.5" +futures = "0.3" +pin-project = "1" +hex = "0.4.3" +everscale-crypto = "0.2.1" [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"] -dalek = ["x25519-dalek", "curve25519-dalek"] +base64 = "0.22.1" [[example]] name = "time" -required-features = ["dalek"] + +[[example]] +name = "echo_client" + +[[example]] +name = "echo_server" \ No newline at end of file diff --git a/README.md b/README.md index c197cf6..8dc18e1 100644 --- a/README.md +++ b/README.md @@ -5,44 +5,31 @@ Minimal client-server ADNL implementation in Rust. Specification of ADNL is available [here](https://github.com/tonstack/ton-docs/blob/main/ADNL/README.md). -| Feature | Status | -|--------------|---------------------------------| -| ADNL Client | ✅ Implemented | -| ADNL Server | ❌ Not implemented | -| async | ✅ Implemented | -| ed25519 libs | curve25519_dalek + x25519_dalek | - ## Quickstart Run this example: `cargo run --example time` ```rust -use adnl::AdnlClient; -use anyhow::{anyhow, Context, Result}; -use std::net::SocketAddrV4; - +use adnl::AdnlPeer; +use base64::Engine as _; +use futures::{SinkExt, StreamExt}; +use std::error::Error; #[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 = base64::engine::general_purpose::STANDARD.decode("n4VDnSCUuSpjnCyUk9e3QOOd6o0ItSWYbTnW3Wnn8wk=")?; - let ls_ip = "65.21.74.140"; - let ls_port = 46427; - // create AdnlClient - let mut client = - AdnlClient::connect(remote_public, SocketAddrV4::new(ls_ip.parse()?, ls_port)).await?; + // act as a client: connect to ADNL server and perform handshake + let mut client = AdnlPeer::connect(remote_public, "5.9.10.47:19949").await?; - // already serialized TL with gettime query - let mut query = hex::decode("7af98bb435263e6c95d6fecb497dfd0aa5f031e7d412986b5ce720496db512052e8f2d100cdf068c7904345aad16000000000000")?; + // already serialized TL with getTime query + let query = hex::decode("7af98bb435263e6c95d6fecb497dfd0aa5f031e7d412986b5ce720496db512052e8f2d100cdf068c7904345aad16000000000000")?; - // send over ADNL, use random nonce - client.send(&mut query).await?; + // send over ADNL + client.send(query.into()).await?; - // receive result into vector, use 8192 bytes buffer - let mut result = Vec::::new(); - client.receive(&mut result).await?; + // receive result + let result = client.next().await.ok_or_else(|| "no result")??; // get time from serialized TL answer println!( diff --git a/examples/echo_client.rs b/examples/echo_client.rs new file mode 100644 index 0000000..771ba1a --- /dev/null +++ b/examples/echo_client.rs @@ -0,0 +1,26 @@ +use adnl::AdnlPeer; +use futures::{SinkExt, StreamExt}; +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(|| { + "691a14528fb2911839649c489cb4cbec1f4aa126c244c0ea2ac294eb568a7037".to_string() + }); + + // act as a client: connect to ADNL server and perform handshake + let mut client = AdnlPeer::connect(hex::decode(public_key_hex)?, addr).await?; + + // send over ADNL + client.send("hello".as_bytes().into()).await?; + + // receive result + let result = client.next().await.ok_or("packet must be received")??; + + println!("received: {}", String::from_utf8(result.to_vec())?); + Ok(()) +} diff --git a/examples/echo_server.rs b/examples/echo_server.rs new file mode 100644 index 0000000..c24e4c5 --- /dev/null +++ b/examples/echo_server.rs @@ -0,0 +1,67 @@ +//! Adopted from https://github.com/tokio-rs/tokio/blob/b32826bc937a34e4d871c89bb2c3711ed3e20cdc/examples/echo.rs + +use std::{env, error::Error}; + +use adnl::crypto::{KeyPair, SecretKey}; +use adnl::{AdnlAddress, AdnlPeer}; +use futures::{SinkExt, StreamExt}; +use tokio::net::TcpListener; + +#[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(|_| { + "f0971651aec4bb0d65ec3861c597687fda9c1e7d2ee8a93acb9a131aa9f3aee7".to_string() + }); + let private_key_bytes: [u8; 32] = hex::decode(private_key_hex)?.try_into().unwrap(); + let keypair = KeyPair::from(&SecretKey::from_bytes(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(keypair.public_key.as_bytes()) + ); + println!( + "Address is: {}", + hex::encode(AdnlAddress::from(&keypair.public_key).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 = keypair.clone(); + tokio::spawn(async move { + // ADNL: handle handshake + let mut adnl_server = AdnlPeer::handle_handshake(socket, |_| Some(private_key.clone())) + .await + .expect("handshake failed"); + + // In a loop, read data from the socket and write the data back. + while let Some(Ok(packet)) = adnl_server.next().await { + let _ = adnl_server.send(packet).await; + } + }); + } +} diff --git a/examples/time.rs b/examples/time.rs index bc044cf..396584b 100644 --- a/examples/time.rs +++ b/examples/time.rs @@ -1,30 +1,25 @@ -use adnl::AdnlClient; -use anyhow::{anyhow, Context, Result}; -use std::net::SocketAddrV4; +use adnl::AdnlPeer; +use base64::Engine as _; +use futures::{SinkExt, StreamExt}; +use std::error::Error; #[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 = base64::engine::general_purpose::STANDARD + .decode("n4VDnSCUuSpjnCyUk9e3QOOd6o0ItSWYbTnW3Wnn8wk=")?; - let ls_ip = "65.21.74.140"; - let ls_port = 46427; - // create AdnlClient - let mut client = - AdnlClient::connect(remote_public, SocketAddrV4::new(ls_ip.parse()?, ls_port)).await?; + // act as a client: connect to ADNL server and perform handshake + let mut client = AdnlPeer::connect(remote_public, "5.9.10.47:19949").await?; - // already serialized TL with gettime query - let mut query = hex::decode("7af98bb435263e6c95d6fecb497dfd0aa5f031e7d412986b5ce720496db512052e8f2d100cdf068c7904345aad16000000000000")?; + // already serialized TL with getTime query + let query = hex::decode("7af98bb435263e6c95d6fecb497dfd0aa5f031e7d412986b5ce720496db512052e8f2d100cdf068c7904345aad16000000000000")?; - // send over ADNL, use random nonce - client.send(&mut query).await?; + // send over ADNL + client.send(query.into()).await?; - // receive result into vector, use 8192 bytes buffer - let mut result = Vec::::new(); - client.receive(&mut result).await?; + // receive result + let result = client.next().await.ok_or_else(|| "no result")??; // get time from serialized TL answer println!( diff --git a/src/helper_types.rs b/src/helper_types.rs index b025bce..fc3e24e 100644 --- a/src/helper_types.rs +++ b/src/helper_types.rs @@ -1,49 +1,49 @@ +use crate::crypto::PublicKey; 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 {} impl CryptoRandom for T where T: rand_core::RngCore + rand_core::CryptoRng {} -pub trait AdnlPublicKey { - fn address(&self) -> AdnlAddress { - let mut hasher = Sha256::new(); - hasher.update([0xc6, 0xb4, 0x13, 0x48]); // type id - always ed25519 - hasher.update(self.to_bytes()); - AdnlAddress(hasher.finalize().into()) - } +/// Wrapper struct to hold ADNL address, which is a hash of public key +#[derive(PartialEq, Clone)] +pub struct AdnlAddress([u8; 32]); - fn to_bytes(&self) -> [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() + } } -/// Public key can be provided using raw slice -impl AdnlPublicKey for [u8; 32] { - fn to_bytes(&self) -> [u8; 32] { - *self +impl From<[u8; 32]> for AdnlAddress { + fn from(value: [u8; 32]) -> Self { + Self(value) } } -/// Trait which must be implemented to perform key agreement inside [`AdnlHandshake`] -pub trait AdnlPrivateKey { - type PublicKey: AdnlPublicKey; - - /// Perform key agreement protocol (usually x25519) between our private key - /// and their public - fn key_agreement(&self, their_public: P) -> AdnlSecret; +impl TryFrom<&[u8]> for AdnlAddress { + type Error = TryFromSliceError; - /// Get public key corresponding to this private - fn public(&self) -> Self::PublicKey; + fn try_from(value: &[u8]) -> Result { + Ok(Self(value.try_into()?)) + } } -/// Wrapper struct to hold the secret, result of ECDH between peers -pub struct AdnlSecret([u8; 32]); - -/// Wrapper struct to hold ADNL address, which is a hash of public key -pub struct AdnlAddress([u8; 32]); +impl From<&PublicKey> for AdnlAddress { + fn from(value: &PublicKey) -> Self { + let mut hasher = Sha256::new(); + hasher.update([0xc6, 0xb4, 0x13, 0x48]); // type id - always ed25519 + hasher.update(value.as_bytes()); + AdnlAddress(hasher.finalize().into()) + } +} /// Session parameters for AES-CTR encryption of datagrams +#[derive(Clone)] pub struct AdnlAesParams { rx_key: [u8; 32], tx_key: [u8; 32], @@ -94,13 +94,9 @@ impl AdnlAesParams { /// Generate random session parameters pub fn random(csprng: &mut T) -> Self { - let mut result: AdnlAesParams = Default::default(); - csprng.fill_bytes(&mut result.rx_key); - csprng.fill_bytes(&mut result.tx_key); - csprng.fill_bytes(&mut result.rx_nonce); - csprng.fill_bytes(&mut result.tx_nonce); - csprng.fill_bytes(&mut result.padding); - result + let mut result = [0u8; 160]; + csprng.fill_bytes(&mut result); + Self::from(result) } } @@ -116,12 +112,6 @@ impl Default for AdnlAesParams { } } -impl From<[u8; 32]> for AdnlSecret { - fn from(secret: [u8; 32]) -> Self { - Self(secret) - } -} - impl AdnlAddress { #[inline] pub fn to_bytes(&self) -> [u8; 32] { @@ -134,33 +124,44 @@ impl AdnlAddress { } } -impl AdnlSecret { - #[inline] - pub fn to_bytes(&self) -> [u8; 32] { - self.0 - } - - #[inline] - pub fn as_bytes(&self) -> &[u8; 32] { - &self.0 - } -} - /// Common error type #[derive(Debug, Error)] pub enum AdnlError { - #[error("Read error")] - ReadError(Error), - #[error("Write error")] - WriteError(Error), - #[error("Consume error")] - ConsumeError(Error), + #[error("IO error")] + IoError(#[from] Error), #[error("Integrity error")] IntegrityError, - #[error("TooShortPacket error")] + #[error("Too short packet (32 bytes min)")] TooShortPacket, - #[error("Incorrect ip address")] - IncorrectAddr(AddrParseError), - #[error(transparent)] - OtherError(#[from] Error), + #[error("Too long packet (4 MiB max)")] + TooLongPacket, + #[error("Receiver ADNL address mismatch")] + UnknownAddr(AdnlAddress), + #[error("End of stream")] + EndOfStream, + #[error("Invalid public key")] + InvalidPublicKey, +} + +/// Information about connected peers. +pub struct AdnlConnectionInfo { + local_address: AdnlAddress, + remote_address: AdnlAddress, +} + +impl AdnlConnectionInfo { + pub fn new(local_address: AdnlAddress, remote_address: AdnlAddress) -> Self { + Self { + local_address, + remote_address, + } + } + + pub fn local_address(&self) -> &AdnlAddress { + &self.local_address + } + + pub fn remote_address(&self) -> &AdnlAddress { + &self.remote_address + } } diff --git a/src/integrations/dalek.rs b/src/integrations/dalek.rs deleted file mode 100644 index c83c2e4..0000000 --- a/src/integrations/dalek.rs +++ /dev/null @@ -1,42 +0,0 @@ -//! Integration with `curve25519_dalek` and `x25519_dalek` libraries - -use crate::helper_types::AdnlPrivateKey; -use crate::{AdnlPublicKey, AdnlSecret}; -use curve25519_dalek::edwards::CompressedEdwardsY; -use curve25519_dalek::montgomery::MontgomeryPoint; -use x25519_dalek::{PublicKey, StaticSecret}; - -impl AdnlPublicKey for PublicKey { - fn to_bytes(&self) -> [u8; 32] { - MontgomeryPoint(self.to_bytes()) - .to_edwards(0) - .unwrap() - .compress() - .to_bytes() - } -} - -fn edwards_to_montgomery(public_key: P) -> PublicKey { - PublicKey::from( - CompressedEdwardsY::from_slice(&public_key.to_bytes()) - .decompress() - .unwrap() - .to_montgomery() - .to_bytes(), - ) -} - -impl AdnlPrivateKey for StaticSecret { - type PublicKey = PublicKey; - - fn key_agreement(&self, their_public: P) -> AdnlSecret { - AdnlSecret::from( - self.diffie_hellman(&edwards_to_montgomery(their_public)) - .to_bytes(), - ) - } - - fn public(&self) -> Self::PublicKey { - PublicKey::from(self) - } -} diff --git a/src/integrations/mod.rs b/src/integrations/mod.rs deleted file mode 100644 index 3fb4b1b..0000000 --- a/src/integrations/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -#[cfg(feature = "dalek")] -pub mod dalek; diff --git a/src/lib.rs b/src/lib.rs index 9ca6d46..7500368 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,14 +1,55 @@ -pub use helper_types::{ - AdnlAddress, AdnlAesParams, AdnlError, AdnlPrivateKey, AdnlPublicKey, AdnlSecret, -}; +//! # ADNL +//! +//! This crate provides a minimal implementation of the Abstract Datagram Network Layer (ADNL) +//! protocol in Rust. ADNL is a network protocol used in The Open Network (TON) blockchain. +//! +//! ## Client example +//! +//! ```rust,no_run +//! use adnl::AdnlPeer; +//! use base64::Engine as _; +//! use futures::{SinkExt, StreamExt}; +//! use std::error::Error; +//! +//! #[tokio::main] +//! async fn main() -> Result<(), Box> { +//! // decode liteserver public key +//! let remote_public = base64::engine::general_purpose::STANDARD.decode("n4VDnSCUuSpjnCyUk9e3QOOd6o0ItSWYbTnW3Wnn8wk=")?; +//! +//! // act as a client: connect to ADNL server and perform handshake +//! let mut client = AdnlPeer::connect(remote_public, "5.9.10.47:19949").await?; +//! +//! // already serialized TL with getTime query +//! let query = hex::decode("7af98bb435263e6c95d6fecb497dfd0aa5f031e7d412986b5ce720496db512052e8f2d100cdf068c7904345aad16000000000000")?; +//! +//! // send over ADNL +//! client.send(query.into()).await?; +//! +//! // receive result +//! let result = client.next().await.ok_or_else(|| "no result")??; +//! +//! // get time from serialized TL answer +//! println!( +//! "received: {}", +//! u32::from_le_bytes(result[result.len() - 7..result.len() - 3].try_into()?) +//! ); +//! Ok(()) +//! } +//! ``` +//! +//! See the `examples/` directory for more usage examples. + +pub use helper_types::{AdnlAddress, AdnlAesParams, AdnlConnectionInfo, AdnlError}; +pub use primitives::codec::AdnlCodec; 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; + +pub mod crypto { + pub use everscale_crypto::ed25519::*; +} mod helper_types; -mod integrations; mod primitives; mod wrappers; diff --git a/src/primitives/codec.rs b/src/primitives/codec.rs new file mode 100644 index 0000000..ded0be3 --- /dev/null +++ b/src/primitives/codec.rs @@ -0,0 +1,114 @@ +use aes::cipher::{KeyIvInit, StreamCipher}; +use sha2::{Digest, Sha256}; +use tokio_util::{ + bytes::{Buf, Bytes, BytesMut}, + codec::{Decoder, Encoder}, +}; + +use crate::{AdnlAesParams, AdnlError}; + +use super::AdnlAes; + +/// Implementation of ADNL protocol. Connection must be first initialized with [`AdnlHandshake`] to exchange keys. +pub struct AdnlCodec { + aes_rx: AdnlAes, + aes_tx: AdnlAes, + last_readed_length: Option, +} + +impl AdnlCodec { + pub fn client(aes_params: &AdnlAesParams) -> Self { + Self { + aes_rx: AdnlAes::new(aes_params.rx_key().into(), aes_params.rx_nonce().into()), + aes_tx: AdnlAes::new(aes_params.tx_key().into(), aes_params.tx_nonce().into()), + last_readed_length: None, + } + } + + pub fn server(aes_params: &AdnlAesParams) -> Self { + Self { + aes_rx: AdnlAes::new(aes_params.tx_key().into(), aes_params.tx_nonce().into()), + aes_tx: AdnlAes::new(aes_params.rx_key().into(), aes_params.rx_nonce().into()), + last_readed_length: None, + } + } +} + +impl Decoder for AdnlCodec { + type Item = Bytes; + + type Error = AdnlError; + + fn decode(&mut self, src: &mut BytesMut) -> Result, Self::Error> { + let length = if let Some(length) = self.last_readed_length { + length + } else { + if src.len() < 4 { + return Ok(None); + } + self.aes_rx.apply_keystream(&mut src[..4]); + let mut length_bytes = [0u8; 4]; + length_bytes.copy_from_slice(&src[..4]); + let length = u32::from_le_bytes(length_bytes) as usize; + if length < 64 { + return Err(AdnlError::TooShortPacket); + } + if length > (1 << 24) { + return Err(AdnlError::TooLongPacket); + } + src.advance(4); + self.last_readed_length = Some(length); + length + }; + + // not enough bytes, need to wait for more data + if src.len() < length { + if src.capacity() < length { + src.reserve(length - src.capacity()); + } + return Ok(None); + } + + self.last_readed_length = None; + + // decode packet + self.aes_rx.apply_keystream(&mut src[..length]); + let given_hash = &src[length - 32..length]; + + // integrity check + let mut hasher = Sha256::new(); + hasher.update(&src[..length - 32]); + if given_hash != hasher.finalize().as_slice() { + return Err(AdnlError::IntegrityError); + } + + // copy and return buffer + let result = Bytes::copy_from_slice(&src[32..length - 32]); + src.advance(length); + Ok(Some(result)) + } +} + +impl Encoder for AdnlCodec { + type Error = AdnlError; + + fn encode(&mut self, buffer: Bytes, dst: &mut BytesMut) -> Result<(), Self::Error> { + if buffer.len() > ((1 << 24) - 64) { + return Err(AdnlError::TooLongPacket); + } + let length = ((buffer.len() + 64) as u32).to_le_bytes(); + let nonce = rand::random::<[u8; 32]>(); + let mut hash = Sha256::new(); + hash.update(nonce); + hash.update(&buffer); + let hash = hash.finalize(); + dst.reserve(buffer.len() + 68); + dst.extend_from_slice(&length); + dst.extend_from_slice(&nonce); + dst.extend_from_slice(&buffer); + dst.extend_from_slice(&hash); + let start_offset = dst.len() - buffer.len() - 68; + self.aes_tx.apply_keystream(&mut dst[start_offset..]); + Ok(()) + } +} diff --git a/src/primitives/handshake.rs b/src/primitives/handshake.rs index 7696d4c..90ea1ff 100644 --- a/src/primitives/handshake.rs +++ b/src/primitives/handshake.rs @@ -1,25 +1,28 @@ +use crate::crypto::{KeyPair, PublicKey}; use crate::primitives::AdnlAes; -use crate::{AdnlAddress, AdnlAesParams, AdnlClient, AdnlError, AdnlPublicKey, AdnlSecret}; +use crate::{AdnlAddress, AdnlAesParams, AdnlError, AdnlPeer}; use aes::cipher::KeyIvInit; use ctr::cipher::StreamCipher; use sha2::{Digest, Sha256}; use tokio::io::{AsyncReadExt, AsyncWriteExt}; +use super::codec::AdnlCodec; + /// Handshake packet, must be sent from client to server prior to any datagrams -pub struct AdnlHandshake { +pub struct AdnlHandshake { receiver: AdnlAddress, - sender: P, + sender: PublicKey, aes_params: AdnlAesParams, - secret: AdnlSecret, + secret: [u8; 32], } -impl AdnlHandshake

{ +impl AdnlHandshake { /// Create handshake with given sender and receiver, who already agreed on given secret, also /// use given session parameters pub fn new( receiver: AdnlAddress, - sender: P, - secret: AdnlSecret, + sender: PublicKey, + secret: [u8; 32], aes_params: AdnlAesParams, ) -> Self { Self { @@ -35,37 +38,102 @@ impl AdnlHandshake

{ &self.aes_params } + /// Get initiator public key of this handshake + pub fn sender(&self) -> &PublicKey { + &self.sender + } + + /// Get destination ADNL address of this handshake + pub fn receiver(&self) -> &AdnlAddress { + &self.receiver + } + /// Serialize handshake to send it over the transport pub fn to_bytes(&self) -> [u8; 256] { - let mut packet = [0u8; 256]; - packet[..32].copy_from_slice(self.receiver.as_bytes()); - packet[32..64].copy_from_slice(&self.sender.to_bytes()); - let mut raw_params = self.aes_params.to_bytes(); - let mut hasher = Sha256::new(); - hasher.update(raw_params); - let hash: [u8; 32] = hasher.finalize().into(); - - let mut key = [0u8; 32]; - key[..16].copy_from_slice(&self.secret.as_bytes()[..16]); - key[16..32].copy_from_slice(&hash[16..32]); - let mut nonce = [0u8; 16]; - nonce[..4].copy_from_slice(&hash[..4]); - nonce[4..16].copy_from_slice(&self.secret.as_bytes()[20..32]); - - let mut aes = AdnlAes::new(key.as_slice().into(), nonce.as_slice().into()); + let hash = Self::sha256(raw_params); + let mut aes = Self::initialize_aes(&self.secret, &hash); aes.apply_keystream(&mut raw_params); + let mut packet = [0u8; 256]; + packet[..32].copy_from_slice(self.receiver.as_bytes()); + packet[32..64].copy_from_slice(self.sender.as_bytes()); packet[64..96].copy_from_slice(&hash); packet[96..256].copy_from_slice(&raw_params); packet } + pub fn make_client_codec(&self) -> AdnlCodec { + AdnlCodec::client(&self.aes_params) + } + + pub fn make_server_codec(&self) -> AdnlCodec { + AdnlCodec::server(&self.aes_params) + } + /// Send handshake over the given transport, build [`AdnlClient`] on top of it pub async fn perform_handshake( &self, transport: T, - ) -> Result, AdnlError> { - AdnlClient::perform_handshake(transport, self).await + ) -> Result, AdnlError> { + AdnlPeer::perform_custom_handshake(transport, self).await + } + + fn initialize_aes(secret: &[u8; 32], hash: &[u8]) -> AdnlAes { + let mut key = [0u8; 32]; + key[..16].copy_from_slice(&secret[..16]); + key[16..32].copy_from_slice(&hash[16..32]); + + let mut nonce = [0u8; 16]; + nonce[..4].copy_from_slice(&hash[..4]); + nonce[4..16].copy_from_slice(&secret[20..32]); + + AdnlAes::new(key.as_slice().into(), nonce.as_slice().into()) + } + + fn sha256(data: impl AsRef<[u8]>) -> [u8; 32] { + let mut hasher = Sha256::new(); + hasher.update(data); + hasher.finalize().into() + } + + /// Deserialize and decrypt handshake using keypair from `keypair_selector` function + pub fn decrypt_from_raw Option>( + packet: &[u8; 256], + keypair_selector: F, + ) -> Result { + let receiver = packet[..32].try_into().unwrap(); + let sender = PublicKey::from_bytes(packet[32..64].try_into().unwrap()) + .ok_or_else(|| AdnlError::InvalidPublicKey)?; + let hash: [u8; 32] = packet[64..96].try_into().unwrap(); + let mut raw_params: [u8; 160] = packet[96..256].try_into().unwrap(); + + let keypair = + keypair_selector(&receiver).ok_or_else(|| AdnlError::UnknownAddr(receiver.clone()))?; + + let our_address = AdnlAddress::from(&keypair.public_key); + if our_address != receiver { + log::error!( + "private key selector returned wrong key, expected address: {:?}, got: {:?}", + &receiver, + our_address + ); + return Err(AdnlError::UnknownAddr(receiver)); + } + + let secret = keypair.compute_shared_secret(&sender); + let mut aes = Self::initialize_aes(&secret, &hash); + aes.apply_keystream(&mut raw_params); + + if hash != Self::sha256(raw_params) { + return Err(AdnlError::IntegrityError); + } + + Ok(Self { + receiver, + sender, + aes_params: AdnlAesParams::from(raw_params), + secret, + }) } } diff --git a/src/primitives/mod.rs b/src/primitives/mod.rs index b32d077..1836048 100644 --- a/src/primitives/mod.rs +++ b/src/primitives/mod.rs @@ -3,6 +3,5 @@ use ctr::Ctr128BE; pub type AdnlAes = Ctr128BE; +pub mod codec; pub mod handshake; -pub mod receive; -pub mod send; diff --git a/src/primitives/receive.rs b/src/primitives/receive.rs deleted file mode 100644 index a4cf14f..0000000 --- a/src/primitives/receive.rs +++ /dev/null @@ -1,116 +0,0 @@ -use crate::primitives::AdnlAes; -use crate::{AdnlAesParams, AdnlError}; -use aes::cipher::KeyIvInit; -use ctr::cipher::StreamCipher; -use sha2::{Digest, Sha256}; -use tokio::io::{AsyncReadExt, AsyncWriteExt}; - -/// Low-level incoming datagram processor -pub struct AdnlReceiver { - aes: AdnlAes, -} - -impl AdnlReceiver { - /// Create receiver with given session parameters - pub fn new(aes_params: &AdnlAesParams) -> Self { - Self { - aes: AdnlAes::new(aes_params.rx_key().into(), aes_params.rx_nonce().into()), - } - } - - /// Receive datagram from `transport`. Received parts of the decrypted buffer - /// 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. - /// - /// You can adjust `BUFFER` according to your memory requirements. - /// Recommended size is 8192 bytes. - pub async fn receive( - &mut self, - transport: &mut R, - consumer: &mut C, - ) -> Result<(), AdnlError> { - // read length - let mut length = [0u8; 4]; - log::debug!("reading length"); - transport - .read_exact(&mut length) - .await - .map_err(AdnlError::ReadError)?; - self.aes.apply_keystream(&mut length); - let length = u32::from_le_bytes(length); - log::debug!("length = {}", length); - if length < 64 { - return Err(AdnlError::TooShortPacket); - } - - let mut hasher = Sha256::new(); - - // read nonce - let mut nonce = [0u8; 32]; - log::debug!("reading nonce"); - transport - .read_exact(&mut nonce) - .await - .map_err(AdnlError::ReadError)?; - self.aes.apply_keystream(&mut nonce); - hasher.update(nonce); - - // read buffer chunks, decrypt and write to consumer - if BUFFER > 0 { - let mut buffer = [0u8; BUFFER]; - let mut bytes_to_read = length as usize - 64; - while bytes_to_read >= BUFFER { - log::debug!( - "chunked read (chunk len = {}), {} bytes remaining", - BUFFER, - bytes_to_read - ); - transport - .read_exact(&mut buffer) - .await - .map_err(AdnlError::ReadError)?; - self.aes.apply_keystream(&mut buffer); - hasher.update(buffer); - consumer - .write_all(&buffer) - .await - .map_err(AdnlError::WriteError)?; - bytes_to_read -= BUFFER; - } - - // read last chunk - if bytes_to_read > 0 { - log::debug!("last chunk, {} bytes remaining", bytes_to_read); - let buffer = &mut buffer[..bytes_to_read]; - transport - .read_exact(buffer) - .await - .map_err(AdnlError::ReadError)?; - self.aes.apply_keystream(buffer); - hasher.update(&buffer); - consumer - .write_all(buffer) - .await - .map_err(AdnlError::WriteError)?; - } - } - - let mut given_hash = [0u8; 32]; - log::debug!("reading hash"); - transport - .read_exact(&mut given_hash) - .await - .map_err(AdnlError::ReadError)?; - self.aes.apply_keystream(&mut given_hash); - - let real_hash = hasher.finalize(); - if real_hash.as_slice() != given_hash { - return Err(AdnlError::IntegrityError); - } - - log::debug!("receive finished successfully"); - - Ok(()) - } -} diff --git a/src/primitives/send.rs b/src/primitives/send.rs deleted file mode 100644 index 41a7e93..0000000 --- a/src/primitives/send.rs +++ /dev/null @@ -1,71 +0,0 @@ -use aes::cipher::KeyIvInit; -use ctr::cipher::StreamCipher; -use sha2::{Digest, Sha256}; -use tokio::io::AsyncWriteExt; - -use crate::primitives::AdnlAes; -use crate::{AdnlAesParams, AdnlError}; - -/// Low-level outgoing datagram generator -pub struct AdnlSender { - aes: AdnlAes, -} - -impl AdnlSender { - /// Create sender with given session parameters - pub fn new(aes_params: &AdnlAesParams) -> Self { - Self { - aes: AdnlAes::new(aes_params.tx_key().into(), aes_params.tx_nonce().into()), - } - } - - /// Get estimated size of datagram for the given buffer - pub fn estimate_packet_length(buffer: &[u8]) -> u32 { - buffer.len() as u32 + 68 - } - - /// Send `buffer` over `transport` with `nonce`. Note that `nonce` must be random - /// in order to prevent bit-flipping attacks when an attacker knows whole plaintext in datagram. - pub async fn send( - &mut self, - transport: &mut W, - nonce: &mut [u8; 32], - buffer: &mut [u8], - ) -> Result<(), AdnlError> { - // remember not to send more than 4 GiB in a single packet - let mut length = ((buffer.len() + 64) as u32).to_le_bytes(); - - // calc hash - let mut hasher = Sha256::new(); - hasher.update(*nonce); - hasher.update(&*buffer); - let mut hash: [u8; 32] = hasher.finalize().into(); - - // encrypt packet - self.aes.apply_keystream(&mut length); - self.aes.apply_keystream(nonce); - self.aes.apply_keystream(buffer); - self.aes.apply_keystream(&mut hash); - - // write to transport - transport - .write_all(&length) - .await - .map_err(AdnlError::WriteError)?; - transport - .write_all(nonce) - .await - .map_err(AdnlError::WriteError)?; - transport - .write_all(buffer) - .await - .map_err(AdnlError::WriteError)?; - transport - .write_all(&hash) - .await - .map_err(AdnlError::WriteError)?; - transport.flush().await.map_err(AdnlError::WriteError)?; - - Ok(()) - } -} diff --git a/src/tests.rs b/src/tests.rs index 9474b04..0e014bf 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -1,7 +1,15 @@ extern crate alloc; use super::*; +use crate::crypto::{KeyPair, PublicKey}; use alloc::vec::Vec; +use futures::{SinkExt, StreamExt}; +use rand_core::OsRng; +use tokio::net::TcpListener; +use tokio_util::{ + bytes::BytesMut, + codec::{Decoder, Encoder}, +}; #[test] fn test_handshake_1() { @@ -48,88 +56,154 @@ fn test_handshake( aes_params: Vec, expected_handshake: Vec, ) { - let aes_params: [u8; 160] = aes_params.try_into().unwrap(); - let aes_params = AdnlAesParams::from(aes_params); - let remote_public: [u8; 32] = remote_public.try_into().unwrap(); - let local_public: [u8; 32] = local_public.try_into().unwrap(); - let ecdh: [u8; 32] = ecdh.try_into().unwrap(); - let ecdh = AdnlSecret::from(ecdh); - let handshake = AdnlHandshake::new(remote_public.address(), local_public, ecdh, aes_params); + // test serializing + let aes_params_raw: [u8; 160] = aes_params.try_into().unwrap(); + let aes_params = AdnlAesParams::from(aes_params_raw); + let remote_public = + PublicKey::from_bytes(remote_public.as_slice().try_into().unwrap()).unwrap(); + let local_public = PublicKey::from_bytes(local_public.as_slice().try_into().unwrap()).unwrap(); + let ecdh_raw: [u8; 32] = ecdh.try_into().unwrap(); + let handshake = AdnlHandshake::new( + AdnlAddress::from(&remote_public), + local_public.clone(), + ecdh_raw, + aes_params, + ); assert_eq!( handshake.to_bytes(), expected_handshake.as_slice(), "handshake is not the same!" ); + + // test deserializing + // let handshake2 = AdnlHandshake::decrypt_from_raw(expected_handshake.as_slice().try_into().unwrap(), |_| Some(key.clone())).expect("invalid handshake"); + // assert_eq!(handshake2.aes_params().to_bytes(), aes_params_raw, "aes_params mismatch"); + // assert_eq!(handshake2.receiver(), &AdnlAddress::from(&remote_public), "receiver mismatch"); + // assert_eq!(handshake2.sender(), &local_public, "sender mismatch"); + // assert_eq!(&handshake2.to_bytes(), expected_handshake.as_slice(), "reencryption failed"); } -#[tokio::test] -async fn test_send_1() { +#[test] +fn test_send_1() { let aes_params = hex::decode("b3d529e34b839a521518447b68343aebaae9314ac95aaacfdb687a2163d1a98638db306b63409ef7bc906b4c9dc115488cf90dfa964f520542c69e1a4a495edf9ae9ee72023203c8b266d552f251e8d724929733428c8e276ab3bd6291367336a6ab8dc3d36243419bd0b742f76691a5dec14edbd50f7c1b58ec961ae45be58cbf6623f3ec9705bd5d227761ec79cee377e2566ff668f863552bddfd6ff3a16b").unwrap(); - let nonce = + let _nonce = hex::decode("9a5ecd5d9afdfff2823e7520fa1c338f2baf1a21f51e6fdab0491d45a50066f7").unwrap(); let buffer = hex::decode("7af98bb471ff48e9b263959b17a04faae4a23501380d2aa932b09eac6f9846fcbae9bbcb0cdf068c7904345aad16000000000000").unwrap(); let expected_packet = hex::decode("250d70d08526791bc2b6278ded7bf2b051afb441b309dda06f76e4419d7c31d4d5baafc4ff71e0ebabe246d4ea19e3e579bd15739c8fc916feaf46ea7a6bc562ed1cf87c9bf4220eb037b9a0b58f663f0474b8a8b18fa24db515e41e4b02e509d8ef261a27ba894cbbecc92e59fc44bf5ff7c8281cb5e900").unwrap(); - test_send(aes_params, nonce, buffer, expected_packet).await; + test_send(aes_params, buffer, expected_packet); } -#[tokio::test] -async fn test_send_2() { +#[test] +fn test_send_2() { let aes_params = hex::decode("7e3c66de7c64d4bee4368e69560101991db4b084430a336cffe676c9ac0a795d8c98367309422a8e927e62ed657ba3eaeeb6acd3bbe5564057dfd1d60609a25a48963cbb7d14acf4fc83ec59254673bc85be22d04e80e7b83c641d37cae6e1d82a400bf159490bbc0048e69234ad89e999d792eefdaa56734202546d9188706e95e1272267206a8e7ee1f7c077f76bd26e494972e34d72e257bf20364dbf39b0").unwrap(); - let nonce = + let _nonce = hex::decode("d36d0683da23e62910fa0e8a9331dfc257db4cde0ba8d63893e88ac4de7d8d6c").unwrap(); let buffer = hex::decode("7af98bb47bcae111ea0e56457826b1aec7f0f59b9b6579678b3db3839d17b63eb60174f20cdf068c7904345aad16000000000000").unwrap(); let expected_packet = hex::decode("24c709a0f676750ddaeafc8564d84546bfc831af27fb66716de382a347a1c32adef1a27e597c8a07605a09087fff32511d314970cad3983baefff01e7ee51bb672b17f7914a6d3f229a13acb14cdc14d98beae8a1e96510756726913541f558c2ffac63ed6cb076d0e888c3c0bb014d9f229c2a3f62e0847").unwrap(); - test_send(aes_params, nonce, buffer, expected_packet).await; + test_send(aes_params, buffer, expected_packet); } -async fn test_send(aes_params: Vec, nonce: Vec, buffer: Vec, expected_packet: Vec) { - let mut nonce = nonce.try_into().unwrap(); - let mut buffer = buffer; +fn test_send(aes_params: Vec, buffer: Vec, expected_packet: Vec) { let aes_params: [u8; 160] = aes_params.try_into().unwrap(); - let aes_params = AdnlAesParams::from(aes_params); - let mut protocol_client = AdnlSender::new(&aes_params); - let mut packet = Vec::::new(); - let _result = protocol_client - .send(&mut packet, &mut nonce, &mut buffer) - .await; + let mut codec = AdnlCodec::client(&aes_params.into()); + let mut packet = BytesMut::new(); + codec + .encode(buffer.clone().into(), &mut packet) + .expect("packet must be encoded correctly"); + + // do not check nonce and hash as it's random assert_eq!( - packet.as_slice(), - &expected_packet, - "outcoming packet is wrong" + &packet[..4], + &expected_packet[..4], + "outcoming packet length is wrong" ); + assert_eq!( + &packet[36..packet.len() - 32], + &expected_packet[36..expected_packet.len() - 32], + "outcoming packet length is wrong" + ); + + // check packet decoding to original buffer + // swap aes params + let mut new_aes_params = [0u8; 160]; + new_aes_params[..32].copy_from_slice(&aes_params[32..64]); + new_aes_params[32..64].copy_from_slice(&aes_params[..32]); + new_aes_params[64..80].copy_from_slice(&aes_params[80..96]); + new_aes_params[80..96].copy_from_slice(&aes_params[64..80]); + new_aes_params[96..160].copy_from_slice(&aes_params[96..160]); + let mut codec = AdnlCodec::client(&new_aes_params.into()); + test_recv(&mut codec, packet.into(), buffer); } -#[tokio::test] -async fn test_recv_1() { +#[test] +fn test_recv_1() { let encrypted_data = hex::decode("81e95e433c87c9ad2a716637b3a12644fbfb12dbd02996abc40ed2beb352483d6ecf9e2ad181a5abde4d4146ca3a8524739d3acebb2d7599cc6b81967692a62118997e16").unwrap(); let expected_data = Vec::new(); let aes_params = hex::decode("b3d529e34b839a521518447b68343aebaae9314ac95aaacfdb687a2163d1a98638db306b63409ef7bc906b4c9dc115488cf90dfa964f520542c69e1a4a495edf9ae9ee72023203c8b266d552f251e8d724929733428c8e276ab3bd6291367336a6ab8dc3d36243419bd0b742f76691a5dec14edbd50f7c1b58ec961ae45be58cbf6623f3ec9705bd5d227761ec79cee377e2566ff668f863552bddfd6ff3a16b").unwrap(); - let aes_params: [u8; 160] = aes_params.try_into().unwrap(); - let aes_params = AdnlAesParams::from(aes_params); - let mut protocol_client = AdnlReceiver::new(&aes_params); - test_recv(&mut protocol_client, encrypted_data, expected_data).await; + let aes_params: [u8; 160] = aes_params.as_slice().try_into().unwrap(); + let mut codec = AdnlCodec::client(&aes_params.into()); + test_recv(&mut codec, encrypted_data, expected_data); let encrypted_data = hex::decode("4b72a32bf31894cce9ceffd2dd97176e502946524e45e62689bd8c5d31ad53603c5fd3b402771f707cd2747747fad9df52e6c23ceec9fa2ee5b0f68b61c33c7790db03d1c593798a29d716505cea75acdf0e031c25447c55c4d29d32caab29bd5a0787644843bafc04160c92140aab0ecc990927").unwrap(); let expected_data = hex::decode("1684ac0f71ff48e9b263959b17a04faae4a23501380d2aa932b09eac6f9846fcbae9bbcb080d0053e9a3ac3062000000").unwrap(); - test_recv(&mut protocol_client, encrypted_data, expected_data).await; + test_recv(&mut codec, encrypted_data, expected_data); } -#[tokio::test] -async fn test_recv_2() { +#[test] +fn test_recv_2() { let encrypted_data = hex::decode("b75dcf27582beb4031d6d3700c9b7925bf84a78f2bd16b186484d36427a8824ac86e27cea81eb5bcbac447a37269845c65be51babd11c80627f81b4247f84df16d05c4f1").unwrap(); let expected_data = Vec::new(); let aes_params = hex::decode("7e3c66de7c64d4bee4368e69560101991db4b084430a336cffe676c9ac0a795d8c98367309422a8e927e62ed657ba3eaeeb6acd3bbe5564057dfd1d60609a25a48963cbb7d14acf4fc83ec59254673bc85be22d04e80e7b83c641d37cae6e1d82a400bf159490bbc0048e69234ad89e999d792eefdaa56734202546d9188706e95e1272267206a8e7ee1f7c077f76bd26e494972e34d72e257bf20364dbf39b0").unwrap(); - let aes_params: [u8; 160] = aes_params.try_into().unwrap(); - let aes_params = AdnlAesParams::from(aes_params); - let mut protocol_client = AdnlReceiver::new(&aes_params); - test_recv(&mut protocol_client, encrypted_data, expected_data).await; + let aes_params: [u8; 160] = aes_params.as_slice().try_into().unwrap(); + let mut codec = AdnlCodec::client(&aes_params.into()); + test_recv(&mut codec, encrypted_data, expected_data); let encrypted_data = hex::decode("77ebea5a6e6c8758e7703d889abad16e7e3c4e0c10c4e81ca10d0d9abddabb6f008905133a070ff825ad3f4b0ae969e04dbd8b280864d3d2175f3bc7cf3deb31de5497fa43997d8e2acafb9a31de2a22ecb279b5854c00791216e39c2e65863539d82716fc020e9647b2dd99d0f14e4f553b645f").unwrap(); let expected_data = hex::decode("1684ac0f7bcae111ea0e56457826b1aec7f0f59b9b6579678b3db3839d17b63eb60174f2080d0053e90bb03062000000").unwrap(); - test_recv(&mut protocol_client, encrypted_data, expected_data).await; + test_recv(&mut codec, encrypted_data, expected_data); } -async fn test_recv(client: &mut AdnlReceiver, encrypted_packet: Vec, expected_data: Vec) { - let mut data = Vec::::new(); - let mut binding = encrypted_packet.as_slice(); - let _r = client.receive::<_, _, 8192>(&mut binding, &mut data).await; +fn test_recv(codec: &mut AdnlCodec, encrypted_packet: Vec, expected_data: Vec) { + let data = codec + .decode(&mut encrypted_packet.as_slice().into()) + .expect("decoding must be correct") + .expect("input must contain full packet"); assert_eq!(data, expected_data.as_slice(), "incoming packet is wrong"); } + +#[tokio::test] +async fn integrity_test() { + let keypair = KeyPair::generate(&mut OsRng); + let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); + let port = listener.local_addr().unwrap().port(); + let server_public = keypair.public_key; + tokio::spawn(async move { + loop { + let (socket, _) = listener.accept().await.unwrap(); + let keypair = keypair.clone(); + tokio::spawn(async move { + let mut adnl_server = AdnlPeer::handle_handshake(socket, |_| Some(keypair)) + .await + .expect("handshake failed"); + while let Some(Ok(packet)) = adnl_server.next().await { + let _ = adnl_server.send(packet).await; + } + }); + } + }); + + // act as a client: connect to ADNL server and perform handshake + let mut client = AdnlPeer::connect(server_public.as_bytes(), ("127.0.0.1", port)) + .await + .expect("adnl connect"); + + // send over ADNL + client.send("hello".as_bytes().into()).await.expect("send"); + + // receive result + let result = client + .next() + .await + .expect("packet must be received") + .expect("packet must be decoded properly"); + + assert_eq!(result, "hello".as_bytes()); +} diff --git a/src/wrappers/builder.rs b/src/wrappers/builder.rs index 4a73fec..1d06674 100644 --- a/src/wrappers/builder.rs +++ b/src/wrappers/builder.rs @@ -1,6 +1,8 @@ -use crate::{AdnlAddress, AdnlAesParams, AdnlHandshake, AdnlPublicKey, AdnlSecret}; +use crate::crypto::{KeyPair, PublicKey}; -use crate::helper_types::{AdnlPrivateKey, CryptoRandom}; +use crate::{AdnlAddress, AdnlAesParams, AdnlHandshake}; + +use crate::helper_types::CryptoRandom; /// Builder of [`AdnlHandshake`] structure, which then can be transformed into [`crate::AdnlClient`] pub struct AdnlBuilder { @@ -25,12 +27,12 @@ impl AdnlBuilder { } /// Specify sender, receiver, and secret on which they already agreed. - pub fn use_static_ecdh( + pub fn use_static_ecdh( self, - sender_public: P, + sender_public: PublicKey, receiver_address: AdnlAddress, - ecdh_secret: AdnlSecret, - ) -> AdnlHandshake

{ + ecdh_secret: [u8; 32], + ) -> AdnlHandshake { AdnlHandshake::new( receiver_address, sender_public, @@ -40,19 +42,17 @@ impl AdnlBuilder { } /// Perform key agreement using sender private key and receiver public - pub fn perform_ecdh( + pub fn perform_ecdh( self, - sender_private: S, - receiver_public: P, - ) -> AdnlHandshake<::PublicKey> - where - S: AdnlPrivateKey, - P: AdnlPublicKey, - { + sender_keypair: &KeyPair, + receiver_public: &PublicKey, + ) -> AdnlHandshake { AdnlHandshake::new( - receiver_public.address(), - sender_private.public(), - sender_private.key_agreement(receiver_public), + AdnlAddress::from(receiver_public), + sender_keypair.public_key, + sender_keypair + .secret_key + .compute_shared_secret(receiver_public), self.aes_params, ) } diff --git a/src/wrappers/client.rs b/src/wrappers/client.rs deleted file mode 100644 index 72c5049..0000000 --- a/src/wrappers/client.rs +++ /dev/null @@ -1,99 +0,0 @@ -use crate::{AdnlBuilder, AdnlError, AdnlHandshake, 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 { - sender: AdnlSender, - receiver: AdnlReceiver, - transport: T, -} - -impl AdnlClient { - /// Create ADNL client use random private key and random AES params - #[cfg(feature = "dalek")] - pub async fn connect( - ls_public: P, - ls_addr: A, - ) -> Result, AdnlError> { - // generate private key - let local_secret = StaticSecret::new(rand::rngs::OsRng); - - // use TcpStream as transport for our ADNL connection - let transport = TcpStream::connect(ls_addr).await?; - - // 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_handshake(transport) - .await?; - Ok(client) - } -} - -impl AdnlClient { - /// Send `handshake` over `transport` and check that handshake was successful - pub async fn perform_handshake( - mut transport: T, - handshake: &AdnlHandshake

, - ) -> Result { - // send handshake - transport - .write_all(&handshake.to_bytes()) - .await - .map_err(AdnlError::WriteError)?; - - // receive empty message to ensure that server knows our AES keys - let mut client = Self { - sender: AdnlSender::new(handshake.aes_params()), - receiver: AdnlReceiver::new(handshake.aes_params()), - transport, - }; - let mut empty = empty(); - client - .receiver - .receive::<_, _, 0>(&mut client.transport, &mut empty) - .await?; - Ok(client) - } - - /// Send `data` to another peer with random nonce - pub async fn send(&mut self, data: &mut [u8]) -> Result<(), AdnlError> { - self.sender - .send(&mut self.transport, &mut rand::random(), data) - .await - } - - /// Send `data` to another peer. Random `nonce` must be provided to eliminate bit-flipping attacks. - pub async fn send_with_nonce( - &mut self, - data: &mut [u8], - nonce: &mut [u8; 32], - ) -> Result<(), AdnlError> { - self.sender.send(&mut self.transport, nonce, data).await - } - - /// Receive data from another peer into `consumer` which will process the data with - /// a `BUFFER` size of 8192 bytes. - pub async fn receive( - &mut self, - consumer: &mut C, - ) -> Result<(), AdnlError> { - self.receiver - .receive::<_, _, 8192>(&mut self.transport, consumer) - .await - } - - /// Receive data from another peer into `consumer` which will process the data. Set `BUFFER` - /// according to your memory requirements, recommended size is 8192 bytes. - pub async fn receive_with_buffer( - &mut self, - consumer: &mut C, - ) -> Result<(), AdnlError> { - self.receiver - .receive::<_, _, BUFFER>(&mut self.transport, consumer) - .await - } -} diff --git a/src/wrappers/mod.rs b/src/wrappers/mod.rs index 0c2e48b..bcac638 100644 --- a/src/wrappers/mod.rs +++ b/src/wrappers/mod.rs @@ -1,2 +1,2 @@ pub mod builder; -pub mod client; +pub mod peer; diff --git a/src/wrappers/peer.rs b/src/wrappers/peer.rs new file mode 100644 index 0000000..cc62b79 --- /dev/null +++ b/src/wrappers/peer.rs @@ -0,0 +1,146 @@ +use std::pin::Pin; +use std::task::{Context, Poll}; + +use crate::crypto::{KeyPair, PublicKey}; +use crate::helper_types::AdnlConnectionInfo; +use crate::{AdnlAddress, AdnlBuilder, AdnlError, AdnlHandshake}; +use futures::{Sink, SinkExt, Stream, StreamExt}; +use pin_project::pin_project; +use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}; +use tokio::net::{TcpStream, ToSocketAddrs}; +use tokio_util::bytes::Bytes; +use tokio_util::codec::{Decoder, Framed}; + +use crate::primitives::codec::AdnlCodec; + +/// Abstraction over [`AdnlSender`] and [`AdnlReceiver`] to keep things simple +#[pin_project] +pub struct AdnlPeer +where + T: AsyncRead + AsyncWrite, +{ + #[pin] + stream: Framed, + connection_info: AdnlConnectionInfo, +} + +impl AdnlPeer { + /// Connect ADNL client to specified server over [`TcpStream`] + pub async fn connect( + server_public: impl AsRef<[u8]>, + server_address: A, + ) -> Result, AdnlError> { + let transport = TcpStream::connect(server_address).await?; + let client = Self::perform_handshake(transport, server_public).await?; + Ok(client) + } +} + +impl AdnlPeer { + /// Act as a client: perform handshake built from random client keys and `remote_public` over `transport` and check that handshake was successful. + /// That is a simple version of `perform_custom_handshake`, which uses random protocol parameters. + /// Returns client part of ADNL connection + pub async fn perform_handshake( + transport: T, + remote_public: impl AsRef<[u8]>, + ) -> Result { + let local_keypair = KeyPair::generate(&mut rand::rngs::OsRng); + let remote_public = remote_public + .as_ref() + .try_into() + .ok() + .and_then(PublicKey::from_bytes) + .ok_or(AdnlError::InvalidPublicKey)?; + let handshake = AdnlBuilder::with_random_aes_params(&mut rand::rngs::OsRng) + .perform_ecdh(&local_keypair, &remote_public); + Self::perform_custom_handshake(transport, &handshake).await + } + + /// Act as a client: send `handshake` over `transport` and check that handshake was successful + /// Returns client part of ADNL connection + pub async fn perform_custom_handshake( + mut transport: T, + handshake: &AdnlHandshake, + ) -> Result { + // send handshake + transport + .write_all(&handshake.to_bytes()) + .await + .map_err(AdnlError::IoError)?; + + let mut stream = handshake.make_client_codec().framed(transport); + + // receive empty message to ensure that server knows our AES keys + if let Some(x) = stream.next().await { + x?; + let connection_info = + AdnlConnectionInfo::new(handshake.sender().into(), handshake.receiver().clone()); + Ok(Self { + stream, + connection_info, + }) + } else { + Err(AdnlError::EndOfStream) + } + } + + /// Act as a server: receive handshake over transport using [`KeyPair`] provided by `keypair_selector`. + pub async fn handle_handshake Option>( + mut transport: T, + keypair_selector: F, + ) -> Result { + // receive handshake + let mut packet = [0u8; 256]; + transport + .read_exact(&mut packet) + .await + .map_err(AdnlError::IoError)?; + let handshake = AdnlHandshake::decrypt_from_raw(&packet, keypair_selector)?; + let connection_info = + AdnlConnectionInfo::new(handshake.receiver().clone(), handshake.sender().into()); + + let mut server = Self { + stream: handshake.make_server_codec().framed(transport), + connection_info, + }; + + // send empty packet to proof knowledge of AES keys + server.send(Bytes::new()).await?; + + Ok(server) + } +} + +impl Stream for AdnlPeer +where + T: AsyncRead + AsyncWrite, +{ + type Item = Result; + + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + self.project().stream.poll_next(cx) + } +} + +impl Sink for AdnlPeer +where + T: AsyncWrite + AsyncRead, +{ + type Error = AdnlError; + + fn poll_ready(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + self.project().stream.poll_ready(cx) + } + + fn start_send(self: Pin<&mut Self>, item: Bytes) -> Result<(), Self::Error> { + self.project().stream.start_send(item) + } + + fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + self.project().stream.poll_flush(cx) + } + + fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + self.project().stream.poll_close(cx) + } +}