A bunch more work on VL2 config objects.

This commit is contained in:
Adam Ierymenko 2022-09-30 13:57:59 -04:00
parent a31f413eeb
commit 3385953e93
No known key found for this signature in database
GPG key ID: C8877CF2D7A5D7F3
8 changed files with 469 additions and 16 deletions

View file

@ -285,7 +285,7 @@ impl<HostSystemImpl: HostSystem> Peer<HostSystemImpl> {
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 {

View file

@ -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<u8, { identity::IDENTITY_MAX_SIGNATURE_SIZE }>,
}
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<Vec<u8>> {
if self.signature.is_empty() || self.v1.is_none() {
return None;
}
let mut v: Vec<u8> = 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);
}
}

View file

@ -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<Thing>,
pub issued_to: Address,
pub signed_by: Address,
pub signature: ArrayVec<u8, { crate::vl1::identity::IDENTITY_MAX_SIGNATURE_SIZE }>,
}
impl CertificateOfOwnership {
fn internal_v1_proto_to_bytes(&self, for_sign: bool) -> Option<Vec<u8>> {
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<Vec<u8>> {
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;
}
}

View file

@ -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<IpRoute>,
pub static_ips: Vec<InetAddress>,
@ -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<SSOAuthConfiguration>,
}
impl NetworkConfig {
pub fn v1_proto_to_dictionary(&self) -> Option<Dictionary> {
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<const BL: usize>(
&self,
buf: &mut zerotier_utils::buffer::Buffer<BL>,
) -> 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<const BL: usize>(
buf: &zerotier_utils::buffer::Buffer<BL>,
cursor: &mut usize,
) -> Result<Self, zerotier_utils::marshalable::UnmarshalError> {
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<InetAddress>,
pub domain: String,
}
impl Marshalable for Nameserver {
const MAX_MARSHAL_SIZE: usize = 128 + InetAddress::MAX_MARSHAL_SIZE;
fn marshal<const BL: usize>(&self, buf: &mut Buffer<BL>) -> 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<const BL: usize>(buf: &Buffer<BL>, cursor: &mut usize) -> Result<Self, UnmarshalError> {
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(),
})
}
}

View file

@ -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,

View file

@ -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<u8, { identity::IDENTITY_MAX_SIGNATURE_SIZE }>,
}
impl Tag {
fn internal_v1_proto_to_bytes(&self, for_sign: bool) -> Option<Vec<u8>> {
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<Vec<u8>> {
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;
}
}

View file

@ -204,6 +204,19 @@ impl<const L: usize> Buffer<L> {
*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<T: Copy>(&mut self) -> Result<&mut T, OutOfBoundsError> {

View file

@ -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<u8> {
assert!(Self::MAX_MARSHAL_SIZE <= TEMP_BUF_SIZE);
let mut tmp = Buffer::<TEMP_BUF_SIZE>::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<Vec<u8>, UnmarshalError> {
assert!(Self::MAX_MARSHAL_SIZE <= TEMP_BUF_SIZE);
let mut tmp: Buffer<{ TEMP_BUF_SIZE }> = Buffer::new();
let mut v: Vec<u8> = 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<Vec<Self>, UnmarshalError> {
assert!(Self::MAX_MARSHAL_SIZE <= TEMP_BUF_SIZE);
let mut tmp: Buffer<{ TEMP_BUF_SIZE }> = Buffer::new();
let mut v: Vec<Self> = 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<const BL: usize>(buf: &Buffer<BL>, cursor: &mut usize, eof: usize) -> Result<Vec<Self>, UnmarshalError> {
let mut v: Vec<Self> = Vec::new();
while *cursor < eof {
v.push(Self::unmarshal(buf, cursor)?);
}
Ok(v)
}
}
pub enum UnmarshalError {