aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorWill Dillon <william@housedillon.com>2025-11-07 23:19:11 +0000
committerWill Dillon <william@housedillon.com>2025-11-07 23:19:11 +0000
commit5fc50c5f6e3295c5480e4f576fe2415e0c3fc96d (patch)
tree01ed06fb46efa750eb27d894fef43ca1dab8ac63 /src
parentSwitching to Mac (diff)
downloadmeshcore-rs-5fc50c5f6e3295c5480e4f576fe2415e0c3fc96d.tar.gz
meshcore-rs-5fc50c5f6e3295c5480e4f576fe2415e0c3fc96d.zip
Back over to the laptop
Diffstat (limited to 'src')
-rw-r--r--src/bin/packet_analyzer.rs36
-rw-r--r--src/crypto.rs35
-rw-r--r--src/identity.rs103
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
+}
+