diff --git a/zerotier-network-hypervisor/Cargo.toml b/zerotier-network-hypervisor/Cargo.toml index 5d56f6f73..c16b7454b 100644 --- a/zerotier-network-hypervisor/Cargo.toml +++ b/zerotier-network-hypervisor/Cargo.toml @@ -18,9 +18,7 @@ lz4_flex = { version = "^0", features = ["safe-encode", "safe-decode", "checked- dashmap = "^4" parking_lot = "^0" lazy_static = "^1" -serde = "^1" -serde_derive = "^1" -serde_json = "^1" +serde = { version = "^1", features = ["derive"], default-features = false } [target."cfg(not(windows))".dependencies] libc = "^0" diff --git a/zerotier-network-hypervisor/src/vl1/address.rs b/zerotier-network-hypervisor/src/vl1/address.rs index d2e5c0a88..7e2c1b327 100644 --- a/zerotier-network-hypervisor/src/vl1/address.rs +++ b/zerotier-network-hypervisor/src/vl1/address.rs @@ -10,14 +10,15 @@ use std::hash::{Hash, Hasher}; use std::num::NonZeroU64; use std::str::FromStr; -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; use crate::error::InvalidFormatError; use crate::util::buffer::Buffer; use crate::util::hex::HEX_CHARS; use crate::vl1::protocol::{ADDRESS_RESERVED_PREFIX, ADDRESS_SIZE}; -#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +/// A unique address on the global ZeroTier VL1 network. +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] #[repr(transparent)] pub struct Address(NonZeroU64); @@ -91,3 +92,57 @@ impl Hash for Address { state.write_u64(self.0.get()); } } + +impl Serialize for Address { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + if serializer.is_human_readable() { + serializer.serialize_str(self.to_string().as_str()) + } else { + serializer.serialize_bytes(&self.to_bytes()) + } + } +} + +struct AddressVisitor; + +impl<'de> serde::de::Visitor<'de> for AddressVisitor { + type Value = Address; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("a ZeroTier address") + } + + fn visit_bytes(self, v: &[u8]) -> Result + 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)) + } else { + Err(E::custom("object too large")) + } + } + + fn visit_str(self, v: &str) -> Result + where + E: serde::de::Error, + { + Address::from_str(v).map_err(|e| E::custom(e.to_string())) + } +} + +impl<'de> Deserialize<'de> for Address { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + if deserializer.is_human_readable() { + deserializer.deserialize_str(AddressVisitor) + } else { + deserializer.deserialize_bytes(AddressVisitor) + } + } +} diff --git a/zerotier-network-hypervisor/src/vl1/identity.rs b/zerotier-network-hypervisor/src/vl1/identity.rs index fe098e888..5bf477858 100644 --- a/zerotier-network-hypervisor/src/vl1/identity.rs +++ b/zerotier-network-hypervisor/src/vl1/identity.rs @@ -17,7 +17,7 @@ use std::str::FromStr; use lazy_static::lazy_static; -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; use zerotier_core_crypto::c25519::*; use zerotier_core_crypto::hash::*; @@ -45,12 +45,14 @@ pub const IDENTITY_ALGORITHM_ALL: u8 = 0xff; pub const MAX_MARSHAL_SIZE: usize = ADDRESS_SIZE + C25519_PUBLIC_KEY_SIZE + ED25519_PUBLIC_KEY_SIZE + C25519_SECRET_KEY_SIZE + ED25519_SECRET_KEY_SIZE + P384_PUBLIC_KEY_SIZE + P384_PUBLIC_KEY_SIZE + P384_SECRET_KEY_SIZE + P384_SECRET_KEY_SIZE + P384_ECDSA_SIGNATURE_SIZE + ED25519_SIGNATURE_SIZE + 16; +/// Secret keys associated with NIST P-384 public keys. #[derive(Clone)] pub struct IdentityP384Secret { 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, @@ -59,6 +61,7 @@ pub struct IdentityP384Public { pub ed25519_self_signature: [u8; ED25519_SIGNATURE_SIZE], } +/// Secret keys associated with an identity. #[derive(Clone)] pub struct IdentitySecret { pub c25519: C25519KeyPair, @@ -66,6 +69,14 @@ pub struct IdentitySecret { pub p384: Option, } +/// 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 by the serde serializer. If you want secrets you must use to_string_with_options() or +/// marshal(). This is to prevent accidental leakage of secrets by naive code. #[derive(Clone)] pub struct Identity { pub address: Address, @@ -76,9 +87,6 @@ pub struct Identity { pub fingerprint: [u8; SHA512_HASH_SIZE], } -#[derive(Eq, PartialEq, Clone, Debug, Ord, PartialOrd, Deserialize, Serialize)] -pub struct NetworkId(pub u64); - #[inline(always)] fn concat_arrays_2(a: &[u8; A], b: &[u8; B]) -> [u8; S] { assert_eq!(A + B, S); @@ -726,6 +734,65 @@ impl Hash for Identity { } } +impl Serialize for Identity { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + if serializer.is_human_readable() { + serializer.serialize_str(self.to_string_with_options(IDENTITY_ALGORITHM_ALL, false).as_str()) + } else { + let mut tmp: Buffer = Buffer::new(); + assert!(self.marshal(&mut tmp, IDENTITY_ALGORITHM_ALL, false).is_ok()); + serializer.serialize_bytes(tmp.as_bytes()) + } + } +} + +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, + { + if v.len() <= MAX_MARSHAL_SIZE { + let mut tmp: Buffer = Buffer::new(); + let _ = tmp.append_bytes(v); + let mut cursor = 0; + Identity::unmarshal(&tmp, &mut cursor).map_err(|e| E::custom(e.to_string())) + } else { + Err(E::custom("object too large")) + } + } + + 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) + } + } +} + const ADDRESS_DERIVATION_HASH_MEMORY_SIZE: usize = 2097152; /// This is a compound hasher used for the work function that derives an address. diff --git a/zerotier-network-hypervisor/src/vl1/mac.rs b/zerotier-network-hypervisor/src/vl1/mac.rs index f2c296567..b17317eda 100644 --- a/zerotier-network-hypervisor/src/vl1/mac.rs +++ b/zerotier-network-hypervisor/src/vl1/mac.rs @@ -11,9 +11,12 @@ use std::hash::{Hash, Hasher}; use std::num::NonZeroU64; use std::str::FromStr; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; + use crate::error::InvalidFormatError; use crate::util::buffer::Buffer; +/// An Ethernet MAC address. #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] #[repr(transparent)] pub struct MAC(NonZeroU64); @@ -87,3 +90,57 @@ impl Hash for MAC { state.write_u64(self.0.get()); } } + +impl Serialize for MAC { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + if serializer.is_human_readable() { + serializer.serialize_str(self.to_string().as_str()) + } else { + serializer.serialize_bytes(&self.to_bytes()) + } + } +} + +struct MACVisitor; + +impl<'de> serde::de::Visitor<'de> for MACVisitor { + type Value = MAC; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("a ZeroTier address") + } + + fn visit_bytes(self, v: &[u8]) -> Result + where + E: serde::de::Error, + { + if v.len() == 6 { + MAC::from_bytes(v).map_or_else(|| Err(E::custom("object too large")), |a| Ok(a)) + } else { + Err(E::custom("object too large")) + } + } + + fn visit_str(self, v: &str) -> Result + where + E: serde::de::Error, + { + MAC::from_str(v).map_err(|e| E::custom(e.to_string())) + } +} + +impl<'de> Deserialize<'de> for MAC { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + if deserializer.is_human_readable() { + deserializer.deserialize_str(MACVisitor) + } else { + deserializer.deserialize_bytes(MACVisitor) + } + } +} diff --git a/zerotier-network-hypervisor/src/vl1/peer.rs b/zerotier-network-hypervisor/src/vl1/peer.rs index a264e45db..1b97d01cb 100644 --- a/zerotier-network-hypervisor/src/vl1/peer.rs +++ b/zerotier-network-hypervisor/src/vl1/peer.rs @@ -509,7 +509,7 @@ impl Peer { // Set legacy poly1305 MAC in packet header. Newer nodes check HMAC-SHA512 but older ones only use this. let (_, mut poly) = salsa_poly_create(&self.identity_symmetric_key, packet.struct_at::(0).unwrap(), packet.len()); poly.update(packet.as_bytes_starting_at(PACKET_HEADER_SIZE).unwrap()); - packet.as_mut_range_fixed::().copy_from_slice(&poly.finish()[0..8]); + packet.as_mut()[HEADER_MAC_FIELD_INDEX..HEADER_MAC_FIELD_INDEX + 8].copy_from_slice(&poly.finish()[0..8]); self.last_send_time_ticks.store(time_ticks, Ordering::Relaxed); self.total_bytes_sent.fetch_add(packet.len() as u64, Ordering::Relaxed); diff --git a/zerotier-network-hypervisor/src/vl1/protocol.rs b/zerotier-network-hypervisor/src/vl1/protocol.rs index 81da490b6..917f43391 100644 --- a/zerotier-network-hypervisor/src/vl1/protocol.rs +++ b/zerotier-network-hypervisor/src/vl1/protocol.rs @@ -48,13 +48,13 @@ pub const KBKDF_KEY_USAGE_LABEL_EPHEMERAL_RATCHET_KEY: u8 = b'e'; pub const EPHEMERAL_SECRET_REKEY_AFTER_TIME: i64 = 300000; // 5 minutes /// Maximum number of times to use an ephemeral secret before trying to replace it. -pub const EPHEMERAL_SECRET_REKEY_AFTER_USES: u32 = 536870912; // 1/4 the NIST/FIPS security bound of 2^31 +pub const EPHEMERAL_SECRET_REKEY_AFTER_USES: usize = 536870912; // 1/4 the NIST/FIPS security bound of 2^31 /// Ephemeral secret reject after time. pub const EPHEMERAL_SECRET_REJECT_AFTER_TIME: i64 = EPHEMERAL_SECRET_REKEY_AFTER_TIME * 2; /// Ephemeral secret reject after uses. -pub const EPHEMERAL_SECRET_REJECT_AFTER_USES: u32 = 2147483648; // NIST/FIPS security bound +pub const EPHEMERAL_SECRET_REJECT_AFTER_USES: usize = 2147483648; // NIST/FIPS security bound pub const SESSION_METADATA_INSTANCE_ID: &'static str = "i"; pub const SESSION_METADATA_CLOCK: &'static str = "t"; @@ -211,7 +211,7 @@ pub fn compress_packet(src: &[u8], dest: &mut Buffer<{ PACKET_SIZE_MAX }>) -> bo /// /// This is the header for a complete packet. If the fragmented flag is set, it will /// arrive with one or more fragments that must be assembled to complete it. -#[repr(packed)] +#[repr(C, packed)] pub struct PacketHeader { pub id: [u8; 8], pub dest: [u8; 5], @@ -275,7 +275,7 @@ impl PacketHeader { /// is normally illegal since addresses can't begin with that. Fragmented packets /// will arrive with the first fragment carrying a normal header with the fragment /// bit set and remaining fragments being these. -#[repr(packed)] +#[repr(C, packed)] pub struct FragmentHeader { pub id: [u8; 8], // (outer) packet ID pub dest: [u8; 5], // destination address @@ -324,7 +324,7 @@ impl FragmentHeader { pub(crate) mod message_component_structs { use crate::util::buffer::RawObject; - #[repr(packed)] + #[repr(C, packed)] pub struct OkHeader { pub in_re_verb: u8, pub in_re_message_id: [u8; 8], @@ -332,7 +332,7 @@ pub(crate) mod message_component_structs { unsafe impl RawObject for OkHeader {} - #[repr(packed)] + #[repr(C, packed)] pub struct ErrorHeader { pub in_re_verb: u8, pub in_re_message_id: [u8; 8], @@ -341,7 +341,7 @@ pub(crate) mod message_component_structs { unsafe impl RawObject for ErrorHeader {} - #[repr(packed)] + #[repr(C, packed)] pub struct HelloFixedHeaderFields { pub verb: u8, pub version_proto: u8, @@ -353,7 +353,7 @@ pub(crate) mod message_component_structs { unsafe impl RawObject for HelloFixedHeaderFields {} - #[repr(packed)] + #[repr(C, packed)] pub struct OkHelloFixedHeaderFields { pub timestamp_echo: [u8; 8], // u64 pub version_proto: u8, diff --git a/zerotier-network-hypervisor/src/vl1/symmetricsecret.rs b/zerotier-network-hypervisor/src/vl1/symmetricsecret.rs index 0747e6966..adfa6d185 100644 --- a/zerotier-network-hypervisor/src/vl1/symmetricsecret.rs +++ b/zerotier-network-hypervisor/src/vl1/symmetricsecret.rs @@ -6,7 +6,8 @@ * https://www.zerotier.com/ */ -use std::sync::atomic::{AtomicU32, Ordering}; +use std::sync::atomic::{AtomicUsize, Ordering}; + use zerotier_core_crypto::aes_gmac_siv::AesGmacSiv; use zerotier_core_crypto::kbkdf::*; use zerotier_core_crypto::secret::Secret; @@ -20,7 +21,7 @@ pub(crate) struct AesGmacSivPoolFactory(Secret<32>, Secret<32>); impl PoolFactory for AesGmacSivPoolFactory { #[inline(always)] fn create(&self) -> AesGmacSiv { - AesGmacSiv::new(&self.0 .0, &self.1 .0) + AesGmacSiv::new(self.0.as_bytes(), self.1.as_bytes()) } #[inline(always)] @@ -33,16 +34,23 @@ impl PoolFactory for AesGmacSivPoolFactory { /// /// This contains the key and several sub-keys and ciphers keyed with sub-keys. pub(crate) struct SymmetricSecret { + /// Master key from which other keys are derived. pub key: Secret<64>, + + /// Key used for HMAC extended validation on packets like HELLO. pub packet_hmac_key: Secret<64>, + + /// Key used with ephemeral keying/re-keying. pub ephemeral_ratchet_key: Secret<64>, + + /// Pool of keyed AES-GMAC-SIV engines (pooled to avoid AES re-init every time). pub aes_gmac_siv: Pool, } impl PartialEq for SymmetricSecret { #[inline(always)] fn eq(&self, other: &Self) -> bool { - self.key.0.eq(&other.key.0) + self.key == other.key } } @@ -70,8 +78,8 @@ pub(crate) struct EphemeralSymmetricSecret { pub rekey_time: i64, pub expire_time: i64, pub ratchet_count: u64, - pub encrypt_uses: AtomicU32, - pub decrypt_uses: AtomicU32, + pub encrypt_uses: AtomicUsize, + pub decrypt_uses: AtomicUsize, pub fips_compliant_exchange: bool, } diff --git a/zerotier-system-service/Cargo.lock b/zerotier-system-service/Cargo.lock index abb7e88a2..197dd4514 100644 --- a/zerotier-system-service/Cargo.lock +++ b/zerotier-system-service/Cargo.lock @@ -868,8 +868,6 @@ dependencies = [ "lz4_flex", "parking_lot", "serde", - "serde_derive", - "serde_json", "winapi", "zerotier-core-crypto", ]