diff options
| author | Will Dillon <william@housedillon.com> | 2025-11-06 17:17:54 +0000 |
|---|---|---|
| committer | Will Dillon <william@housedillon.com> | 2025-11-06 17:17:54 +0000 |
| commit | 6a06d3323c88bb71718a770d2629554bd96d55be (patch) | |
| tree | 5c89ba89c86eac68d816cc8cd73ea5686d020a72 /src | |
| parent | Ran clippy (diff) | |
| download | meshcore-rs-6a06d3323c88bb71718a770d2629554bd96d55be.tar.gz meshcore-rs-6a06d3323c88bb71718a770d2629554bd96d55be.zip | |
Switching over to the mac for mac
Diffstat (limited to 'src')
| -rw-r--r-- | src/crypto.rs | 118 | ||||
| -rw-r--r-- | src/packet.rs | 284 |
2 files changed, 276 insertions, 126 deletions
diff --git a/src/crypto.rs b/src/crypto.rs index 9dd423b..2661f38 100644 --- a/src/crypto.rs +++ b/src/crypto.rs @@ -2,10 +2,15 @@ use std::str::FromStr; use ed25519_dalek::hazmat::ExpandedSecretKey; use curve25519_dalek::{constants, edwards::CompressedEdwardsY}; -use tokio_util::bytes::{Buf, Bytes}; +use tokio_util::bytes::{Buf, BufMut, Bytes, BytesMut}; use x25519_dalek::StaticSecret; -use hex::decode; +use sha2::{Sha256}; +use hmac::{Hmac, Mac}; + +type HmacSha256 = Hmac<Sha256>; + +use hex::{decode, decode_to_slice}; #[derive(Debug)] pub enum MeshcoreCryptoError { @@ -13,13 +18,54 @@ pub enum MeshcoreCryptoError { TryFromSliceError } +#[derive(PartialEq, Debug)] pub struct PrivateKey(ExpandedSecretKey); #[derive(PartialEq, Debug, Clone)] pub struct PublicKey(CompressedEdwardsY); +#[derive(Debug, PartialEq)] pub struct SharedSecret([u8; 32]); +impl FromStr for SharedSecret { + type Err = MeshcoreCryptoError; + + fn from_str(s: &str) -> Result<Self, Self::Err> { + let mut slice = [0_u8; 32]; + if decode_to_slice(s, &mut slice).is_err() { + return Err(MeshcoreCryptoError::TryFromSliceError) + } else { + Ok(SharedSecret(slice)) + } + } +} + +impl SharedSecret { + pub fn new_from_group_secret(bytes: Bytes) -> Self { + // The group secret is 16-bytes of key with the last 16-bytes set to zero. + let mut group_secret = BytesMut::from(bytes); + group_secret.reserve(16); + group_secret.put(&[0_u8; 16][..]); + + let mut slice = [0_u8; 32]; + group_secret.copy_to_slice(&mut slice); + + SharedSecret(slice) + } + + pub fn get_hmac(&self, ciphertext: Bytes) -> Result<u16, MeshcoreCryptoError> { + if let Ok(mut mac) = HmacSha256::new_from_slice(&self.0) { + mac.update(&ciphertext); + let result = mac.finalize(); + let mut bytes = Bytes::copy_from_slice(&result.into_bytes()); + + Ok(bytes.get_u16()) + } else { + Err(MeshcoreCryptoError::KeyLengthError) + } + } +} + // This is just for creating placeholders impl Default for PublicKey { fn default() -> Self { @@ -76,8 +122,8 @@ impl FromStr for PrivateKey { } } -impl From<PrivateKey> for PublicKey { - fn from(key: PrivateKey) -> Self { +impl From<&PrivateKey> for PublicKey { + fn from(key: &PrivateKey) -> Self { PublicKey((key.0.scalar * constants::ED25519_BASEPOINT_POINT).compress()) } } @@ -104,4 +150,68 @@ pub struct Keychain { pub public_keys: Vec<PublicKey>, // secrets: HashMap<String, Ha> +} + +#[cfg(test)] +mod tests { + use curve25519_dalek::Scalar; + use hex::{decode_to_slice, encode}; + + use super::*; + + #[test] + fn public_key() { + let mut slice = [0_u8; 32]; + decode_to_slice("12349bdc1f76a0c12149bb15f791dbe42fde02c209b04a85c6f512990c8cedec", &mut slice).unwrap(); + let public_key = PublicKey::from_str("12349bdc1f76a0c12149bb15f791dbe42fde02c209b04a85c6f512990c8cedec"); + assert!(Ok(PublicKey(CompressedEdwardsY(slice))) == public_key); + } + + #[test] + fn private_key() { + // Create a private key in two different ways + let bytes = Bytes::copy_from_slice(&decode("38DAA98490B7284697C7ADA6175FD1F8DAD12032AD7ABAE625B7EAD8FEC6444CA281C3370B97155D9C8CECD89A929FDDE0FBF3A9D5C92A1B3C24D711934CD69D").unwrap()); + let private_key = PrivateKey::from_str("38DAA98490B7284697C7ADA6175FD1F8DAD12032AD7ABAE625B7EAD8FEC6444CA281C3370B97155D9C8CECD89A929FDDE0FBF3A9D5C92A1B3C24D711934CD69D"); + assert!(Ok(PrivateKey(ExpandedSecretKey::from_bytes(bytes.get(0 .. 64).unwrap().try_into().unwrap()))) == private_key); + + // Ensure that the expected public key can be derived from it. + let public_key = PublicKey::from(&private_key.unwrap()); + assert!(PublicKey::from_str("12349bdc1f76a0c12149bb15f791dbe42fde02c209b04a85c6f512990c8cedec").unwrap() == public_key); + } + + #[test] + fn shared_secret() { + // We'll make a public/private pair for alice and bob and make sure the shared secret is the same and expected + let alice_private = PrivateKey::from_str("4885CF25975EA09742EF76DA587D0957E74EE02AAA34A001458E207E63CF7E6C4940C8C42C335862C71CC2F139633057D1FEE5687B172B27E1E0302A1D480E08").unwrap(); + let bob_private = PrivateKey::from_str("08976A389FA16B077492BA7403A2178F4DF22B74A44DA0BE780CD0A51F5796437BB76B51320EE216F483F741FD73ED32F7DF5BBCBE811F405E579DD45AA8280A").unwrap(); + + let alice_public = PublicKey::from(&alice_private); + let bob_public = PublicKey::from(&bob_private); + + println!("Alice's public key: {}", encode(&alice_public.0.to_bytes())); + println!("Bob's public key: {}", encode(&bob_public.0.to_bytes())); + + let left_secret = alice_private.create_secret(&bob_public); + let right_secret = bob_private.create_secret(&alice_public); + + println!("Left shared secret: {}", encode(left_secret.0)); + println!("Right shared secret: {}", encode(right_secret.0)); + + // assert!(left_secret == right_secret); + } + + #[test] + fn hmac() { + // Test using a group secret + let group_secret = SharedSecret::new_from_group_secret(Bytes::copy_from_slice(&decode("8b3387e9c5cdea6ac9e5edbaa115cd72").unwrap())); + let test_group_secret =SharedSecret::from_str("8b3387e9c5cdea6ac9e5edbaa115cd7200000000000000000000000000000000").unwrap(); + assert!(group_secret == test_group_secret); + + // Test using the secret and ciphertext to make a MAC and ensure it matches an example + let sample_data = Bytes::copy_from_slice(&decode("150011C3C1354D619BAE9590E4D177DB7EEAF982F5BDCF78005D75157D9535FA90178F785D").unwrap()); + let mac = group_secret.get_hmac(sample_data).unwrap(); + // assert!(0xC3C1 == mac); + + + } }
\ No newline at end of file diff --git a/src/packet.rs b/src/packet.rs index 4325e10..090b3ec 100644 --- a/src/packet.rs +++ b/src/packet.rs @@ -18,6 +18,127 @@ struct Packet { incomplete: bool } +impl FromStr for Packet { + type Err = hex::FromHexError; + + fn from_str(hex_str: &str) -> Result<Self, Self::Err> { + let hex = decode(hex_str)?; + Ok(Packet::from(Bytes::copy_from_slice(&hex))) + } +} + +impl From<Bytes> for Packet { + fn from(bytes_in: Bytes) -> Self { + let mut bytes = bytes_in; + + // This is the packet we'll build as we begin to parse + let mut packet = Packet::default(); + + if bytes.len() < 2 { + return packet + } + + let header = bytes.get_u8(); + + // Parse the header byte + packet.route_type = RouteType::from(header); + packet.version = PayloadVersion::from(header); + + // Get the transport if it's provided + packet.transport = match packet.route_type { + RouteType::TransportFlood | RouteType::TransportDirect => { + // The packet isn't long enough to contain the transport + if bytes.len() < 4 { return packet; } + + let t1 = bytes.get_u16_le(); + let t2 = bytes.get_u16_le(); + [t1, t2] + }, + _ => { + [0, 0] + } + }; + + // Get the route + if bytes.is_empty() { return packet; } + let path_length = bytes.get_u8() as usize; + + packet.path = match packet.version { + PayloadVersion::VersionOne => { + // The packet isn't long enough for the indicated route + if bytes.len() < path_length { return packet; } + + let route: Vec<u16> = bytes + .split_to(path_length) + .into_iter() + .map(|x| x as u16) + .collect(); + route + }, + PayloadVersion::VersionTwo => { + // The packet isn't long enough for the indicated route + if bytes.len() < path_length * 2 { return packet; } + + let route: Vec<u16> = bytes + .split_to(path_length * 2) + .chunks(2) + .map(|c| Bytes::copy_from_slice(c).get_u16_le()) + .collect(); + + route + }, + _ => { + return packet; + } + }; + + // Get the rest of the payload and pass the parsing to the other structs + packet.raw_content = bytes.clone(); + + packet.content = PacketContent::new(header, bytes); + + // Unfortunately, the trace packet is a bit weird. + // So, if this is a trace packet, move things back into the right place. + if let PacketContent::Trace(trace) = packet.content { + let mut trace = trace; + let path_snr = packet.path; + packet.path = trace.temp_path.iter().map(|i| *i as u16).collect(); + trace.path_snr = path_snr.iter().map( |u| { + let i = *u as i8; + let f = i as f32; + f / 4.0 + }).collect(); + trace.temp_path = vec![]; + packet.content = PacketContent::Trace(trace); + } + + // Mark the packet as complete and valid + packet.incomplete = false; + + packet + } +} + +impl Default for Packet { + fn default() -> Self { + Packet { + route_type: RouteType::Invalid, + version: PayloadVersion::Invalid, + path: vec![], + transport: [0, 0], + raw_content: Bytes::new(), + content: PacketContent::Invalid, + incomplete: true + } + } +} + +impl Packet { + fn try_decrypt(&mut self, key: SharedSecret) -> bool { + false + } +} + #[derive(PartialEq, Debug, Clone)] enum RouteType { TransportFlood, @@ -158,12 +279,6 @@ impl From<Bytes> for PeerToPeerCipher { } } -impl PeerToPeerCipher { - pub fn attempt_decrypt(&mut self, secret: SharedSecret) -> Option<(Bytes, PublicKey, PrivateKey)> { - None - } -} - #[derive(PartialEq, Debug, Clone)] struct Request { cipher: PeerToPeerCipher, @@ -307,17 +422,26 @@ impl From<Bytes> for Advert { } } -#[derive(PartialEq, Debug, Clone)] +#[derive(PartialEq, Debug, Clone, Difference)] +#[difference(expose)] struct GroupText { hash: u8, mac: u16, ciphertext: Bytes, - cleartext: Option<String>, + cleartext: Option<ClearGroupText>, incomplete: bool } +#[derive(PartialEq, Debug, Clone, Difference)] +#[difference(expose)] +struct ClearGroupText { + sender: String, + message: String, + timestamp: DateTime<Utc> +} + impl From<Bytes> for GroupText { fn from(value: Bytes) -> Self { let mut bytes = value; @@ -455,120 +579,6 @@ struct Raw { bytes: Bytes } -impl FromStr for Packet { - type Err = hex::FromHexError; - - fn from_str(hex_str: &str) -> Result<Self, Self::Err> { - let hex = decode(hex_str)?; - Ok(Packet::from(Bytes::copy_from_slice(&hex))) - } -} - -impl From<Bytes> for Packet { - fn from(bytes_in: Bytes) -> Self { - let mut bytes = bytes_in; - - // This is the packet we'll build as we begin to parse - let mut packet = Packet::default(); - - if bytes.len() < 2 { - return packet - } - - let header = bytes.get_u8(); - - // Parse the header byte - packet.route_type = RouteType::from(header); - packet.version = PayloadVersion::from(header); - - // Get the transport if it's provided - packet.transport = match packet.route_type { - RouteType::TransportFlood | RouteType::TransportDirect => { - // The packet isn't long enough to contain the transport - if bytes.len() < 4 { return packet; } - - let t1 = bytes.get_u16_le(); - let t2 = bytes.get_u16_le(); - [t1, t2] - }, - _ => { - [0, 0] - } - }; - - // Get the route - if bytes.is_empty() { return packet; } - let path_length = bytes.get_u8() as usize; - - packet.path = match packet.version { - PayloadVersion::VersionOne => { - // The packet isn't long enough for the indicated route - if bytes.len() < path_length { return packet; } - - let route: Vec<u16> = bytes - .split_to(path_length) - .into_iter() - .map(|x| x as u16) - .collect(); - route - }, - PayloadVersion::VersionTwo => { - // The packet isn't long enough for the indicated route - if bytes.len() < path_length * 2 { return packet; } - - let route: Vec<u16> = bytes - .split_to(path_length * 2) - .chunks(2) - .map(|c| Bytes::copy_from_slice(c).get_u16_le()) - .collect(); - - route - }, - _ => { - return packet; - } - }; - - // Get the rest of the payload and pass the parsing to the other structs - packet.raw_content = bytes.clone(); - - packet.content = PacketContent::new(header, bytes); - - // Unfortunately, the trace packet is a bit weird. - // So, if this is a trace packet, move things back into the right place. - if let PacketContent::Trace(trace) = packet.content { - let mut trace = trace; - let path_snr = packet.path; - packet.path = trace.temp_path.iter().map(|i| *i as u16).collect(); - trace.path_snr = path_snr.iter().map( |u| { - let i = *u as i8; - let f = i as f32; - f / 4.0 - }).collect(); - trace.temp_path = vec![]; - packet.content = PacketContent::Trace(trace); - } - - // Mark the packet as complete and valid - packet.incomplete = false; - - packet - } -} - -impl Default for Packet { - fn default() -> Self { - Packet { - route_type: RouteType::Invalid, - version: PayloadVersion::Invalid, - path: vec![], - transport: [0, 0], - raw_content: Bytes::new(), - content: PacketContent::Invalid, - incomplete: true - } - } -} #[cfg(test)] mod tests { @@ -879,6 +889,36 @@ mod tests { } #[test] + fn group_text_decrypted() { + let sample = "150011C3C1354D619BAE9590E4D177DB7EEAF982F5BDCF78005D75157D9535FA90178F785D"; + + let lhs_packet = Packet { + route_type: RouteType::Flood, + version: PayloadVersion::VersionOne, + path: vec![], + transport: [0, 0], + raw_content: Bytes::copy_from_slice(&decode("11C3C1354D619BAE9590E4D177DB7EEAF982F5BDCF78005D75157D9535FA90178F785D").unwrap()), + content: PacketContent::GroupText(GroupText { + hash: 0x11, + mac: 0xC3C1, + ciphertext: Bytes::copy_from_slice(&decode("354D619BAE9590E4D177DB7EEAF982F5BDCF78005D75157D9535FA90178F785D").unwrap()), + cleartext: Some(ClearGroupText { + sender: "🌲 Tree".to_owned(), + message: "☁️".to_owned(), + timestamp: DateTime::from_timestamp_secs(1758484279).unwrap() + }), + incomplete: false + }), + incomplete: false + }; + + let mut rhs_packet = Packet::from_str(sample).unwrap(); + _ = rhs_packet.try_decrypt(SharedSecret::new_from_group_secret(Bytes::copy_from_slice(&decode("8b3387e9c5cdea6ac9e5edbaa115cd72").unwrap()))); + + // assert!(lhs_packet == rhs_packet, "{}", print_compare(lhs_packet, rhs_packet)); + } + + #[test] fn multipart() { let sample = "2A0013E8C59624"; |
