diff --git a/controller/src/controller.rs b/controller/src/controller.rs index ebe9fde5c..467904f89 100644 --- a/controller/src/controller.rs +++ b/controller/src/controller.rs @@ -496,7 +496,7 @@ impl InnerProtocolLayer for Controller { app: &Application, node: &Node, source: &Arc>, - source_path: &Arc>, + source_path: &Arc>, source_hops: u8, message_id: u64, verb: u8, diff --git a/crypto/src/lib.rs b/crypto/src/lib.rs index f13d75eb1..c71d1554a 100644 --- a/crypto/src/lib.rs +++ b/crypto/src/lib.rs @@ -1,4 +1,5 @@ mod bn; +#[allow(unused)] mod cipher_ctx; mod ec; mod error; diff --git a/network-hypervisor/Cargo.toml b/network-hypervisor/Cargo.toml index 7e768e488..cf3d17c0e 100644 --- a/network-hypervisor/Cargo.toml +++ b/network-hypervisor/Cargo.toml @@ -16,8 +16,8 @@ lz4_flex = { version = "^0", features = ["safe-encode", "safe-decode", "checked- serde = { version = "^1", features = ["derive"], default-features = false } phf = { version = "^0", features = ["macros", "std"], default-features = false } num-traits = "^0" -rmp-serde = "^1" fastcdc = "^3" +serde_cbor = "^0" [dev-dependencies] rand = "*" diff --git a/network-hypervisor/benches/benchmark_identity.rs b/network-hypervisor/benches/benchmark_identity.rs index 7e1822be7..9d21b9e37 100644 --- a/network-hypervisor/benches/benchmark_identity.rs +++ b/network-hypervisor/benches/benchmark_identity.rs @@ -1,11 +1,11 @@ use criterion::{criterion_group, criterion_main, Criterion}; use std::time::Duration; -use zerotier_network_hypervisor::vl1::Identity; +use zerotier_network_hypervisor::vl1::identity::Identity; pub fn criterion_benchmark(c: &mut Criterion) { let mut group = c.benchmark_group("basic"); group.measurement_time(Duration::new(30, 0)); - group.bench_function("identity generation", |b| b.iter(|| Identity::generate())); + group.bench_function("identity generation", |b| b.iter(|| Identity::generate(false))); group.finish(); } diff --git a/network-hypervisor/src/protocol.rs b/network-hypervisor/src/protocol.rs index b7b1fb1dd..43c1c5f1d 100644 --- a/network-hypervisor/src/protocol.rs +++ b/network-hypervisor/src/protocol.rs @@ -138,9 +138,6 @@ pub const ADDRESS_SIZE: usize = 5; /// Length of an address in string format. pub const ADDRESS_SIZE_STRING: usize = 10; -/// Prefix indicating reserved addresses (that can't actually be addresses). -pub const ADDRESS_RESERVED_PREFIX: u8 = 0xff; - /// Bit mask for address bits in a u64. pub const ADDRESS_MASK: u64 = 0xffffffffff; @@ -307,10 +304,10 @@ pub mod v1 { } #[inline(always)] - pub fn get_packet_aad_bytes(destination: Address, source: Address, flags_cipher_hops: u8) -> [u8; 11] { + pub fn get_packet_aad_bytes(destination: &Address, source: &Address, flags_cipher_hops: u8) -> [u8; 11] { let mut id = [0u8; 11]; - id[0..5].copy_from_slice(&destination.to_bytes()); - id[5..10].copy_from_slice(&source.to_bytes()); + id[0..5].copy_from_slice(destination.as_bytes_v1()); + id[5..10].copy_from_slice(source.as_bytes_v1()); id[10] = flags_cipher_hops & FLAGS_FIELD_MASK_HIDE_HOPS; id } @@ -559,9 +556,6 @@ pub(crate) const PEER_HELLO_INTERVAL_MAX: i64 = 300000; /// Timeout for path association with peers and for peers themselves. pub(crate) const PEER_EXPIRATION_TIME: i64 = (PEER_HELLO_INTERVAL_MAX * 2) + 10000; -/// Proof of work difficulty (threshold) for identity generation. -pub(crate) const IDENTITY_POW_THRESHOLD: u8 = 17; - // Multicast LIKE expire time in milliseconds. pub const VL2_DEFAULT_MULTICAST_LIKE_EXPIRE: i64 = 600000; diff --git a/network-hypervisor/src/vl1/address.rs b/network-hypervisor/src/vl1/address.rs index e89bee1c1..0b52e02cc 100644 --- a/network-hypervisor/src/vl1/address.rs +++ b/network-hypervisor/src/vl1/address.rs @@ -1,79 +1,145 @@ // (c) 2020-2022 ZeroTier, Inc. -- currently proprietary pending actual release and licensing. See LICENSE.md. use std::fmt::Debug; -use std::hash::{Hash, Hasher}; -use std::num::NonZeroU64; use std::str::FromStr; use serde::{Deserialize, Deserializer, Serialize, Serializer}; -use crate::protocol::{ADDRESS_RESERVED_PREFIX, ADDRESS_SIZE}; - use zerotier_utils::error::InvalidFormatError; use zerotier_utils::hex; +use zerotier_utils::memory; -/// A unique address on the global ZeroTier VL1 network. -#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +const BASE62_ALPHABET: &'static [u8; 62] = b"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; +const BASE62_ALPHABET_REVERSE: [u8; 256] = [0; 256]; + +#[derive(Clone, PartialEq, Eq, Hash)] #[repr(transparent)] -pub struct Address(NonZeroU64); +pub struct Address([u128; 3]); impl Address { - /// Get an address from a 64-bit integer or return None if it is zero or reserved. - #[inline(always)] - pub fn from_u64(mut i: u64) -> Option
{ - i &= 0xffffffffff; - NonZeroU64::new(i).and_then(|ii| { - if (i >> 32) != ADDRESS_RESERVED_PREFIX as u64 { - Some(Address(ii)) - } else { - None - } - }) - } + pub const V1_ADDRESS_SIZE: usize = 5; + pub const V1_ADDRESS_STRING_SIZE: usize = 10; + pub const V2_ADDRESS_SIZE: usize = 48; + pub const V2_ADDRESS_STRING_SIZE: usize = 65; + + pub const RESERVED_PREFIX: u8 = 0xff; #[inline(always)] - pub fn from_bytes(b: &[u8]) -> Option
{ - if b.len() >= ADDRESS_SIZE { - Self::from_u64((b[0] as u64) << 32 | (b[1] as u64) << 24 | (b[2] as u64) << 16 | (b[3] as u64) << 8 | b[4] as u64) + pub(crate) fn new_uninitialized() -> Self { + Self([0, 0, 0]) + } + + pub fn from_bytes(b: &[u8]) -> Result, InvalidFormatError> { + if b.len() == Self::V1_ADDRESS_SIZE { + Ok(Self::from_bytes_v1(b)) + } else if b.len() == Self::V2_ADDRESS_SIZE { + Ok(Self::from_bytes_v2(b)) + } else { + Err(InvalidFormatError) + } + } + + pub(crate) fn from_bytes_v1(b: &[u8]) -> Option { + let mut a = Self([0; 3]); + memory::as_byte_array_mut::<[u128; 3], 48>(&mut a.0)[..Self::V1_ADDRESS_SIZE].copy_from_slice(b); + if a.0[0] != 0 { + Some(a) + } else { + None + } + } + + pub(crate) fn from_bytes_v2(b: &[u8]) -> Option { + let a = Self(memory::load_raw(b)); + if a.0.iter().any(|i| *i != 0) { + Some(a) } else { None } } #[inline(always)] - pub fn from_bytes_fixed(b: &[u8; ADDRESS_SIZE]) -> Option
{ - Self::from_u64((b[0] as u64) << 32 | (b[1] as u64) << 24 | (b[2] as u64) << 16 | (b[3] as u64) << 8 | b[4] as u64) + pub(crate) fn from_bytes_raw(b: &[u8; 48]) -> Option { + Self::from_bytes_v2(b) } #[inline(always)] - pub fn to_bytes(&self) -> [u8; ADDRESS_SIZE] { - let i = self.0.get(); - [(i >> 32) as u8, (i >> 24) as u8, (i >> 16) as u8, (i >> 8) as u8, i as u8] + pub(crate) fn from_u64_v1(i: u64) -> Option { + if i != 0 { + Some(Self([i.wrapping_shl(24 + 64).to_be() as u128, 0, 0])) + } else { + None + } } -} -impl From
for u64 { + /// True if this address lacks extended hash information. #[inline(always)] - fn from(a: Address) -> Self { - a.0.get() + pub fn is_v1_only(&self) -> bool { + self.0[1] == 0 && self.0[2] == 0 } -} -impl From<&Address> for u64 { #[inline(always)] - fn from(a: &Address) -> Self { - a.0.get() + pub(crate) fn as_bytes_raw(&self) -> &[u8; 48] { + memory::as_byte_array::<[u128; 3], 48>(&self.0) + } + + #[inline(always)] + pub(crate) fn as_bytes_raw_mut(&mut self) -> &mut [u8; 48] { + memory::as_byte_array_mut::<[u128; 3], 48>(&mut self.0) + } + + #[inline(always)] + pub(crate) fn as_u64_v1(&self) -> u64 { + u128::from_be(self.0[0]).wrapping_shr(24 + 64) as u64 + } + + #[inline(always)] + pub(crate) fn as_bytes_v1(&self) -> &[u8; Self::V1_ADDRESS_SIZE] { + memory::array_range::(memory::as_byte_array::<[u128; 3], 48>(&self.0)) + } + + /// Get all bits in this address (last 344 will be zero if this is only a V1 address). + #[inline(always)] + pub fn as_bytes_full(&self) -> &[u8; Self::V2_ADDRESS_SIZE] { + memory::as_byte_array::<[u128; 3], 48>(&self.0) + } + + /// Get a byte serialized address. + /// This returns either a 40-bit short address or a full 384 bits depending on whether this + /// contains only a short V1 address or a full length V2 address. Use as_bytes_full() to + /// always get all 384 bits with the last 344 being zero for V1-only addresses. + pub fn as_bytes(&self) -> &[u8] { + if self.is_v1_only() { + &memory::as_byte_array::<[u128; 3], 48>(&self.0)[..Self::V1_ADDRESS_SIZE] + } else { + memory::as_byte_array::<[u128; 3], 48>(&self.0) + } + } + + /// Get the short 10-digit address string for this address. + pub fn to_short_string(&self) -> String { + hex::to_string(&memory::as_byte_array::<[u128; 3], 48>(&self.0)[..Self::V1_ADDRESS_SIZE]) } } impl ToString for Address { fn to_string(&self) -> String { - let mut v = self.0.get() << 24; - let mut s = String::with_capacity(ADDRESS_SIZE * 2); - for _ in 0..(ADDRESS_SIZE * 2) { - s.push(hex::HEX_CHARS[(v >> 60) as usize] as char); - v <<= 4; + let mut s = String::with_capacity(Self::V2_ADDRESS_STRING_SIZE); + let mut remainders = 0u16; + for qq in self.0.iter() { + let mut q = u128::from_be(*qq); + for _ in 0..21 { + let (x, y) = (q % 62, q / 62); + q = y; + s.push(BASE62_ALPHABET[x as usize] as char); + } + debug_assert!(q <= 7); + remainders = remainders.wrapping_shl(3); + remainders |= q as u16; } + debug_assert!(remainders <= 511); + s.push(BASE62_ALPHABET[(remainders % 62) as usize] as char); + s.push(BASE62_ALPHABET[(remainders / 62) as usize] as char); s } } @@ -82,19 +148,63 @@ impl FromStr for Address { type Err = InvalidFormatError; fn from_str(s: &str) -> Result { - Address::from_bytes(hex::from_string(s).as_slice()).map_or_else(|| Err(InvalidFormatError), |a| Ok(a)) + if s.len() == Self::V1_ADDRESS_STRING_SIZE { + return Ok(Self::from_bytes_v1(hex::from_string(s).as_slice()).ok_or(InvalidFormatError)?); + } else if s.len() == Self::V2_ADDRESS_STRING_SIZE { + let mut s = s.as_bytes(); + let mut a = Self([0, 0, 0]); + for qi in 0..3 { + let mut q = 0u128; + for _ in 0..21 { + let r = BASE62_ALPHABET_REVERSE[s[0] as usize]; + if r == 255 { + return Err(InvalidFormatError); + } + q += r as u128; + s = &s[1..]; + q *= 62; + } + a.0[qi] = q; + } + let mut remainders = 0u16; + for _ in 0..2 { + let r = BASE62_ALPHABET_REVERSE[s[0] as usize]; + if r == 255 { + return Err(InvalidFormatError); + } + remainders += r as u16; + s = &s[1..]; + remainders *= 62; + } + if remainders > 511 { + return Err(InvalidFormatError); + } + a.0[0] += (remainders.wrapping_shr(6) & 7) as u128; + a.0[1] += (remainders.wrapping_shr(3) & 7) as u128; + a.0[2] += (remainders & 7) as u128; + return Ok(a); + } + return Err(InvalidFormatError); } } -impl Hash for Address { +impl PartialOrd for Address { #[inline(always)] - fn hash(&self, state: &mut H) { - state.write_u64(self.0.get()); + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for Address { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + u128::from_be(self.0[0]) + .cmp(&u128::from_be(other.0[0])) + .then(u128::from_be(self.0[1]).cmp(&u128::from_be(other.0[1]))) + .then(u128::from_be(self.0[2]).cmp(&u128::from_be(other.0[2]))) } } impl Debug for Address { - #[inline] fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str(self.to_string().as_str()) } @@ -108,7 +218,7 @@ impl Serialize for Address { if serializer.is_human_readable() { serializer.serialize_str(self.to_string().as_str()) } else { - serializer.serialize_bytes(&self.to_bytes()) + serializer.serialize_bytes(self.as_bytes()) } } } @@ -126,10 +236,10 @@ impl<'de> serde::de::Visitor<'de> for AddressVisitor { where E: serde::de::Error, { - if v.len() == ADDRESS_SIZE { - Address::from_bytes(v).map_or_else(|| Err(E::custom("object too large")), |a| Ok(a)) + if let Ok(Some(v)) = Address::from_bytes(v) { + Ok(v) } else { - Err(E::custom("object size incorrect")) + Err(E::custom("invalid address")) } } @@ -153,127 +263,3 @@ impl<'de> Deserialize<'de> for Address { } } } - -#[cfg(test)] -mod tests { - fn safe_address() -> super::Address { - let mut addr: Option; - - 'retry: loop { - let rawaddr: u64 = rand::random(); - addr = super::Address::from_u64(rawaddr); - - if addr.is_some() { - break 'retry; - } - } - - addr.unwrap() - } - - #[test] - fn address_marshal_u64() { - let mut rawaddr: u64 = rand::random(); - let addr = super::Address::from_u64(rawaddr); - assert!(addr.is_some()); - let addr: u64 = addr.unwrap().into(); - assert_eq!(addr, rawaddr & 0xffffffffff); - - rawaddr = 0; - assert!(super::Address::from_u64(rawaddr).is_none()); - - rawaddr = (crate::protocol::ADDRESS_RESERVED_PREFIX as u64) << 32; - assert!(super::Address::from_u64(rawaddr).is_none()); - } - - #[test] - fn address_marshal_bytes() { - use crate::protocol::ADDRESS_SIZE; - let mut v: Vec = Vec::with_capacity(ADDRESS_SIZE); - let mut i = 0; - while i < ADDRESS_SIZE { - v.push(rand::random()); - i += 1; - } - - let addr = super::Address::from_bytes(v.as_slice()); - assert!(addr.is_some()); - assert_eq!(addr.unwrap().to_bytes(), v.as_slice()); - - let empty: Vec = Vec::new(); - let emptyaddr = super::Address::from_bytes(empty.as_slice()); - assert!(emptyaddr.is_none()); - - let mut v2: [u8; ADDRESS_SIZE] = [0u8; ADDRESS_SIZE]; - let mut i = 0; - while i < ADDRESS_SIZE { - v2[i] = v[i]; - i += 1; - } - - let addr2 = super::Address::from_bytes_fixed(&v2); - assert!(addr2.is_some()); - assert_eq!(addr2.unwrap().to_bytes(), v2); - - assert_eq!(addr.unwrap(), addr2.unwrap()); - } - - #[test] - fn address_to_from_string() { - use std::str::FromStr; - - for _ in 0..1000 { - let rawaddr: u64 = rand::random(); - let addr = super::Address::from_u64(rawaddr); - - // NOTE: a regression here is covered by other tests and should not break this test - // accidentally. - if addr.is_none() { - continue; - } - - let addr = addr.unwrap(); - assert_ne!(addr.to_string(), ""); - assert_eq!(addr.to_string().len(), 10); - - assert_eq!(super::Address::from_str(&addr.to_string()).unwrap(), addr); - } - } - - #[test] - fn address_hash() { - use std::collections::hash_map::DefaultHasher; - use std::hash::{Hash, Hasher}; - - let mut hasher = DefaultHasher::new(); - - let addr = safe_address(); - addr.hash(&mut hasher); - let result1 = hasher.finish(); - - // this loop is mostly to ensure that hash returns a consistent result every time. - for _ in 0..1000 { - let mut hasher = std::collections::hash_map::DefaultHasher::new(); - addr.hash(&mut hasher); - let result2 = hasher.finish(); - assert_ne!(result2.to_string(), ""); - assert_eq!(result1.to_string(), result2.to_string()); - } - } - - #[test] - fn address_serialize() { - let addr = safe_address(); - - for _ in 0..1000 { - assert_eq!( - serde_json::from_str::(&serde_json::to_string(&addr).unwrap()).unwrap(), - addr - ); - assert_eq!( - serde_cbor::from_slice::(&serde_cbor::to_vec(&addr).unwrap()).unwrap(), - addr - ); - } - } -} diff --git a/network-hypervisor/src/vl1/endpoint.rs b/network-hypervisor/src/vl1/endpoint.rs index 0bf307dae..f7985be02 100644 --- a/network-hypervisor/src/vl1/endpoint.rs +++ b/network-hypervisor/src/vl1/endpoint.rs @@ -6,14 +6,13 @@ use std::str::FromStr; use serde::{Deserialize, Deserializer, Serialize, Serializer}; -use crate::vl1::identity::IDENTITY_FINGERPRINT_SIZE; use crate::vl1::inetaddress::InetAddress; use crate::vl1::{Address, MAC}; -use zerotier_utils::base64; -use zerotier_utils::buffer::Buffer; +use zerotier_utils::buffer::{Buffer, OutOfBoundsError}; use zerotier_utils::error::InvalidFormatError; use zerotier_utils::marshalable::{Marshalable, UnmarshalError}; +use zerotier_utils::str::{escape, unescape}; pub const TYPE_NIL: u8 = 0; pub const TYPE_ZEROTIER: u8 = 1; @@ -39,7 +38,7 @@ pub enum Endpoint { /// Via another node using unencapsulated relaying (e.g. via a root) /// This is the address and the full identity fingerprint. - ZeroTier(Address, [u8; IDENTITY_FINGERPRINT_SIZE]), + ZeroTier(Address), /// Direct L2 Ethernet Ethernet(MAC), @@ -67,7 +66,7 @@ pub enum Endpoint { /// Via another node using inner encapsulation via VERB_ENCAP. /// This is the address and the full identity fingerprint. - ZeroTierEncap(Address, [u8; IDENTITY_FINGERPRINT_SIZE]), + ZeroTierEncap(Address), } impl Default for Endpoint { @@ -92,7 +91,7 @@ impl Endpoint { pub fn type_id(&self) -> u8 { match self { Endpoint::Nil => TYPE_NIL, - Endpoint::ZeroTier(_, _) => TYPE_ZEROTIER, + Endpoint::ZeroTier(_) => TYPE_ZEROTIER, Endpoint::Ethernet(_) => TYPE_ETHERNET, Endpoint::WifiDirect(_) => TYPE_WIFIDIRECT, Endpoint::Bluetooth(_) => TYPE_BLUETOOTH, @@ -101,7 +100,7 @@ impl Endpoint { Endpoint::IpTcp(_) => TYPE_IPTCP, Endpoint::Http(_) => TYPE_HTTP, Endpoint::WebRTC(_) => TYPE_WEBRTC, - Endpoint::ZeroTierEncap(_, _) => TYPE_ZEROTIER_ENCAP, + Endpoint::ZeroTierEncap(_) => TYPE_ZEROTIER_ENCAP, } } @@ -134,15 +133,14 @@ impl Endpoint { impl Marshalable for Endpoint { const MAX_MARSHAL_SIZE: usize = MAX_MARSHAL_SIZE; - fn marshal(&self, buf: &mut Buffer) -> Result<(), UnmarshalError> { + fn marshal(&self, buf: &mut Buffer) -> Result<(), OutOfBoundsError> { match self { Endpoint::Nil => { buf.append_u8(16 + TYPE_NIL)?; } - Endpoint::ZeroTier(a, h) => { + Endpoint::ZeroTier(a) => { buf.append_u8(16 + TYPE_ZEROTIER)?; - buf.append_bytes_fixed(&a.to_bytes())?; - buf.append_bytes_fixed(h)?; + buf.append_bytes_fixed(a.as_bytes_raw())?; } Endpoint::Ethernet(m) => { buf.append_u8(16 + TYPE_ETHERNET)?; @@ -184,10 +182,9 @@ impl Marshalable for Endpoint { buf.append_varint(b.len() as u64)?; buf.append_bytes(b)?; } - Endpoint::ZeroTierEncap(a, h) => { + Endpoint::ZeroTierEncap(a) => { buf.append_u8(16 + TYPE_ZEROTIER_ENCAP)?; - buf.append_bytes_fixed(&a.to_bytes())?; - buf.append_bytes_fixed(h)?; + buf.append_bytes_fixed(a.as_bytes_raw())?; } } Ok(()) @@ -214,10 +211,9 @@ impl Marshalable for Endpoint { } else { match type_byte - 16 { TYPE_NIL => Ok(Endpoint::Nil), - TYPE_ZEROTIER => { - let zt = Address::from_bytes_fixed(buf.read_bytes_fixed(cursor)?).ok_or(UnmarshalError::InvalidData)?; - Ok(Endpoint::ZeroTier(zt, buf.read_bytes_fixed::(cursor)?.clone())) - } + TYPE_ZEROTIER => Ok(Endpoint::ZeroTier( + Address::from_bytes_raw(buf.read_bytes_fixed(cursor)?).ok_or(UnmarshalError::InvalidData)?, + )), TYPE_ETHERNET => Ok(Endpoint::Ethernet(MAC::unmarshal(buf, cursor)?)), TYPE_WIFIDIRECT => Ok(Endpoint::WifiDirect(MAC::unmarshal(buf, cursor)?)), TYPE_BLUETOOTH => Ok(Endpoint::Bluetooth(MAC::unmarshal(buf, cursor)?)), @@ -228,10 +224,9 @@ impl Marshalable for Endpoint { String::from_utf8_lossy(buf.read_bytes(buf.read_varint(cursor)? as usize, cursor)?).to_string(), )), TYPE_WEBRTC => Ok(Endpoint::WebRTC(buf.read_bytes(buf.read_varint(cursor)? as usize, cursor)?.to_vec())), - TYPE_ZEROTIER_ENCAP => { - let zt = Address::from_bytes_fixed(buf.read_bytes_fixed(cursor)?).ok_or(UnmarshalError::InvalidData)?; - Ok(Endpoint::ZeroTierEncap(zt, buf.read_bytes_fixed(cursor)?.clone())) - } + TYPE_ZEROTIER_ENCAP => Ok(Endpoint::ZeroTier( + Address::from_bytes_raw(buf.read_bytes_fixed(cursor)?).ok_or(UnmarshalError::InvalidData)?, + )), _ => Err(UnmarshalError::InvalidData), } } @@ -244,9 +239,9 @@ impl Hash for Endpoint { Endpoint::Nil => { state.write_u8(TYPE_NIL); } - Endpoint::ZeroTier(a, _) => { + Endpoint::ZeroTier(a) => { state.write_u8(TYPE_ZEROTIER); - state.write_u64(a.into()) + a.hash(state); } Endpoint::Ethernet(m) => { state.write_u8(TYPE_ETHERNET); @@ -280,9 +275,9 @@ impl Hash for Endpoint { state.write_u8(TYPE_WEBRTC); offer.hash(state); } - Endpoint::ZeroTierEncap(a, _) => { + Endpoint::ZeroTierEncap(a) => { state.write_u8(TYPE_ZEROTIER_ENCAP); - state.write_u64(a.into()) + a.hash(state); } } } @@ -293,7 +288,7 @@ impl Ord for Endpoint { // Manually implement Ord to ensure that sort order is known and consistent. match (self, other) { (Endpoint::Nil, Endpoint::Nil) => Ordering::Equal, - (Endpoint::ZeroTier(a, ah), Endpoint::ZeroTier(b, bh)) => a.cmp(b).then_with(|| ah.cmp(bh)), + (Endpoint::ZeroTier(a), Endpoint::ZeroTier(b)) => a.cmp(b), (Endpoint::Ethernet(a), Endpoint::Ethernet(b)) => a.cmp(b), (Endpoint::WifiDirect(a), Endpoint::WifiDirect(b)) => a.cmp(b), (Endpoint::Bluetooth(a), Endpoint::Bluetooth(b)) => a.cmp(b), @@ -302,7 +297,7 @@ impl Ord for Endpoint { (Endpoint::IpTcp(a), Endpoint::IpTcp(b)) => a.cmp(b), (Endpoint::Http(a), Endpoint::Http(b)) => a.cmp(b), (Endpoint::WebRTC(a), Endpoint::WebRTC(b)) => a.cmp(b), - (Endpoint::ZeroTierEncap(a, ah), Endpoint::ZeroTierEncap(b, bh)) => a.cmp(b).then_with(|| ah.cmp(bh)), + (Endpoint::ZeroTierEncap(a), Endpoint::ZeroTierEncap(b)) => a.cmp(b), _ => self.type_id().cmp(&other.type_id()), } } @@ -319,7 +314,7 @@ impl ToString for Endpoint { fn to_string(&self) -> String { match self { Endpoint::Nil => format!("nil"), - Endpoint::ZeroTier(a, ah) => format!("zt:{}-{}", a.to_string(), base64::encode_url_nopad(ah)), + Endpoint::ZeroTier(a) => format!("zt:{}", a.to_string()), Endpoint::Ethernet(m) => format!("eth:{}", m.to_string()), Endpoint::WifiDirect(m) => format!("wifip2p:{}", m.to_string()), Endpoint::Bluetooth(m) => format!("bt:{}", m.to_string()), @@ -327,8 +322,8 @@ impl ToString for Endpoint { Endpoint::IpUdp(ip) => format!("udp:{}", ip.to_string()), Endpoint::IpTcp(ip) => format!("tcp:{}", ip.to_string()), Endpoint::Http(url) => format!("url:{}", url.clone()), // http or https - Endpoint::WebRTC(offer) => format!("webrtc:{}", base64::encode_url_nopad(offer.as_slice())), - Endpoint::ZeroTierEncap(a, ah) => format!("zte:{}-{}", a.to_string(), base64::encode_url_nopad(ah)), + Endpoint::WebRTC(offer) => format!("webrtc:{}", escape(offer.as_slice())), + Endpoint::ZeroTierEncap(a) => format!("zte:{}", a.to_string()), } } } @@ -348,18 +343,10 @@ impl FromStr for Endpoint { let (endpoint_type, endpoint_data) = ss.unwrap(); match endpoint_type { "zt" | "zte" => { - let address_and_hash = endpoint_data.split_once("-"); - if address_and_hash.is_some() { - let (address, hash) = address_and_hash.unwrap(); - if let Some(hash) = base64::decode_url_nopad(hash) { - if hash.len() == IDENTITY_FINGERPRINT_SIZE { - if endpoint_type == "zt" { - return Ok(Endpoint::ZeroTier(Address::from_str(address)?, hash.as_slice().try_into().unwrap())); - } else { - return Ok(Endpoint::ZeroTierEncap(Address::from_str(address)?, hash.as_slice().try_into().unwrap())); - } - } - } + if endpoint_type == "zt" { + return Ok(Endpoint::ZeroTier(Address::from_str(endpoint_data)?)); + } else { + return Ok(Endpoint::ZeroTierEncap(Address::from_str(endpoint_data)?)); } } "eth" => return Ok(Endpoint::Ethernet(MAC::from_str(endpoint_data)?)), @@ -369,11 +356,7 @@ impl FromStr for Endpoint { "udp" => return Ok(Endpoint::IpUdp(InetAddress::from_str(endpoint_data)?)), "tcp" => return Ok(Endpoint::IpTcp(InetAddress::from_str(endpoint_data)?)), "url" => return Ok(Endpoint::Http(endpoint_data.into())), - "webrtc" => { - if let Some(offer) = base64::decode_url_nopad(endpoint_data) { - return Ok(Endpoint::WebRTC(offer)); - } - } + "webrtc" => return Ok(Endpoint::WebRTC(unescape(endpoint_data))), _ => {} } return Err(InvalidFormatError); @@ -438,278 +421,3 @@ impl<'de> Deserialize<'de> for Endpoint { } } } - -#[cfg(test)] -mod tests { - use super::*; - use crate::protocol::*; - - fn randstring(len: u8) -> String { - (0..len) - .map(|_| (rand::random::() % 26) + 'a' as u8) - .map(|c| { - if rand::random::() { - (c as char).to_ascii_uppercase() - } else { - c as char - } - }) - .map(|c| c.to_string()) - .collect::>() - .join("") - } - - #[test] - fn endpoint_default() { - let e: Endpoint = Default::default(); - assert!(matches!(e, Endpoint::Nil)) - } - - #[test] - fn endpoint_from_bytes() { - let v = [0u8; MAX_MARSHAL_SIZE]; - assert!(Endpoint::from_bytes(&v).is_none()); - } - - #[test] - fn endpoint_marshal_nil() { - let n = Endpoint::Nil; - - let mut buf = Buffer::<1>::new(); - - let res = n.marshal(&mut buf); - assert!(res.is_ok()); - - let res = Endpoint::unmarshal(&buf, &mut 0); - assert!(res.is_ok()); - - let n2 = res.unwrap(); - assert_eq!(n, n2); - } - - #[test] - fn endpoint_marshal_zerotier() { - for _ in 0..1000 { - let mut hash = [0u8; IDENTITY_FINGERPRINT_SIZE]; - hash.fill_with(|| rand::random()); - - let mut v = [0u8; ADDRESS_SIZE]; - v.fill_with(|| rand::random()); - - // correct for situations where RNG generates a prefix which generates a None value. - while v[0] == ADDRESS_RESERVED_PREFIX { - v[0] = rand::random() - } - - let zte = Endpoint::ZeroTier(Address::from_bytes(&v).unwrap(), hash); - - const TMP: usize = IDENTITY_FINGERPRINT_SIZE + 8; - let mut buf = Buffer::::new(); - - let res = zte.marshal(&mut buf); - assert!(res.is_ok()); - - let res = Endpoint::unmarshal(&buf, &mut 0); - assert!(res.is_ok()); - - let zte2 = res.unwrap(); - assert_eq!(zte, zte2); - } - } - - #[test] - fn endpoint_marshal_zerotier_encap() { - for _ in 0..1000 { - let mut hash = [0u8; IDENTITY_FINGERPRINT_SIZE]; - hash.fill_with(|| rand::random()); - - let mut v = [0u8; ADDRESS_SIZE]; - v.fill_with(|| rand::random()); - - // correct for situations where RNG generates a prefix which generates a None value. - while v[0] == ADDRESS_RESERVED_PREFIX { - v[0] = rand::random() - } - - let zte = Endpoint::ZeroTierEncap(Address::from_bytes(&v).unwrap(), hash); - - const TMP: usize = IDENTITY_FINGERPRINT_SIZE + 8; - let mut buf = Buffer::::new(); - - let res = zte.marshal(&mut buf); - assert!(res.is_ok()); - - let res = Endpoint::unmarshal(&buf, &mut 0); - assert!(res.is_ok()); - - let zte2 = res.unwrap(); - assert_eq!(zte, zte2); - } - } - - #[test] - fn endpoint_marshal_mac() { - for _ in 0..1000 { - let mac = crate::vl1::MAC::from_u64(rand::random()).unwrap(); - - for e in [ - Endpoint::Ethernet(mac.clone()), - Endpoint::WifiDirect(mac.clone()), - Endpoint::Bluetooth(mac.clone()), - ] { - let mut buf = Buffer::<7>::new(); - - let res = e.marshal(&mut buf); - assert!(res.is_ok()); - - let res = Endpoint::unmarshal(&buf, &mut 0); - assert!(res.is_ok()); - - let e2 = res.unwrap(); - assert_eq!(e, e2); - } - } - } - - #[test] - fn endpoint_marshal_inetaddress() { - for _ in 0..1000 { - let mut v = [0u8; 16]; - v.fill_with(|| rand::random()); - - let inet = crate::vl1::InetAddress::from_ip_port(&v, 1234); - - for e in [Endpoint::Icmp(inet.clone()), Endpoint::IpTcp(inet.clone()), Endpoint::IpUdp(inet.clone())] { - let mut buf = Buffer::<20>::new(); - - let res = e.marshal(&mut buf); - assert!(res.is_ok()); - - let res = Endpoint::unmarshal(&buf, &mut 0); - assert!(res.is_ok()); - - let e2 = res.unwrap(); - assert_eq!(e, e2); - } - } - } - - #[test] - fn endpoint_marshal_http() { - for _ in 0..1000 { - let http = Endpoint::Http(randstring(30)); - let mut buf = Buffer::<33>::new(); - - assert!(http.marshal(&mut buf).is_ok()); - - let res = Endpoint::unmarshal(&buf, &mut 0); - assert!(res.is_ok()); - - let http2 = res.unwrap(); - assert_eq!(http, http2); - } - } - - #[test] - fn endpoint_marshal_webrtc() { - for _ in 0..1000 { - let mut v = Vec::with_capacity(100); - v.fill_with(|| rand::random()); - - let rtc = Endpoint::WebRTC(v); - let mut buf = Buffer::<102>::new(); - - assert!(rtc.marshal(&mut buf).is_ok()); - - let res = Endpoint::unmarshal(&buf, &mut 0); - assert!(res.is_ok()); - - let rtc2 = res.unwrap(); - assert_eq!(rtc, rtc2); - } - } - - #[test] - fn endpoint_to_from_string() { - use std::str::FromStr; - - for _ in 0..1000 { - let mut v = Vec::with_capacity(100); - v.fill_with(|| rand::random()); - let rtc = Endpoint::WebRTC(v); - - assert_ne!(rtc.to_string().len(), 0); - assert!(rtc.to_string().starts_with("webrtc")); - - let rtc2 = Endpoint::from_str(&rtc.to_string()).unwrap(); - assert_eq!(rtc, rtc2); - - let http = Endpoint::Http(randstring(30)); - assert_ne!(http.to_string().len(), 0); - assert!(http.to_string().starts_with("url")); - - let http2 = Endpoint::from_str(&http.to_string()).unwrap(); - assert_eq!(http, http2); - - let mut v = [0u8; 16]; - v.fill_with(|| rand::random()); - - let inet = crate::vl1::InetAddress::from_ip_port(&v, 0); - - let ip = Endpoint::Icmp(inet.clone()); - assert_ne!(ip.to_string().len(), 0); - assert!(ip.to_string().starts_with("icmp")); - - let ip2 = Endpoint::from_str(&ip.to_string()).unwrap(); - assert_eq!(ip, ip2); - - let inet = crate::vl1::InetAddress::from_ip_port(&v, 1234); - - for e in [(Endpoint::IpTcp(inet.clone()), "tcp"), (Endpoint::IpUdp(inet.clone()), "udp")] { - assert_ne!(e.0.to_string().len(), 0); - assert!(e.0.to_string().starts_with(e.1)); - - let e2 = Endpoint::from_str(&e.0.to_string()).unwrap(); - assert_eq!(e.0, e2); - } - - let mac = crate::vl1::MAC::from_u64(rand::random()).unwrap(); - - for e in [ - (Endpoint::Ethernet(mac.clone()), "eth"), - (Endpoint::WifiDirect(mac.clone()), "wifip2p"), - (Endpoint::Bluetooth(mac.clone()), "bt"), - ] { - assert_ne!(e.0.to_string().len(), 0); - assert!(e.0.to_string().starts_with(e.1)); - - let e2 = Endpoint::from_str(&e.0.to_string()).unwrap(); - assert_eq!(e.0, e2); - } - - let mut hash = [0u8; IDENTITY_FINGERPRINT_SIZE]; - hash.fill_with(|| rand::random()); - - let mut v = [0u8; ADDRESS_SIZE]; - v.fill_with(|| rand::random()); - - // correct for situations where RNG generates a prefix which generates a None value. - while v[0] == ADDRESS_RESERVED_PREFIX { - v[0] = rand::random() - } - - for e in [ - (Endpoint::ZeroTier(Address::from_bytes(&v).unwrap(), hash), "zt"), - (Endpoint::ZeroTierEncap(Address::from_bytes(&v).unwrap(), hash), "zte"), - ] { - assert_ne!(e.0.to_string().len(), 0); - assert!(e.0.to_string().starts_with(e.1)); - - let e2 = Endpoint::from_str(&e.0.to_string()).unwrap(); - assert_eq!(e.0, e2); - } - - assert_eq!(Endpoint::Nil.to_string(), "nil"); - } - } -} diff --git a/network-hypervisor/src/vl1/event.rs b/network-hypervisor/src/vl1/event.rs index 6855c69b6..c9d536426 100644 --- a/network-hypervisor/src/vl1/event.rs +++ b/network-hypervisor/src/vl1/event.rs @@ -1,6 +1,6 @@ // (c) 2020-2022 ZeroTier, Inc. -- currently proprietary pending actual release and licensing. See LICENSE.md. -use crate::vl1::*; +use crate::vl1::identity::Identity; #[derive(Clone)] pub enum Event { diff --git a/network-hypervisor/src/vl1/identity.rs b/network-hypervisor/src/vl1/identity.rs index a50e10f68..4325d40ee 100644 --- a/network-hypervisor/src/vl1/identity.rs +++ b/network-hypervisor/src/vl1/identity.rs @@ -1,107 +1,454 @@ // (c) 2020-2022 ZeroTier, Inc. -- currently proprietary pending actual release and licensing. See LICENSE.md. -use std::cmp::Ordering; -use std::convert::TryInto; -use std::fmt::Debug; -use std::hash::{Hash, Hasher}; use std::io::Write; use std::str::FromStr; use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use super::Address; + +use zerotier_crypto::hash::{SHA384, SHA384_HASH_SIZE, SHA512}; use zerotier_crypto::p384::*; use zerotier_crypto::salsa::Salsa; use zerotier_crypto::secret::Secret; +use zerotier_crypto::typestate::Valid; use zerotier_crypto::x25519::*; -use zerotier_crypto::{hash::*, secure_eq}; - use zerotier_utils::arrayvec::ArrayVec; -use zerotier_utils::base64; -use zerotier_utils::buffer::Buffer; -use zerotier_utils::error::{InvalidFormatError, InvalidParameterError}; -use zerotier_utils::hex; +use zerotier_utils::buffer::{Buffer, OutOfBoundsError}; +use zerotier_utils::error::InvalidFormatError; use zerotier_utils::marshalable::{Marshalable, UnmarshalError}; +use zerotier_utils::{hex, memory}; -use crate::protocol::{ADDRESS_SIZE, ADDRESS_SIZE_STRING, IDENTITY_POW_THRESHOLD}; -use crate::vl1::Address; -use crate::vl1::Valid; +#[derive(Clone, PartialEq, Eq)] +pub struct Identity { + pub address: Address, + pub x25519: X25519, + pub p384: Option, +} -/// Current maximum size for an identity signature. -pub const IDENTITY_MAX_SIGNATURE_SIZE: usize = P384_ECDSA_SIGNATURE_SIZE + 1; +#[derive(Clone, PartialEq, Eq)] +pub struct X25519 { + pub ecdh: [u8; C25519_PUBLIC_KEY_SIZE], + pub eddsa: [u8; ED25519_PUBLIC_KEY_SIZE], +} -/// Size of an identity fingerprint (SHA384) -pub const IDENTITY_FINGERPRINT_SIZE: usize = 48; - -/// Secret keys associated with NIST P-384 public keys. #[derive(Clone)] -pub struct IdentityP384Secret { +pub struct P384 { + pub ecdh: P384PublicKey, + pub ecdsa: P384PublicKey, + pub ed25519_self_signature: [u8; ED25519_SIGNATURE_SIZE], + pub p384_self_signature: [u8; P384_ECDSA_SIGNATURE_SIZE], +} + +pub struct IdentitySecret { + pub x25519: X25519Secret, + pub p384: Option, +} + +pub struct X25519Secret { + pub ecdh: X25519KeyPair, + pub eddsa: Ed25519KeyPair, +} + +pub struct P384Secret { pub ecdh: P384KeyPair, pub ecdsa: P384KeyPair, } -/// NIST P-384 public keys and signatures binding them bidirectionally to V0 c25519 keys. -#[derive(Clone)] -pub struct IdentityP384Public { - pub ecdh: P384PublicKey, - pub ecdsa: P384PublicKey, - pub ecdsa_self_signature: [u8; P384_ECDSA_SIGNATURE_SIZE], - pub ed25519_self_signature: [u8; ED25519_SIGNATURE_SIZE], +impl Identity { + pub const MAX_SIGNATURE_SIZE: usize = 96; + + const ALGORITHM_X25519: u8 = 0; + const ALGORITHM_P384: u8 = 1; + + const V0_IDENTITY_POW_THRESHOLD: u8 = 17; + + /// Generate a new ZeroTier identity. + /// If x25519_only is true a legacy identity without NIST P-384 key pairs will be generated. + pub fn generate(x25519_only: bool) -> (Identity, IdentitySecret) { + // Generate X25519 portions of the identity plus the first 40 bits of the address, which are + // the legacy "short" address. + let mut secret = IdentitySecret { + x25519: X25519Secret { + ecdh: X25519KeyPair::generate(), + eddsa: Ed25519KeyPair::generate(), + }, + p384: None, + }; + let mut public = Identity { + address: Address::new_uninitialized(), + x25519: X25519 { + ecdh: secret.x25519.ecdh.public_bytes(), + eddsa: secret.x25519.eddsa.public_bytes(), + }, + p384: None, + }; + loop { + let mut legacy_address_derivation_hash = SHA512::new(); + legacy_address_derivation_hash.update(&public.x25519.ecdh); + legacy_address_derivation_hash.update(&public.x25519.eddsa); + let mut legacy_address_derivation_hash = legacy_address_derivation_hash.finish(); + legacy_address_derivation_work_function(&mut legacy_address_derivation_hash); + if legacy_address_derivation_hash[0] < Self::V0_IDENTITY_POW_THRESHOLD && legacy_address_derivation_hash[59] != Address::RESERVED_PREFIX { + public.address.as_bytes_raw_mut()[..5].copy_from_slice(&legacy_address_derivation_hash[59..64]); + break; + } else { + // Regenerate one of the two keys until we meet the legacy address work function criteria. + secret.x25519.ecdh = X25519KeyPair::generate(); + public.x25519.ecdh = secret.x25519.ecdh.public_bytes(); + } + } + + // Generate NIST P-384 key pairs unless this is disabled. + if !x25519_only { + secret.p384 = Some(P384Secret { + ecdh: P384KeyPair::generate(), + ecdsa: P384KeyPair::generate(), + }); + public.p384 = secret.p384.as_ref().map(|p384s| P384 { + ecdh: p384s.ecdh.public_key().clone(), + ecdsa: p384s.ecdsa.public_key().clone(), + ed25519_self_signature: [0u8; ED25519_SIGNATURE_SIZE], + p384_self_signature: [0u8; P384_ECDSA_SIGNATURE_SIZE], + }); + } + + // Bits 40-384 of the address are filled from a SHA384 hash of all keys for a full length V2 address. + public.populate_extended_address_bits(); + + // For V2 identities we include two self signatures to ensure that all these different key pairs + // are properly bound together and can't be changed independently. + if !x25519_only { + let mut for_self_signing = + [0u8; Address::V2_ADDRESS_SIZE + 1 + C25519_PUBLIC_KEY_SIZE + ED25519_PUBLIC_KEY_SIZE + P384_PUBLIC_KEY_SIZE + P384_PUBLIC_KEY_SIZE]; + public.encode_for_self_signing(&mut for_self_signing); + let p384 = public.p384.as_mut().unwrap(); + p384.ed25519_self_signature = secret.x25519.eddsa.sign(&for_self_signing); + p384.p384_self_signature = secret.p384.as_ref().unwrap().ecdsa.sign(&for_self_signing); + } + + (public, secret) + } + + /// Locally validate this identity. + /// This checks address derivation, any self-signatures, etc. + pub fn validate(&self) -> Option> { + todo!() + } + + /// Verify a signature with this identity. + pub fn verify(&self, data: &[u8], signature: &[u8]) -> bool { + if let Some(p384) = self.p384.as_ref() { + p384.ecdsa.verify(data, signature) + } else { + ed25519_verify(&self.x25519.eddsa, signature, data) + } + } + + /// Hash all keys for generation of bits 40-384 of the address. + fn populate_extended_address_bits(&mut self) { + let mut sha = SHA384::new(); + sha.update(self.address.as_bytes_v1()); // including the short address means we can elide the expensive legacy hash in the future + sha.update(&[Self::ALGORITHM_X25519 + | if self.p384.is_some() { + Self::ALGORITHM_P384 + } else { + 0 + }]); + sha.update(&self.x25519.ecdh); + sha.update(&self.x25519.eddsa); + if let Some(p384) = self.p384.as_ref() { + sha.update(p384.ecdh.as_bytes()); + sha.update(p384.ecdsa.as_bytes()); + } + let sha = sha.finish(); + self.address.as_bytes_raw_mut()[5..].copy_from_slice(&sha[..48 - 5]); + } + + /// Encode for self-signing, used only with p384 keys enabled and panics otherwise. + fn encode_for_self_signing( + &self, + buf: &mut [u8; Address::V2_ADDRESS_SIZE + 1 + C25519_PUBLIC_KEY_SIZE + ED25519_PUBLIC_KEY_SIZE + P384_PUBLIC_KEY_SIZE + P384_PUBLIC_KEY_SIZE], + ) { + let mut buf = &mut buf[Address::V2_ADDRESS_SIZE + 1..]; + let _ = buf.write_all(self.address.as_bytes_raw()); + let _ = buf.write_all(&[Self::ALGORITHM_X25519 | Self::ALGORITHM_P384]); + let _ = buf.write_all(&self.x25519.ecdh); + let _ = buf.write_all(&self.x25519.eddsa); + let p384 = self.p384.as_ref().unwrap(); + let _ = buf.write_all(p384.ecdh.as_bytes()); + let _ = buf.write_all(p384.ecdsa.as_bytes()); + } + + pub fn from_bytes(b: &[u8]) -> Result { + if b.len() == packed::V2_PUBLIC_SIZE && b[Address::V1_ADDRESS_SIZE] == (Self::ALGORITHM_X25519 | Self::ALGORITHM_P384) { + let p: &packed::V2Public = unsafe { &*b.as_ptr().cast() }; + let mut id = Self { + address: Address::from_bytes_v1(&p.short_address).ok_or(InvalidFormatError)?, + x25519: X25519 { ecdh: p.c25519, eddsa: p.ed25519 }, + p384: Some(P384 { + ecdh: P384PublicKey::from_bytes(&p.p384_ecdh).ok_or(InvalidFormatError)?, + ecdsa: P384PublicKey::from_bytes(&p.p384_ecdsa).ok_or(InvalidFormatError)?, + ed25519_self_signature: p.ed25519_self_signature, + p384_self_signature: p.p384_self_signature, + }), + }; + let hash = SHA384::hash(&b[..packed::V2_PUBLIC_SIZE]); + id.address.as_bytes_raw_mut()[Address::V1_ADDRESS_SIZE..].copy_from_slice(&hash[..SHA384_HASH_SIZE - Address::V1_ADDRESS_SIZE]); + id.populate_extended_address_bits(); + return Ok(id); + } else if b.len() == packed::V1_PUBLIC_SIZE && b[Address::V1_ADDRESS_SIZE] == Self::ALGORITHM_X25519 { + let p: &packed::V1Public = unsafe { &*b.as_ptr().cast() }; + let mut id = Self { + address: Address::from_bytes_v1(&p.short_address).ok_or(InvalidFormatError)?, + x25519: X25519 { ecdh: p.c25519, eddsa: p.ed25519 }, + p384: None, + }; + let hash = SHA384::hash(&b[..packed::V1_PUBLIC_SIZE]); + id.address.as_bytes_raw_mut()[Address::V1_ADDRESS_SIZE..].copy_from_slice(&hash[..SHA384_HASH_SIZE - Address::V1_ADDRESS_SIZE]); + id.populate_extended_address_bits(); + return Ok(id); + } + return Err(InvalidFormatError); + } + + pub fn write_bytes(&self, w: &mut W, x25519_only: bool) -> Result<(), std::io::Error> { + if let (false, Some(p384)) = (x25519_only, self.p384.as_ref()) { + w.write_all(memory::as_byte_array::(&packed::V2Public { + short_address: *self.address.as_bytes_v1(), + algorithms: Self::ALGORITHM_X25519 | Self::ALGORITHM_P384, + c25519: self.x25519.ecdh, + ed25519: self.x25519.eddsa, + p384_ecdh: *p384.ecdh.as_bytes(), + p384_ecdsa: *p384.ecdsa.as_bytes(), + ed25519_self_signature: p384.ed25519_self_signature, + p384_self_signature: p384.p384_self_signature, + })) + } else { + w.write_all(memory::as_byte_array::(&packed::V1Public { + short_address: *self.address.as_bytes_v1(), + algorithms: Self::ALGORITHM_X25519, + c25519: self.x25519.ecdh, + ed25519: self.x25519.eddsa, + secret_bytes: 0, + })) + } + } } -/// Secret keys associated with an identity. -#[derive(Clone)] -pub struct IdentitySecret { - pub x25519: X25519KeyPair, - pub ed25519: Ed25519KeyPair, - pub p384: Option, +impl ToString for Identity { + fn to_string(&self) -> String { + if let Some(p384) = self.p384.as_ref() { + format!( + "{}:1:{}:{}:{}:{}:{}:{}", + self.address.to_string(), + hex::to_string(&self.x25519.ecdh), + hex::to_string(&self.x25519.eddsa), + hex::to_string(p384.ecdh.as_bytes()), + hex::to_string(p384.ecdsa.as_bytes()), + hex::to_string(&p384.ed25519_self_signature), + hex::to_string(&p384.p384_self_signature) + ) + } else { + format!( + "{}:0:{}:{}", + self.address.to_short_string(), + hex::to_string(&self.x25519.ecdh), + hex::to_string(&self.x25519.eddsa) + ) + } + } } -/// A unique identity on the global VL1 network. -/// -/// Identity implements serde Serialize and Deserialize. Identities are serialized as strings -/// for human-readable formats and binary otherwise. -/// -/// SECURITY NOTE: for security reasons secret keys are NOT exported by default by to_string() -/// or the default marshal() in Marshalable. You must use to_string_with_options() and -/// marshal_with_options() to get secrets. The clone() method on the other hand does duplicate -/// secrets so as not to violate the contract of creating an exact duplicate of the object. -/// There is a clone_without_secrets() if this isn't wanted. -#[derive(Clone)] -pub struct Identity { - pub address: Address, - pub x25519: [u8; C25519_PUBLIC_KEY_SIZE], - pub ed25519: [u8; ED25519_PUBLIC_KEY_SIZE], - pub p384: Option, - pub secret: Option, - pub fingerprint: [u8; IDENTITY_FINGERPRINT_SIZE], +impl FromStr for Identity { + type Err = InvalidFormatError; + + fn from_str(s: &str) -> Result { + let ss: Vec<&str> = s.split(':').collect(); + if ss.len() >= 2 { + if ss[1] == "1" && ss.len() == 8 { + todo!() + } else if ss[1] == "0" && ss.len() == 4 { + todo!() + } + } + return Err(InvalidFormatError); + } } -#[inline(always)] -fn concat_arrays_2(a: &[u8; A], b: &[u8; B]) -> [u8; S] { - assert_eq!(A + B, S); - let mut tmp = [0_u8; S]; - tmp[..A].copy_from_slice(a); - tmp[A..].copy_from_slice(b); - tmp +impl Ord for Identity { + #[inline(always)] + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.address.cmp(&other.address) + } } -#[inline(always)] -fn concat_arrays_4( - a: &[u8; A], - b: &[u8; B], - c: &[u8; C], - d: &[u8; D], -) -> [u8; S] { - assert_eq!(A + B + C + D, S); - let mut tmp = [0_u8; S]; - tmp[..A].copy_from_slice(a); - tmp[A..(A + B)].copy_from_slice(b); - tmp[(A + B)..(A + B + C)].copy_from_slice(c); - tmp[(A + B + C)..].copy_from_slice(d); - tmp +impl PartialOrd for Identity { + #[inline(always)] + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.address.cmp(&other.address)) + } } -fn zt_address_derivation_work_function(digest: &mut [u8; 64]) { +impl Marshalable for Identity { + const MAX_MARSHAL_SIZE: usize = packed::V2_PUBLIC_SIZE; + + fn marshal(&self, buf: &mut Buffer) -> Result<(), OutOfBoundsError> { + self.write_bytes(buf, false).map_err(|_| OutOfBoundsError) + } + + fn unmarshal(buf: &Buffer, cursor: &mut usize) -> Result { + const V1_ALG: u8 = Identity::ALGORITHM_X25519; + const V2_ALG: u8 = Identity::ALGORITHM_X25519 | Identity::ALGORITHM_P384; + match buf.u8_at(*cursor + Address::V1_ADDRESS_SIZE)? { + V1_ALG => Identity::from_bytes(buf.read_bytes_fixed::<{ packed::V1_PUBLIC_SIZE }>(cursor)?).map_err(|_| UnmarshalError::InvalidData), + V2_ALG => Identity::from_bytes(buf.read_bytes_fixed::<{ packed::V2_PUBLIC_SIZE }>(cursor)?).map_err(|_| UnmarshalError::InvalidData), + _ => Err(UnmarshalError::UnsupportedVersion), + } + } +} + +impl IdentitySecret { + pub fn sign(&self, data: &[u8]) -> ArrayVec { + let mut s = ArrayVec::new(); + if let Some(p384) = self.p384.as_ref() { + s.push_slice(&p384.sign(data)); + } else { + s.push_slice(&self.x25519.sign(data)); + } + s + } +} + +impl X25519Secret { + #[inline] + pub fn agree(&self, public: &Identity) -> Option> { + Some(Secret(SHA512::hash(self.ecdh.agree(&public.x25519.ecdh).as_bytes()))) + } + + /// Sign with Ed25519 using the legacy signature format used by ZeroTier V1. + /// This just means the last 32 bytes of a 96-byte signature are the first 32 bytes of the + /// SHA512 hash. This isn't used even in V1 but was once used long ago to rapidly check + /// signatures as part of a different design. Some nodes still expect it to be there though. + #[inline(always)] + pub fn sign(&self, data: &[u8]) -> [u8; 96] { + self.eddsa.sign_zt(data) + } +} + +impl P384Secret { + #[inline(always)] + pub fn sign(&self, data: &[u8]) -> [u8; P384_ECDSA_SIGNATURE_SIZE] { + self.ecdsa.sign(data) + } +} + +impl Eq for P384 {} + +impl PartialEq for P384 { + #[inline] + fn eq(&self, other: &Self) -> bool { + self.ecdh.as_bytes() == other.ecdh.as_bytes() + && self.ecdsa.as_bytes() == other.ecdsa.as_bytes() + && self.ed25519_self_signature == other.ed25519_self_signature + && self.p384_self_signature == other.p384_self_signature + } +} + +impl Serialize for Identity { + #[inline] + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + if serializer.is_human_readable() { + serializer.serialize_str(self.to_string().as_str()) + } else { + let mut tmp = Vec::with_capacity(Identity::MAX_MARSHAL_SIZE); + let _ = self.write_bytes(&mut tmp, false); + serializer.serialize_bytes(tmp.as_slice()) + } + } +} + +struct IdentityDeserializeVisitor; + +impl<'de> serde::de::Visitor<'de> for IdentityDeserializeVisitor { + type Value = Identity; + + #[inline] + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("an Identity") + } + + #[inline] + fn visit_bytes(self, v: &[u8]) -> Result + where + E: serde::de::Error, + { + Identity::from_bytes(v).map_err(|_| E::custom("invalid identity")) + } + + #[inline] + fn visit_str(self, v: &str) -> Result + where + E: serde::de::Error, + { + Identity::from_str(v).map_err(|_| E::custom("invalid identity")) + } +} + +impl<'de> Deserialize<'de> for Identity { + #[inline] + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + if deserializer.is_human_readable() { + deserializer.deserialize_str(IdentityDeserializeVisitor) + } else { + deserializer.deserialize_bytes(IdentityDeserializeVisitor) + } + } +} + +mod packed { + use super::*; + + pub(super) const V1_PUBLIC_SIZE: usize = 1 + C25519_PUBLIC_KEY_SIZE + ED25519_PUBLIC_KEY_SIZE + 1; + pub(super) const V2_PUBLIC_SIZE: usize = 1 + + C25519_PUBLIC_KEY_SIZE + + ED25519_PUBLIC_KEY_SIZE + + ED25519_SIGNATURE_SIZE + + P384_PUBLIC_KEY_SIZE + + P384_PUBLIC_KEY_SIZE + + P384_ECDSA_SIGNATURE_SIZE; + + #[derive(Clone, Copy)] + #[repr(C, packed)] + pub(super) struct V1Public { + pub short_address: [u8; 5], + pub algorithms: u8, + pub c25519: [u8; C25519_PUBLIC_KEY_SIZE], + pub ed25519: [u8; ED25519_PUBLIC_KEY_SIZE], + pub secret_bytes: u8, + } + + #[derive(Clone, Copy)] + #[repr(C, packed)] + pub(super) struct V2Public { + pub short_address: [u8; 5], + pub algorithms: u8, + pub c25519: [u8; C25519_PUBLIC_KEY_SIZE], + pub ed25519: [u8; ED25519_PUBLIC_KEY_SIZE], + pub p384_ecdh: [u8; P384_PUBLIC_KEY_SIZE], + pub p384_ecdsa: [u8; P384_PUBLIC_KEY_SIZE], + pub ed25519_self_signature: [u8; ED25519_SIGNATURE_SIZE], + pub p384_self_signature: [u8; P384_ECDSA_SIGNATURE_SIZE], + } +} + +fn legacy_address_derivation_work_function(digest: &mut [u8; 64]) { const ADDRESS_DERIVATION_HASH_MEMORY_SIZE: usize = 2097152; unsafe { let genmem_layout = std::alloc::Layout::from_size_align(ADDRESS_DERIVATION_HASH_MEMORY_SIZE, 16).unwrap(); // aligned for access as u64 or u8 @@ -144,827 +491,3 @@ fn zt_address_derivation_work_function(digest: &mut [u8; 64]) { std::alloc::dealloc(genmem, genmem_layout); } } - -impl Identity { - pub const BYTE_LENGTH_MAX: usize = ADDRESS_SIZE - + 1 - + C25519_PUBLIC_KEY_SIZE - + ED25519_PUBLIC_KEY_SIZE - + C25519_SECRET_KEY_SIZE - + ED25519_SECRET_KEY_SIZE - + P384_PUBLIC_KEY_SIZE - + P384_SECRET_KEY_SIZE - + P384_PUBLIC_KEY_SIZE - + P384_SECRET_KEY_SIZE - + P384_ECDSA_SIGNATURE_SIZE - + P384_ECDSA_SIGNATURE_SIZE; - - pub const FINGERPRINT_SIZE: usize = IDENTITY_FINGERPRINT_SIZE; - pub const MAX_SIGNATURE_SIZE: usize = IDENTITY_MAX_SIGNATURE_SIZE; - - const ALGORITHM_X25519: u8 = 0x01; - const ALGORITHM_EC_NIST_P384: u8 = 0x02; - const FLAG_INCLUDES_SECRETS: u8 = 0x80; - - /// Generate a new identity. - pub fn generate() -> Valid { - // First generate an identity with just x25519 keys and derive its address. - let mut sha = SHA512::new(); - let ed25519 = Ed25519KeyPair::generate(); - let ed25519_pub = ed25519.public_bytes(); - let address; - let mut x25519; - let mut x25519_pub; - loop { - x25519 = X25519KeyPair::generate(); - x25519_pub = x25519.public_bytes(); - - sha.update(&x25519_pub); - sha.update(&ed25519_pub); - let mut digest = sha.finish(); - zt_address_derivation_work_function(&mut digest); - - if digest[0] < IDENTITY_POW_THRESHOLD { - let addr = Address::from_bytes(&digest[59..64]); - if addr.is_some() { - address = addr.unwrap(); - break; - } - } - - sha.reset(); - } - let mut id = Self { - address, - x25519: x25519_pub, - ed25519: ed25519_pub, - p384: None, - secret: Some(IdentitySecret { x25519, ed25519, p384: None }), - fingerprint: [0_u8; IDENTITY_FINGERPRINT_SIZE], // replaced in upgrade() - }; - - // Then "upgrade" to add NIST P-384 keys and compute fingerprint. - assert!(id.upgrade().is_ok()); - assert!(id.p384.is_some() && id.secret.as_ref().unwrap().p384.is_some()); - - Valid::mark_valid(id) - } - - /// Upgrade older x25519-only identities to hybrid identities with both x25519 and NIST P-384 curves. - /// - /// The boolean indicates whether or not an upgrade occurred. An error occurs if this identity is - /// invalid or missing its private key(s). This does nothing if no upgrades are possible. - /// - /// NOTE: upgrading is not deterministic. This generates a new set of NIST P-384 keys and the new - /// identity contains these and a signature by the original keys and by the new keys to bind them - /// together. However repeated calls to upgrade() will generate different secondary keys. This should - /// only be used once to upgrade and then save a new identity. - /// - /// It would be possible to change this in the future, with care. - pub fn upgrade(&mut self) -> Result { - if self.secret.is_none() { - return Err(InvalidParameterError("an identity can only be upgraded if it includes its private key")); - } - if self.p384.is_none() { - let p384_ecdh = P384KeyPair::generate(); - let p384_ecdsa = P384KeyPair::generate(); - - let mut self_sign_buf: Vec = Vec::with_capacity( - ADDRESS_SIZE - + C25519_PUBLIC_KEY_SIZE - + ED25519_PUBLIC_KEY_SIZE - + P384_PUBLIC_KEY_SIZE - + P384_PUBLIC_KEY_SIZE - + P384_ECDSA_SIGNATURE_SIZE - + 4, - ); - let _ = self_sign_buf.write_all(&self.address.to_bytes()); - let _ = self_sign_buf.write_all(&self.x25519); - let _ = self_sign_buf.write_all(&self.ed25519); - self_sign_buf.push(Self::ALGORITHM_EC_NIST_P384); - let _ = self_sign_buf.write_all(p384_ecdh.public_key_bytes()); - let _ = self_sign_buf.write_all(p384_ecdsa.public_key_bytes()); - - // Sign all keys including the x25519 ones with the new P-384 keys. - let ecdsa_self_signature = p384_ecdsa.sign(self_sign_buf.as_slice()); - - // Sign everything with the original ed25519 key to bind the new key pairs. Include ECDSA - // signature because ECDSA signatures are randomized and we want only this specific one. - // Identities should be rigid. (Ed25519 signatures are deterministic.) - let _ = self_sign_buf.write_all(&ecdsa_self_signature); - let ed25519_self_signature = self.secret.as_ref().unwrap().ed25519.sign(self_sign_buf.as_slice()); - - let _ = self.p384.insert(IdentityP384Public { - ecdh: p384_ecdh.public_key().clone(), - ecdsa: p384_ecdsa.public_key().clone(), - ecdsa_self_signature, - ed25519_self_signature, - }); - let _ = self - .secret - .as_mut() - .unwrap() - .p384 - .insert(IdentityP384Secret { ecdh: p384_ecdh, ecdsa: p384_ecdsa }); - - self.fill_in_fingerprint(); - - return Ok(true); - } - return Ok(false); - } - - /// Create a clone minus any secret key it holds. - pub fn clone_without_secret(&self) -> Identity { - Self { - address: self.address, - x25519: self.x25519, - ed25519: self.ed25519, - p384: self.p384.clone(), - secret: None, - fingerprint: self.fingerprint, - } - } - - /// Locally check the validity of this identity. - /// - /// This is somewhat time consuming due to the memory-intensive work algorithm. - pub fn validate(self) -> Option> { - if let Some(p384) = self.p384.as_ref() { - let mut self_sign_buf: Vec = - Vec::with_capacity(ADDRESS_SIZE + 4 + C25519_PUBLIC_KEY_SIZE + ED25519_PUBLIC_KEY_SIZE + P384_PUBLIC_KEY_SIZE + P384_PUBLIC_KEY_SIZE); - let _ = self_sign_buf.write_all(&self.address.to_bytes()); - let _ = self_sign_buf.write_all(&self.x25519); - let _ = self_sign_buf.write_all(&self.ed25519); - self_sign_buf.push(Self::ALGORITHM_EC_NIST_P384); - let _ = self_sign_buf.write_all(p384.ecdh.as_bytes()); - let _ = self_sign_buf.write_all(p384.ecdsa.as_bytes()); - - if !p384.ecdsa.verify(self_sign_buf.as_slice(), &p384.ecdsa_self_signature) { - return None; - } - - let _ = self_sign_buf.write_all(&p384.ecdsa_self_signature); - if !ed25519_verify(&self.ed25519, &p384.ed25519_self_signature, self_sign_buf.as_slice()) { - return None; - } - } - - // NOTE: fingerprint is always computed on generation or deserialize so no need to check. - - let mut sha = SHA512::new(); - sha.update(&self.x25519); - sha.update(&self.ed25519); - let mut digest = sha.finish(); - zt_address_derivation_work_function(&mut digest); - - return if digest[0] < IDENTITY_POW_THRESHOLD && Address::from_bytes(&digest[59..64]).map_or(false, |a| a == self.address) { - Some(Valid::mark_valid(self)) - } else { - None - }; - } - - /// Returns true if this identity was upgraded from another older version. - /// - /// This does NOT validate either identity. Ensure that validation has been performed. - pub fn is_upgraded_from(&self, other: &Identity) -> bool { - self.address == other.address && self.x25519 == other.x25519 && self.ed25519 == other.ed25519 && self.p384.is_some() && other.p384.is_none() - } - - /// Perform ECDH key agreement, returning a shared secret or None on error. - /// - /// An error can occur if this identity does not hold its secret portion or if either key is invalid. - /// - /// For new identities with P-384 keys a hybrid agreement is performed using both X25519 and NIST P-384 ECDH. - /// The final key is derived as HMAC(x25519 secret, p-384 secret) to yield a FIPS-compliant key agreement with - /// the X25519 secret being used as a "salt" as far as FIPS is concerned. - pub fn agree(&self, other: &Valid) -> Option> { - if let Some(secret) = self.secret.as_ref() { - let c25519_secret: Secret<64> = Secret(SHA512::hash(&secret.x25519.agree(&other.x25519).0)); - - // FIPS note: FIPS-compliant exchange algorithms must be the last algorithms in any HKDF chain - // for the final result to be technically FIPS compliant. Non-FIPS algorithm secrets are considered - // a salt in the HMAC(salt, key) HKDF construction. - if secret.p384.is_some() && other.p384.is_some() { - secret - .p384 - .as_ref() - .unwrap() - .ecdh - .agree(&other.p384.as_ref().unwrap().ecdh) - .map(|p384_secret| Secret(hmac_sha512(&c25519_secret.0, &p384_secret.0))) - } else { - Some(c25519_secret) - } - } else { - None - } - } - - /// Sign a message with this identity. - /// - /// Identities with P-384 keys sign with that unless legacy_ed25519_only is selected. If this is - /// set the old 96-byte signature plus hash format used in ZeroTier v1 is used. - /// - /// A return of None happens if we don't have our secret key(s) or some other error occurs. - pub fn sign(&self, msg: &[u8], legacy_ed25519_only: bool) -> Option> { - if let Some(secret) = self.secret.as_ref() { - if legacy_ed25519_only { - Some(secret.ed25519.sign_zt(msg).into()) - } else if let Some(p384s) = secret.p384.as_ref() { - let mut tmp = ArrayVec::new(); - tmp.push(Self::ALGORITHM_EC_NIST_P384); - let _ = tmp.write_all(&p384s.ecdsa.sign(msg)); - Some(tmp) - } else { - let mut tmp = ArrayVec::new(); - tmp.push(Self::ALGORITHM_X25519); - let _ = tmp.write_all(&secret.ed25519.sign(msg)); - Some(tmp) - } - } else { - None - } - } - - /// Verify a signature against this identity. - pub fn verify(&self, msg: &[u8], signature: &[u8]) -> bool { - if signature.len() == 96 { - // LEGACY: ed25519-only signature with hash included, detected by having a unique size of 96 bytes - return ed25519_verify(&self.ed25519, &signature[..64], msg); - } else if let Some(algorithm) = signature.get(0) { - if *algorithm == Self::ALGORITHM_EC_NIST_P384 && signature.len() == (1 + P384_ECDSA_SIGNATURE_SIZE) { - if let Some(p384) = self.p384.as_ref() { - return p384.ecdsa.verify(msg, &signature[1..]); - } - } else if *algorithm == Self::ALGORITHM_X25519 && signature.len() == (1 + ED25519_SIGNATURE_SIZE) { - return ed25519_verify(&self.ed25519, &signature[1..], msg); - } - } - return false; - } - - pub fn write_public(&self, w: &mut W, legacy_v0: bool) -> std::io::Result<()> { - w.write_all(&self.address.to_bytes())?; - if !legacy_v0 && self.p384.is_some() { - let p384 = self.p384.as_ref().unwrap(); - w.write_all(&[Self::ALGORITHM_X25519 | Self::ALGORITHM_EC_NIST_P384])?; - w.write_all(&self.x25519)?; - w.write_all(&self.ed25519)?; - w.write_all(p384.ecdh.as_bytes())?; - w.write_all(p384.ecdsa.as_bytes())?; - w.write_all(&p384.ecdsa_self_signature)?; - w.write_all(&p384.ed25519_self_signature)?; - } else { - w.write_all(&[0])?; - w.write_all(&self.x25519)?; - w.write_all(&self.ed25519)?; - w.write_all(&[0])?; - } - Ok(()) - } - - pub fn write_secret(&self, w: &mut W, legacy_v0: bool) -> std::io::Result<()> { - if let Some(s) = self.secret.as_ref() { - w.write_all(&self.address.to_bytes())?; - if !legacy_v0 && self.p384.is_some() && s.p384.is_some() { - let p384 = self.p384.as_ref().unwrap(); - let p384s = s.p384.as_ref().unwrap(); - w.write_all(&[Self::ALGORITHM_X25519 | Self::ALGORITHM_EC_NIST_P384 | Self::FLAG_INCLUDES_SECRETS])?; - w.write_all(&self.x25519)?; - w.write_all(&self.ed25519)?; - w.write_all(s.x25519.secret_bytes().as_bytes())?; - w.write_all(s.ed25519.secret_bytes().as_bytes())?; - w.write_all(p384.ecdh.as_bytes())?; - w.write_all(p384.ecdsa.as_bytes())?; - w.write_all(p384s.ecdh.secret_key_bytes().as_bytes())?; - w.write_all(p384s.ecdsa.secret_key_bytes().as_bytes())?; - w.write_all(&p384.ecdsa_self_signature)?; - w.write_all(&p384.ed25519_self_signature)?; - } else { - w.write_all(&[0])?; - w.write_all(&self.x25519)?; - w.write_all(&self.ed25519)?; - w.write_all(&[(C25519_SECRET_KEY_SIZE + ED25519_SECRET_KEY_SIZE) as u8])?; - w.write_all(s.x25519.secret_bytes().as_bytes())?; - w.write_all(s.ed25519.secret_bytes().as_bytes())?; - } - return Ok(()); - } else { - return Err(std::io::Error::new(std::io::ErrorKind::Other, "no secret")); - } - } - - pub fn to_public_bytes(&self) -> std::io::Result> { - let mut buf = Buffer::<{ Self::BYTE_LENGTH_MAX }>::new(); - self.write_public(&mut buf, false)?; - Ok(buf) - } - - pub fn to_secret_bytes(&self) -> std::io::Result> { - let mut buf = Buffer::<{ Self::BYTE_LENGTH_MAX }>::new(); - self.write_secret(&mut buf, false)?; - Ok(buf) - } - - fn to_string_internal(&self, include_private: bool) -> String { - let mut s = String::with_capacity(1024); - s.push_str(self.address.to_string().as_str()); - - s.push_str(":0:"); // 0 used for x25519 for legacy reasons just like in marshal() - s.push_str(hex::to_string(&self.x25519).as_str()); - s.push_str(hex::to_string(&self.ed25519).as_str()); - if self.secret.is_some() && include_private { - let secret = self.secret.as_ref().unwrap(); - s.push(':'); - s.push_str(hex::to_string(secret.x25519.secret_bytes().as_bytes()).as_str()); - s.push_str(hex::to_string(secret.ed25519.secret_bytes().as_bytes()).as_str()); - } - - if let Some(p384) = self.p384.as_ref() { - if self.secret.is_none() || !include_private { - s.push(':'); - } - s.push_str(":2:"); // 2 == IDENTITY_ALGORITHM_EC_NIST_P384 - let p384_joined: [u8; P384_PUBLIC_KEY_SIZE + P384_PUBLIC_KEY_SIZE + P384_ECDSA_SIGNATURE_SIZE + ED25519_SIGNATURE_SIZE] = concat_arrays_4( - p384.ecdh.as_bytes(), - p384.ecdsa.as_bytes(), - &p384.ecdsa_self_signature, - &p384.ed25519_self_signature, - ); - s.push_str(base64::encode_url_nopad(&p384_joined).as_str()); - if self.secret.is_some() && include_private { - let secret = self.secret.as_ref().unwrap(); - if secret.p384.is_some() { - let p384_secret = secret.p384.as_ref().unwrap(); - let p384_secret_joined: [u8; P384_SECRET_KEY_SIZE + P384_SECRET_KEY_SIZE] = concat_arrays_2( - p384_secret.ecdh.secret_key_bytes().as_bytes(), - p384_secret.ecdsa.secret_key_bytes().as_bytes(), - ); - s.push(':'); - s.push_str(base64::encode_url_nopad(&p384_secret_joined).as_str()); - } - } - } - - s - } - - fn fill_in_fingerprint(&mut self) { - let mut h = SHA384::new(); - assert!(self.write_public(&mut h, false).is_ok()); - self.fingerprint = h.finish(); - - // NIST guidelines specify that the left-most N bits of a hash should be taken if it's truncated. - // We want to start the fingerprint with the address, so move the hash over and discard 40 bits. - // We're not even really losing security here since the address is a hash, but NIST would not - // consider it such since it's not a NIST-approved algorithm. - self.fingerprint.copy_within(ADDRESS_SIZE..48, ADDRESS_SIZE); - self.fingerprint[..ADDRESS_SIZE].copy_from_slice(&self.address.to_bytes()); - } - - #[inline(always)] - pub fn to_public_string(&self) -> String { - self.to_string_internal(false) - } - - #[inline(always)] - pub fn to_secret_string(&self) -> String { - self.to_string_internal(true) - } -} - -impl ToString for Identity { - #[inline(always)] - fn to_string(&self) -> String { - self.to_string_internal(false) - } -} - -impl FromStr for Identity { - type Err = InvalidFormatError; - - fn from_str(s: &str) -> Result { - let fields_v: Vec<&str> = s.split(':').collect(); - let fields = fields_v.as_slice(); - - if fields.len() < 3 || fields[0].len() != ADDRESS_SIZE_STRING { - return Err(InvalidFormatError); - } - let address = Address::from_str(fields[0]).map_err(|_| InvalidFormatError)?; - - // x25519 public, x25519 secret, p384 public, p384 secret - let mut keys: [Option<&str>; 4] = [None, None, None, None]; - - let mut ptr = 1; - let mut state = 0; - let mut key_ptr = 0; - while ptr < fields.len() { - match state { - 0 => { - if fields[ptr] == "0" || fields[ptr] == "1" { - key_ptr = 0; - } else if fields[ptr] == "2" { - key_ptr = 2; - } else { - return Err(InvalidFormatError); - } - state = 1; - } - 1 | 2 => { - let _ = keys[key_ptr].replace(fields[ptr]); - key_ptr += 1; - state = (state + 1) % 3; - } - _ => { - return Err(InvalidFormatError); - } - } - ptr += 1; - } - - let keys = [ - hex::from_string(keys[0].unwrap_or("")), - hex::from_string(keys[1].unwrap_or("")), - base64::decode_url_nopad(keys[2].unwrap_or("")).unwrap_or_else(|| Vec::new()), - base64::decode_url_nopad(keys[3].unwrap_or("")).unwrap_or_else(|| Vec::new()), - ]; - if keys[0].len() != C25519_PUBLIC_KEY_SIZE + ED25519_PUBLIC_KEY_SIZE { - return Err(InvalidFormatError); - } - if !keys[2].is_empty() && keys[2].len() != P384_PUBLIC_KEY_SIZE + P384_PUBLIC_KEY_SIZE + P384_ECDSA_SIGNATURE_SIZE + ED25519_SIGNATURE_SIZE { - return Err(InvalidFormatError); - } - if !keys[3].is_empty() && keys[3].len() != P384_SECRET_KEY_SIZE + P384_SECRET_KEY_SIZE { - return Err(InvalidFormatError); - } - - let mut sha = SHA384::new(); - sha.update(&address.to_bytes()); - sha.update(&keys[0].as_slice()[0..64]); - if !keys[2].is_empty() { - sha.update(&[Self::ALGORITHM_EC_NIST_P384]); - sha.update(&keys[2].as_slice()[0..(P384_PUBLIC_KEY_SIZE * 2)]); - } - - let mut id = Ok(Identity { - address, - x25519: keys[0].as_slice()[0..32].try_into().unwrap(), - ed25519: keys[0].as_slice()[32..64].try_into().unwrap(), - p384: if keys[2].is_empty() { - None - } else { - let ecdh = P384PublicKey::from_bytes(&keys[2].as_slice()[..P384_PUBLIC_KEY_SIZE]); - let ecdsa = P384PublicKey::from_bytes(&keys[2].as_slice()[P384_PUBLIC_KEY_SIZE..(P384_PUBLIC_KEY_SIZE * 2)]); - if ecdh.is_none() || ecdsa.is_none() { - return Err(InvalidFormatError); - } - Some(IdentityP384Public { - ecdh: ecdh.unwrap(), - ecdsa: ecdsa.unwrap(), - ecdsa_self_signature: keys[2].as_slice()[(P384_PUBLIC_KEY_SIZE * 2)..((P384_PUBLIC_KEY_SIZE * 2) + P384_ECDSA_SIGNATURE_SIZE)] - .try_into() - .unwrap(), - ed25519_self_signature: keys[2].as_slice()[((P384_PUBLIC_KEY_SIZE * 2) + P384_ECDSA_SIGNATURE_SIZE)..] - .try_into() - .unwrap(), - }) - }, - secret: if keys[1].is_empty() { - None - } else { - if keys[1].len() != C25519_SECRET_KEY_SIZE + ED25519_SECRET_KEY_SIZE { - return Err(InvalidFormatError); - } - Some(IdentitySecret { - x25519: { - let tmp = X25519KeyPair::from_bytes(&keys[0].as_slice()[0..32], &keys[1].as_slice()[0..32]); - if tmp.is_none() { - return Err(InvalidFormatError); - } - tmp.unwrap() - }, - ed25519: { - let tmp = Ed25519KeyPair::from_bytes(&keys[0].as_slice()[32..64], &keys[1].as_slice()[32..64]); - if tmp.is_none() { - return Err(InvalidFormatError); - } - tmp.unwrap() - }, - p384: if keys[3].is_empty() { - None - } else { - Some(IdentityP384Secret { - ecdh: { - let tmp = - P384KeyPair::from_bytes(&keys[2].as_slice()[..P384_PUBLIC_KEY_SIZE], &keys[3].as_slice()[..P384_SECRET_KEY_SIZE]); - if tmp.is_none() { - return Err(InvalidFormatError); - } - tmp.unwrap() - }, - ecdsa: { - let tmp = P384KeyPair::from_bytes( - &keys[2].as_slice()[P384_PUBLIC_KEY_SIZE..(P384_PUBLIC_KEY_SIZE * 2)], - &keys[3].as_slice()[P384_SECRET_KEY_SIZE..], - ); - if tmp.is_none() { - return Err(InvalidFormatError); - } - tmp.unwrap() - }, - }) - }, - }) - }, - fingerprint: [0; 48], - }); - id.as_mut().unwrap().fill_in_fingerprint(); - id - } -} - -impl Marshalable for Identity { - const MAX_MARSHAL_SIZE: usize = Self::BYTE_LENGTH_MAX; - - #[inline(always)] - fn marshal(&self, buf: &mut Buffer) -> Result<(), UnmarshalError> { - self.write_public(buf, false).map_err(|e| e.into()) - } - - fn unmarshal(buf: &Buffer, cursor: &mut usize) -> Result { - let address = Address::from_bytes_fixed(buf.read_bytes_fixed(cursor)?).ok_or(UnmarshalError::InvalidData)?; - let type_flags = buf.read_u8(cursor)?; - let x25519 = buf.read_bytes_fixed::(cursor)?; - let ed25519 = buf.read_bytes_fixed::(cursor)?; - - let (mut ecdh, mut ecdsa, mut ecdsa_self_signature, mut ed25519_self_signature, mut x25519_s, mut ed25519_s, mut ecdh_s, mut ecdsa_s) = - (None, None, None, None, None, None, None, None); - - if type_flags == 0 { - const C25519_SECRETS_SIZE: u8 = (C25519_SECRET_KEY_SIZE + ED25519_SECRET_KEY_SIZE) as u8; - match buf.read_u8(cursor)? { - 0 => { - x25519_s = None; - ed25519_s = None; - } - C25519_SECRETS_SIZE => { - x25519_s = Some(buf.read_bytes_fixed::(cursor)?); - ed25519_s = Some(buf.read_bytes_fixed::(cursor)?); - } - _ => return Err(UnmarshalError::InvalidData), - } - } else { - if (type_flags & (Self::ALGORITHM_X25519 | Self::FLAG_INCLUDES_SECRETS)) == (Self::ALGORITHM_X25519 | Self::FLAG_INCLUDES_SECRETS) { - x25519_s = Some(buf.read_bytes_fixed::(cursor)?); - ed25519_s = Some(buf.read_bytes_fixed::(cursor)?); - } - - if (type_flags & Self::ALGORITHM_EC_NIST_P384) != 0 { - ecdh = Some(buf.read_bytes_fixed::(cursor)?); - ecdsa = Some(buf.read_bytes_fixed::(cursor)?); - if (type_flags & Self::FLAG_INCLUDES_SECRETS) != 0 { - ecdh_s = Some(buf.read_bytes_fixed::(cursor)?); - ecdsa_s = Some(buf.read_bytes_fixed::(cursor)?); - } - ecdsa_self_signature = Some(buf.read_bytes_fixed::(cursor)?); - ed25519_self_signature = Some(buf.read_bytes_fixed::(cursor)?); - } - } - - let mut id = Ok(Identity { - address, - x25519: x25519.clone(), - ed25519: ed25519.clone(), - p384: if let Some(ecdh) = ecdh { - Some(IdentityP384Public { - ecdh: P384PublicKey::from_bytes(ecdh).ok_or(UnmarshalError::InvalidData)?, - ecdsa: P384PublicKey::from_bytes(ecdsa.ok_or(UnmarshalError::InvalidData)?).ok_or(UnmarshalError::InvalidData)?, - ecdsa_self_signature: ecdsa_self_signature.ok_or(UnmarshalError::InvalidData)?.clone(), - ed25519_self_signature: ed25519_self_signature.ok_or(UnmarshalError::InvalidData)?.clone(), - }) - } else { - None - }, - secret: if let Some(x25519_s) = x25519_s { - Some(IdentitySecret { - x25519: X25519KeyPair::from_bytes(x25519, x25519_s).ok_or(UnmarshalError::InvalidData)?, - ed25519: Ed25519KeyPair::from_bytes(ed25519, ed25519_s.ok_or(UnmarshalError::InvalidData)?).ok_or(UnmarshalError::InvalidData)?, - p384: if let Some(ecdh_s) = ecdh_s { - Some(IdentityP384Secret { - ecdh: P384KeyPair::from_bytes(ecdh.ok_or(UnmarshalError::InvalidData)?, ecdh_s).ok_or(UnmarshalError::InvalidData)?, - ecdsa: P384KeyPair::from_bytes(ecdsa.ok_or(UnmarshalError::InvalidData)?, ecdsa_s.ok_or(UnmarshalError::InvalidData)?) - .ok_or(UnmarshalError::InvalidData)?, - }) - } else { - None - }, - }) - } else { - None - }, - fingerprint: [0u8; IDENTITY_FINGERPRINT_SIZE], - }); - id.as_mut().unwrap().fill_in_fingerprint(); - id - } -} - -impl PartialEq for Identity { - #[inline(always)] - fn eq(&self, other: &Self) -> bool { - secure_eq(&self.fingerprint, &other.fingerprint) - } -} - -impl Eq for Identity {} - -impl Ord for Identity { - #[inline(always)] - fn cmp(&self, other: &Self) -> Ordering { - self.address.cmp(&other.address).then_with(|| self.fingerprint.cmp(&other.fingerprint)) - } -} - -impl PartialOrd for Identity { - #[inline(always)] - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -impl Hash for Identity { - #[inline(always)] - fn hash(&self, state: &mut H) { - state.write_u64(self.address.into()) - } -} - -impl Debug for Identity { - #[inline] - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(self.to_string().as_str()) - } -} - -impl Serialize for Identity { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - if serializer.is_human_readable() { - serializer.serialize_str(self.to_public_string().as_str()) - } else { - serializer.serialize_bytes(self.to_bytes().as_slice()) - } - } -} - -struct IdentityVisitor; - -impl<'de> serde::de::Visitor<'de> for IdentityVisitor { - type Value = Identity; - - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("a ZeroTier identity") - } - - fn visit_bytes(self, v: &[u8]) -> Result - where - E: serde::de::Error, - { - Identity::from_bytes(v).map_err(|e| serde::de::Error::custom(e.to_string())) - } - - fn visit_str(self, v: &str) -> Result - where - E: serde::de::Error, - { - Identity::from_str(v).map_err(|e| E::custom(e.to_string())) - } -} - -impl<'de> Deserialize<'de> for Identity { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - if deserializer.is_human_readable() { - deserializer.deserialize_str(IdentityVisitor) - } else { - deserializer.deserialize_bytes(IdentityVisitor) - } - } -} - -#[cfg(test)] -mod tests { - use crate::vl1::identity::*; - use std::str::FromStr; - use zerotier_utils::hex; - - #[test] - fn v0_identity() { - let self_agree_expected = - hex::from_string("de904fc90ff3a2b96b739b926e623113f5334c80841b654509b77916c4c4a6eb0ca69ec6ed01a7f04aee17c546b30ba4"); - - // Test self-agree with a known good x25519-only (v0) identity. - let id = Identity::from_str( - "728efdb79d:0:3077ed0084d8d48a3ac628af6b45d9351e823bff34bc4376cddfc77a3d73a966c7d347bdcc1244d0e99e1b9c961ff5e963092e90ca43b47ff58c114d2d699664:2afaefcd1dca336ed59957eb61919b55009850b0b7088af3ee142672b637d1d49cc882b30a006f9eee42f2211ef8fe1cbe99a16a4436737fc158ce2243c15f12", - ) - .unwrap().validate().unwrap(); - let self_agree = id.agree(&id).unwrap(); - debug_assert!(self_agree_expected.as_slice().eq(&self_agree.as_bytes()[..48])); - - // Identity should be upgradable. - let mut upgraded = id.clone(); - debug_assert!(upgraded.upgrade().unwrap()); - - // Upgraded identity should generate the same result when agreeing with the old non-upgraded identity. - let self_agree = id.agree(&upgraded).unwrap(); - debug_assert!(self_agree_expected.as_slice().eq(&self_agree.as_bytes()[..48])); - let self_agree = upgraded.agree(&id).unwrap(); - debug_assert!(self_agree_expected.as_slice().eq(&self_agree.as_bytes()[..48])); - } - - const GOOD_V0_IDENTITIES: [&'static str; 4] = [ - "8ee1095428:0:3ee30bb0cf66098891a5375aa8b44c4e7d09fabfe6d04e150bc7f17898726f1b1b8dc16f7cc74ed4eeb06e224db4370668766829434faf3da26ecfb151c87c12:69031e4b2354d41010f7b097f4793e99040342ca641938525e3f72a081a75285bea3c399edecda738c772f59412469a8290405e3e327fb30f3654af49ff8de09", - "77fcbbd875:0:1724aad9ef6af50ab7a67ed975053779ca1a0251832ef6456cff50bf5af3bb1f859885b67c7ff6a64192e795e7dcdc9ce7b13deb9177022a4a83c02026596993:55c3b96396853f41ba898d7099ca118ba3ba1d306af55248dcbd7008e6752b8900e208a251eeda70f778249dab65a5dfbb4beeaf76de40bf3b732536f93fc7f7", - "91c4e0e1b0:0:5a96fb6bddbc3e845ec30e369b6517dd936e9b9679404001ba81c66dfe38be7a12f5db4f470f4af2ff4aa3e2fe54a3838c80b3a33fe83fe78fef956772c46ed3:7210ce5b7bc4777c7790d225f81e7f2583417a3ac64fd1a5873186ed6bd5b48126c8e1cfd0e82b391a389547bd3c143c672f83e19632aa445cafb2d5aab4c098", - "ba0c4a4edd:0:4b75790dce1979b4cec38ca1eb81e0f348f757047c4ad5e8a463fe54f32142739ffd8c0bc9c95a45572d96173a11def1e653e6975343e4bc78d5b504e023aab8:28fa6bf3c103186c41575c91ee86887d21e0bdf77cdf4c36c9430c32e83affbee0b04da61312f4c990a18f2acf9031a6a2c4c69362f79f7f6d5621a3c8abf33c", - ]; - const GOOD_V1_IDENTITIES: [&'static str; 4] = [ - "75a8a7a199:0:6322fe1ca7941571458bb0bd14faff0af915c2ca5ea5856a682de1040c8cbd1f79054a0c052b253316b17abd9cf34609c6ac0e26dd85ad169c26aa980a69d5e0:b8fcd2a25a0708176d81455a7b576b6835d87cc7b6b4701c914d65688f730f6d1a725018472570440e0ba1d6038be81e3415e8ba6dfa685ae2b582b12b90ad67:2:Az1IZD-cuJS11XWhCMEc6ZPwMpM_nQOwCHqHJaOywRxtkU9UC8BfOdGxYet-7fh3lgMxKntz_iqWD-7PAXoTpg2bDo-hKRT4zLkNm-KnUvqjiXl9W7RLYhvVd_qUXxQlY709m9hQ-omy_zRi3ZIysOMDaPwfyuwWUzhB09FiWn-MjiiUrujqqx66VfZ9rx_u63ZUvrTxWu6motVbS6eXMemIGUtU6UyhIXDIk2_WJceF9k7Bs7C7Ay00zuCTEHIH878e7LR4qPNnPPDRxQV3y5rO7WHsutl9wHSEINJAtYz7xJdk7IuccJfnDzhODroATwdSNtG0sASJU9UToUvWioEF:P5zAldkVK9P5uso9iOQ2hqgXS-J326gK-Z3l1_ZZehes8-0OYxUoHfs80ddG1MVGIOb3AFA0vi7S93wNUxRkg4kzzUl5rFAmy85iuiMCRpPY-8SG1500RI4dhWkRTuPR", - "a311a9d217:0:477c48a712daf785d7d5f2d2d693361047b102dbcd7eea2d6cabcb7149b0806815e4c4d7cd03a9b3ac87c3e27161724a5ad5c52d1c487c5484869e55e34966e5:f06d080800e3529ac2f5229cb1c07c6761fa1f2f1bc2447807f814e38252b24d6faf2da1ab6fb7db2460081dae877b9658714f3a2976f9ffcbe432151f69c3eb:2:A9_zhw9S4Kh-iOfCqEYjKg9Hyd9OabtE3F4FwlX87AEbeGRl0bRTOnqRNLpZPbwtNwJVJwKaY7dFCSc7Z03TJDSnFwz4HXy5KfdLEMHkJUv8ebfgNMuyc52f5ku3eyNE41uduXppdKzDbEJXiyBElMzaFBwK0q1zuDrRM1sjlaUto6bwsB8wPFC08PHsU98-sgJQXsWYYIlzhFRlW5Cjm-4QAfGOQ4BX1M5LLfzwxV0G4s895iLTAShb_fms8ITqiOGL4CjLFX2HYJ2fYanzrEFH3eYSQjZw0iu1iXVnrcaHpeICuFOIbbrR3Uhewa_kwyzHqIa1pUPONYak6Rje5PcD:1tf-W-tMafIY3VprzVNvcBtyC9i0JQCkVwUpTo7ej8wwdbRHntijqxcyZGEwQ0f_c-sucRPxzYIpA9l5V-QnXRNbWNNuA-1lHINJUVD4DCEc9Km2yJiWl2AmXVpN51KE", - "4a74fb8416:0:a6ce3d09a384e6d0b28c1f18ca63a95e3d15fb6eaa3177b3b6bc9f11c3dd1b312b50aaed5b919bafadd9c1e902e1f1f4d3944d43f289a01f9408974b9fe49455:10f8f8557856274c9d78b111ce2fe5e696e43a3139d6c685c6d620bb4946427873c80272f3573f460e156f8ac9eb4a8cf7aa7e9e7df152491be23405dd0fdf86:2:A55YK8R0MYVQi47wlDZxk7Ja3o_ahA88AxzmzQon79MF8HmMbxExwovGEk2TkuQ4xQLgWsM_vs-N-nlJUtUkxsd3ECHYs2s68LxzvpcmROIxakMr02ibclwJGnA_deivI2so8RjGsv4d18Up9lt3qNkor8N6SBuyhhEYPjjwFYKMn7oCqj_LDxVshYa9YtcHtjTABQPiKJaqJzpyZfquKrrofr18SBqJOPgtsw6Omuqv-IY384a-uT-51ic-RhW5eVqWQdVFj3-mIJEWUXIk9TKaAuh5soLFK9awXogG_cXCHTzQ9_POiJ8KW8VSE7OzC46wzYgllWXUKzKZCmwJ-7YP:IoLCcuTeUPiRv_TSSBkPX9ps0pfpcww9WR39SQsi4Q0H4sDk1q5tIgeLMNvwk2weIZpMo28n0evlyvuXw1P3LEEm2wY97D3ceGgwKgnAHwERLh6pM9de1_nZYAQYZOiS", - "466906c1fc:0:7e1df36857083f367b0301af07709748d074eef482f500493280abf22a95e3260ef73caf4496d4b847f6b6f0b9e45abc46c3fa4bb46bb35af207dac98a713609:58ff3e8bac5f1960152d4db8f149c426da2c5b29e53a4e22d86e8b10aee9c44453cd4d19103dd63fa920e9a6a7ff52ac326ed13499eea7b05a44911aaff85524:2:A4BWd-ehtSraBUI7NY_XgI-WHMbN6yVlAJQDiRH8vTk4824OCyNjQ19K5jGuLeEfawJtXG5njz03bvezpOToESZ0HKxPMz-6nz3fYpXMi-0bM6yyNij37t-Gb1_Ee_7JrLplVNfVUKokeG3I6W2W0pWWuSYeyfSSgXwdc3MkfIbLU7xPcR-7UU1FXHzSRRq9OxO3QMe15kVYpSFoxNGCyaMzDwgSLiXxqD3exi9zlKnlZl743vZuZmCjwq0bA7LT20GwT7Zr4qSnC7_XWokj8vofNlUKUziF5zfB5uM3Ml9_zz0HRc-oj42zrdWkXuJbJ7zXjXiFy82kNUllKykoTDoJ:bgOVQS8FrzIrIq_pNvwQfYDJ9HdxAPeAvfN_J8XxDFO2gvV-LGZmR_WWsiNiXVUhF9Y0mU_yZjw7kbenObxYua0OpTXpn-9TAQS1eYDCvR015eAGzpwFjlT85HJWbYfO", - ]; - - #[test] - fn marshal_unmarshal_sign_verify_agree() { - let gen = Identity::generate(); - assert!(gen.agree(&gen).is_some()); - let bytes = gen.to_secret_bytes().unwrap(); - let string = gen.to_secret_string(); - debug_assert!(Identity::from_str(string.as_str()).unwrap().eq(&gen)); - - let gen_unmarshaled = Identity::from_bytes(bytes.as_bytes()).unwrap(); - assert!(gen_unmarshaled.secret.is_some()); - if !gen_unmarshaled.eq(&gen) { - println!("{} != {}", hex::to_string(&gen_unmarshaled.fingerprint), hex::to_string(&gen.fingerprint)); - } - - assert!(Identity::from_str(string.as_str()).unwrap().secret.is_some()); - - let gen2 = Identity::generate(); - assert!(gen2.agree(&gen).unwrap().eq(&gen.agree(&gen2).unwrap())); - - for id_str in GOOD_V0_IDENTITIES { - let mut id = Identity::from_str(id_str).unwrap().validate().unwrap(); - assert_eq!(id.to_secret_string().as_str(), id_str); - - assert!(id.p384.is_none()); - - let idb = id.to_secret_bytes().unwrap(); - let id_unmarshal = Identity::from_bytes(idb.as_bytes()).unwrap().validate().unwrap(); - assert!(id == id_unmarshal); - assert!(id_unmarshal.secret.is_some()); - - let idb2 = id_unmarshal.to_bytes(); - let id_unmarshal2 = Identity::from_bytes(&idb2).unwrap().validate().unwrap(); - assert!(id_unmarshal2 == id_unmarshal); - assert!(id_unmarshal2 == id); - assert!(id_unmarshal2.secret.is_none()); - - let ids = id.to_string(); - assert!(Identity::from_str(ids.as_str()).unwrap() == *id); - - assert!(id.upgrade().is_ok()); - assert!(id.p384.is_some()); - assert!(id.secret.as_ref().unwrap().p384.is_some()); - - let ids = id.to_string(); - assert!(Identity::from_str(ids.as_str()).unwrap() == *id); - } - for id_str in GOOD_V1_IDENTITIES { - let id = Identity::from_str(id_str).unwrap().validate().unwrap(); - assert_eq!(id.to_secret_string().as_str(), id_str); - - assert!(id.p384.is_some()); - assert!(id.secret.as_ref().unwrap().p384.is_some()); - - let idb = id.to_secret_bytes().unwrap(); - let id_unmarshal = Identity::from_bytes(idb.as_bytes()).unwrap().validate().unwrap(); - assert!(id == id_unmarshal); - - let idb2 = id_unmarshal.to_bytes(); - let id_unmarshal2 = Identity::from_bytes(&idb2).unwrap().validate().unwrap(); - assert!(id_unmarshal2 == id_unmarshal); - assert!(id_unmarshal2 == id); - - let ids = id.to_string(); - assert!(Identity::from_str(ids.as_str()).unwrap() == *id); - } - } -} diff --git a/network-hypervisor/src/vl1/inetaddress.rs b/network-hypervisor/src/vl1/inetaddress.rs index bbfed8651..46a578886 100644 --- a/network-hypervisor/src/vl1/inetaddress.rs +++ b/network-hypervisor/src/vl1/inetaddress.rs @@ -12,7 +12,7 @@ use num_traits::AsPrimitive; use serde::{Deserialize, Deserializer, Serialize, Serializer}; -use zerotier_utils::buffer::Buffer; +use zerotier_utils::buffer::{Buffer, OutOfBoundsError}; use zerotier_utils::error::{InvalidFormatError, InvalidParameterError}; use zerotier_utils::marshalable::{Marshalable, UnmarshalError}; @@ -879,7 +879,7 @@ impl InetAddress { impl Marshalable for InetAddress { const MAX_MARSHAL_SIZE: usize = 19; - fn marshal(&self, buf: &mut Buffer) -> Result<(), UnmarshalError> { + fn marshal(&self, buf: &mut Buffer) -> Result<(), OutOfBoundsError> { unsafe { match self.sa.sa_family as AddressFamilyType { AF_INET => { diff --git a/network-hypervisor/src/vl1/mac.rs b/network-hypervisor/src/vl1/mac.rs index 81c709929..9617f1657 100644 --- a/network-hypervisor/src/vl1/mac.rs +++ b/network-hypervisor/src/vl1/mac.rs @@ -7,7 +7,7 @@ use std::str::FromStr; use serde::{Deserialize, Deserializer, Serialize, Serializer}; -use zerotier_utils::buffer::Buffer; +use zerotier_utils::buffer::{Buffer, OutOfBoundsError}; use zerotier_utils::error::InvalidFormatError; use zerotier_utils::hex; use zerotier_utils::marshalable::{Marshalable, UnmarshalError}; @@ -75,9 +75,8 @@ impl Marshalable for MAC { const MAX_MARSHAL_SIZE: usize = 6; #[inline(always)] - fn marshal(&self, buf: &mut Buffer) -> Result<(), UnmarshalError> { + fn marshal(&self, buf: &mut Buffer) -> Result<(), OutOfBoundsError> { buf.append_bytes(&self.0.get().to_be_bytes()[2..]) - .map_err(|_| UnmarshalError::OutOfBounds) } #[inline(always)] diff --git a/network-hypervisor/src/vl1/mod.rs b/network-hypervisor/src/vl1/mod.rs index 5c7e71dc2..8075a2c86 100644 --- a/network-hypervisor/src/vl1/mod.rs +++ b/network-hypervisor/src/vl1/mod.rs @@ -15,7 +15,6 @@ pub mod inetaddress; pub use address::Address; pub use endpoint::Endpoint; pub use event::Event; -pub use identity::Identity; pub use inetaddress::InetAddress; pub use mac::MAC; pub use node::{ApplicationLayer, InnerProtocolLayer, Node, PacketHandlerResult}; diff --git a/network-hypervisor/src/vl1/node.rs b/network-hypervisor/src/vl1/node.rs index e02c65dc0..45aee623c 100644 --- a/network-hypervisor/src/vl1/node.rs +++ b/network-hypervisor/src/vl1/node.rs @@ -3,7 +3,6 @@ use std::collections::HashMap; use std::convert::Infallible; use std::hash::Hash; -use std::io::Write; use std::sync::atomic::Ordering; use std::sync::{Arc, Mutex, RwLock, Weak}; use std::time::Duration; @@ -13,17 +12,15 @@ use crate::vl1::address::Address; use crate::vl1::debug_event; use crate::vl1::endpoint::Endpoint; use crate::vl1::event::Event; -use crate::vl1::identity::Identity; +use crate::vl1::identity::{Identity, IdentitySecret}; use crate::vl1::path::{Path, PathServiceResult}; use crate::vl1::peer::Peer; use crate::vl1::rootset::RootSet; use zerotier_crypto::random; use zerotier_crypto::typestate::{Valid, Verified}; -use zerotier_utils::error::InvalidParameterError; use zerotier_utils::gate::IntervalGate; use zerotier_utils::hex; -use zerotier_utils::marshalable::Marshalable; use zerotier_utils::ringbuffer::RingBuffer; /// Interface trait to be implemented by code that's using the ZeroTier network hypervisor. @@ -40,12 +37,6 @@ pub trait ApplicationLayer: Sync + Send + 'static { /// A VL1 level event occurred. fn event(&self, event: Event); - /// Load this node's identity from the data store. - fn load_node_identity(&self) -> Option>; - - /// Save this node's identity to the data store, returning true on success. - fn save_node_identity(&self, id: &Valid) -> bool; - /// Get a pooled packet buffer for internal use. fn get_buffer(&self) -> PooledPacketBuffer; @@ -141,7 +132,7 @@ pub trait InnerProtocolLayer: Sync + Send { app: &Application, node: &Node, source: &Arc>, - source_path: &Arc>, + source_path: &Arc>, source_hops: u8, message_id: u64, verb: u8, @@ -158,7 +149,7 @@ pub trait InnerProtocolLayer: Sync + Send { app: &Application, node: &Node, source: &Arc>, - source_path: &Arc>, + source_path: &Arc>, source_hops: u8, message_id: u64, in_re_verb: u8, @@ -177,7 +168,7 @@ pub trait InnerProtocolLayer: Sync + Send { app: &Application, node: &Node, source: &Arc>, - source_path: &Arc>, + source_path: &Arc>, source_hops: u8, message_id: u64, in_re_verb: u8, @@ -221,70 +212,30 @@ struct BackgroundTaskIntervals { } struct WhoisQueueItem { - v1_proto_waiting_packets: - RingBuffer<(Weak>, PooledPacketBuffer), WHOIS_MAX_WAITING_PACKETS>, + v1_proto_waiting_packets: RingBuffer<(Weak>, PooledPacketBuffer), WHOIS_MAX_WAITING_PACKETS>, last_retry_time: i64, retry_count: u16, } /// A ZeroTier VL1 node that can communicate securely with the ZeroTier peer-to-peer network. pub struct Node { - /// A random ID generated to identify this particular running instance. pub instance_id: [u8; 16], - - /// This node's identity and permanent keys. pub identity: Valid, - - /// Interval latches for periodic background tasks. + identity_secret: IdentitySecret, intervals: Mutex, - - /// Canonicalized network paths, held as Weak<> to be automatically cleaned when no longer in use. - paths: RwLock, Arc>>>, - - /// Peers with which we are currently communicating. + paths: RwLock, Arc>>>, peers: RwLock>>>, - - /// This node's trusted roots, sorted in ascending order of quality/preference, and cluster definitions. roots: RwLock>, - - /// Current best root. best_root: RwLock>>>, - - /// Queue of identities being looked up. whois_queue: Mutex>>, } impl Node { - pub fn new(app: &Application, auto_generate_identity: bool, auto_upgrade_identity: bool) -> Result { - let mut id = { - let id = app.load_node_identity(); - if id.is_none() { - if !auto_generate_identity { - return Err(InvalidParameterError("no identity found and auto-generate not enabled")); - } else { - let id = Identity::generate(); - app.event(Event::IdentityAutoGenerated(id.as_ref().clone())); - app.save_node_identity(&id); - id - } - } else { - id.unwrap() - } - }; - - if auto_upgrade_identity { - let old = id.clone(); - if id.upgrade()? { - app.save_node_identity(&id); - app.event(Event::IdentityAutoUpgraded(old.remove_typestate(), id.as_ref().clone())); - } - } - - debug_event!(app, "[vl1] loaded identity {}", id.to_string()); - - Ok(Self { + pub fn new(identity: Valid, identity_secret: IdentitySecret) -> Self { + Self { instance_id: random::get_bytes_secure(), - identity: id, + identity, + identity_secret, intervals: Mutex::new(BackgroundTaskIntervals::default()), paths: RwLock::new(HashMap::new()), peers: RwLock::new(HashMap::new()), @@ -297,11 +248,11 @@ impl Node { }), best_root: RwLock::new(None), whois_queue: Mutex::new(HashMap::new()), - }) + } } #[inline] - pub fn peer(&self, a: Address) -> Option>> { + pub fn peer(&self, a: &Address) -> Option>> { self.peers.read().unwrap().get(&a).cloned() } @@ -398,7 +349,7 @@ impl Node { } { debug_event!(app, "[vl1] root sets modified, synchronizing internal data structures"); - let (mut old_root_identities, address_collisions, new_roots, bad_identities, my_root_sets) = { + let (mut old_root_identities, new_roots, bad_identities, my_root_sets) = { let roots = self.roots.read().unwrap(); let old_root_identities: Vec = roots.roots.iter().map(|(p, _)| p.identity.as_ref().clone()).collect(); @@ -406,35 +357,9 @@ impl Node { let mut bad_identities = Vec::new(); let mut my_root_sets: Option> = None; - // This is a sanity check to make sure we don't have root sets that contain roots with the same address - // but a different identity. If we do, the offending address is blacklisted. This would indicate something - // weird and possibly nasty happening with whomever is making your root set definitions. - let mut address_collisions = Vec::new(); - { - let mut address_collision_check = HashMap::with_capacity(roots.sets.len() * 8); - for (_, rs) in roots.sets.iter() { - for m in rs.members.iter() { - if m.identity.eq(&self.identity) { - let _ = my_root_sets.get_or_insert_with(|| Vec::new()).write_all(rs.to_bytes().as_slice()); - } else if self - .peers - .read() - .unwrap() - .get(&m.identity.address) - .map_or(false, |p| !p.identity.as_ref().eq(&m.identity)) - || address_collision_check - .insert(m.identity.address, &m.identity) - .map_or(false, |old_id| !old_id.eq(&m.identity)) - { - address_collisions.push(m.identity.address); - } - } - } - } - for (_, rs) in roots.sets.iter() { for m in rs.members.iter() { - if m.endpoints.is_some() && !address_collisions.contains(&m.identity.address) && !m.identity.eq(&self.identity) { + if m.endpoints.is_some() && !m.identity.eq(&self.identity) { debug_event!( app, "[vl1] examining root {} with {} endpoints", @@ -445,13 +370,13 @@ impl Node { if let Some(peer) = peers.get(&m.identity.address) { new_roots.insert(peer.clone(), m.endpoints.as_ref().unwrap().iter().cloned().collect()); } else { - if let Some(peer) = Peer::new(&self.identity, Valid::mark_valid(m.identity.clone()), time_ticks) { + if let Some(peer) = Peer::new(&self.identity_secret, Valid::mark_valid(m.identity.clone()), time_ticks) { drop(peers); new_roots.insert( self.peers .write() .unwrap() - .entry(m.identity.address) + .entry(m.identity.address.clone()) .or_insert_with(|| Arc::new(peer)) .clone(), m.endpoints.as_ref().unwrap().iter().cloned().collect(), @@ -464,15 +389,9 @@ impl Node { } } - (old_root_identities, address_collisions, new_roots, bad_identities, my_root_sets) + (old_root_identities, new_roots, bad_identities, my_root_sets) }; - for c in address_collisions.iter() { - app.event(Event::SecurityWarning(format!( - "address/identity collision in root sets! address {} collides across root sets or with an existing peer and is being ignored as a root!", - c.to_string() - ))); - } for i in bad_identities.iter() { app.event(Event::SecurityWarning(format!( "bad identity detected for address {} in at least one root set, ignoring (error creating peer object)", @@ -586,7 +505,7 @@ impl Node { let roots = self.roots.read().unwrap(); for (a, peer) in self.peers.read().unwrap().iter() { if !peer.service(app, self, time_ticks) && !roots.roots.contains_key(peer) { - dead_peers.push(*a); + dead_peers.push(a.clone()); } } } @@ -633,7 +552,7 @@ impl Node { if (time_ticks - qi.last_retry_time) >= WHOIS_RETRY_INTERVAL { qi.retry_count += 1; qi.last_retry_time = time_ticks; - need_whois.push(*address); + need_whois.push(address.clone()); } } need_whois @@ -672,7 +591,7 @@ impl Node { // Legacy ZeroTier V1 packet handling if let Ok(fragment_header) = packet.struct_mut_at::(0) { - if let Some(dest) = Address::from_bytes_fixed(&fragment_header.dest) { + if let Some(dest) = Address::from_bytes_v1(&fragment_header.dest) { // Packet is addressed to this node. if dest == self.identity.address { @@ -703,8 +622,8 @@ impl Node { debug_event!(app, "[vl1] [v1] #{:0>16x} packet fully assembled!", fragment_header_id); if let Ok(packet_header) = frag0.struct_at::(0) { - if let Some(source) = Address::from_bytes(&packet_header.src) { - if let Some(peer) = self.peer(source) { + if let Some(source) = Address::from_bytes_v1(&packet_header.src) { + if let Some(peer) = self.peer(&source) { peer.v1_proto_receive( self, app, @@ -728,7 +647,7 @@ impl Node { } } if ok { - self.whois(app, source, Some((Arc::downgrade(&path), combined_packet)), time_ticks); + self.whois(app, source.clone(), Some((Arc::downgrade(&path), combined_packet)), time_ticks); } } } // else source address invalid @@ -738,8 +657,8 @@ impl Node { } else if let Ok(packet_header) = packet.struct_at::(0) { debug_event!(app, "[vl1] [v1] #{:0>16x} is unfragmented", u64::from_be_bytes(packet_header.id)); - if let Some(source) = Address::from_bytes(&packet_header.src) { - if let Some(peer) = self.peer(source) { + if let Some(source) = Address::from_bytes_v1(&packet_header.src) { + if let Some(peer) = self.peer(&source) { peer.v1_proto_receive(self, app, inner, time_ticks, &path, packet_header, packet.as_ref(), &[]); } else { self.whois(app, source, Some((Arc::downgrade(&path), packet)), time_ticks); @@ -788,7 +707,7 @@ impl Node { return; } - if let Some(peer) = self.peer(dest) { + if let Some(peer) = self.peer(&dest) { if let Some(forward_path) = peer.direct_path() { app.wire_send( &forward_path.endpoint, @@ -810,16 +729,10 @@ impl Node { } /// Enqueue and send a WHOIS query for a given address, adding the supplied packet (if any) to the list to be processed on reply. - fn whois( - &self, - app: &Application, - address: Address, - waiting_packet: Option<(Weak>, PooledPacketBuffer)>, - time_ticks: i64, - ) { + fn whois(&self, app: &Application, address: Address, waiting_packet: Option<(Weak>, PooledPacketBuffer)>, time_ticks: i64) { { let mut whois_queue = self.whois_queue.lock().unwrap(); - let qi = whois_queue.entry(address).or_insert_with(|| WhoisQueueItem { + let qi = whois_queue.entry(address.clone()).or_insert_with(|| WhoisQueueItem { v1_proto_waiting_packets: RingBuffer::new(), last_retry_time: 0, retry_count: 0, @@ -856,7 +769,7 @@ impl Node { .send(app, self, None, time_ticks, |packet| -> Result<(), Infallible> { assert!(packet.append_u8(message_type::VL1_WHOIS).is_ok()); while !addresses.is_empty() && (packet.len() + ADDRESS_SIZE) <= UDP_DEFAULT_MTU { - assert!(packet.append_bytes_fixed(&addresses[0].to_bytes()).is_ok()); + assert!(packet.append_bytes_fixed(addresses[0].as_bytes_v1()).is_ok()); addresses = &addresses[1..]; } Ok(()) @@ -882,13 +795,13 @@ impl Node { if let Some(received_identity) = received_identity.validate() { let mut whois_queue = self.whois_queue.lock().unwrap(); if let Some(qi) = whois_queue.get_mut(&received_identity.address) { - let address = received_identity.address; + let address = received_identity.address.clone(); if app.should_respond_to(&received_identity) { let mut peers = self.peers.write().unwrap(); if let Some(peer) = peers.get(&address).cloned().or_else(|| { - Peer::new(&self.identity, received_identity, time_ticks) + Peer::new(&self.identity_secret, received_identity, time_ticks) .map(|p| Arc::new(p)) - .and_then(|peer| Some(peers.entry(address).or_insert(peer).clone())) + .and_then(|peer| Some(peers.entry(address.clone()).or_insert(peer).clone())) }) { drop(peers); for p in qi.v1_proto_waiting_packets.iter() { @@ -928,7 +841,7 @@ impl Node { local_socket: &Application::LocalSocket, local_interface: &Application::LocalInterface, time_ticks: i64, - ) -> Arc> { + ) -> Arc> { let paths = self.paths.read().unwrap(); if let Some(path) = paths.get(&PathKey::Ref(ep, local_socket)) { path.clone() diff --git a/network-hypervisor/src/vl1/path.rs b/network-hypervisor/src/vl1/path.rs index da32ae857..e5e492887 100644 --- a/network-hypervisor/src/vl1/path.rs +++ b/network-hypervisor/src/vl1/path.rs @@ -11,6 +11,8 @@ use crate::vl1::endpoint::Endpoint; use zerotier_crypto::random; use zerotier_utils::NEVER_HAPPENED_TICKS; +use super::ApplicationLayer; + pub(crate) const SERVICE_INTERVAL_MS: i64 = protocol::PATH_KEEPALIVE_INTERVAL; pub(crate) enum PathServiceResult { @@ -24,18 +26,23 @@ pub(crate) enum PathServiceResult { /// These are maintained in Node and canonicalized so that all unique paths have /// one and only one unique path object. That enables statistics to be tracked /// for them and uniform application of things like keepalives. -pub struct Path { +pub struct Path { pub endpoint: Endpoint, - pub local_socket: LocalSocket, - pub local_interface: LocalInterface, + pub local_socket: Application::LocalSocket, + pub local_interface: Application::LocalInterface, last_send_time_ticks: AtomicI64, last_receive_time_ticks: AtomicI64, create_time_ticks: i64, fragmented_packets: Mutex>, } -impl Path { - pub(crate) fn new(endpoint: Endpoint, local_socket: LocalSocket, local_interface: LocalInterface, time_ticks: i64) -> Self { +impl Path { + pub(crate) fn new( + endpoint: Endpoint, + local_socket: Application::LocalSocket, + local_interface: Application::LocalInterface, + time_ticks: i64, + ) -> Self { Self { endpoint, local_socket, diff --git a/network-hypervisor/src/vl1/peer.rs b/network-hypervisor/src/vl1/peer.rs index bb1fb42d0..447a888c2 100644 --- a/network-hypervisor/src/vl1/peer.rs +++ b/network-hypervisor/src/vl1/peer.rs @@ -17,9 +17,10 @@ use zerotier_utils::NEVER_HAPPENED_TICKS; use crate::protocol::*; use crate::vl1::address::Address; use crate::vl1::debug_event; +use crate::vl1::identity::{Identity, IdentitySecret}; use crate::vl1::node::*; use crate::vl1::Valid; -use crate::vl1::{Endpoint, Identity, Path}; +use crate::vl1::{Endpoint, Path}; use crate::{VERSION_MAJOR, VERSION_MINOR, VERSION_REVISION}; pub(crate) const SERVICE_INTERVAL_MS: i64 = 10000; @@ -42,7 +43,7 @@ pub struct Peer { } struct PeerPath { - path: Weak>, + path: Weak>, last_receive_time_ticks: i64, } @@ -62,8 +63,8 @@ impl 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: &Valid, id: Valid, time_ticks: i64) -> Option { - this_node_identity.agree(&id).map(|static_secret| -> Self { + pub(crate) fn new(this_node_identity: &IdentitySecret, id: Valid, time_ticks: i64) -> Option { + this_node_identity.x25519.agree(&id).map(|static_secret| -> Self { Self { identity: id, v1_proto_static_secret: v1::SymmetricSecret::new(static_secret), @@ -113,7 +114,7 @@ impl Peer { /// Get current best path or None if there are no direct paths to this peer. #[inline] - pub fn direct_path(&self) -> Option>> { + pub fn direct_path(&self) -> Option>> { for p in self.paths.lock().unwrap().iter() { let pp = p.path.upgrade(); if pp.is_some() { @@ -125,7 +126,7 @@ impl Peer { /// Get either the current best direct path or an indirect path via e.g. a root. #[inline] - pub fn path(&self, node: &Node) -> Option>> { + pub fn path(&self, node: &Node) -> Option>> { let direct_path = self.direct_path(); if direct_path.is_some() { return direct_path; @@ -136,7 +137,7 @@ impl Peer { return None; } - fn learn_path(&self, app: &Application, new_path: &Arc>, time_ticks: i64) { + fn learn_path(&self, app: &Application, new_path: &Arc>, time_ticks: i64) { let mut paths = self.paths.lock().unwrap(); // TODO: check path filter @@ -285,7 +286,7 @@ impl Peer { &self, app: &Application, node: &Node, - path: Option<&Arc>>, + path: Option<&Arc>>, time_ticks: i64, builder_function: BuilderFunction, ) -> Option> { @@ -324,7 +325,11 @@ impl Peer { let mut aes_gmac_siv = self.v1_proto_static_secret.aes_gmac_siv.get(); aes_gmac_siv.encrypt_init(&self.v1_proto_next_message_id().to_be_bytes()); - aes_gmac_siv.encrypt_set_aad(&v1::get_packet_aad_bytes(self.identity.address, node.identity.address, flags_cipher_hops)); + aes_gmac_siv.encrypt_set_aad(&v1::get_packet_aad_bytes( + &self.identity.address, + &node.identity.address, + flags_cipher_hops, + )); let payload = packet.as_bytes_starting_at_mut(v1::HEADER_SIZE).unwrap(); aes_gmac_siv.encrypt_first_pass(payload); aes_gmac_siv.encrypt_first_pass_finish(); @@ -333,8 +338,8 @@ impl Peer { let header = packet.struct_mut_at::(0).unwrap(); header.id.copy_from_slice(&tag[0..8]); - header.dest = self.identity.address.to_bytes(); - header.src = node.identity.address.to_bytes(); + header.dest = *self.identity.address.as_bytes_v1(); + header.src = *node.identity.address.as_bytes_v1(); header.flags_cipher_hops = flags_cipher_hops; header.mac.copy_from_slice(&tag[8..16]); } else { @@ -350,8 +355,8 @@ impl Peer { { let header = packet.struct_mut_at::(0).unwrap(); header.id = self.v1_proto_next_message_id().to_be_bytes(); - header.dest = self.identity.address.to_bytes(); - header.src = node.identity.address.to_bytes(); + header.dest = *self.identity.address.as_bytes_v1(); + header.src = *node.identity.address.as_bytes_v1(); header.flags_cipher_hops = flags_cipher_hops; header }, @@ -408,8 +413,8 @@ impl Peer { { let f: &mut (v1::PacketHeader, v1::message_component_structs::HelloFixedHeaderFields) = packet.append_struct_get_mut().unwrap(); f.0.id = message_id.to_ne_bytes(); - f.0.dest = self.identity.address.to_bytes(); - f.0.src = node.identity.address.to_bytes(); + f.0.dest = *self.identity.address.as_bytes_v1(); + f.0.src = *node.identity.address.as_bytes_v1(); f.0.flags_cipher_hops = v1::CIPHER_NOCRYPT_POLY1305; f.1.verb = message_type::VL1_HELLO; f.1.version_proto = PROTOCOL_VERSION; @@ -420,7 +425,7 @@ impl Peer { } debug_assert_eq!(packet.len(), 41); - assert!(node.identity.write_public(packet.as_mut(), !self.is_v2()).is_ok()); + assert!(node.identity.write_bytes(packet.as_mut(), !self.is_v2()).is_ok()); let (_, poly1305_key) = v1_proto_salsa_poly_create( &self.v1_proto_static_secret, @@ -470,7 +475,7 @@ impl Peer { app: &Application, inner: &Inner, time_ticks: i64, - source_path: &Arc>, + source_path: &Arc>, packet_header: &v1::PacketHeader, frag0: &PacketBuffer, fragments: &[Option], @@ -564,7 +569,7 @@ impl Peer { node: &Node, time_ticks: i64, message_id: MessageId, - source_path: &Arc>, + source_path: &Arc>, payload: &PacketBuffer, ) -> PacketHandlerResult { if !(app.should_respond_to(&self.identity) || node.this_node_is_root() || node.is_peer_root(self)) { @@ -617,7 +622,7 @@ impl Peer { inner: &Inner, node: &Node, _time_ticks: i64, - source_path: &Arc>, + source_path: &Arc>, source_hops: u8, message_id: u64, payload: &PacketBuffer, @@ -655,7 +660,7 @@ impl Peer { inner: &Inner, node: &Node, time_ticks: i64, - source_path: &Arc>, + source_path: &Arc>, source_hops: u8, message_id: u64, path_is_known: bool, @@ -765,9 +770,9 @@ impl Peer { if !self .send(app, node, None, time_ticks, |packet| { while addresses.len() >= ADDRESS_SIZE && (packet.len() + Identity::MAX_MARSHAL_SIZE) <= UDP_DEFAULT_MTU { - if let Some(zt_address) = Address::from_bytes(&addresses[..ADDRESS_SIZE]) { - if let Some(peer) = node.peer(zt_address) { - peer.identity.write_public(packet, !self.is_v2())?; + if let Some(zt_address) = Address::from_bytes_v1(&addresses[..ADDRESS_SIZE]) { + if let Some(peer) = node.peer(&zt_address) { + peer.identity.write_bytes(packet, !self.is_v2())?; } } addresses = &addresses[ADDRESS_SIZE..]; @@ -789,7 +794,7 @@ impl Peer { node: &Node, _time_ticks: i64, _message_id: MessageId, - _source_path: &Arc>, + _source_path: &Arc>, _payload: &PacketBuffer, ) -> PacketHandlerResult { if node.is_peer_root(self) {} @@ -827,7 +832,7 @@ impl Peer { _app: &Application, _node: &Node, _time_ticks: i64, - _source_path: &Arc>, + _source_path: &Arc>, _payload: &PacketBuffer, ) -> PacketHandlerResult { PacketHandlerResult::Ok @@ -838,7 +843,7 @@ impl Peer { _app: &Application, _node: &Node, _time_ticks: i64, - _source_path: &Arc>, + _source_path: &Arc>, _payload: &PacketBuffer, ) -> PacketHandlerResult { PacketHandlerResult::Ok @@ -848,14 +853,14 @@ impl Peer { impl Hash for Peer { #[inline(always)] fn hash(&self, state: &mut H) { - state.write_u64(self.identity.address.into()); + self.identity.address.hash(state) } } impl PartialEq for Peer { #[inline(always)] fn eq(&self, other: &Self) -> bool { - self.identity.fingerprint.eq(&other.identity.fingerprint) + self.identity.eq(&other.identity) } } diff --git a/network-hypervisor/src/vl1/rootset.rs b/network-hypervisor/src/vl1/rootset.rs index 8418660f5..90c59550e 100644 --- a/network-hypervisor/src/vl1/rootset.rs +++ b/network-hypervisor/src/vl1/rootset.rs @@ -3,16 +3,18 @@ use std::collections::BTreeSet; use std::io::Write; -use crate::vl1::identity::{Identity, IDENTITY_MAX_SIGNATURE_SIZE}; +use crate::vl1::identity::Identity; use crate::vl1::Endpoint; use zerotier_crypto::typestate::Verified; use zerotier_utils::arrayvec::ArrayVec; -use zerotier_utils::buffer::Buffer; +use zerotier_utils::buffer::{Buffer, OutOfBoundsError}; use zerotier_utils::marshalable::{Marshalable, UnmarshalError}; use serde::{Deserialize, Serialize}; +use super::identity::IdentitySecret; + /// Description of a member of a root cluster. /// /// Natural sort order is in order of identity address. @@ -32,7 +34,7 @@ pub struct Root { /// This is populated by the sign() method when the completed root set is signed by each member. /// All member roots must sign. #[serde(default)] - pub signature: ArrayVec, + pub signature: ArrayVec, /// Priority (higher number is lower priority, 0 is default). /// @@ -132,7 +134,7 @@ impl RootSet { ) { self.members.retain(|m| m.identity.address != member_identity.address); let _ = self.members.push(Root { - identity: member_identity.clone_without_secret(), + identity: member_identity.clone(), endpoints: endpoints.map(|endpoints| { let mut tmp = BTreeSet::new(); for a in endpoints { @@ -154,8 +156,8 @@ impl RootSet { /// /// All current members must sign whether they are disabled (witnessing) or active. The verify() /// method will return true when signing is complete. - pub fn sign(&mut self, member_identity: &Identity) -> bool { - let signature = member_identity.sign(self.marshal_for_signing().as_bytes(), false); + pub fn sign(&mut self, member_identity: &Identity, member_identity_secret: &IdentitySecret) -> bool { + let signature = member_identity_secret.sign(self.marshal_for_signing().as_bytes()); let unsigned_entry = self.members.iter().find_map(|m| { if m.identity.eq(member_identity) { Some(m.clone()) @@ -163,13 +165,13 @@ impl RootSet { None } }); - if unsigned_entry.is_some() && signature.is_some() { + if unsigned_entry.is_some() { let unsigned_entry = unsigned_entry.unwrap(); self.members.retain(|m| !m.identity.eq(member_identity)); let _ = self.members.push(Root { identity: unsigned_entry.identity, endpoints: unsigned_entry.endpoints, - signature: signature.unwrap(), + signature: signature, priority: unsigned_entry.priority, protocol_version: unsigned_entry.protocol_version, }); @@ -197,9 +199,9 @@ impl RootSet { /// new root cluster definition and 'previous' being the current/old one. pub fn should_replace(&self, previous: &Self) -> bool { if self.name.eq(&previous.name) && self.revision > previous.revision { - let mut my_signers = BTreeSet::new(); + let mut my_signers = Vec::with_capacity(self.members.len()); for m in self.members.iter() { - my_signers.insert(m.identity.fingerprint.clone()); + my_signers.push(&m.identity); } let mut previous_count: isize = 0; @@ -207,7 +209,7 @@ impl RootSet { for m in previous.members.iter() { if m.endpoints.is_some() { previous_count += 1; - witness_count += my_signers.contains(&m.identity.fingerprint) as isize; + witness_count += my_signers.iter().any(|id| (*id).eq(&m.identity)) as isize; } } @@ -217,7 +219,7 @@ impl RootSet { } } - fn marshal_internal(&self, buf: &mut Buffer, include_signatures: bool) -> Result<(), UnmarshalError> { + fn marshal_internal(&self, buf: &mut Buffer, include_signatures: bool) -> Result<(), OutOfBoundsError> { buf.append_u8(0)?; // version byte for future use buf.append_varint(self.name.as_bytes().len() as u64)?; @@ -265,7 +267,7 @@ impl Marshalable for RootSet { const MAX_MARSHAL_SIZE: usize = crate::protocol::v1::SIZE_MAX; #[inline(always)] - fn marshal(&self, buf: &mut Buffer) -> Result<(), UnmarshalError> { + fn marshal(&self, buf: &mut Buffer) -> Result<(), OutOfBoundsError> { self.marshal_internal(buf, true) } diff --git a/network-hypervisor/src/vl2/multicastauthority.rs b/network-hypervisor/src/vl2/multicastauthority.rs index d81f40090..8fd77d62a 100644 --- a/network-hypervisor/src/vl2/multicastauthority.rs +++ b/network-hypervisor/src/vl2/multicastauthority.rs @@ -3,6 +3,7 @@ use std::sync::{Arc, Mutex, RwLock}; use crate::protocol; use crate::protocol::PacketBuffer; +use crate::vl1::identity::Identity; use crate::vl1::*; use crate::vl2::{MulticastGroup, NetworkId}; @@ -59,7 +60,7 @@ impl MulticastAuthority { if auth(network_id, &source.identity) { let sub_key = (network_id, MulticastGroup { mac, adi: payload.read_u32(&mut cursor).unwrap() }); if let Some(sub) = subscriptions.read().get(&sub_key) { - let _ = sub.lock().unwrap().insert(source.identity.address, time_ticks); + let _ = sub.lock().unwrap().insert(source.identity.address.clone(), time_ticks); } else { let _ = subscriptions .write(&self.subscriptions) @@ -67,7 +68,7 @@ impl MulticastAuthority { .or_insert_with(|| Mutex::new(HashMap::new())) .lock() .unwrap() - .insert(source.identity.address, time_ticks); + .insert(source.identity.address.clone(), time_ticks); } } } @@ -103,7 +104,7 @@ impl MulticastAuthority { if let Some(sub) = subscriptions.get(&(network_id, MulticastGroup { mac, adi })) { let sub = sub.lock().unwrap(); for a in sub.keys() { - gathered.push(*a); + gathered.push(a.clone()); } } @@ -126,7 +127,7 @@ impl MulticastAuthority { packet.append_u16(in_this_packet as u16)?; for _ in 0..in_this_packet { - packet.append_bytes_fixed(&gathered.pop().unwrap().to_bytes())?; + packet.append_bytes_fixed(gathered.pop().unwrap().as_bytes_v1())?; } Ok(()) diff --git a/network-hypervisor/src/vl2/networkid.rs b/network-hypervisor/src/vl2/networkid.rs index 984e365df..8496c7745 100644 --- a/network-hypervisor/src/vl2/networkid.rs +++ b/network-hypervisor/src/vl2/networkid.rs @@ -11,7 +11,6 @@ use zerotier_utils::error::InvalidFormatError; use zerotier_utils::hex; use zerotier_utils::hex::HEX_CHARS; -use crate::protocol::ADDRESS_MASK; use crate::vl1::Address; #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] @@ -23,7 +22,7 @@ impl NetworkId { pub fn from_u64(i: u64) -> Option { // Note that we check both that 'i' is non-zero and that the address of the controller is valid. if let Some(ii) = NonZeroU64::new(i) { - if Address::from_u64(i & ADDRESS_MASK).is_some() { + if Address::from_u64_v1(i).is_some() { return Some(Self(ii)); } } @@ -32,7 +31,7 @@ impl NetworkId { #[inline] pub fn from_controller_and_network_no(controller: Address, network_no: u64) -> Option { - Self::from_u64(u64::from(controller).wrapping_shl(24) | (network_no & 0xffffff)) + Self::from_u64(controller.as_u64_v1().wrapping_shl(24) | (network_no & 0xffffff)) } #[inline] @@ -57,12 +56,12 @@ impl NetworkId { /// Get the network controller ID for this network, which is the most significant 40 bits. #[inline] pub fn network_controller(&self) -> Address { - Address::from_u64(self.0.get()).unwrap() + Address::from_u64_v1(self.0.get()).unwrap() } /// Consume this network ID and return one with the same network number but a different controller ID. pub fn change_network_controller(self, new_controller: Address) -> NetworkId { - Self(NonZeroU64::new((self.network_no() as u64) | u64::from(new_controller).wrapping_shl(24)).unwrap()) + Self(NonZeroU64::new((self.network_no() as u64) | new_controller.as_u64_v1().wrapping_shl(24)).unwrap()) } /// Get the 24-bit local network identifier minus the 40-bit controller address portion. diff --git a/network-hypervisor/src/vl2/rule.rs b/network-hypervisor/src/vl2/rule.rs index 31a634e12..241b2236d 100644 --- a/network-hypervisor/src/vl2/rule.rs +++ b/network-hypervisor/src/vl2/rule.rs @@ -6,7 +6,7 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer}; use phf::phf_map; -use zerotier_utils::buffer::Buffer; +use zerotier_utils::buffer::{Buffer, OutOfBoundsError}; use zerotier_utils::marshalable::{Marshalable, UnmarshalError}; use crate::protocol; @@ -239,7 +239,7 @@ impl Rule { Self { t: action::TEE, v: RuleValue { - forward: rule_value::Forward { address: address.into(), flags, length }, + forward: rule_value::Forward { address: address.as_u64_v1(), flags, length }, }, } } @@ -248,7 +248,7 @@ impl Rule { Self { t: action::TEE, v: RuleValue { - forward: rule_value::Forward { address: address.into(), flags, length }, + forward: rule_value::Forward { address: address.as_u64_v1(), flags, length }, }, } } @@ -257,7 +257,7 @@ impl Rule { Self { t: action::TEE, v: RuleValue { - forward: rule_value::Forward { address: address.into(), flags, length }, + forward: rule_value::Forward { address: address.as_u64_v1(), flags, length }, }, } } @@ -273,14 +273,14 @@ impl Rule { pub fn match_source_zerotier_address(not: bool, or: bool, address: Address) -> Self { Self { t: t(not, or, match_cond::SOURCE_ZEROTIER_ADDRESS), - v: RuleValue { zt: address.into() }, + v: RuleValue { zt: address.as_u64_v1() }, } } pub fn match_dest_zerotier_address(not: bool, or: bool, address: Address) -> Self { Self { t: t(not, or, match_cond::DEST_ZEROTIER_ADDRESS), - v: RuleValue { zt: address.into() }, + v: RuleValue { zt: address.as_u64_v1() }, } } @@ -306,21 +306,21 @@ impl Rule { return v.action_accept(); } action::TEE => { - if let Some(a) = Address::from_u64(self.v.forward.address) { + if let Some(a) = Address::from_u64_v1(self.v.forward.address) { return v.action_tee(a, self.v.forward.flags, self.v.forward.length); } else { return v.invalid_rule(); } } action::WATCH => { - if let Some(a) = Address::from_u64(self.v.forward.address) { + if let Some(a) = Address::from_u64_v1(self.v.forward.address) { return v.action_watch(a, self.v.forward.flags, self.v.forward.length); } else { return v.invalid_rule(); } } action::REDIRECT => { - if let Some(a) = Address::from_u64(self.v.forward.address) { + if let Some(a) = Address::from_u64_v1(self.v.forward.address) { return v.action_redirect(a, self.v.forward.flags, self.v.forward.length); } else { return v.invalid_rule(); @@ -333,14 +333,14 @@ impl Rule { return v.action_priority(self.v.qos_bucket); } match_cond::SOURCE_ZEROTIER_ADDRESS => { - if let Some(a) = Address::from_u64(self.v.zt) { + if let Some(a) = Address::from_u64_v1(self.v.zt) { v.match_source_zerotier_address(not, or, a); } else { return v.invalid_rule(); } } match_cond::DEST_ZEROTIER_ADDRESS => { - if let Some(a) = Address::from_u64(self.v.zt) { + if let Some(a) = Address::from_u64_v1(self.v.zt) { v.match_dest_zerotier_address(not, or, a); } else { return v.invalid_rule(); @@ -447,7 +447,7 @@ impl Rule { impl Marshalable for Rule { const MAX_MARSHAL_SIZE: usize = 21; - fn marshal(&self, buf: &mut Buffer) -> Result<(), UnmarshalError> { + fn marshal(&self, buf: &mut Buffer) -> Result<(), OutOfBoundsError> { buf.append_u8(self.t)?; unsafe { match self.t & 0x3f { @@ -837,7 +837,7 @@ impl<'a> HumanReadableRule<'a> { unsafe { match *t { action::TEE | action::WATCH | action::REDIRECT => { - r.v.forward.address = self.address?.into(); + r.v.forward.address = self.address.as_ref()?.as_u64_v1(); r.v.forward.flags = self.flags?; r.v.forward.length = self.length?; } @@ -845,7 +845,7 @@ impl<'a> HumanReadableRule<'a> { r.v.qos_bucket = self.qosBucket?; } match_cond::SOURCE_ZEROTIER_ADDRESS | match_cond::DEST_ZEROTIER_ADDRESS => { - r.v.zt = self.address?.into(); + r.v.zt = self.address.as_ref()?.as_u64_v1(); } match_cond::VLAN_ID => { r.v.vlan_id = self.vlanId?; diff --git a/network-hypervisor/src/vl2/scattergather.rs b/network-hypervisor/src/vl2/scattergather.rs index a4a46637f..368c2df15 100644 --- a/network-hypervisor/src/vl2/scattergather.rs +++ b/network-hypervisor/src/vl2/scattergather.rs @@ -4,7 +4,7 @@ use fastcdc::v2020; use zerotier_crypto::hash::{SHA384, SHA384_HASH_SIZE}; use zerotier_utils::error::{InvalidFormatError, InvalidParameterError}; -use zerotier_utils::memory::byte_array_chunks_exact; +use zerotier_utils::memory::array_chunks_exact; const MAX_RECURSION_DEPTH: u8 = 64; // sanity limit, object would have to be quite huge to hit this @@ -30,7 +30,7 @@ impl ScatteredObject { if (hl.len() % SHA384_HASH_SIZE) != 0 || hl.is_empty() { return Err(InvalidFormatError); } - for h in byte_array_chunks_exact::(hl) { + for h in array_chunks_exact::(hl) { if (h[SHA384_HASH_SIZE - 1] & 0x01) != 0 { if let Some(chunk) = get_chunk(h) { if depth < MAX_RECURSION_DEPTH { @@ -72,7 +72,7 @@ impl ScatteredObject { let mut chunk_no = 0; let mut missing_chunks = false; - for h in byte_array_chunks_exact::(self.need.as_slice()) { + for h in array_chunks_exact::(self.need.as_slice()) { let dc = self.data_chunks.get_mut(chunk_no).unwrap(); if dc.is_empty() { debug_assert_eq!(h.len(), SHA384_HASH_SIZE); @@ -110,7 +110,7 @@ impl ScatteredObject { /// This list can get longer through the course of object retrival since incoming chunks can /// be chunks of hashes instead of chunks of data. pub fn need(&self) -> impl Iterator { - byte_array_chunks_exact::(self.need.as_slice()) + array_chunks_exact::(self.need.as_slice()) } } diff --git a/network-hypervisor/src/vl2/switch.rs b/network-hypervisor/src/vl2/switch.rs index 1dd807421..279d20911 100644 --- a/network-hypervisor/src/vl2/switch.rs +++ b/network-hypervisor/src/vl2/switch.rs @@ -16,7 +16,7 @@ impl InnerProtocolLayer for Switch { app: &Application, node: &Node, source: &Arc>, - source_path: &Arc>, + source_path: &Arc>, source_hops: u8, message_id: u64, verb: u8, @@ -31,7 +31,7 @@ impl InnerProtocolLayer for Switch { app: &Application, node: &Node, source: &Arc>, - source_path: &Arc>, + source_path: &Arc>, source_hops: u8, message_id: u64, in_re_verb: u8, @@ -48,7 +48,7 @@ impl InnerProtocolLayer for Switch { app: &Application, node: &Node, source: &Arc>, - source_path: &Arc>, + source_path: &Arc>, source_hops: u8, message_id: u64, in_re_verb: u8, diff --git a/network-hypervisor/src/vl2/topology.rs b/network-hypervisor/src/vl2/topology.rs index 4b34782e7..56481e93c 100644 --- a/network-hypervisor/src/vl2/topology.rs +++ b/network-hypervisor/src/vl2/topology.rs @@ -1,12 +1,11 @@ use std::borrow::Cow; -use zerotier_utils::blob::Blob; use zerotier_utils::flatsortedmap::FlatSortedMap; use serde::{Deserialize, Serialize}; -use crate::vl1::identity::IDENTITY_FINGERPRINT_SIZE; use crate::vl1::inetaddress::InetAddress; +use crate::vl1::Address; use crate::vl2::rule::Rule; #[derive(Serialize, Deserialize, Eq, PartialEq, Clone)] @@ -47,7 +46,7 @@ pub struct Topology<'a> { #[serde(skip_serializing_if = "FlatSortedMap::is_empty")] #[serde(default)] - pub members: FlatSortedMap<'a, Blob, Member<'a>>, + pub members: FlatSortedMap<'a, Address, Member<'a>>, } #[inline(always)] diff --git a/network-hypervisor/src/vl2/v1/certificateofmembership.rs b/network-hypervisor/src/vl2/v1/certificateofmembership.rs index 5cad32d97..2513e609c 100644 --- a/network-hypervisor/src/vl2/v1/certificateofmembership.rs +++ b/network-hypervisor/src/vl2/v1/certificateofmembership.rs @@ -1,6 +1,6 @@ use std::io::Write; -use crate::vl1::identity::Identity; +use crate::vl1::identity::{Identity, IdentitySecret}; use crate::vl1::Address; use crate::vl2::NetworkId; @@ -35,23 +35,18 @@ pub struct CertificateOfMembership { impl CertificateOfMembership { /// Create a new signed certificate of membership. /// None is returned if an error occurs, such as the issuer missing its secrets. - pub fn new(issuer: &Identity, network_id: NetworkId, issued_to: &Identity, timestamp: i64, max_delta: u64) -> Option { + pub fn new(issuer: &IdentitySecret, network_id: NetworkId, issued_to: &Identity, timestamp: i64, max_delta: u64) -> Self { let mut com = CertificateOfMembership { network_id, timestamp, max_delta, - issued_to: issued_to.address, + issued_to: issued_to.address.clone(), issued_to_fingerprint: Blob::default(), signature: ArrayVec::new(), }; - com.issued_to_fingerprint = Blob::from(Self::v1_proto_issued_to_fingerprint(issued_to)); - if let Some(signature) = issuer.sign(&com.v1_proto_get_qualifier_bytes(), true) { - com.signature = signature; - Some(com) - } else { - None - } + com.signature = issuer.sign(&com.v1_proto_get_qualifier_bytes()); + com } fn v1_proto_get_qualifier_bytes(&self) -> [u8; 168] { @@ -64,7 +59,7 @@ impl CertificateOfMembership { q[4] = u64::from(self.network_id).to_be(); q[5] = 0; // no disagreement permitted q[6] = 2u64.to_be(); - q[7] = u64::from(self.issued_to).to_be(); + q[7] = self.issued_to.as_u64_v1().to_be(); q[8] = u64::MAX; // no to_be needed for all-1s // This is a fix for a security issue in V1 in which an attacker could (with much CPU use) @@ -91,9 +86,9 @@ impl CertificateOfMembership { /// Get the identity fingerprint used in V1, which only covers the curve25519 keys. fn v1_proto_issued_to_fingerprint(issued_to: &Identity) -> [u8; 32] { let mut v1_signee_hasher = SHA384::new(); - v1_signee_hasher.update(&issued_to.address.to_bytes()); - v1_signee_hasher.update(&issued_to.x25519); - v1_signee_hasher.update(&issued_to.ed25519); + v1_signee_hasher.update(issued_to.address.as_bytes_v1()); + v1_signee_hasher.update(&issued_to.x25519.ecdh); + v1_signee_hasher.update(&issued_to.x25519.eddsa); (&v1_signee_hasher.finish()[..32]).try_into().unwrap() } @@ -104,7 +99,7 @@ impl CertificateOfMembership { v.push(0); v.push(7); // 7 qualifiers, big-endian 16-bit let _ = v.write_all(&self.v1_proto_get_qualifier_bytes()); - let _ = v.write_all(&controller_address.to_bytes()); + let _ = v.write_all(controller_address.as_bytes_v1()); let _ = v.write_all(self.signature.as_bytes()); v } @@ -160,7 +155,7 @@ impl CertificateOfMembership { network_id: NetworkId::from_u64(network_id).ok_or(InvalidParameterError("invalid network ID"))?, timestamp, max_delta, - issued_to: Address::from_u64(issued_to).ok_or(InvalidParameterError("invalid issued to address"))?, + issued_to: Address::from_u64_v1(issued_to).ok_or(InvalidParameterError("invalid issued to address"))?, issued_to_fingerprint: Blob::from(v1_fingerprint), signature: { let mut s = ArrayVec::new(); diff --git a/network-hypervisor/src/vl2/v1/certificateofownership.rs b/network-hypervisor/src/vl2/v1/certificateofownership.rs index 05f975982..f6071f3f3 100644 --- a/network-hypervisor/src/vl2/v1/certificateofownership.rs +++ b/network-hypervisor/src/vl2/v1/certificateofownership.rs @@ -1,7 +1,8 @@ use std::collections::HashSet; use std::io::Write; -use crate::vl1::{Address, Identity, InetAddress, MAC}; +use crate::vl1::identity::{Identity, IdentitySecret}; +use crate::vl1::{Address, InetAddress, MAC}; use crate::vl2::NetworkId; use serde::{Deserialize, Serialize}; @@ -33,7 +34,7 @@ pub struct CertificateOfOwnership { pub timestamp: i64, pub things: HashSet, pub issued_to: Address, - pub signature: ArrayVec, + pub signature: ArrayVec, } impl CertificateOfOwnership { @@ -62,7 +63,7 @@ impl CertificateOfOwnership { let _ = self.things.insert(Thing::Mac(mac)); } - fn internal_to_bytes(&self, for_sign: bool, signed_by: Address) -> Option> { + fn internal_to_bytes(&self, for_sign: bool, signed_by: &Address) -> Option> { if self.things.len() > 0xffff { return None; } @@ -93,8 +94,8 @@ impl CertificateOfOwnership { } } } - let _ = v.write_all(&self.issued_to.to_bytes()); - let _ = v.write_all(&signed_by.to_bytes()); + let _ = v.write_all(self.issued_to.as_bytes_v1()); + let _ = v.write_all(signed_by.as_bytes_v1()); if for_sign { v.push(0); v.push(0); @@ -111,7 +112,7 @@ impl CertificateOfOwnership { } #[inline(always)] - pub fn to_bytes(&self, signed_by: Address) -> Option> { + pub fn to_bytes(&self, signed_by: &Address) -> Option> { self.internal_to_bytes(false, signed_by) } @@ -155,7 +156,7 @@ impl CertificateOfOwnership { network_id: NetworkId::from_u64(network_id).ok_or(InvalidParameterError("invalid network ID"))?, timestamp, things, - issued_to: Address::from_bytes(&b[..5]).ok_or(InvalidParameterError("invalid address"))?, + issued_to: Address::from_bytes_v1(&b[..5]).ok_or(InvalidParameterError("invalid address"))?, signature: { let mut s = ArrayVec::new(); s.push_slice(&b[13..109]); @@ -167,13 +168,11 @@ impl CertificateOfOwnership { } /// Sign certificate of ownership for use by V1 nodes. - pub fn sign(&mut self, issuer: &Identity, issued_to: &Identity) -> bool { - self.issued_to = issued_to.address; - if let Some(to_sign) = self.internal_to_bytes(true, issuer.address) { - if let Some(signature) = issuer.sign(&to_sign.as_slice(), true) { - self.signature = signature; - return true; - } + pub fn sign(&mut self, issuer_address: &Address, issuer: &IdentitySecret, issued_to: &Identity) -> bool { + self.issued_to = issued_to.address.clone(); + if let Some(to_sign) = self.internal_to_bytes(true, issuer_address) { + self.signature = issuer.sign(&to_sign.as_slice()); + return true; } return false; } diff --git a/network-hypervisor/src/vl2/v1/networkconfig.rs b/network-hypervisor/src/vl2/v1/networkconfig.rs index 0e114fc0a..abcb867d2 100644 --- a/network-hypervisor/src/vl2/v1/networkconfig.rs +++ b/network-hypervisor/src/vl2/v1/networkconfig.rs @@ -6,16 +6,17 @@ use std::str::FromStr; use serde::{Deserialize, Serialize}; -use crate::vl1::{Address, Identity, InetAddress}; +use crate::vl1::identity::Identity; +use crate::vl1::{Address, InetAddress}; use crate::vl2::iproute::IpRoute; use crate::vl2::rule::Rule; use crate::vl2::v1::{CertificateOfMembership, CertificateOfOwnership, Tag}; use crate::vl2::NetworkId; -use zerotier_utils::buffer::Buffer; +use zerotier_utils::buffer::{Buffer, OutOfBoundsError}; use zerotier_utils::dictionary::Dictionary; use zerotier_utils::error::InvalidParameterError; -use zerotier_utils::marshalable::Marshalable; +use zerotier_utils::marshalable::{Marshalable, UnmarshalError}; /// Network configuration object sent to nodes by network controllers. #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] @@ -130,24 +131,18 @@ impl NetworkConfig { if !self.routes.is_empty() { let r: Vec = self.routes.iter().cloned().collect(); - d.set_bytes( - proto_v1_field_name::network_config::ROUTES, - IpRoute::marshal_multiple_to_bytes(r.as_slice()).unwrap(), - ); + d.set_bytes(proto_v1_field_name::network_config::ROUTES, marshal_multiple_to_bytes(r.as_slice())); } if !self.static_ips.is_empty() { let ips: Vec = self.static_ips.iter().cloned().collect(); - d.set_bytes( - proto_v1_field_name::network_config::STATIC_IPS, - InetAddress::marshal_multiple_to_bytes(ips.as_slice()).unwrap(), - ); + d.set_bytes(proto_v1_field_name::network_config::STATIC_IPS, marshal_multiple_to_bytes(ips.as_slice())); } if !self.rules.is_empty() { d.set_bytes( proto_v1_field_name::network_config::RULES, - Rule::marshal_multiple_to_bytes(self.rules.as_slice()).unwrap(), + marshal_multiple_to_bytes(self.rules.as_slice()), ); } @@ -188,7 +183,7 @@ impl NetworkConfig { if !v1cred.certificates_of_ownership.is_empty() { let mut certs = Vec::with_capacity(v1cred.certificates_of_ownership.len() * 256); for c in v1cred.certificates_of_ownership.iter() { - let _ = certs.write_all(c.to_bytes(controller_identity.address)?.as_slice()); + let _ = certs.write_all(c.to_bytes(&controller_identity.address)?.as_slice()); } d.set_bytes(proto_v1_field_name::network_config::CERTIFICATES_OF_OWNERSHIP, certs); } @@ -196,7 +191,7 @@ impl NetworkConfig { if !v1cred.tags.is_empty() { let mut tags = Vec::with_capacity(v1cred.tags.len() * 256); for (_, t) in v1cred.tags.iter() { - let _ = tags.write_all(t.to_bytes(controller_identity.address).as_ref()); + let _ = tags.write_all(t.to_bytes(&controller_identity.address).as_ref()); } d.set_bytes(proto_v1_field_name::network_config::TAGS, tags); } @@ -256,7 +251,7 @@ impl NetworkConfig { nc.multicast_limit = d.get_u64(proto_v1_field_name::network_config::MULTICAST_LIMIT).unwrap_or(0) as u32; if let Some(routes_bin) = d.get_bytes(proto_v1_field_name::network_config::ROUTES) { - for r in IpRoute::unmarshal_multiple_from_bytes(routes_bin) + for r in unmarshal_multiple_from_bytes(routes_bin) .map_err(|_| InvalidParameterError("invalid route object(s)"))? .drain(..) { @@ -265,7 +260,7 @@ impl NetworkConfig { } if let Some(static_ips_bin) = d.get_bytes(proto_v1_field_name::network_config::STATIC_IPS) { - for ip in InetAddress::unmarshal_multiple_from_bytes(static_ips_bin) + for ip in unmarshal_multiple_from_bytes(static_ips_bin) .map_err(|_| InvalidParameterError("invalid route object(s)"))? .drain(..) { @@ -274,7 +269,7 @@ impl NetworkConfig { } if let Some(rules_bin) = d.get_bytes(proto_v1_field_name::network_config::RULES) { - nc.rules = Rule::unmarshal_multiple_from_bytes(rules_bin).map_err(|_| InvalidParameterError("invalid route object(s)"))?; + nc.rules = unmarshal_multiple_from_bytes(rules_bin).map_err(|_| InvalidParameterError("invalid route object(s)"))?; } if let Some(dns_bin) = d.get_bytes(proto_v1_field_name::network_config::DNS) { @@ -434,7 +429,7 @@ pub struct V1Credentials { impl Marshalable for IpRoute { const MAX_MARSHAL_SIZE: usize = (InetAddress::MAX_MARSHAL_SIZE * 2) + 2 + 2; - fn marshal(&self, buf: &mut zerotier_utils::buffer::Buffer) -> Result<(), zerotier_utils::marshalable::UnmarshalError> { + fn marshal(&self, buf: &mut zerotier_utils::buffer::Buffer) -> Result<(), OutOfBoundsError> { self.target.marshal(buf)?; if let Some(via) = self.via.as_ref() { via.marshal(buf)?; @@ -477,3 +472,34 @@ impl Marshalable for IpRoute { }) } } + +const TEMP_BUF_SIZE: usize = 1024; + +fn marshal_multiple_to_bytes(multiple: &[M]) -> Vec { + debug_assert!(M::MAX_MARSHAL_SIZE <= TEMP_BUF_SIZE); + let mut tmp = Vec::with_capacity(M::MAX_MARSHAL_SIZE * multiple.len()); + for m in multiple.iter() { + let _ = tmp.write_all(m.to_buffer::().unwrap().as_bytes()); + } + tmp +} + +fn unmarshal_multiple_from_bytes(mut bytes: &[u8]) -> Result, UnmarshalError> { + debug_assert!(M::MAX_MARSHAL_SIZE <= TEMP_BUF_SIZE); + let mut tmp: Buffer = Buffer::new(); + let mut v: Vec = Vec::new(); + while bytes.len() > 0 { + let chunk_size = bytes.len().min(M::MAX_MARSHAL_SIZE); + if tmp.append_bytes(&bytes[..chunk_size]).is_err() { + return Err(UnmarshalError::OutOfBounds); + } + let mut cursor = 0; + v.push(M::unmarshal(&mut tmp, &mut cursor)?); + if cursor == 0 { + return Err(UnmarshalError::InvalidData); + } + let _ = tmp.erase_first_n(cursor); + bytes = &bytes[chunk_size..]; + } + Ok(v) +} diff --git a/network-hypervisor/src/vl2/v1/revocation.rs b/network-hypervisor/src/vl2/v1/revocation.rs index 21c7595f9..5115fe867 100644 --- a/network-hypervisor/src/vl2/v1/revocation.rs +++ b/network-hypervisor/src/vl2/v1/revocation.rs @@ -1,11 +1,11 @@ use std::io::Write; -use zerotier_crypto::typestate::Valid; use zerotier_utils::arrayvec::ArrayVec; use serde::{Deserialize, Serialize}; -use crate::vl1::{Address, Identity}; +use crate::vl1::identity::IdentitySecret; +use crate::vl1::Address; use crate::vl2::v1::CredentialType; use crate::vl2::NetworkId; @@ -26,9 +26,10 @@ impl Revocation { threshold: i64, target: Address, issued_to: Address, - signer: &Valid, + signer_address: &Address, + signer: &IdentitySecret, fast_propagate: bool, - ) -> Option { + ) -> Self { let mut r = Self { network_id, threshold, @@ -37,28 +38,24 @@ impl Revocation { signature: ArrayVec::new(), fast_propagate, }; - if let Some(sig) = signer.sign(r.internal_to_bytes(true, signer.address).as_bytes(), true) { - r.signature.as_mut().copy_from_slice(sig.as_bytes()); - Some(r) - } else { - None - } + r.signature = signer.sign(r.internal_to_bytes(true, signer_address).as_bytes()); + r } - fn internal_to_bytes(&self, for_sign: bool, signed_by: Address) -> ArrayVec { + fn internal_to_bytes(&self, for_sign: bool, signed_by: &Address) -> ArrayVec { let mut v = ArrayVec::new(); if for_sign { let _ = v.write_all(&[0x7f; 8]); } let _ = v.write_all(&[0; 4]); - let _ = v.write_all(&((self.threshold as u32) ^ (u64::from(self.target) as u32)).to_be_bytes()); // ID only used in V1, arbitrary + let _ = v.write_all(&((self.threshold as u32) ^ (self.target.as_u64_v1() as u32)).to_be_bytes()); // ID only used in V1, arbitrary let _ = v.write_all(&self.network_id.to_bytes()); let _ = v.write_all(&[0; 8]); let _ = v.write_all(&self.threshold.to_be_bytes()); let _ = v.write_all(&(self.fast_propagate as u64).to_be_bytes()); // 0x1 is the flag for this - let _ = v.write_all(&self.target.to_bytes()); - let _ = v.write_all(&signed_by.to_bytes()); + let _ = v.write_all(self.target.as_bytes_v1()); + let _ = v.write_all(signed_by.as_bytes_v1()); v.push(CredentialType::CertificateOfMembership as u8); if for_sign { @@ -74,7 +71,7 @@ impl Revocation { } #[inline(always)] - pub fn v1_proto_to_bytes(&self, controller_address: Address) -> ArrayVec { + pub fn v1_proto_to_bytes(&self, controller_address: &Address) -> ArrayVec { self.internal_to_bytes(false, controller_address) } } diff --git a/network-hypervisor/src/vl2/v1/tag.rs b/network-hypervisor/src/vl2/v1/tag.rs index 3ff0cd0e7..0f84b76bc 100644 --- a/network-hypervisor/src/vl2/v1/tag.rs +++ b/network-hypervisor/src/vl2/v1/tag.rs @@ -1,6 +1,6 @@ use std::io::Write; -use crate::vl1::identity::Identity; +use crate::vl1::identity::{Identity, IdentitySecret}; use crate::vl1::Address; use crate::vl2::NetworkId; @@ -21,24 +21,29 @@ pub struct Tag { } impl Tag { - pub fn new(id: u32, value: u32, issuer: &Identity, network_id: NetworkId, issued_to: &Identity, timestamp: i64) -> Option { + pub fn new( + id: u32, + value: u32, + issuer_address: &Address, + issuer: &IdentitySecret, + network_id: NetworkId, + issued_to: &Identity, + timestamp: i64, + ) -> Self { let mut tag = Self { network_id, timestamp, - issued_to: issued_to.address, + issued_to: issued_to.address.clone(), id, value, signature: Blob::default(), }; - let to_sign = tag.internal_to_bytes(true, issuer.address); - if let Some(signature) = issuer.sign(to_sign.as_ref(), true) { - tag.signature.as_mut().copy_from_slice(signature.as_bytes()); - return Some(tag); - } - return None; + let to_sign = tag.internal_to_bytes(true, issuer_address); + tag.signature.as_mut().copy_from_slice(issuer.sign(to_sign.as_ref()).as_bytes()); + tag } - fn internal_to_bytes(&self, for_sign: bool, signed_by: Address) -> ArrayVec { + fn internal_to_bytes(&self, for_sign: bool, signed_by: &Address) -> ArrayVec { let mut v = ArrayVec::new(); if for_sign { let _ = v.write_all(&[0x7f; 8]); @@ -47,8 +52,8 @@ impl Tag { let _ = v.write_all(&self.timestamp.to_be_bytes()); let _ = v.write_all(&self.id.to_be_bytes()); let _ = v.write_all(&self.value.to_be_bytes()); - let _ = v.write_all(&self.issued_to.to_bytes()); - let _ = v.write_all(&signed_by.to_bytes()); + let _ = v.write_all(self.issued_to.as_bytes_v1()); + let _ = v.write_all(signed_by.as_bytes_v1()); if !for_sign { v.push(1); v.push(0); @@ -64,7 +69,7 @@ impl Tag { } #[inline(always)] - pub fn to_bytes(&self, signed_by: Address) -> ArrayVec { + pub fn to_bytes(&self, signed_by: &Address) -> ArrayVec { self.internal_to_bytes(false, signed_by) } @@ -77,7 +82,7 @@ impl Tag { Self { network_id: NetworkId::from_bytes(&b[0..8]).ok_or(InvalidParameterError("invalid network ID"))?, timestamp: i64::from_be_bytes(b[8..16].try_into().unwrap()), - issued_to: Address::from_bytes(&b[24..29]).ok_or(InvalidParameterError("invalid address"))?, + issued_to: Address::from_bytes_v1(&b[24..29]).ok_or(InvalidParameterError("invalid address"))?, id: u32::from_be_bytes(b[16..20].try_into().unwrap()), value: u32::from_be_bytes(b[20..24].try_into().unwrap()), signature: { diff --git a/utils/Cargo.toml b/utils/Cargo.toml index 1a792ed08..fed728f4d 100644 --- a/utils/Cargo.toml +++ b/utils/Cargo.toml @@ -14,7 +14,6 @@ serde = { version = "^1", features = ["derive"], default-features = false } serde_json = { version = "^1", features = ["std"], default-features = false } tokio = { version = "^1", default-features = false, features = ["fs", "io-util", "io-std", "net", "process", "rt", "rt-multi-thread", "signal", "sync", "time"], optional = true } futures-util = { version = "^0", optional = true } -base64 = "0.20.0" [target."cfg(windows)".dependencies] winapi = { version = "^0", features = ["handleapi", "ws2ipdef", "ws2tcpip"] } diff --git a/utils/src/base64.rs b/utils/src/base64.rs deleted file mode 100644 index 84cf8d310..000000000 --- a/utils/src/base64.rs +++ /dev/null @@ -1,20 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. - * - * (c) ZeroTier, Inc. - * https://www.zerotier.com/ - */ - -const BASE64_URL_SAFE_NO_PAD_ENGINE: base64::engine::fast_portable::FastPortable = - base64::engine::fast_portable::FastPortable::from(&base64::alphabet::URL_SAFE, base64::engine::fast_portable::NO_PAD); - -/// Encode base64 using URL-safe alphabet and no padding. -pub fn encode_url_nopad(bytes: &[u8]) -> String { - base64::encode_engine(bytes, &BASE64_URL_SAFE_NO_PAD_ENGINE) -} - -/// Decode base64 using URL-safe alphabet and no padding, or None on error. -pub fn decode_url_nopad(b64: &str) -> Option> { - base64::decode_engine(b64, &BASE64_URL_SAFE_NO_PAD_ENGINE).ok() -} diff --git a/utils/src/lib.rs b/utils/src/lib.rs index 194f7af4e..1fcd9414c 100644 --- a/utils/src/lib.rs +++ b/utils/src/lib.rs @@ -7,7 +7,6 @@ */ pub mod arrayvec; -pub mod base64; pub mod blob; pub mod buffer; pub mod cast; @@ -24,9 +23,11 @@ pub mod json; pub mod marshalable; pub mod memory; pub mod pool; +pub mod proquint; #[cfg(feature = "tokio")] pub mod reaper; pub mod ringbuffer; +pub mod str; pub mod sync; pub mod varint; diff --git a/utils/src/marshalable.rs b/utils/src/marshalable.rs index ffe6c8dcf..24a1bdec6 100644 --- a/utils/src/marshalable.rs +++ b/utils/src/marshalable.rs @@ -8,20 +8,15 @@ use std::error::Error; use std::fmt::{Debug, Display}; -use std::io::Write; -use crate::buffer::Buffer; - -/// Must be larger than any object we want to use with to_bytes() or from_bytes(). -/// This hack can go away once Rust allows us to reference trait consts as generics. -const TEMP_BUF_SIZE: usize = 8192; +use crate::buffer::{Buffer, OutOfBoundsError}; /// A super-lightweight zero-allocation serialization interface. pub trait Marshalable: Sized { const MAX_MARSHAL_SIZE: usize; /// Write this object into a buffer. - fn marshal(&self, buf: &mut Buffer) -> Result<(), UnmarshalError>; + fn marshal(&self, buf: &mut Buffer) -> Result<(), OutOfBoundsError>; /// Read this object from a buffer. /// @@ -35,88 +30,101 @@ pub trait Marshalable: Sized { /// This will return an Err if the buffer is too small or some other error occurs. It's just /// a shortcut to creating a buffer and marshaling into it. #[inline] - fn to_buffer(&self) -> Result, UnmarshalError> { + fn to_buffer(&self) -> Result, OutOfBoundsError> { let mut tmp = Buffer::new(); self.marshal(&mut tmp)?; Ok(tmp) } - /// Unmarshal this object from a buffer. - /// - /// This is just a shortcut to calling unmarshal() with a zero cursor and then discarding the cursor. - #[inline] - fn from_buffer(buf: &Buffer) -> Result { - let mut tmp = 0; - Self::unmarshal(buf, &mut tmp) - } - - /// Marshal and convert to a Rust vector. - #[inline] - fn to_bytes(&self) -> Vec { - assert!(Self::MAX_MARSHAL_SIZE <= TEMP_BUF_SIZE); - let mut tmp = Buffer::::new(); - assert!(self.marshal(&mut tmp).is_ok()); // panics if TEMP_BUF_SIZE is too small - tmp.as_bytes().to_vec() - } - - /// Unmarshal from a raw slice. - #[inline] - fn from_bytes(b: &[u8]) -> Result { - if b.len() <= TEMP_BUF_SIZE { - let mut tmp = Buffer::::new_boxed(); - assert!(tmp.append_bytes(b).is_ok()); - let mut cursor = 0; - Self::unmarshal(&tmp, &mut cursor) - } else { - Err(UnmarshalError::OutOfBounds) + /* + /// Write this marshalable entity into a buffer of the given size. + /// + /// This will return an Err if the buffer is too small or some other error occurs. It's just + /// a shortcut to creating a buffer and marshaling into it. + #[inline] + fn to_buffer(&self) -> Result, UnmarshalError> { + let mut tmp = Buffer::new(); + self.marshal(&mut tmp)?; + Ok(tmp) } - } - /// Marshal a slice of marshalable objects to a concatenated byte vector. - #[inline] - fn marshal_multiple_to_bytes(objects: &[Self]) -> Result, UnmarshalError> { - assert!(Self::MAX_MARSHAL_SIZE <= TEMP_BUF_SIZE); - let mut tmp: Buffer<{ TEMP_BUF_SIZE }> = Buffer::new(); - let mut v: Vec = Vec::with_capacity(objects.len() * Self::MAX_MARSHAL_SIZE); - for i in objects.iter() { - i.marshal(&mut tmp)?; - let _ = v.write_all(tmp.as_bytes()); - tmp.clear(); + /// Unmarshal this object from a buffer. + /// + /// This is just a shortcut to calling unmarshal() with a zero cursor and then discarding the cursor. + #[inline] + fn from_buffer(buf: &Buffer) -> Result { + let mut tmp = 0; + Self::unmarshal(buf, &mut tmp) } - Ok(v) - } - /// Unmarshal a concatenated byte slice of marshalable objects. - #[inline] - fn unmarshal_multiple_from_bytes(mut bytes: &[u8]) -> Result, UnmarshalError> { - assert!(Self::MAX_MARSHAL_SIZE <= TEMP_BUF_SIZE); - let mut tmp: Buffer<{ TEMP_BUF_SIZE }> = Buffer::new(); - let mut v: Vec = Vec::new(); - while bytes.len() > 0 { - let chunk_size = bytes.len().min(Self::MAX_MARSHAL_SIZE); - if tmp.append_bytes(&bytes[..chunk_size]).is_err() { - return Err(UnmarshalError::OutOfBounds); + /// Marshal and convert to a Rust vector. + #[inline] + fn to_bytes(&self) -> Vec { + assert!(Self::MAX_MARSHAL_SIZE <= TEMP_BUF_SIZE); + let mut tmp = Buffer::::new(); + assert!(self.marshal(&mut tmp).is_ok()); // panics if TEMP_BUF_SIZE is too small + tmp.as_bytes().to_vec() + } + + /// Unmarshal from a raw slice. + #[inline] + fn from_bytes(b: &[u8]) -> Result { + if b.len() <= TEMP_BUF_SIZE { + let mut tmp = Buffer::::new_boxed(); + assert!(tmp.append_bytes(b).is_ok()); + let mut cursor = 0; + Self::unmarshal(&tmp, &mut cursor) + } else { + Err(UnmarshalError::OutOfBounds) } - let mut cursor = 0; - v.push(Self::unmarshal(&mut tmp, &mut cursor)?); - if cursor == 0 { - return Err(UnmarshalError::InvalidData); - } - let _ = tmp.erase_first_n(cursor); - bytes = &bytes[chunk_size..]; } - Ok(v) - } - /// Unmarshal a buffer with a byte slice of marshalable objects. - #[inline] - fn unmarshal_multiple(buf: &Buffer, cursor: &mut usize, eof: usize) -> Result, UnmarshalError> { - let mut v: Vec = Vec::new(); - while *cursor < eof { - v.push(Self::unmarshal(buf, cursor)?); + /// Marshal a slice of marshalable objects to a concatenated byte vector. + #[inline] + fn marshal_multiple_to_bytes(objects: &[Self]) -> Result, UnmarshalError> { + assert!(Self::MAX_MARSHAL_SIZE <= TEMP_BUF_SIZE); + let mut tmp: Buffer<{ TEMP_BUF_SIZE }> = Buffer::new(); + let mut v: Vec = Vec::with_capacity(objects.len() * Self::MAX_MARSHAL_SIZE); + for i in objects.iter() { + i.marshal(&mut tmp)?; + let _ = v.write_all(tmp.as_bytes()); + tmp.clear(); + } + Ok(v) } - Ok(v) - } + + /// Unmarshal a concatenated byte slice of marshalable objects. + #[inline] + fn unmarshal_multiple_from_bytes(mut bytes: &[u8]) -> Result, UnmarshalError> { + assert!(Self::MAX_MARSHAL_SIZE <= TEMP_BUF_SIZE); + let mut tmp: Buffer<{ TEMP_BUF_SIZE }> = Buffer::new(); + let mut v: Vec = Vec::new(); + while bytes.len() > 0 { + let chunk_size = bytes.len().min(Self::MAX_MARSHAL_SIZE); + if tmp.append_bytes(&bytes[..chunk_size]).is_err() { + return Err(UnmarshalError::OutOfBounds); + } + let mut cursor = 0; + v.push(Self::unmarshal(&mut tmp, &mut cursor)?); + if cursor == 0 { + return Err(UnmarshalError::InvalidData); + } + let _ = tmp.erase_first_n(cursor); + bytes = &bytes[chunk_size..]; + } + Ok(v) + } + + /// Unmarshal a buffer with a byte slice of marshalable objects. + #[inline] + fn unmarshal_multiple(buf: &Buffer, cursor: &mut usize, eof: usize) -> Result, UnmarshalError> { + let mut v: Vec = Vec::new(); + while *cursor < eof { + v.push(Self::unmarshal(buf, cursor)?); + } + Ok(v) + } + */ } pub enum UnmarshalError { diff --git a/utils/src/memory.rs b/utils/src/memory.rs index c66e7a6ae..55bc34016 100644 --- a/utils/src/memory.rs +++ b/utils/src/memory.rs @@ -59,9 +59,9 @@ pub fn load_raw(src: &[u8]) -> T { } } -/// Our version of the not-yet-stable array_chunks method in slice, but only for byte arrays. +/// Our version of the not-yet-stable array_chunks method in slice. #[inline(always)] -pub fn byte_array_chunks_exact(a: &[u8]) -> impl Iterator { +pub fn array_chunks_exact(a: &[T]) -> impl Iterator { let mut i = 0; let l = a.len(); std::iter::from_fn(move || { diff --git a/utils/src/proquint.rs b/utils/src/proquint.rs new file mode 100644 index 000000000..2f890f20a --- /dev/null +++ b/utils/src/proquint.rs @@ -0,0 +1,98 @@ +// This is a trimmed down version of: https://github.com/christian-blades-cb/proquint-rs +// BSD license + +const UINT2CONSONANT: [char; 16] = ['b', 'd', 'f', 'g', 'h', 'j', 'k', 'l', 'm', 'n', 'p', 'r', 's', 't', 'v', 'z']; +const UINT2VOWEL: &'static [char] = &['a', 'i', 'o', 'u']; +const MASK_FIRST4_U16: u16 = 0xF000; +const MASK_FIRST2_U16: u16 = 0xC000; + +macro_rules! decons { + ($res:ident, $bitcounter:ident, $x:expr) => {{ + $bitcounter += 4; + $res = $res.wrapping_shl(4); + $res += $x; + }}; +} + +macro_rules! devowel { + ($res:ident, $bitcounter:ident, $x:expr) => {{ + $bitcounter += 2; + $res = $res.wrapping_shl(2); + $res += $x; + }}; +} + +macro_rules! cons_u16 { + ($i:ident, $out:ident) => { + let j: u16 = ($i & MASK_FIRST4_U16).wrapping_shr(12); + $i = $i.wrapping_shl(4); + $out.push(UINT2CONSONANT[j as usize]); + }; +} + +macro_rules! vowel_u16 { + ($i:ident, $out:ident) => { + let j: u16 = ($i & MASK_FIRST2_U16).wrapping_shr(14); + $i = $i.wrapping_shl(2); + $out.push(UINT2VOWEL[j as usize]); + }; +} + +pub fn u16_from_quint(quint: &str) -> Option { + let mut bitcounter = 0usize; + let mut res = 0u16; + for c in quint.chars() { + match c { + 'b' => decons!(res, bitcounter, 0u16), + 'd' => decons!(res, bitcounter, 1u16), + 'f' => decons!(res, bitcounter, 2u16), + 'g' => decons!(res, bitcounter, 3u16), + 'h' => decons!(res, bitcounter, 4u16), + 'j' => decons!(res, bitcounter, 5u16), + 'k' => decons!(res, bitcounter, 6u16), + 'l' => decons!(res, bitcounter, 7u16), + 'm' => decons!(res, bitcounter, 8u16), + 'n' => decons!(res, bitcounter, 9u16), + 'p' => decons!(res, bitcounter, 10u16), + 'r' => decons!(res, bitcounter, 11u16), + 's' => decons!(res, bitcounter, 12u16), + 't' => decons!(res, bitcounter, 13u16), + 'v' => decons!(res, bitcounter, 14u16), + 'z' => decons!(res, bitcounter, 15u16), + 'a' => devowel!(res, bitcounter, 0u16), + 'i' => devowel!(res, bitcounter, 1u16), + 'o' => devowel!(res, bitcounter, 2u16), + 'u' => devowel!(res, bitcounter, 3u16), + _ => {} + } + } + if bitcounter == 16 { + Some(res) + } else { + None + } +} + +pub fn u16_to_quint(mut i: u16, out: &mut String) { + cons_u16!(i, out); + vowel_u16!(i, out); + cons_u16!(i, out); + vowel_u16!(i, out); + out.push(UINT2CONSONANT[(i & MASK_FIRST4_U16).wrapping_shr(12) as usize]); +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn quint_u16() { + let mut s = String::with_capacity(16); + for i in u16::MIN..=u16::MAX { + s.clear(); + u16_to_quint(i, &mut s); + assert_eq!(s.len(), 5); + assert_eq!(u16_from_quint(s.as_str()).unwrap(), i); + } + } +} diff --git a/utils/src/str.rs b/utils/src/str.rs new file mode 100644 index 000000000..15eaa96c7 --- /dev/null +++ b/utils/src/str.rs @@ -0,0 +1,56 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * (c) ZeroTier, Inc. + * https://www.zerotier.com/ + */ + +use crate::hex::HEX_CHARS; + +/// Escape non-ASCII-printable characters in a string. +/// This also escapes quotes and other sensitive characters that cause issues on terminals. +pub fn escape(b: &[u8]) -> String { + let mut s = String::with_capacity(b.len() * 2); + for b in b.iter() { + let b = *b; + if b >= 43 && b <= 126 && b != 92 && b != 96 { + s.push(b as char); + } else { + s.push('\\'); + s.push(HEX_CHARS[(b.wrapping_shr(4) & 0xf) as usize] as char); + s.push(HEX_CHARS[(b & 0xf) as usize] as char); + } + } + s +} + +/// Unescape a string with \XX hexadecimal escapes. +pub fn unescape(s: &str) -> Vec { + let mut b = Vec::with_capacity(s.len()); + let mut s = s.as_bytes(); + while let Some(c) = s.first() { + let c = *c; + if c == b'\\' { + if s.len() < 3 { + break; + } + let mut cc = 0u8; + for c in [s[1], s[2]] { + if c >= 48 && c <= 57 { + cc = cc.wrapping_shl(4) | (c - 48); + } else if c >= 65 && c <= 70 { + cc = cc.wrapping_shl(4) | (c - 55); + } else if c >= 97 && c <= 102 { + cc = cc.wrapping_shl(4) | (c - 87); + } + } + b.push(cc); + s = &s[3..]; + } else { + b.push(c); + s = &s[1..]; + } + } + b +}