diff --git a/network-hypervisor/src/vl1/peer.rs b/network-hypervisor/src/vl1/peer.rs index 52ee75419..b65834f1f 100644 --- a/network-hypervisor/src/vl1/peer.rs +++ b/network-hypervisor/src/vl1/peer.rs @@ -285,7 +285,7 @@ impl Peer { let max_fragment_size = path.endpoint.max_fragment_size(); - if self.remote_node_info.read().unwrap().remote_protocol_version >= 12 { + if self.remote_node_info.read().unwrap().remote_protocol_version >= 11 { let flags_cipher_hops = if packet.len() > max_fragment_size { v1::HEADER_FLAG_FRAGMENTED | v1::CIPHER_AES_GMAC_SIV } else { diff --git a/network-hypervisor/src/vl2/certificateofmembership.rs b/network-hypervisor/src/vl2/certificateofmembership.rs index d4cd550e7..e0d28fa96 100644 --- a/network-hypervisor/src/vl2/certificateofmembership.rs +++ b/network-hypervisor/src/vl2/certificateofmembership.rs @@ -1,18 +1,105 @@ +use std::io::Write; + use crate::vl1::identity; +use crate::vl1::identity::Identity; use crate::vl1::Address; use crate::vl2::NetworkId; use serde::{Deserialize, Serialize}; +use zerotier_crypto::hash::SHA384; use zerotier_utils::arrayvec::ArrayVec; use zerotier_utils::blob::Blob; +use zerotier_utils::memory; #[derive(Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct CertificateOfMembership { - pub issued_to: Address, - pub issued_to_fingerprint: Blob<48>, pub network_id: NetworkId, + pub issued_to: Address, pub timestamp: i64, pub max_delta: i64, + pub issued_to_fingerprint: Blob<48>, + pub v1: Option<[u8; 32]>, + pub signed_by: Address, pub signature: ArrayVec, } + +impl CertificateOfMembership { + /// Generate the first three "qualifiers" for V1 nodes. + fn v1_proto_write_first_3_qualifiers(&self, q: &mut [u64]) { + q[0] = 0; + q[1] = self.timestamp.to_be() as u64; + q[2] = self.max_delta.to_be() as u64; + q[3] = 1u64.to_be(); + let nwid: u64 = self.network_id.into(); + q[4] = nwid.to_be(); + q[5] = 0; + q[6] = 2u64.to_be(); + let a: u64 = self.issued_to.into(); + q[7] = a.to_be(); + q[8] = 0xffffffffffffffffu64; // no to_be needed + } + + /// Generate all the qualifiers for V1 nodes, which is part of marshaling for those. + fn v1_proto_get_qualifier_bytes(&self) -> Option<[u8; 168]> { + self.v1.as_ref().map(|v1| { + let mut q = [0u64; 21]; + + self.v1_proto_write_first_3_qualifiers(&mut q); + + q[9] = 3; + q[10] = u64::from_ne_bytes(v1[0..8].try_into().unwrap()); + q[11] = 0xffffffffffffffffu64; + q[12] = 4; + q[13] = u64::from_ne_bytes(v1[8..16].try_into().unwrap()); + q[14] = 0xffffffffffffffffu64; + q[15] = 5; + q[16] = u64::from_ne_bytes(v1[16..24].try_into().unwrap()); + q[17] = 0xffffffffffffffffu64; + q[18] = 6; + q[19] = u64::from_ne_bytes(v1[24..32].try_into().unwrap()); + q[20] = 0xffffffffffffffffu64; + + *memory::as_byte_array(&q) + }) + } + + /// Sign this certificate of membership for use by V1 nodes. + /// + /// This should be used in conjunction with v1_proto_to_bytes() to generate a COM for v1 + /// nodes. This sets the issued_to and v1 fields. + pub fn v1_proto_sign(&mut self, issuer: &Identity, issued_to: &Identity) -> bool { + 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); + let v1_signee_hash = v1_signee_hasher.finish(); + + let mut to_sign = [0u64; 9]; + self.v1_proto_write_first_3_qualifiers(&mut to_sign); + if let Some(signature) = issuer.sign(memory::as_byte_array::<[u64; 9], 27>(&to_sign), true) { + self.issued_to = issued_to.address; + self.v1 = Some(v1_signee_hash[..32].try_into().unwrap()); + self.signed_by = issuer.address; + self.signature = signature; + true + } else { + false + } + } + + /// Get this certificate of membership encoded in the format expected by old V1 nodes. + pub fn v1_proto_to_bytes(&self) -> Option> { + if self.signature.is_empty() || self.v1.is_none() { + return None; + } + let mut v: Vec = Vec::with_capacity(384); + v.push(1); + v.push(0); + v.push(7); // 7 qualifiers, big-endian 16-bit + let _ = v.write_all(&self.v1_proto_get_qualifier_bytes().unwrap()); + let _ = v.write_all(&self.signed_by.to_bytes()); + let _ = v.write_all(self.signature.as_bytes()); + return Some(v); + } +} diff --git a/network-hypervisor/src/vl2/certificateofownership.rs b/network-hypervisor/src/vl2/certificateofownership.rs index d0aeb0501..7116910a2 100644 --- a/network-hypervisor/src/vl2/certificateofownership.rs +++ b/network-hypervisor/src/vl2/certificateofownership.rs @@ -1,12 +1,98 @@ +use std::io::Write; + +use crate::vl1::{Address, Identity, InetAddress, MAC}; use crate::vl2::NetworkId; use serde::{Deserialize, Serialize}; +use zerotier_utils::arrayvec::ArrayVec; + +#[derive(Clone, Serialize, Deserialize, PartialEq, Eq)] +pub enum Thing { + Ip(InetAddress), + Mac(MAC), +} + #[derive(Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct CertificateOfOwnership { pub network_id: NetworkId, pub timestamp: i64, pub flags: u64, pub id: u32, - // TODO + pub things: Vec, + pub issued_to: Address, + pub signed_by: Address, + pub signature: ArrayVec, +} + +impl CertificateOfOwnership { + fn internal_v1_proto_to_bytes(&self, for_sign: bool) -> Option> { + if self.things.len() > 0xffff || self.signature.len() != 96 { + return None; + } + let mut v = Vec::with_capacity(256); + if for_sign { + let _ = v.write_all(&[0x7fu8; 8]); + } + let _ = v.write_all(&self.network_id.to_bytes()); + let _ = v.write_all(&self.timestamp.to_be_bytes()); + let _ = v.write_all(&self.flags.to_be_bytes()); + let _ = v.write_all(&self.id.to_be_bytes()); + let _ = v.write_all(&(self.things.len() as u16).to_be_bytes()); + for t in self.things.iter() { + match t { + Thing::Ip(ip) => { + if ip.is_ipv4() { + v.push(2); + let mut tmp = [0u8; 16]; + tmp[..4].copy_from_slice(&ip.ip_bytes()); + let _ = v.write_all(&tmp); + } else if ip.is_ipv6() { + v.push(3); + let _ = v.write_all(ip.ip_bytes()); + } else { + return None; + } + } + Thing::Mac(m) => { + v.push(1); + let mut tmp = [0u8; 16]; + tmp[..6].copy_from_slice(&m.to_bytes()); + let _ = v.write_all(&tmp); + } + } + } + let _ = v.write_all(&self.issued_to.to_bytes()); + let _ = v.write_all(&self.signed_by.to_bytes()); + if for_sign { + v.push(0); + v.push(0); + let _ = v.write_all(&[0x7fu8; 8]); + } else { + v.push(1); + v.push(0); + v.push(96); // size of legacy signature, 16 bits + let _ = v.write_all(self.signature.as_bytes()); + v.push(0); + v.push(0); + } + return Some(v); + } + + #[inline(always)] + pub fn v1_proto_to_bytes(&self) -> Option> { + self.internal_v1_proto_to_bytes(false) + } + + pub fn v1_proto_sign(&mut self, issuer: &Identity, issued_to: &Identity) -> bool { + self.issued_to = issued_to.address; + self.signed_by = issuer.address; + if let Some(to_sign) = self.internal_v1_proto_to_bytes(true) { + if let Some(signature) = issuer.sign(&to_sign.as_slice(), true) { + self.signature = signature; + return true; + } + } + return false; + } } diff --git a/network-hypervisor/src/vl2/networkconfig.rs b/network-hypervisor/src/vl2/networkconfig.rs index 2b3c10fc4..1a40a1dcb 100644 --- a/network-hypervisor/src/vl2/networkconfig.rs +++ b/network-hypervisor/src/vl2/networkconfig.rs @@ -1,6 +1,7 @@ // (c) 2020-2022 ZeroTier, Inc. -- currently propritery pending actual release and licensing. See LICENSE.md. use std::collections::HashMap; +use std::io::Write; use serde::{Deserialize, Serialize}; @@ -10,8 +11,12 @@ use crate::vl2::certificateofownership::CertificateOfOwnership; use crate::vl2::rule::Rule; use crate::vl2::tag::Tag; +use zerotier_utils::buffer::Buffer; +use zerotier_utils::dictionary::Dictionary; +use zerotier_utils::marshalable::{Marshalable, UnmarshalError}; + #[allow(unused)] -pub mod dictionary_fields { +pub mod field_name { pub mod network_config { pub const VERSION: &'static str = "v"; pub const NETWORK_ID: &'static str = "nwid"; @@ -55,6 +60,18 @@ pub mod dictionary_fields { } } +/// SSO authentication configuration object. +#[derive(Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct SSOAuthConfiguration { + pub version: u32, + pub authentication_url: String, + pub authentication_expiry_time: i64, + pub issuer_url: String, + pub nonce: String, + pub state: String, + pub client_id: String, +} + /// Network configuration object sent to nodes by network controllers. #[derive(Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct NetworkConfig { @@ -68,7 +85,7 @@ pub struct NetworkConfig { pub max_delta: i64, pub revision: u64, - pub mtu: u32, + pub mtu: u16, pub multicast_limit: u32, pub routes: Vec, pub static_ips: Vec, @@ -83,14 +100,96 @@ pub struct NetworkConfig { pub central_url: String, - pub sso_enabled: bool, - pub sso_version: u32, - pub sso_authentication_url: String, - pub sso_authentication_expiry_time: i64, - pub sso_issuer_url: String, - pub sso_nonce: String, - pub sso_state: String, - pub sso_client_id: String, + pub sso: Option, +} + +impl NetworkConfig { + pub fn v1_proto_to_dictionary(&self) -> Option { + let mut d = Dictionary::new(); + d.set_u64(field_name::network_config::NETWORK_ID, self.id); + if !self.name.is_empty() { + d.set_str(field_name::network_config::NAME, self.name.as_str()); + } + if !self.motd.is_empty() { + d.set_str(field_name::network_config::MOTD, self.motd.as_str()); + } + d.set_str(field_name::network_config::ISSUED_TO, self.issued_to.to_string().as_str()); + d.set_str( + field_name::network_config::TYPE, + if self.private { + "0" + } else { + "1" + }, + ); + d.set_u64(field_name::network_config::TIMESTAMP, self.timestamp as u64); + d.set_u64(field_name::network_config::MAX_DELTA, self.max_delta as u64); + d.set_u64(field_name::network_config::REVISION, self.revision); + d.set_u64(field_name::network_config::MTU, self.mtu as u64); + d.set_u64(field_name::network_config::MULTICAST_LIMIT, self.multicast_limit as u64); + if !self.routes.is_empty() { + d.set_bytes( + field_name::network_config::ROUTES, + IpRoute::marshal_multiple_to_bytes(self.routes.as_slice()).unwrap(), + ); + } + if !self.static_ips.is_empty() { + d.set_bytes( + field_name::network_config::STATIC_IPS, + InetAddress::marshal_multiple_to_bytes(self.static_ips.as_slice()).unwrap(), + ); + } + if !self.rules.is_empty() { + d.set_bytes( + field_name::network_config::RULES, + Rule::marshal_multiple_to_bytes(self.rules.as_slice()).unwrap(), + ); + } + if !self.dns.is_empty() { + d.set_bytes( + field_name::network_config::DNS, + Nameserver::marshal_multiple_to_bytes(self.dns.as_slice()).unwrap(), + ); + } + d.set_bytes( + field_name::network_config::CERTIFICATE_OF_MEMBERSHIP, + self.certificate_of_membership.v1_proto_to_bytes()?, + ); + if !self.certificates_of_ownership.is_empty() { + let mut certs = Vec::with_capacity(self.certificates_of_ownership.len() * 256); + for c in self.certificates_of_ownership.iter() { + let _ = certs.write_all(c.v1_proto_to_bytes()?.as_slice()); + } + d.set_bytes(field_name::network_config::CERTIFICATES_OF_OWNERSHIP, certs); + } + if !self.tags.is_empty() { + let mut certs = Vec::with_capacity(self.certificates_of_ownership.len() * 256); + for t in self.tags.iter() { + let _ = certs.write_all(t.v1_proto_to_bytes()?.as_slice()); + } + d.set_bytes(field_name::network_config::TAGS, certs); + } + // node_info is not supported by V1 nodes + if !self.central_url.is_empty() { + d.set_str(field_name::network_config::CENTRAL_URL, self.central_url.as_str()); + } + if let Some(sso) = self.sso.as_ref() { + d.set_bool(field_name::network_config::SSO_ENABLED, true); + d.set_u64(field_name::network_config::SSO_VERSION, sso.version as u64); + d.set_str(field_name::network_config::SSO_AUTHENTICATION_URL, sso.authentication_url.as_str()); + d.set_u64( + field_name::network_config::SSO_AUTHENTICATION_EXPIRY_TIME, + sso.authentication_expiry_time as u64, + ); + d.set_str(field_name::network_config::SSO_ISSUER_URL, sso.issuer_url.as_str()); + d.set_str(field_name::network_config::SSO_NONCE, sso.nonce.as_str()); + d.set_str(field_name::network_config::SSO_STATE, sso.state.as_str()); + d.set_str(field_name::network_config::SSO_CLIENT_ID, sso.client_id.as_str()); + } else { + d.set_bool(field_name::network_config::SSO_ENABLED, false); + } + Some(d) + } } /// Information about nodes on the network that can be included in a network config. @@ -111,9 +210,79 @@ pub struct IpRoute { pub metric: u16, } +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> { + self.target.marshal(buf)?; + if let Some(via) = self.via.as_ref() { + via.marshal(buf)?; + } else { + buf.append_u8(0)?; // "nil" InetAddress + } + buf.append_u16(self.flags)?; + buf.append_u16(self.metric)?; + Ok(()) + } + + fn unmarshal( + buf: &zerotier_utils::buffer::Buffer, + cursor: &mut usize, + ) -> Result { + Ok(IpRoute { + target: InetAddress::unmarshal(buf, cursor)?, + via: { + let via = InetAddress::unmarshal(buf, cursor)?; + if via.is_nil() { + None + } else { + Some(via) + } + }, + flags: buf.read_u16(cursor)?, + metric: buf.read_u16(cursor)?, + }) + } +} + /// ZeroTier-pushed DNS nameserver configuration. #[derive(Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct Nameserver { - pub ip: InetAddress, + pub ip: Vec, pub domain: String, } + +impl Marshalable for Nameserver { + const MAX_MARSHAL_SIZE: usize = 128 + InetAddress::MAX_MARSHAL_SIZE; + + fn marshal(&self, buf: &mut Buffer) -> Result<(), UnmarshalError> { + let domain_bytes = self.domain.as_bytes(); + let domain_bytes_len = domain_bytes.len().min(127); + let mut domain_bytes_pad128 = [0_u8; 128]; + domain_bytes_pad128[..domain_bytes_len].copy_from_slice(&domain_bytes[..domain_bytes_len]); + buf.append_bytes_fixed(&domain_bytes_pad128)?; + buf.append_bytes(InetAddress::marshal_multiple_to_bytes(self.ip.as_slice()).unwrap().as_slice())?; + Ok(()) + } + + fn unmarshal(buf: &Buffer, cursor: &mut usize) -> Result { + let domain_bytes_pad128: &[u8; 128] = buf.read_bytes_fixed(cursor)?; + let mut domain_bytes_len = 0; + for i in 0..128 { + if domain_bytes_pad128[i] == 0 { + domain_bytes_len = i; + break; + } + } + if domain_bytes_len == 0 { + return Err(UnmarshalError::InvalidData); + } + Ok(Nameserver { + ip: InetAddress::unmarshal_multiple(buf, cursor, buf.len())?, + domain: String::from_utf8_lossy(&domain_bytes_pad128[..domain_bytes_len]).to_string(), + }) + } +} diff --git a/network-hypervisor/src/vl2/rule.rs b/network-hypervisor/src/vl2/rule.rs index 06917fcd4..a8835f86d 100644 --- a/network-hypervisor/src/vl2/rule.rs +++ b/network-hypervisor/src/vl2/rule.rs @@ -639,7 +639,7 @@ impl<'de> Deserialize<'de> for Rule { } } -// Compilte time generated perfect hash O(1) lookup of types from human readable names. +// Compile time generated perfect hash for O(1) lookup of types from human readable names. static HR_NAME_TO_RULE_TYPE: phf::Map<&'static str, u8> = phf_map! { "ACTION_DROP" => action::DROP, "ACTION_ACCEPT" => action::ACCEPT, diff --git a/network-hypervisor/src/vl2/tag.rs b/network-hypervisor/src/vl2/tag.rs index e59bf9bb5..db1ce65f1 100644 --- a/network-hypervisor/src/vl2/tag.rs +++ b/network-hypervisor/src/vl2/tag.rs @@ -1,4 +1,7 @@ +use std::io::Write; + use crate::vl1::identity; +use crate::vl1::identity::Identity; use crate::vl1::Address; use crate::vl2::NetworkId; @@ -16,3 +19,50 @@ pub struct Tag { pub signed_by: Address, pub signature: ArrayVec, } + +impl Tag { + fn internal_v1_proto_to_bytes(&self, for_sign: bool) -> Option> { + if self.signature.len() == 96 { + let mut v = Vec::with_capacity(256); + if for_sign { + let _ = v.write_all(&[0x7f; 8]); + } + let _ = v.write_all(&self.network_id.to_bytes()); + 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(&self.signed_by.to_bytes()); + if !for_sign { + v.push(1); + v.push(0); + v.push(96); // size of legacy signatures, 16-bit + let _ = v.write_all(self.signature.as_bytes()); + } + v.push(0); + v.push(0); + if for_sign { + let _ = v.write_all(&[0x7f; 8]); + } + return Some(v); + } + return None; + } + + #[inline(always)] + pub fn v1_proto_to_bytes(&self) -> Option> { + self.internal_v1_proto_to_bytes(false) + } + + pub fn v1_proto_sign(&mut self, issuer: &Identity, issued_to: &Identity) -> bool { + self.issued_to = issued_to.address; + self.signed_by = issuer.address; + if let Some(to_sign) = self.internal_v1_proto_to_bytes(true) { + if let Some(signature) = issuer.sign(&to_sign.as_slice(), true) { + self.signature = signature; + return true; + } + } + return false; + } +} diff --git a/utils/src/buffer.rs b/utils/src/buffer.rs index 532c9d1a8..30461aeda 100644 --- a/utils/src/buffer.rs +++ b/utils/src/buffer.rs @@ -204,6 +204,19 @@ impl Buffer { *self.1.get_unchecked(i) } + /// Erase the first N bytes of this buffer, copying remaining bytes to the front. + pub fn erase_first_n(&mut self, i: usize) -> Result<(), OutOfBoundsError> { + if i < self.0 { + let l = self.0; + self.1.copy_within(i..l, 0); + self.0 = l - i; + Ok(()) + } else { + unlikely_branch(); + Err(OutOfBoundsError) + } + } + /// Append a structure and return a mutable reference to its memory. #[inline(always)] pub fn append_struct_get_mut(&mut self) -> Result<&mut T, OutOfBoundsError> { diff --git a/utils/src/marshalable.rs b/utils/src/marshalable.rs index a38faf959..57743cfb5 100644 --- a/utils/src/marshalable.rs +++ b/utils/src/marshalable.rs @@ -2,6 +2,7 @@ use std::error::Error; use std::fmt::{Debug, Display}; +use std::io::Write; use crate::buffer::Buffer; @@ -46,6 +47,7 @@ pub trait Marshalable: Sized { /// 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() @@ -63,6 +65,52 @@ pub trait Marshalable: Sized { Err(UnmarshalError::OutOfBounds) } } + + /// 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) + } + + /// 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 {