diff options
| author | Will Dillon <william@housedillon.com> | 2025-11-07 23:19:11 +0000 |
|---|---|---|
| committer | Will Dillon <william@housedillon.com> | 2025-11-07 23:19:11 +0000 |
| commit | 5fc50c5f6e3295c5480e4f576fe2415e0c3fc96d (patch) | |
| tree | 01ed06fb46efa750eb27d894fef43ca1dab8ac63 /src | |
| parent | Switching to Mac (diff) | |
| download | meshcore-rs-5fc50c5f6e3295c5480e4f576fe2415e0c3fc96d.tar.gz meshcore-rs-5fc50c5f6e3295c5480e4f576fe2415e0c3fc96d.zip | |
Back over to the laptop
Diffstat (limited to 'src')
| -rw-r--r-- | src/bin/packet_analyzer.rs | 36 | ||||
| -rw-r--r-- | src/crypto.rs | 35 | ||||
| -rw-r--r-- | src/identity.rs | 103 |
3 files changed, 149 insertions, 25 deletions
diff --git a/src/bin/packet_analyzer.rs b/src/bin/packet_analyzer.rs new file mode 100644 index 0000000..ee3c3fe --- /dev/null +++ b/src/bin/packet_analyzer.rs @@ -0,0 +1,36 @@ +use std::path::PathBuf; +use tokio::fs::File; + +use clap::Parser; +use color_eyre::eyre::Result; +use meshcore::{crypto::Keychain, identity::KeystoreInput}; +use pretty_env_logger; +use pcap_file_tokio::pcapng::{Block::*, PcapNgReader, PcapNgWriter, blocks::interface_description::InterfaceDescriptionBlock}; + +#[derive(Parser)] +struct AnalyzerArguments { + #[arg(long, short, help = "Identities file for packet decryption")] + identities_file: PathBuf, + + #[arg(long, short, help = "Pcapng file to input packets for analysis")] + pcap_file: PathBuf, +} + +#[tokio::main] +async fn main() -> Result<()> { + pretty_env_logger::init(); + + let args = AnalyzerArguments::parse(); + + // Attempt to load the identities file from disk and load all the identities + let identity_string = std::fs::read_to_string(args.identities_file)?; + let keystore_in: KeystoreInput = toml::from_str(&identity_string)?; + let keystore = keystore_in.compile(); + + // Pcapng file for loading packets + let pcap_file = File::open(args.pcap_file).await?; + let pcap_reader = PcapNgReader::new(pcap_file).await?; + + + Ok(()) +}
\ No newline at end of file diff --git a/src/crypto.rs b/src/crypto.rs index d428c20..8e3afa7 100644 --- a/src/crypto.rs +++ b/src/crypto.rs @@ -1,6 +1,5 @@ use std::{fmt::{Debug, Display}, str::FromStr}; use ed25519_dalek::{VerifyingKey, hazmat::ExpandedSecretKey}; -use serde::Deserialize; use tokio_util::bytes::{Buf, BufMut, Bytes, BytesMut}; use hex::{decode, decode_to_slice, encode}; @@ -53,8 +52,29 @@ impl Clone for PrivateKey { } } +impl Default for PrivateKey { + fn default() -> Self { + // To make a key whole-cloth, we need to start with a SigningKey made + // using a good RNG. Then we can use that to make an ExpandedSecretKey. + use rand::rngs::OsRng; + use ed25519_dalek::SigningKey; + + // This seems like the same sequence of steps that's used with ed25519 itself. + // I have to copy-pasta it because I don't see a way to do it directly with + // thei API. In the docs the give this example for creating a signing key + let mut csprng = OsRng; + let signing_key: SigningKey = SigningKey::generate(&mut csprng); + // Then, there are only a few constructors for making an ExpandedSecretKey. + // Meshcore uses this kind of key, so it's what we need in this application, + // but it's an uncommon formulation. + let esk = ExpandedSecretKey::from(&signing_key.to_bytes()); + Self(esk) + } +} + #[derive(PartialEq, Clone)] pub struct PublicKey(ed25519_dalek::VerifyingKey); + impl std::fmt::Debug for PublicKey { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_tuple("PublicKey").field(&encode(self.0.as_bytes())).finish() @@ -207,18 +227,27 @@ impl Default for PublicKey { } } +impl TryFrom<&[u8]> for PublicKey { + type Error = MeshcoreCryptoError; + + fn try_from(value: &[u8]) -> Result<Self, Self::Error> { + let bytes = Bytes::copy_from_slice(value); + Self::try_from(bytes) + } +} + impl TryFrom<Bytes> for PublicKey { type Error = MeshcoreCryptoError; fn try_from(mut value: Bytes) -> Result<Self, Self::Error> { if value.len() < 32 { - return Err(MeshcoreCryptoError::TryFromSliceError) + return Err(MeshcoreCryptoError::KeyLengthError) } let mut slice = [0_u8; 32]; if value.try_copy_to_slice(&mut slice).is_err() { - return Err(MeshcoreCryptoError::TryFromSliceError) + return Err(MeshcoreCryptoError::KeyLengthError) } if let Ok(key) = VerifyingKey::from_bytes(&slice) { diff --git a/src/identity.rs b/src/identity.rs index 5737ec0..050bfa1 100644 --- a/src/identity.rs +++ b/src/identity.rs @@ -1,9 +1,7 @@ use std::{collections::HashMap, str::FromStr}; - +use hex::decode; use serde::{Deserialize, de}; -use x25519_dalek::PublicKey; - -use crate::crypto::{MeshcoreCryptoError, PrivateKey, SharedSecret}; +use crate::crypto::{MeshcoreCryptoError, PrivateKey, PublicKey, SharedSecret}; #[derive(PartialEq, Debug, Clone, Deserialize)] /// The Identity structure contains the information to decrypt @@ -19,13 +17,13 @@ pub struct Identity { /// no ways in this library to generate a new private key /// from scratch (while ensuing it's sound) so it must be /// provided by the user. - #[serde(deserialize_with = "private_key")] + #[serde(deserialize_with = "deserialize_private_key")] pub private_key: PrivateKey, /// The derived public key given this identity's /// private key. The hash prefix of this (typically 1-byte) /// is used as a first-pass to identity the recipient. - #[serde(deserialize_with = "public_key")] + #[serde(skip)] pub public_key: PublicKey, /// The secrets has is a collection of shared secrets @@ -37,8 +35,7 @@ pub struct Identity { pub secrets: HashMap<String, SharedSecret> } -#[derive(PartialEq, Debug, Clone)] - +#[derive(PartialEq, Debug, Clone, Deserialize)] /// The Contact structure contains the information needed /// to decrypt messages from a remote user intended for /// either an identity or a channel. @@ -53,10 +50,11 @@ pub struct Contact { pub name: String, /// The provided public key of the remote contact + #[serde(deserialize_with = "deserialize_public_key")] pub public_key: PublicKey } -#[derive(PartialEq, Debug, Clone)] +#[derive(PartialEq, Debug, Clone, Deserialize)] /// A Group in MeshCore is a kind of contact, except that its /// secret is fixed and shared directly. It's not derived via /// a public and private key. Otherwise, it behaves more like a @@ -67,34 +65,95 @@ pub struct Group { pub name: String, /// The group's shared secret + #[serde(deserialize_with = "deserialize_secret")] pub secret: SharedSecret } +#[derive(PartialEq, Debug, Clone)] pub struct Keystore { identities: HashMap<String, Identity>, contacts: HashMap<String, Contact>, groups: HashMap<String, Group>, +} +#[derive(PartialEq, Debug, Clone, Deserialize)] +pub struct KeystoreInput { + identities: Vec<Identity>, + contacts: Vec<Contact>, + groups: Vec<Group> } -impl Identity { - fn private_key<'de, D>(deserializer: D) -> Result<PrivateKey, D::Error> +impl KeystoreInput { + pub fn compile(self) -> Keystore { + let mut retval = Keystore { + identities: todo!(), + contacts: todo!(), + groups: todo!() + }; + + // Iterate through the input keystore file + // for each one, make sure it's in the map correctly + // and use the opportunity to compute the shared keys. + for identity in self.identities.iter_mut() { + identity.public_key = PublicKey::from(identity.public_key); + + // Iterate through the contacts and generate shared secrets + for contact in self.contacts.as_i + } + + retval + } +} + +fn deserialize_private_key<'de, D>(deserializer: D) -> Result<PrivateKey, D::Error> where D: de::Deserializer<'de> { - let s: &str = de::Deserialize::deserialize(deserializer)?; - let key = PrivateKey::from_str(s).map_err(de::Error::custom)? + let s: &str = de::Deserialize::deserialize(deserializer)?; + match PrivateKey::from_str(s) { + Ok(key) => Ok(key), + Err(e) => { + Err(de::Error::custom(format!("{}", e))) + } } } -impl<'de> Deserialize<'de> for Contact { - fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> - where D: serde::Deserializer<'de> { - todo!() +fn deserialize_public_key<'de, D>(deserializer: D) -> Result<PublicKey, D::Error> + where D: de::Deserializer<'de> { + let s: &str = de::Deserialize::deserialize(deserializer)?; + + match decode(s) { + Err(hex::FromHexError::InvalidHexCharacter { c, index }) => { + Err(de::Error::custom(format!("Invalid hex character {} at {} when decoding Public Key.", c, index))) + }, + Err(hex::FromHexError::OddLength) => { + Err(de::Error::custom(format!("Odd number of characters when decoding Public Key."))) + }, + Err(hex::FromHexError::InvalidStringLength) => { + Err(de::Error::custom(format!("Invalid hex string length when decoding Public Key."))) + } + Ok(hex) => { + let key = PublicKey::try_from(hex.as_slice()); + match key { + Ok(key) => Ok(key), + Err(MeshcoreCryptoError::KeyCreationError) => { + Err(de::Error::custom(format!("Unable to decompress public key points"))) + } + Err(_) => { + Err(de::Error::custom(format!("Invalid hex string length when decoding Public Key."))) + } + } + } } } -impl<'de> Deserialize<'de> for Group { - fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> - where D: serde::Deserializer<'de> { - todo!() +fn deserialize_secret<'de, D>(deserializer: D) -> Result<SharedSecret, D::Error> + where D: de::Deserializer<'de> { + let s: &str = de::Deserialize::deserialize(deserializer)?; + + match SharedSecret::from_str(s) { + Ok(secret) => Ok(secret), + Err(e) => { + Err(de::Error::custom(format!("{}", e))) + } } -}
\ No newline at end of file +} + |
