diff --git a/Cargo.lock b/Cargo.lock index af93669..d8ffdf3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -206,12 +206,12 @@ dependencies = [ [[package]] name = "cryptohelpers" -version = "1.3.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bc52622422e80c9a71f9664e3b90a77465b3ea4f05f06591987395eda0ff0f2" +checksum = "f58a1678cb9178e7af87eeba23f1524507e6fcf76a2f4807179a02dde722ccc7" dependencies = [ "crc", - "getrandom", + "getrandom 0.1.15", "hex-literal", "hmac", "libc", @@ -395,6 +395,17 @@ dependencies = [ "wasi 0.9.0+wasi-snapshot-preview1", ] +[[package]] +name = "getrandom" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee8025cf36f917e6a52cce185b7c7177689b838b7ec138364e50cc2277a56cf4" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.9.0+wasi-snapshot-preview1", +] + [[package]] name = "gimli" version = "0.22.0" @@ -771,6 +782,7 @@ dependencies = [ "color-eyre", "cryptohelpers", "futures", + "getrandom 0.2.0", "hex", "hex-literal", "lazy_static", @@ -816,7 +828,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" dependencies = [ - "getrandom", + "getrandom 0.1.15", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index c0e00ef..dc00a60 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.3", features=["full", "async", "serialise"]} +crypto = {package= "cryptohelpers", version = "1.4.1", features=["full", "async", "serialise"]} cfg-if = "0.1.10" tokio = {version = "0.2", features=["full"]} serde = {version ="1.0.116", features=["derive"]} @@ -36,6 +36,7 @@ hex = "0.4.2" async-trait = "0.1.40" hex-literal = "0.3.1" once_cell = "1.4.1" +getrandom = "0.2.0" #serde_json = "1.0.57" # serde not suitable for our text formatting :/ maybe just use `cbor` -> base64 with text header? would be a PEM-like format. sounds good imo [build-dependencies] diff --git a/src/ext.rs b/src/ext.rs index cafb5e7..25a1fa4 100644 --- a/src/ext.rs +++ b/src/ext.rs @@ -4,6 +4,9 @@ use std::{ error, pin::Pin, task::{Poll,Context,}, + ops::{ + Range, + }, }; use tokio::{ io::AsyncRead, @@ -434,3 +437,4 @@ mod tests } } } + diff --git a/src/format/key/aes.rs b/src/format/key/aes.rs new file mode 100644 index 0000000..04ee57b --- /dev/null +++ b/src/format/key/aes.rs @@ -0,0 +1,177 @@ +//! AES key serialisation +use super::*; +use crypto::{ + aes::{ + self, + AesKey, + }, + consts::{AES_IVSIZE, AES_KEYSIZE}, +}; +use eyre::eyre; + +const PADDING_SZ: usize = AES_KEYSIZE + AES_IVSIZE; + +fn new_padding() -> [u8; PADDING_SZ] +{ + let mut buf = [0u8; PADDING_SZ]; + getrandom::getrandom(&mut buf[..]).expect("Not enough entropy"); + buf +} + +/// Contains the body of an AES key +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] +pub struct AesBody +{ + key: AesKey, + garbage: [u8; PADDING_SZ], +} + +impl AesBody +{ + /// Compute the sha256 hash of this body instance + #[inline] pub fn compute_hash(&self) -> Sha256Hash + { + debug_assert_ne!(self.garbage, [0u8; PADDING_SZ], "Empty padding is unwise"); + + let mut cbuf = [0u8; PADDING_SZ + AES_KEYSIZE+ AES_IVSIZE]; + bytes::copy_slice(&mut cbuf[..AES_KEYSIZE+ AES_IVSIZE], self.key.as_ref()); + bytes::copy_slice(&mut cbuf[AES_KEYSIZE+AES_IVSIZE..], &self.garbage[..]); + crypto::sha256::compute_slice(cbuf) + } + /// Create a new body container from this key + pub fn new_key(key: AesKey) -> Self + { + Self { key, garbage: new_padding() } + } + /// Create an empty, keyless body + /// + /// # Important + /// This creates an instance with all 0 padding. Use `with_padding` or `give_padding` pls. + pub const fn empty() -> Self + { + Self { key: AesKey::empty(), garbage: [0u8; PADDING_SZ] } + } + /// 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) + { + bytes::copy_slice(&mut self.garbage[..], &new_padding()[..]); + } + /// Consume into the internal aes key + pub fn into_key(self) -> AesKey + { + self.key + } + /// Get the internal body key + pub fn key(&self) -> &AesKey + { + &self.key + } + /// Get the internal body key + pub fn key_mut(&mut self) -> &mut AesKey + { + &mut self.key + } + + /// Write an aes 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 = [0u8; AES_KEYSIZE +AES_IVSIZE + PADDING_SZ]; + bytes::copy_slice(&mut buf[..AES_KEYSIZE], self.key.k()); + bytes::copy_slice(&mut buf[AES_KEYSIZE..], self.key.i()); + bytes::copy_slice(&mut buf[AES_KEYSIZE+ AES_IVSIZE..], &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 aes 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 buf = [0u8; AES_KEYSIZE + AES_IVSIZE + PADDING_SZ]; + let mut cbuf = std::io::Cursor::new(&mut buf[..]); + + let written = aes::decrypt_stream(ses_enc, &mut &tbuf[..], &mut cbuf).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 written != buf.len() { + return Err(eyre::eyre!("Decrypted buffer length {} is not equal to expected AES key size {}", written, buf.len())) + .with_section(|| format!("{}", (&buf[..written]).fmt_view()).header("Decrypted buffer was")) + .with_note(|| "This likeley indicates bad decrypted data"); + } + let mut key = AesKey::default(); + bytes::copy_slice(key.k_mut(), &buf[..AES_KEYSIZE]); + bytes::copy_slice(key.i_mut(), &buf[AES_KEYSIZE..]); + let mut garbage = [0u8; PADDING_SZ]; + bytes::copy_slice(&mut garbage[..], &buf[AES_KEYSIZE + AES_IVSIZE..]); + if garbage == [0u8; PADDING_SZ] { + warn!("Read empty-padded body"); + } + Ok(Self{ + key, + garbage, + }) + } + /// Write an aes 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(|| self.key.to_string().header("Body was")) + .with_section(|| ses_enc.to_string().header("Header key was"))?; + let mut written = 0; + trace!("Writing whole {:?}", 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; + } + Ok(written) + } + /// Read an AES body from a text stream + #[instrument(err, skip(input))] + pub async fn read_text(input: &mut T, ses_enc: &AesKey) -> Result + { + let mut whole = String::new(); + let mut buf = String::with_capacity(32); + loop { + match input.read_line(&mut buf).await? { + 0 => break, + _ => { + whole.push_str(buf.trim()); + buf.clear(); + }, + } + } + trace!("Read whole {:?}", whole); + 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"))?) + } +} diff --git a/src/format/key/mod.rs b/src/format/key/mod.rs index beaf27e..4614578 100644 --- a/src/format/key/mod.rs +++ b/src/format/key/mod.rs @@ -15,6 +15,7 @@ use crypto::{ Password, SALTSIZE }, + aes::AesKey, }; #[derive(Debug, Clone, Copy, PartialEq, Eq, Ord,PartialOrd, Hash, Serialize, Deserialize)] @@ -39,6 +40,25 @@ impl fmt::Display for ParsingError } } +macro_rules! try_usize { + (-> $read:expr) => { + { + let read = $read; + usize::try_from(read) + .wrap_err_with(|| eyre::eyre!("Size does not fit into usize (u{} -> u{}).", std::mem::size_of_val(&read)*8, std::mem::size_of::()*8)) + .with_section(|| read.to_string().header("Read size was")) + .with_note(|| "This is a likely indication of file corruption") + } + }; + (<- $read:expr) => { + { + let read = $read; + read.try_into() + .wrap_err_with(|| eyre::eyre!("Failed to represent size (u{}) as 64 bit integer. Something is very wrong", std::mem::size_of_val(&read)*8)) + .with_section(|| read.to_string().header("Buffer size was")) + } + } +} impl TryFrom for KeyHeaderKind { @@ -70,11 +90,23 @@ impl TryFrom for KeyHeaderKind pub struct KeyHeader { kind: KeyHeaderKind, + /// Key info info: config::op::KeyDescription, + /// Used to encrypt the key body + content_enc: crypto::aes::AesKey, + /// Hash of encrypted key body hash: Sha256Hash, timestamp: i64, } +/// Parts of the key that are encrypted +#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)] +struct InternalKeyInfo +{ + content_enc: crypto::aes::AesKey, + info: config::op::KeyDescription +} + impl Header for KeyHeader { const CHECK: u16 = CHECK_KEY; @@ -115,22 +147,29 @@ fn encode_salt(salt: &Salt) -> Result<[u8; SALTSIZE*2], eyre::Report> impl KeyHeader { + /// Get the key used to encrypt the real key + pub fn body_key(&self) -> &AesKey + { + &self.content_enc + } /// Create a new key header from these values - pub const fn new(kind: KeyHeaderKind, info: config::op::KeyDescription, hash: Sha256Hash, timestamp: i64) -> Self + pub const fn new(kind: KeyHeaderKind, info: config::op::KeyDescription, content_enc: crypto::aes::AesKey, hash: Sha256Hash, timestamp: i64) -> Self { Self { kind, info, hash, timestamp, + content_enc, } } /// Create a new key header from these values with the current timestamp + #[instrument] pub fn new_now(kind: KeyHeaderKind, info: config::op::KeyDescription, hash: Sha256Hash) -> Self { Self { timestamp: timestamp::now(), - ..Self::new(kind,info,hash,0) + ..Self::new(kind,info, crypto::aes::AesKey::generate().expect("Not enough entropy") ,hash,0) } } @@ -141,11 +180,8 @@ impl KeyHeader let salt = SaltGen::new(); let (mut written, passwd)= match passwd(&salt) { Some(passwd) => { - let mut salt = salt.into_inner().ok_or_else(|| eyre::eyre!("Password function returned a password not bound to the provided salt generator")) - .with_warning(|| "This is a bug, and should never happen") - .with_section(|| passwd.to_string().header("Unbound returned password was"))?; - - salt.as_mut()[0] |= 1u8; // make sure no conflict with NOPASS + let salt = extract_salt!(salt, passwd)?; + trace!("Using password {} with salt {}", passwd.to_hex_string(), salt.to_hex_string()); let salt = encode_salt_to_string(&salt); out.write_all(salt.as_bytes()).await?; out.write_u8(b'\n').await?; @@ -166,13 +202,14 @@ impl KeyHeader }.wrap_err_with(|| eyre::eyre!("Failed to serialise header to text")) .with_section(|| format!("{:?}", self).header("Header was"))?; - for bytes in text.as_bytes().chunks(16) { + 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; } - out.write_all(b"---").await?; - Ok(written + 4) + out.write_all(b"---\n>BEGIN\nBODY<\n").await?; // We can add a comment after > as long as it doesn't have any newlines. + // this is because of the retarded way i did read_text with mpsc. + Ok(written + 8 + 9) } /// Read a superheader as text bytes from this stream #[instrument(err, skip(input, passwd))] @@ -226,7 +263,7 @@ impl KeyHeader } - let line_sender = async move { //this is actually kinda overkill for this... + let line_sender = async move { //this causes the >\n<\n dumbness in the encoding. Rewrite this to not use mpsc pls let mut buffer = String::new(); while input.read_line(&mut buffer).await? != 0 { tx.send(buffer.clone()).await?; @@ -291,6 +328,7 @@ impl KeyHeader if !had_delim { warn!("Buffer contained no end-of-entry delimiter"); } + trace!("Done reading lines"); match password { Some(salt) => { @@ -303,12 +341,12 @@ impl KeyHeader .with_section(|| aes.to_string().header("Derived key was")) .with_section(|| salt.to_hex_string().header("Embedded salt was")) .with_section(|| enc.header("Read string was")) - .with_note(|| "Did you enter the wrong password?")?) + .with_suggestion(|| "Did you enter the wrong password?")?) }, None => return Err(eyre::eyre!("Container is password encrypted but no password was provided")) .with_section(|| enc.header("Read (encrypted) string was")) .with_section(|| salt.to_hex_string().header("Embedded salt was")) - .with_note(|| "This key must be loaded with `-p` or `-P`"), + .with_suggestion(|| "Specify the password to load this key with"), } } None => { @@ -323,35 +361,70 @@ impl KeyHeader let (sres, rres) = tokio::join!(line_sender, line_reader); match sres { Err(x @ SendError::IO(_)) => Err(x).with_note(|| "In line reader"), - Err(s @ SendError::SendError) => { - rres - .with_error(move || s)?; - warn!("Unreachable code entered"); - Err(SendError::SendError) - .with_note(|| "In line reader") - .with_warning(|| "`sres` failed with `SendError` but `rres` completed successfully. This should not happen") + Err(_) => { + trace!("We completed without reading whole file"); + Ok(rres?) + }, + _ => { + warn!("Header reading completed to EOF, there is no body in this stream."); + Ok(rres?) }, - _ => Ok(rres?), } } /// Write this key header as bytes to this stream #[instrument(err, skip(out, passwd))] pub async fn write_bytes Option>(&self, out: &mut T, passwd: F) -> Result { + //TODO: Replace with just serialising whole object with cbor. + let salt= SaltGen::new(); out.write_u8(self.kind as u8).await?; - let desc = { - let buf = serde_cbor::to_vec(&self.info) + + let desc = { + let info = InternalKeyInfo { + content_enc: self.content_enc.clone(), + info: self.info.clone(), + }; + let buf = serde_cbor::to_vec(&info) .wrap_err_with(|| eyre::eyre!("Failed to serialise key info into bytes")) .with_section(|| format!("{:?}", self.info).header("Key info was"))?; - out.write_u64(buf.len().try_into() - .wrap_err_with(|| eyre::eyre!("Failed to represent key info size as 64 bit integer. Something is very wrong")) - .with_section(|| buf.len().to_string().header("Buffer size was"))?).await?; - out.write_all(&buf[..]).await?; - buf.len() + 1 + if let Some(passwd) = passwd(&salt) { + + let salt = salt.into_inner().ok_or_else(|| eyre::eyre!("Password function returned a password not bound to the provided salt generator")) + .with_warning(|| "This is a bug, and should never happen") + .with_section(|| passwd.to_string().header("Unbound returned password was"))?; + + let buf = { + let mut ebuf = Vec::with_capacity(buf.len()); + let aes = passwd.create_aes(); + let ew =crypto::aes::encrypt_stream(&aes, &mut &buf[..], &mut ebuf).await //TODO: Remove this 2nd temporary, calculate the size beforehand and encrypt directly to output stream + .wrap_err_with(|| eyre::eyre!("Failed to encrypt serialised object to buffer")) + .with_section(|| format!("{}", buf.fmt_view()).header("Buffer was")) + .with_section(|| passwd.to_hex_string().header("Password hash was")) + .with_section(|| salt.to_hex_string().header("Salt was"))?; + debug!("Encrypted in-memory {} bytes (from {} -> {})", ew, buf.len(), ebuf.len()); + ebuf + }; + out.write_i8(1).await?; + out.write_u64(buf.len().try_into() + .wrap_err_with(|| eyre::eyre!("Failed to represent key info size as 64 bit integer. Something is very wrong")) + .with_section(|| buf.len().to_string().header("Encrypted buffer size was"))?).await?; + out.write_all(salt.as_ref()).await?; + out.write_all(&buf[..]).await?; + buf.len() + 8 + salt.as_ref().len() + } + else + { + out.write_u8(0).await?; + out.write_u64(buf.len().try_into() + .wrap_err_with(|| eyre::eyre!("Failed to represent key info size as 64 bit integer. Something is very wrong")) + .with_section(|| buf.len().to_string().header("Buffer size was"))?).await?; + out.write_all(&buf[..]).await?; + buf.len() + 8 + } }; out.write_all(self.hash.as_ref()).await?; out.write_i64(self.timestamp).await?; - Ok(1 + std::mem::size_of::() + 8 + desc) + Ok(1 + std::mem::size_of::() + desc + 8) } /// Read a key header as bytes from this stream @@ -363,18 +436,58 @@ impl KeyHeader byte.try_into(). with_section(|| [byte].to_hex_string().header("Invalid byte was"))? }; - let info = { - let read = input.read_u64().await?; - let read = usize::try_from(read) - .wrap_err_with(|| eyre::eyre!("Read buffer size does not fit into usize (u64 -> u{}).", std::mem::size_of::()*8)) - .with_section(|| read.to_string().header("Read size was")) - .with_note(|| "This is a likely indication of file corruption")?; - let mut buffer = vec![0u8; read]; - input.read_exact(&mut buffer[..]).await?; - serde_cbor::from_reader(&buffer[..]) - .wrap_err_with(|| eyre::eyre!("Failed to deserialise key info from byte buffer")) - .with_section(|| read.to_string().header("Byte buffer's valid read size was")) - .with_section(|| format!("{}", buffer.fmt_view()).header("Byte buffer was"))? + + let encrypted = input.read_u8().await?; + let read = input.read_u64().await?; + let read = usize::try_from(read) + .wrap_err_with(|| eyre::eyre!("Read buffer size does not fit into usize (u64 -> u{}).", std::mem::size_of::()*8)) + .with_section(|| read.to_string().header("Read size was")) + .with_note(|| "This is a likely indication of file corruption")?; + let info: InternalKeyInfo = match encrypted { + 1 => { // encrypted + let mut salt = Salt::none(); + input.read_exact(salt.as_mut()).await?; + if let Some(passwd) = passwd(&salt) { + let mut buffer = vec![0u8; read]; + input.read_exact(&mut buffer[..]).await?; + let mut ebuffer = Vec::with_capacity(buffer.len()); + + let aes = passwd.create_aes(); + let r = crypto::aes::decrypt_stream(&aes, &mut &buffer[..], &mut ebuffer).await + .wrap_err_with(|| eyre::eyre!("Failed to decrypt key data")) + .with_section(|| aes.to_string().header("Derived key was")) + .with_section(|| salt.to_hex_string().header("Embedded salt was")) + .with_section(|| format!("{}", buffer.fmt_view()).header("Read data was")) + .with_suggestion(|| "Did you enter the wrong password?")?; + debug!("Decrypted {} (from {} -> {})", r, buffer.len(), ebuffer.len()); + + serde_cbor::from_reader(&ebuffer[..]) + .wrap_err_with(|| eyre::eyre!("Failed to deserialise key info from decrypted byte buffer")) + .with_section(|| read.to_string().header("Encrypted byte buffer's valid read size was")) + .with_section(|| ebuffer.len().to_string().header("Decrypted byte buffer's valid read size was")) + .with_section(|| r.to_string().header("Processed stream size was")) + .with_section(|| aes.to_string().header("Derived key was")) + .with_section(|| salt.to_hex_string().header("Embedded salt was")) + .with_section(|| format!("{}", ebuffer.fmt_view()).header("Decrypted byte buffer was")) + .with_section(|| format!("{}", buffer.fmt_view()).header("Encrypted read data was"))? + } + else { + return Err(eyre::eyre!("Container is password encrypted but no password was provided")) + .with_section(|| salt.to_hex_string().header("Embedded salt was")) + .with_suggestion(|| "Specify the password to load this key with"); + } + }, + 0 => { // not encrypted + let mut buffer = vec![0u8; read]; + input.read_exact(&mut buffer[..]).await?; + serde_cbor::from_reader(&buffer[..]) + .wrap_err_with(|| eyre::eyre!("Failed to deserialise key info from byte buffer")) + .with_section(|| read.to_string().header("Byte buffer's valid read size was")) + .with_section(|| format!("{}", buffer.fmt_view()).header("Byte buffer was"))? + }, + invalid => return Err(eyre::eyre!("Invalid encryption specifier")) + .with_note(|| "This suggests very likely corrupted data") + .with_section(|| format!("{:#x}", invalid).header("Invalid bit was")), }; let hash = { let mut hash = Sha256Hash::empty(); @@ -385,7 +498,8 @@ impl KeyHeader Ok(Self { kind, - info, + info: info.info, + content_enc: info.content_enc, hash, timestamp }) @@ -509,8 +623,7 @@ mod tests #[tokio::test] async fn serde_text_with_super() -> Result<(), eyre::Report> - { - + { let header = KeyHeader::new_now(KeyHeaderKind::Aes, Default::default(), Default::default()); let mut ser = Vec::new(); let superheader = SuperHeader::::new_for(&header); @@ -536,3 +649,5 @@ mod tests Ok(()) } } + +pub mod aes; diff --git a/src/format/mod.rs b/src/format/mod.rs index 445ec74..d079a38 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -23,6 +23,8 @@ use version::Version; use crypto::password::{Password,Salt}; use serialise::SaltGen; +const TEXT_CHUNK_SIZE: usize = 32; + /// Trait RAE headers implement pub trait Header: fmt::Debug { @@ -45,6 +47,46 @@ pub trait Header: fmt::Debug } } +macro_rules! extract_salt { + ($salt:expr, $passwd:expr) => { + $salt.into_inner().ok_or_else(|| eyre::eyre!("Password function returned a password not bound to the provided salt generator")) + .with_warning(|| "This is a bug, and should never happen") + .with_section(|| $passwd.to_string().header("Unbound returned password was")) + } +} + +macro_rules! encrypt_buffer { + ($aes:expr, $buf:expr, $ebuf:expr, $passwd:expr, $salt:expr; $msg:literal $($tt:tt)*) => { + { + let buf = $buf; + let ebuf = $ebuf; + crypto::aes::encrypt_stream($aes, buf, ebuf).await //TODO: Remove this 2nd temporary, calculate the size beforehand and encrypt directly to output stream + .wrap_err_with(|| eyre::eyre!($msg $($tt)*)) + .map(|ew| debug!("Encrypted in-memory {} bytes (from {} -> {})", ew, buf.len(), ebuf.len())) + .with_section(|| format!("{}", buf.fmt_view()).header("Buffer was")) + .with_section(|| $passwd.to_hex_string().header("Password hash was")) + .with_section(|| $salt.to_hex_string().header("Salt was")) + } + }; +} + +macro_rules! decrypt_buffer { + + ($aes:expr, $buf:expr, $ebuf:expr, $passwd:expr, $salt:expr; $msg:literal $($tt:tt)*) => { + { + let buffer = $buf; + let ebuffer= $ebuf; + crypto::aes::decrypt_stream($aes, buffer, ebuffer).await + .map(|r| debug!("Decrypted in-memory {} (from {} -> {})", r, buffer.len(), ebuffer.len())) + .wrap_err_with(|| eyre::eyre!($msg $($tt)*)) + .with_section(|| $aes.to_string().header("Derived key was")) + .with_section(|| $salt.to_hex_string().header("Embedded salt was")) + .with_section(|| format!("{}", buffer.fmt_view()).header("Read data was")) + .with_suggestion(|| "Did you enter the wrong password?") + } + }; +} + pub const RAE_HEADER_BIT: [u8; 4] = *b"RAE0"; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] @@ -73,8 +115,8 @@ const MAX_TEXT_SZ: Option = Some(1024 * 1024); impl SuperHeader { /// Write this superheader as text bytes to this stream - #[instrument(err, skip(out, passwd))] - pub async fn write_text Option>(&self, out: &mut T, passwd: F) -> Result + #[instrument(err, skip(out, _passwd))] + pub async fn write_text Option>(&self, out: &mut T, _passwd: F) -> Result { let string = format!(r#"--- {} v{}-{:x} ({}) {} --- "#, std::str::from_utf8(&self.head[..]) @@ -89,8 +131,8 @@ impl SuperHeader Ok(string.len()) } /// Read a superheader as text bytes from this stream - #[instrument(err, skip(input, passwd))] - pub async fn read_text Option>(input: &mut T, passwd: F) -> Result + #[instrument(err, skip(input, _passwd))] + pub async fn read_text Option>(input: &mut T, _passwd: F) -> Result { let mut line = String::new(); input.read_line(&mut line).await?; @@ -198,17 +240,37 @@ impl SuperHeader } /// Write this superheader as bytes to this stream - #[instrument(err, skip(out, passwd))] - pub async fn write_bytes Option>(&self, out: &mut T, passwd: F) -> Result + #[instrument(err, skip(out, _passwd))] + pub async fn write_bytes Option>(&self, out: &mut T, _passwd: F) -> Result { Ok({out.write_all(&self.head[..]).await?; self.head.len()} + {out.write_all(self.vers.as_bytes()).await?; std::mem::size_of::()} + {out.write_u16(self.chk).await?; 2} + - {out.write_all(self.header_hash.as_ref()).await?; std::mem::size_of::()}) //this is what gets encrypted by password + { + // for now we won't bother encrypting here + /* + let salt= SaltGen::new(); + if let Some(passwd) = passwd(&salt) { + let salt = extract_salt!(salt, passwd); + let aes = passwd.create_aes(); + let hash_bytes = self.header_hash.as_ref(); + let mut output = Vec::with_capacity(hash_bytes.len()); + + encrypt_buffer!(&aes, &mut &hash_bytes[..], &mut output, passwd, salt; "Failed to encrypt hash part") + .with_section(|| self.header_hash.to_string().header("Hash was"))?; + out.write_u8(1).await?; + out.write_all(&output[..]).await?; + output.len() + } else { + out.write_u8(0).await?;*/ + out.write_all(self.header_hash.as_ref()).await?; + std::mem::size_of::() + /*}*/ + }) } /// Read a superheader as bytes from this stream - #[instrument(err, skip(input, passwd))] - pub async fn read_bytes Option>(input: &mut T, passwd: F) -> Result + #[instrument(err, skip(input, _passwd))] + pub async fn read_bytes Option>(input: &mut T, _passwd: F) -> Result { let mut new = Self::new(); input.read_exact(&mut new.head[..]).await?; @@ -349,6 +411,6 @@ impl serialise::TextSerialiseable for SuperHeader } /// Nopassword text constant -const TEXT_NOPASS: &[u8; 16] = b"0000000000000000"; +const TEXT_NOPASS: &[u8; 64] = b"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"; pub mod key; const CHECK_KEY: u16 = 0x0001; diff --git a/src/main.rs b/src/main.rs index 601cf1d..8def5a0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,7 +3,7 @@ #![cfg_attr(nightly, feature(const_fn_transmute))] #![cfg_attr(nightly, feature(never_type))] #![cfg_attr(nightly, feature(test))] - +#![cfg_attr(nightly, feature(untagged_unions))] #![allow(dead_code)] #![allow(unused_macros)] @@ -140,31 +140,45 @@ fn install_tracing() { .with(ErrorLayer::default()) .init(); } + +#[instrument] async fn fuck() -> eyre::Result<()> { use format::*; use format::key::*; - let header = KeyHeader::new_now(KeyHeaderKind::Aes, Default::default(), Default::default()); + use crypto::password::Password; + let header = KeyHeader::new_now(KeyHeaderKind::Aes, config::op::KeyDescription{ + name: "Name!".to_owned(), + description: "Some description".to_owned(), + ..Default::default() + }, Default::default()); + let body = key::aes::AesBody::new_key(crypto::aes::AesKey::generate().unwrap()); let mut ser = Vec::new(); + let password = "hello world. I am a password"; let superheader = SuperHeader::::new_for(&header); - println!("Writing: {:?} + {:?}", superheader, header); - let written = superheader.write_text(&mut ser, |_| None).await?; - ser.extend(header.into_memory(serialise::Mode::Text, |_| None)?); //header.write_text(&mut ser).await?; + println!("Writing: {:?}\n\t{:?}\n\t{:?}", superheader, header, body); + let mut written = superheader.write_bytes(&mut ser, |salt| Some(Password::derive(password, salt.as_ref()))).await?; + ser.extend(header.into_memory(serialise::Mode::Binary, |salt| Some(Password::derive(password, salt.as_ref())))?); //header.write_text(&mut ser).await?; + written += body.write_bytes(&mut ser, header.body_key()).await?; println!("Wrote {} bytes", written); println!("{}\n", ser.fmt_view()); + println!("As text:\n{}\n", String::from_utf8_lossy(&ser[..])); + let mut read = &ser[..]; - let (reads, readn) = SuperHeader::from_memory(&mut read, serialise::Mode::Text, |_| None)?; // SuperHeader::read_text(read).await?; + let (reads, readn) = SuperHeader::from_memory(&mut read, serialise::Mode::Binary, |salt| Some(Password::derive(password, salt)))?; // SuperHeader::read_text(read).await?; let mut read = &read[readn..]; println!("Read super: {:?}", reads); - let readheader = KeyHeader::read_text(&mut read, |_| None).await?; + let readheader = KeyHeader::read_bytes(&mut read, |salt| Some(Password::derive(password, salt))).await?; println!("Read real: {:?}", readheader); - + let readbody = key::aes::AesBody::read_bytes(&mut read, readheader.body_key()).await?; + println!("Read body: {:?}", readbody); reads.verify_for(&header)?; reads.verify_for(&readheader)?; assert_eq!(readheader, header); assert_eq!(reads, superheader); - + assert_eq!(readbody, body); + info!("All okay!"); Ok(()) } diff --git a/src/serialise.rs b/src/serialise.rs index be17691..60b2c53 100644 --- a/src/serialise.rs +++ b/src/serialise.rs @@ -116,7 +116,7 @@ pub trait Serialisable: BinarySerialisable + TextSerialiseable /// /// # Notes /// This function will fail if any `await`ed future within the `serialise_*` method cannot complete immediately. - #[instrument(skip(self, passwd))] + #[instrument(skip(self, passwd), fields(ty = ?std::any::type_name::()))] fn into_memory Option>(&self, mode: Mode, passwd: F) -> Result, eyre::Report> { let mut output = match mode.size_hint(self) @@ -147,7 +147,7 @@ pub trait Serialisable: BinarySerialisable + TextSerialiseable /// /// # Notes /// This function will fail if any `await`ed future within the `deserialise_*` method cannot complete immediately. - #[instrument(skip(buf, passwd),fields(buf = ?buf.as_ref()))] + #[instrument(skip(buf, passwd),fields(ty = ?std::any::type_name::(), buf_len = ?buf.as_ref().len()))] fn from_memory, F: FnOnce(&Salt) -> Option>(buf: T, mode: Mode, passwd: F) -> Result<(Self, usize), eyre::Report> { let buf = buf.as_ref();