Impl Debug & From

This commit is contained in:
Matéo Duparc 2021-05-18 22:17:21 +02:00
parent 08c3c48a67
commit 3944105d72
Signed by: hardcoresushi
GPG Key ID: 007F84120107191E
4 changed files with 113 additions and 44 deletions

View File

@ -1,6 +1,6 @@
[package] [package]
name = "async-psec" name = "async-psec"
version = "0.1.0" version = "0.2.0"
authors = ["Hardcore Sushi <hardcore.sushi@disroot.org>"] authors = ["Hardcore Sushi <hardcore.sushi@disroot.org>"]
edition = "2018" edition = "2018"
description = "Asynchronous PSEC implementation" description = "Asynchronous PSEC implementation"

View File

@ -16,7 +16,7 @@ async fn main() -> Result<(), PsecError> {
//connect to another PSEC node listening on 10.152.152.10:7530 //connect to another PSEC node listening on 10.152.152.10:7530
let stream = TcpStream::connect("10.152.152.10:7530").await.unwrap(); let stream = TcpStream::connect("10.152.152.10:7530").await.unwrap();
let mut psec_session = Session::new(stream); //wrap the TcpStream into a PSEC session let mut psec_session = Session::from(stream); //wrap the TcpStream into a PSEC session
psec_session.do_handshake(&identity).await?; //perform the PSEC handshake psec_session.do_handshake(&identity).await?; //perform the PSEC handshake
//encrypt a message, obfuscate its length with padding then send it //encrypt a message, obfuscate its length with padding then send it
@ -30,7 +30,7 @@ async fn main() -> Result<(), PsecError> {
To add this crate to your project, add the following to your project's Cargo.toml: To add this crate to your project, add the following to your project's Cargo.toml:
```toml ```toml
[dependencies] [dependencies]
async-psec = { version = "0.1", git = "https://forge.chapril.org/hardcoresushi/async-psec" } async-psec = { version = "0.2", git = "https://forge.chapril.org/hardcoresushi/async-psec" }
``` ```
# Documentation # Documentation

View File

@ -21,7 +21,7 @@ async fn main() -> Result<(), PsecError> {
//connect to another PSEC node listening on 10.152.152.10:7530 //connect to another PSEC node listening on 10.152.152.10:7530
let stream = TcpStream::connect("10.152.152.10:7530").await.unwrap(); let stream = TcpStream::connect("10.152.152.10:7530").await.unwrap();
let mut psec_session = Session::new(stream); //wrap the TcpStream into a PSEC session let mut psec_session = Session::from(stream); //wrap the TcpStream into a PSEC session
psec_session.do_handshake(&identity).await?; //perform the PSEC handshake psec_session.do_handshake(&identity).await?; //perform the PSEC handshake
//encrypt a message, obfuscate its length with padding then send it //encrypt a message, obfuscate its length with padding then send it
@ -44,7 +44,7 @@ This can be useful if you want to send data from one thread/task and receive fro
#![warn(missing_docs)] #![warn(missing_docs)]
mod crypto; mod crypto;
use std::{convert::TryInto, fmt::{self, Display, Formatter}, io::{self, ErrorKind}, net::SocketAddr}; use std::{convert::TryInto, fmt::{self, Debug, Display, Formatter}, io::{self, ErrorKind}, net::SocketAddr};
use tokio::{io::{AsyncReadExt, AsyncWriteExt}, net::TcpStream}; use tokio::{io::{AsyncReadExt, AsyncWriteExt}, net::TcpStream};
#[cfg(feature = "split")] #[cfg(feature = "split")]
use tokio::net::tcp::{OwnedReadHalf, OwnedWriteHalf}; use tokio::net::tcp::{OwnedReadHalf, OwnedWriteHalf};
@ -88,6 +88,8 @@ pub enum PsecError {
/// The [`ErrorKind`] of the I/O [`Error`](io::Error). /// The [`ErrorKind`] of the I/O [`Error`](io::Error).
error_kind: ErrorKind, error_kind: ErrorKind,
}, },
/// The plain text was not properly padded.
BadPadding,
} }
impl Display for PsecError { impl Display for PsecError {
@ -99,6 +101,7 @@ impl Display for PsecError {
PsecError::BufferTooLarge => f.write_str("Received buffer is too large"), PsecError::BufferTooLarge => f.write_str("Received buffer is too large"),
PsecError::UnexpectedEof => f.write_str("Unexpected EOF"), PsecError::UnexpectedEof => f.write_str("Unexpected EOF"),
PsecError::IoError { error_kind } => f.write_str(&format!("{:?}", error_kind)), PsecError::IoError { error_kind } => f.write_str(&format!("{:?}", error_kind)),
PsecError::BadPadding => f.write_str("Bad Padding"),
} }
} }
} }
@ -157,9 +160,13 @@ fn pad(plain_text: &[u8], use_padding: bool) -> Vec<u8> {
output output
} }
fn unpad(input: Vec<u8>) -> Vec<u8> { fn unpad(input: Vec<u8>) -> Result<Vec<u8>, PsecError> {
let msg_len = MessageLenType::from_be_bytes(input[0..MESSAGE_LEN_LEN].try_into().unwrap()) as usize; if input.len() < 4 {
Vec::from(&input[MESSAGE_LEN_LEN..MESSAGE_LEN_LEN+msg_len]) Err(PsecError::BadPadding)
} else {
let msg_len = MessageLenType::from_be_bytes(input[0..MESSAGE_LEN_LEN].try_into().unwrap()) as usize;
Ok(Vec::from(&input[MESSAGE_LEN_LEN..MESSAGE_LEN_LEN+msg_len]))
}
} }
fn encrypt(local_cipher: &Aes128Gcm, local_iv: &[u8], local_counter: &mut usize, plain_text: &[u8], use_padding: bool) -> Vec<u8> { fn encrypt(local_cipher: &Aes128Gcm, local_iv: &[u8], local_counter: &mut usize, plain_text: &[u8], use_padding: bool) -> Vec<u8> {
@ -195,7 +202,7 @@ async fn receive_and_decrypt<T: AsyncReadExt + Unpin>(reader: &mut T, peer_ciphe
aad: &message_len aad: &message_len
}; };
match peer_cipher.decrypt(Nonce::from_slice(&peer_nonce), payload) { match peer_cipher.decrypt(Nonce::from_slice(&peer_nonce), payload) {
Ok(plain_text) => Ok(unpad(plain_text)), Ok(plain_text) => unpad(plain_text),
Err(_) => Err(PsecError::TransmissionCorrupted) Err(_) => Err(PsecError::TransmissionCorrupted)
} }
} else { } else {
@ -228,18 +235,23 @@ pub trait PsecReader {
The default value is 32 768 020, which allows to receive any messages under 32 768 000 bytes.*/ The default value is 32 768 020, which allows to receive any messages under 32 768 000 bytes.*/
fn set_max_recv_size(&mut self, size: usize, is_raw_size: bool); fn set_max_recv_size(&mut self, size: usize, is_raw_size: bool);
/// Read then decrypt from a PSEC session. /** Read then decrypt from a PSEC session.
# Panic
Panics if the PSEC handshake is not finished and successful.*/
async fn receive_and_decrypt(&mut self) -> Result<Vec<u8>, PsecError>; async fn receive_and_decrypt(&mut self) -> Result<Vec<u8>, PsecError>;
/** Take ownership of the `PsecReader`, read, decrypt, then return back the `PsecReader`. Useful when used with [`tokio::select!`]. /** Take ownership of the `PsecReader`, read, decrypt, then return back the `PsecReader`. Useful when used with [`tokio::select!`].
# Panic
Panics if the PSEC handshake is not finished and successful.
```no_run ```no_run
# use tokio::net::TcpStream; # use tokio::net::TcpStream;
# use async_psec::{Session, PsecReader}; # use async_psec::{Session, PsecReader};
# #[tokio::main] # #[tokio::main]
# async fn main() { # async fn main() {
# let stream = TcpStream::connect("10.152.152.10:7530").await.unwrap(); # let stream = TcpStream::connect("10.152.152.10:7530").await.unwrap();
# let psec_session = Session::new(stream); # let psec_session = Session::from(stream);
let receiving = psec_session.into_receive_and_decrypt(); let receiving = psec_session.into_receive_and_decrypt();
tokio::pin!(receiving); tokio::pin!(receiving);
@ -270,19 +282,25 @@ pub trait PsecWriter {
/** Encrypt then send through a PSEC session. /** Encrypt then send through a PSEC session.
`use_padding` specifies whether or not the plain text length should be obfuscated with padding. Enabling padding will use more network bandwidth: all messages below 1KB will be padded to 1KB and then the padded length doubles at each step (2KB, 4KB, 8KB...). When sending a buffer of 17MB, it will padded to 32MB. `use_padding` specifies whether or not the plain text length should be obfuscated with padding. Enabling padding will use more network bandwidth: all messages below 1KB will be padded to 1KB and then the padded length doubles at each step (2KB, 4KB, 8KB...). When sending a buffer of 17MB, it will padded to 32MB.
# Panic
Panics if the PSEC handshake is not finished and successful.
*/ */
async fn encrypt_and_send(&mut self, plain_text: &[u8], use_padding: bool) -> Result<(), PsecError>; async fn encrypt_and_send(&mut self, plain_text: &[u8], use_padding: bool) -> Result<(), PsecError>;
/** Encrypt a buffer but return it instead of sending it. /** Encrypt a buffer but return it instead of sending it.
All encrypted buffers must be sent __in the same order__ they have been encrypted otherwise the remote peer won't be able to decrypt them and should close the connection. All encrypted buffers must be sent __in the same order__ they have been encrypted otherwise the remote peer won't be able to decrypt them and should close the connection.
# Panic
Panics if the PSEC handshake is not finished and successful.
```no_run ```no_run
# use tokio::net::TcpStream; # use tokio::net::TcpStream;
# use async_psec::{Session, PsecWriter, PsecError}; # use async_psec::{Session, PsecWriter, PsecError};
# #[tokio::main] # #[tokio::main]
# async fn main() -> Result<(), PsecError> { # async fn main() -> Result<(), PsecError> {
# let stream = TcpStream::connect("10.152.152.10:7530").await.unwrap(); # let stream = TcpStream::connect("10.152.152.10:7530").await.unwrap();
# let mut psec_session = Session::new(stream); # let mut psec_session = Session::from(stream);
let buffer1 = psec_session.encrypt(b"Hello ", false); let buffer1 = psec_session.encrypt(b"Hello ", false);
let buffer2 = psec_session.encrypt(b" world!", false); let buffer2 = psec_session.encrypt(b" world!", false);
psec_session.send(&buffer1).await?; psec_session.send(&buffer1).await?;
@ -309,6 +327,18 @@ pub struct SessionReadHalf {
max_recv_size: usize, max_recv_size: usize,
} }
#[cfg(feature = "split")]
impl Debug for SessionReadHalf {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.debug_struct("SessionReadHalf")
.field("read_half", &self.read_half)
.field("max_recv_size", &self.max_recv_size)
.field("peer_counter", &self.peer_counter)
.field("peer_iv", &hex_encode(&self.peer_iv))
.finish()
}
}
#[cfg(feature = "split")] #[cfg(feature = "split")]
#[async_trait] #[async_trait]
impl PsecReader for SessionReadHalf { impl PsecReader for SessionReadHalf {
@ -346,6 +376,17 @@ impl PsecWriter for SessionWriteHalf {
} }
} }
#[cfg(feature = "split")]
impl Debug for SessionWriteHalf {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.debug_struct("SessionWriteHalf")
.field("write_half", &self.write_half)
.field("local_counter", &self.local_counter)
.field("local_iv", &hex_encode(&self.local_iv))
.finish()
}
}
/// A PSEC connection. /// A PSEC connection.
pub struct Session { pub struct Session {
stream: TcpStream, stream: TcpStream,
@ -370,7 +411,7 @@ pub struct Session {
# let identity = Identity::generate(&mut OsRng); # let identity = Identity::generate(&mut OsRng);
let stream = TcpStream::connect("10.152.152.10:7530").await?; let stream = TcpStream::connect("10.152.152.10:7530").await?;
let mut psec_session = Session::new(stream); let mut psec_session = Session::from(stream);
psec_session.do_handshake(&identity).await.unwrap(); psec_session.do_handshake(&identity).await.unwrap();
println!("Peer public key: {:?}", psec_session.peer_public_key.unwrap()); println!("Peer public key: {:?}", psec_session.peer_public_key.unwrap());
@ -381,25 +422,6 @@ pub struct Session {
} }
impl Session { impl Session {
/** Wrap a [`TcpStream`] into a PSEC session.
The returned `Session` can't be used to transfer data until [`do_handshake`](Session::do_handshake) is called.*/
pub fn new(stream: TcpStream) -> Session {
Session {
stream: stream,
handshake_sent_buff: Vec::new(),
handshake_recv_buff: Vec::new(),
local_cipher: None,
local_iv: None,
local_counter: 0,
peer_cipher: None,
peer_iv: None,
peer_counter: 0,
peer_public_key: None,
max_recv_size: DEFAULT_MAX_RECV_SIZE,
}
}
/** Split the `Session` in two parts: a reader and a writer. /** Split the `Session` in two parts: a reader and a writer.
Calling this before a successful call to [`do_handshake`](Session::do_handshake) will return `None`. Calling this before a successful call to [`do_handshake`](Session::do_handshake) will return `None`.
@ -410,7 +432,7 @@ impl Session {
# #[tokio::main] # #[tokio::main]
# async fn main() -> Result<(), Error> { # async fn main() -> Result<(), Error> {
# let stream = TcpStream::connect("10.152.152.10:7530").await?; # let stream = TcpStream::connect("10.152.152.10:7530").await?;
# let psec_session = Session::new(stream); # let psec_session = Session::from(stream);
let (mut session_read, mut session_write) = psec_session.into_split().unwrap(); let (mut session_read, mut session_write) = psec_session.into_split().unwrap();
tokio::spawn(async move { tokio::spawn(async move {
@ -455,13 +477,13 @@ impl Session {
let addr: SocketAddr = "10.152.152.10:7530".parse().unwrap(); let addr: SocketAddr = "10.152.152.10:7530".parse().unwrap();
let stream = TcpStream::connect(addr).await?; let stream = TcpStream::connect(addr).await?;
let psec_session = Session::new(stream); let psec_session = Session::from(stream);
assert_eq!(psec_session.get_addr()?, addr); assert_eq!(psec_session.peer_addr()?, addr);
# Ok(()) # Ok(())
# } # }
```*/ ```*/
pub fn get_addr(&self) -> io::Result<SocketAddr> { pub fn peer_addr(&self) -> io::Result<SocketAddr> {
self.stream.peer_addr() self.stream.peer_addr()
} }
@ -612,6 +634,53 @@ impl PsecReader for Session {
} }
} }
impl From<TcpStream> for Session {
fn from(stream: TcpStream) -> Self {
Session {
stream: stream,
handshake_sent_buff: Vec::new(),
handshake_recv_buff: Vec::new(),
local_cipher: None,
local_iv: None,
local_counter: 0,
peer_cipher: None,
peer_iv: None,
peer_counter: 0,
peer_public_key: None,
max_recv_size: DEFAULT_MAX_RECV_SIZE,
}
}
}
impl Debug for Session {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let handshake_successful = self.peer_cipher.is_some();
let mut debug_struct = f.debug_struct("PSEC Session");
debug_struct
.field("stream", &self.stream)
.field("max_recv_size", &self.max_recv_size)
.field("handshake_successful", &handshake_successful);
if let Some(peer_public_key) = self.peer_public_key {
debug_struct.field("peer_public_key", &hex_encode(&peer_public_key));
}
if handshake_successful {
debug_struct.field("local_counter", &self.local_counter)
.field("local_iv", &hex_encode(&self.local_iv.unwrap()))
.field("peer_counter", &self.peer_counter)
.field("peer_iv", &hex_encode(&self.peer_iv.unwrap()));
}
debug_struct.finish()
}
}
fn hex_encode(buff: &[u8]) -> String {
let mut s = String::with_capacity(buff.len()*2);
for i in buff {
s += &format!("{:x}", i);
}
s
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::{pad, unpad, MESSAGE_LEN_LEN}; use super::{pad, unpad, MESSAGE_LEN_LEN};
@ -623,11 +692,11 @@ mod tests {
let not_padded = pad(b"Hello world!", false); let not_padded = pad(b"Hello world!", false);
assert_eq!(not_padded.len(), "Hello world!".len()+MESSAGE_LEN_LEN); assert_eq!(not_padded.len(), "Hello world!".len()+MESSAGE_LEN_LEN);
let unpadded = unpad(padded); let unpadded = unpad(padded).unwrap();
assert_eq!(unpadded, unpad(not_padded)); assert_eq!(unpadded, unpad(not_padded).unwrap());
assert_eq!(unpadded, b"Hello world!"); assert_eq!(unpadded, b"Hello world!");
let large_msg = "a".repeat(5000); let large_msg = "a".repeat(5000);
assert_eq!(pad(large_msg.as_bytes(), true).len(), 8000); assert_eq!(pad(large_msg.as_bytes(), true).len(), 8000);
} }
} }

View File

@ -14,8 +14,8 @@ async fn tokio_main() {
tokio::spawn(async move { tokio::spawn(async move {
let (stream, addr) = listener.accept().await.unwrap(); let (stream, addr) = listener.accept().await.unwrap();
let mut session = Session::new(stream); let mut session = Session::from(stream);
assert_eq!(session.get_addr().unwrap(), addr); assert_eq!(session.peer_addr().unwrap(), addr);
session.do_handshake(&server_keypair).await.unwrap(); session.do_handshake(&server_keypair).await.unwrap();
assert_eq!(session.peer_public_key.unwrap(), client_public_key); assert_eq!(session.peer_public_key.unwrap(), client_public_key);
@ -30,8 +30,8 @@ async fn tokio_main() {
let stream = TcpStream::connect(format!("127.0.0.1:{}", bind_addr.port())).await.unwrap(); let stream = TcpStream::connect(format!("127.0.0.1:{}", bind_addr.port())).await.unwrap();
let mut session = Session::new(stream); let mut session = Session::from(stream);
assert_eq!(session.get_addr().unwrap(), bind_addr); assert_eq!(session.peer_addr().unwrap(), bind_addr);
session.do_handshake(&client_keypair).await.unwrap(); session.do_handshake(&client_keypair).await.unwrap();
assert_eq!(session.peer_public_key.unwrap(), server_public_key); assert_eq!(session.peer_public_key.unwrap(), server_public_key);
@ -48,4 +48,4 @@ async fn tokio_main() {
#[test] #[test]
fn psec_session() { fn psec_session() {
tokio_main(); tokio_main();
} }