diff options
| author | Will Dillon <william@housedillon.com> | 2025-11-05 21:23:17 +0000 |
|---|---|---|
| committer | Will Dillon <william@housedillon.com> | 2025-11-05 21:23:17 +0000 |
| commit | 7b79979ea4ba2968fad469174c3d9138e8a4a688 (patch) | |
| tree | 189c9a6af24e3c5b3e42c629310015df40eff015 | |
| download | meshcore-rs-7b79979ea4ba2968fad469174c3d9138e8a4a688.tar.gz meshcore-rs-7b79979ea4ba2968fad469174c3d9138e8a4a688.zip | |
Initial commit
| -rw-r--r-- | .gitignore | 1 | ||||
| -rw-r--r-- | Cargo.lock | 424 | ||||
| -rw-r--r-- | Cargo.toml | 11 | ||||
| -rw-r--r-- | src/crypto.rs | 58 | ||||
| -rw-r--r-- | src/lib.rs | 3 | ||||
| -rw-r--r-- | src/packet.rs | 259 |
6 files changed, 756 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..fc8cc6c --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,424 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "base64ct" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bytes" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "digest", + "fiat-crypto", + "group", + "rand_core", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid", + "zeroize", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "ed25519" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +dependencies = [ + "pkcs8", + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" +dependencies = [ + "curve25519-dalek", + "ed25519", + "serde", + "sha2", + "subtle", + "zeroize", +] + +[[package]] +name = "ff" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" +dependencies = [ + "rand_core", + "subtle", +] + +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "generic-array" +version = "0.14.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core", + "subtle", +] + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "libc" +version = "0.2.177" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" + +[[package]] +name = "meshcore" +version = "0.1.0" +dependencies = [ + "curve25519-dalek", + "ed25519-dalek", + "hex", + "tokio-util", + "x25519-dalek", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "proc-macro2" +version = "1.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "rand_core", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da58917d35242480a05c2897064da0a80589a2a0476c9a3f2fdc83b53502e917" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tokio" +version = "1.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" +dependencies = [ + "pin-project-lite", +] + +[[package]] +name = "tokio-util" +version = "0.7.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2efa149fe76073d6e8fd97ef4f4eca7b67f599660115591483572e406e165594" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + +[[package]] +name = "unicode-ident" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "x25519-dalek" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277" +dependencies = [ + "curve25519-dalek", + "rand_core", + "serde", + "zeroize", +] + +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..4deb83a --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "meshcore" +version = "0.1.0" +edition = "2024" + +[dependencies] +ed25519-dalek = { version = "2.2.0", features = ["hazmat", "pkcs8"] } +curve25519-dalek = { version = "4.1.3", features = ["group"] } +x25519-dalek = { version = "*", features = ["static_secrets"] } +hex = "0.4" +tokio-util = "*"
\ No newline at end of file diff --git a/src/crypto.rs b/src/crypto.rs new file mode 100644 index 0000000..adcd4d8 --- /dev/null +++ b/src/crypto.rs @@ -0,0 +1,58 @@ +use std::{collections::HashMap, str::FromStr}; + +use ed25519_dalek::hazmat::ExpandedSecretKey; +use curve25519_dalek::{constants, edwards::CompressedEdwardsY}; +use tokio_util::bytes::Bytes; +use x25519_dalek::StaticSecret; + +use hex::decode; + +pub struct PrivateKey(ExpandedSecretKey); +pub struct PublicKey(CompressedEdwardsY); +pub struct SharedSecret([u8; 32]); + +impl FromStr for PrivateKey { + type Err = hex::FromHexError; + + fn from_str(hex_str: &str) -> Result<Self, Self::Err> { + let hex = decode(hex_str)?; + + if let Ok(bytes) = TryInto::<[u8; 64]>::try_into(hex) { + let exp_sec_key = ExpandedSecretKey::from_bytes(&bytes); + + Ok(PrivateKey(exp_sec_key)) + } else { + Err(hex::FromHexError::InvalidStringLength) + } + } +} + +impl From<PrivateKey> for PublicKey { + fn from(key: PrivateKey) -> Self { + PublicKey((key.0.scalar * constants::ED25519_BASEPOINT_POINT).compress()) + } +} + +impl PrivateKey { + pub fn create_secret(&self, other: &PublicKey) -> SharedSecret { + let private = StaticSecret::from(*self.0.scalar.as_bytes()); + SharedSecret(private.diffie_hellman(&other.0.to_bytes().into()).to_bytes()) + } +} + +impl SharedSecret { + pub fn mac_then_decrypt(&self, mac: [u8; 2], data: Bytes) -> Option<Bytes> { + None + } + + pub fn encrypt_then_mac(&self, data: Bytes) -> Option<([u8; 2], Bytes)> { + None + } +} + +pub struct Keychain { + pub private_keys: Vec<PrivateKey>, + pub public_keys: Vec<PublicKey>, + + // secrets: HashMap<String, Ha> +}
\ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..817b464 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,3 @@ +pub mod crypto; +pub mod packet; + diff --git a/src/packet.rs b/src/packet.rs new file mode 100644 index 0000000..d44aa66 --- /dev/null +++ b/src/packet.rs @@ -0,0 +1,259 @@ +use std::str::FromStr; + +use hex::decode; +use tokio_util::bytes::{Buf, Bytes}; + +use crate::{crypto::SharedSecret, packet}; + +#[derive(PartialEq, Debug)] +struct Packet { + route_type: RouteType, + version: PayloadVersion, + path: Vec<u16>, + transport: [u16; 2], + raw_content: Bytes, + content: PacketContent, + incomplete: bool +} + +#[derive(PartialEq, Debug)] +enum RouteType { + TransportFlood, + Flood, + Direct, + TransportDirect, + Invalid, +} + +impl From<u8> for RouteType { + fn from(value: u8) -> Self { + match value & 0x03 { + 0x00 => RouteType::TransportFlood, + 0x01 => RouteType::Flood, + 0x02 => RouteType::Direct, + 0x03 => RouteType::TransportDirect, + _ => RouteType::Invalid + } + } +} + +#[derive(PartialEq, Debug)] +enum PayloadVersion { + VersionOne, + VersionTwo, + VersionThree, + VersionFour, + Invalid, +} + +impl From<u8> for PayloadVersion { + fn from(value: u8) -> Self { + let value = (value & 0xC0) >> 6; + assert!(value < 4, "Programming error in masking and bitshifting"); + + match value { + 0x00 => PayloadVersion::VersionOne, + 0x01 => PayloadVersion::VersionTwo, + 0x02 => PayloadVersion::VersionThree, + 0x03 => PayloadVersion::VersionFour, + _ => PayloadVersion::Invalid + } + } +} + +#[derive(PartialEq, Debug)] +enum PacketContent { + Request(Request), + Response(Response), + Text(Text), + Ack(Ack), + Advert(Advert), + GroupText(GroupText), + GroupData(GroupData), + AnonReq(AnonReq), + Path(Path), + Trace(Trace), + Multipart(MultiPart), + Raw(Raw), + + Invalid, +} + +#[derive(PartialEq, Debug)] +struct Request {} + +#[derive(PartialEq, Debug)] +struct Response {} + +#[derive(PartialEq, Debug)] +struct Text {} + +#[derive(PartialEq, Debug)] +struct Ack {} + +#[derive(PartialEq, Debug)] +struct Advert {} + +#[derive(PartialEq, Debug)] +struct GroupText {} + +#[derive(PartialEq, Debug)] +struct GroupData {} + +#[derive(PartialEq, Debug)] +struct AnonReq {} + +#[derive(PartialEq, Debug)] +struct Path {} + +#[derive(PartialEq, Debug)] +struct Trace {} + +#[derive(PartialEq, Debug)] +struct MultiPart {} + +#[derive(PartialEq, Debug)] +struct Raw {} + +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: Bytes) -> Self { + let mut bytes = bytes; + + // This is the packet we'll build as we begin to parse + let mut packet = Packet::default(); + + if bytes.len() == 0 { + return packet + } + + let header = bytes.split_to(0).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; } + + [bytes.split_to(2).get_u16(), bytes.split_to(2).get_u16()] + }, + _ => { + [0, 0] + } + }; + + // Get the route + if bytes.len() == 0 { return packet; } + let path_length = bytes.split_to(1).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 as usize * 2 { return packet; } + + let route: Vec<u16> = bytes + .split_to(path_length * 2) + .chunks(2) + .map(|c| Bytes::copy_from_slice(c).get_u16()) + .collect(); + + route + }, + _ => { + return packet; + } + }; + + // Get the rest of the payload and subscript the parsing to the other structs + packet.raw_content = bytes; + + // Mark the packet as complete and valid + packet.incomplete = false; + + packet + } +} + +impl Packet { + pub fn attempt_decrypt(&mut self, secret: SharedSecret) -> bool { + false + } + +} + +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 { + use super::*; + + #[test] + fn header_route_type() { + // The route type is the lowest-order two bits + assert!(RouteType::TransportFlood == RouteType::from(0x00)); + assert!(RouteType::TransportFlood == RouteType::from(0xFC)); + assert!(RouteType::Flood == RouteType::from(0x01)); + assert!(RouteType::Flood == RouteType::from(0xFD)); + assert!(RouteType::Direct == RouteType::from(0x02)); + assert!(RouteType::Direct == RouteType::from(0xFE)); + assert!(RouteType::TransportDirect == RouteType::from(0x03)); + assert!(RouteType::TransportDirect == RouteType::from(0xFF)); + } + + #[test] + fn header_version() { + assert!(PayloadVersion::VersionOne == PayloadVersion::from(0x00)); + assert!(PayloadVersion::VersionOne == PayloadVersion::from(0x3F)); + assert!(PayloadVersion::VersionTwo == PayloadVersion::from(0x40)); + assert!(PayloadVersion::VersionTwo == PayloadVersion::from(0x7F)); + assert!(PayloadVersion::VersionThree == PayloadVersion::from(0x80)); + assert!(PayloadVersion::VersionThree == PayloadVersion::from(0xBF)); + assert!(PayloadVersion::VersionFour == PayloadVersion::from(0xC0)); + assert!(PayloadVersion::VersionFour == PayloadVersion::from(0xFF)); + } + + #[test] + fn packet() { + // Check the hex decode errors + assert!(Err(hex::FromHexError::InvalidHexCharacter { c: 's', index: 0 }) == Packet::from_str("s0")); + assert!(Err(hex::FromHexError::OddLength) == Packet::from_str("0")); + + // Check errors related to packet length issues + assert!(Packet::default() == Packet::from_str("").unwrap()) + } +}
\ No newline at end of file |
