From b8d549bebb48f8d4d03dd99dc7d15ef8b26d8bfa Mon Sep 17 00:00:00 2001 From: Avril Date: Thu, 8 Oct 2020 02:58:44 +0100 Subject: [PATCH] rsa private body --- Cargo.lock | 79 +++++++++++++- Cargo.toml | 2 +- src/format/key/rsa/mod.rs | 16 ++- src/format/key/rsa/private.rs | 187 ++++++++++++++++++++++++++++++++++ 4 files changed, 278 insertions(+), 6 deletions(-) create mode 100644 src/format/key/rsa/private.rs diff --git a/Cargo.lock b/Cargo.lock index b310dbb..10fd74f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -179,6 +179,18 @@ dependencies = [ "tracing-error", ] +[[package]] +name = "config_struct" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6aefd4a90b45fed157c31d107f8fdd9f8aaee79f0df625cefb773a9a1a10059f" +dependencies = [ + "failure", + "linear-map", + "quote", + "toml", +] + [[package]] name = "cpuid-bool" version = "0.1.2" @@ -206,9 +218,9 @@ dependencies = [ [[package]] name = "cryptohelpers" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "487ef8c0290bfb3137440389d02c638341b3cb944c39e475ed543584ec596f7c" +checksum = "2cfc491baaffd7cbd6acc02ebd23564760d83a2c17e1a47e6a04a8d5a86e7fb5" dependencies = [ "crc", "getrandom 0.1.15", @@ -242,6 +254,28 @@ dependencies = [ "once_cell", ] +[[package]] +name = "failure" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d32e9bd16cc02eae7db7ef620b392808b89f6a5e16bb3497d159c6b92a0f4f86" +dependencies = [ + "backtrace", + "failure_derive", +] + +[[package]] +name = "failure_derive" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa4da3c766cd7a0db8242e326e9e4e081edd567072893ed320008189715366a4" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + [[package]] name = "fnv" version = "1.0.7" @@ -492,6 +526,16 @@ version = "0.2.77" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2f96b10ec2560088a8e76961b00d47107b3a625fecb76dedb29ee7ccbf98235" +[[package]] +name = "linear-map" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfae20f6b19ad527b550c223fddc3077a547fc70cda94b9b566575423fd303ee" +dependencies = [ + "serde", + "serde_test", +] + [[package]] name = "log" version = "0.4.11" @@ -780,6 +824,7 @@ dependencies = [ "cfg-if", "chrono", "color-eyre", + "config_struct", "cryptohelpers", "futures", "getrandom 0.2.0", @@ -959,6 +1004,15 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_test" +version = "1.0.116" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "923edec3f1ab4a2f489f384e117dc4f826fd977a9d189b28717cba8474dd5c6b" +dependencies = [ + "serde", +] + [[package]] name = "sha2" version = "0.9.1" @@ -1041,6 +1095,18 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "synstructure" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b834f2d66f734cb897113e34aaff2f1ab4719ca946f9a7358dba8f8064148701" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "unicode-xid", +] + [[package]] name = "thread_local" version = "1.0.1" @@ -1096,6 +1162,15 @@ dependencies = [ "syn", ] +[[package]] +name = "toml" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "758664fc71a3a69038656bee8b6be6477d2a6c315a6b81f7081f591bffa4111f" +dependencies = [ + "serde", +] + [[package]] name = "tracing" version = "0.1.19" diff --git a/Cargo.toml b/Cargo.toml index e52295b..a3431b0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,7 @@ local-time = [] [dependencies] color-eyre = "0.5.3" lazy_static = "1.4.0" -crypto = {package= "cryptohelpers", version = "1.5.0", features=["full", "async", "serialise"]} +crypto = {package= "cryptohelpers", version = "1.5.1", 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/rsa/mod.rs b/src/format/key/rsa/mod.rs index b064eb3..3c2685f 100644 --- a/src/format/key/rsa/mod.rs +++ b/src/format/key/rsa/mod.rs @@ -1,9 +1,10 @@ //! RSA key serialisation use super::*; -use crypto::{aes, rsa}; +use crypto::{aes, rsa, password::{Password,}}; use eyre::eyre; const PADDING_SZ: usize = 16; +#[instrument] fn new_padding() -> [u8; PADDING_SZ] { let mut buf = [0u8; PADDING_SZ]; @@ -16,7 +17,7 @@ fn new_padding() -> [u8; PADDING_SZ] pub enum RsaBody { Public(RsaPublicBody), - Private(!) + Private(RsaPrivateBody) } impl RsaBody @@ -29,10 +30,14 @@ impl RsaBody } } /// Write this RSA key body to a PEM string - pub fn to_pem_string(&self) -> eyre::Result + /// + /// # Notes + /// Password is only applicable with Private keys + pub fn to_pem_string(&self, passwd: Option<&Password>) -> eyre::Result { match self { Self::Public(public) => public.to_pem_string(), + Self::Private(private) => private.to_pem_string(passwd), } .wrap_err_with(|| eyre!("Failed to write RSA key body to PEM string")) .with_section(|| self.kind_name().header("Kind was")) @@ -64,6 +69,7 @@ impl RsaBody { match self { Self::Public(public) => public.write_bytes(out, ses_enc).await, + Self::Private(private) => private.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")) @@ -75,6 +81,7 @@ impl RsaBody { match self { Self::Public(public) => public.write_text(out, ses_enc).await, + Self::Private(private) => private.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")) @@ -95,3 +102,6 @@ impl KeyBodySerialisable for RsaBody mod public; pub use public::*; + +mod private; +pub use private::*; diff --git a/src/format/key/rsa/private.rs b/src/format/key/rsa/private.rs new file mode 100644 index 0000000..a415004 --- /dev/null +++ b/src/format/key/rsa/private.rs @@ -0,0 +1,187 @@ +//! RSA Private key serialisation +use super::*; + +/// A container of RSA private key +#[derive(Debug, Serialize, Deserialize)] +pub struct RsaPrivateBody +{ + garbage: [u8; PADDING_SZ], + key: rsa::RsaPrivateKey, +} + +impl RsaPrivateBody +{ + /// Create a new body container from this RSA private key + pub fn new_key(key: rsa::RsaPrivateKey) -> Self + { + Self { + garbage: new_padding(), + key, + } + } + + /// Create an instace of public body from the public parts of this private key + pub fn get_public(&self) -> RsaPublicBody + { + RsaPublicBody::new_key(self.key.get_public_parts()) + } + + /// 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::RsaPrivateKey + { + self.key + } + + /// The internal key + pub fn key(&self) -> &rsa::RsaPrivateKey + { + &self.key + } + + /// Mutable reference to the internal key + pub fn key_mut(&mut self) -> &mut rsa::RsaPrivateKey + { + &mut self.key + } + + /// Write this body's key to a PEM string + pub fn to_pem_string(&self, pw: Option<&Password>) -> eyre::Result + { + self.key.to_pem(pw) + .wrap_err_with(|| eyre!("Failed to write private 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 Option>(pem: impl AsRef, pw: F) -> eyre::Result + { + let pem = pem.as_ref(); + Ok(Self::new_key(rsa::RsaPrivateKey::from_pem(pem, pw) + .wrap_err_with(|| eyre!("Failed to read private key body from PEM string")) + .with_section(|| pem.to_owned().header("PEM string was"))?)) + } + + + /// Write RSA private 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 private 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::RsaPrivateKey::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 private 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 private 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 RsaPrivateBody +{ + #[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 RsaPrivateBody +{ + #[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() + } +}