diff --git a/Cargo.lock b/Cargo.lock index d8ffdf3..b310dbb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -206,9 +206,9 @@ dependencies = [ [[package]] name = "cryptohelpers" -version = "1.4.1" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f58a1678cb9178e7af87eeba23f1524507e6fcf76a2f4807179a02dde722ccc7" +checksum = "487ef8c0290bfb3137440389d02c638341b3cb944c39e475ed543584ec596f7c" dependencies = [ "crc", "getrandom 0.1.15", diff --git a/Cargo.toml b/Cargo.toml index dc00a60..c7cab52 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,7 @@ local-time = [] [dependencies] color-eyre = "0.5.3" lazy_static = "1.4.0" -crypto = {package= "cryptohelpers", version = "1.4.1", features=["full", "async", "serialise"]} +crypto = {package= "cryptohelpers", version = "1.5.0", features=["full", "async", "serialise"]} cfg-if = "0.1.10" tokio = {version = "0.2", features=["full"]} serde = {version ="1.0.116", features=["derive"]} diff --git a/src/format/key/aes.rs b/src/format/key/aes.rs index fc5dcc0..58a7033 100644 --- a/src/format/key/aes.rs +++ b/src/format/key/aes.rs @@ -68,7 +68,8 @@ impl AesBody /// Give this body new padding pub fn give_padding(&mut self) { - bytes::copy_slice(&mut self.garbage[..], &new_padding()[..]); + //bytes::copy_slice(&mut self.garbage[..], &new_padding()[..]); + self.garbage = new_padding(); } /// Consume into the internal aes key pub fn into_key(self) -> AesKey @@ -164,7 +165,7 @@ impl AesBody pub async fn read_text(input: &mut T, ses_enc: &AesKey) -> Result { let mut whole = String::new(); - let mut buf = String::with_capacity(32); + let mut buf = String::with_capacity(TEXT_CHUNK_SIZE); loop { match input.read_line(&mut buf).await? { 0 => break, @@ -181,3 +182,31 @@ impl AesBody .with_section(|| ses_enc.to_string().header("Header key was"))?) } } + + +impl KeyBodySerialisable for AesBody +{ + #[inline] fn serialise_text<'a, 'b: 'a, T: AsyncWrite+Unpin+?Sized>(&'a self, out: &'b mut T, ses_enc: &'b AesKey) -> LocalBoxFuture<'a, Result> + { + self.write_text(out, ses_enc).boxed_local() + } + #[inline] fn serialise_bytes<'a, 'b: 'a, T: AsyncWrite+Unpin+?Sized>(&'a self, out: &'b mut T, ses_enc: &'b AesKey) -> LocalBoxFuture<'a, Result> + { + self.write_bytes(out, ses_enc).boxed_local() + } +} + +impl KeyBodyDeserialisable for AesBody +{ + #[inline] fn deserialise_bytes<'a, T: AsyncBufRead+Unpin+?Sized>(input: &'a mut T, ses_enc: &'a AesKey) -> LocalBoxFuture<'a, Result> + where Self: 'a + { + Self::read_bytes(input, ses_enc).boxed_local() + } + + #[inline] fn deserialise_text<'a, T: AsyncBufRead+Unpin+?Sized>(input: &'a mut T, ses_enc: &'a AesKey) -> LocalBoxFuture<'a, Result> + where Self: 'a + { + Self::read_text(input, ses_enc).boxed_local() + } +} diff --git a/src/format/key/mod.rs b/src/format/key/mod.rs index 2b6021a..b613ca1 100644 --- a/src/format/key/mod.rs +++ b/src/format/key/mod.rs @@ -60,6 +60,43 @@ macro_rules! try_usize { } } +macro_rules! write_chunked_text +{ + ($out:expr, $text:expr) => { + { + let mut written =0; + let out = $out; + let text = $text; + for bytes in text.as_bytes().chunks(TEXT_CHUNK_SIZE) { + out.write_all(bytes).await?; + out.write_u8(b'\n').await?; + written += bytes.len() + 1; + } + written + } + } +} + +macro_rules! read_chunked_text { + ($input:expr) => { + { + let input = $input; + let mut whole = String::new(); + let mut buf = String::with_capacity(TEXT_CHUNK_SIZE+1); + loop { + match input.read_line(&mut buf).await? { + 0 => break, + _ => { + whole.push_str(buf.trim()); + buf.clear(); + }, + } + } + whole + } + }; +} + impl TryFrom for KeyHeaderKind { type Error = ParsingError; @@ -527,6 +564,28 @@ impl serialise::TextSerialiseable for KeyHeader } } +/// Trait for serialising key bodies +/// +/// # Notes +/// Using this requires heap allocations, use each key's `write_` methods instead. +pub trait KeyBodySerialisable +{ + fn serialise_text<'a, 'b: 'a, T: AsyncWrite+Unpin+?Sized>(&'a self, out: &'b mut T, ses_enc: &'b AesKey) -> LocalBoxFuture<'a, Result>; + fn serialise_bytes<'a, 'b: 'a, T: AsyncWrite+Unpin+?Sized>(&'a self, out: &'b mut T, ses_enc: &'b AesKey) -> LocalBoxFuture<'a, Result>; +} + +/// Trait for deserialising key bodies +/// +/// # Notes +/// Using this requires heap allocations, use each key's `read_` methods instead. +pub trait KeyBodyDeserialisable: Sized +{ + fn deserialise_text<'a, T: AsyncBufRead+Unpin+?Sized>(input: &'a mut T, ses_enc: &'a AesKey) -> LocalBoxFuture<'a, Result> + where Self: 'a; + fn deserialise_bytes<'a, T: AsyncBufRead+Unpin+?Sized>(input: &'a mut T, ses_enc: &'a AesKey) -> LocalBoxFuture<'a, Result> + where Self: 'a; +} + #[cfg(test)] mod tests { @@ -645,3 +704,4 @@ mod tests } pub mod aes; +pub mod rsa; diff --git a/src/format/key/rsa/mod.rs b/src/format/key/rsa/mod.rs new file mode 100644 index 0000000..b064eb3 --- /dev/null +++ b/src/format/key/rsa/mod.rs @@ -0,0 +1,97 @@ +//! RSA key serialisation +use super::*; +use crypto::{aes, rsa}; +use eyre::eyre; +const PADDING_SZ: usize = 16; + +fn new_padding() -> [u8; PADDING_SZ] +{ + let mut buf = [0u8; PADDING_SZ]; + getrandom::getrandom(&mut buf[..]).expect("Not enough entropy"); + buf +} + +/// An RSA key body +#[derive(Debug, Serialize, Deserialize)] +pub enum RsaBody +{ + Public(RsaPublicBody), + Private(!) +} + +impl RsaBody +{ + #[cold] fn kind_name(&self) -> &'static str + { + match self { + Self::Public(_) => "Public", + Self::Private(_) => "Private", + } + } + /// Write this RSA key body to a PEM string + pub fn to_pem_string(&self) -> eyre::Result + { + match self { + Self::Public(public) => public.to_pem_string(), + } + .wrap_err_with(|| eyre!("Failed to write RSA key body to PEM string")) + .with_section(|| self.kind_name().header("Kind was")) + } + + /// Is this key a public part + pub fn is_public(&self) -> bool + { + if let Self::Public(_) = self { + true + } else { + false + } + } + + /// Is this key a private part + pub fn is_private(&self) -> bool + { + if let Self::Private(_) = self { + true + } else { + false + } + } + + /// Write RSA body to a binary stream + #[instrument(skip(out))] + pub async fn write_bytes(&self, out: &mut T, ses_enc: &AesKey) -> Result + { + match self { + Self::Public(public) => public.write_bytes(out, ses_enc).await, + } + .wrap_err_with(|| eyre!("Failed to write RSA key body to binary stream")) + .with_section(|| self.kind_name().header("Kind was")) + } + + /// Write an RSA body to a text stream + #[instrument(skip(out))] + pub async fn write_text(&self, out: &mut T, ses_enc: &AesKey) -> Result + { + match self { + Self::Public(public) => public.write_text(out, ses_enc).await, + } + .wrap_err_with(|| eyre!("Failed to write RSA key body to text stream")) + .with_section(|| self.kind_name().header("Kind was")) + } +} + +impl KeyBodySerialisable for RsaBody +{ + #[inline] fn serialise_text<'a, 'b: 'a, T: AsyncWrite+Unpin+?Sized>(&'a self, out: &'b mut T, ses_enc: &'b AesKey) -> LocalBoxFuture<'a, Result> + { + self.write_text(out, ses_enc).boxed_local() + } + #[inline] fn serialise_bytes<'a, 'b: 'a, T: AsyncWrite+Unpin+?Sized>(&'a self, out: &'b mut T, ses_enc: &'b AesKey) -> LocalBoxFuture<'a, Result> + { + self.write_bytes(out, ses_enc).boxed_local() + } +} + +mod public; +pub use public::*; diff --git a/src/format/key/rsa/public.rs b/src/format/key/rsa/public.rs new file mode 100644 index 0000000..702962a --- /dev/null +++ b/src/format/key/rsa/public.rs @@ -0,0 +1,179 @@ +//! RSA public key serialisation +use super::*; + +/// A container of RSA public key +#[derive(Debug, Serialize, Deserialize)] +pub struct RsaPublicBody +{ + garbage: [u8; PADDING_SZ], + key: rsa::RsaPublicKey, +} + +impl RsaPublicBody +{ + /// Create a new body container from this RSA public key + pub fn new_key(key: rsa::RsaPublicKey) -> Self + { + Self { + garbage: new_padding(), + key, + } + } + + /// Create a new instance with new padding + pub fn with_padding(self) -> Self + { + Self { + garbage: new_padding(), + ..self + } + } + /// Give this body new padding + pub fn give_padding(&mut self) + { + self.garbage = new_padding(); + } + + /// Consume into the internal key + pub fn into_key(self) -> rsa::RsaPublicKey + { + self.key + } + + /// The internal key + pub fn key(&self) -> &rsa::RsaPublicKey + { + &self.key + } + + /// Mutable reference to the internal key + pub fn key_mut(&mut self) -> &mut rsa::RsaPublicKey + { + &mut self.key + } + + /// Write this body's key to a PEM string + pub fn to_pem_string(&self) -> eyre::Result + { + self.key.to_pem() + .wrap_err_with(|| eyre!("Failed to write public key body to PEM string")) + .with_section(|| format!("{:?}", self.key).header("Key body data was")) + } + + /// Read this body from a PEM string encoded key + /// + /// # Notes + /// This creates new padding for the returned body + pub fn from_pem(pem: impl AsRef) -> eyre::Result + { + let pem = pem.as_ref(); + Ok(Self::new_key(rsa::RsaPublicKey::from_pem(pem) + .wrap_err_with(|| eyre!("Failed to read public key body from PEM string")) + .with_section(|| pem.to_owned().header("PEM string was"))?)) + } + + /// Write RSA public body to a binary stream + #[instrument(skip(out), err)] + pub async fn write_bytes(&self, out: &mut T, ses_enc: &AesKey) -> Result + { + if self.garbage == [0u8; PADDING_SZ] { + warn!("Writing empty-padded body"); + } + + let mut buf: Vec = self.key.to_bytes(); + buf.extend_from_slice(&self.garbage[..]); + + let mut tbuf = Vec::with_capacity(buf.len()); //eh, i need to do calculating instead of these dumb temp buffers... + aes::encrypt_stream(ses_enc, &mut &buf[..], &mut tbuf).await + .wrap_err_with(|| eyre!("Failed to encrypt body stream with header key")) + .with_section(|| format!("{}", buf.fmt_view()).header("Body stream was")) + .with_section(|| ses_enc.to_string().header("Header key was"))?; + + out.write_u64(try_usize!(<- tbuf.len())?).await?; + out.write_all(&tbuf[..]).await?; + Ok(8 + tbuf.len()) + } + + /// Read an RSA public body from a binary stream + #[instrument(skip(input), err)] + pub async fn read_bytes(input: &mut T, ses_enc: &AesKey) -> Result + { + let sz = try_usize!(-> input.read_u64().await?)?; + let mut tbuf = vec![0u8; sz]; + input.read_exact(&mut tbuf[..]).await?; + + let mut output = Vec::with_capacity(tbuf.len()); + + aes::decrypt_stream(ses_enc, &mut &tbuf[..], &mut output).await + .wrap_err_with(|| eyre!("Failed to decrypt body stream with header key")) + .with_section(|| format!("{}", tbuf.fmt_view()).header("Body stream was")) + .with_section(|| ses_enc.to_string().header("Header key was"))?; + + if output.len() < PADDING_SZ { + return Err(eyre!("Padding is invalid")) + .with_section(|| tbuf.to_view_string().header("Decrypted body stream was")); + } + + let mut garbage = [0u8; PADDING_SZ]; + bytes::copy_slice(&mut garbage[..], &output[output.len()-PADDING_SZ..]); + + let key = rsa::RsaPublicKey::from_bytes(&output[..output.len()-PADDING_SZ]) + .wrap_err_with(|| eyre!("Failed to instantiate key from body")) + .with_section(|| tbuf.to_view_string().header("Decrypted body stream was"))?; + + Ok(Self { + key, garbage + }) + } + + + /// Write an RSA public body to a text stream + #[instrument(err, skip(out))] + pub async fn write_text(&self, out: &mut T, ses_enc: &AesKey) -> Result + { + let text = serialise::into_text_with_key_async(self, ses_enc).await + .wrap_err_with(|| eyre!("Failed to serialise body to text")) + .with_section(|| format!("{:?}", self.key).header("Body key was")) + .with_section(|| ses_enc.to_string().header("Header key was"))?; + + Ok(write_chunked_text!(out, text)) + } + + /// Read an RSA public body from a text stream + #[instrument(err, skip(input))] + pub async fn read_text(input: &mut T, ses_enc: &AesKey) -> Result + { + let whole = read_chunked_text!(input); + Ok(serialise::from_text_with_key_async(&whole[..], ses_enc).await + .wrap_err_with(|| eyre!("Failed to deserialise body from text")) + .with_section(move|| whole.header("Body was")) + .with_section(|| ses_enc.to_string().header("Header key was"))?) + } +} + +impl KeyBodySerialisable for RsaPublicBody +{ + #[inline] fn serialise_text<'a, 'b: 'a, T: AsyncWrite+Unpin+?Sized>(&'a self, out: &'b mut T, ses_enc: &'b AesKey) -> LocalBoxFuture<'a, Result> + { + self.write_text(out, ses_enc).boxed_local() + } + #[inline] fn serialise_bytes<'a, 'b: 'a, T: AsyncWrite+Unpin+?Sized>(&'a self, out: &'b mut T, ses_enc: &'b AesKey) -> LocalBoxFuture<'a, Result> + { + self.write_bytes(out, ses_enc).boxed_local() + } +} + +impl KeyBodyDeserialisable for RsaPublicBody +{ + #[inline] fn deserialise_bytes<'a, T: AsyncBufRead+Unpin+?Sized>(input: &'a mut T, ses_enc: &'a AesKey) -> LocalBoxFuture<'a, Result> + where Self: 'a + { + Self::read_bytes(input, ses_enc).boxed_local() + } + + #[inline] fn deserialise_text<'a, T: AsyncBufRead+Unpin+?Sized>(input: &'a mut T, ses_enc: &'a AesKey) -> LocalBoxFuture<'a, Result> + where Self: 'a + { + Self::read_text(input, ses_enc).boxed_local() + } +}