Added `bin` module for byte slice concat.

Removed !Unpin `exchange()` function.

Removed `Exchange<"sock,W,R>` future

Tests for RSA ciphertext len passing. (512 bytes static.)

Fortune for rsh's current commit: Future small blessing − 末小吉
exchange-unsafe
Avril 3 years ago
parent 0f7f22f290
commit 3a5331b5f1
Signed by: flanchan
GPG Key ID: 284488987C31F630

12
Cargo.lock generated

@ -2,6 +2,12 @@
# It is not intended for manual editing. # It is not intended for manual editing.
version = 3 version = 3
[[package]]
name = "ad-hoc-iter"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90a8dd76beceb5313687262230fcbaaf8d4e25c37541350cf0932e9adb8309c8"
[[package]] [[package]]
name = "addr2line" name = "addr2line"
version = "0.16.0" version = "0.16.0"
@ -106,9 +112,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]] [[package]]
name = "chacha20stream" name = "chacha20stream"
version = "2.0.1" version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5b34f4279658654cc2c7dbb36ba00b620dd5532153a9b9ebfea1b7ef282e1d4" checksum = "54c8d48b47fa0a89a94b80d32b1b3fc9ffc1a232a5201ff5a2d14ac77bc7561d"
dependencies = [ dependencies = [
"base64 0.13.0", "base64 0.13.0",
"getrandom 0.2.3", "getrandom 0.2.3",
@ -116,6 +122,7 @@ dependencies = [
"openssl", "openssl",
"pin-project", "pin-project",
"rustc_version", "rustc_version",
"serde",
"smallvec", "smallvec",
"stackalloc", "stackalloc",
"tokio 0.2.25", "tokio 0.2.25",
@ -791,6 +798,7 @@ dependencies = [
name = "rsh" name = "rsh"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"ad-hoc-iter",
"bytes 1.0.1", "bytes 1.0.1",
"chacha20stream", "chacha20stream",
"color-eyre", "color-eyre",

@ -6,8 +6,9 @@ edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
ad-hoc-iter = "0.2.3"
bytes = { version = "1.0.1", features = ["serde"] } 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" color-eyre = "0.5.11"
cryptohelpers = { version = "1.8.1" , features = ["serialise", "full"] } cryptohelpers = { version = "1.8.1" , features = ["serialise", "full"] }
futures = "0.3.16" futures = "0.3.16"

@ -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<B: BufMut + ?Sized, I, T>(into: &mut B, from: I) -> usize
where I: IntoIterator<Item=T>,
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<T, I, const SIZE: usize>(from: I) -> (usize, [u8; SIZE])
where I: IntoIterator<Item=T>,
T: AsRef<[u8]>
{
let mut output = [0u8; SIZE];
(collect_slices_into(&mut &mut output[..], from), output)
}

@ -4,6 +4,7 @@
#![allow(dead_code)] #![allow(dead_code)]
#[macro_use] extern crate serde; #[macro_use] extern crate serde;
#[macro_use] extern crate ad_hoc_iter;
#[macro_use] extern crate pin_project; #[macro_use] extern crate pin_project;
#[allow(unused_imports)] #[allow(unused_imports)]
@ -21,6 +22,7 @@ use std::convert::{
}; };
mod ext; use ext::*; mod ext; use ext::*;
mod bin;
mod message; mod message;
mod cancel; mod cancel;
@ -30,8 +32,8 @@ mod sock;
//mod pipeline; //mod pipeline;
#[tokio::main] #[tokio::main]
async fn main() -> eyre::Result<()> { async fn main() -> eyre::Result<()>
println!("Hello, world!"); {
Ok(()) Ok(())
} }

@ -10,11 +10,15 @@ use cryptohelpers::{
use chacha20stream::{ use chacha20stream::{
AsyncSink, AsyncSink,
AsyncSource, AsyncSource,
Key, IV,
}; };
use std::sync::Arc; use std::sync::Arc;
use tokio::{ use tokio::{
sync::{ sync::{
RwLock, RwLock,
RwLockReadGuard,
RwLockWriteGuard,
}, },
io::{ io::{
DuplexStream, DuplexStream,
@ -26,9 +30,15 @@ use std::{
Context, Poll, Context, Poll,
}, },
pin::Pin, 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 /// Max size to read when exchanging keys
const TRANS_KEY_MAX_SIZE: usize = 4096; const TRANS_KEY_MAX_SIZE: usize = 4096;
@ -39,12 +49,28 @@ struct ESockInfo {
them: Option<RsaPublicKey>, them: Option<RsaPublicKey>,
} }
#[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. /// A tx+rx socket.
#[pin_project] #[pin_project]
#[derive(Debug)] #[derive(Debug)]
pub struct ESock<W, R> { pub struct ESock<W, R> {
info: RwLock<ESockInfo>, info: ESockInfo,
state: ESockState,
#[pin] #[pin]
rx: AsyncSource<R>, rx: AsyncSource<R>,
#[pin] #[pin]
@ -64,19 +90,84 @@ impl<W: AsyncWrite, R: AsyncRead> ESock<W, R>
} }
/// Create a future that exchanges keys /// 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<W>, &mut AsyncSource<R>)
{
(&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<W: AsyncWrite+ Unpin, R: AsyncRead + Unpin> ESock<W, R> impl<W: AsyncWrite+ Unpin, R: AsyncRead + Unpin> ESock<W, R>
{ {
/// 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. /// Exchange keys.
pub async fn exchange_unpin(&mut self) -> eyre::Result<()> pub async fn exchange(&mut self) -> eyre::Result<()>
{ {
use tokio::prelude::*; 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 (tx, rx) = self.inner_mut();
let read_fut = { let read_fut = {
@ -112,30 +203,172 @@ impl<W: AsyncWrite+ Unpin, R: AsyncRead + Unpin> ESock<W, R>
let (send, recv) = tokio::join! [write_fut, read_fut]; let (send, recv) = tokio::join! [write_fut, read_fut];
send?; send?;
let recv = recv?; let recv = recv?;
self.info.write().await.them = Some(recv); self.info.them = Some(recv);
Ok(()) 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> pub struct Exchange<'a, W, R>
{ {
sock: &'a mut ESock<W, R>, sock: &'a mut ESock<W, R>,
us: RsaPublicKey,
us_written: usize,
us_buf: Vec<u8>,
/// The return value
them: Option<RsaPublicKey>,
write_sz_num: usize,
write_sz_buf: [u8; std::mem::size_of::<u64>()],
read_sz_buf: [u8; std::mem::size_of::<u64>()],
read_buf: Vec<u8>,
write_state: ExchangeState,
read_state: ExchangeState,
#[pin] _pin: PhantomPinned,
} }
/*
impl<'a, W: AsyncWrite, R: AsyncRead> Future for Exchange<'a, W, R> impl<'a, W: AsyncWrite, R: AsyncRead> Future for Exchange<'a, W, R>
{ {
type Output = eyre::Result<()>; type Output = eyre::Result<()>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
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") todo!("This is going to be dificult to implement... We don't have access to write_all and read_exact")
} }
} }
*/
/// Write half for `ESock`. /// Write half for `ESock`.
#[pin_project] #[pin_project]
#[derive(Debug)] #[derive(Debug)]
pub struct ESockWriteHalf<W>(Arc<ESockInfo>, #[pin] AsyncSink<W>); pub struct ESockWriteHalf<W>(Arc<(ESockInfo, RwLock<ESockState>)>, #[pin] AsyncSink<W>);
/// Read half for `ESock`. /// Read half for `ESock`.
#[pin_project] #[pin_project]
#[derive(Debug)] #[derive(Debug)]
pub struct ESockReadHalf<R>(Arc<ESockInfo>, #[pin] AsyncSource<R>); pub struct ESockReadHalf<R>(Arc<(ESockInfo, RwLock<ESockState>)>, #[pin] AsyncSource<R>);
#[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(())
}
}

Loading…
Cancel
Save