From b6b3bb0fd59923c72d2ee01035187b78edb5b871 Mon Sep 17 00:00:00 2001 From: Avril Date: Mon, 16 Aug 2021 14:59:13 +0100 Subject: [PATCH] Improved error messages for `set_encrypted_read()` and `exchange()`. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fortune for rsh's current commit: Future small blessing − 末小吉 --- Cargo.lock | 5 +- Cargo.toml | 2 +- src/ext/base64.rs | 15 ++++++ src/ext/mod.rs | 3 ++ src/sock/enc.rs | 125 +++++++++++++++++++++++++++++++++++++++------- 5 files changed, 128 insertions(+), 22 deletions(-) create mode 100644 src/ext/base64.rs diff --git a/Cargo.lock b/Cargo.lock index 7dd93ba..d2d7638 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -185,10 +185,11 @@ dependencies = [ [[package]] name = "cryptohelpers" -version = "1.8.1" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14be74ce15793a86acd04872953368ce27d07f384f07b8028bd5aaa31a031a38" +checksum = "9143447fb393f8d38abbb617af9b986a0941785ddc63685bd8de735fb31bcafc" dependencies = [ + "base64 0.13.0", "crc", "futures", "getrandom 0.1.16", diff --git a/Cargo.toml b/Cargo.toml index 3271d69..7fd768c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ base64 = "0.13.0" bytes = { version = "1.0.1", features = ["serde"] } chacha20stream = { version = "2.1.0", features = ["async", "serde"] } color-eyre = "0.5.11" -cryptohelpers = { version = "1.8.1" , features = ["serialise", "full"] } +cryptohelpers = { version = "1.8.2" , features = ["serialise", "full"] } futures = "0.3.16" mopa = "0.2.2" pin-project = "1.0.8" diff --git a/src/ext/base64.rs b/src/ext/base64.rs new file mode 100644 index 0000000..db7fea2 --- /dev/null +++ b/src/ext/base64.rs @@ -0,0 +1,15 @@ +//! Base64 formatting extensions +use super::*; + +pub trait Base64StringExt +{ + fn to_base64_string(&self) -> String; +} + +impl Base64StringExt for T +where T: AsRef<[u8]> +{ + fn to_base64_string(&self) -> String { + ::base64::encode(self.as_ref()) + } +} diff --git a/src/ext/mod.rs b/src/ext/mod.rs index 84bd83a..c1b7a83 100644 --- a/src/ext/mod.rs +++ b/src/ext/mod.rs @@ -10,6 +10,9 @@ pub use alloc::*; mod hex; pub use hex::*; +mod base64; +pub use self::base64::*; + /// A maybe-atom that can spill into a vector. pub type MaybeVec = SmallVec<[T; 1]>; diff --git a/src/sock/enc.rs b/src/sock/enc.rs index 38b50b3..97c4425 100644 --- a/src/sock/enc.rs +++ b/src/sock/enc.rs @@ -56,12 +56,44 @@ struct ESockInfo { them: Option, } -#[derive(Debug)] +impl ESockInfo +{ + /// Generate a new private key + pub fn new(us: impl Into) -> Self + { + Self { + us: us.into(), + them: None, + } + } + + /// Generate a new private key for the local endpoint + pub fn generate() -> Result + { + Ok(Self::new(RsaPrivateKey::generate()?)) + } +} + +/// The encryption state of the Tx and Rx instances. +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)] struct ESockState { encr: bool, encw: bool, } +impl Default for ESockState +{ + #[inline] + fn default() -> Self + { + Self { + encr: false, + encw: false, + } + } +} + + /// Contains a cc20 Key and IV that can be serialized and then encrypted #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] struct ESockSessionKey @@ -188,16 +220,42 @@ impl ESock impl ESock { + /// Get the Tx and Rx of the stream. + /// + /// # Returns + /// Returns encrypted stream halfs if the stream is encrypted, unencrypted if not. + pub fn stream(&mut self) -> (&mut (dyn AsyncWrite + Unpin + '_), &mut (dyn AsyncRead + Unpin + '_)) + { + (if self.state.encw { + &mut self.tx + } else { + self.tx.inner_mut() + }, if self.state.encr { + &mut self.rx + } else { + self.rx.inner_mut() + }) + } /// Enable write encryption pub async fn set_encrypted_write(&mut self, set: bool) -> eyre::Result<()> { use tokio::prelude::*; if set { let session_key = ESockSessionKey::generate(); - let data = session_key.to_ciphertext(self.info.them.as_ref().expect("Cannot set encrypted write when keys have not been exchanged"))?; - let crypter = session_key.to_encrypter()?; + let data = { + let them = self.info.them.as_ref().expect("Cannot set encrypted write when keys have not been exchanged"); + session_key.to_ciphertext(them) + .wrap_err(eyre!("Failed to encrypt session key with foreign endpoint's key")) + .with_section(|| session_key.to_string().header("Session key was")) + .with_section(|| them.to_string().header("Foreign pubkey was"))? + }; + let crypter = session_key.to_encrypter() + .wrap_err(eyre!("Failed to create encryption device from session key for Tx")) + .with_section(|| session_key.to_string().header("Session key was"))?; // Send rsa `data` over unencrypted endpoint - self.unencrypted().0.write_all(&data[..]).await?; + self.unencrypted().0.write_all(&data[..]).await + .wrap_err(eyre!("Failed to write ciphertext to endpoint")) + .with_section(|| data.to_base64_string().header("Ciphertext of session key was"))?; // Set crypter of `tx` to `session_key`. *self.tx.crypter_mut() = crypter; // Set `encw` to true @@ -218,11 +276,17 @@ impl ESock if set { let mut data = [0u8; RSA_CIPHERTEXT_SIZE]; // Read `data` from unencrypted endpoint - self.unencrypted().1.read_exact(&mut data[..]).await?; + self.unencrypted().1.read_exact(&mut data[..]).await + .wrap_err(eyre!("Failed to read ciphertext from endpoint"))?; // Decrypt `data` - let session_key = ESockSessionKey::from_ciphertext(&data, &self.info.us)?; + let session_key = ESockSessionKey::from_ciphertext(&data, &self.info.us) + .wrap_err(eyre!("Failed to decrypt session key from ciphertext")) + .with_section(|| data.to_base64_string().header("Ciphertext was")) + .with_section(|| self.info.us.to_string().header("Our RSA key is"))?; // Set crypter of `rx` to `session_key`. - *self.rx.crypter_mut() = session_key.to_decrypter()?; + *self.rx.crypter_mut() = session_key.to_decrypter() + .wrap_err(eyre!("Failed to create decryption device from session key for Rx")) + .with_section(|| session_key.to_string().header("Decrypted session key was"))?; // Set `encr` to true self.state.encr = true; Ok(()) @@ -255,17 +319,29 @@ impl ESock // Read the public key from `rx`. //TODO: Find pubkey max size. let mut sz_buf = [0u8; std::mem::size_of::()]; - rx.read_exact(&mut sz_buf[..]).await?; - let sz= match usize::try_from(u64::from_be_bytes(sz_buf))? { - x if x > TRANS_KEY_MAX_SIZE => return Err(eyre!("Recv'd key size exceeded max")), - x => x - }; + rx.read_exact(&mut sz_buf[..]).await + .wrap_err(eyre!("Failed to read size of pubkey form endpoint"))?; + let sz64 = u64::from_be_bytes(sz_buf); + let sz= match usize::try_from(sz64) + .wrap_err(eyre!("Read size could not fit into u64")) + .with_section(|| format!("{:?}", sz_buf).header("Read buffer was")) + .with_section(|| u64::from_be_bytes(sz_buf).header("64=bit size value was")) + .with_warning(|| "This should not happen, it is only possible when you are running a machine with a pointer size lower than 64 bits.") + .with_suggestion(|| "The message is likely malformed. If it is not, then you are communicating with an endpoint of 64 bits whereas your pointer size is far less.")? { + x if x > TRANS_KEY_MAX_SIZE => return Err(eyre!("Recv'd key size exceeded max acceptable key buffer size")), + x => x + }; let mut key_bytes = Vec::with_capacity(sz); - tokio::io::copy(&mut rx.take(sz as u64), &mut key_bytes).await?; + tokio::io::copy(&mut rx.take(sz64), &mut key_bytes).await + .wrap_err("Failed to read key bytes into buffer") + .with_section(move || sz64.header("Pubkey size to read was"))?; if key_bytes.len() != sz { return Err(eyre!("Could not read required bytes")); } - let k = RsaPublicKey::from_bytes(key_bytes)?; + let k = RsaPublicKey::from_bytes(&key_bytes) + .wrap_err("Failed to construct RSA public key from read bytes") + .with_section(|| sz.header("Pubkey size was")) + .with_section(move || key_bytes.to_base64_string().header("Pubkey bytes were"))?; Result::::Ok(k) } @@ -273,16 +349,27 @@ impl ESock let write_fut = { let key_bytes = our_key.to_bytes(); assert!(key_bytes.len() <= TRANS_KEY_MAX_SIZE); - let sz_buf = u64::try_from(key_bytes.len())?.to_be_bytes(); + let sz64 = u64::try_from(key_bytes.len()) + .wrap_err(eyre!("Size of our pubkey could not fit into u64")) + .with_section(|| key_bytes.len().header("Size was")) + .with_warning(|| "This should not happen, it is only possible when you are running a machine with a pointer size larger than 64 bits.") + .with_warning(|| "There was likely internal memory corruption.")?; + let sz_buf = sz64.to_be_bytes(); async move { - tx.write_all(&sz_buf[..]).await?; - tx.write_all(&key_bytes[..]).await?; + tx.write_all(&sz_buf[..]).await + .wrap_err(eyre!("Failed to write key size")) + .with_section(|| sz64.header("Key size bytes were")) + .with_section(|| format!("{:?}", sz_buf).header("Key size bytes (BE) were"))?; + tx.write_all(&key_bytes[..]).await + .wrap_err(eyre!("Failed to write key bytes")) + .with_section(|| sz64.header("Size of key was")) + .with_section(|| key_bytes.to_base64_string().header("Key bytes are"))?; Result::<(), eyre::Report>::Ok(()) } }; let (send, recv) = tokio::join! [write_fut, read_fut]; - send?; - let recv = recv?; + send.wrap_err("Failed to send our pubkey")?; + let recv = recv.wrap_err("Failed to receive foreign pubkey")?; self.info.them = Some(recv); Ok(()) }