diff --git a/Cargo.toml b/Cargo.toml index ca20fe4..84c0095 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,6 +32,7 @@ futures = { version = "^0.3" } libc = { version = "^0.2" } num-traits = { version = "^0.2" } num-derive = { version = "^0.3" } +ring = { version = "^0.16" } strum = { version = "^0.24" } strum_macros = { version = "^0.24" } thiserror = { version = "^1.0" } diff --git a/src/connection/handshake.rs b/src/connection/handshake.rs deleted file mode 100644 index a0ca9ba..0000000 --- a/src/connection/handshake.rs +++ /dev/null @@ -1,3 +0,0 @@ -//! Handhsake handling - - diff --git a/src/connection/handshake/dirsync.rs b/src/connection/handshake/dirsync.rs new file mode 100644 index 0000000..15ae3c5 --- /dev/null +++ b/src/connection/handshake/dirsync.rs @@ -0,0 +1,97 @@ +//! 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 super::{Error, HandshakeData}; +use crate::connection::{KeyID, UnparsedExchangePubKey, ID}; +use ::std::vec::Vec; + +/// Parsed handshake +#[derive(Debug, Clone)] +pub enum DirSync { + /// Directory synchronized handshake: client request + Req(Req), + /// Directory synchronized handshake: server response + Resp(Resp), +} + +/// Client request of a directory synchronized handshake +#[derive(Debug, Clone)] +pub struct Req { + /// Id of the server key used for the key exchange + key_id: KeyID, + /// Client ephemeral public key used for key exchanges + exchange_key: UnparsedExchangePubKey, + /// encrypted data + enc: Vec, +} + +impl super::HandshakeParsing for Req { + fn parse(raw: &[u8]) -> Result { + const MIN_PKT_LEN: usize = 8; + if raw.len() < MIN_PKT_LEN { + return Err(Error::NotEnoughData); + } + let key_id: KeyID = + KeyID(u16::from_le_bytes(raw[0..1].try_into().unwrap())); + let (exchange_key, len) = + match UnparsedExchangePubKey::from_raw(&raw[2..]) { + Ok(exchange_key) => exchange_key, + Err(e) => return Err(e), + }; + let enc = raw[(2 + len)..].to_vec(); + Ok(HandshakeData::DirSync(DirSync::Req(Self { + key_id, + exchange_key, + enc, + }))) + } +} + +/// Decrypted request data +#[derive(Debug, Clone, Copy)] +pub struct ReqData { + /// Random nonce, the client can use this to track multiple key exchanges + nonce: [u8; 16], + /// Client key id so the client can use and rotate keys + client_key_id: KeyID, + /// Authentication token + token: [u8; 32], + /// Receiving connection id for the client + id: ID, + // TODO: service info +} + +/// Server response in a directory synchronized handshake +#[derive(Debug, Clone)] +pub struct Resp { + /// Tells the client with which key the exchange was done + client_key_id: KeyID, + /// encrypted data + enc: Vec, +} + +impl super::HandshakeParsing for Resp { + fn parse(raw: &[u8]) -> Result { + todo!() + } +} + +/// Decrypted response data +#[derive(Debug, Clone, Copy)] +pub struct RespData { + /// Client nonce, copied from the request + client_nonce: [u8; 16], + /// Server Connection ID + id: ID, + /// Service Connection ID + service_id: ID, + /// Service encryption key + service_key: [u8; 32], +} diff --git a/src/connection/handshake/mod.rs b/src/connection/handshake/mod.rs new file mode 100644 index 0000000..0070df3 --- /dev/null +++ b/src/connection/handshake/mod.rs @@ -0,0 +1,83 @@ +//! Handhsake handling + +pub mod dirsync; + +use ::num_traits::FromPrimitive; + +use crate::connection::ProtocolVersion; + +/// Handshake errors +#[derive(::thiserror::Error, Debug, Copy, Clone)] +pub enum Error { + /// Error while parsing the handshake packet + /// TODO: more detailed parsing errors + #[error("not an handshake packet")] + Parsing, + /// Not enough data + #[error("not enough data")] + NotEnoughData, +} + +/// Parsed handshake +#[derive(Debug, Clone)] +pub enum HandshakeData { + /// Directory synchronized handhsake + DirSync(dirsync::DirSync), +} + +/// Kind of handshake +#[derive(::num_derive::FromPrimitive, Debug, Clone, Copy)] +#[repr(u8)] +pub enum Kind { + /// 1-RTT, Directory synchronized handshake + /// Request + DirSyncReq = 0, + /// 1-RTT, Directory synchronized handshake + /// Response + DirSyncResp, + /* + /// 2-RTT, Stateful connection + Stateful, + ... + /// 3 RTT, Stateless connection + Stateless, + .... + */ +} + +/// Parsed handshake +#[derive(Debug, Clone)] +pub struct Handshake { + /// Fenrir Protocol version + fenrir_version: ProtocolVersion, + /// enum for the parsed data + data: HandshakeData, +} + +impl Handshake { + const MIN_PKT_LEN: usize = 8; + /// Parse the packet and return the parsed handshake + pub fn parse(raw: &[u8]) -> Result { + if raw.len() < Self::MIN_PKT_LEN { + return Err(Error::NotEnoughData); + } + let fenrir_version = match ProtocolVersion::from_u8(raw[0]) { + Some(fenrir_version) => fenrir_version, + None => return Err(Error::Parsing), + }; + let handshake_kind = match Kind::from_u8(raw[1]) { + Some(handshake_kind) => handshake_kind, + None => return Err(Error::Parsing), + }; + let parsed = match handshake_kind { + Kind::DirSyncReq => dirsync::Req::parse(&raw[2..])?, + Kind::DirSyncResp => dirsync::Resp::parse(&raw[2..])?, + }; + + todo!() + } +} + +trait HandshakeParsing { + fn parse(raw: &[u8]) -> Result; +} diff --git a/src/connection/mod.rs b/src/connection/mod.rs index d5ded85..ad20748 100644 --- a/src/connection/mod.rs +++ b/src/connection/mod.rs @@ -1,6 +1,74 @@ //! Connection handling and send/receive queues -mod handshake; +pub mod handshake; mod packet; -pub use packet::ConnectionID; + +use ::num_traits::FromPrimitive; +use ::std::vec::Vec; + +pub use handshake::Handshake; +pub use packet::ConnectionID as ID; pub use packet::Packet; + +/// Public key ID +#[derive(Debug, Copy, Clone)] +pub struct KeyID(u16); + +/// Version of the fenrir protocol in use +#[derive(::num_derive::FromPrimitive, Debug, Copy, Clone)] +#[repr(u8)] +pub enum ProtocolVersion { + /// First Fenrir Protocol Version + V0 = 0, +} + +/// Kind of key in the key exchange +#[derive(Debug, Copy, Clone, ::num_derive::FromPrimitive)] +#[repr(u8)] +pub enum ExchangePubKeyKind { + /// X25519 Public key + X25519 = 0, +} + +/// all Ephemeral Public key types +#[derive(Debug, Clone)] +pub enum ExchangePubKey { + /// X25519(Curve25519) used for key exchange + X25519(Vec), +} + +/// Mark the Ephemeral key as yet unparsed, meaning it could be +/// maliciously currupted data +#[derive(Debug, Clone)] +pub struct UnparsedExchangePubKey(ExchangePubKey); + +impl UnparsedExchangePubKey { + /// Load public key used for key exchange from it raw bytes + /// The riesult is "unparsed" since we don't verify + /// the actual key + pub fn from_raw(raw: &[u8]) -> Result<(Self, usize), handshake::Error> { + // FIXME: get *real* minimum key size + const MIN_KEY_SIZE: usize = 8; + if raw.len() < 1 + MIN_KEY_SIZE { + return Err(handshake::Error::NotEnoughData); + } + match ExchangePubKeyKind::from_u8(raw[0]) { + Some(kind) => match kind { + ExchangePubKeyKind::X25519 => { + // FIXME: is this really the + const len: usize = + 1 + ::ring::signature::ED25519_PUBLIC_KEY_LEN; + Ok(( + UnparsedExchangePubKey(ExchangePubKey::X25519( + raw[1..len].to_vec(), + )), + len, + )) + } + }, + None => { + return Err(handshake::Error::Parsing); + } + } + } +} diff --git a/src/dnssec/record.rs b/src/dnssec/record.rs index cc63417..76d89c2 100644 --- a/src/dnssec/record.rs +++ b/src/dnssec/record.rs @@ -57,6 +57,9 @@ impl TryFrom<&str> for PublicKeyId { pub enum PublicKeyType { /// ed25519 asymmetric key Ed25519 = 0, + /// Ephemeral X25519 (Curve25519) key. + /// Used in the directory synchronized handshake + X25519, } impl PublicKeyType { @@ -64,6 +67,7 @@ impl PublicKeyType { pub fn key_len(&self) -> usize { match &self { PublicKeyType::Ed25519 => 32, + PublicKeyType::X25519 => 32, // FIXME: hopefully... } } } @@ -116,9 +120,8 @@ impl PublicKey { } let mut raw_key = Vec::with_capacity(kind.key_len()); - raw_key.extend_from_slice(&raw[2..(2 + kind.key_len())]); - let total_length = 2 + kind.key_len(); + raw_key.extend_from_slice(&raw[2..total_length]); Ok(( Self { @@ -243,7 +246,7 @@ pub struct Address { pub priority: AddressPriority, /// Weight of this address in the priority group pub weight: AddressWeight, - /// Public key ID used by this address + /// Public key IDs used by this address pub public_key_id: PublicKeyId, } diff --git a/src/lib.rs b/src/lib.rs index 811c1b2..656aa8a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -24,15 +24,18 @@ pub mod dnssec; /// Main fenrir library errors #[derive(::thiserror::Error, Debug)] pub enum Error { + /// The library was not initialized (run .start()) + #[error("not initialized")] + NotInitialized, /// General I/O error #[error("IO: {0:?}")] IO(#[from] ::std::io::Error), /// Dnssec errors #[error("Dnssec: {0:?}")] Dnssec(#[from] dnssec::Error), - /// The library was not initialized (run .start()) - #[error("not initialized")] - NotInitialized, + /// Handshake errors + #[error("Handshake: {0:?}")] + Handshake(#[from] connection::handshake::Error), } /// Instance of a fenrir endpoint @@ -224,11 +227,12 @@ impl Fenrir { if buffer.len() < Self::MIN_PACKET_BYTES { return; } - use connection::ConnectionID; + use connection::ID; let raw_id: [u8; 8] = buffer.try_into().expect("unreachable"); - if ConnectionID::from(raw_id).is_handshake() { + if ID::from(raw_id).is_handshake() { todo!(); } + // copy packet, spawn todo!(); } }