Skip to content

Commit

Permalink
Huge refactoring
Browse files Browse the repository at this point in the history
  • Loading branch information
hacker-volodya committed Feb 17, 2024
1 parent cfd8abe commit 9d3255f
Show file tree
Hide file tree
Showing 14 changed files with 209 additions and 70 deletions.
4 changes: 0 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,12 @@ 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"]
dalek = ["x25519-dalek", "curve25519-dalek"]

[[example]]
name = "time"
required-features = ["dalek"]
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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")?;
Expand Down
28 changes: 28 additions & 0 deletions examples/echo_client.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
use adnl::{AdnlPeer, AdnlRawPublicKey};
use std::{env, error::Error};

#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
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::<u8>::new();
client.receive(&mut result).await?;

println!("received: {}", String::from_utf8(result).unwrap());
Ok(())
}
65 changes: 65 additions & 0 deletions examples/echo_server.rs
Original file line number Diff line number Diff line change
@@ -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<dyn Error>> {
// 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");
}
});
}
}
16 changes: 6 additions & 10 deletions examples/time.rs
Original file line number Diff line number Diff line change
@@ -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<dyn Error>> {
// 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")?;
Expand Down
46 changes: 38 additions & 8 deletions src/helper_types.rs
Original file line number Diff line number Diff line change
@@ -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 {}
Expand All @@ -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<Self, Self::Error> {
Ok(Self(value.try_into()?))
}
}

Expand All @@ -31,26 +47,42 @@ pub trait AdnlPrivateKey {

/// Perform key agreement protocol (usually x25519) between our private key
/// and their public
fn key_agreement<P: AdnlPublicKey>(&self, their_public: P) -> AdnlSecret;
fn key_agreement<P: AdnlPublicKey>(&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<Self, Self::Error> {
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],
Expand Down Expand Up @@ -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)]
Expand Down
4 changes: 2 additions & 2 deletions src/integrations/dalek.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ impl AdnlPublicKey for PublicKey {
}
}

fn edwards_to_montgomery<P: AdnlPublicKey>(public_key: P) -> PublicKey {
fn edwards_to_montgomery<P: AdnlPublicKey>(public_key: &P) -> PublicKey {
PublicKey::from(
CompressedEdwardsY::from_slice(&public_key.to_bytes())
.decompress()
Expand All @@ -29,7 +29,7 @@ fn edwards_to_montgomery<P: AdnlPublicKey>(public_key: P) -> PublicKey {
impl AdnlPrivateKey for StaticSecret {
type PublicKey = PublicKey;

fn key_agreement<P: AdnlPublicKey>(&self, their_public: P) -> AdnlSecret {
fn key_agreement<P: AdnlPublicKey>(&self, their_public: &P) -> AdnlSecret {
AdnlSecret::from(
self.diffie_hellman(&edwards_to_montgomery(their_public))
.to_bytes(),
Expand Down
4 changes: 2 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
16 changes: 8 additions & 8 deletions src/primitives/handshake.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand Down Expand Up @@ -64,8 +65,8 @@ impl<P: AdnlPublicKey> AdnlHandshake<P> {
pub async fn perform_handshake<T: AsyncReadExt + AsyncWriteExt + Unpin>(
&self,
transport: T,
) -> Result<AdnlClient<T>, AdnlError> {
AdnlClient::perform_handshake(transport, self).await
) -> Result<AdnlPeer<T>, AdnlError> {
AdnlPeer::perform_handshake(transport, self).await
}

fn initialize_aes(secret: &AdnlSecret, hash: &[u8]) -> AdnlAes {
Expand All @@ -87,11 +88,10 @@ impl<P: AdnlPublicKey> AdnlHandshake<P> {
}
}

impl AdnlHandshake<[u8; 32]> {
impl AdnlHandshake<AdnlRawPublicKey> {
/// Deserialize and decrypt handshake
pub fn decrypt_from_raw<S: AdnlPrivateKey>(packet: &[u8], key: &S) -> Result<Self, AdnlError> {
let receiver: [u8; 32] = packet[..32].try_into().unwrap();
let receiver = AdnlAddress::from(receiver);
pub fn decrypt_from_raw<S: AdnlPrivateKey>(packet: &[u8; 256], key: &S) -> Result<Self, AdnlError> {
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();
Expand All @@ -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);

Expand Down
6 changes: 4 additions & 2 deletions src/primitives/receive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,16 @@ 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.
pub async fn receive<R: AsyncReadExt + Unpin, C: AsyncWriteExt + Unpin, const BUFFER: usize>(
&mut self,
transport: &mut R,
consumer: &mut C,
) -> Result<(), AdnlError> {
) -> Result<usize, AdnlError> {
// read length
let mut length = [0u8; 4];
log::debug!("reading length");
Expand Down Expand Up @@ -111,6 +113,6 @@ impl AdnlReceiver {

log::debug!("receive finished successfully");

Ok(())
Ok(length as usize)
}
}
Loading

0 comments on commit 9d3255f

Please sign in to comment.