From a8da84c055e104ea8f484b8292d73539c56810ff Mon Sep 17 00:00:00 2001 From: Adam Ierymenko Date: Wed, 4 Aug 2021 12:31:46 -0400 Subject: [PATCH] Lots of Rust stuff. --- aes-gmac-siv/Cargo.toml | 7 + network-hypervisor/Cargo.lock | 39 --- network-hypervisor/Cargo.toml | 4 +- network-hypervisor/src/crypto/hash.rs | 6 - network-hypervisor/src/crypto/kbkdf.rs | 4 +- network-hypervisor/src/crypto/mod.rs | 5 - network-hypervisor/src/crypto/random.rs | 19 ++ network-hypervisor/src/crypto/secret.rs | 2 +- network-hypervisor/src/error.rs | 6 +- network-hypervisor/src/lib.rs | 4 +- network-hypervisor/src/util/pool.rs | 1 + network-hypervisor/src/vl1/address.rs | 4 +- network-hypervisor/src/vl1/buffer.rs | 67 ++-- network-hypervisor/src/vl1/constants.rs | 33 ++ network-hypervisor/src/vl1/dictionary.rs | 5 + network-hypervisor/src/vl1/endpoint.rs | 38 +-- .../src/vl1/fragmentedpacket.rs | 31 +- network-hypervisor/src/vl1/mod.rs | 2 +- network-hypervisor/src/vl1/node.rs | 34 +- network-hypervisor/src/vl1/path.rs | 65 ++-- network-hypervisor/src/vl1/peer.rs | 322 +++++++++++++++++- network-hypervisor/src/vl1/protocol.rs | 88 +++-- network-hypervisor/src/vl1/whois.rs | 18 +- 23 files changed, 613 insertions(+), 191 deletions(-) diff --git a/aes-gmac-siv/Cargo.toml b/aes-gmac-siv/Cargo.toml index 87422f503..64430ce1d 100644 --- a/aes-gmac-siv/Cargo.toml +++ b/aes-gmac-siv/Cargo.toml @@ -7,6 +7,13 @@ edition = "2018" opt-level = 3 lto = true codegen-units = 1 +panic = 'abort' + +[profile.release] +opt-level = 3 +lto = true +codegen-units = 1 +panic = 'abort' [target."cfg(not(any(target_os = \"macos\", target_os = \"ios\")))".dependencies] gcrypt = "^0" diff --git a/network-hypervisor/Cargo.lock b/network-hypervisor/Cargo.lock index 0f6aa8391..fd7164217 100644 --- a/network-hypervisor/Cargo.lock +++ b/network-hypervisor/Cargo.lock @@ -190,12 +190,6 @@ dependencies = [ "cfg-if 1.0.0", ] -[[package]] -name = "itoa" -version = "0.4.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" - [[package]] name = "libc" version = "0.2.98" @@ -369,12 +363,6 @@ dependencies = [ "bitflags", ] -[[package]] -name = "ryu" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" - [[package]] name = "scopeguard" version = "1.1.0" @@ -386,31 +374,6 @@ name = "serde" version = "1.0.126" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec7505abeacaec74ae4778d9d9328fe5a5d04253220a85c4ee022239fc996d03" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.126" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "963a7dbc9895aeac7ac90e74f34a5d5261828f79df35cbed41e10189d3804d43" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "serde_json" -version = "1.0.66" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "336b10da19a12ad094b59d870ebde26a45402e5b470add4b5fd03c5048a32127" -dependencies = [ - "itoa", - "ryu", - "serde", -] [[package]] name = "sha2" @@ -588,8 +551,6 @@ dependencies = [ "lz4_flex", "parking_lot", "rand_core", - "serde", - "serde_json", "urlencoding", "winapi", "x25519-dalek", diff --git a/network-hypervisor/Cargo.toml b/network-hypervisor/Cargo.toml index 7b3aed876..e2839c54b 100644 --- a/network-hypervisor/Cargo.toml +++ b/network-hypervisor/Cargo.toml @@ -5,7 +5,7 @@ edition = "2018" [profile.release] lto = true -opt-level = 'z' +opt-level = 3 codegen-units = 1 panic = 'abort' @@ -20,8 +20,6 @@ urlencoding = "^2" lz4_flex = { version = "^0", features = ["safe-encode", "safe-decode", "checked-decode"] } dashmap = "^4" parking_lot = "^0" -serde = { version = "1", features = ["derive"] } -serde_json = "1" [target."cfg(not(windows))".dependencies] libc = "^0" diff --git a/network-hypervisor/src/crypto/hash.rs b/network-hypervisor/src/crypto/hash.rs index 9b53be1c8..6469001a0 100644 --- a/network-hypervisor/src/crypto/hash.rs +++ b/network-hypervisor/src/crypto/hash.rs @@ -15,7 +15,6 @@ impl SHA512 { h } - /// Compute HMAC-SHA512(key, msg) #[inline(always)] pub fn hmac(key: &[u8], msg: &[u8]) -> [u8; SHA512_HASH_SIZE] { let mut m = gcrypt::mac::Mac::new(gcrypt::mac::Algorithm::HmacSha512).unwrap(); @@ -47,8 +46,6 @@ impl SHA512 { self.0.get_only_digest().unwrap().try_into().unwrap() } - /// Return a reference to an internally stored result. - /// This saves a copy, but the returned result is only valid so long as no other methods are called. #[inline(always)] pub fn finish_get_ref(&mut self) -> &[u8] { self.0.finish(); @@ -79,7 +76,6 @@ impl SHA384 { h } - /// Compute HMAC-SHA384(key, msg) #[inline(always)] pub fn hmac(key: &[u8], msg: &[u8]) -> [u8; SHA384_HASH_SIZE] { let mut m = gcrypt::mac::Mac::new(gcrypt::mac::Algorithm::HmacSha384).unwrap(); @@ -111,8 +107,6 @@ impl SHA384 { self.0.get_only_digest().unwrap().try_into().unwrap() } - /// Return a reference to an internally stored result. - /// This saves a copy, but the returned result is only valid so long as no other methods are called. #[inline(always)] pub fn finish_get_ref(&mut self) -> &[u8] { self.0.finish(); diff --git a/network-hypervisor/src/crypto/kbkdf.rs b/network-hypervisor/src/crypto/kbkdf.rs index 8ee330f52..38056ae1b 100644 --- a/network-hypervisor/src/crypto/kbkdf.rs +++ b/network-hypervisor/src/crypto/kbkdf.rs @@ -5,8 +5,10 @@ use crate::crypto::secret::Secret; /// This is a fixed cost key derivation function used to derive sub-keys from a single original /// shared secret for different uses, such as the K0/K1 in AES-GMAC-SIV. /// Key must be 384 bits in length. +#[inline(always)] pub fn zt_kbkdf_hmac_sha384(key: &[u8], label: u8, context: u8, iter: u32) -> Secret<{ SHA384_HASH_SIZE }> { - debug_assert!(key.len() == SHA384_HASH_SIZE); + debug_assert_eq!(key.len(), SHA384_HASH_SIZE); + // HMAC'd message is: preface | iteration[4], preface[2], label, 0x00, context, hash size[4] // See: https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-108.pdf Secret(SHA384::hmac(key, &[(iter >> 24) as u8, (iter >> 16) as u8, (iter >> 8) as u8, iter as u8, b'Z', b'T', label, 0, context, 0, 0, 0x01, 0x80])) diff --git a/network-hypervisor/src/crypto/mod.rs b/network-hypervisor/src/crypto/mod.rs index 98aa7cfc1..e443914d1 100644 --- a/network-hypervisor/src/crypto/mod.rs +++ b/network-hypervisor/src/crypto/mod.rs @@ -9,8 +9,3 @@ pub mod random; pub mod secret; pub use aes_gmac_siv; - -pub fn init() { - // We always run gcrypt in "FIPS mode," but it doesn't count as fully compliant unless it's a FIPS-certified library. - let _ = gcrypt::init_fips_mode(|_| -> Result<(), std::convert::Infallible> { Ok(()) }); -} diff --git a/network-hypervisor/src/crypto/random.rs b/network-hypervisor/src/crypto/random.rs index 8dc8fa505..dbfd3f047 100644 --- a/network-hypervisor/src/crypto/random.rs +++ b/network-hypervisor/src/crypto/random.rs @@ -39,3 +39,22 @@ impl RngCore for SecureRandom { } impl CryptoRng for SecureRandom {} + +#[inline(always)] +pub(crate) fn next_u32_secure() -> u32 { + let mut tmp = 0_u32; + randomize(Level::Strong, unsafe { &mut *(&mut tmp as *mut u32).cast::<[u8; 4]>() }); + tmp +} + +#[inline(always)] +pub(crate) fn next_u64_secure() -> u64 { + let mut tmp = 0_u64; + randomize(Level::Strong, unsafe { &mut *(&mut tmp as *mut u64).cast::<[u8; 8]>() }); + tmp +} + +#[inline(always)] +pub(crate) fn fill_bytes_secure(dest: &mut [u8]) { + randomize(Level::Strong, dest); +} diff --git a/network-hypervisor/src/crypto/secret.rs b/network-hypervisor/src/crypto/secret.rs index 165acde0d..3deceded0 100644 --- a/network-hypervisor/src/crypto/secret.rs +++ b/network-hypervisor/src/crypto/secret.rs @@ -3,7 +3,7 @@ use std::ptr::write_volatile; /// Container for secrets that clears them on drop. #[derive(Clone, PartialEq, Eq)] -pub struct Secret(pub [u8; L]); +pub struct Secret(pub(crate) [u8; L]); impl Secret { #[inline(always)] diff --git a/network-hypervisor/src/error.rs b/network-hypervisor/src/error.rs index 530266c66..f570a53aa 100644 --- a/network-hypervisor/src/error.rs +++ b/network-hypervisor/src/error.rs @@ -10,8 +10,9 @@ impl Display for InvalidFormatError { } impl Debug for InvalidFormatError { + #[inline(always)] fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str("InvalidFormatError") + ::fmt(self, f) } } @@ -26,8 +27,9 @@ impl Display for InvalidParameterError { } impl Debug for InvalidParameterError { + #[inline(always)] fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "InvalidParameterError: {}", self.0) + ::fmt(self, f) } } diff --git a/network-hypervisor/src/lib.rs b/network-hypervisor/src/lib.rs index eff8e280a..e74876654 100644 --- a/network-hypervisor/src/lib.rs +++ b/network-hypervisor/src/lib.rs @@ -5,6 +5,6 @@ pub mod vl1; pub mod vl2; pub const VERSION_MAJOR: u8 = 1; -pub const VERSION_MINOR: u8 = 9; +pub const VERSION_MINOR: u8 = 99; pub const VERSION_REVISION: u8 = 1; -pub const VERSION_STR: &'static str = "1.9.1"; +pub const VERSION_STR: &'static str = "1.99.1"; diff --git a/network-hypervisor/src/util/pool.rs b/network-hypervisor/src/util/pool.rs index b4c98730c..eaaa4a0f7 100644 --- a/network-hypervisor/src/util/pool.rs +++ b/network-hypervisor/src/util/pool.rs @@ -36,6 +36,7 @@ impl Pooled { pub unsafe fn into_raw(self) -> *mut O { debug_assert!(!self.0.is_null()); debug_assert_eq!(self.0.cast::(), (&mut (*self.0).obj as *mut O).cast::()); + std::mem::forget(self); self.0.cast() } diff --git a/network-hypervisor/src/vl1/address.rs b/network-hypervisor/src/vl1/address.rs index f13716689..b992da5dd 100644 --- a/network-hypervisor/src/vl1/address.rs +++ b/network-hypervisor/src/vl1/address.rs @@ -1,9 +1,9 @@ -use std::str::FromStr; use std::hash::{Hash, Hasher}; +use std::str::FromStr; -use crate::vl1::constants::{ADDRESS_RESERVED_PREFIX, ADDRESS_SIZE}; use crate::error::InvalidFormatError; use crate::util::hex::HEX_CHARS; +use crate::vl1::constants::{ADDRESS_RESERVED_PREFIX, ADDRESS_SIZE}; #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub struct Address(u64); diff --git a/network-hypervisor/src/vl1/buffer.rs b/network-hypervisor/src/vl1/buffer.rs index 2f85e2294..39daa2c4a 100644 --- a/network-hypervisor/src/vl1/buffer.rs +++ b/network-hypervisor/src/vl1/buffer.rs @@ -50,31 +50,42 @@ impl Buffer { } } - /// Get a slice containing the entire buffer in raw form including the header. #[inline(always)] pub fn as_bytes(&self) -> &[u8] { &self.1[0..self.0] } - /// Get a slice containing the entire buffer in raw form including the header. #[inline(always)] pub fn as_bytes_mut(&mut self) -> &mut [u8] { &mut self.1[0..self.0] } - /// Erase contents and zero size. + /// Get all bytes after a given position. + #[inline(always)] + pub fn as_bytes_after(&self, start: usize) -> std::io::Result<&[u8]> { + if start <= self.0 { + Ok(&self.1[start..]) + } else { + Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG)) + } + } + #[inline(always)] pub fn clear(&mut self) { self.0 = 0; self.1.fill(0); } - /// Get the length of this buffer (including header, if any). #[inline(always)] pub fn len(&self) -> usize { self.0 } + #[inline(always)] + pub fn is_empty(&self) -> bool { + self.0 == 0 + } + /// Append a packed structure and call a function to initialize it in place. /// Anything not initialized will be zero. #[inline(always)] @@ -87,7 +98,7 @@ impl Buffer { Ok(initializer(&mut *self.1.as_mut_ptr().cast::().offset(ptr as isize).cast::())) } } else { - std::io::Result::Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG)) + Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG)) } } @@ -104,7 +115,7 @@ impl Buffer { Ok(initializer(&mut *self.1.as_mut_ptr().cast::().offset(ptr as isize).cast::<[u8; N]>())) } } else { - std::io::Result::Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG)) + Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG)) } } @@ -118,7 +129,7 @@ impl Buffer { self.0 = end; Ok(initializer(&mut self.1[ptr..end])) } else { - std::io::Result::Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG)) + Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG)) } } @@ -133,7 +144,7 @@ impl Buffer { self.1[ptr..end].copy_from_slice(buf); Ok(()) } else { - std::io::Result::Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG)) + Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG)) } } @@ -148,7 +159,7 @@ impl Buffer { self.1[ptr..end].copy_from_slice(buf); Ok(()) } else { - std::io::Result::Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG)) + Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG)) } } @@ -161,7 +172,7 @@ impl Buffer { self.1[ptr] = i; Ok(()) } else { - std::io::Result::Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG)) + Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG)) } } @@ -175,7 +186,7 @@ impl Buffer { crate::util::integer_store_be_u16(i, &mut self.1[ptr..end]); Ok(()) } else { - std::io::Result::Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG)) + Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG)) } } @@ -189,7 +200,7 @@ impl Buffer { crate::util::integer_store_be_u32(i, &mut self.1[ptr..end]); Ok(()) } else { - std::io::Result::Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG)) + Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG)) } } @@ -203,7 +214,7 @@ impl Buffer { crate::util::integer_store_be_u64(i, &mut self.1[ptr..end]); Ok(()) } else { - std::io::Result::Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG)) + Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG)) } } @@ -215,7 +226,7 @@ impl Buffer { Ok(&*self.1.as_ptr().cast::().offset(ptr as isize).cast::()) } } else { - std::io::Result::Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG)) + Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG)) } } @@ -227,7 +238,17 @@ impl Buffer { Ok(&mut *self.1.as_mut_ptr().cast::().offset(ptr as isize).cast::()) } } else { - std::io::Result::Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG)) + Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG)) + } + } + + /// Get a byte at a fixed position. + #[inline(always)] + pub fn u8_at(&self, ptr: usize) -> std::io::Result { + if ptr < self.0 { + Ok(self.1[ptr]) + } else { + Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG)) } } @@ -243,7 +264,7 @@ impl Buffer { Ok(&*self.1.as_ptr().cast::().offset(ptr as isize).cast::()) } } else { - std::io::Result::Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG)) + Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG)) } } @@ -260,7 +281,7 @@ impl Buffer { Ok(&*self.1.as_ptr().cast::().offset(ptr as isize).cast::<[u8; S]>()) } } else { - std::io::Result::Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG)) + Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG)) } } @@ -274,7 +295,7 @@ impl Buffer { *cursor = end; Ok(&self.1[ptr..end]) } else { - std::io::Result::Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG)) + Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG)) } } @@ -287,7 +308,7 @@ impl Buffer { *cursor = ptr + 1; Ok(self.1[ptr]) } else { - std::io::Result::Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG)) + Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG)) } } @@ -301,7 +322,7 @@ impl Buffer { *cursor = end; Ok(crate::util::integer_load_be_u16(&self.1[ptr..end])) } else { - std::io::Result::Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG)) + Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG)) } } @@ -315,7 +336,7 @@ impl Buffer { *cursor = end; Ok(crate::util::integer_load_be_u32(&self.1[ptr..end])) } else { - std::io::Result::Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG)) + Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG)) } } @@ -329,7 +350,7 @@ impl Buffer { *cursor = end; Ok(crate::util::integer_load_be_u64(&self.1[ptr..end])) } else { - std::io::Result::Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG)) + Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG)) } } } @@ -344,7 +365,7 @@ impl Write for Buffer { self.1[ptr..end].copy_from_slice(buf); Ok(buf.len()) } else { - std::io::Result::Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG)) + Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG)) } } diff --git a/network-hypervisor/src/vl1/constants.rs b/network-hypervisor/src/vl1/constants.rs index b532d96a9..cbe13eb74 100644 --- a/network-hypervisor/src/vl1/constants.rs +++ b/network-hypervisor/src/vl1/constants.rs @@ -4,6 +4,18 @@ pub const ADDRESS_SIZE: usize = 5; /// Prefix indicating reserved addresses (that can't actually be addresses). pub const ADDRESS_RESERVED_PREFIX: u8 = 0xff; +/// KBKDF usage label indicating a key used to encrypt the dictionary inside HELLO. +pub const KBKDF_KEY_USAGE_LABEL_HELLO_DICTIONARY_ENCRYPT: u8 = b'H'; + +/// KBKDF usage label indicating a key used to HMAC packets, which is currently only used for HELLO. +pub const KBKDF_KEY_USAGE_LABEL_PACKET_HMAC: u8 = b'M'; + +/// KBKDF usage label for the first AES-GMAC-SIV key. +pub const KBKDF_KEY_USAGE_LABEL_AES_GMAC_SIV_K0: u8 = b'0'; + +/// KBKDF usage label for the second AES-GMAC-SIV key. +pub const KBKDF_KEY_USAGE_LABEL_AES_GMAC_SIV_K1: u8 = b'1'; + /// Size of packet header that lies outside the encryption envelope. pub const PACKET_HEADER_SIZE: usize = 27; @@ -22,15 +34,27 @@ pub const PACKET_SIZE_MIN: usize = PACKET_HEADER_SIZE + 1; /// Maximum size of an entire packet. pub const PACKET_SIZE_MAX: usize = PACKET_HEADER_SIZE + PACKET_PAYLOAD_SIZE_MAX; +/// Index of packet verb after header. +pub const PACKET_VERB_INDEX: usize = 27; + /// Index of destination in both fragment and full packet headers. pub const PACKET_DESTINATION_INDEX: usize = 8; +/// Maximum number of paths to a remote peer. +pub const PEER_MAX_PATHS: usize = 16; + /// Mask to select cipher from header flags field. pub const HEADER_FLAGS_FIELD_MASK_CIPHER: u8 = 0x30; /// Mask to select packet hops from header flags field. pub const HEADER_FLAGS_FIELD_MASK_HOPS: u8 = 0x07; +/// Mask to select packet hops from header flags field. +pub const HEADER_FLAGS_FIELD_MASK_HIDE_HOPS: u8 = 0xf8; + +/// Index of hops/flags field +pub const HEADER_FLAGS_FIELD_INDEX: usize = 18; + /// Packet is not encrypted but contains a Poly1305 MAC of the plaintext. /// Poly1305 is initialized with Salsa20/12 in the same manner as SALSA2012_POLY1305. pub const CIPHER_NOCRYPT_POLY1305: u8 = 0x00; @@ -51,6 +75,9 @@ pub const HEADER_FLAG_FRAGMENTED: u8 = 0x40; /// Minimum size of a fragment. pub const FRAGMENT_SIZE_MIN: usize = 16; +/// Size of fragment header after which data begins. +pub const FRAGMENT_HEADER_SIZE: usize = 16; + /// Maximum allowed number of fragments. pub const FRAGMENT_COUNT_MAX: usize = 16; @@ -63,9 +90,15 @@ pub const FRAGMENT_INDICATOR: u8 = 0xff; /// Maximum number of inbound fragments to handle at once per path. pub const FRAGMENT_MAX_PER_PATH: usize = 64; +/// Time after which an incomplete fragmented packet expires. +pub const FRAGMENT_EXPIRATION: i64 = 1500; + /// Verb (inner) flag indicating that the packet's payload (after the verb) is LZ4 compressed. pub const VERB_FLAG_COMPRESSED: u8 = 0x80; +/// Mask to get only the verb from the verb + verb flags byte. +pub const VERB_MASK: u8 = 0x1f; + /// Maximum number of packet hops allowed by the protocol. pub const PROTOCOL_MAX_HOPS: usize = 7; diff --git a/network-hypervisor/src/vl1/dictionary.rs b/network-hypervisor/src/vl1/dictionary.rs index 945ec78b7..09eb3e3f0 100644 --- a/network-hypervisor/src/vl1/dictionary.rs +++ b/network-hypervisor/src/vl1/dictionary.rs @@ -66,6 +66,11 @@ impl Dictionary { self.0.len() } + #[inline(always)] + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } + pub fn get_str(&self, k: &str) -> Option<&str> { self.0.get(k).map_or(None, |v| std::str::from_utf8(v.as_slice()).map_or(None, |s| Some(s))) } diff --git a/network-hypervisor/src/vl1/endpoint.rs b/network-hypervisor/src/vl1/endpoint.rs index b5d424388..9dab128ea 100644 --- a/network-hypervisor/src/vl1/endpoint.rs +++ b/network-hypervisor/src/vl1/endpoint.rs @@ -17,16 +17,16 @@ const TYPE_WEBRTC: u8 = 9; #[repr(u8)] pub enum Type { - Nil = 0, - ZeroTier = 1, - Ethernet = 2, - WifiDirect = 3, - Bluetooth = 4, - Ip = 5, - IpUdp = 6, - IpTcp = 7, - Http = 8, - WebRTC = 9, + Nil = TYPE_NIL, + ZeroTier = TYPE_ZEROTIER, + Ethernet = TYPE_ETHERNET, + WifiDirect = TYPE_WIFIDIRECT, + Bluetooth = TYPE_BLUETOOTH, + Ip = TYPE_IP, + IpUdp = TYPE_IPUDP, + IpTcp = TYPE_IPTCP, + Http = TYPE_HTTP, + WebRTC = TYPE_WEBRTC, } #[derive(Clone, PartialEq, Eq, PartialOrd, Ord)] @@ -165,39 +165,39 @@ impl Hash for Endpoint { match self { Endpoint::Nil => { state.write_u8(Type::Nil as u8); - }, + } Endpoint::ZeroTier(a) => { state.write_u8(Type::ZeroTier as u8); state.write_u64(a.to_u64()) - }, + } Endpoint::Ethernet(m) => { state.write_u8(Type::Ethernet as u8); state.write_u64(m.to_u64()) - }, + } Endpoint::WifiDirect(m) => { state.write_u8(Type::WifiDirect as u8); state.write_u64(m.to_u64()) - }, + } Endpoint::Bluetooth(m) => { state.write_u8(Type::Bluetooth as u8); state.write_u64(m.to_u64()) - }, + } Endpoint::Ip(ip) => { state.write_u8(Type::Ip as u8); ip.hash(state); - }, + } Endpoint::IpUdp(ip) => { state.write_u8(Type::IpUdp as u8); ip.hash(state); - }, + } Endpoint::IpTcp(ip) => { state.write_u8(Type::IpTcp as u8); ip.hash(state); - }, + } Endpoint::Http(url) => { state.write_u8(Type::Http as u8); url.hash(state); - }, + } Endpoint::WebRTC(offer) => { state.write_u8(Type::WebRTC as u8); offer.hash(state); diff --git a/network-hypervisor/src/vl1/fragmentedpacket.rs b/network-hypervisor/src/vl1/fragmentedpacket.rs index 3a9668397..43fe4c2f8 100644 --- a/network-hypervisor/src/vl1/fragmentedpacket.rs +++ b/network-hypervisor/src/vl1/fragmentedpacket.rs @@ -5,12 +5,14 @@ use crate::vl1::constants::FRAGMENT_COUNT_MAX; use crate::vl1::Path; use crate::vl1::protocol::PacketID; -pub struct FragmentedPacket { +/// Packet fragment re-assembler and container. +/// This is only used in the receive path. +pub(crate) struct FragmentedPacket { pub id: PacketID, pub ts_ticks: i64, - frags: [Option; FRAGMENT_COUNT_MAX], - have: u8, - expecting: u8, + pub frags: [Option; FRAGMENT_COUNT_MAX], + pub have: u8, + pub expecting: u8, } impl Default for FragmentedPacket { @@ -26,29 +28,32 @@ impl Default for FragmentedPacket { } impl FragmentedPacket { - /// Reset this fragmented packet for re-use. + /// Return fragments to pool and reset id and ts_ticks to 0 and -1 respectively. #[inline(always)] - pub fn reset(&mut self) { + pub fn clear(&mut self) { self.id = 0; self.ts_ticks = -1; self.frags.fill(None); - self.have = 0; - self.expecting = 0; } - /// Initialize for a new packet. + /// Initialize for a new packet and log the first fragment. + /// This will panic if 'no' is out of bounds. #[inline(always)] - pub fn init(&mut self, id: PacketID, ts_ticks: i64) { + pub fn first_fragment(&mut self, id: PacketID, ts_ticks: i64, frag: PacketBuffer, no: u8, expecting: u8) { self.id = id; self.ts_ticks = ts_ticks; + let _ = self.frags[no as usize].replace(frag); + self.have = 1; + self.expecting = expecting; } /// Add a fragment to this fragment set and return true if the packet appears complete. + /// This will panic if 'no' is out of bounds. #[inline(always)] - pub fn add(&mut self, frag: PacketBuffer, no: u8, expecting: u8) -> bool { - if self.frags[no].replace(frag).is_none() { + pub fn add_fragment(&mut self, frag: PacketBuffer, no: u8, expecting: u8) -> bool { + if self.frags[no as usize].replace(frag).is_none() { self.have = self.have.wrapping_add(1); - self.expecting |= expecting; + self.expecting |= expecting; // in valid streams expecting is either 0 or the (same) total self.have == self.expecting } else { false diff --git a/network-hypervisor/src/vl1/mod.rs b/network-hypervisor/src/vl1/mod.rs index 9afc095c6..dc5ef8c51 100644 --- a/network-hypervisor/src/vl1/mod.rs +++ b/network-hypervisor/src/vl1/mod.rs @@ -7,7 +7,7 @@ pub(crate) mod dictionary; pub(crate) mod address; pub(crate) mod mac; pub(crate) mod fragmentedpacket; -mod(crate) mod whois; +pub(crate) mod whois; pub mod constants; pub mod identity; diff --git a/network-hypervisor/src/vl1/node.rs b/network-hypervisor/src/vl1/node.rs index 1a0b0c7f6..73c711738 100644 --- a/network-hypervisor/src/vl1/node.rs +++ b/network-hypervisor/src/vl1/node.rs @@ -17,7 +17,7 @@ use crate::vl1::constants::PACKET_SIZE_MAX; use crate::vl1::path::Path; use crate::vl1::peer::Peer; use crate::vl1::protocol::{FragmentHeader, is_fragment, PacketHeader, PacketID}; -use crate::vl1::whois::Whois; +use crate::vl1::whois::WhoisQueue; /// Standard packet buffer type including pool container. pub type PacketBuffer = Pooled>; @@ -99,9 +99,24 @@ pub trait VL1CallerInterface { fn time_clock(&self) -> i64; } +/// Trait implemented by VL2 to handle messages after they are unwrapped by VL1. +pub(crate) trait VL1PacketHandler { + /// Handle a packet, returning true if the verb was recognized. + /// True should be returned even if the packet is not valid, since the return value is used + /// to determine if this is a VL2 or VL1 packet. ERROR and OK should not be handled here but + /// in handle_error() and handle_ok() instead. + fn handle_packet(&self, peer: &Peer, source_path: &Arc, forward_secrecy: bool, verb: u8, payload: &Buffer<{ PACKET_SIZE_MAX }>) -> bool; + + /// Handle errors, returning true if the error was recognized. + fn handle_error(&self, peer: &Peer, source_path: &Arc, forward_secrecy: bool, in_re_verb: u8, in_re_packet_id: PacketID, error_code: u8, payload: &Buffer<{ PACKET_SIZE_MAX }>) -> bool; + + /// Handle an OK, returing true if the OK was recognized. + fn handle_ok(&self, peer: &Peer, source_path: &Arc, forward_secrecy: bool, in_re_verb: u8, in_re_packet_id: PacketID, payload: &Buffer<{ PACKET_SIZE_MAX }>) -> bool; +} + #[derive(Default)] struct BackgroundTaskIntervals { - whois: IntervalGate<{ Whois::INTERVAL }>, + whois: IntervalGate<{ WhoisQueue::INTERVAL }>, } pub struct Node { @@ -110,7 +125,7 @@ pub struct Node { locator: Mutex>, paths: DashMap>, peers: DashMap>, - whois: Whois, + whois: WhoisQueue, buffer_pool: Pool>, secure_prng: SecureRandom, } @@ -149,7 +164,7 @@ impl Node { locator: Mutex::new(None), paths: DashMap::new(), peers: DashMap::new(), - whois: Whois::new(), + whois: WhoisQueue::new(), buffer_pool: Pool::new(64), secure_prng: SecureRandom::get(), }) @@ -205,7 +220,8 @@ impl Node { } /// Called when a packet is received on the physical wire. - pub fn wire_receive(&self, ci: &CI, source_endpoint: &Endpoint, source_local_socket: i64, source_local_interface: i64, mut data: PacketBuffer) { + pub fn wire_receive(&self, ci: &CI, ph: &PH, source_endpoint: &Endpoint, source_local_socket: i64, source_local_interface: i64, mut data: PacketBuffer) { + /* let _ = data.struct_mut_at::(0).map(|fragment_header| { // NOTE: destination address is located at the same index in both the fragment // header and the full packet header, allowing us to make this decision once. @@ -216,19 +232,13 @@ impl Node { let path = self.path(source_endpoint, source_local_socket, source_local_interface); if fragment_header.is_fragment() { } else { - data.struct_mut_at::(0).map(|header| { - let source = Address::from(&header.src); - - if header.is_fragmented() { - } else { - } - }); } } else { // Packet or fragment is addressed to another node. } }); + */ } /// Get the canonical Path object for a given endpoint and local socket information. diff --git a/network-hypervisor/src/vl1/path.rs b/network-hypervisor/src/vl1/path.rs index 14f230a40..13f28a409 100644 --- a/network-hypervisor/src/vl1/path.rs +++ b/network-hypervisor/src/vl1/path.rs @@ -1,7 +1,7 @@ use std::sync::atomic::{AtomicI64, Ordering}; use crate::vl1::Endpoint; -use crate::vl1::constants::FRAGMENT_COUNT_MAX; +use crate::vl1::constants::{FRAGMENT_COUNT_MAX, FRAGMENT_EXPIRATION}; use crate::vl1::fragmentedpacket::FragmentedPacket; use crate::vl1::protocol::{FragmentHeader, PacketID}; use crate::vl1::node::PacketBuffer; @@ -48,46 +48,73 @@ impl Path { self.rxs.lock().last_receive_time_ticks } + /// Receive a fragment and invoke the handler if a packet appears fully assembled. + /// This also updates last receive time, etc. #[inline(always)] pub(crate) fn receive_fragment(&self, packet_id: PacketID, fragment_no: u8, fragment_expecting_count: u8, packet: PacketBuffer, time_ticks: i64, assembled_packet_handler: F) { if fragment_no < FRAGMENT_COUNT_MAX as u8 { let mut rxs = self.rxs.lock(); rxs.last_receive_time_ticks = time_ticks; - let mut fpcnt = rxs.fragmented_packet_count; - let mut fidx = 0; - while fpcnt > 0 { - let mut f = &mut rxs.fragmented_packets[fidx]; + // In most situlations this algorithms runs right through and doesn't need to iterate. + // If there are no fragments fpcnt will be 0 and the first loop will skip. If there are + // no fragments then the second loop won't be needed either since the first slot will + // be open. Looping only happens when there are multiple fragments in flight, which is + // not a common scenario for peer-to-peer links. The maximum iteration count in the + // worst case is only 2*FRAGMENT_COUNT_MAX and the loops are only doing integer + // comparisons, so the worst case is still linear. + + let mut fragmented_packets_to_check = rxs.fragmented_packet_count; + let mut i = 0; + while fragmented_packets_to_check > 0 { + let mut f = &mut rxs.fragmented_packets[i]; if f.id == packet_id { - if f.add(packet, fragment_no, fragment_expecting_count) { + if f.add_fragment(packet, fragment_no, fragment_expecting_count) { assembled_packet_handler(f); - f.reset(); - rxs.fragmented_packet_count = rxs.fragmented_packet_count.wrapping_sub(1); + f.clear(); + rxs.fragmented_packet_count -= 1; } return; } else if f.ts_ticks >= 0 { - fpcnt = fpcnt.wrapping_sub(1); + if (time_ticks - f.ts_ticks) > FRAGMENT_EXPIRATION { + f.clear(); + rxs.fragmented_packet_count -= 1; + } + fragmented_packets_to_check -= 1; } - fidx = fidx.wrapping_add(1); + i += 1; } - let mut oldest_ts = rxs.fragmented_packets[0].ts_ticks; - let mut oldest_idx = 0; - if oldest_ts >= 0 { + let mut oldest_ts = &mut rxs.fragmented_packets[0]; + let mut oldest_ts_ticks = oldest_ts.ts_ticks; + if oldest_ts_ticks >= 0 { for fidx in 1..FRAGMENT_COUNT_MAX { - let ts = rxs.fragmented_packets[fidx].ts_ticks; - if ts < oldest_ts { + let ts = &mut rxs.fragmented_packets[fidx]; + let tst = ts.ts_ticks; + if tst < oldest_ts_ticks { oldest_ts = ts; - oldest_idx = fidx; + oldest_ts_ticks = tst; + if tst < 0 { + break; + } } } } - let mut f = &mut rxs.fragmented_packets[oldest_idx]; - f.init(packet_id, time_ticks); - let _ = f.add(packet, fragment_no, fragment_expecting_count); + if oldest_ts_ticks < 0 { + rxs.fragmented_packet_count += 1; + } else { + oldest_ts.clear(); + } + rxs.fragmented_packets[oldest_idx].init(packet_id, time_ticks, packet, fragment_no, fragment_expecting_count); } } + + /// Register receipt of "anything" else which right now includes unfragmented packets and keepalives. + #[inline(always)] + pub(crate) fn receive_other(&self, time_ticks: i64) { + self.rxs.lock().last_receive_time_ticks = time_ticks; + } } unsafe impl Send for Path {} diff --git a/network-hypervisor/src/vl1/peer.rs b/network-hypervisor/src/vl1/peer.rs index 454f6cc0c..595eb0d6a 100644 --- a/network-hypervisor/src/vl1/peer.rs +++ b/network-hypervisor/src/vl1/peer.rs @@ -1,24 +1,93 @@ +use std::ops::DerefMut; use std::sync::Arc; -use std::sync::atomic::{AtomicI64, AtomicU64, AtomicU8}; - -use crate::vl1::{Identity, Path}; -use crate::vl1::fragmentedpacket::FragmentedPacket; -use crate::vl1::protocol::{PacketID, PacketHeader}; -use crate::vl1::node::{VL1CallerInterface, PacketBuffer, Node}; use parking_lot::Mutex; +use aes_gmac_siv::AesGmacSiv; -const MAX_PATHS: usize = 16; +use crate::crypto::c25519::C25519KeyPair; +use crate::crypto::hash::SHA384_HASH_SIZE; +use crate::crypto::kbkdf::zt_kbkdf_hmac_sha384; +use crate::crypto::p521::P521KeyPair; +use crate::crypto::poly1305::Poly1305; +use crate::crypto::random::next_u64_secure; +use crate::crypto::salsa::Salsa; +use crate::crypto::secret::Secret; +use crate::vl1::{Identity, Path}; +use crate::vl1::buffer::Buffer; +use crate::vl1::constants::*; +use crate::vl1::fragmentedpacket::FragmentedPacket; +use crate::vl1::node::*; +use crate::vl1::protocol::*; + +struct PeerSecrets { + // Time secret was created in ticks or -1 for static secrets. + create_time_ticks: i64, + + // Number of time secret has been used to encrypt something during this session. + encrypt_count: u64, + + // Raw secret itself. + secret: Secret<48>, + + // Reusable AES-GMAC-SIV initialized with secret. + aes: AesGmacSiv, +} + +struct EphemeralKeyPair { + // Time ephemeral key pair was created. + create_time_ticks: i64, + + // SHA384(c25519 public | p521 public) + public_keys_hash: [u8; 48], + + // Curve25519 ECDH key pair. + c25519: C25519KeyPair, + + // NIST P-521 ECDH key pair. + p521: P521KeyPair, +} struct TxState { - packet_iv_counter: u64, + // Time we last sent something to this peer. last_send_time_ticks: i64, - paths: [Arc; MAX_PATHS], + + // Outgoing packet IV counter, starts at a random position. + packet_iv_counter: u64, + + // Total bytes sent to this peer during this session. + total_bytes: u64, + + // "Eternal" static secret created via identity agreement. + static_secret: PeerSecrets, + + // The most recently negotiated ephemeral secret. + ephemeral_secret: Option, + + // The current ephemeral key pair we will share with HELLO. + ephemeral_pair: Option, + + // Paths to this peer sorted in descending order of quality with None entries at the end. + paths: [Option>; PEER_MAX_PATHS], } struct RxState { + // Time we last received something (authenticated) from this peer. last_receive_time_ticks: i64, - remote_version: [u8; 4], + + // Total bytes received from this peer during this session. + total_bytes: u64, + + // "Eternal" static secret created via identity agreement. + static_secret: PeerSecrets, + + // The most recently negotiated ephemeral secret. + ephemeral_secret: Option, + + // Remote version as major, minor, revision, build in most-to-least-significant 16-bit chunks. + // This is the user-facing software version and is zero if not yet known. + remote_version: u64, + + // Remote protocol version or zero if not yet known. remote_protocol_version: u8, } @@ -26,22 +95,245 @@ struct RxState { /// Sending-related and receiving-related fields are locked separately since concurrent /// send/receive is not uncommon. pub struct Peer { + // This peer's identity. identity: Identity, // Static shared secret computed from agreement with identity. - identity_static_secret: [u8; 48], + static_secret: Secret<48>, + + // Derived static secret used to encrypt the dictionary part of HELLO. + static_secret_hello_dictionary_encrypt: Secret<48>, + + // Derived static secret used to add full HMAC-SHA384 to packets, currently just HELLO. + static_secret_packet_hmac: Secret<48>, // State used primarily when sending to this peer. - txs: Mutex, + tx: Mutex, // State used primarily when receiving from this peer. - rxs: Mutex, + rx: Mutex, +} + +/// Derive per-packet key for Sals20/12 encryption (and Poly1305 authentication). +/// +/// This effectively adds a few additional bits of entropy to the IV from packet +/// characteristics such as its size and direction of communication. It also +/// effectively incorporates header information as AAD, since if the header info +/// is different the key will be wrong and MAC will fail. +/// +/// This is only used for Salsa/Poly modes. +#[inline(always)] +fn salsa_derive_per_packet_key(key: &Secret<48>, header: &PacketHeader, packet_size: usize) -> Secret<48> { + let hb = header.as_bytes(); + let mut k = key.clone(); + + for i in 0..18 { + k.0[i] ^= hb[i]; + } + + k.0[18] ^= hb[HEADER_FLAGS_FIELD_INDEX] & HEADER_FLAGS_FIELD_MASK_HIDE_HOPS; + + k.0[19] ^= (packet_size >> 8) as u8; + k.0[20] ^= packet_size as u8; + + k } impl Peer { - pub(crate) fn receive_from_singular(&self, node: &Node, ci: &CI, header: &PacketHeader, packet: &PacketBuffer) { + /// Create a new peer. + /// This only returns None if this_node_identity does not have its secrets or if some + /// fatal error occurs performing key agreement between the two identities. + pub(crate) fn new(this_node_identity: &Identity, id: Identity) -> Option { + this_node_identity.agree(&id).map(|static_secret| { + let aes_k0 = zt_kbkdf_hmac_sha384(&static_secret.0, KBKDF_KEY_USAGE_LABEL_AES_GMAC_SIV_K0, 0, 0); + let aes_k1 = zt_kbkdf_hmac_sha384(&static_secret.0, KBKDF_KEY_USAGE_LABEL_AES_GMAC_SIV_K1, 0, 0); + let static_secret_hello_dictionary_encrypt = zt_kbkdf_hmac_sha384(&static_secret.0, KBKDF_KEY_USAGE_LABEL_HELLO_DICTIONARY_ENCRYPT, 0, 0); + let static_secret_packet_hmac = zt_kbkdf_hmac_sha384(&static_secret.0, KBKDF_KEY_USAGE_LABEL_PACKET_HMAC, 0, 0); + Peer { + identity: id, + static_secret: static_secret.clone(), + static_secret_hello_dictionary_encrypt, + static_secret_packet_hmac, + tx: Mutex::new(TxState { + last_send_time_ticks: 0, + packet_iv_counter: next_u64_secure(), + total_bytes: 0, + static_secret: TxSecret { + create_time_ticks: -1, + usage_count: 0, + secret: static_secret.clone(), + aes: AesGmacSiv::new(&aes_k0.0, &aes_k1.0), + }, + ephemeral_secret: None, + paths: [None; PEER_MAX_PATHS], + ephemeral_pair: None, + }), + rx: Mutex::new(RxState { + last_receive_time_ticks: 0, + total_bytes: 0, + static_secret: PeerSecrets { + create_time_ticks: -1, + encrypt_count: 0, + secret: static_secret, + aes: AesGmacSiv::new(&aes_k0.0, &aes_k1.0), + }, + ephemeral_secret: None, + remote_version: 0, + remote_protocol_version: 0, + }), + } + }) } - pub(crate) fn receive_from_fragmented(&self, node: &Node, ci: CI, header: &PacketHeader, packet: &FragmentedPacket) { + /// Receive, decrypt, authenticate, and process an incoming packet from this peer. + /// If the packet comes in multiple fragments, the fragments slice should contain all + /// those fragments after the main packet header and first chunk. + pub(crate) fn receive(&self, node: &Node, ci: &CI, ph: &PH, time_ticks: i64, source_path: &Arc, header: &PacketHeader, packet: &Buffer<{ PACKET_SIZE_MAX }>, fragments: &[Option]) { + let packet_frag0_payload_bytes = packet.as_bytes_after(PACKET_VERB_INDEX).unwrap_or(&[]); + if !packet_frag0_payload_bytes.is_empty() { + let mut payload: Buffer<{ PACKET_SIZE_MAX }> = Buffer::new(); + let mut rx = self.rx.lock(); + + // When handling incoming packets we try any current ephemeral secret first, and if that + // fails we fall back to the static secret. If decryption with an ephemeral secret succeeds + // the forward secrecy flag in the receive path is set. + let mut secret = rx.ephemeral_secret.as_mut().unwrap_or(&mut rx.static_secret); + loop { + match header.cipher() { + CIPHER_NOCRYPT_POLY1305 => { + // Only HELLO is allowed in the clear (but still authenticated). + if (packet_frag0_payload_bytes[0] & VERB_MASK) == VERB_VL1_HELLO { + let _ = payload.append_bytes(packet_frag0_payload_bytes); + + for f in fragments.iter() { + let _ = f.as_ref().map(|f| { + let _ = f.as_bytes_after(FRAGMENT_HEADER_SIZE).map(|f| { + let _ = payload.append_bytes(f); + }); + }); + } + + // FIPS note: for FIPS purposes the HMAC-SHA384 tag at the end of V2 HELLOs + // will be considered the "real" handshake authentication. + let key = salsa_derive_per_packet_key(&secret.secret, header, payload.len()); + let mut salsa = Salsa::new(&key.0[0..32], header.id_bytes(), true).unwrap(); + let mut poly1305_key = [0_u8; 32]; + salsa.crypt_in_place(&mut poly1305_key); + let mut poly = Poly1305::new(&poly1305_key).unwrap(); + poly.update(packet_frag0_payload_bytes); + + if poly.finish()[0..8].eq(&header.message_auth) { + break; + } + } + } + + CIPHER_SALSA2012_POLY1305 => { + // FIPS note: support for this mode would have to be disabled in FIPS compliant + // modes of operation. + let key = salsa_derive_per_packet_key(&secret.secret, header, payload.len()); + let mut salsa = Salsa::new(&key.0[0..32], header.id_bytes(), true).unwrap(); + let mut poly1305_key = [0_u8; 32]; + salsa.crypt_in_place(&mut poly1305_key); + let mut poly = Poly1305::new(&poly1305_key).unwrap(); + + poly.update(packet_frag0_payload_bytes); + let _ = payload.append_and_init_bytes(packet_frag0_payload_bytes.len(), |b| salsa.crypt(packet_frag0_payload_bytes, b)); + for f in fragments.iter() { + let _ = f.as_ref().map(|f| { + let _ = f.as_bytes_after(FRAGMENT_HEADER_SIZE).map(|f| { + poly.update(f); + let _ = payload.append_and_init_bytes(f.len(), |b| salsa.crypt(f, b)); + }); + }); + } + + if poly.finish()[0..8].eq(&header.message_auth) { + break; + } + } + + CIPHER_AES_GMAC_SIV => { + secret.aes.reset(); + secret.aes.decrypt_init(&header.aes_gmac_siv_tag()); + secret.aes.decrypt_set_aad(&header.aad_bytes()); + + let _ = payload.append_and_init_bytes(packet_frag0_payload_bytes.len(), |b| secret.aes.decrypt(packet_frag0_payload_bytes, b)); + for f in fragments.iter() { + let _ = f.as_ref().map(|f| { + let _ = f.as_bytes_after(FRAGMENT_HEADER_SIZE).map(|f| { + let _ = payload.append_and_init_bytes(f.len(), |b| secret.aes.decrypt(f, b)); + }); + }); + } + + if secret.aes.decrypt_finish() { + break; + } + } + + _ => {} + } + + if (secret as *const PeerSecrets) != (&rx.static_secret as *const PeerSecrets) { + payload.clear(); + secret = &mut rx.static_secret; + } else { + // Both ephemeral (if any) and static secret have failed, drop packet. + return; + } + } + + // If we make it here we've successfully decrypted and authenticated the packet. + + rx.last_receive_time_ticks = time_ticks; + rx.total_bytes += payload.len() as u64; + + let forward_secrecy = (secret as *const PeerSecrets) != (&(rx.static_secret) as *const PeerSecrets); + + // Unlock rx state mutex. + drop(rx); + + let _ = payload.u8_at(0).map(|verb| { + // For performance reasons we let VL2 handle packets first. It returns false + // if it didn't pick up anything. + if !ph.handle_packet(self, source_path, forward_secrecy, verb, &payload) { + match verb { + VERB_VL1_NOP => {} + VERB_VL1_HELLO => {} + VERB_VL1_ERROR => {} + VERB_VL1_OK => {} + VERB_VL1_WHOIS => {} + VERB_VL1_RENDEZVOUS => {} + VERB_VL1_ECHO => {} + VERB_VL1_PUSH_DIRECT_PATHS => {} + VERB_VL1_USER_MESSAGE => {} + VERB_VL1_REMOTE_TRACE => {} + _ => {} + } + } + }); + } + } + + /// Get the remote version of this peer: major, minor, revision, and build. + /// Returns None if it's not yet known. + pub fn version(&self) -> Option<[u16; 4]> { + let rv = self.rx.lock().remote_version; + if rv != 0 { + Some([(rv >> 48) as u16, (rv >> 32) as u16, (rv >> 16) as u16, rv as u16]) + } else { + None + } + } + + /// Get the remote protocol version of this peer or None if not yet known. + pub fn protocol_version(&self) -> Option { + let pv = self.rx.lock().remote_protocol_version; + if pv != 0 { + Some(pv) + } else { + None + } } } diff --git a/network-hypervisor/src/vl1/protocol.rs b/network-hypervisor/src/vl1/protocol.rs index 5bb19cbc5..cff60d431 100644 --- a/network-hypervisor/src/vl1/protocol.rs +++ b/network-hypervisor/src/vl1/protocol.rs @@ -1,20 +1,33 @@ -use std::ops::Not; +use std::intrinsics::size_of; +use std::mem::MaybeUninit; -use crate::vl1::buffer::{RawObject, Buffer}; -use crate::vl1::constants::{HEADER_FLAGS_FIELD_MASK_CIPHER, HEADER_FLAGS_FIELD_MASK_HOPS, HEADER_FLAG_FRAGMENTED, FRAGMENT_INDICATOR}; use crate::vl1::Address; +use crate::vl1::buffer::{Buffer, RawObject}; +use crate::vl1::constants::*; + +pub const VERB_VL1_NOP: u8 = 0x00; +pub const VERB_VL1_HELLO: u8 = 0x01; +pub const VERB_VL1_ERROR: u8 = 0x02; +pub const VERB_VL1_OK: u8 = 0x03; +pub const VERB_VL1_WHOIS: u8 = 0x04; +pub const VERB_VL1_RENDEZVOUS: u8 = 0x05; +pub const VERB_VL1_ECHO: u8 = 0x08; +pub const VERB_VL1_PUSH_DIRECT_PATHS: u8 = 0x10; +pub const VERB_VL1_USER_MESSAGE: u8 = 0x14; +pub const VERB_VL1_REMOTE_TRACE: u8 = 0x15; /// A unique packet identifier, also the cryptographic nonce. +/// /// Packet IDs are stored as u64s for efficiency but they should be treated as /// [u8; 8] fields in that their endianness is "wire" endian. If for some reason /// packet IDs need to be portably compared or shared across systems they should /// be treated as bytes not integers. pub type PacketID = u64; -/// ZeroTier unencrypted outer header +/// ZeroTier unencrypted outer packet header +/// /// This is the header for a complete packet. If the fragmented flag is set, it will /// arrive with one or more fragments that must be assembled to complete it. -#[derive(Clone)] #[repr(packed)] pub struct PacketHeader { pub id: PacketID, @@ -32,9 +45,6 @@ impl PacketHeader { self.flags_cipher_hops & HEADER_FLAGS_FIELD_MASK_CIPHER } - /// Get this packet's hops field. - /// This is the only field in the unencrypted header that is not authenticated, allowing intermediate - /// nodes to increment it as they forward packets between indirectly connected peers. #[inline(always)] pub fn hops(&self) -> u8 { self.flags_cipher_hops & HEADER_FLAGS_FIELD_MASK_HOPS @@ -43,12 +53,9 @@ impl PacketHeader { #[inline(always)] pub fn increment_hops(&mut self) { let f = self.flags_cipher_hops; - self.flags_cipher_hops = (f & HEADER_FLAGS_FIELD_MASK_HOPS.not()) | ((f + 1) & HEADER_FLAGS_FIELD_MASK_HOPS); + self.flags_cipher_hops = (f & HEADER_FLAGS_FIELD_MASK_HIDE_HOPS) | ((f + 1) & HEADER_FLAGS_FIELD_MASK_HOPS); } - /// If true, this packet requires one or more fragments to fully assemble. - /// The one with the full header is always fragment 0. Note that is_fragment() is checked first - /// to see if this IS a fragment. #[inline(always)] pub fn is_fragmented(&self) -> bool { (self.flags_cipher_hops & HEADER_FLAG_FRAGMENTED) != 0 @@ -63,14 +70,41 @@ impl PacketHeader { pub fn source(&self) -> Address { Address::from(&self.src) } + + #[inline(always)] + pub fn id_bytes(&self) -> &[u8; 8] { + unsafe { &*(self as *const Self).cast::<[u8; 8]>() } + } + + #[inline(always)] + pub fn as_bytes(&self) -> &[u8; PACKET_HEADER_SIZE] { + unsafe { &*(self as *const Self).cast::<[u8; PACKET_HEADER_SIZE]>() } + } + + #[inline(always)] + pub fn aad_bytes(&self) -> [u8; 11] { + let mut id = unsafe { MaybeUninit::<[u8; 11]>::uninit().assume_init() }; + id[0..5].copy_from_slice(&self.dest); + id[5..10].copy_from_slice(&self.src); + id[10] = self.flags_cipher_hops & HEADER_FLAGS_FIELD_MASK_HIDE_HOPS; + id + } + + #[inline(always)] + pub fn aes_gmac_siv_tag(&self) -> [u8; 16] { + let mut id = unsafe { MaybeUninit::<[u8; 16]>::uninit().assume_init() }; + id[0..8].copy_from_slice(self.id_bytes()); + id[8..16].copy_from_slice(&self.message_auth); + id + } } /// ZeroTier fragment header +/// /// Fragments are indicated by byte 0xff at the start of the source address, which /// is normally illegal since addresses can't begin with that. Fragmented packets /// will arrive with the first fragment carrying a normal header with the fragment /// bit set and remaining fragments being these. -#[derive(Clone)] #[repr(packed)] pub struct FragmentHeader { pub id: PacketID, // packet ID @@ -80,7 +114,7 @@ pub struct FragmentHeader { pub reserved_hops: u8, // rrrrrHHH (3 hops bits, rest reserved) } -unsafe impl crate::vl1::buffer::RawObject for FragmentHeader {} +unsafe impl RawObject for FragmentHeader {} impl FragmentHeader { #[inline(always)] @@ -106,7 +140,7 @@ impl FragmentHeader { #[inline(always)] pub fn increment_hops(&mut self) { let f = self.reserved_hops; - self.reserved_hops = (f & HEADER_FLAGS_FIELD_MASK_HOPS.not()) | ((f + 1) & HEADER_FLAGS_FIELD_MASK_HOPS); + self.reserved_hops = (f & HEADER_FLAGS_FIELD_MASK_HIDE_HOPS) | ((f + 1) & HEADER_FLAGS_FIELD_MASK_HOPS); } #[inline(always)] @@ -118,12 +152,28 @@ impl FragmentHeader { #[cfg(test)] mod tests { use std::mem::size_of; - use crate::vl1::protocol::{PacketHeader, FragmentHeader}; - use crate::vl1::constants::{PACKET_HEADER_SIZE, FRAGMENT_SIZE_MIN}; + + use crate::vl1::constants::{FRAGMENT_HEADER_SIZE, PACKET_HEADER_SIZE}; + use crate::vl1::protocol::{FragmentHeader, PacketHeader}; #[test] - fn object_sizing() { + fn representation() { assert_eq!(size_of::(), PACKET_HEADER_SIZE); - assert_eq!(size_of::(), FRAGMENT_SIZE_MIN); + assert_eq!(size_of::(), FRAGMENT_HEADER_SIZE); + + let mut foo = [0_u8; 32]; + unsafe { + (*foo.as_mut_ptr().cast::()).src[0] = 0xff; + assert_eq!((*foo.as_ptr().cast::()).fragment_indicator, 0xff); + } + + let bar = PacketHeader{ + id: 0x0102030405060708_u64.to_be(), + dest: [0_u8; 5], + src: [0_u8; 5], + flags_cipher_hops: 0, + message_auth: [0_u8; 8], + }; + assert_eq!(bar.id_bytes(), [1_u8, 2, 3, 4, 5, 6, 7, 8]); } } diff --git a/network-hypervisor/src/vl1/whois.rs b/network-hypervisor/src/vl1/whois.rs index b7da6df98..148e93f9e 100644 --- a/network-hypervisor/src/vl1/whois.rs +++ b/network-hypervisor/src/vl1/whois.rs @@ -1,14 +1,14 @@ use std::collections::HashMap; -use crate::vl1::Address; -use crate::vl1::fragmentedpacket::FragmentedPacket; -use crate::vl1::node::{VL1CallerInterface, Node, PacketBuffer}; -use crate::util::gate::IntervalGate; - use parking_lot::Mutex; -use crate::vl1::constants::{WHOIS_RETRY_INTERVAL, WHOIS_RETRY_MAX}; -pub enum QueuedPacket { +use crate::util::gate::IntervalGate; +use crate::vl1::Address; +use crate::vl1::constants::*; +use crate::vl1::fragmentedpacket::FragmentedPacket; +use crate::vl1::node::{Node, PacketBuffer, VL1CallerInterface}; + +pub(crate) enum QueuedPacket { Singular(PacketBuffer), Fragmented(FragmentedPacket) } @@ -19,11 +19,11 @@ struct WhoisQueueItem { packet_queue: Vec } -pub struct Whois { +pub(crate) struct WhoisQueue { queue: Mutex> } -impl Whois { +impl WhoisQueue { pub const INTERVAL: i64 = WHOIS_RETRY_INTERVAL; pub fn new() -> Self {