diff --git a/Cargo.lock b/Cargo.lock index c87c964..b512b73 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,12 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "ad-hoc-iter" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90a8dd76beceb5313687262230fcbaaf8d4e25c37541350cf0932e9adb8309c8" + [[package]] name = "addr2line" version = "0.16.0" @@ -106,9 +112,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chacha20stream" -version = "2.0.1" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b34f4279658654cc2c7dbb36ba00b620dd5532153a9b9ebfea1b7ef282e1d4" +checksum = "54c8d48b47fa0a89a94b80d32b1b3fc9ffc1a232a5201ff5a2d14ac77bc7561d" dependencies = [ "base64 0.13.0", "getrandom 0.2.3", @@ -116,6 +122,7 @@ dependencies = [ "openssl", "pin-project", "rustc_version", + "serde", "smallvec", "stackalloc", "tokio 0.2.25", @@ -791,6 +798,7 @@ dependencies = [ name = "rsh" version = "0.1.0" dependencies = [ + "ad-hoc-iter", "bytes 1.0.1", "chacha20stream", "color-eyre", diff --git a/Cargo.toml b/Cargo.toml index e86180c..d29c398 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,8 +6,9 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +ad-hoc-iter = "0.2.3" bytes = { version = "1.0.1", features = ["serde"] } -chacha20stream = { version = "2.0.1", features = ["async"] } +chacha20stream = { version = "2.1.0", features = ["async", "serde"] } color-eyre = "0.5.11" cryptohelpers = { version = "1.8.1" , features = ["serialise", "full"] } futures = "0.3.16" diff --git a/src/bin.rs b/src/bin.rs new file mode 100644 index 0000000..60c15a0 --- /dev/null +++ b/src/bin.rs @@ -0,0 +1,37 @@ +//! Binary / byte maniuplation +use bytes::BufMut; + +/// Concatenate an iterator of byte slices into a buffer. +/// +/// # Returns +/// The number of bytes written +/// # Panics +/// If the buffer cannot hold all the slices +pub fn collect_slices_into(into: &mut B, from: I) -> usize +where I: IntoIterator, + T: AsRef<[u8]> +{ + let mut done =0; + for slice in from.into_iter() + { + let s = slice.as_ref(); + into.put_slice(s); + done+=s.len(); + } + done +} + +/// Collect an iterator of byte slices into a new exact-size buffer. +/// +/// # Returns +/// The number of bytes written, and the new array +/// +/// # Panics +/// If the total bytes in all slices exceeds `SIZE`. +pub fn collect_slices_exact(from: I) -> (usize, [u8; SIZE]) +where I: IntoIterator, + T: AsRef<[u8]> +{ + let mut output = [0u8; SIZE]; + (collect_slices_into(&mut &mut output[..], from), output) +} diff --git a/src/main.rs b/src/main.rs index 3700c1b..32a852e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,6 +4,7 @@ #![allow(dead_code)] #[macro_use] extern crate serde; +#[macro_use] extern crate ad_hoc_iter; #[macro_use] extern crate pin_project; #[allow(unused_imports)] @@ -21,6 +22,7 @@ use std::convert::{ }; mod ext; use ext::*; +mod bin; mod message; mod cancel; @@ -30,8 +32,8 @@ mod sock; //mod pipeline; #[tokio::main] -async fn main() -> eyre::Result<()> { - println!("Hello, world!"); - +async fn main() -> eyre::Result<()> +{ + Ok(()) } diff --git a/src/sock/enc.rs b/src/sock/enc.rs index 5633e14..892eb05 100644 --- a/src/sock/enc.rs +++ b/src/sock/enc.rs @@ -10,11 +10,15 @@ use cryptohelpers::{ use chacha20stream::{ AsyncSink, AsyncSource, + + Key, IV, }; use std::sync::Arc; use tokio::{ sync::{ RwLock, + RwLockReadGuard, + RwLockWriteGuard, }, io::{ DuplexStream, @@ -26,9 +30,15 @@ use std::{ Context, Poll, }, pin::Pin, - marker::Unpin, + marker::{ + Unpin, + PhantomPinned, + }, }; +/// Size of a single RSA ciphertext. +pub const RSA_CIPHERTEXT_SIZE: usize = 512; + /// Max size to read when exchanging keys const TRANS_KEY_MAX_SIZE: usize = 4096; @@ -39,12 +49,28 @@ struct ESockInfo { them: Option, } +#[derive(Debug)] +struct ESockState { + encr: bool, + encw: bool, +} + +/// Contains a Key and IV that can be serialized and then encrypted +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +struct ESockSessionKey +{ + key: Key, + iv: IV, +} + /// A tx+rx socket. #[pin_project] #[derive(Debug)] pub struct ESock { - info: RwLock, + info: ESockInfo, + state: ESockState, + #[pin] rx: AsyncSource, #[pin] @@ -64,19 +90,84 @@ impl ESock } /// Create a future that exchanges keys - pub fn exchange(&mut self) -> Exchange<'_, W, R> + pub fn exchange_unsafe(&mut self) -> Exchange<'_, W, R> { - Exchange{sock: self} + let us = self.info.us.get_public_parts(); + todo!("Currently unimplemented") + /* + Exchange{ + sock: self, + write_buf: Default::default(), + read_buf: Default::default(), + _pin: PhantomPinned, + + read_state: Default::default(), + write_state: Default::default(), + + us, + them: None, + us_written: Default::default(), + us_buf: (), + write_sz_num: (), + write_sz_buf: (), + read_sz_buf: (), + }*/ + } + + ///Get a mutable ref to unencrypted read+write + fn unencrypted(&mut self) -> (&mut W, &mut R) + { + (self.tx.inner_mut(), self.rx.inner_mut()) + } + /// Get a mutable ref to encrypted write+read + fn encrypted(&mut self) -> (&mut AsyncSink, &mut AsyncSource) + { + (&mut self.tx, &mut self.rx) + } + + /// Have the RSA keys been exchanged? + pub fn has_exchanged(&self) -> bool + { + self.info.them.is_some() + } + + /// Is the Write + Read operation encrypted? Tuple is `(Tx, Rx)`. + pub fn is_encrypted(&self) -> (bool, bool) + { + (self.state.encw, self.state.encr) } } impl ESock { + /// Enable write encryption + pub async fn set_encrypted_write(&mut self, set: bool) -> eyre::Result<()> + { + if set { + let (key, iv) = ((),()); + self.state.encw = true; + Ok(()) + } else { + self.state.encw = false; + Ok(()) + } + } + + /// Get dynamic ref to unencrypted write+read + fn unencrypted_dyn(&mut self) -> (&mut (dyn AsyncWrite + Unpin + '_), &mut (dyn AsyncRead + Unpin + '_)) + { + (self.tx.inner_mut(), self.rx.inner_mut()) + } + /// Get dynamic ref to encrypted write+read + fn encrypted_dyn(&mut self) -> (&mut (dyn AsyncWrite + Unpin + '_), &mut (dyn AsyncRead + Unpin + '_)) + { + (&mut self.tx, &mut self.rx) + } /// Exchange keys. - pub async fn exchange_unpin(&mut self) -> eyre::Result<()> + pub async fn exchange(&mut self) -> eyre::Result<()> { use tokio::prelude::*; - let our_key = self.info.read().await.us.get_public_parts(); + let our_key = self.info.us.get_public_parts(); let (tx, rx) = self.inner_mut(); let read_fut = { @@ -112,30 +203,172 @@ impl ESock let (send, recv) = tokio::join! [write_fut, read_fut]; send?; let recv = recv?; - self.info.write().await.them = Some(recv); + self.info.them = Some(recv); Ok(()) } } +#[derive(Debug, Clone, PartialEq, Eq, Hash, Copy, PartialOrd, Ord)] +enum ExchangeState +{ + /// We are currently reading/writing the buffer's size + BufferSize, + /// We are currently reading/writing the buffer itself + Buffer, +} + +impl Default for ExchangeState +{ + #[inline] + fn default() -> Self + { + Self::BufferSize + } +} + + +#[pin_project] +#[derive(Debug)] pub struct Exchange<'a, W, R> { sock: &'a mut ESock, + + us: RsaPublicKey, + + us_written: usize, + us_buf: Vec, + + /// The return value + them: Option, + + write_sz_num: usize, + write_sz_buf: [u8; std::mem::size_of::()], + read_sz_buf: [u8; std::mem::size_of::()], + + read_buf: Vec, + + write_state: ExchangeState, + read_state: ExchangeState, + + #[pin] _pin: PhantomPinned, } +/* impl<'a, W: AsyncWrite, R: AsyncRead> Future for Exchange<'a, W, R> { - type Output = eyre::Result<()>; - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { +type Output = eyre::Result<()>; +fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { +use futures::ready; +let this = self.project(); +let (tx, rx) = { +let sock = this.sock; +//XXX: Idk if this is safe? +unsafe { +(Pin::new_unchecked(&mut sock.tx), Pin::new_unchecked(&mut sock.rx)) + } + }; + + if this.us_buf.is_empty() { + *this.us_buf = this.us.to_bytes(); + } + + let poll_write = loop { + break match this.write_state { + ExchangeState::BufferSize => { + if *this.write_sz_num == 0 { + *this.write_sz_buf = u64::try_from(this.us_buf.len())?.to_be_bytes(); + } + // Write this to tx. + match tx.poll_write(cx, &this.write_sz_buf[(*this.write_sz_num)..]) { + x @ Poll::Ready(Ok(n)) => { + *this.write_sz_num+=n; + if *this.write_sz_num == this.write_sz_buf.len() { + // We've written all the size bytes, continue to writing the buffer bytes. + *this.write_state = ExchangeState::Buffer; + continue; + } + + x + }, + x => x, + } + }, + ExchangeState::Buffer => { + match tx.poll_write(cx, &this.us_buf[(*this.us_written)..]) { + x @ Poll::Ready(Ok(n)) => { + if *this.us_written == this.us.len() { + + } + + x + }, + x=> x, + } + }, + } + }; + let poll_read = match this.read_state { + ExchangeState::BufferSize => { + + }, + ExchangeState::Buffer => { + + }, + }; todo!("This is going to be dificult to implement... We don't have access to write_all and read_exact") } } - +*/ /// Write half for `ESock`. #[pin_project] #[derive(Debug)] -pub struct ESockWriteHalf(Arc, #[pin] AsyncSink); +pub struct ESockWriteHalf(Arc<(ESockInfo, RwLock)>, #[pin] AsyncSink); /// Read half for `ESock`. #[pin_project] #[derive(Debug)] -pub struct ESockReadHalf(Arc, #[pin] AsyncSource); +pub struct ESockReadHalf(Arc<(ESockInfo, RwLock)>, #[pin] AsyncSource); + +#[cfg(test)] +mod tests +{ + #[test] + fn rsa_ciphertext_len() -> crate::eyre::Result<()> + { + let data = { + use chacha20stream::cha::{KEY_SIZE, IV_SIZE}; + let (key, iv) = chacha20stream::cha::keygen(); + let (sz, d) = crate::bin::collect_slices_exact::<&[u8], _, {KEY_SIZE + IV_SIZE}>([key.as_ref(), iv.as_ref()]); + assert_eq!(sz, d.len()); + d + }; + println!("KEY+IV: {} bytes", data.len()); + + let key = cryptohelpers::rsa::RsaPublicKey::generate()?; + let rsa = cryptohelpers::rsa::encrypt_slice_to_vec(data, &key)?; + println!("Rsa ciphertext size: {}", rsa.len()); + + assert_eq!(rsa.len(), super::RSA_CIPHERTEXT_SIZE, "Incorrect RSA ciphertext length constant for cc20 KEY+IV encoding."); + + Ok(()) + } + #[test] + fn rsa_serial_ciphertext_len() -> crate::eyre::Result<()> + { + let data = serde_cbor::to_vec(&{ + let (key, iv) = chacha20stream::cha::keygen(); + super::ESockSessionKey { + key, iv, + } + }).expect("Failed to CBOR encode Key+IV"); + println!("(cbor) KEY+IV: {} bytes", data.len()); + + let key = cryptohelpers::rsa::RsaPublicKey::generate()?; + let rsa = cryptohelpers::rsa::encrypt_slice_to_vec(data, &key)?; + println!("Rsa ciphertext size: {}", rsa.len()); + + assert_eq!(rsa.len(), super::RSA_CIPHERTEXT_SIZE, "Incorrect RSA ciphertext length constant for cc20 KEY+IV CBOR encoding."); + + Ok(()) + } +}