diff --git a/src/auth/mod.rs b/src/auth/mod.rs index aef9cbc..20df6f7 100644 --- a/src/auth/mod.rs +++ b/src/auth/mod.rs @@ -5,7 +5,7 @@ use ::zeroize::Zeroize; /// User identifier. 16 bytes for easy uuid conversion #[derive(Debug, Copy, Clone)] -pub struct UserID([u8; 16]); +pub struct UserID(pub [u8; 16]); impl From<[u8; 16]> for UserID { fn from(raw: [u8; 16]) -> Self { @@ -36,7 +36,7 @@ impl UserID { /// Authentication Token, basically just 32 random bytes #[derive(Clone, Zeroize)] #[zeroize(drop)] -pub struct Token([u8; 32]); +pub struct Token(pub [u8; 32]); impl Token { /// New random token, anonymous should not check this anyway @@ -110,7 +110,7 @@ impl Domain { pub const SERVICEID_AUTH: ServiceID = ServiceID([0; 16]); /// The Service ID is a UUID associated with the service. #[derive(Debug, Copy, Clone, PartialEq)] -pub struct ServiceID([u8; 16]); +pub struct ServiceID(pub [u8; 16]); impl From<[u8; 16]> for ServiceID { fn from(raw: [u8; 16]) -> Self { diff --git a/src/connection/handshake/dirsync.rs b/src/connection/handshake/dirsync.rs index 031f789..e387d2c 100644 --- a/src/connection/handshake/dirsync.rs +++ b/src/connection/handshake/dirsync.rs @@ -97,7 +97,9 @@ pub struct Req { pub exchange_key: ExchangePubKey, /// encrypted data pub data: ReqInner, - // Security: Add padding to min: 1200 bytes to avoid amplification attaks + // SECURITY: TODO: Add padding to min: 1200 bytes + // to avoid amplification attaks + // also: 1200 < 1280 to allow better vpn compatibility } impl Req { @@ -125,7 +127,9 @@ impl Req { + HkdfKind::len() + CipherKind::len() + self.exchange_key.kind().pub_len() + + self.cipher.nonce_len().0 + self.data.len() + + self.cipher.tag_len().0 } /// Serialize into raw bytes /// NOTE: assumes that there is exactly as much buffer as needed @@ -135,8 +139,21 @@ impl Req { tag_len: TagLen, out: &mut [u8], ) { - //assert!(out.len() > , ": not enough buffer to serialize"); - todo!() + out[0..2].copy_from_slice(&self.key_id.0.to_le_bytes()); + out[2] = self.exchange as u8; + out[3] = self.hkdf as u8; + out[4] = self.cipher as u8; + let key_len = self.exchange_key.len(); + 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 { + let from = written + head_len.0; + let to = out.len() - tag_len.0; + data.serialize(&mut out[from..to]); + } else { + unreachable!(); + } } } @@ -147,7 +164,7 @@ impl super::HandshakeParsing for Req { return Err(Error::NotEnoughData); } let key_id: KeyID = - KeyID(u16::from_le_bytes(raw[0..1].try_into().unwrap())); + KeyID(u16::from_le_bytes(raw[0..2].try_into().unwrap())); use ::num_traits::FromPrimitive; let exchange: KeyExchangeKind = match KeyExchangeKind::from_u8(raw[2]) { Some(exchange) => exchange, @@ -161,7 +178,7 @@ impl super::HandshakeParsing for Req { Some(cipher) => cipher, None => return Err(Error::Parsing), }; - let (exchange_key, len) = match ExchangePubKey::from_slice(&raw[5..]) { + let (exchange_key, len) = match ExchangePubKey::deserialize(&raw[5..]) { Ok(exchange_key) => exchange_key, Err(e) => return Err(e.into()), }; @@ -235,6 +252,21 @@ impl AuthInfo { pub fn len(&self) -> usize { Self::MIN_PKT_LEN + self.domain.len() } + /// serialize into a buffer + /// Note: assumes there is enough space + pub fn serialize(&self, out: &mut [u8]) { + out[..auth::UserID::len()].copy_from_slice(&self.user.0); + const WRITTEN_TOKEN: usize = auth::UserID::len() + auth::Token::len(); + out[auth::UserID::len()..WRITTEN_TOKEN].copy_from_slice(&self.token.0); + const WRITTEN_SERVICE_ID: usize = + WRITTEN_TOKEN + auth::ServiceID::len(); + out[WRITTEN_TOKEN..WRITTEN_SERVICE_ID] + .copy_from_slice(&self.service_id.0); + let domain_len = self.domain.0.as_bytes().len() as u8; + out[WRITTEN_SERVICE_ID] = domain_len; + const WRITTEN_DOMAIN_LEN: usize = WRITTEN_SERVICE_ID + 1; + out[WRITTEN_DOMAIN_LEN..].copy_from_slice(&self.domain.0.as_bytes()); + } /// deserialize from raw bytes pub fn deserialize(raw: &[u8]) -> Result { if raw.len() < Self::MIN_PKT_LEN { @@ -295,6 +327,19 @@ impl ReqData { /// Minimum byte length of the request data pub const MIN_PKT_LEN: usize = 16 + KeyID::len() + ID::len() + AuthInfo::MIN_PKT_LEN; + /// serialize into a buffer + /// Note: assumes there is enough space + pub fn serialize(&self, out: &mut [u8]) { + out[..Nonce::len()].copy_from_slice(&self.nonce.0); + const WRITTEN_KEY: usize = Nonce::len() + KeyID::len(); + out[Nonce::len()..WRITTEN_KEY] + .copy_from_slice(&self.client_key_id.0.to_le_bytes()); + const WRITTEN: usize = WRITTEN_KEY; + const WRITTEN_ID: usize = WRITTEN + 8; + out[WRITTEN..WRITTEN_ID] + .copy_from_slice(&self.id.as_u64().to_le_bytes()); + self.auth.serialize(&mut out[WRITTEN_ID..]); + } /// Parse the cleartext raw data pub fn deserialize(raw: &[u8]) -> Result { if raw.len() < Self::MIN_PKT_LEN { diff --git a/src/connection/handshake/mod.rs b/src/connection/handshake/mod.rs index 14ba8fd..bd0b501 100644 --- a/src/connection/handshake/mod.rs +++ b/src/connection/handshake/mod.rs @@ -1,6 +1,8 @@ //! Handhsake handling pub mod dirsync; +#[cfg(test)] +mod tests; use crate::{ auth::ServiceID, @@ -194,7 +196,7 @@ impl HandshakeData { /// Kind of handshake #[derive(::num_derive::FromPrimitive, Debug, Clone, Copy)] #[repr(u8)] -pub enum Kind { +pub enum HandshakeKind { /// 1-RTT, Directory synchronized handshake /// Request DirSyncReq = 0, @@ -210,6 +212,12 @@ pub enum Kind { .... */ } +impl HandshakeKind { + /// Length of the serialized field + pub const fn len() -> usize { + 1 + } +} /// Parsed handshake #[derive(Debug, Clone)] @@ -230,7 +238,7 @@ impl Handshake { } /// return the total length of the handshake pub fn len(&self) -> usize { - ProtocolVersion::len() + self.data.len() + ProtocolVersion::len() + HandshakeKind::len() + self.data.len() } const MIN_PKT_LEN: usize = 8; /// Parse the packet and return the parsed handshake @@ -242,13 +250,15 @@ impl Handshake { Some(fenrir_version) => fenrir_version, None => return Err(Error::Parsing), }; - let handshake_kind = match Kind::from_u8(raw[1]) { + let handshake_kind = match HandshakeKind::from_u8(raw[1]) { Some(handshake_kind) => handshake_kind, None => return Err(Error::Parsing), }; let data = match handshake_kind { - Kind::DirSyncReq => dirsync::Req::deserialize(&raw[2..])?, - Kind::DirSyncResp => dirsync::Resp::deserialize(&raw[2..])?, + HandshakeKind::DirSyncReq => dirsync::Req::deserialize(&raw[2..])?, + HandshakeKind::DirSyncResp => { + dirsync::Resp::deserialize(&raw[2..])? + } }; Ok(Self { fenrir_version, @@ -263,9 +273,14 @@ impl Handshake { tag_len: TagLen, out: &mut [u8], ) { - assert!(out.len() > 1, "Handshake: not enough buffer to serialize"); - self.fenrir_version.serialize(&mut out[0]); - self.data.serialize(head_len, tag_len, &mut out[1..]); + out[0] = self.fenrir_version as u8; + out[1] = match &self.data { + HandshakeData::DirSync(d) => match d { + dirsync::DirSync::Req(_) => HandshakeKind::DirSyncReq, + dirsync::DirSync::Resp(_) => HandshakeKind::DirSyncResp, + }, + } as u8; + self.data.serialize(head_len, tag_len, &mut out[2..]); } } diff --git a/src/connection/handshake/tests.rs b/src/connection/handshake/tests.rs new file mode 100644 index 0000000..df485d7 --- /dev/null +++ b/src/connection/handshake/tests.rs @@ -0,0 +1,65 @@ +use crate::{ + auth, + connection::{handshake::*, ID}, + enc, +}; + +#[test] +fn test_handshake_dirsync_req() { + let rand = enc::Random::new(); + let secret = enc::Secret::new_rand(&rand); + let cipher_send = enc::sym::CipherSend::new( + enc::sym::CipherKind::XChaCha20Poly1305, + secret, + &rand, + ); + + let (_, exchange_key) = + match enc::asym::KeyExchangeKind::X25519DiffieHellman.new_keypair(&rand) + { + Ok(pair) => pair, + Err(_) => { + assert!(false, "Can't generate random keypair"); + return; + } + }; + + let data = dirsync::ReqInner::ClearText(dirsync::ReqData { + nonce: dirsync::Nonce::new(&rand), + client_key_id: KeyID(2424), + id: ID::ID(::core::num::NonZeroU64::new(424242).unwrap()), + auth: dirsync::AuthInfo { + user: auth::UserID::new(&rand), + token: auth::Token::new_anonymous(&rand), + service_id: auth::SERVICEID_AUTH, + domain: auth::Domain("example.com".to_owned()), + }, + }); + + let h_req = Handshake::new(HandshakeData::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, + exchange_key, + data, + }, + ))); + + let mut bytes = Vec::::with_capacity(h_req.len()); + bytes.resize(h_req.len(), 0); + h_req.serialize( + cipher_send.kind().nonce_len(), + cipher_send.kind().tag_len(), + &mut bytes, + ); + + let deserialized = match Handshake::deserialize(&bytes) { + Ok(deserialized) => deserialized, + Err(e) => { + assert!(false, "{}", e.to_string()); + return; + } + }; +} diff --git a/src/dnssec/mod.rs b/src/dnssec/mod.rs index bb479ba..bc0f661 100644 --- a/src/dnssec/mod.rs +++ b/src/dnssec/mod.rs @@ -152,34 +152,56 @@ mod tests { #[test] fn test_serialization() { - // The record was generated with: - // f-dnssec generate dnssec \ - // -a 1 2 42 directory_synchronized 127.0.0.1 31337 \ - // -p 42 x25519 x25519.pub \ - // -x x25519diffiehellman \ - // -c xchacha20poly1305 - const TXT_RECORD: &'static str = "v=Fenrir1 \ - 5fBgo5ovk=0Dk}g0V)6>0cKP8KO-Vna846zp@MaLF|nim_XH&nQvT-I|B9HfJpcd"; + let rand = enc::Random::new(); + let (_, exchange_key) = + match enc::asym::KeyExchangeKind::X25519DiffieHellman + .new_keypair(&rand) + { + Ok(pair) => pair, + Err(_) => { + assert!(false, "Can't generate random keypair"); + return; + } + }; + use crate::enc; + let record = Record { + public_keys : [(enc::asym::KeyID(42), + enc::asym::PubKey::Exchange(exchange_key))].to_vec(), + addresses: [record::Address { + ip: ::std::net::IpAddr::V4(::std::net::Ipv4Addr::new(127,0,0,1)), + port: Some(::core::num::NonZeroU16::new(31337).unwrap()), + priority: record::AddressPriority::P1, + weight: record::AddressWeight::W1, + handshake_ids: [crate::connection::handshake::HandshakeID::DirectorySynchronized].to_vec(), + public_key_idx : [record::PubKeyIdx(0)].to_vec(), - let record = match Dnssec::parse_txt_record(TXT_RECORD) { + }].to_vec(), + key_exchanges: [enc::asym::KeyExchangeKind::X25519DiffieHellman].to_vec(), + hkdfs: [enc::hkdf::HkdfKind::Sha3].to_vec(), + ciphers: [enc::sym::CipherKind::XChaCha20Poly1305].to_vec(), + + }; + let encoded = match record.encode() { + Ok(encoded) => encoded, + Err(e) => { + assert!(false, "{}", e.to_string()); + return; + } + }; + let full_record = "v=Fenrir1 ".to_string() + &encoded; + let record = match Dnssec::parse_txt_record(&full_record) { Ok(record) => record, Err(e) => { assert!(false, "{}", e.to_string()); return; } }; - let re_encoded = match record.encode() { + let _re_encoded = match record.encode() { Ok(re_encoded) => re_encoded, Err(e) => { assert!(false, "{}", e.to_string()); return; } }; - assert!( - TXT_RECORD[10..] == re_encoded, - "DNSSEC record decoding->encoding failed:\n{}\n{}", - TXT_RECORD, - re_encoded - ); } } diff --git a/src/dnssec/record.rs b/src/dnssec/record.rs index f5595e2..b3fdbec 100644 --- a/src/dnssec/record.rs +++ b/src/dnssec/record.rs @@ -429,7 +429,7 @@ impl Record { + self .public_keys .iter() - .map(|(_, key)| 4 + key.kind().pub_len()) + .map(|(_, key)| 3 + key.kind().pub_len()) .sum::() + self.key_exchanges.len() + self.hkdfs.len() @@ -463,7 +463,7 @@ impl Record { let written_next = written + KeyID::len(); raw[written..written_next].copy_from_slice(&key_id_bytes); written = written_next; - raw[written] = public_key.kind().pub_len() as u8; + raw[written] = public_key.len() as u8; written = written + 1; let written_next = written + public_key.len(); public_key.serialize_into(&mut raw[written..written_next]); @@ -531,10 +531,10 @@ impl Record { let raw_key_id = u16::from_le_bytes([raw[bytes_parsed], raw[bytes_parsed + 1]]); let id = KeyID(raw_key_id); - bytes_parsed = bytes_parsed + 2; + bytes_parsed = bytes_parsed + KeyID::len(); let pubkey_length = raw[bytes_parsed] as usize; bytes_parsed = bytes_parsed + 1; - let bytes_next_key = bytes_parsed + 1 + pubkey_length; + let bytes_next_key = bytes_parsed + pubkey_length; if bytes_next_key > raw.len() { return Err(Error::NotEnoughData(bytes_parsed)); } @@ -551,7 +551,7 @@ impl Record { return Err(Error::UnsupportedData(bytes_parsed)); } }; - if bytes != 1 + pubkey_length { + if bytes != pubkey_length { return Err(Error::UnsupportedData(bytes_parsed)); } bytes_parsed = bytes_parsed + bytes; diff --git a/src/enc/asym.rs b/src/enc/asym.rs index 5d057f2..16201cf 100644 --- a/src/enc/asym.rs +++ b/src/enc/asym.rs @@ -93,16 +93,19 @@ pub enum KeyKind { #[strum(serialize = "x25519")] X25519, } -// FIXME: actually check this -const MIN_KEY_SIZE: usize = 32; impl KeyKind { + /// 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 { - match self { - // FIXME: 99% wrong size - KeyKind::Ed25519 => ::ring::signature::ED25519_PUBLIC_KEY_LEN, - KeyKind::X25519 => 32, - } + KeyKind::len() + + match self { + // FIXME: 99% wrong size + KeyKind::Ed25519 => ::ring::signature::ED25519_PUBLIC_KEY_LEN, + KeyKind::X25519 => 32, + } } /// Get the capabilities of this key type pub fn capabilities(&self) -> KeyCapabilities { @@ -185,7 +188,7 @@ pub enum PubKey { impl PubKey { /// Get the serialized key length pub fn len(&self) -> usize { - 1 + match self { + match self { PubKey::Exchange(ex) => ex.len(), PubKey::Signing => todo!(), } @@ -215,17 +218,12 @@ impl PubKey { /// serialize the key into the buffer /// NOTE: Assumes there is enough space pub fn serialize_into(&self, out: &mut [u8]) { - assert!( - out.len() >= 1 + self.kind().pub_len(), - "Not enough out buffer", - ); - out[0] = self.kind() as u8; match self { PubKey::Signing => { ::tracing::error!("serializing ed25519 not supported"); return; } - PubKey::Exchange(ex) => ex.serialize_into(&mut out[1..]), + PubKey::Exchange(ex) => ex.serialize_into(out), } } /// Try to deserialize the pubkey from raw bytes @@ -238,7 +236,7 @@ impl PubKey { Some(kind) => kind, None => return Err(Error::UnsupportedKey(1)), }; - if raw.len() < 1 + kind.pub_len() { + if raw.len() < kind.pub_len() { return Err(Error::NotEnoughData(1)); } match kind { @@ -259,7 +257,7 @@ impl PubKey { }; Ok(( PubKey::Exchange(ExchangePubKey::X25519(pub_key)), - 1 + kind.pub_len(), + kind.pub_len(), )) } } @@ -281,7 +279,7 @@ pub enum PrivKey { impl PrivKey { /// Get the serialized key length pub fn len(&self) -> usize { - 1 + match self { + match self { PrivKey::Exchange(ex) => ex.len(), PrivKey::Signing => todo!(), } @@ -296,9 +294,8 @@ impl PrivKey { /// serialize the key into the buffer /// NOTE: Assumes there is enough space pub fn serialize_into(&self, out: &mut [u8]) { - out[0] = self.kind() as u8; match self { - PrivKey::Exchange(ex) => ex.serialize_into(&mut out[1..]), + PrivKey::Exchange(ex) => ex.serialize_into(out), PrivKey::Signing => todo!(), } } @@ -346,9 +343,10 @@ impl ExchangePrivKey { /// serialize the key into the buffer /// NOTE: Assumes there is enough space pub fn serialize_into(&self, out: &mut [u8]) { + out[0] = self.kind() as u8; match self { ExchangePrivKey::X25519(key) => { - out[0..32].copy_from_slice(&key.to_bytes()); + out[1..33].copy_from_slice(&key.to_bytes()); } } } @@ -378,21 +376,18 @@ impl ExchangePubKey { /// serialize the key into the buffer /// NOTE: Assumes there is enough space pub fn serialize_into(&self, out: &mut [u8]) { + out[0] = self.kind() as u8; match self { ExchangePubKey::X25519(pk) => { let bytes = pk.as_bytes(); - assert!(bytes.len() == 32, "x25519 should have been 32 bytes"); - out[..32].copy_from_slice(bytes); + out[1..33].copy_from_slice(bytes); } } } /// 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_slice(raw: &[u8]) -> Result<(Self, usize), Error> { - if raw.len() < 1 + MIN_KEY_SIZE { - return Err(Error::NotEnoughData(0)); - } + pub fn deserialize(raw: &[u8]) -> Result<(Self, usize), Error> { match KeyKind::from_u8(raw[0]) { Some(kind) => match kind { KeyKind::Ed25519 => {