//! # Isabelle's Lazy Message Protocol //! //! ### network packet protocol //! //! I don't know whether or not this is a super practical way of doing things //! but i'm lazy and it seems to work so gonna roll with it lol //! //! | segment size | usage | //! |--------------|--------------------------------------------| //! | 1 byte | u8 packet kind | //! | 1 byte | u8 encrypt kind | //! | 8 byte | u64 length of the packet contents | //! | 4 byte | CRC32 packet contents checksum | //! | 32 byte | SHA256 packet contents integrity check | //! | `u64::MAX` | packet contents | //! mod message; pub use message::Message; mod agreement; pub use agreement::Agreement; /// encryption types and functions pub mod encrypt; use encrypt::{EncryptKind, Encryption}; use futures_util::io::{AsyncReadExt, AsyncWriteExt}; use orion::aead; use ring::digest; use std::convert::TryInto; use std::marker::Unpin; use thiserror::Error; pub type Result = std::result::Result; struct NetworkPacket(Vec); /// a type of data that can be sent pub trait Sendable: Sized { fn to_packet(&self, encrypt_kind: EncryptKind) -> Result; fn from_packet(packet: Packet) -> Result; } /// data to be sent #[derive(Debug)] pub struct Packet { pub kind: PacketKind, pub encrypt_kind: EncryptKind, pub integrity_hash: Vec, pub contents: Vec, } impl Packet { /// create a new `Packet` pub fn new(kind: PacketKind, contents: Vec, encrypt_kind: EncryptKind) -> Packet { let integrity_hash = digest::digest(&digest::SHA256, &contents).as_ref().to_vec(); Packet { kind, integrity_hash, contents, encrypt_kind, } } // generate a checksum from the packet fn generate_checksum(&self) -> u32 { // combine integrity hash and contents let mut hash_and_contents = self.integrity_hash.clone(); hash_and_contents.extend_from_slice(&self.contents); // generate checksum let mut hasher = crc32fast::Hasher::new(); hasher.update(hash_and_contents.as_ref()); hasher.finalize() } fn to_network_packet(&self) -> NetworkPacket { let mut contents: Vec = Vec::new(); // write packet kind byte contents.push(self.kind as u8); // write encrypt kind byte contents.push(self.encrypt_kind as u8); // write the packet length let contents_length = self.contents.len() as u64; contents.extend_from_slice(&contents_length.to_le_bytes()); // write checksum let checksum = self.generate_checksum(); contents.extend_from_slice(&checksum.to_le_bytes()); // write hash and contents contents.extend_from_slice(&self.integrity_hash); contents.extend_from_slice(&self.contents); NetworkPacket(contents) } /// verifies SHA256 integrity pub fn verify_integrity(&self) -> Result<()> { let expected = digest::digest(&digest::SHA256, &self.contents) .as_ref() .to_vec(); if expected == self.integrity_hash { Ok(()) } else { println!("bad integrity"); Err(IlmpError::BadHashIntegrity { found: self.integrity_hash.clone(), expected, } .into()) } } /// verifies CRC32 checksum pub fn verify_checksum(&self, expected: u32) -> Result<()> { let found = self.generate_checksum(); if found == expected { Ok(()) } else { println!("bad checksum"); Err(IlmpError::BadChecksumIntegrity { expected, found }) } } } /// kinds of packets that can be sent #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[repr(u8)] pub enum PacketKind { Message = 0x00, Agreement = 0xff, } impl PacketKind { /// returns `Option given valid matching variant pub fn from_u8(kind: u8) -> Option { match kind { 0x00 => Some(PacketKind::Message), 0xff => Some(PacketKind::Agreement), _ => None, } } } /// ilmp's error type #[derive(Error, Debug)] pub enum IlmpError { #[error("checksum integrity check failed: (expected {expected:?} found {found:?})")] BadChecksumIntegrity { expected: u32, found: u32 }, #[error("hash integrity check failed: (expected {expected:?} found {found:?})")] BadHashIntegrity { expected: Vec, found: Vec }, // external error conversions #[error("std::io error")] StdIo(#[from] std::io::Error), #[error("serde_json error")] SerdeJson(#[from] serde_json::error::Error), #[error("string parsing error")] StringParse(#[from] std::string::FromUtf8Error), #[error("orion error")] Orion(#[from] orion::errors::UnknownCryptoError), } /// reads a `Packet` from a stream /// /// if `Ok(None)` is returned the stream has been disconnected. pub async fn read(stream: &mut S) -> Result> where S: AsyncReadExt + Unpin, { let mut info_buf = [0u8; 14]; let check = stream.read(&mut info_buf).await?; if check == 0 { return Ok(None); } let kind = PacketKind::from_u8(info_buf[0]).unwrap(); let encrypt_kind = EncryptKind::from_u8(info_buf[1]).unwrap(); let length = u64::from_le_bytes(info_buf[2..10].try_into().unwrap()) as usize; let checksum = u32::from_le_bytes(info_buf[10..14].try_into().unwrap()); let mut integrity_hash: Vec = vec![0; 32]; stream.read(&mut integrity_hash).await?; let mut contents: Vec = vec![0; length]; stream.read(&mut contents).await?; let packet = Packet { kind, contents, integrity_hash, encrypt_kind, }; packet.verify_checksum(checksum)?; packet.verify_integrity()?; Ok(Some(packet)) } /// writes a `Sendable` packet to a stream pub async fn write(stream: &mut S, packet: P, encryption: &E) -> Result<()> where S: AsyncWriteExt + Unpin, P: Sendable, E: Encryption, { match encryption.kind() { EncryptKind::None => { let network_packet = packet.to_packet(encryption.kind())?.to_network_packet(); stream.write(&network_packet.0).await?; Ok(()) } EncryptKind::Symmetric => { let mut packet = packet.to_packet(encryption.kind())?; packet.contents = aead::seal(encryption.key().unwrap(), &packet.contents)?; packet.integrity_hash = digest::digest(&digest::SHA256, &packet.contents) .as_ref() .to_vec(); let network_packet = packet.to_network_packet(); stream.write(&network_packet.0).await?; Ok(()) } } }