From cd7be0ff69c274c203547b2f77fc7c7906c40003 Mon Sep 17 00:00:00 2001 From: Luca Fulchir Date: Mon, 19 Jun 2023 18:37:28 +0200 Subject: [PATCH 1/3] Stream stubs, start using namespaces as intended Signed-off-by: Luca Fulchir --- flake.lock | 24 ++-- src/connection/handshake/tracker.rs | 12 +- src/connection/mod.rs | 118 +++++++++++++++--- src/connection/packet.rs | 122 ++++--------------- src/connection/stream/errors.rs | 10 ++ src/connection/stream/mod.rs | 183 ++++++++++++++++++++++++++++ src/connection/stream/rob.rs | 29 +++++ src/inner/worker.rs | 28 +++-- src/lib.rs | 8 +- 9 files changed, 382 insertions(+), 152 deletions(-) create mode 100644 src/connection/stream/errors.rs create mode 100644 src/connection/stream/mod.rs create mode 100644 src/connection/stream/rob.rs diff --git a/flake.lock b/flake.lock index 7a5770d..8eb84e1 100644 --- a/flake.lock +++ b/flake.lock @@ -5,11 +5,11 @@ "systems": "systems" }, "locked": { - "lastModified": 1681202837, - "narHash": "sha256-H+Rh19JDwRtpVPAWp64F+rlEtxUWBAQW28eAi3SRSzg=", + "lastModified": 1685518550, + "narHash": "sha256-o2d0KcvaXzTrPRIo0kOLV0/QXHhDQ5DTi+OxcjO8xqY=", "owner": "numtide", "repo": "flake-utils", - "rev": "cfacdce06f30d2b68473a46042957675eebb3401", + "rev": "a1720a10a6cfe8234c0e93907ffe81be440f4cef", "type": "github" }, "original": { @@ -38,11 +38,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1684922889, - "narHash": "sha256-l0WZAmln8959O7RdYUJ3gnAIM9OPKFLKHKGX4q+Blrk=", + "lastModified": 1686921029, + "narHash": "sha256-J1bX9plPCFhTSh6E3TWn9XSxggBh/zDD4xigyaIQBy8=", "owner": "nixos", "repo": "nixpkgs", - "rev": "04aaf8511678a0d0f347fdf1e8072fe01e4a509e", + "rev": "c7ff1b9b95620ce8728c0d7bd501c458e6da9e04", "type": "github" }, "original": { @@ -54,11 +54,11 @@ }, "nixpkgs-unstable": { "locked": { - "lastModified": 1684844536, - "narHash": "sha256-M7HhXYVqAuNb25r/d3FOO0z4GxPqDIZp5UjHFbBgw0Q=", + "lastModified": 1686960236, + "narHash": "sha256-AYCC9rXNLpUWzD9hm+askOfpliLEC9kwAo7ITJc4HIw=", "owner": "nixos", "repo": "nixpkgs", - "rev": "d30264c2691128adc261d7c9388033645f0e742b", + "rev": "04af42f3b31dba0ef742d254456dc4c14eedac86", "type": "github" }, "original": { @@ -98,11 +98,11 @@ "nixpkgs": "nixpkgs_2" }, "locked": { - "lastModified": 1684894917, - "narHash": "sha256-kwKCfmliHIxKuIjnM95TRcQxM/4AAEIZ+4A9nDJ6cJs=", + "lastModified": 1687055571, + "narHash": "sha256-UvLoO6u5n9TzY80BpM4DaacxvyJl7u9mm9CA72d309g=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "9ea38d547100edcf0da19aaebbdffa2810585495", + "rev": "2de557c780dcb127128ae987fca9d6c2b0d7dc0f", "type": "github" }, "original": { diff --git a/src/connection/handshake/tracker.rs b/src/connection/handshake/tracker.rs index aaa1163..5da29bb 100644 --- a/src/connection/handshake/tracker.rs +++ b/src/connection/handshake/tracker.rs @@ -5,7 +5,7 @@ use crate::{ connection::{ self, handshake::{self, Error, Handshake}, - Connection, IDRecv, IDSend, + Conn, IDRecv, IDSend, }, enc::{ self, @@ -29,7 +29,7 @@ pub(crate) type ConnectAnswer = Result<(KeyID, IDSend), crate::Error>; pub(crate) struct HandshakeClient { pub service_id: ServiceID, pub service_conn_id: IDRecv, - pub connection: Connection, + pub connection: Conn, pub timeout: Option<::tokio::task::JoinHandle<()>>, pub answer: oneshot::Sender, pub srv_key_id: KeyID, @@ -79,7 +79,7 @@ impl HandshakeClientList { pub_key: PubKey, service_id: ServiceID, service_conn_id: IDRecv, - connection: Connection, + connection: Conn, answer: oneshot::Sender, srv_key_id: KeyID, ) -> Result<(KeyID, &mut HandshakeClient), oneshot::Sender> @@ -144,8 +144,8 @@ pub(crate) struct ClientConnectInfo { pub service_connection_id: IDRecv, /// Parsed handshake packet pub handshake: Handshake, - /// Connection - pub connection: Connection, + /// Conn + pub connection: Conn, /// where to wake up the waiting client pub answer: oneshot::Sender, /// server public key id that we used on the handshake @@ -233,7 +233,7 @@ impl HandshakeTracker { pub_key: PubKey, service_id: ServiceID, service_conn_id: IDRecv, - connection: Connection, + connection: Conn, answer: oneshot::Sender, srv_key_id: KeyID, ) -> Result<(KeyID, &mut HandshakeClient), oneshot::Sender> diff --git a/src/connection/mod.rs b/src/connection/mod.rs index 45a50e9..2889381 100644 --- a/src/connection/mod.rs +++ b/src/connection/mod.rs @@ -3,13 +3,11 @@ pub mod handshake; pub mod packet; pub mod socket; +pub mod stream; use ::std::{rc::Rc, vec::Vec}; -pub use crate::connection::{ - handshake::Handshake, - packet::{ConnectionID as ID, Packet, PacketData}, -}; +pub use crate::connection::{handshake::Handshake, packet::Packet}; use crate::{ dnssec, @@ -21,12 +19,93 @@ use crate::{ }, inner::ThreadTracker, }; +use ::std::rc; + +/// Fenrir Connection ID +/// +/// 0 is special as it represents the handshake +/// Connection IDs are to be considered u64 little endian +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub enum ID { + /// Connection id 0 represent the handshake + Handshake, + /// Non-zero id can represent any connection + ID(::core::num::NonZeroU64), +} + +impl ID { + /// Set the conenction id to handshake + pub fn new_handshake() -> Self { + Self::Handshake + } + /// New id from u64. PLZ NON ZERO + pub(crate) fn new_u64(raw: u64) -> Self { + #[allow(unsafe_code)] + unsafe { + ID::ID(::core::num::NonZeroU64::new_unchecked(raw)) + } + } + pub(crate) fn as_u64(&self) -> u64 { + match self { + ID::Handshake => 0, + ID::ID(id) => id.get(), + } + } + /// New random service ID + pub fn new_rand(rand: &Random) -> Self { + let mut raw = [0; 8]; + let mut num = 0; + while num == 0 { + rand.fill(&mut raw); + num = u64::from_le_bytes(raw); + } + #[allow(unsafe_code)] + unsafe { + ID::ID(::core::num::NonZeroU64::new_unchecked(num)) + } + } + /// Quick check to know if this is an handshake + pub fn is_handshake(&self) -> bool { + *self == ID::Handshake + } + /// length if the connection ID in bytes + pub const fn len() -> usize { + 8 + } + /// write the ID to a buffer + pub fn serialize(&self, out: &mut [u8]) { + match self { + ID::Handshake => out[..8].copy_from_slice(&[0; 8]), + ID::ID(id) => out[..8].copy_from_slice(&id.get().to_le_bytes()), + } + } +} + +impl From for ID { + fn from(raw: u64) -> Self { + if raw == 0 { + ID::Handshake + } else { + #[allow(unsafe_code)] + unsafe { + ID::ID(::core::num::NonZeroU64::new_unchecked(raw)) + } + } + } +} + +impl From<[u8; 8]> for ID { + fn from(raw: [u8; 8]) -> Self { + let raw_u64 = u64::from_le_bytes(raw); + raw_u64.into() + } +} /// strong typedef for receiving connection id -#[derive(Debug, Copy, Clone, PartialEq)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct IDRecv(pub ID); /// strong typedef for sending connection id -#[derive(Debug, Copy, Clone, PartialEq)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct IDSend(pub ID); /// Version of the fenrir protocol in use @@ -47,12 +126,16 @@ impl ProtocolVersion { } } +/// The connection, as seen from a user of libFenrir +#[derive(Debug)] +pub struct Connection(rc::Weak); + /// A single connection and its data #[derive(Debug)] -pub struct Connection { - /// Receiving Connection ID +pub(crate) struct Conn { + /// Receiving Conn ID pub id_recv: IDRecv, - /// Sending Connection ID + /// Sending Conn ID pub id_send: IDSend, /// The main hkdf used for all secrets in this connection pub hkdf: Hkdf, @@ -62,9 +145,12 @@ pub struct Connection { pub cipher_send: CipherSend, } -/// Role: used to set the correct secrets -/// * Server: Connection is Incoming -/// * Client: Connection is Outgoing +/// Role: track the connection direction +/// +/// The Role is used to select the correct secrets, and track the direction +/// of the connection +/// * Server: Conn is Incoming +/// * Client: Conn is Outgoing #[derive(Debug, Copy, Clone)] #[repr(u8)] pub enum Role { @@ -74,7 +160,7 @@ pub enum Role { Client, } -impl Connection { +impl Conn { pub(crate) fn new( hkdf: Hkdf, cipher: CipherKind, @@ -102,11 +188,9 @@ impl Connection { } } -// PERF: Arc> loks a bit too much, need to find -// faster ways to do this pub(crate) struct ConnList { thread_id: ThreadTracker, - connections: Vec>>, + connections: Vec>>, /// Bitmap to track which connection ids are used or free ids_used: Vec<::bitmaps::Bitmap<1024>>, } @@ -177,7 +261,7 @@ impl ConnList { new_id } /// NOTE: does NOT check if the connection has been previously reserved! - pub(crate) fn track(&mut self, conn: Rc) -> Result<(), ()> { + pub(crate) fn track(&mut self, conn: Rc) -> Result<(), ()> { let conn_id = match conn.id_recv { IDRecv(ID::Handshake) => { return Err(()); diff --git a/src/connection/packet.rs b/src/connection/packet.rs index 925460c..545c882 100644 --- a/src/connection/packet.rs +++ b/src/connection/packet.rs @@ -1,107 +1,26 @@ // //! Raw packet handling, encryption, decryption, parsing -use crate::enc::{ - sym::{HeadLen, TagLen}, - Random, +use crate::{ + connection, + enc::sym::{HeadLen, TagLen}, }; -/// Fenrir Connection id -/// 0 is special as it represents the handshake -/// Connection IDs are to be considered u64 little endian -#[derive(Debug, Copy, Clone, PartialEq)] -pub enum ConnectionID { - /// Connection id 0 represent the handshake - Handshake, - /// Non-zero id can represent any connection - ID(::core::num::NonZeroU64), -} - -impl ConnectionID { - /// Set the conenction id to handshake - pub fn new_handshake() -> Self { - Self::Handshake - } - /// New id from u64. PLZ NON ZERO - pub(crate) fn new_u64(raw: u64) -> Self { - #[allow(unsafe_code)] - unsafe { - ConnectionID::ID(::core::num::NonZeroU64::new_unchecked(raw)) - } - } - pub(crate) fn as_u64(&self) -> u64 { - match self { - ConnectionID::Handshake => 0, - ConnectionID::ID(id) => id.get(), - } - } - /// New random service ID - pub fn new_rand(rand: &Random) -> Self { - let mut raw = [0; 8]; - let mut num = 0; - while num == 0 { - rand.fill(&mut raw); - num = u64::from_le_bytes(raw); - } - #[allow(unsafe_code)] - unsafe { - ConnectionID::ID(::core::num::NonZeroU64::new_unchecked(num)) - } - } - /// Quick check to know if this is an handshake - pub fn is_handshake(&self) -> bool { - *self == ConnectionID::Handshake - } - /// length if the connection ID in bytes - pub const fn len() -> usize { - 8 - } - /// write the ID to a buffer - pub fn serialize(&self, out: &mut [u8]) { - match self { - ConnectionID::Handshake => out[..8].copy_from_slice(&[0; 8]), - ConnectionID::ID(id) => { - out[..8].copy_from_slice(&id.get().to_le_bytes()) - } - } - } -} - -impl From for ConnectionID { - fn from(raw: u64) -> Self { - if raw == 0 { - ConnectionID::Handshake - } else { - #[allow(unsafe_code)] - unsafe { - ConnectionID::ID(::core::num::NonZeroU64::new_unchecked(raw)) - } - } - } -} - -impl From<[u8; 8]> for ConnectionID { - fn from(raw: [u8; 8]) -> Self { - let raw_u64 = u64::from_le_bytes(raw); - raw_u64.into() - } -} - /// Enumerate the possible data in a fenrir packet #[derive(Debug, Clone)] -pub enum PacketData { +pub enum Data { /// A parsed handshake packet Handshake(super::Handshake), /// Raw packet. we only have the connection ID and packet length Raw(usize), } -impl PacketData { +impl Data { /// total length of the data in bytes pub fn len(&self, head_len: HeadLen, tag_len: TagLen) -> usize { match self { - PacketData::Handshake(h) => h.len(head_len, tag_len), - PacketData::Raw(len) => *len, + Data::Handshake(h) => h.len(head_len, tag_len), + Data::Raw(len) => *len, } } /// serialize data into bytes @@ -114,12 +33,12 @@ impl PacketData { ) { assert!( self.len(head_len, tag_len) == out.len(), - "PacketData: wrong buffer length" + "Data: wrong buffer length" ); match self { - PacketData::Handshake(h) => h.serialize(head_len, tag_len, out), - PacketData::Raw(_) => { - ::tracing::error!("Tried to serialize a raw PacketData!"); + Data::Handshake(h) => h.serialize(head_len, tag_len, out), + Data::Raw(_) => { + ::tracing::error!("Tried to serialize a raw Data!"); } } } @@ -131,9 +50,9 @@ const MIN_PACKET_BYTES: usize = 16; #[derive(Debug, Clone)] pub struct Packet { /// Id of the Fenrir connection. - pub id: ConnectionID, + pub id: connection::ID, /// actual data inside the packet - pub data: PacketData, + pub data: Data, } impl Packet { @@ -146,12 +65,12 @@ impl Packet { let raw_id: [u8; 8] = (raw[..8]).try_into().expect("unreachable"); Ok(Packet { id: raw_id.into(), - data: PacketData::Raw(raw.len()), + data: Data::Raw(raw.len()), }) } /// get the total length of the packet pub fn len(&self, head_len: HeadLen, tag_len: TagLen) -> usize { - ConnectionID::len() + self.data.len(head_len, tag_len) + connection::ID::len() + self.data.len(head_len, tag_len) } /// serialize packet into buffer /// NOTE: assumes that there is exactly asa much buffer as needed @@ -162,11 +81,14 @@ impl Packet { out: &mut [u8], ) { assert!( - out.len() > ConnectionID::len(), + out.len() > connection::ID::len(), "Packet: not enough buffer to serialize" ); - self.id.serialize(&mut out[0..ConnectionID::len()]); - self.data - .serialize(head_len, tag_len, &mut out[ConnectionID::len()..]); + self.id.serialize(&mut out[0..connection::ID::len()]); + self.data.serialize( + head_len, + tag_len, + &mut out[connection::ID::len()..], + ); } } diff --git a/src/connection/stream/errors.rs b/src/connection/stream/errors.rs new file mode 100644 index 0000000..133d976 --- /dev/null +++ b/src/connection/stream/errors.rs @@ -0,0 +1,10 @@ +//! Errors while parsing streams + + +/// Crypto errors +#[derive(::thiserror::Error, Debug, Copy, Clone)] +pub enum Error { + /// Error while parsing key material + #[error("Not enough data for stream chunk: {0}")] + NotEnoughData(usize), +} diff --git a/src/connection/stream/mod.rs b/src/connection/stream/mod.rs new file mode 100644 index 0000000..1b56a2b --- /dev/null +++ b/src/connection/stream/mod.rs @@ -0,0 +1,183 @@ +//! Here we implement the multiplexing stream feature of Fenrir +//! +//! For now we will only have the TCP-like, reliable, in-order delivery + +mod errors; +mod rob; +pub use errors::Error; + +use crate::{connection::stream::rob::ReliableOrderedBytestream, enc::Random}; + +/// Kind of stream. any combination of: +/// reliable/unreliable ordered/unordered, bytestream/datagram +#[derive(Debug, Copy, Clone)] +#[repr(u8)] +pub enum Kind { + /// ROB: Reliable, Ordered, Bytestream + /// AKA: TCP-like + ROB = 0, +} + +/// Id of the stream +#[derive(Debug, Copy, Clone)] +pub struct ID(pub u16); + +impl ID { + /// Length of the serialized field + pub const fn len() -> usize { + 2 + } +} + +/// length of the chunk +#[derive(Debug, Copy, Clone)] +pub struct ChunkLen(pub u16); + +impl ChunkLen { + /// Length of the serialized field + pub const fn len() -> usize { + 2 + } +} + +/// Sequence number to rebuild the stream correctly +#[derive(Debug, Copy, Clone)] +pub struct Sequence(pub ::core::num::Wrapping); + +impl Sequence { + const SEQ_NOFLAG: u32 = 0x3FFFFFFF; + /// return a new sequence number, starting at random + pub fn new(rand: &Random) -> Self { + let seq: u32 = 0; + rand.fill(&mut seq.to_le_bytes()); + Self(::core::num::Wrapping(seq & Self::SEQ_NOFLAG)) + } + /// Length of the serialized field + pub const fn len() -> usize { + 4 + } +} + +/// Chunk of data representing a stream +/// Every chunk is as follows: +/// | id (2 bytes) | length (2 bytes) | +/// | flag_start (1 BIT) | flag_end (1 BIT) | sequence (30 bits) | +#[derive(Debug, Clone)] +pub struct Chunk<'a> { + /// Id of the stream this chunk is part of + pub id: ID, + /// Is this the beginning of a message? + pub flag_start: bool, + /// Is this the end of a message? + pub flag_end: bool, + /// Sequence number to reconstruct the Stream + pub sequence: Sequence, + data: &'a [u8], +} + +impl<'a> Chunk<'a> { + const FLAGS_EXCLUDED_BITMASK: u8 = 0x3F; + const FLAG_START_BITMASK: u8 = 0x80; + const FLAG_END_BITMASK: u8 = 0x40; + /// Returns the total length of the chunk, including headers + pub fn len(&self) -> usize { + ID::len() + ChunkLen::len() + Sequence::len() + self.data.len() + } + /// deserialize a chunk of a stream + pub fn deserialize(raw: &'a [u8]) -> Result { + if raw.len() <= ID::len() + ChunkLen::len() + Sequence::len() { + return Err(Error::NotEnoughData(0)); + } + + let id = ID(u16::from_le_bytes(raw[0..ID::len()].try_into().unwrap())); + + let mut bytes_next = ID::len() + ChunkLen::len(); + let length = ChunkLen(u16::from_le_bytes( + raw[ID::len()..bytes_next].try_into().unwrap(), + )); + if ID::len() + ChunkLen::len() + Sequence::len() + length.0 as usize + > raw.len() + { + return Err(Error::NotEnoughData(4)); + } + + let flag_start = (raw[bytes_next] & Self::FLAG_START_BITMASK) != 0; + let flag_end = (raw[bytes_next] & Self::FLAG_END_BITMASK) != 0; + + let bytes = bytes_next + 1; + bytes_next = bytes + Sequence::len(); + let mut sequence_bytes: [u8; Sequence::len()] = + raw[bytes..bytes_next].try_into().unwrap(); + sequence_bytes[0] = sequence_bytes[0] & Self::FLAGS_EXCLUDED_BITMASK; + let sequence = + Sequence(::core::num::Wrapping(u32::from_le_bytes(sequence_bytes))); + + Ok(Self { + id, + flag_start, + flag_end, + sequence, + data: &raw[bytes_next..(bytes_next + length.0 as usize)], + }) + } + /// serialize a chunk of a stream + pub fn serialize(&self, raw_out: &mut [u8]) { + raw_out[0..ID::len()].copy_from_slice(&self.id.0.to_le_bytes()); + let mut bytes_next = ID::len() + ChunkLen::len(); + raw_out[ID::len()..bytes_next] + .copy_from_slice(&(self.data.len() as u16).to_le_bytes()); + let bytes = bytes_next; + bytes_next = bytes_next + Sequence::len(); + raw_out[bytes..bytes_next] + .copy_from_slice(&self.sequence.0 .0.to_le_bytes()); + let mut flag_byte = raw_out[bytes] & Self::FLAGS_EXCLUDED_BITMASK; + if self.flag_start { + flag_byte = flag_byte | Self::FLAG_START_BITMASK; + } + if self.flag_end { + flag_byte = flag_byte | Self::FLAG_END_BITMASK; + } + raw_out[bytes] = flag_byte; + let bytes = bytes_next; + bytes_next = bytes_next + self.data.len(); + raw_out[bytes..bytes_next].copy_from_slice(&self.data); + } +} + +/// Kind of stream. any combination of: +/// reliable/unreliable ordered/unordered, bytestream/datagram +/// differences from Kind: +/// * not public +/// * has actual data +#[derive(Debug, Clone)] +pub(crate) enum Tracker { + /// ROB: Reliable, Ordered, Bytestream + /// AKA: TCP-like + ROB(ReliableOrderedBytestream), +} + +impl Tracker { + pub(crate) fn new(kind: Kind, rand: &Random) -> Self { + match kind { + Kind::ROB => Tracker::ROB(ReliableOrderedBytestream::new(rand)), + } + } +} + +/// Actual stream-tracking structure +#[derive(Debug, Clone)] +pub(crate) struct Stream { + id: ID, + data: Tracker, +} + +impl Stream { + pub(crate) fn new(kind: Kind, rand: &Random) -> Self { + let id: u16 = 0; + rand.fill(&mut id.to_le_bytes()); + Self { + id: ID(id), + data: Tracker::new(kind, rand), + } + } +} diff --git a/src/connection/stream/rob.rs b/src/connection/stream/rob.rs new file mode 100644 index 0000000..5d28f59 --- /dev/null +++ b/src/connection/stream/rob.rs @@ -0,0 +1,29 @@ +//! Implementation of the Reliable, Ordered, Bytestream transmission model +//! AKA: TCP-like + +use crate::{ + connection::stream::{Chunk, Error, Sequence}, + enc::Random, +}; + +/// Reliable, Ordered, Bytestream stream tracker +/// AKA: TCP-like +#[derive(Debug, Clone)] +pub(crate) struct ReliableOrderedBytestream { + window_start: Sequence, + window_len: usize, + data: Vec, +} + +impl ReliableOrderedBytestream { + pub(crate) fn new(rand: &Random) -> Self { + Self { + window_start: Sequence::new(rand), + window_len: 1048576, // 1MB. should be enough for anybody. (lol) + data: Vec::new(), + } + } + pub(crate) fn recv(&mut self, chunk: Chunk) -> Result<(), Error> { + todo!() + } +} diff --git a/src/inner/worker.rs b/src/inner/worker.rs index 083a9ac..cdd5d75 100644 --- a/src/inner/worker.rs +++ b/src/inner/worker.rs @@ -10,8 +10,9 @@ use crate::{ tracker::{HandshakeAction, HandshakeTracker}, Handshake, HandshakeData, }, + packet::{self, Packet}, socket::{UdpClient, UdpServer}, - ConnList, Connection, IDSend, Packet, + Conn, ConnList, IDSend, }, dnssec, enc::{ @@ -125,6 +126,7 @@ impl Worker { handshakes, }) } + /// Continuously loop and process work as needed pub async fn work_loop(&mut self) { 'mainloop: loop { @@ -292,7 +294,7 @@ impl Worker { // are PubKey::Exchange unreachable!() } - let mut conn = Connection::new( + let mut conn = Conn::new( hkdf, cipher_selected, connection::Role::Client, @@ -345,7 +347,8 @@ impl Worker { exchange_key: pub_key, data: dirsync::ReqInner::ClearText(req_data), }; - let encrypt_start = ID::len() + req.encrypted_offset(); + let encrypt_start = + connection::ID::len() + req.encrypted_offset(); let encrypt_end = encrypt_start + req.encrypted_length( cipher_selected.nonce_len(), @@ -354,10 +357,9 @@ impl Worker { let h_req = Handshake::new(HandshakeData::DirSync( DirSync::Req(req), )); - use connection::{PacketData, ID}; let packet = Packet { - id: ID::Handshake, - data: PacketData::Handshake(h_req), + id: connection::ID::Handshake, + data: packet::Data::Handshake(h_req), }; let tot_len = packet.len( @@ -510,12 +512,12 @@ impl Worker { // Client has correctly authenticated // TODO: contact the service, get the key and // connection ID - let srv_conn_id = ID::new_rand(&self.rand); + let srv_conn_id = connection::ID::new_rand(&self.rand); let srv_secret = Secret::new_rand(&self.rand); let head_len = req.cipher.nonce_len(); let tag_len = req.cipher.tag_len(); - let mut auth_conn = Connection::new( + let mut auth_conn = Conn::new( authinfo.hkdf, req.cipher, connection::Role::Server, @@ -541,16 +543,16 @@ impl Worker { client_key_id: req_data.client_key_id, data: RespInner::ClearText(resp_data), }; - let encrypt_from = ID::len() + resp.encrypted_offset(); + let encrypt_from = + connection::ID::len() + resp.encrypted_offset(); let encrypt_until = encrypt_from + resp.encrypted_length(head_len, tag_len); let resp_handshake = Handshake::new( HandshakeData::DirSync(DirSync::Resp(resp)), ); - use connection::{PacketData, ID}; let packet = Packet { - id: ID::new_handshake(), - data: PacketData::Handshake(resp_handshake), + id: connection::ID::new_handshake(), + data: packet::Data::Handshake(resp_handshake), }; let tot_len = packet.len(head_len, tag_len); let mut raw_out = Vec::::with_capacity(tot_len); @@ -611,7 +613,7 @@ impl Worker { cci.service_id.as_bytes(), resp_data.service_key, ); - let mut service_connection = Connection::new( + let mut service_connection = Conn::new( hkdf, cipher, connection::Role::Client, diff --git a/src/lib.rs b/src/lib.rs index fbca09a..57da614 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -39,6 +39,7 @@ use crate::{ }, }; pub use config::Config; +pub use connection::Connection; /// Main fenrir library errors #[derive(::thiserror::Error, Debug)] @@ -332,7 +333,7 @@ impl Fenrir { let data: Vec = buffer[..bytes].to_vec(); // we very likely have multiple threads, pinned to different cpus. - // use the ConnectionID to send the same connection + // use the connection::ID to send the same connection // to the same thread. // Handshakes have connection ID 0, so we use the sender's UDP port @@ -341,13 +342,12 @@ impl Fenrir { Err(_) => continue, // packet way too short, ignore. }; let thread_idx: usize = { - use connection::packet::ConnectionID; match packet.id { - ConnectionID::Handshake => { + connection::ID::Handshake => { let send_port = sock_sender.0.port() as u64; (send_port % queues_num) as usize } - ConnectionID::ID(id) => (id.get() % queues_num) as usize, + connection::ID::ID(id) => (id.get() % queues_num) as usize, } }; let _ = work_queues[thread_idx] -- 2.44.1 From bf877cf86e7dd15a591040027ee45a256257ad2a Mon Sep 17 00:00:00 2001 From: Luca Fulchir Date: Mon, 19 Jun 2023 19:26:21 +0200 Subject: [PATCH 2/3] Rename lots of stuff to properly use namespaces Signed-off-by: Luca Fulchir --- src/config/mod.rs | 17 +++-- src/connection/handshake/dirsync.rs | 108 ++++++++++++++-------------- src/connection/handshake/mod.rs | 33 ++++----- src/connection/handshake/tests.rs | 31 ++++---- src/connection/handshake/tracker.rs | 79 ++++++++++---------- src/connection/mod.rs | 4 +- src/connection/packet.rs | 10 +-- src/dnssec/record.rs | 17 +++-- src/dnssec/tests.rs | 8 +-- src/enc/asym.rs | 66 ++++++++--------- src/enc/hkdf.rs | 28 ++++---- src/enc/sym.rs | 107 ++++++++++----------------- src/enc/tests.rs | 24 ++++--- src/inner/worker.rs | 37 +++++----- src/tests.rs | 11 ++- 15 files changed, 269 insertions(+), 311 deletions(-) diff --git a/src/config/mod.rs b/src/config/mod.rs index f196ac9..392bdcc 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -2,11 +2,10 @@ //! Configuration to initialize the Fenrir networking library use crate::{ - connection::handshake::HandshakeID, + connection::handshake, enc::{ asym::{KeyExchangeKind, KeyID, PrivKey, PubKey}, - hkdf::HkdfKind, - sym::CipherKind, + hkdf, sym, }, }; use ::std::{ @@ -44,13 +43,13 @@ pub struct Config { /// List of DNS resolvers to use pub resolvers: Vec, /// Supported handshakes - pub handshakes: Vec, + pub handshakes: Vec, /// Supported key exchanges pub key_exchanges: Vec, /// Supported Hkdfs - pub hkdfs: Vec, + pub hkdfs: Vec, /// Supported Ciphers - pub ciphers: Vec, + pub ciphers: Vec, /// list of authentication servers /// clients will have this empty pub servers: Vec, @@ -73,10 +72,10 @@ impl Default for Config { ), ], resolvers: Vec::new(), - handshakes: [HandshakeID::DirectorySynchronized].to_vec(), + handshakes: [handshake::ID::DirectorySynchronized].to_vec(), key_exchanges: [KeyExchangeKind::X25519DiffieHellman].to_vec(), - hkdfs: [HkdfKind::Sha3].to_vec(), - ciphers: [CipherKind::XChaCha20Poly1305].to_vec(), + hkdfs: [hkdf::Kind::Sha3].to_vec(), + ciphers: [sym::Kind::XChaCha20Poly1305].to_vec(), servers: Vec::new(), server_keys: Vec::new(), } diff --git a/src/connection/handshake/dirsync.rs b/src/connection/handshake/dirsync.rs index 0be2dd4..676af37 100644 --- a/src/connection/handshake/dirsync.rs +++ b/src/connection/handshake/dirsync.rs @@ -8,14 +8,14 @@ //! To grant a form of perfect forward secrecy, the server should periodically //! change the DNSSEC public/private keys -use super::{Error, HandshakeData}; +use super::Error; use crate::{ auth, - connection::{ProtocolVersion, ID}, + connection::{handshake, ProtocolVersion, ID}, enc::{ asym::{ExchangePubKey, KeyExchangeKind, KeyID}, - hkdf::HkdfKind, - sym::{CipherKind, HeadLen, TagLen}, + hkdf, + sym::{self, NonceLen, TagLen}, Random, Secret, }, }; @@ -59,7 +59,7 @@ pub enum DirSync { impl DirSync { /// actual length of the dirsync handshake data - pub fn len(&self, head_len: HeadLen, tag_len: TagLen) -> usize { + pub fn len(&self, head_len: NonceLen, tag_len: TagLen) -> usize { match self { DirSync::Req(req) => req.len(), DirSync::Resp(resp) => resp.len(head_len, tag_len), @@ -69,7 +69,7 @@ impl DirSync { /// NOTE: assumes that there is exactly asa much buffer as needed pub fn serialize( &self, - head_len: HeadLen, + head_len: NonceLen, tag_len: TagLen, out: &mut [u8], ) { @@ -88,13 +88,13 @@ pub struct Req { /// Selected key exchange pub exchange: KeyExchangeKind, /// Selected hkdf - pub hkdf: HkdfKind, + pub hkdf: hkdf::Kind, /// Selected cipher - pub cipher: CipherKind, + pub cipher: sym::Kind, /// Client ephemeral public key used for key exchanges pub exchange_key: ExchangePubKey, /// encrypted data - pub data: ReqInner, + pub data: ReqState, // SECURITY: TODO: Add padding to min: 1200 bytes // to avoid amplification attaks // also: 1200 < 1280 to allow better vpn compatibility @@ -105,30 +105,30 @@ impl Req { /// NOTE: starts from the beginning of the fenrir packet pub fn encrypted_offset(&self) -> usize { ProtocolVersion::len() - + crate::handshake::HandshakeID::len() + + handshake::ID::len() + KeyID::len() + KeyExchangeKind::len() - + HkdfKind::len() - + CipherKind::len() + + hkdf::Kind::len() + + sym::Kind::len() + self.exchange_key.kind().pub_len() } /// return the total length of the cleartext data pub fn encrypted_length( &self, - head_len: HeadLen, + head_len: NonceLen, tag_len: TagLen, ) -> usize { match &self.data { - ReqInner::ClearText(data) => data.len() + head_len.0 + tag_len.0, - ReqInner::CipherText(length) => *length, + ReqState::ClearText(data) => data.len() + head_len.0 + tag_len.0, + ReqState::CipherText(length) => *length, } } /// actual length of the directory synchronized request pub fn len(&self) -> usize { KeyID::len() + KeyExchangeKind::len() - + HkdfKind::len() - + CipherKind::len() + + hkdf::Kind::len() + + sym::Kind::len() + self.exchange_key.kind().pub_len() + self.cipher.nonce_len().0 + self.data.len() @@ -138,7 +138,7 @@ impl Req { /// NOTE: assumes that there is exactly as much buffer as needed pub fn serialize( &self, - head_len: HeadLen, + head_len: NonceLen, tag_len: TagLen, out: &mut [u8], ) { @@ -150,7 +150,7 @@ impl Req { let written_next = 5 + key_len; self.exchange_key.serialize_into(&mut out[5..written_next]); let written = written_next; - if let ReqInner::ClearText(data) = &self.data { + if let ReqState::ClearText(data) = &self.data { let from = written + head_len.0; let to = out.len() - tag_len.0; data.serialize(&mut out[from..to]); @@ -160,8 +160,8 @@ impl Req { } } -impl super::HandshakeParsing for Req { - fn deserialize(raw: &[u8]) -> Result { +impl handshake::Parsing for Req { + fn deserialize(raw: &[u8]) -> Result { const MIN_PKT_LEN: usize = 10; if raw.len() < MIN_PKT_LEN { return Err(Error::NotEnoughData); @@ -173,25 +173,25 @@ impl super::HandshakeParsing for Req { Some(exchange) => exchange, None => return Err(Error::Parsing), }; - let hkdf: HkdfKind = match HkdfKind::from_u8(raw[3]) { + let hkdf: hkdf::Kind = match hkdf::Kind::from_u8(raw[3]) { Some(exchange) => exchange, None => return Err(Error::Parsing), }; - let cipher: CipherKind = match CipherKind::from_u8(raw[4]) { + let cipher: sym::Kind = match sym::Kind::from_u8(raw[4]) { Some(cipher) => cipher, None => return Err(Error::Parsing), }; const CURR_SIZE: usize = KeyID::len() + KeyExchangeKind::len() - + HkdfKind::len() - + CipherKind::len(); + + hkdf::Kind::len() + + sym::Kind::len(); let (exchange_key, len) = match ExchangePubKey::deserialize(&raw[CURR_SIZE..]) { Ok(exchange_key) => exchange_key, Err(e) => return Err(e.into()), }; - let data = ReqInner::CipherText(raw.len() - (CURR_SIZE + len)); - Ok(HandshakeData::DirSync(DirSync::Req(Self { + let data = ReqState::CipherText(raw.len() - (CURR_SIZE + len)); + Ok(handshake::Data::DirSync(DirSync::Req(Self { key_id, exchange, hkdf, @@ -204,18 +204,18 @@ impl super::HandshakeParsing for Req { /// Quick way to avoid mixing cipher and clear text #[derive(Debug, Clone, PartialEq)] -pub enum ReqInner { +pub enum ReqState { /// Data is still encrytped, we only keep the length CipherText(usize), /// Client data, decrypted and parsed ClearText(ReqData), } -impl ReqInner { +impl ReqState { /// The length of the data pub fn len(&self) -> usize { match self { - ReqInner::CipherText(len) => *len, - ReqInner::ClearText(data) => data.len(), + ReqState::CipherText(len) => *len, + ReqState::ClearText(data) => data.len(), } } /// parse the cleartext @@ -224,10 +224,10 @@ impl ReqInner { raw: &[u8], ) -> Result<(), Error> { let clear = match self { - ReqInner::CipherText(len) => { + ReqState::CipherText(len) => { assert!( *len > raw.len(), - "DirSync::ReqInner::CipherText length mismatch" + "DirSync::ReqState::CipherText length mismatch" ); match ReqData::deserialize(raw) { Ok(clear) => clear, @@ -236,7 +236,7 @@ impl ReqInner { } _ => return Err(Error::Parsing), }; - *self = ReqInner::ClearText(clear); + *self = ReqState::ClearText(clear); Ok(()) } } @@ -386,18 +386,18 @@ impl ReqData { /// Quick way to avoid mixing cipher and clear text #[derive(Debug, Clone, PartialEq)] -pub enum RespInner { +pub enum RespState { /// Server data, still in ciphertext CipherText(usize), /// Parsed, cleartext server data ClearText(RespData), } -impl RespInner { +impl RespState { /// The length of the data pub fn len(&self) -> usize { match self { - RespInner::CipherText(len) => *len, - RespInner::ClearText(_) => RespData::len(), + RespState::CipherText(len) => *len, + RespState::ClearText(_) => RespData::len(), } } /// parse the cleartext @@ -406,10 +406,10 @@ impl RespInner { raw: &[u8], ) -> Result<(), Error> { let clear = match self { - RespInner::CipherText(len) => { + RespState::CipherText(len) => { assert!( *len > raw.len(), - "DirSync::RespInner::CipherText length mismatch" + "DirSync::RespState::CipherText length mismatch" ); match RespData::deserialize(raw) { Ok(clear) => clear, @@ -418,12 +418,12 @@ impl RespInner { } _ => return Err(Error::Parsing), }; - *self = RespInner::ClearText(clear); + *self = RespState::ClearText(clear); Ok(()) } /// Serialize the still cleartext data pub fn serialize(&self, out: &mut [u8]) { - if let RespInner::ClearText(clear) = &self { + if let RespState::ClearText(clear) = &self { clear.serialize(out); } } @@ -435,20 +435,20 @@ pub struct Resp { /// Tells the client with which key the exchange was done pub client_key_id: KeyID, /// actual response data, might be encrypted - pub data: RespInner, + pub data: RespState, } -impl super::HandshakeParsing for Resp { - fn deserialize(raw: &[u8]) -> Result { +impl handshake::Parsing for Resp { + fn deserialize(raw: &[u8]) -> Result { const MIN_PKT_LEN: usize = 68; if raw.len() < MIN_PKT_LEN { return Err(Error::NotEnoughData); } let client_key_id: KeyID = KeyID(u16::from_le_bytes(raw[0..KeyID::len()].try_into().unwrap())); - Ok(HandshakeData::DirSync(DirSync::Resp(Self { + Ok(handshake::Data::DirSync(DirSync::Resp(Self { client_key_id, - data: RespInner::CipherText(raw[KeyID::len()..].len()), + data: RespState::CipherText(raw[KeyID::len()..].len()), }))) } } @@ -457,32 +457,30 @@ impl Resp { /// return the offset of the encrypted data /// NOTE: starts from the beginning of the fenrir packet pub fn encrypted_offset(&self) -> usize { - ProtocolVersion::len() - + crate::connection::handshake::HandshakeID::len() - + KeyID::len() + ProtocolVersion::len() + handshake::ID::len() + KeyID::len() } /// return the total length of the cleartext data pub fn encrypted_length( &self, - head_len: HeadLen, + head_len: NonceLen, tag_len: TagLen, ) -> usize { match &self.data { - RespInner::ClearText(_data) => { + RespState::ClearText(_data) => { RespData::len() + head_len.0 + tag_len.0 } - RespInner::CipherText(len) => *len, + RespState::CipherText(len) => *len, } } /// Total length of the response handshake - pub fn len(&self, head_len: HeadLen, tag_len: TagLen) -> usize { + pub fn len(&self, head_len: NonceLen, tag_len: TagLen) -> usize { KeyID::len() + head_len.0 + self.data.len() + tag_len.0 } /// Serialize into raw bytes /// NOTE: assumes that there is exactly as much buffer as needed pub fn serialize( &self, - head_len: HeadLen, + head_len: NonceLen, _tag_len: TagLen, out: &mut [u8], ) { diff --git a/src/connection/handshake/mod.rs b/src/connection/handshake/mod.rs index b5204a1..6c0e53d 100644 --- a/src/connection/handshake/mod.rs +++ b/src/connection/handshake/mod.rs @@ -4,10 +4,11 @@ pub mod dirsync; #[cfg(test)] mod tests; pub(crate) mod tracker; +pub(crate) use tracker::{Action, Tracker}; use crate::{ connection::ProtocolVersion, - enc::sym::{HeadLen, TagLen}, + enc::sym::{NonceLen, TagLen}, }; use ::num_traits::FromPrimitive; @@ -56,7 +57,7 @@ pub enum Error { ::strum_macros::IntoStaticStr, )] #[repr(u8)] -pub enum HandshakeID { +pub enum ID { /// 1-RTT Directory synchronized handshake. Fast, no forward secrecy #[strum(serialize = "directory_synchronized")] DirectorySynchronized = 0, @@ -67,7 +68,7 @@ pub enum HandshakeID { #[strum(serialize = "stateless")] Stateless, } -impl HandshakeID { +impl ID { /// The length of the serialized field pub const fn len() -> usize { 1 @@ -75,28 +76,28 @@ impl HandshakeID { } /// Parsed handshake #[derive(Debug, Clone, PartialEq)] -pub enum HandshakeData { +pub enum Data { /// Directory synchronized handhsake DirSync(dirsync::DirSync), } -impl HandshakeData { +impl Data { /// actual length of the handshake data - pub fn len(&self, head_len: HeadLen, tag_len: TagLen) -> usize { + pub fn len(&self, head_len: NonceLen, tag_len: TagLen) -> usize { match self { - HandshakeData::DirSync(d) => d.len(head_len, tag_len), + Data::DirSync(d) => d.len(head_len, tag_len), } } /// Serialize into raw bytes /// NOTE: assumes that there is exactly asa much buffer as needed pub fn serialize( &self, - head_len: HeadLen, + head_len: NonceLen, tag_len: TagLen, out: &mut [u8], ) { match self { - HandshakeData::DirSync(d) => d.serialize(head_len, tag_len, out), + Data::DirSync(d) => d.serialize(head_len, tag_len, out), } } } @@ -133,19 +134,19 @@ pub struct Handshake { /// Fenrir Protocol version pub fenrir_version: ProtocolVersion, /// enum for the parsed data - pub data: HandshakeData, + pub data: Data, } impl Handshake { /// Build new handshake from the data - pub fn new(data: HandshakeData) -> Self { + pub fn new(data: Data) -> Self { Handshake { fenrir_version: ProtocolVersion::V0, data, } } /// return the total length of the handshake - pub fn len(&self, head_len: HeadLen, tag_len: TagLen) -> usize { + pub fn len(&self, head_len: NonceLen, tag_len: TagLen) -> usize { ProtocolVersion::len() + HandshakeKind::len() + self.data.len(head_len, tag_len) @@ -179,13 +180,13 @@ impl Handshake { /// NOTE: assumes that there is exactly as much buffer as needed pub fn serialize( &self, - head_len: HeadLen, + head_len: NonceLen, tag_len: TagLen, out: &mut [u8], ) { out[0] = self.fenrir_version as u8; out[1] = match &self.data { - HandshakeData::DirSync(d) => match d { + Data::DirSync(d) => match d { dirsync::DirSync::Req(_) => HandshakeKind::DirSyncReq, dirsync::DirSync::Resp(_) => HandshakeKind::DirSyncResp, }, @@ -194,6 +195,6 @@ impl Handshake { } } -trait HandshakeParsing { - fn deserialize(raw: &[u8]) -> Result; +trait Parsing { + fn deserialize(raw: &[u8]) -> Result; } diff --git a/src/connection/handshake/tests.rs b/src/connection/handshake/tests.rs index 83cacfd..d8eff42 100644 --- a/src/connection/handshake/tests.rs +++ b/src/connection/handshake/tests.rs @@ -1,13 +1,16 @@ use crate::{ auth, - connection::{handshake::*, ID}, + connection::{ + handshake::{self, dirsync, Handshake}, + ID, + }, enc::{self, asym::KeyID}, }; #[test] fn test_handshake_dirsync_req() { let rand = enc::Random::new(); - let cipher = enc::sym::CipherKind::XChaCha20Poly1305; + let cipher = enc::sym::Kind::XChaCha20Poly1305; let (_, exchange_key) = match enc::asym::KeyExchangeKind::X25519DiffieHellman.new_keypair(&rand) @@ -19,7 +22,7 @@ fn test_handshake_dirsync_req() { } }; - let data = dirsync::ReqInner::ClearText(dirsync::ReqData { + let data = dirsync::ReqState::ClearText(dirsync::ReqData { nonce: dirsync::Nonce::new(&rand), client_key_id: KeyID(2424), id: ID::ID(::core::num::NonZeroU64::new(424242).unwrap()), @@ -31,16 +34,16 @@ fn test_handshake_dirsync_req() { }, }); - let h_req = Handshake::new(HandshakeData::DirSync(dirsync::DirSync::Req( - dirsync::Req { + let h_req = Handshake::new(handshake::Data::DirSync( + dirsync::DirSync::Req(dirsync::Req { key_id: KeyID(4224), exchange: enc::asym::KeyExchangeKind::X25519DiffieHellman, - hkdf: enc::hkdf::HkdfKind::Sha3, - cipher: enc::sym::CipherKind::XChaCha20Poly1305, + hkdf: enc::hkdf::Kind::Sha3, + cipher: enc::sym::Kind::XChaCha20Poly1305, exchange_key, data, - }, - ))); + }), + )); let mut bytes = Vec::::with_capacity( h_req.len(cipher.nonce_len(), cipher.tag_len()), @@ -55,7 +58,7 @@ fn test_handshake_dirsync_req() { return; } }; - if let HandshakeData::DirSync(dirsync::DirSync::Req(r_a)) = + if let handshake::Data::DirSync(dirsync::DirSync::Req(r_a)) = &mut deserialized.data { let enc_start = r_a.encrypted_offset() + cipher.nonce_len().0; @@ -74,11 +77,11 @@ fn test_handshake_dirsync_req() { #[test] fn test_handshake_dirsync_reqsp() { let rand = enc::Random::new(); - let cipher = enc::sym::CipherKind::XChaCha20Poly1305; + let cipher = enc::sym::Kind::XChaCha20Poly1305; let service_key = enc::Secret::new_rand(&rand); - let data = dirsync::RespInner::ClearText(dirsync::RespData { + let data = dirsync::RespState::ClearText(dirsync::RespData { client_nonce: dirsync::Nonce::new(&rand), id: ID::ID(::core::num::NonZeroU64::new(424242).unwrap()), service_connection_id: ID::ID( @@ -87,7 +90,7 @@ fn test_handshake_dirsync_reqsp() { service_key, }); - let h_resp = Handshake::new(HandshakeData::DirSync( + let h_resp = Handshake::new(handshake::Data::DirSync( dirsync::DirSync::Resp(dirsync::Resp { client_key_id: KeyID(4444), data, @@ -107,7 +110,7 @@ fn test_handshake_dirsync_reqsp() { return; } }; - if let HandshakeData::DirSync(dirsync::DirSync::Resp(r_a)) = + if let handshake::Data::DirSync(dirsync::DirSync::Resp(r_a)) = &mut deserialized.data { let enc_start = r_a.encrypted_offset() + cipher.nonce_len().0; diff --git a/src/connection/handshake/tracker.rs b/src/connection/handshake/tracker.rs index 5da29bb..30a6a8f 100644 --- a/src/connection/handshake/tracker.rs +++ b/src/connection/handshake/tracker.rs @@ -3,22 +3,21 @@ use crate::{ auth::{Domain, ServiceID}, connection::{ - self, handshake::{self, Error, Handshake}, Conn, IDRecv, IDSend, }, enc::{ self, asym::{self, KeyID, PrivKey, PubKey}, - hkdf::{Hkdf, HkdfKind}, - sym::{CipherKind, CipherRecv}, + hkdf::{self, Hkdf}, + sym::{self, CipherRecv}, }, inner::ThreadTracker, }; use ::tokio::sync::oneshot; -pub(crate) struct HandshakeServer { +pub(crate) struct Server { pub id: KeyID, pub key: PrivKey, pub domains: Vec, @@ -26,7 +25,7 @@ pub(crate) struct HandshakeServer { pub(crate) type ConnectAnswer = Result<(KeyID, IDSend), crate::Error>; -pub(crate) struct HandshakeClient { +pub(crate) struct Client { pub service_id: ServiceID, pub service_conn_id: IDRecv, pub connection: Conn, @@ -37,13 +36,13 @@ pub(crate) struct HandshakeClient { /// Tracks the keys used by the client and the handshake /// they are associated with -pub(crate) struct HandshakeClientList { +pub(crate) struct ClientList { used: Vec<::bitmaps::Bitmap<1024>>, // index = KeyID keys: Vec>, - list: Vec>, + list: Vec>, } -impl HandshakeClientList { +impl ClientList { pub(crate) fn new() -> Self { Self { used: [::bitmaps::Bitmap::<1024>::new()].to_vec(), @@ -51,13 +50,13 @@ impl HandshakeClientList { list: Vec::with_capacity(16), } } - pub(crate) fn get(&self, id: KeyID) -> Option<&HandshakeClient> { + pub(crate) fn get(&self, id: KeyID) -> Option<&Client> { if id.0 as usize >= self.list.len() { return None; } self.list[id.0 as usize].as_ref() } - pub(crate) fn remove(&mut self, id: KeyID) -> Option { + pub(crate) fn remove(&mut self, id: KeyID) -> Option { if id.0 as usize >= self.list.len() { return None; } @@ -82,8 +81,7 @@ impl HandshakeClientList { connection: Conn, answer: oneshot::Sender, srv_key_id: KeyID, - ) -> Result<(KeyID, &mut HandshakeClient), oneshot::Sender> - { + ) -> Result<(KeyID, &mut Client), oneshot::Sender> { let maybe_free_key_idx = self.used.iter().enumerate().find_map(|(idx, bmap)| { match bmap.first_false_index() { @@ -112,7 +110,7 @@ impl HandshakeClientList { self.list.push(None); } self.keys[free_key_idx] = Some((priv_key, pub_key)); - self.list[free_key_idx] = Some(HandshakeClient { + self.list[free_key_idx] = Some(Client { service_id, service_conn_id, connection, @@ -153,7 +151,7 @@ pub(crate) struct ClientConnectInfo { } /// Intermediate actions to be taken while parsing the handshake #[derive(Debug)] -pub(crate) enum HandshakeAction { +pub(crate) enum Action { /// Parsing finished, all ok, nothing to do Nothing, /// Packet parsed, now go perform authentication @@ -167,20 +165,20 @@ pub(crate) enum HandshakeAction { /// Each of them will handle a subset of all handshakes. /// Each handshake is routed to a different tracker by checking /// core = (udp_src_sender_port % total_threads) - 1 -pub(crate) struct HandshakeTracker { +pub(crate) struct Tracker { thread_id: ThreadTracker, key_exchanges: Vec, - ciphers: Vec, + ciphers: Vec, /// ephemeral keys used server side in key exchange - keys_srv: Vec, + keys_srv: Vec, /// ephemeral keys used client side in key exchange - hshake_cli: HandshakeClientList, + hshake_cli: ClientList, } -impl HandshakeTracker { +impl Tracker { pub(crate) fn new( thread_id: ThreadTracker, - ciphers: Vec, + ciphers: Vec, key_exchanges: Vec, ) -> Self { Self { @@ -188,7 +186,7 @@ impl HandshakeTracker { ciphers, key_exchanges, keys_srv: Vec::new(), - hshake_cli: HandshakeClientList::new(), + hshake_cli: ClientList::new(), } } pub(crate) fn add_server_key( @@ -199,7 +197,7 @@ impl HandshakeTracker { if self.keys_srv.iter().find(|&k| k.id == id).is_some() { return Err(()); } - self.keys_srv.push(HandshakeServer { + self.keys_srv.push(Server { id, key, domains: Vec::new(), @@ -236,8 +234,7 @@ impl HandshakeTracker { connection: Conn, answer: oneshot::Sender, srv_key_id: KeyID, - ) -> Result<(KeyID, &mut HandshakeClient), oneshot::Sender> - { + ) -> Result<(KeyID, &mut Client), oneshot::Sender> { self.hshake_cli.add( priv_key, pub_key, @@ -248,10 +245,7 @@ impl HandshakeTracker { srv_key_id, ) } - pub(crate) fn remove_client( - &mut self, - key_id: KeyID, - ) -> Option { + pub(crate) fn remove_client(&mut self, key_id: KeyID) -> Option { self.hshake_cli.remove(key_id) } pub(crate) fn timeout_client( @@ -269,10 +263,10 @@ impl HandshakeTracker { &mut self, mut handshake: Handshake, handshake_raw: &mut [u8], - ) -> Result { - use connection::handshake::{dirsync::DirSync, HandshakeData}; + ) -> Result { + use handshake::dirsync::DirSync; match handshake.data { - HandshakeData::DirSync(ref mut ds) => match ds { + handshake::Data::DirSync(ref mut ds) => match ds { DirSync::Req(ref mut req) => { if !self.key_exchanges.contains(&req.exchange) { return Err(enc::Error::UnsupportedKeyExchange.into()); @@ -310,7 +304,8 @@ impl HandshakeTracker { Ok(shared_key) => shared_key, Err(e) => return Err(handshake::Error::Key(e).into()), }; - let hkdf = Hkdf::new(HkdfKind::Sha3, b"fenrir", shared_key); + let hkdf = + Hkdf::new(hkdf::Kind::Sha3, b"fenrir", shared_key); let secret_recv = hkdf.get_secret(b"to_server"); let cipher_recv = CipherRecv::new(req.cipher, secret_recv); use crate::enc::sym::AAD; @@ -334,7 +329,7 @@ impl HandshakeTracker { } } - return Ok(HandshakeAction::AuthNeeded(AuthNeededInfo { + return Ok(Action::AuthNeeded(AuthNeededInfo { handshake, hkdf, })); @@ -374,16 +369,14 @@ impl HandshakeTracker { if let Some(timeout) = hshake.timeout { timeout.abort(); } - return Ok(HandshakeAction::ClientConnect( - ClientConnectInfo { - service_id: hshake.service_id, - service_connection_id: hshake.service_conn_id, - handshake, - connection: hshake.connection, - answer: hshake.answer, - srv_key_id: hshake.srv_key_id, - }, - )); + return Ok(Action::ClientConnect(ClientConnectInfo { + service_id: hshake.service_id, + service_connection_id: hshake.service_conn_id, + handshake, + connection: hshake.connection, + answer: hshake.answer, + srv_key_id: hshake.srv_key_id, + })); } }, } diff --git a/src/connection/mod.rs b/src/connection/mod.rs index 2889381..f6c0a86 100644 --- a/src/connection/mod.rs +++ b/src/connection/mod.rs @@ -14,7 +14,7 @@ use crate::{ enc::{ asym::PubKey, hkdf::Hkdf, - sym::{CipherKind, CipherRecv, CipherSend}, + sym::{self, CipherRecv, CipherSend}, Random, }, inner::ThreadTracker, @@ -163,7 +163,7 @@ pub enum Role { impl Conn { pub(crate) fn new( hkdf: Hkdf, - cipher: CipherKind, + cipher: sym::Kind, role: Role, rand: &Random, ) -> Self { diff --git a/src/connection/packet.rs b/src/connection/packet.rs index 545c882..d501fd4 100644 --- a/src/connection/packet.rs +++ b/src/connection/packet.rs @@ -3,7 +3,7 @@ use crate::{ connection, - enc::sym::{HeadLen, TagLen}, + enc::sym::{NonceLen, TagLen}, }; /// Enumerate the possible data in a fenrir packet @@ -17,7 +17,7 @@ pub enum Data { impl Data { /// total length of the data in bytes - pub fn len(&self, head_len: HeadLen, tag_len: TagLen) -> usize { + pub fn len(&self, head_len: NonceLen, tag_len: TagLen) -> usize { match self { Data::Handshake(h) => h.len(head_len, tag_len), Data::Raw(len) => *len, @@ -27,7 +27,7 @@ impl Data { /// NOTE: assumes that there is exactly asa much buffer as needed pub fn serialize( &self, - head_len: HeadLen, + head_len: NonceLen, tag_len: TagLen, out: &mut [u8], ) { @@ -69,14 +69,14 @@ impl Packet { }) } /// get the total length of the packet - pub fn len(&self, head_len: HeadLen, tag_len: TagLen) -> usize { + pub fn len(&self, head_len: NonceLen, tag_len: TagLen) -> usize { connection::ID::len() + self.data.len(head_len, tag_len) } /// serialize packet into buffer /// NOTE: assumes that there is exactly asa much buffer as needed pub fn serialize( &self, - head_len: HeadLen, + head_len: NonceLen, tag_len: TagLen, out: &mut [u8], ) { diff --git a/src/dnssec/record.rs b/src/dnssec/record.rs index a995cea..638ec68 100644 --- a/src/dnssec/record.rs +++ b/src/dnssec/record.rs @@ -43,12 +43,11 @@ //! ] use crate::{ - connection::handshake::HandshakeID, + connection::handshake, enc::{ self, asym::{KeyExchangeKind, KeyID, PubKey}, - hkdf::HkdfKind, - sym::CipherKind, + hkdf, sym, }, }; use ::core::num::NonZeroU16; @@ -180,7 +179,7 @@ pub struct Address { /// Weight of this address in the priority group pub weight: AddressWeight, /// List of supported handshakes - pub handshake_ids: Vec, + pub handshake_ids: Vec, /// Public key IDs used by this address pub public_key_idx: Vec, } @@ -331,7 +330,7 @@ impl Address { for raw_handshake_id in raw[bytes_parsed..(bytes_parsed + num_handshake_ids)].iter() { - match HandshakeID::from_u8(*raw_handshake_id) { + match handshake::ID::from_u8(*raw_handshake_id) { Some(h_id) => handshake_ids.push(h_id), None => { ::tracing::warn!( @@ -392,9 +391,9 @@ pub struct Record { /// List of supported key exchanges pub key_exchanges: Vec, /// List of supported key exchanges - pub hkdfs: Vec, + pub hkdfs: Vec, /// List of supported ciphers - pub ciphers: Vec, + pub ciphers: Vec, } impl Record { @@ -597,7 +596,7 @@ impl Record { num_key_exchanges = num_key_exchanges - 1; } while num_hkdfs > 0 { - let hkdf = match HkdfKind::from_u8(raw[bytes_parsed]) { + let hkdf = match hkdf::Kind::from_u8(raw[bytes_parsed]) { Some(hkdf) => hkdf, None => { // continue parsing. This could be a new hkdf type @@ -615,7 +614,7 @@ impl Record { num_hkdfs = num_hkdfs - 1; } while num_ciphers > 0 { - let cipher = match CipherKind::from_u8(raw[bytes_parsed]) { + let cipher = match sym::Kind::from_u8(raw[bytes_parsed]) { Some(cipher) => cipher, None => { // continue parsing. This could be a new cipher type diff --git a/src/dnssec/tests.rs b/src/dnssec/tests.rs index ff450ae..04cbb18 100644 --- a/src/dnssec/tests.rs +++ b/src/dnssec/tests.rs @@ -12,7 +12,7 @@ fn test_dnssec_serialization() { return; } }; - use crate::{connection::handshake::HandshakeID, enc}; + use crate::{connection::handshake, enc}; let record = Record { public_keys: [( @@ -25,14 +25,14 @@ fn test_dnssec_serialization() { port: Some(::core::num::NonZeroU16::new(31337).unwrap()), priority: record::AddressPriority::P1, weight: record::AddressWeight::W1, - handshake_ids: [HandshakeID::DirectorySynchronized].to_vec(), + handshake_ids: [handshake::ID::DirectorySynchronized].to_vec(), public_key_idx: [record::PubKeyIdx(0)].to_vec(), }] .to_vec(), key_exchanges: [enc::asym::KeyExchangeKind::X25519DiffieHellman] .to_vec(), - hkdfs: [enc::hkdf::HkdfKind::Sha3].to_vec(), - ciphers: [enc::sym::CipherKind::XChaCha20Poly1305].to_vec(), + hkdfs: [enc::hkdf::Kind::Sha3].to_vec(), + ciphers: [enc::sym::Kind::XChaCha20Poly1305].to_vec(), }; let encoded = match record.encode() { Ok(encoded) => encoded, diff --git a/src/enc/asym.rs b/src/enc/asym.rs index 47a3b5a..4958a38 100644 --- a/src/enc/asym.rs +++ b/src/enc/asym.rs @@ -45,7 +45,7 @@ impl ::std::fmt::Display for KeyID { /// Capabilities of each key #[derive(Debug, Clone, Copy)] -pub enum KeyCapabilities { +pub enum Capabilities { /// signing *only* Sign, /// encrypt *only* @@ -61,13 +61,13 @@ pub enum KeyCapabilities { /// All: sign, encrypt, Key Exchange SignEncryptExchage, } -impl KeyCapabilities { +impl Capabilities { /// Check if this key supports eky exchage pub fn has_exchange(&self) -> bool { match self { - KeyCapabilities::Exchange - | KeyCapabilities::SignExchange - | KeyCapabilities::SignEncryptExchage => true, + Capabilities::Exchange + | Capabilities::SignExchange + | Capabilities::SignEncryptExchage => true, _ => false, } } @@ -85,7 +85,7 @@ impl KeyCapabilities { )] #[non_exhaustive] #[repr(u8)] -pub enum KeyKind { +pub enum Kind { /// Ed25519 Public key (sign only) #[strum(serialize = "ed25519")] Ed25519 = 0, @@ -93,25 +93,25 @@ pub enum KeyKind { #[strum(serialize = "x25519")] X25519, } -impl KeyKind { +impl Kind { /// Length of the serialized field pub const fn len() -> usize { 1 } /// return the expected length of the public key pub fn pub_len(&self) -> usize { - KeyKind::len() + Kind::len() + match self { // FIXME: 99% wrong size - KeyKind::Ed25519 => ::ring::signature::ED25519_PUBLIC_KEY_LEN, - KeyKind::X25519 => 32, + Kind::Ed25519 => ::ring::signature::ED25519_PUBLIC_KEY_LEN, + Kind::X25519 => 32, } } /// Get the capabilities of this key type - pub fn capabilities(&self) -> KeyCapabilities { + pub fn capabilities(&self) -> Capabilities { match self { - KeyKind::Ed25519 => KeyCapabilities::Sign, - KeyKind::X25519 => KeyCapabilities::Exchange, + Kind::Ed25519 => Capabilities::Sign, + Kind::X25519 => Capabilities::Exchange, } } /// Returns the key exchanges supported by this key @@ -120,8 +120,8 @@ impl KeyKind { const X25519_KEY_EXCHANGES: [KeyExchangeKind; 1] = [KeyExchangeKind::X25519DiffieHellman]; match self { - KeyKind::Ed25519 => &EMPTY, - KeyKind::X25519 => &X25519_KEY_EXCHANGES, + Kind::Ed25519 => &EMPTY, + Kind::X25519 => &X25519_KEY_EXCHANGES, } } /// generate new keypair @@ -193,21 +193,21 @@ impl PubKey { } } /// return the kind of public key - pub fn kind(&self) -> KeyKind { + pub fn kind(&self) -> Kind { match self { // FIXME: lie, we don't fully support this - PubKey::Signing => KeyKind::Ed25519, + PubKey::Signing => Kind::Ed25519, PubKey::Exchange(ex) => ex.kind(), } } /// generate new keypair fn new_keypair( - kind: KeyKind, + kind: Kind, rnd: &Random, ) -> Result<(PrivKey, PubKey), Error> { match kind { - KeyKind::Ed25519 => todo!(), - KeyKind::X25519 => { + Kind::Ed25519 => todo!(), + Kind::X25519 => { let (priv_key, pub_key) = KeyExchangeKind::X25519DiffieHellman.new_keypair(rnd)?; Ok((PrivKey::Exchange(priv_key), PubKey::Exchange(pub_key))) @@ -231,7 +231,7 @@ impl PubKey { if raw.len() < 1 { return Err(Error::NotEnoughData(0)); } - let kind: KeyKind = match KeyKind::from_u8(raw[0]) { + let kind: Kind = match Kind::from_u8(raw[0]) { Some(kind) => kind, None => return Err(Error::UnsupportedKey(1)), }; @@ -239,11 +239,11 @@ impl PubKey { return Err(Error::NotEnoughData(1)); } match kind { - KeyKind::Ed25519 => { + Kind::Ed25519 => { ::tracing::error!("ed25519 keys are not yet supported"); return Err(Error::Parsing); } - KeyKind::X25519 => { + Kind::X25519 => { let pub_key: ::x25519_dalek::PublicKey = //match ::bincode::deserialize(&raw[1..(1 + kind.pub_len())]) match ::bincode::deserialize(&raw[1..]) @@ -284,7 +284,7 @@ impl PrivKey { } } /// return the kind of public key - pub fn kind(&self) -> KeyKind { + pub fn kind(&self) -> Kind { match self { PrivKey::Signing => todo!(), PrivKey::Exchange(ex) => ex.kind(), @@ -322,13 +322,13 @@ impl ExchangePrivKey { /// Get the serialized key length pub fn len(&self) -> usize { match self { - ExchangePrivKey::X25519(_) => KeyKind::X25519.pub_len(), + ExchangePrivKey::X25519(_) => Kind::X25519.pub_len(), } } /// Get the kind of key - pub fn kind(&self) -> KeyKind { + pub fn kind(&self) -> Kind { match self { - ExchangePrivKey::X25519(_) => KeyKind::X25519, + ExchangePrivKey::X25519(_) => Kind::X25519, } } /// Run the key exchange between two keys of the same kind @@ -372,13 +372,13 @@ impl ExchangePubKey { /// Get the serialized key length pub fn len(&self) -> usize { match self { - ExchangePubKey::X25519(_) => KeyKind::X25519.pub_len(), + ExchangePubKey::X25519(_) => Kind::X25519.pub_len(), } } /// Get the kind of key - pub fn kind(&self) -> KeyKind { + pub fn kind(&self) -> Kind { match self { - ExchangePubKey::X25519(_) => KeyKind::X25519, + ExchangePubKey::X25519(_) => Kind::X25519, } } /// serialize the key into the buffer @@ -396,13 +396,13 @@ impl ExchangePubKey { /// The riesult is "unparsed" since we don't verify /// the actual key pub fn deserialize(raw: &[u8]) -> Result<(Self, usize), Error> { - match KeyKind::from_u8(raw[0]) { + match Kind::from_u8(raw[0]) { Some(kind) => match kind { - KeyKind::Ed25519 => { + Kind::Ed25519 => { ::tracing::error!("ed25519 keys are not yet supported"); return Err(Error::Parsing); } - KeyKind::X25519 => { + Kind::X25519 => { let pub_key: ::x25519_dalek::PublicKey = match ::bincode::deserialize( &raw[1..(1 + kind.pub_len())], diff --git a/src/enc/hkdf.rs b/src/enc/hkdf.rs index e52e236..7ba885b 100644 --- a/src/enc/hkdf.rs +++ b/src/enc/hkdf.rs @@ -18,12 +18,12 @@ use crate::{config::Config, enc::Secret}; )] #[non_exhaustive] #[repr(u8)] -pub enum HkdfKind { +pub enum Kind { /// Sha3 #[strum(serialize = "sha3")] Sha3 = 0, } -impl HkdfKind { +impl Kind { /// Length of the serialized type pub const fn len() -> usize { 1 @@ -34,7 +34,7 @@ impl HkdfKind { #[derive(Clone)] pub enum Hkdf { /// Sha3 based - Sha3(HkdfSha3), + Sha3(Sha3), } // Fake debug implementation to avoid leaking secrets @@ -49,9 +49,9 @@ impl ::core::fmt::Debug for Hkdf { impl Hkdf { /// New Hkdf - pub fn new(kind: HkdfKind, salt: &[u8], key: Secret) -> Self { + pub fn new(kind: Kind, salt: &[u8], key: Secret) -> Self { match kind { - HkdfKind::Sha3 => Self::Sha3(HkdfSha3::new(salt, key)), + Kind::Sha3 => Self::Sha3(Sha3::new(salt, key)), } } /// Get a secret generated from the key and a given context @@ -61,9 +61,9 @@ impl Hkdf { } } /// get the kind of this Hkdf - pub fn kind(&self) -> HkdfKind { + pub fn kind(&self) -> Kind { match self { - Hkdf::Sha3(_) => HkdfKind::Sha3, + Hkdf::Sha3(_) => Kind::Sha3, } } } @@ -106,11 +106,11 @@ impl Clone for HkdfInner { /// Sha3 based HKDF #[derive(Clone)] -pub struct HkdfSha3 { +pub struct Sha3 { inner: HkdfInner, } -impl HkdfSha3 { +impl Sha3 { /// Instantiate a new HKDF with Sha3-256 pub(crate) fn new(salt: &[u8], key: Secret) -> Self { let hkdf = ::hkdf::Hkdf::::new(Some(salt), key.as_ref()); @@ -132,7 +132,7 @@ impl HkdfSha3 { } // Fake debug implementation to avoid leaking secrets -impl ::core::fmt::Debug for HkdfSha3 { +impl ::core::fmt::Debug for Sha3 { fn fmt( &self, f: &mut core::fmt::Formatter<'_>, @@ -146,8 +146,8 @@ impl ::core::fmt::Debug for HkdfSha3 { /// Give priority to our list pub fn server_select_hkdf( cfg: &Config, - client_supported: &Vec, -) -> Option { + client_supported: &Vec, +) -> Option { cfg.hkdfs .iter() .find(|h| client_supported.contains(h)) @@ -159,8 +159,8 @@ pub fn server_select_hkdf( /// this is used only in the directory synchronized handshake pub fn client_select_hkdf( cfg: &Config, - server_supported: &Vec, -) -> Option { + server_supported: &Vec, +) -> Option { server_supported .iter() .find(|h| cfg.hkdfs.contains(h)) diff --git a/src/enc/sym.rs b/src/enc/sym.rs index d4204e0..14d712d 100644 --- a/src/enc/sym.rs +++ b/src/enc/sym.rs @@ -17,20 +17,20 @@ use crate::{ ::strum_macros::IntoStaticStr, )] #[repr(u8)] -pub enum CipherKind { +pub enum Kind { /// XChaCha20_Poly1305 #[strum(serialize = "xchacha20poly1305")] XChaCha20Poly1305 = 0, } -impl CipherKind { +impl Kind { /// length of the serialized id for the cipher kind field pub const fn len() -> usize { 1 } /// required length of the nonce - pub fn nonce_len(&self) -> HeadLen { - HeadLen(Nonce::len()) + pub fn nonce_len(&self) -> NonceLen { + Nonce::len() } /// required length of the key pub fn key_len(&self) -> usize { @@ -48,21 +48,10 @@ impl CipherKind { #[derive(Debug)] pub struct AAD<'a>(pub &'a [u8]); -/// Cipher direction, to make sure we don't reuse the same cipher -/// for both decrypting and encrypting -#[derive(Debug, Copy, Clone)] -#[repr(u8)] -pub enum CipherDirection { - /// Receive, to decrypt only - Recv = 0, - /// Send, to encrypt only - Send, -} - /// strong typedef for header length /// aka: nonce length in the encrypted data) #[derive(Debug, Copy, Clone)] -pub struct HeadLen(pub usize); +pub struct NonceLen(pub usize); /// strong typedef for the Tag length /// aka: cryptographic authentication tag length at the end /// of the encrypted data @@ -77,21 +66,21 @@ enum Cipher { impl Cipher { /// Build a new Cipher - fn new(kind: CipherKind, secret: Secret) -> Self { + fn new(kind: Kind, secret: Secret) -> Self { match kind { - CipherKind::XChaCha20Poly1305 => { + Kind::XChaCha20Poly1305 => { Self::XChaCha20Poly1305(XChaCha20Poly1305::new(secret)) } } } - pub fn kind(&self) -> CipherKind { + pub fn kind(&self) -> Kind { match self { - Cipher::XChaCha20Poly1305(_) => CipherKind::XChaCha20Poly1305, + Cipher::XChaCha20Poly1305(_) => Kind::XChaCha20Poly1305, } } - fn nonce_len(&self) -> HeadLen { + fn nonce_len(&self) -> NonceLen { match self { - Cipher::XChaCha20Poly1305(_) => HeadLen(Nonce::len()), + Cipher::XChaCha20Poly1305(_) => Nonce::len(), } } fn tag_len(&self) -> TagLen { @@ -117,7 +106,7 @@ impl Cipher { return Err(Error::NotEnoughData(raw_data.len())); } let (nonce_bytes, data_and_tag) = - raw_data.split_at_mut(Nonce::len()); + raw_data.split_at_mut(Nonce::len().0); let (data_notag, tag_bytes) = data_and_tag.split_at_mut( data_and_tag.len() - ::ring::aead::CHACHA20_POLY1305.tag_len(), @@ -137,20 +126,20 @@ impl Cipher { }; //data.drain(..Nonce::len()); //data.truncate(final_len); - Ok(&raw_data[Nonce::len()..Nonce::len() + final_len]) + Ok(&raw_data[Nonce::len().0..Nonce::len().0 + final_len]) } } } fn overhead(&self) -> usize { match self { Cipher::XChaCha20Poly1305(_) => { - let cipher = CipherKind::XChaCha20Poly1305; + let cipher = Kind::XChaCha20Poly1305; cipher.nonce_len().0 + cipher.tag_len().0 } } } fn encrypt( - &self, + &mut self, nonce: &Nonce, aad: AAD, data: &mut [u8], @@ -162,13 +151,13 @@ impl Cipher { let tag_len: usize = ::ring::aead::CHACHA20_POLY1305.tag_len(); let data_len_notag = data.len() - tag_len; // write nonce - data[..Nonce::len()].copy_from_slice(nonce.as_bytes()); + data[..Nonce::len().0].copy_from_slice(nonce.as_bytes()); // encrypt data match cipher.cipher.encrypt_in_place_detached( nonce.as_bytes().into(), aad.0, - &mut data[Nonce::len()..data_len_notag], + &mut data[Nonce::len().0..data_len_notag], ) { Ok(tag) => { data[data_len_notag..].copy_from_slice(tag.as_slice()); @@ -194,11 +183,11 @@ impl ::core::fmt::Debug for CipherRecv { impl CipherRecv { /// Build a new Cipher - pub fn new(kind: CipherKind, secret: Secret) -> Self { + pub fn new(kind: Kind, secret: Secret) -> Self { Self(Cipher::new(kind, secret)) } /// Get the length of the nonce for this cipher - pub fn nonce_len(&self) -> HeadLen { + pub fn nonce_len(&self) -> NonceLen { self.0.nonce_len() } /// Get the length of the nonce for this cipher @@ -215,14 +204,14 @@ impl CipherRecv { self.0.decrypt(aad, data) } /// return the underlying cipher id - pub fn kind(&self) -> CipherKind { + pub fn kind(&self) -> Kind { self.0.kind() } } /// Send only cipher pub struct CipherSend { - nonce: NonceSync, + nonce: Nonce, cipher: Cipher, } impl ::core::fmt::Debug for CipherSend { @@ -236,20 +225,20 @@ impl ::core::fmt::Debug for CipherSend { impl CipherSend { /// Build a new Cipher - pub fn new(kind: CipherKind, secret: Secret, rand: &Random) -> Self { + pub fn new(kind: Kind, secret: Secret, rand: &Random) -> Self { Self { - nonce: NonceSync::new(rand), + nonce: Nonce::new(rand), cipher: Cipher::new(kind, secret), } } /// Encrypt the given data - pub fn encrypt(&self, aad: AAD, data: &mut [u8]) -> Result<(), Error> { + pub fn encrypt(&mut self, aad: AAD, data: &mut [u8]) -> Result<(), Error> { let old_nonce = self.nonce.advance(); self.cipher.encrypt(&old_nonce, aad, data)?; Ok(()) } /// return the underlying cipher id - pub fn kind(&self) -> CipherKind { + pub fn kind(&self) -> Kind { self.cipher.kind() } } @@ -285,7 +274,7 @@ struct NonceNum { #[repr(C)] pub union Nonce { num: NonceNum, - raw: [u8; Self::len()], + raw: [u8; Self::len().0], } impl ::core::fmt::Debug for Nonce { @@ -303,17 +292,17 @@ impl ::core::fmt::Debug for Nonce { impl Nonce { /// Generate a new random Nonce pub fn new(rand: &Random) -> Self { - let mut raw = [0; Self::len()]; + let mut raw = [0; Self::len().0]; rand.fill(&mut raw); Self { raw } } /// Length of this nonce in bytes - pub const fn len() -> usize { + pub const fn len() -> NonceLen { // FIXME: was:12. xchacha20poly1305 requires 24. // but we should change keys much earlier than that, and our // nonces are not random, but sequential. // we should change keys every 2^30 bytes to be sure (stream max window) - return 24; + return NonceLen(24); } /// Get reference to the nonce bytes pub fn as_bytes(&self) -> &[u8] { @@ -323,11 +312,12 @@ impl Nonce { } } /// Create Nonce from array - pub fn from_slice(raw: [u8; Self::len()]) -> Self { + pub fn from_slice(raw: [u8; Self::len().0]) -> Self { Self { raw } } /// Go to the next nonce - pub fn advance(&mut self) { + pub fn advance(&mut self) -> Self { + let old_nonce = self.clone(); #[allow(unsafe_code)] unsafe { let old_low = self.num.low; @@ -336,40 +326,17 @@ impl Nonce { self.num.high = self.num.high; } } - } -} - -/// Synchronize the mutex acess with a nonce for multithread safety -// TODO: remove mutex, not needed anymore -#[derive(Debug)] -pub struct NonceSync { - nonce: ::std::sync::Mutex, -} -impl NonceSync { - /// Create a new thread safe nonce - pub fn new(rand: &Random) -> Self { - Self { - nonce: ::std::sync::Mutex::new(Nonce::new(rand)), - } - } - /// Advance the nonce and return the *old* value - pub fn advance(&self) -> Nonce { - let old_nonce: Nonce; - { - let mut nonce = self.nonce.lock().unwrap(); - old_nonce = *nonce; - nonce.advance(); - } old_nonce } } + /// Select the best cipher from our supported list /// and the other endpoint supported list. /// Give priority to our list pub fn server_select_cipher( cfg: &Config, - client_supported: &Vec, -) -> Option { + client_supported: &Vec, +) -> Option { cfg.ciphers .iter() .find(|c| client_supported.contains(c)) @@ -381,8 +348,8 @@ pub fn server_select_cipher( /// This is used only in the Directory synchronized handshake pub fn client_select_cipher( cfg: &Config, - server_supported: &Vec, -) -> Option { + server_supported: &Vec, +) -> Option { server_supported .iter() .find(|c| cfg.ciphers.contains(c)) diff --git a/src/enc/tests.rs b/src/enc/tests.rs index ead07ee..af2ddef 100644 --- a/src/enc/tests.rs +++ b/src/enc/tests.rs @@ -1,24 +1,26 @@ use crate::{ - auth, - connection::{handshake::*, ID}, + connection::{ + handshake::{self, *}, + ID, + }, enc::{self, asym::KeyID}, }; #[test] fn test_simple_encrypt_decrypt() { let rand = enc::Random::new(); - let cipher = enc::sym::CipherKind::XChaCha20Poly1305; + let cipher = enc::sym::Kind::XChaCha20Poly1305; let secret = enc::Secret::new_rand(&rand); let secret2 = secret.clone(); - let cipher_send = enc::sym::CipherSend::new(cipher, secret, &rand); + let mut cipher_send = enc::sym::CipherSend::new(cipher, secret, &rand); let cipher_recv = enc::sym::CipherRecv::new(cipher, secret2); let mut data = Vec::new(); let tot_len = cipher_recv.nonce_len().0 + 1234 + cipher_recv.tag_len().0; data.resize(tot_len, 0); rand.fill(&mut data); - data[..enc::sym::Nonce::len()].copy_from_slice(&[0; 24]); + data[..enc::sym::Nonce::len().0].copy_from_slice(&[0; 24]); let last = data.len() - cipher_recv.tag_len().0; data[last..].copy_from_slice(&[0; 16]); let orig = data.clone(); @@ -31,7 +33,7 @@ fn test_simple_encrypt_decrypt() { if cipher_recv.decrypt(aad2, &mut data).is_err() { assert!(false, "Decrypt failed"); } - data[..enc::sym::Nonce::len()].copy_from_slice(&[0; 24]); + data[..enc::sym::Nonce::len().0].copy_from_slice(&[0; 24]); let last = data.len() - cipher_recv.tag_len().0; data[last..].copy_from_slice(&[0; 16]); assert!(orig == data, "DIFFERENT!\n{:?}\n{:?}\n", orig, data); @@ -40,18 +42,18 @@ fn test_simple_encrypt_decrypt() { #[test] fn test_encrypt_decrypt() { let rand = enc::Random::new(); - let cipher = enc::sym::CipherKind::XChaCha20Poly1305; + let cipher = enc::sym::Kind::XChaCha20Poly1305; let secret = enc::Secret::new_rand(&rand); let secret2 = secret.clone(); - let cipher_send = enc::sym::CipherSend::new(cipher, secret, &rand); + let mut cipher_send = enc::sym::CipherSend::new(cipher, secret, &rand); let cipher_recv = enc::sym::CipherRecv::new(cipher, secret2); let nonce_len = cipher_recv.nonce_len(); let tag_len = cipher_recv.tag_len(); let service_key = enc::Secret::new_rand(&rand); - let data = dirsync::RespInner::ClearText(dirsync::RespData { + let data = dirsync::RespState::ClearText(dirsync::RespData { client_nonce: dirsync::Nonce::new(&rand), id: ID::ID(::core::num::NonZeroU64::new(424242).unwrap()), service_connection_id: ID::ID( @@ -68,7 +70,7 @@ fn test_encrypt_decrypt() { let encrypt_to = encrypt_from + resp.encrypted_length(nonce_len, tag_len); let h_resp = - Handshake::new(HandshakeData::DirSync(dirsync::DirSync::Resp(resp))); + Handshake::new(handshake::Data::DirSync(dirsync::DirSync::Resp(resp))); let mut bytes = Vec::::with_capacity( h_resp.len(cipher.nonce_len(), cipher.tag_len()), @@ -117,7 +119,7 @@ fn test_encrypt_decrypt() { } }; // reparse - if let HandshakeData::DirSync(dirsync::DirSync::Resp(r_a)) = + if let handshake::Data::DirSync(dirsync::DirSync::Resp(r_a)) = &mut deserialized.data { let enc_start = r_a.encrypted_offset() + cipher.nonce_len().0; diff --git a/src/inner/worker.rs b/src/inner/worker.rs index cdd5d75..80176ca 100644 --- a/src/inner/worker.rs +++ b/src/inner/worker.rs @@ -7,8 +7,7 @@ use crate::{ handshake::{ self, dirsync::{self, DirSync}, - tracker::{HandshakeAction, HandshakeTracker}, - Handshake, HandshakeData, + Handshake, }, packet::{self, Packet}, socket::{UdpClient, UdpServer}, @@ -17,7 +16,7 @@ use crate::{ dnssec, enc::{ asym::{self, KeyID, PrivKey, PubKey}, - hkdf::{self, Hkdf, HkdfKind}, + hkdf::{self, Hkdf}, sym, Random, Secret, }, inner::ThreadTracker, @@ -69,7 +68,7 @@ pub struct Worker { queue_timeouts_send: mpsc::UnboundedSender, thread_channels: Vec<::async_channel::Sender>, connections: ConnList, - handshakes: HandshakeTracker, + handshakes: handshake::Tracker, } #[allow(unsafe_code)] @@ -86,7 +85,7 @@ impl Worker { ) -> ::std::io::Result { let (queue_timeouts_send, queue_timeouts_recv) = mpsc::unbounded_channel(); - let mut handshakes = HandshakeTracker::new( + let mut handshakes = handshake::Tracker::new( thread_id, cfg.ciphers.clone(), cfg.key_exchanges.clone(), @@ -345,7 +344,7 @@ impl Worker { hkdf: hkdf_selected, cipher: cipher_selected, exchange_key: pub_key, - data: dirsync::ReqInner::ClearText(req_data), + data: dirsync::ReqState::ClearText(req_data), }; let encrypt_start = connection::ID::len() + req.encrypted_offset(); @@ -354,7 +353,7 @@ impl Worker { cipher_selected.nonce_len(), cipher_selected.tag_len(), ); - let h_req = Handshake::new(HandshakeData::DirSync( + let h_req = Handshake::new(handshake::Data::DirSync( DirSync::Req(req), )); let packet = Packet { @@ -450,9 +449,9 @@ impl Worker { } }; match action { - HandshakeAction::AuthNeeded(authinfo) => { + handshake::Action::AuthNeeded(authinfo) => { let req; - if let HandshakeData::DirSync(DirSync::Req(r)) = + if let handshake::Data::DirSync(DirSync::Req(r)) = authinfo.handshake.data { req = r; @@ -460,9 +459,9 @@ impl Worker { ::tracing::error!("AuthInfo on non DS::Req"); return; } - use dirsync::ReqInner; + use dirsync::ReqState; let req_data = match req.data { - ReqInner::ClearText(req_data) => req_data, + ReqState::ClearText(req_data) => req_data, _ => { ::tracing::error!("AuthNeeded: expected ClearText"); assert!(false, "AuthNeeded: unreachable"); @@ -538,17 +537,17 @@ impl Worker { // no aad for now let aad = AAD(&mut []); - use dirsync::RespInner; + use dirsync::RespState; let resp = dirsync::Resp { client_key_id: req_data.client_key_id, - data: RespInner::ClearText(resp_data), + data: RespState::ClearText(resp_data), }; let encrypt_from = connection::ID::len() + resp.encrypted_offset(); let encrypt_until = encrypt_from + resp.encrypted_length(head_len, tag_len); let resp_handshake = Handshake::new( - HandshakeData::DirSync(DirSync::Resp(resp)), + handshake::Data::DirSync(DirSync::Resp(resp)), ); let packet = Packet { id: connection::ID::new_handshake(), @@ -568,9 +567,9 @@ impl Worker { } self.send_packet(raw_out, udp.src, udp.dst).await; } - HandshakeAction::ClientConnect(cci) => { + handshake::Action::ClientConnect(cci) => { let ds_resp; - if let HandshakeData::DirSync(DirSync::Resp(resp)) = + if let handshake::Data::DirSync(DirSync::Resp(resp)) = cci.handshake.data { ds_resp = resp; @@ -580,7 +579,7 @@ impl Worker { } // track connection let resp_data; - if let dirsync::RespInner::ClearText(r_data) = ds_resp.data + if let dirsync::RespState::ClearText(r_data) = ds_resp.data { resp_data = r_data; } else { @@ -609,7 +608,7 @@ impl Worker { //FIXME: the Secret should be XORed with the client // stored secret (if any) let hkdf = Hkdf::new( - HkdfKind::Sha3, + hkdf::Kind::Sha3, cci.service_id.as_bytes(), resp_data.service_key, ); @@ -628,7 +627,7 @@ impl Worker { let _ = cci.answer.send(Ok((cci.srv_key_id, auth_srv_conn))); } - HandshakeAction::Nothing => {} + handshake::Action::Nothing => {} }; } } diff --git a/src/tests.rs b/src/tests.rs index acf57cc..4e2a0b6 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -62,10 +62,7 @@ async fn test_connection_dirsync() { rt.block_on(local_thread); }); - use crate::{ - connection::handshake::HandshakeID, - dnssec::{record, Record}, - }; + use crate::dnssec::{record, Record}; let port: u16 = server.addresses()[0].port(); @@ -76,14 +73,14 @@ async fn test_connection_dirsync() { port: Some(::core::num::NonZeroU16::new(port).unwrap()), priority: record::AddressPriority::P1, weight: record::AddressWeight::W1, - handshake_ids: [HandshakeID::DirectorySynchronized].to_vec(), + handshake_ids: [handshake::ID::DirectorySynchronized].to_vec(), public_key_idx: [record::PubKeyIdx(0)].to_vec(), }] .to_vec(), key_exchanges: [enc::asym::KeyExchangeKind::X25519DiffieHellman] .to_vec(), - hkdfs: [enc::hkdf::HkdfKind::Sha3].to_vec(), - ciphers: [enc::sym::CipherKind::XChaCha20Poly1305].to_vec(), + hkdfs: [enc::hkdf::Kind::Sha3].to_vec(), + ciphers: [enc::sym::Kind::XChaCha20Poly1305].to_vec(), }; ::tokio::time::sleep(::std::time::Duration::from_millis(500)).await; -- 2.44.1 From 5dff5c8c9aab3355d1d289b7f3186abb2e348950 Mon Sep 17 00:00:00 2001 From: Luca Fulchir Date: Mon, 19 Jun 2023 21:57:27 +0200 Subject: [PATCH 3/3] Namespace split the dirsync request/response There was no big problem, but it was messy Signed-off-by: Luca Fulchir --- src/connection/handshake/dirsync/mod.rs | 77 +++++ .../handshake/{dirsync.rs => dirsync/req.rs} | 287 ++---------------- src/connection/handshake/dirsync/resp.rs | 189 ++++++++++++ src/connection/handshake/mod.rs | 6 +- src/connection/handshake/tests.rs | 10 +- src/enc/tests.rs | 4 +- src/inner/worker.rs | 21 +- 7 files changed, 312 insertions(+), 282 deletions(-) create mode 100644 src/connection/handshake/dirsync/mod.rs rename src/connection/handshake/{dirsync.rs => dirsync/req.rs} (54%) create mode 100644 src/connection/handshake/dirsync/resp.rs diff --git a/src/connection/handshake/dirsync/mod.rs b/src/connection/handshake/dirsync/mod.rs new file mode 100644 index 0000000..411c283 --- /dev/null +++ b/src/connection/handshake/dirsync/mod.rs @@ -0,0 +1,77 @@ +//! Directory synchronized handshake +//! 1-RTT connection +//! +//! The simplest, fastest handshake supported by Fenrir +//! Downside: It does not offer protection from DDos, +//! no perfect forward secrecy +//! +//! To grant a form of perfect forward secrecy, the server should periodically +//! change the DNSSEC public/private keys + +use crate::enc::{ + sym::{NonceLen, TagLen}, + Random, +}; + +pub mod req; +pub mod resp; + +// TODO: merge with crate::enc::sym::Nonce +/// random nonce +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct Nonce(pub(crate) [u8; 16]); + +impl Nonce { + /// Create a new random Nonce + pub fn new(rnd: &Random) -> Self { + use ::core::mem::MaybeUninit; + let mut out: MaybeUninit<[u8; 16]>; + #[allow(unsafe_code)] + unsafe { + out = MaybeUninit::uninit(); + let _ = rnd.fill(out.assume_init_mut()); + Self(out.assume_init()) + } + } + /// Length of the serialized Nonce + pub const fn len() -> usize { + 16 + } +} +impl From<&[u8; 16]> for Nonce { + fn from(raw: &[u8; 16]) -> Self { + Self(raw.clone()) + } +} + +/// Parsed handshake +#[derive(Debug, Clone, PartialEq)] +pub enum DirSync { + /// Directory synchronized handshake: client request + Req(req::Req), + /// Directory synchronized handshake: server response + Resp(resp::Resp), +} + +impl DirSync { + /// actual length of the dirsync handshake data + pub fn len(&self, head_len: NonceLen, tag_len: TagLen) -> usize { + match self { + DirSync::Req(req) => req.len(), + DirSync::Resp(resp) => resp.len(head_len, tag_len), + } + } + /// Serialize into raw bytes + /// NOTE: assumes that there is exactly asa much buffer as needed + pub fn serialize( + &self, + head_len: NonceLen, + tag_len: TagLen, + out: &mut [u8], + ) { + match self { + DirSync::Req(req) => req.serialize(head_len, tag_len, out), + DirSync::Resp(resp) => resp.serialize(head_len, tag_len, out), + } + } +} diff --git a/src/connection/handshake/dirsync.rs b/src/connection/handshake/dirsync/req.rs similarity index 54% rename from src/connection/handshake/dirsync.rs rename to src/connection/handshake/dirsync/req.rs index 676af37..906b149 100644 --- a/src/connection/handshake/dirsync.rs +++ b/src/connection/handshake/dirsync/req.rs @@ -1,85 +1,22 @@ -//! Directory synchronized handshake -//! 1-RTT connection -//! -//! The simplest, fastest handshake supported by Fenrir -//! Downside: It does not offer protection from DDos, -//! no perfect forward secrecy -//! -//! To grant a form of perfect forward secrecy, the server should periodically -//! change the DNSSEC public/private keys +//! Directory synchronized handshake, Request parsing -use super::Error; use crate::{ auth, - connection::{handshake, ProtocolVersion, ID}, + connection::{ + handshake::{ + self, + dirsync::{DirSync, Nonce}, + Error, + }, + ProtocolVersion, ID, + }, enc::{ asym::{ExchangePubKey, KeyExchangeKind, KeyID}, hkdf, sym::{self, NonceLen, TagLen}, - Random, Secret, }, }; -// TODO: merge with crate::enc::sym::Nonce -/// random nonce -#[derive(Debug, Clone, Copy, PartialEq)] -pub struct Nonce(pub(crate) [u8; 16]); - -impl Nonce { - /// Create a new random Nonce - pub fn new(rnd: &Random) -> Self { - use ::core::mem::MaybeUninit; - let mut out: MaybeUninit<[u8; 16]>; - #[allow(unsafe_code)] - unsafe { - out = MaybeUninit::uninit(); - let _ = rnd.fill(out.assume_init_mut()); - Self(out.assume_init()) - } - } - /// Length of the serialized Nonce - pub const fn len() -> usize { - 16 - } -} -impl From<&[u8; 16]> for Nonce { - fn from(raw: &[u8; 16]) -> Self { - Self(raw.clone()) - } -} - -/// Parsed handshake -#[derive(Debug, Clone, PartialEq)] -pub enum DirSync { - /// Directory synchronized handshake: client request - Req(Req), - /// Directory synchronized handshake: server response - Resp(Resp), -} - -impl DirSync { - /// actual length of the dirsync handshake data - pub fn len(&self, head_len: NonceLen, tag_len: TagLen) -> usize { - match self { - DirSync::Req(req) => req.len(), - DirSync::Resp(resp) => resp.len(head_len, tag_len), - } - } - /// Serialize into raw bytes - /// NOTE: assumes that there is exactly asa much buffer as needed - pub fn serialize( - &self, - head_len: NonceLen, - tag_len: TagLen, - out: &mut [u8], - ) { - match self { - DirSync::Req(req) => req.serialize(head_len, tag_len, out), - DirSync::Resp(resp) => resp.serialize(head_len, tag_len, out), - } - } -} - /// Client request of a directory synchronized handshake #[derive(Debug, Clone, PartialEq)] pub struct Req { @@ -94,7 +31,7 @@ pub struct Req { /// Client ephemeral public key used for key exchanges pub exchange_key: ExchangePubKey, /// encrypted data - pub data: ReqState, + pub data: State, // SECURITY: TODO: Add padding to min: 1200 bytes // to avoid amplification attaks // also: 1200 < 1280 to allow better vpn compatibility @@ -119,8 +56,8 @@ impl Req { tag_len: TagLen, ) -> usize { match &self.data { - ReqState::ClearText(data) => data.len() + head_len.0 + tag_len.0, - ReqState::CipherText(length) => *length, + State::ClearText(data) => data.len() + head_len.0 + tag_len.0, + State::CipherText(length) => *length, } } /// actual length of the directory synchronized request @@ -150,7 +87,7 @@ impl Req { let written_next = 5 + key_len; self.exchange_key.serialize_into(&mut out[5..written_next]); let written = written_next; - if let ReqState::ClearText(data) = &self.data { + if let State::ClearText(data) = &self.data { let from = written + head_len.0; let to = out.len() - tag_len.0; data.serialize(&mut out[from..to]); @@ -190,7 +127,7 @@ impl handshake::Parsing for Req { Ok(exchange_key) => exchange_key, Err(e) => return Err(e.into()), }; - let data = ReqState::CipherText(raw.len() - (CURR_SIZE + len)); + let data = State::CipherText(raw.len() - (CURR_SIZE + len)); Ok(handshake::Data::DirSync(DirSync::Req(Self { key_id, exchange, @@ -204,18 +141,18 @@ impl handshake::Parsing for Req { /// Quick way to avoid mixing cipher and clear text #[derive(Debug, Clone, PartialEq)] -pub enum ReqState { +pub enum State { /// Data is still encrytped, we only keep the length CipherText(usize), /// Client data, decrypted and parsed - ClearText(ReqData), + ClearText(Data), } -impl ReqState { +impl State { /// The length of the data pub fn len(&self) -> usize { match self { - ReqState::CipherText(len) => *len, - ReqState::ClearText(data) => data.len(), + State::CipherText(len) => *len, + State::ClearText(data) => data.len(), } } /// parse the cleartext @@ -224,19 +161,19 @@ impl ReqState { raw: &[u8], ) -> Result<(), Error> { let clear = match self { - ReqState::CipherText(len) => { + State::CipherText(len) => { assert!( *len > raw.len(), - "DirSync::ReqState::CipherText length mismatch" + "DirSync::State::CipherText length mismatch" ); - match ReqData::deserialize(raw) { + match Data::deserialize(raw) { Ok(clear) => clear, Err(e) => return Err(e), } } _ => return Err(Error::Parsing), }; - *self = ReqState::ClearText(clear); + *self = State::ClearText(clear); Ok(()) } } @@ -321,7 +258,7 @@ impl AuthInfo { /// Decrypted request data #[derive(Debug, Clone, PartialEq)] -pub struct ReqData { +pub struct Data { /// Random nonce, the client can use this to track multiple key exchanges pub nonce: Nonce, /// Client key id so the client can use and rotate keys @@ -331,7 +268,7 @@ pub struct ReqData { /// Authentication data pub auth: AuthInfo, } -impl ReqData { +impl Data { /// actual length of the request data pub fn len(&self) -> usize { Nonce::len() + KeyID::len() + ID::len() + self.auth.len() @@ -383,177 +320,3 @@ impl ReqData { }) } } - -/// Quick way to avoid mixing cipher and clear text -#[derive(Debug, Clone, PartialEq)] -pub enum RespState { - /// Server data, still in ciphertext - CipherText(usize), - /// Parsed, cleartext server data - ClearText(RespData), -} -impl RespState { - /// The length of the data - pub fn len(&self) -> usize { - match self { - RespState::CipherText(len) => *len, - RespState::ClearText(_) => RespData::len(), - } - } - /// parse the cleartext - pub fn deserialize_as_cleartext( - &mut self, - raw: &[u8], - ) -> Result<(), Error> { - let clear = match self { - RespState::CipherText(len) => { - assert!( - *len > raw.len(), - "DirSync::RespState::CipherText length mismatch" - ); - match RespData::deserialize(raw) { - Ok(clear) => clear, - Err(e) => return Err(e), - } - } - _ => return Err(Error::Parsing), - }; - *self = RespState::ClearText(clear); - Ok(()) - } - /// Serialize the still cleartext data - pub fn serialize(&self, out: &mut [u8]) { - if let RespState::ClearText(clear) = &self { - clear.serialize(out); - } - } -} - -/// Server response in a directory synchronized handshake -#[derive(Debug, Clone, PartialEq)] -pub struct Resp { - /// Tells the client with which key the exchange was done - pub client_key_id: KeyID, - /// actual response data, might be encrypted - pub data: RespState, -} - -impl handshake::Parsing for Resp { - fn deserialize(raw: &[u8]) -> Result { - const MIN_PKT_LEN: usize = 68; - if raw.len() < MIN_PKT_LEN { - return Err(Error::NotEnoughData); - } - let client_key_id: KeyID = - KeyID(u16::from_le_bytes(raw[0..KeyID::len()].try_into().unwrap())); - Ok(handshake::Data::DirSync(DirSync::Resp(Self { - client_key_id, - data: RespState::CipherText(raw[KeyID::len()..].len()), - }))) - } -} - -impl Resp { - /// return the offset of the encrypted data - /// NOTE: starts from the beginning of the fenrir packet - pub fn encrypted_offset(&self) -> usize { - ProtocolVersion::len() + handshake::ID::len() + KeyID::len() - } - /// return the total length of the cleartext data - pub fn encrypted_length( - &self, - head_len: NonceLen, - tag_len: TagLen, - ) -> usize { - match &self.data { - RespState::ClearText(_data) => { - RespData::len() + head_len.0 + tag_len.0 - } - RespState::CipherText(len) => *len, - } - } - /// Total length of the response handshake - pub fn len(&self, head_len: NonceLen, tag_len: TagLen) -> usize { - KeyID::len() + head_len.0 + self.data.len() + tag_len.0 - } - /// Serialize into raw bytes - /// NOTE: assumes that there is exactly as much buffer as needed - pub fn serialize( - &self, - head_len: NonceLen, - _tag_len: TagLen, - out: &mut [u8], - ) { - out[0..KeyID::len()] - .copy_from_slice(&self.client_key_id.0.to_le_bytes()); - let start_data = KeyID::len() + head_len.0; - let end_data = start_data + self.data.len(); - self.data.serialize(&mut out[start_data..end_data]); - } -} - -/// Decrypted response data -#[derive(Debug, Clone, PartialEq)] -pub struct RespData { - /// Client nonce, copied from the request - pub client_nonce: Nonce, - /// Server Connection ID - pub id: ID, - /// Service Connection ID - pub service_connection_id: ID, - /// Service encryption key - pub service_key: Secret, -} - -impl RespData { - /// Return the expected length for buffer allocation - pub fn len() -> usize { - Nonce::len() + ID::len() + ID::len() + Secret::len() - } - /// Serialize the data into a buffer - /// NOTE: assumes that there is exactly asa much buffer as needed - pub fn serialize(&self, out: &mut [u8]) { - let mut start = 0; - let mut end = Nonce::len(); - out[start..end].copy_from_slice(&self.client_nonce.0); - start = end; - end = end + ID::len(); - self.id.serialize(&mut out[start..end]); - start = end; - end = end + ID::len(); - self.service_connection_id.serialize(&mut out[start..end]); - start = end; - end = end + Secret::len(); - out[start..end].copy_from_slice(self.service_key.as_ref()); - } - /// Parse the cleartext raw data - pub fn deserialize(raw: &[u8]) -> Result { - let raw_sized: &[u8; 16] = raw[..Nonce::len()].try_into().unwrap(); - let client_nonce: Nonce = raw_sized.into(); - let end = Nonce::len() + ID::len(); - let id: ID = - u64::from_le_bytes(raw[Nonce::len()..end].try_into().unwrap()) - .into(); - if id.is_handshake() { - return Err(Error::Parsing); - } - let parsed = end; - let end = parsed + ID::len(); - let service_connection_id: ID = - u64::from_le_bytes(raw[parsed..end].try_into().unwrap()).into(); - if service_connection_id.is_handshake() { - return Err(Error::Parsing); - } - let parsed = end; - let end = parsed + Secret::len(); - let raw_secret: &[u8; 32] = raw[parsed..end].try_into().unwrap(); - let service_key = raw_secret.into(); - - Ok(Self { - client_nonce, - id, - service_connection_id, - service_key, - }) - } -} diff --git a/src/connection/handshake/dirsync/resp.rs b/src/connection/handshake/dirsync/resp.rs new file mode 100644 index 0000000..cf1b99f --- /dev/null +++ b/src/connection/handshake/dirsync/resp.rs @@ -0,0 +1,189 @@ +//! Directory synchronized handshake, Response parsing + +use crate::{ + connection::{ + handshake::{ + self, + dirsync::{DirSync, Nonce}, + Error, + }, + ProtocolVersion, ID, + }, + enc::{ + asym::KeyID, + sym::{NonceLen, TagLen}, + Secret, + }, +}; + +/// Server response in a directory synchronized handshake +#[derive(Debug, Clone, PartialEq)] +pub struct Resp { + /// Tells the client with which key the exchange was done + pub client_key_id: KeyID, + /// actual response data, might be encrypted + pub data: State, +} + +impl handshake::Parsing for Resp { + fn deserialize(raw: &[u8]) -> Result { + const MIN_PKT_LEN: usize = 68; + if raw.len() < MIN_PKT_LEN { + return Err(Error::NotEnoughData); + } + let client_key_id: KeyID = + KeyID(u16::from_le_bytes(raw[0..KeyID::len()].try_into().unwrap())); + Ok(handshake::Data::DirSync(DirSync::Resp(Self { + client_key_id, + data: State::CipherText(raw[KeyID::len()..].len()), + }))) + } +} + +impl Resp { + /// return the offset of the encrypted data + /// NOTE: starts from the beginning of the fenrir packet + pub fn encrypted_offset(&self) -> usize { + ProtocolVersion::len() + handshake::ID::len() + KeyID::len() + } + /// return the total length of the cleartext data + pub fn encrypted_length( + &self, + head_len: NonceLen, + tag_len: TagLen, + ) -> usize { + match &self.data { + State::ClearText(_data) => Data::len() + head_len.0 + tag_len.0, + State::CipherText(len) => *len, + } + } + /// Total length of the response handshake + pub fn len(&self, head_len: NonceLen, tag_len: TagLen) -> usize { + KeyID::len() + head_len.0 + self.data.len() + tag_len.0 + } + /// Serialize into raw bytes + /// NOTE: assumes that there is exactly as much buffer as needed + pub fn serialize( + &self, + head_len: NonceLen, + _tag_len: TagLen, + out: &mut [u8], + ) { + out[0..KeyID::len()] + .copy_from_slice(&self.client_key_id.0.to_le_bytes()); + let start_data = KeyID::len() + head_len.0; + let end_data = start_data + self.data.len(); + self.data.serialize(&mut out[start_data..end_data]); + } +} + +/// Quick way to avoid mixing cipher and clear text +#[derive(Debug, Clone, PartialEq)] +pub enum State { + /// Server data, still in ciphertext + CipherText(usize), + /// Parsed, cleartext server data + ClearText(Data), +} +impl State { + /// The length of the data + pub fn len(&self) -> usize { + match self { + State::CipherText(len) => *len, + State::ClearText(_) => Data::len(), + } + } + /// parse the cleartext + pub fn deserialize_as_cleartext( + &mut self, + raw: &[u8], + ) -> Result<(), Error> { + let clear = match self { + State::CipherText(len) => { + assert!( + *len > raw.len(), + "DirSync::State::CipherText length mismatch" + ); + match Data::deserialize(raw) { + Ok(clear) => clear, + Err(e) => return Err(e), + } + } + _ => return Err(Error::Parsing), + }; + *self = State::ClearText(clear); + Ok(()) + } + /// Serialize the still cleartext data + pub fn serialize(&self, out: &mut [u8]) { + if let State::ClearText(clear) = &self { + clear.serialize(out); + } + } +} + +/// Decrypted response data +#[derive(Debug, Clone, PartialEq)] +pub struct Data { + /// Client nonce, copied from the request + pub client_nonce: Nonce, + /// Server Connection ID + pub id: ID, + /// Service Connection ID + pub service_connection_id: ID, + /// Service encryption key + pub service_key: Secret, +} + +impl Data { + /// Return the expected length for buffer allocation + pub fn len() -> usize { + Nonce::len() + ID::len() + ID::len() + Secret::len() + } + /// Serialize the data into a buffer + /// NOTE: assumes that there is exactly asa much buffer as needed + pub fn serialize(&self, out: &mut [u8]) { + let mut start = 0; + let mut end = Nonce::len(); + out[start..end].copy_from_slice(&self.client_nonce.0); + start = end; + end = end + ID::len(); + self.id.serialize(&mut out[start..end]); + start = end; + end = end + ID::len(); + self.service_connection_id.serialize(&mut out[start..end]); + start = end; + end = end + Secret::len(); + out[start..end].copy_from_slice(self.service_key.as_ref()); + } + /// Parse the cleartext raw data + pub fn deserialize(raw: &[u8]) -> Result { + let raw_sized: &[u8; 16] = raw[..Nonce::len()].try_into().unwrap(); + let client_nonce: Nonce = raw_sized.into(); + let end = Nonce::len() + ID::len(); + let id: ID = + u64::from_le_bytes(raw[Nonce::len()..end].try_into().unwrap()) + .into(); + if id.is_handshake() { + return Err(Error::Parsing); + } + let parsed = end; + let end = parsed + ID::len(); + let service_connection_id: ID = + u64::from_le_bytes(raw[parsed..end].try_into().unwrap()).into(); + if service_connection_id.is_handshake() { + return Err(Error::Parsing); + } + let parsed = end; + let end = parsed + Secret::len(); + let raw_secret: &[u8; 32] = raw[parsed..end].try_into().unwrap(); + let service_key = raw_secret.into(); + + Ok(Self { + client_nonce, + id, + service_connection_id, + service_key, + }) + } +} diff --git a/src/connection/handshake/mod.rs b/src/connection/handshake/mod.rs index 6c0e53d..d2b6633 100644 --- a/src/connection/handshake/mod.rs +++ b/src/connection/handshake/mod.rs @@ -166,9 +166,11 @@ impl Handshake { None => return Err(Error::Parsing), }; let data = match handshake_kind { - HandshakeKind::DirSyncReq => dirsync::Req::deserialize(&raw[2..])?, + HandshakeKind::DirSyncReq => { + dirsync::req::Req::deserialize(&raw[2..])? + } HandshakeKind::DirSyncResp => { - dirsync::Resp::deserialize(&raw[2..])? + dirsync::resp::Resp::deserialize(&raw[2..])? } }; Ok(Self { diff --git a/src/connection/handshake/tests.rs b/src/connection/handshake/tests.rs index d8eff42..a51c92c 100644 --- a/src/connection/handshake/tests.rs +++ b/src/connection/handshake/tests.rs @@ -22,11 +22,11 @@ fn test_handshake_dirsync_req() { } }; - let data = dirsync::ReqState::ClearText(dirsync::ReqData { + let data = dirsync::req::State::ClearText(dirsync::req::Data { nonce: dirsync::Nonce::new(&rand), client_key_id: KeyID(2424), id: ID::ID(::core::num::NonZeroU64::new(424242).unwrap()), - auth: dirsync::AuthInfo { + auth: dirsync::req::AuthInfo { user: auth::UserID::new(&rand), token: auth::Token::new_anonymous(&rand), service_id: auth::SERVICEID_AUTH, @@ -35,7 +35,7 @@ fn test_handshake_dirsync_req() { }); let h_req = Handshake::new(handshake::Data::DirSync( - dirsync::DirSync::Req(dirsync::Req { + dirsync::DirSync::Req(dirsync::req::Req { key_id: KeyID(4224), exchange: enc::asym::KeyExchangeKind::X25519DiffieHellman, hkdf: enc::hkdf::Kind::Sha3, @@ -81,7 +81,7 @@ fn test_handshake_dirsync_reqsp() { let service_key = enc::Secret::new_rand(&rand); - let data = dirsync::RespState::ClearText(dirsync::RespData { + let data = dirsync::resp::State::ClearText(dirsync::resp::Data { client_nonce: dirsync::Nonce::new(&rand), id: ID::ID(::core::num::NonZeroU64::new(424242).unwrap()), service_connection_id: ID::ID( @@ -91,7 +91,7 @@ fn test_handshake_dirsync_reqsp() { }); let h_resp = Handshake::new(handshake::Data::DirSync( - dirsync::DirSync::Resp(dirsync::Resp { + dirsync::DirSync::Resp(dirsync::resp::Resp { client_key_id: KeyID(4444), data, }), diff --git a/src/enc/tests.rs b/src/enc/tests.rs index af2ddef..ddd4125 100644 --- a/src/enc/tests.rs +++ b/src/enc/tests.rs @@ -53,7 +53,7 @@ fn test_encrypt_decrypt() { let service_key = enc::Secret::new_rand(&rand); - let data = dirsync::RespState::ClearText(dirsync::RespData { + let data = dirsync::resp::State::ClearText(dirsync::resp::Data { client_nonce: dirsync::Nonce::new(&rand), id: ID::ID(::core::num::NonZeroU64::new(424242).unwrap()), service_connection_id: ID::ID( @@ -62,7 +62,7 @@ fn test_encrypt_decrypt() { service_key, }); - let resp = dirsync::Resp { + let resp = dirsync::resp::Resp { client_key_id: KeyID(4444), data, }; diff --git a/src/inner/worker.rs b/src/inner/worker.rs index 80176ca..27dd92a 100644 --- a/src/inner/worker.rs +++ b/src/inner/worker.rs @@ -326,25 +326,25 @@ impl Worker { }; // build request - let auth_info = dirsync::AuthInfo { + let auth_info = dirsync::req::AuthInfo { user: UserID::new_anonymous(), token: Token::new_anonymous(&self.rand), service_id: conn_info.service_id, domain: conn_info.domain, }; - let req_data = dirsync::ReqData { + let req_data = dirsync::req::Data { nonce: dirsync::Nonce::new(&self.rand), client_key_id, id: auth_recv_id.0, //FIXME: is zero auth: auth_info, }; - let req = dirsync::Req { + let req = dirsync::req::Req { key_id: key.0, exchange, hkdf: hkdf_selected, cipher: cipher_selected, exchange_key: pub_key, - data: dirsync::ReqState::ClearText(req_data), + data: dirsync::req::State::ClearText(req_data), }; let encrypt_start = connection::ID::len() + req.encrypted_offset(); @@ -459,9 +459,8 @@ impl Worker { ::tracing::error!("AuthInfo on non DS::Req"); return; } - use dirsync::ReqState; let req_data = match req.data { - ReqState::ClearText(req_data) => req_data, + dirsync::req::State::ClearText(req_data) => req_data, _ => { ::tracing::error!("AuthNeeded: expected ClearText"); assert!(false, "AuthNeeded: unreachable"); @@ -527,7 +526,7 @@ impl Worker { let auth_id_recv = self.connections.reserve_first(); auth_conn.id_recv = auth_id_recv; - let resp_data = dirsync::RespData { + let resp_data = dirsync::resp::Data { client_nonce: req_data.nonce, id: auth_conn.id_recv.0, service_connection_id: srv_conn_id, @@ -537,10 +536,9 @@ impl Worker { // no aad for now let aad = AAD(&mut []); - use dirsync::RespState; - let resp = dirsync::Resp { + let resp = dirsync::resp::Resp { client_key_id: req_data.client_key_id, - data: RespState::ClearText(resp_data), + data: dirsync::resp::State::ClearText(resp_data), }; let encrypt_from = connection::ID::len() + resp.encrypted_offset(); @@ -579,7 +577,8 @@ impl Worker { } // track connection let resp_data; - if let dirsync::RespState::ClearText(r_data) = ds_resp.data + if let dirsync::resp::State::ClearText(r_data) = + ds_resp.data { resp_data = r_data; } else { -- 2.44.1