From ec7fae71b4795a6e65e7131841adc22630f8ddca Mon Sep 17 00:00:00 2001 From: Adam Ierymenko Date: Fri, 24 Mar 2023 18:38:33 -0400 Subject: [PATCH] Finish huge network hypervisor refactor. --- network-hypervisor/src/vl1/address.rs | 60 ++-- network-hypervisor/src/vl1/endpoint.rs | 2 - network-hypervisor/src/vl1/identity.rs | 266 +++++++++++++++--- network-hypervisor/src/vl1/mod.rs | 2 +- network-hypervisor/src/vl1/node.rs | 1 + network-hypervisor/src/vl1/peermap.rs | 17 +- network-hypervisor/src/vl1/whois.rs | 10 +- .../src/vl2/multicastauthority.rs | 24 +- network-hypervisor/src/vl2/networkid.rs | 165 ++++++----- network-hypervisor/src/vl2/rule.rs | 60 ++-- .../src/vl2/v1/certificateofmembership.rs | 24 +- .../src/vl2/v1/certificateofownership.rs | 30 +- .../src/vl2/v1/networkconfig.rs | 6 +- network-hypervisor/src/vl2/v1/revocation.rs | 24 +- network-hypervisor/src/vl2/v1/tag.rs | 28 +- utils/src/base24.rs | 28 +- utils/src/hex.rs | 32 +++ 17 files changed, 523 insertions(+), 256 deletions(-) diff --git a/network-hypervisor/src/vl1/address.rs b/network-hypervisor/src/vl1/address.rs index 775fc7bcc..4de542530 100644 --- a/network-hypervisor/src/vl1/address.rs +++ b/network-hypervisor/src/vl1/address.rs @@ -12,10 +12,18 @@ use zerotier_utils::error::InvalidParameterError; use zerotier_utils::hex; use zerotier_utils::memory; +/// A full (V2) ZeroTier address. +/// +/// The first 40 bits (5 bytes) of the address are the legacy 40-bit short ZeroTier address computed from +/// a hash of the identity's X25519 keys. The remaining bits are a SHA384 hash of that short address and +/// all key types and key material. See identity.rs for details. #[derive(Clone, PartialEq, Eq, PartialOrd, Ord)] #[repr(transparent)] pub struct Address(pub(super) [u8; Self::SIZE_BYTES]); +/// A partial address, which is bytes and the number of bytes of specificity (similar to a CIDR IP address). +/// +/// Partial addresses are looked up to get full addresses (and identities) via roots using WHOIS messages. #[derive(Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct PartialAddress(pub(super) Address, pub(super) u16); @@ -39,18 +47,36 @@ impl Address { } } - /// Get the first 40 bits of this address (for legacy use) + /// Get the first 40 bits of this address (a legacy V1 ZeroTier address) #[inline(always)] - pub(crate) fn legacy_bytes(&self) -> &[u8; 5] { + pub fn legacy_bytes(&self) -> &[u8; 5] { memory::array_range::(&self.0) } + /// Get the legacy address in the least significant bits of a u64. + #[inline(always)] + pub(crate) fn legacy_u64(&self) -> u64 { + u64::from_be(memory::load_raw(&self.0)).wrapping_shr(24) + } + /// Get a partial address object (with full specificity) for this address #[inline(always)] pub fn to_partial(&self) -> PartialAddress { PartialAddress(Address(self.0), Self::SIZE_BYTES as u16) } + /// Get a partial address covering the 40-bit legacy address. + pub fn to_legacy_partial(&self) -> PartialAddress { + PartialAddress( + Address({ + let mut tmp = [0u8; PartialAddress::MAX_SIZE_BYTES]; + tmp[..PartialAddress::LEGACY_SIZE_BYTES].copy_from_slice(&self.0[..PartialAddress::LEGACY_SIZE_BYTES]); + tmp + }), + PartialAddress::LEGACY_SIZE_BYTES as u16, + ) + } + #[inline(always)] pub fn as_bytes(&self) -> &[u8; Self::SIZE_BYTES] { &self.0 @@ -65,20 +91,18 @@ impl Borrow<[u8; Self::SIZE_BYTES]> for Address { } impl ToString for Address { + #[inline(always)] fn to_string(&self) -> String { - let mut tmp = String::with_capacity(Self::SIZE_BYTES * 2); - base24::encode_into(&self.0, &mut tmp); - tmp + base24::encode(&self.0) } } impl FromStr for Address { type Err = InvalidParameterError; + #[inline] fn from_str(s: &str) -> Result { - let mut tmp = Vec::with_capacity(Self::SIZE_BYTES); - base24::decode_into(s, &mut tmp); - Self::from_bytes(tmp.as_slice()) + base24::decode(s.as_bytes()).and_then(|b| Self::from_bytes(b.as_slice())) } } @@ -163,11 +187,6 @@ impl PartialAddress { pub const MIN_SIZE_BYTES: usize = Self::LEGACY_SIZE_BYTES; pub const MAX_SIZE_BYTES: usize = Address::SIZE_BYTES; - /// Create an invalid uninitialized address (used when generating Identity) - pub(super) fn new_uninitialized() -> Self { - Self(Address([0u8; Self::MAX_SIZE_BYTES]), 0) - } - /// Construct an address from a byte slice with its length determining specificity. #[inline] pub fn from_bytes(b: &[u8]) -> Result { @@ -229,6 +248,11 @@ impl PartialAddress { memory::array_range::(&self.0 .0) } + #[inline(always)] + pub(crate) fn legacy_u64(&self) -> u64 { + u64::from_be(memory::load_raw(&self.0 .0)).wrapping_shr(24) + } + #[inline(always)] pub(super) fn matches(&self, k: &Address) -> bool { debug_assert!(self.1 >= Self::MIN_SIZE_BYTES as u16); @@ -270,9 +294,7 @@ impl ToString for PartialAddress { if self.is_legacy() { hex::to_string(&self.0 .0[..Self::LEGACY_SIZE_BYTES]) } else { - let mut tmp = String::with_capacity(Self::MAX_SIZE_BYTES * 2); - base24::encode_into(&self.0 .0[..self.1 as usize], &mut tmp); - tmp + base24::encode(self.as_bytes()) } } } @@ -282,11 +304,9 @@ impl FromStr for PartialAddress { fn from_str(s: &str) -> Result { if s.len() == 10 { - return Self::from_bytes(hex::from_string(s).as_slice()); + Self::from_bytes(hex::from_string(s).as_slice()) } else { - let mut tmp = Vec::with_capacity(Self::MAX_SIZE_BYTES); - base24::decode_into(s, &mut tmp)?; - return Self::from_bytes(tmp.as_slice()); + base24::decode(s.as_bytes()).and_then(|b| Self::from_bytes(b.as_slice())) } } } diff --git a/network-hypervisor/src/vl1/endpoint.rs b/network-hypervisor/src/vl1/endpoint.rs index 0efb838ba..0932ddc6c 100644 --- a/network-hypervisor/src/vl1/endpoint.rs +++ b/network-hypervisor/src/vl1/endpoint.rs @@ -37,7 +37,6 @@ pub enum Endpoint { Nil, /// Via another node using unencapsulated relaying (e.g. via a root) - /// This is the address and the full identity fingerprint. ZeroTier(Address), /// Direct L2 Ethernet @@ -65,7 +64,6 @@ pub enum Endpoint { WebRTC(Vec), /// Via another node using inner encapsulation via VERB_ENCAP. - /// This is the address and the full identity fingerprint. ZeroTierEncap(Address), } diff --git a/network-hypervisor/src/vl1/identity.rs b/network-hypervisor/src/vl1/identity.rs index a9e6c512d..acebdcdaf 100644 --- a/network-hypervisor/src/vl1/identity.rs +++ b/network-hypervisor/src/vl1/identity.rs @@ -1,8 +1,10 @@ // (c) 2020-2022 ZeroTier, Inc. -- currently proprietary pending actual release and licensing. See LICENSE.md. +use std::array::TryFromSliceError; use std::io::Write; use std::str::FromStr; +use serde::de::Error; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use super::address::{Address, PartialAddress}; @@ -14,6 +16,7 @@ use zerotier_crypto::secret::Secret; use zerotier_crypto::typestate::Valid; use zerotier_crypto::x25519::*; use zerotier_utils::arrayvec::ArrayVec; +use zerotier_utils::base24; use zerotier_utils::buffer::{Buffer, OutOfBoundsError}; use zerotier_utils::error::InvalidFormatError; use zerotier_utils::marshalable::{Marshalable, UnmarshalError}; @@ -65,7 +68,7 @@ impl Identity { const ALGORITHM_X25519: u8 = 0; const ALGORITHM_P384: u8 = 1; - const V0_IDENTITY_POW_THRESHOLD: u8 = 17; + const LEGACY_ADDRESS_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. @@ -85,23 +88,27 @@ impl Identity { x25519: X25519Secret { ecdh: x25519_ecdh, eddsa: ed25519_eddsa }, p384: None, }; + let mut legacy_address_derivation_hasher = SHA512::new(); loop { - let mut legacy_address_derivation_hash = SHA512::new(); - legacy_address_derivation_hash.update(&secret.public.x25519.ecdh); - legacy_address_derivation_hash.update(&secret.public.x25519.eddsa); - let mut legacy_address_derivation_hash = legacy_address_derivation_hash.finish(); + legacy_address_derivation_hasher.update(&secret.public.x25519.ecdh); + legacy_address_derivation_hasher.update(&secret.public.x25519.eddsa); + let mut legacy_address_derivation_hash = legacy_address_derivation_hasher.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 { + if legacy_address_derivation_hash[0] < Self::LEGACY_ADDRESS_POW_THRESHOLD + && legacy_address_derivation_hash[59] != Address::RESERVED_PREFIX + && legacy_address_derivation_hash[59..64].iter().any(|i| *i != 0) + { secret.public.address.0[..PartialAddress::LEGACY_SIZE_BYTES].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(); secret.public.x25519.ecdh = secret.x25519.ecdh.public_bytes(); + legacy_address_derivation_hasher.reset(); } } - // Generate NIST P-384 key pairs unless this is disabled. + // Generate NIST P-384 key pairs unless we just want a legacy identity. if !x25519_only { secret.p384 = Some(P384Secret { ecdh: P384KeyPair::generate(), @@ -116,7 +123,9 @@ impl Identity { } // Bits 40-384 of the address are filled from a SHA384 hash of all keys for a full length V2 address. - secret.public.populate_extended_address_bits(); + let mut address = secret.public.address.clone(); + secret.public.populate_extended_address_bits(&mut address); + secret.public.address = address; // 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. @@ -133,9 +142,43 @@ impl Identity { } /// Locally validate this identity. - /// This checks address derivation, any self-signatures, etc. - pub fn validate(&self) -> Option> { - todo!() + /// This checks address derivation, any self-signatures, etc. It's a little time consuming so results should be cached + /// if possible. Returns a marked typestate on success, which does sort of cache the results and prevents misuse. + pub fn validate(self) -> Option> { + // First, check that the full SHA384 (bits 40-384) in the address is correct. Check first since this is fast. + let mut test_address = Address::new_uninitialized(); + test_address.0[..PartialAddress::LEGACY_SIZE_BYTES].copy_from_slice(&self.address.0[..PartialAddress::LEGACY_SIZE_BYTES]); + self.populate_extended_address_bits(&mut test_address); + if self.address != test_address { + return None; + } + + // Second, check self-signatures if we have them. Check second since this is somewhat slower. + if let Some(p384) = self.p384.as_ref() { + let mut for_self_signing = + [0u8; Address::SIZE_BYTES + 1 + C25519_PUBLIC_KEY_SIZE + ED25519_PUBLIC_KEY_SIZE + P384_PUBLIC_KEY_SIZE + P384_PUBLIC_KEY_SIZE]; + self.encode_for_self_signing(&mut for_self_signing); + if !ed25519_verify(&self.x25519.eddsa, &p384.ed25519_self_signature, &for_self_signing) + || !p384.ecdsa.verify(&for_self_signing, &p384.p384_self_signature) + { + return None; + } + } + + // Finally, check the legacy address as this is the most costly check. If we deprecate V1 this could go away + // in the future since the full SHA384 (checked above) includes the first 40 bits of the address in the hash. + let mut legacy_address_derivation_hasher = SHA512::new(); + legacy_address_derivation_hasher.update(&self.x25519.ecdh); + legacy_address_derivation_hasher.update(&self.x25519.eddsa); + let mut legacy_address_derivation_hash = legacy_address_derivation_hasher.finish(); + legacy_address_derivation_work_function(&mut legacy_address_derivation_hash); + if legacy_address_derivation_hash[0] >= Self::LEGACY_ADDRESS_POW_THRESHOLD + || !legacy_address_derivation_hash[59..64].eq(self.address.legacy_bytes()) + { + return None; + } + + return Some(Valid::mark_valid(self)); } /// Verify a signature with this identity. @@ -148,7 +191,7 @@ impl Identity { } /// Populate bits 40-384 of the address with a hash of everything else. - fn populate_extended_address_bits(&mut self) { + fn populate_extended_address_bits(&self, address: &mut Address) { let mut sha = SHA384::new(); sha.update(&self.address.0[..PartialAddress::LEGACY_SIZE_BYTES]); // include short address in full hash sha.update(&[Self::ALGORITHM_X25519 @@ -164,7 +207,7 @@ impl Identity { sha.update(p384.ecdsa.as_bytes()); } let sha = sha.finish(); - self.address.0[PartialAddress::LEGACY_SIZE_BYTES..].copy_from_slice(&sha[..Address::SIZE_BYTES - PartialAddress::LEGACY_SIZE_BYTES]); + address.0[PartialAddress::LEGACY_SIZE_BYTES..].copy_from_slice(&sha[..Address::SIZE_BYTES - PartialAddress::LEGACY_SIZE_BYTES]); } /// Encode for self-signing, used only with p384 keys enabled and panics otherwise. @@ -178,10 +221,13 @@ impl Identity { let _ = buf.write_all(p384.ecdsa.as_bytes()); } + /// Decode a byte serialized identity. pub fn from_bytes(b: &[u8]) -> Result { + let mut id; + let mut address = Address::new_uninitialized(); if b.len() == packed::V2_PUBLIC_SIZE && b[PartialAddress::LEGACY_SIZE_BYTES] == (Self::ALGORITHM_X25519 | Self::ALGORITHM_P384) { let p: &packed::V2Public = memory::cast_to_struct(b); - let mut id = Self { + id = Self { address: Address::new_uninitialized(), x25519: X25519 { ecdh: p.c25519, eddsa: p.ed25519 }, p384: Some(P384 { @@ -191,24 +237,24 @@ impl Identity { p384_self_signature: p.p384_self_signature, }), }; - id.address.0[..PartialAddress::LEGACY_SIZE_BYTES].copy_from_slice(&p.short_address); - id.populate_extended_address_bits(); - return Ok(id); + address.0[..PartialAddress::LEGACY_SIZE_BYTES].copy_from_slice(&p.short_address); } else if b.len() == packed::V1_PUBLIC_SIZE && b[PartialAddress::LEGACY_SIZE_BYTES] == Self::ALGORITHM_X25519 { let p: &packed::V1Public = memory::cast_to_struct(b); - let mut id = Self { + id = Self { address: Address::new_uninitialized(), x25519: X25519 { ecdh: p.c25519, eddsa: p.ed25519 }, p384: None, }; - id.address.0[..PartialAddress::LEGACY_SIZE_BYTES].copy_from_slice(&p.short_address); - id.populate_extended_address_bits(); - return Ok(id); + address.0[..PartialAddress::LEGACY_SIZE_BYTES].copy_from_slice(&p.short_address); } else { return Err(InvalidFormatError); } + id.populate_extended_address_bits(&mut address); + id.address = address; + return Ok(id); } + /// Write as a byte serialized identity to a writer. 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 { @@ -236,16 +282,21 @@ impl Identity { 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) - ) + let mut s = String::with_capacity(1024); + base24::encode_into(self.address.as_bytes(), &mut s); + s.push_str(":1:"); + base24::encode_into(&self.x25519.ecdh, &mut s); + s.push(':'); + base24::encode_into(&self.x25519.eddsa, &mut s); + s.push(':'); + base24::encode_into(p384.ecdh.as_bytes(), &mut s); + s.push(':'); + base24::encode_into(p384.ecdsa.as_bytes(), &mut s); + s.push(':'); + base24::encode_into(&p384.ed25519_self_signature, &mut s); + s.push(':'); + base24::encode_into(&p384.p384_self_signature, &mut s); + s } else { format!( "{}:0:{}:{}", @@ -264,9 +315,54 @@ impl FromStr for Identity { let ss: Vec<&str> = s.split(':').collect(); if ss.len() >= 2 { if ss[1] == "1" && ss.len() == 8 { - todo!() + return Ok(Self { + address: Address::from_str(ss[0]).map_err(|_| InvalidFormatError)?, + x25519: X25519 { + ecdh: base24::decode(ss[2].as_bytes()) + .map_err(|_| InvalidFormatError)? + .try_into() + .map_err(|_| InvalidFormatError)?, + eddsa: base24::decode(ss[3].as_bytes()) + .map_err(|_| InvalidFormatError)? + .try_into() + .map_err(|_| InvalidFormatError)?, + }, + p384: Some(P384 { + ecdh: P384PublicKey::from_bytes(base24::decode(ss[4].as_bytes()).map_err(|_| InvalidFormatError)?.as_slice()) + .ok_or(InvalidFormatError)?, + ecdsa: P384PublicKey::from_bytes(base24::decode(ss[5].as_bytes()).map_err(|_| InvalidFormatError)?.as_slice()) + .ok_or(InvalidFormatError)?, + ed25519_self_signature: base24::decode(ss[6].as_bytes()) + .map_err(|_| InvalidFormatError)? + .try_into() + .map_err(|_| InvalidFormatError)?, + p384_self_signature: base24::decode(ss[7].as_bytes()) + .map_err(|_| InvalidFormatError)? + .try_into() + .map_err(|_| InvalidFormatError)?, + }), + }); } else if ss[1] == "0" && ss.len() == 4 { - todo!() + let mut address = { + let legacy_address = hex::from_string(ss[0]); + if legacy_address.len() != PartialAddress::LEGACY_SIZE_BYTES { + return Err(InvalidFormatError); + } + let mut tmp = [0u8; Address::SIZE_BYTES]; + tmp[..PartialAddress::LEGACY_SIZE_BYTES].copy_from_slice(legacy_address.as_slice()); + Address(tmp) + }; + let mut a = Self { + address: Address::new_uninitialized(), + x25519: X25519 { + ecdh: hex::from_string(ss[2]).try_into().map_err(|_| InvalidFormatError)?, + eddsa: hex::from_string(ss[3]).try_into().map_err(|_| InvalidFormatError)?, + }, + p384: None, + }; + a.populate_extended_address_bits(&mut address); + a.address = address; + return Ok(a); } } return Err(InvalidFormatError); @@ -409,6 +505,110 @@ impl<'de> Deserialize<'de> for Identity { } } +/// The actual serialization format for secret identities. +#[derive(Serialize, Deserialize)] +struct IdentitySecretForSerialization<'a> { + pub address: &'a [u8], + pub ed25519_self_signature: Option<&'a [u8]>, + pub p384_self_signature: Option<&'a [u8]>, + pub x25519_ecdh_public: Option<&'a [u8]>, + pub x25519_ecdh_secret: Option<&'a [u8]>, + pub x25519_eddsa_public: Option<&'a [u8]>, + pub x25519_eddsa_secret: Option<&'a [u8]>, + pub p384_ecdh_public: Option<&'a [u8]>, + pub p384_ecdh_secret: Option<&'a [u8]>, + pub p384_ecdsa_public: Option<&'a [u8]>, + pub p384_ecdsa_secret: Option<&'a [u8]>, +} + +impl Serialize for IdentitySecret { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let p384_ecdh_secret = self.p384.as_ref().map(|p384| p384.ecdh.secret_key_bytes()); + let p384_ecdsa_secret = self.p384.as_ref().map(|p384| p384.ecdsa.secret_key_bytes()); + IdentitySecretForSerialization { + address: self.public.address.as_bytes(), + ed25519_self_signature: self.public.p384.as_ref().map(|p384| &p384.ed25519_self_signature[..]), + p384_self_signature: self.public.p384.as_ref().map(|p384| &p384.p384_self_signature[..]), + x25519_ecdh_public: Some(&self.public.x25519.ecdh), + x25519_ecdh_secret: Some(&self.x25519.ecdh.secret_bytes().as_bytes()[..]), + x25519_eddsa_public: Some(&self.public.x25519.eddsa), + x25519_eddsa_secret: Some(&self.x25519.eddsa.secret_bytes().as_bytes()[..]), + p384_ecdh_public: self.p384.as_ref().map(|p384| &p384.ecdh.public_key_bytes()[..]), + p384_ecdh_secret: p384_ecdh_secret.as_ref().map(|s| &s.as_bytes()[..]), + p384_ecdsa_public: self.p384.as_ref().map(|p384| &p384.ecdsa.public_key_bytes()[..]), + p384_ecdsa_secret: p384_ecdsa_secret.as_ref().map(|s| &s.as_bytes()[..]), + } + .serialize(serializer) + } +} + +impl<'de> Deserialize<'de> for IdentitySecret { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + IdentitySecretForSerialization::deserialize(deserializer).and_then(|tmp| { + if let (Some(x25519_ecdh_public), Some(x25519_ecdh_secret), Some(x25519_eddsa_public), Some(x25519_eddsa_secret)) = ( + tmp.x25519_ecdh_public, + tmp.x25519_ecdh_secret, + tmp.x25519_eddsa_public, + tmp.x25519_eddsa_secret, + ) { + let tmp_p384 = if let (Some(a), Some(b), Some(c), Some(d), Some(e), Some(f)) = ( + tmp.ed25519_self_signature, + tmp.p384_self_signature, + tmp.p384_ecdh_public, + tmp.p384_ecdh_secret, + tmp.p384_ecdsa_public, + tmp.p384_ecdsa_secret, + ) { + Some((a, b, c, d, e, f)) + } else { + None + }; + let e2 = || D::Error::custom("invalid key"); + let e = |_e: TryFromSliceError| e2(); + Ok(IdentitySecret { + public: Valid::mark_valid(Identity { + address: Address::from_bytes(tmp.address).map_err(|_| e2())?, + x25519: X25519 { + ecdh: x25519_ecdh_public.try_into().map_err(e)?, + eddsa: x25519_eddsa_public.try_into().map_err(e)?, + }, + p384: if let Some(tmp_p384) = tmp_p384.as_ref() { + Some(P384 { + ecdh: P384PublicKey::from_bytes(tmp_p384.2).ok_or_else(e2)?, + ecdsa: P384PublicKey::from_bytes(tmp_p384.3).ok_or_else(e2)?, + ed25519_self_signature: tmp_p384.0.try_into().map_err(e)?, + p384_self_signature: tmp_p384.1.try_into().map_err(e)?, + }) + } else { + None + }, + }), + x25519: X25519Secret { + ecdh: X25519KeyPair::from_bytes(x25519_ecdh_public, x25519_ecdh_secret).ok_or_else(e2)?, + eddsa: Ed25519KeyPair::from_bytes(x25519_eddsa_public, x25519_eddsa_secret).ok_or_else(e2)?, + }, + p384: if let Some(tmp_p384) = tmp_p384.as_ref() { + Some(P384Secret { + ecdh: P384KeyPair::from_bytes(tmp_p384.2, tmp_p384.3).ok_or_else(e2)?, + ecdsa: P384KeyPair::from_bytes(tmp_p384.4, tmp_p384.5).ok_or_else(e2)?, + }) + } else { + None + }, + }) + } else { + Err(D::Error::custom("missing required fields")) + } + }) + } +} + mod packed { use super::*; diff --git a/network-hypervisor/src/vl1/mod.rs b/network-hypervisor/src/vl1/mod.rs index 29ff63068..30f462ff2 100644 --- a/network-hypervisor/src/vl1/mod.rs +++ b/network-hypervisor/src/vl1/mod.rs @@ -14,7 +14,7 @@ mod whois; pub mod identity; pub mod inetaddress; -pub use address::Address; +pub use address::{Address, PartialAddress}; pub use endpoint::Endpoint; pub use event::Event; pub use inetaddress::InetAddress; diff --git a/network-hypervisor/src/vl1/node.rs b/network-hypervisor/src/vl1/node.rs index 546a57c9a..2c5c75da5 100644 --- a/network-hypervisor/src/vl1/node.rs +++ b/network-hypervisor/src/vl1/node.rs @@ -50,6 +50,7 @@ pub trait ApplicationLayer: Sync + Send + 'static { /// /// The default implementation always returns true. Typically this is what you want for a /// controller or a root but not a regular node (unless required for backward compatibility). + #[allow(unused)] fn should_respond_to(&self, id: &Valid) -> bool { true } diff --git a/network-hypervisor/src/vl1/peermap.rs b/network-hypervisor/src/vl1/peermap.rs index c7f1dd54a..6a82a3c59 100644 --- a/network-hypervisor/src/vl1/peermap.rs +++ b/network-hypervisor/src/vl1/peermap.rs @@ -45,7 +45,7 @@ impl PeerMap { for m in matches { if address.matches(m.0) { if r.is_none() { - r.insert(m.1); + let _ = r.insert(m.1); } else { return None; } @@ -58,8 +58,8 @@ impl PeerMap { /// Insert the supplied peer if it is in fact new, otherwise return the existing peer with the same address. pub fn add(&self, peer: Arc>) -> (Arc>, bool) { - let mm = self.maps[peer.identity.address.0[0] as usize].write().unwrap(); - let p = mm.entry(peer.identity.address).or_insert(peer.clone()); + let mut mm = self.maps[peer.identity.address.0[0] as usize].write().unwrap(); + let p = mm.entry(peer.identity.address.clone()).or_insert(peer.clone()); if Arc::ptr_eq(p, &peer) { (peer, true) } else { @@ -69,13 +69,18 @@ impl PeerMap { /// Get a peer or create one if not found. /// This should be used when the peer will almost always be new, such as on OK(WHOIS). - pub fn get_or_add(&self, this_node_identity: &IdentitySecret, peer_identity: Valid, time_ticks: i64) -> Option>> { - let peer = Arc::new(Peer::new(this_node_identity, peer_identity, time_ticks)?); + pub fn get_or_add( + &self, + this_node_identity: &IdentitySecret, + peer_identity: &Valid, + time_ticks: i64, + ) -> Option>> { + let peer = Arc::new(Peer::new(this_node_identity, peer_identity.clone(), time_ticks)?); Some( self.maps[peer_identity.address.0[0] as usize] .write() .unwrap() - .entry(peer.identity.address) + .entry(peer_identity.address.clone()) .or_insert(peer) .clone(), ) diff --git a/network-hypervisor/src/vl1/whois.rs b/network-hypervisor/src/vl1/whois.rs index 86cc42895..450cd5976 100644 --- a/network-hypervisor/src/vl1/whois.rs +++ b/network-hypervisor/src/vl1/whois.rs @@ -13,7 +13,7 @@ use crate::protocol; use zerotier_crypto::typestate::Valid; use zerotier_utils::ringbuffer::RingBuffer; -pub struct Whois { +pub(super) struct Whois { whois_queue: Mutex>>, } @@ -54,18 +54,18 @@ impl Whois { let mut to_delete = Vec::with_capacity(2); for qi in q.range((Bound::Unbounded, Bound::Included(identity.address.to_partial()))).rev() { if qi.0.matches(&identity.address) { - to_delete.push(qi.0); + to_delete.push(qi.0.clone()); // TODO } else { break; } } for a in to_delete { - queued_items.push(q.remove(a).unwrap()); + queued_items.push(q.remove(&a).unwrap()); } } - if let Some(peer) = node.peers.get_or_add(&node.identity_secret, identity, time_ticks) { + if let Some(peer) = node.peers.get_or_add(&node.identity_secret, &identity, time_ticks) { for qi in queued_items.iter() { for pkt in qi.pending_v1_packets.iter() { if let Some(source_path) = pkt.0.upgrade() { @@ -80,7 +80,7 @@ impl Whois { pub fn retry_queued(&self) {} - fn send_whois(&self, app: &Application, node: &Node, addresses: &[PartialAddress], time_ticks: i64) { + fn send_whois(&self, app: &Application, node: &Node, mut addresses: &[PartialAddress], time_ticks: i64) { debug_assert!(!addresses.is_empty()); debug_event!(app, "[vl1] [v1] sending WHOIS for {}", { let mut tmp = String::new(); diff --git a/network-hypervisor/src/vl2/multicastauthority.rs b/network-hypervisor/src/vl2/multicastauthority.rs index fe7ad73de..ba3f21221 100644 --- a/network-hypervisor/src/vl2/multicastauthority.rs +++ b/network-hypervisor/src/vl2/multicastauthority.rs @@ -42,7 +42,7 @@ impl MulticastAuthority { } /// Call for VL2_MULTICAST_LIKE packets. - pub fn handle_vl2_multicast_like bool>( + pub fn handle_vl2_multicast_like bool>( &self, auth: Authenticator, time_ticks: i64, @@ -53,12 +53,12 @@ impl MulticastAuthority { let mut subscriptions = RMaybeWLockGuard::new_read(&self.subscriptions); while (cursor + 8 + 6 + 4) <= payload.len() { - let network_id = NetworkId::from_bytes_fixed(payload.read_bytes_fixed(&mut cursor).unwrap()); - if let Some(network_id) = network_id { + let network_id = NetworkId::from_bytes(payload.read_bytes_fixed::<8>(&mut cursor).unwrap()); + if let Ok(network_id) = network_id { let mac = MAC::from_bytes_fixed(payload.read_bytes_fixed(&mut cursor).unwrap()); if let Some(mac) = mac { - if auth(network_id, &source.identity) { - let sub_key = (network_id, MulticastGroup { mac, adi: payload.read_u32(&mut cursor).unwrap() }); + if auth(&network_id, &source.identity) { + let sub_key = (network_id.clone(), 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.clone(), time_ticks); } else { @@ -79,7 +79,7 @@ impl MulticastAuthority { } /// Call for VL2_MULTICAST_GATHER packets. - pub fn handle_vl2_multicast_gather bool>( + pub fn handle_vl2_multicast_gather bool>( &self, auth: Authenticator, time_ticks: i64, @@ -91,17 +91,17 @@ impl MulticastAuthority { mut cursor: usize, ) -> PacketHandlerResult { if let Some(network_id) = payload - .read_bytes_fixed(&mut cursor) - .map_or(None, |network_id| NetworkId::from_bytes_fixed(network_id)) + .read_bytes_fixed::<8>(&mut cursor) + .map_or(None, |network_id| NetworkId::from_bytes(network_id).ok()) { - if auth(network_id, &source.identity) { + if auth(&network_id, &source.identity) { cursor += 1; // skip flags, currently unused if let Some(mac) = payload.read_bytes_fixed(&mut cursor).map_or(None, |mac| MAC::from_bytes_fixed(mac)) { let mut gathered = Vec::new(); let adi = payload.read_u32(&mut cursor).unwrap_or(0); let subscriptions = self.subscriptions.read().unwrap(); - if let Some(sub) = subscriptions.get(&(network_id, MulticastGroup { mac, adi })) { + if let Some(sub) = subscriptions.get(&(network_id.clone(), MulticastGroup { mac, adi })) { let sub = sub.lock().unwrap(); for a in sub.keys() { gathered.push(a.clone()); @@ -115,7 +115,7 @@ impl MulticastAuthority { ok_header.in_re_verb = protocol::message_type::VL2_MULTICAST_GATHER; ok_header.in_re_message_id = message_id.to_be_bytes(); - packet.append_bytes_fixed(&network_id.to_bytes())?; + packet.append_bytes_fixed(&network_id.to_legacy_u64().to_be_bytes())?; packet.append_bytes_fixed(&mac.to_bytes())?; packet.append_u32(adi)?; packet.append_u32(gathered.len() as u32)?; @@ -127,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().legacy_address().as_bytes())?; + packet.append_bytes_fixed(gathered.pop().unwrap().legacy_bytes())?; } Ok(()) diff --git a/network-hypervisor/src/vl2/networkid.rs b/network-hypervisor/src/vl2/networkid.rs index 1dcc262b9..8dec7e386 100644 --- a/network-hypervisor/src/vl2/networkid.rs +++ b/network-hypervisor/src/vl2/networkid.rs @@ -1,113 +1,113 @@ // (c) 2020-2022 ZeroTier, Inc. -- currently proprietary pending actual release and licensing. See LICENSE.md. -pub type NetworkId = u64; +use std::fmt::Debug; +use std::str::FromStr; -//pub struct NetworkId; +use crate::vl1::{Address, PartialAddress}; -/* -#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] -#[repr(transparent)] -pub struct NetworkId(NonZeroU64); +use serde::{Deserialize, Deserializer, Serialize, Serializer}; + +use zerotier_utils::error::InvalidParameterError; +use zerotier_utils::hex; + +#[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub enum NetworkId { + // Legacy network ID consisting of 40-bit partial address and 24-bit network number. + Legacy(u64), + // Full length network ID consisting of 384-bit address and 24-bit network number. + Full(Address, u32), +} impl NetworkId { - #[inline] - 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_legacy_u64(i).is_some() { - return Some(Self(ii)); + pub fn to_bytes(&self) -> Vec { + match self { + Self::Legacy(nwid) => nwid.to_be_bytes().to_vec(), + Self::Full(controller, nw) => { + let mut tmp = [0u8; Address::SIZE_BYTES + 4]; + tmp[..Address::SIZE_BYTES].copy_from_slice(controller.as_bytes()); + tmp[Address::SIZE_BYTES..].copy_from_slice(&nw.to_be_bytes()); + tmp.to_vec() } } - return None; } - #[inline] - pub fn from_controller_and_network_no(controller: Address, network_no: u64) -> Option { - Self::from_u64(controller.to_legacy_u64().wrapping_shl(24) | (network_no & 0xffffff)) - } - - #[inline] - pub fn from_bytes(b: &[u8]) -> Option { - if b.len() >= 8 { - Self::from_bytes_fixed(b[0..8].try_into().unwrap()) + pub fn from_bytes(b: &[u8]) -> Result { + if b.len() == 8 { + Self::from_legacy_u64(u64::from_be_bytes(b.try_into().unwrap())) + } else if b.len() == Address::SIZE_BYTES + 4 { + Ok(Self::Full( + Address::from_bytes(&b[..Address::SIZE_BYTES])?, + u32::from_be_bytes(b[Address::SIZE_BYTES..].try_into().unwrap()), + )) } else { - None + Err(InvalidParameterError("invalid network ID")) } } - #[inline] - pub fn from_bytes_fixed(b: &[u8; 8]) -> Option { - Self::from_u64(u64::from_be_bytes(*b)) + pub fn from_legacy_u64(nwid: u64) -> Result { + let _ = PartialAddress::from_legacy_address_u64(nwid)?; // check validity of address portion + Ok(Self::Legacy(nwid)) } - #[inline] - pub fn to_bytes(&self) -> [u8; 8] { - self.0.get().to_be_bytes() + /// Get the legacy 40-bit partial controller address from this network ID. + pub(crate) fn legacy_controller_address(&self) -> PartialAddress { + match self { + Self::Legacy(nwid) => PartialAddress::from_legacy_address_u64(nwid.wrapping_shr(24)).unwrap(), + Self::Full(controller, _) => PartialAddress::from_bytes(&controller.as_bytes()[..PartialAddress::LEGACY_SIZE_BYTES]).unwrap(), + } } - /// Get the network controller ID for this network, which is the most significant 40 bits. - #[inline] - pub fn network_controller(&self) -> Address { - Address::from_legacy_u64(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) | new_controller.to_legacy_u64().wrapping_shl(24)).unwrap()) - } - - /// Get the 24-bit local network identifier minus the 40-bit controller address portion. - #[inline] - pub fn network_no(&self) -> u32 { - (self.0.get() & 0xffffff) as u32 - } -} - -impl From for u64 { - #[inline(always)] - fn from(v: NetworkId) -> Self { - v.0.get() - } -} - -impl From<&NetworkId> for u64 { - #[inline(always)] - fn from(v: &NetworkId) -> Self { - v.0.get() + /// Convert this into a legacy network ID in u64 form, or return itself if already a legacy ID. + pub(crate) fn to_legacy_u64(&self) -> u64 { + match self { + Self::Legacy(nwid) => *nwid, + Self::Full(controller, nw) => controller.legacy_u64().wrapping_shl(24) | ((*nw & 0xffffff) as u64), + } } } impl ToString for NetworkId { fn to_string(&self) -> String { - let mut v = self.0.get(); - let mut s = String::with_capacity(16); - for _ in 0..16 { - s.push(HEX_CHARS[(v >> 60) as usize] as char); - v <<= 4; + match self { + Self::Legacy(nwid) => hex::to_string_u64(*nwid, false), + Self::Full(controller, nw) => format!("{:08x}@{}", *nw, controller.to_string()), } - s - } -} - -impl Debug for NetworkId { - #[inline] - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(self.to_string().as_str()) } } impl FromStr for NetworkId { - type Err = InvalidFormatError; + type Err = InvalidParameterError; fn from_str(s: &str) -> Result { - NetworkId::from_bytes(hex::from_string(s).as_slice()).map_or_else(|| Err(InvalidFormatError), |a| Ok(a)) + if s.len() == 16 { + Self::from_legacy_u64(hex::from_string_u64(s)) + } else { + let mut fno = 0; + let mut net_no = 0; + let mut controller = None; + for ss in s.split('@') { + if fno == 0 { + net_no = hex::from_string_u64(ss); + } else if fno == 1 { + controller = Some(Address::from_str(ss)?); + } else { + return Err(InvalidParameterError("invalid network ID")); + } + fno += 1; + } + if let Some(controller) = controller { + return Ok(Self::Full(controller, net_no as u32)); + } else { + return Err(InvalidParameterError("invalid network ID")); + } + } } } -impl Hash for NetworkId { +impl Debug for NetworkId { #[inline(always)] - fn hash(&self, state: &mut H) { - state.write_u64(self.0.get()); + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(self.to_string().as_str()) } } @@ -119,7 +119,7 @@ impl Serialize for NetworkId { if serializer.is_human_readable() { serializer.serialize_str(self.to_string().as_str()) } else { - serializer.serialize_bytes(&self.to_bytes()) + serializer.serialize_bytes(self.to_bytes().as_slice()) } } } @@ -130,25 +130,21 @@ impl<'de> serde::de::Visitor<'de> for NetworkIdVisitor { type Value = NetworkId; fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("a ZeroTier network ID") + formatter.write_str("network ID") } fn visit_bytes(self, v: &[u8]) -> Result where E: serde::de::Error, { - if v.len() == 8 { - NetworkId::from_bytes(v).map_or_else(|| Err(E::custom("object too large")), |a| Ok(a)) - } else { - Err(E::custom("object too large")) - } + NetworkId::from_bytes(v).map_err(|_| E::custom("invalid network ID")) } fn visit_str(self, v: &str) -> Result where E: serde::de::Error, { - NetworkId::from_str(v).map_err(|e| E::custom(e.to_string())) + NetworkId::from_str(v).map_err(|_| E::custom("invalid network ID")) } } @@ -164,4 +160,3 @@ impl<'de> Deserialize<'de> for NetworkId { } } } -*/ diff --git a/network-hypervisor/src/vl2/rule.rs b/network-hypervisor/src/vl2/rule.rs index 08b9b9ede..dde2048c2 100644 --- a/network-hypervisor/src/vl2/rule.rs +++ b/network-hypervisor/src/vl2/rule.rs @@ -10,7 +10,7 @@ use zerotier_utils::buffer::{Buffer, OutOfBoundsError}; use zerotier_utils::marshalable::{Marshalable, UnmarshalError}; use crate::protocol; -use crate::vl1::{Address, InetAddress, MAC}; +use crate::vl1::{Address, InetAddress, PartialAddress, MAC}; #[allow(unused)] pub const RULES_ENGINE_REVISION: u8 = 1; @@ -174,16 +174,16 @@ impl Default for RuleValue { pub trait RuleVisitor { fn action_drop(&mut self) -> bool; fn action_accept(&mut self) -> bool; - fn action_tee(&mut self, address: Address, flags: u32, length: u16) -> bool; - fn action_watch(&mut self, address: Address, flags: u32, length: u16) -> bool; - fn action_redirect(&mut self, address: Address, flags: u32, length: u16) -> bool; + fn action_tee(&mut self, address: PartialAddress, flags: u32, length: u16) -> bool; + fn action_watch(&mut self, address: PartialAddress, flags: u32, length: u16) -> bool; + fn action_redirect(&mut self, address: PartialAddress, flags: u32, length: u16) -> bool; fn action_break(&mut self) -> bool; fn action_priority(&mut self, qos_bucket: u8) -> bool; fn invalid_rule(&mut self) -> bool; - fn match_source_zerotier_address(&mut self, not: bool, or: bool, address: Address); - fn match_dest_zerotier_address(&mut self, not: bool, or: bool, address: Address); + fn match_source_zerotier_address(&mut self, not: bool, or: bool, address: PartialAddress); + fn match_dest_zerotier_address(&mut self, not: bool, or: bool, address: PartialAddress); fn match_vlan_id(&mut self, not: bool, or: bool, id: u16); fn match_vlan_pcp(&mut self, not: bool, or: bool, pcp: u8); fn match_vlan_dei(&mut self, not: bool, or: bool, dei: u8); @@ -239,7 +239,7 @@ impl Rule { Self { t: action::TEE, v: RuleValue { - forward: rule_value::Forward { address: address.legacy_address().to_u64(), flags, length }, + forward: rule_value::Forward { address: address.legacy_u64(), flags, length }, }, } } @@ -248,7 +248,7 @@ impl Rule { Self { t: action::TEE, v: RuleValue { - forward: rule_value::Forward { address: address.legacy_address().to_u64(), flags, length }, + forward: rule_value::Forward { address: address.legacy_u64(), flags, length }, }, } } @@ -257,7 +257,7 @@ impl Rule { Self { t: action::TEE, v: RuleValue { - forward: rule_value::Forward { address: address.legacy_address().to_u64(), flags, length }, + forward: rule_value::Forward { address: address.legacy_u64(), 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.legacy_address().to_u64() }, + v: RuleValue { zt: address.legacy_u64() }, } } 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.legacy_address().to_u64() }, + v: RuleValue { zt: address.legacy_u64() }, } } @@ -306,21 +306,21 @@ impl Rule { return v.action_accept(); } action::TEE => { - if let Some(a) = LegacyAddress::from_u64(self.v.forward.address) { + if let Ok(a) = PartialAddress::from_legacy_address_u64(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) = LegacyAddress::from_u64(self.v.forward.address) { + if let Ok(a) = PartialAddress::from_legacy_address_u64(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) = LegacyAddress::from_u64(self.v.forward.address) { + if let Ok(a) = PartialAddress::from_legacy_address_u64(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) = LegacyAddress::from_u64(self.v.zt) { + if let Ok(a) = PartialAddress::from_legacy_address_u64(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) = LegacyAddress::from_u64(self.v.zt) { + if let Ok(a) = PartialAddress::from_legacy_address_u64(self.v.zt) { v.match_dest_zerotier_address(not, or, a); } else { return v.invalid_rule(); @@ -775,13 +775,13 @@ static HR_NAME_TO_RULE_TYPE: phf::Map<&'static str, u8> = phf_map! { #[derive(Default, Serialize, Deserialize)] struct HumanReadableRule<'a> { #[serde(skip_serializing_if = "Option::is_none")] - pub address: Option, + pub address: Option, #[serde(skip_serializing_if = "Option::is_none")] pub flags: Option, #[serde(skip_serializing_if = "Option::is_none")] pub length: Option, #[serde(skip_serializing_if = "Option::is_none")] - pub zt: Option, + pub zt: Option, #[serde(skip_serializing_if = "Option::is_none")] pub vlanId: Option, #[serde(skip_serializing_if = "Option::is_none")] @@ -837,7 +837,7 @@ impl<'a> HumanReadableRule<'a> { unsafe { match *t { action::TEE | action::WATCH | action::REDIRECT => { - r.v.forward.address = self.address.as_ref()?.to_u64(); + r.v.forward.address = self.address.as_ref()?.legacy_u64(); 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.as_ref()?.to_u64(); + r.v.zt = self.address.as_ref()?.legacy_u64(); } match_cond::VLAN_ID => { r.v.vlan_id = self.vlanId?; @@ -982,7 +982,7 @@ impl<'a> RuleVisitor for MakeHumanReadable<'a> { } #[inline(always)] - fn action_tee(&mut self, address: LegacyAddress, flags: u32, length: u16) -> bool { + fn action_tee(&mut self, address: PartialAddress, flags: u32, length: u16) -> bool { self.0._type = "ACTION_TEE"; let _ = self.0.address.insert(address); let _ = self.0.flags.insert(flags); @@ -991,7 +991,7 @@ impl<'a> RuleVisitor for MakeHumanReadable<'a> { } #[inline(always)] - fn action_watch(&mut self, address: LegacyAddress, flags: u32, length: u16) -> bool { + fn action_watch(&mut self, address: PartialAddress, flags: u32, length: u16) -> bool { self.0._type = "ACTION_WATCH"; let _ = self.0.address.insert(address); let _ = self.0.flags.insert(flags); @@ -1000,7 +1000,7 @@ impl<'a> RuleVisitor for MakeHumanReadable<'a> { } #[inline(always)] - fn action_redirect(&mut self, address: LegacyAddress, flags: u32, length: u16) -> bool { + fn action_redirect(&mut self, address: PartialAddress, flags: u32, length: u16) -> bool { self.0._type = "ACTION_REDIRECT"; let _ = self.0.address.insert(address); let _ = self.0.flags.insert(flags); @@ -1027,13 +1027,13 @@ impl<'a> RuleVisitor for MakeHumanReadable<'a> { } #[inline(always)] - fn match_source_zerotier_address(&mut self, not: bool, or: bool, address: LegacyAddress) { + fn match_source_zerotier_address(&mut self, not: bool, or: bool, address: PartialAddress) { let _ = self.0.zt.insert(address); self.do_cond("MATCH_SOURCE_ZEROTIER_ADDRESS", not, or); } #[inline(always)] - fn match_dest_zerotier_address(&mut self, not: bool, or: bool, address: LegacyAddress) { + fn match_dest_zerotier_address(&mut self, not: bool, or: bool, address: PartialAddress) { let _ = self.0.zt.insert(address); self.do_cond("MATCH_DEST_ZEROTIER_ADDRESS", not, or); } @@ -1217,19 +1217,19 @@ impl RuleVisitor for RuleStringer { } #[inline(always)] - fn action_tee(&mut self, address: LegacyAddress, flags: u32, length: u16) -> bool { + fn action_tee(&mut self, address: PartialAddress, flags: u32, length: u16) -> bool { self.0 = format!("ACTION_TEE({}, {}, {})", address.to_string(), flags, length); true } #[inline(always)] - fn action_watch(&mut self, address: LegacyAddress, flags: u32, length: u16) -> bool { + fn action_watch(&mut self, address: PartialAddress, flags: u32, length: u16) -> bool { self.0 = format!("ACTION_WATCH({}, {}, {})", address.to_string(), flags, length); true } #[inline(always)] - fn action_redirect(&mut self, address: LegacyAddress, flags: u32, length: u16) -> bool { + fn action_redirect(&mut self, address: PartialAddress, flags: u32, length: u16) -> bool { self.0 = format!("ACTION_REDIRECT({}, {}, {})", address.to_string(), flags, length); true } @@ -1253,7 +1253,7 @@ impl RuleVisitor for RuleStringer { } #[inline(always)] - fn match_source_zerotier_address(&mut self, not: bool, or: bool, address: LegacyAddress) { + fn match_source_zerotier_address(&mut self, not: bool, or: bool, address: PartialAddress) { self.0 = format!( "MATCH_SOURCE_ZEROTIER_ADDRESS({}{}{})", if or { @@ -1271,7 +1271,7 @@ impl RuleVisitor for RuleStringer { } #[inline(always)] - fn match_dest_zerotier_address(&mut self, not: bool, or: bool, address: LegacyAddress) { + fn match_dest_zerotier_address(&mut self, not: bool, or: bool, address: PartialAddress) { self.0 = format!( "MATCH_DEST_ZEROTIER_ADDRESS({}{}{})", if or { diff --git a/network-hypervisor/src/vl2/v1/certificateofmembership.rs b/network-hypervisor/src/vl2/v1/certificateofmembership.rs index f9df867b0..8a294d5fa 100644 --- a/network-hypervisor/src/vl2/v1/certificateofmembership.rs +++ b/network-hypervisor/src/vl2/v1/certificateofmembership.rs @@ -1,7 +1,7 @@ use std::io::Write; use crate::vl1::identity::{Identity, IdentitySecret}; -use crate::vl1::LegacyAddress; +use crate::vl1::PartialAddress; use crate::vl2::NetworkId; use serde::{Deserialize, Serialize}; @@ -24,10 +24,10 @@ use zerotier_utils::memory; /// certificate. #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct CertificateOfMembership { - pub network_id: NetworkId, + pub network_id: u64, // 64-bit legacy network ID pub timestamp: i64, pub max_delta: u64, - pub issued_to: LegacyAddress, + pub issued_to: u64, // 40-bit legacy address pub issued_to_fingerprint: Blob<32>, pub signature: ArrayVec, } @@ -35,12 +35,12 @@ 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: &IdentitySecret, network_id: NetworkId, issued_to: &Identity, timestamp: i64, max_delta: u64) -> Self { + pub fn new(issuer: &IdentitySecret, network_id: &NetworkId, issued_to: &Identity, timestamp: i64, max_delta: u64) -> Self { let mut com = CertificateOfMembership { - network_id, + network_id: network_id.to_legacy_u64(), timestamp, max_delta, - issued_to: issued_to.address.legacy_address(), + issued_to: issued_to.address.legacy_u64(), issued_to_fingerprint: Blob::default(), signature: ArrayVec::new(), }; @@ -59,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] = self.issued_to.to_u64().to_be(); + q[7] = self.issued_to.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) @@ -86,20 +86,20 @@ 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.legacy_address().as_bytes()); + v1_signee_hasher.update(issued_to.address.legacy_bytes()); v1_signee_hasher.update(&issued_to.x25519.ecdh); v1_signee_hasher.update(&issued_to.x25519.eddsa); (&v1_signee_hasher.finish()[..32]).try_into().unwrap() } /// Get this certificate of membership in byte encoded format. - pub fn to_bytes(&self, controller_address: LegacyAddress) -> ArrayVec { + pub fn to_bytes(&self, controller_address: PartialAddress) -> ArrayVec { let mut v = ArrayVec::new(); v.push(1); // version byte from v1 protocol 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.as_bytes()); + let _ = v.write_all(controller_address.legacy_bytes()); let _ = v.write_all(self.signature.as_bytes()); v } @@ -152,10 +152,10 @@ impl CertificateOfMembership { b = &b[5..]; // skip issuer address which is always the controller Ok(Self { - network_id: NetworkId::from_u64(network_id).ok_or(InvalidParameterError("invalid network ID"))?, + network_id: NetworkId::from_legacy_u64(network_id)?.to_legacy_u64(), timestamp, max_delta, - issued_to: LegacyAddress::from_u64(issued_to).ok_or(InvalidParameterError("invalid issued to address"))?, + issued_to, 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 42b906968..a26ec5ae3 100644 --- a/network-hypervisor/src/vl2/v1/certificateofownership.rs +++ b/network-hypervisor/src/vl2/v1/certificateofownership.rs @@ -2,7 +2,7 @@ use std::collections::HashSet; use std::io::Write; use crate::vl1::identity::{Identity, IdentitySecret}; -use crate::vl1::{InetAddress, LegacyAddress, MAC}; +use crate::vl1::{Address, InetAddress, PartialAddress, MAC}; use crate::vl2::NetworkId; use serde::{Deserialize, Serialize}; @@ -30,21 +30,21 @@ impl Thing { #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct CertificateOfOwnership { - pub network_id: NetworkId, + pub network_id: u64, // legacy 64-bit network ID pub timestamp: i64, pub things: HashSet, - pub issued_to: LegacyAddress, + pub issued_to: u64, // legacy 40-bit address pub signature: ArrayVec, } impl CertificateOfOwnership { /// Create a new empty and unsigned certificate. - pub fn new(network_id: NetworkId, timestamp: i64, issued_to: LegacyAddress) -> Self { + pub fn new(network_id: &NetworkId, timestamp: i64, issued_to: &Address) -> Self { Self { - network_id, + network_id: network_id.to_legacy_u64(), timestamp, things: HashSet::with_capacity(4), - issued_to, + issued_to: issued_to.legacy_u64(), signature: ArrayVec::new(), } } @@ -63,7 +63,7 @@ impl CertificateOfOwnership { let _ = self.things.insert(Thing::Mac(mac)); } - fn internal_to_bytes(&self, for_sign: bool, signed_by: LegacyAddress) -> Option> { + fn internal_to_bytes(&self, for_sign: bool, signed_by: &Address) -> Option> { if self.things.len() > 0xffff { return None; } @@ -71,7 +71,7 @@ impl CertificateOfOwnership { if for_sign { let _ = v.write_all(&[0x7fu8; 8]); } - let _ = v.write_all(&self.network_id.to_bytes()); + let _ = v.write_all(&self.network_id.to_be_bytes()); let _ = v.write_all(&self.timestamp.to_be_bytes()); let _ = v.write_all(&[0u8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]); // obsolete flags and ID fields let _ = v.write_all(&(self.things.len() as u16).to_be_bytes()); @@ -94,8 +94,8 @@ impl CertificateOfOwnership { } } } - let _ = v.write_all(self.issued_to.as_bytes()); - let _ = v.write_all(signed_by.as_bytes()); + let _ = v.write_all(&self.issued_to.to_be_bytes()[3..8]); + let _ = v.write_all(signed_by.legacy_bytes()); if for_sign { v.push(0); v.push(0); @@ -112,7 +112,7 @@ impl CertificateOfOwnership { } #[inline(always)] - pub fn to_bytes(&self, signed_by: LegacyAddress) -> Option> { + pub fn to_bytes(&self, signed_by: &Address) -> Option> { self.internal_to_bytes(false, signed_by) } @@ -153,10 +153,10 @@ impl CertificateOfOwnership { } Ok(( Self { - network_id: NetworkId::from_u64(network_id).ok_or(InvalidParameterError("invalid network ID"))?, + network_id: NetworkId::from_legacy_u64(network_id)?.to_legacy_u64(), timestamp, things, - issued_to: LegacyAddress::from_bytes(&b[..5]).ok_or(InvalidParameterError("invalid address"))?, + issued_to: PartialAddress::from_bytes(&b[..5])?.legacy_u64(), signature: { let mut s = ArrayVec::new(); s.push_slice(&b[13..109]); @@ -168,8 +168,8 @@ impl CertificateOfOwnership { } /// Sign certificate of ownership for use by V1 nodes. - pub fn sign(&mut self, issuer_address: LegacyAddress, issuer: &IdentitySecret, issued_to: &Identity) -> bool { - self.issued_to = issued_to.address.legacy_address(); + pub fn sign(&mut self, issuer_address: &Address, issuer: &IdentitySecret, issued_to: &Identity) -> bool { + self.issued_to = issued_to.address.legacy_u64(); if let Some(to_sign) = self.internal_to_bytes(true, issuer_address) { self.signature = issuer.sign(&to_sign.as_slice()); return true; diff --git a/network-hypervisor/src/vl2/v1/networkconfig.rs b/network-hypervisor/src/vl2/v1/networkconfig.rs index aa39e6ecf..d68021d04 100644 --- a/network-hypervisor/src/vl2/v1/networkconfig.rs +++ b/network-hypervisor/src/vl2/v1/networkconfig.rs @@ -175,7 +175,7 @@ impl NetworkConfig { proto_v1_field_name::network_config::CERTIFICATE_OF_MEMBERSHIP, v1cred .certificate_of_membership - .to_bytes(self.network_id.network_controller().legacy_address()) + .to_bytes(self.network_id.legacy_controller_address()) .as_bytes() .to_vec(), ); @@ -183,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.legacy_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); } @@ -191,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.legacy_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); } diff --git a/network-hypervisor/src/vl2/v1/revocation.rs b/network-hypervisor/src/vl2/v1/revocation.rs index 7791f88e9..124e0ebe2 100644 --- a/network-hypervisor/src/vl2/v1/revocation.rs +++ b/network-hypervisor/src/vl2/v1/revocation.rs @@ -5,7 +5,7 @@ use zerotier_utils::arrayvec::ArrayVec; use serde::{Deserialize, Serialize}; use crate::vl1::identity::IdentitySecret; -use crate::vl1::LegacyAddress; +use crate::vl1::Address; use crate::vl2::v1::CredentialType; use crate::vl2::NetworkId; @@ -14,8 +14,8 @@ use crate::vl2::NetworkId; pub struct Revocation { pub network_id: NetworkId, pub threshold: i64, - pub target: LegacyAddress, - pub issued_to: LegacyAddress, + pub target: u64, // legacy 40-bit address + pub issued_to: u64, // legacy 40-bit address pub signature: ArrayVec, pub fast_propagate: bool, } @@ -24,17 +24,17 @@ impl Revocation { pub fn new( network_id: NetworkId, threshold: i64, - target: LegacyAddress, - issued_to: LegacyAddress, - signer_address: LegacyAddress, + target: &Address, + issued_to: &Address, + signer_address: &Address, signer: &IdentitySecret, fast_propagate: bool, ) -> Self { let mut r = Self { network_id, threshold, - target, - issued_to, + target: target.legacy_u64(), + issued_to: issued_to.legacy_u64(), signature: ArrayVec::new(), fast_propagate, }; @@ -42,19 +42,19 @@ impl Revocation { r } - fn internal_to_bytes(&self, for_sign: bool, signed_by: LegacyAddress) -> 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) ^ (self.target.to_u64() as u32)).to_be_bytes()); // ID only used in V1, arbitrary + let _ = v.write_all(&((self.threshold as u32) ^ (self.target as u32)).to_be_bytes()); // ID is 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.as_bytes()); + let _ = v.write_all(&self.target.to_be_bytes()[3..8]); let _ = v.write_all(signed_by.as_bytes()); v.push(CredentialType::CertificateOfMembership as u8); @@ -71,7 +71,7 @@ impl Revocation { } #[inline(always)] - pub fn v1_proto_to_bytes(&self, controller_address: LegacyAddress) -> 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 3fae4f496..c8c5acf2a 100644 --- a/network-hypervisor/src/vl2/v1/tag.rs +++ b/network-hypervisor/src/vl2/v1/tag.rs @@ -1,7 +1,7 @@ use std::io::Write; use crate::vl1::identity::{Identity, IdentitySecret}; -use crate::vl1::LegacyAddress; +use crate::vl1::{Address, PartialAddress}; use crate::vl2::NetworkId; use serde::{Deserialize, Serialize}; @@ -12,9 +12,9 @@ use zerotier_utils::error::InvalidParameterError; #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct Tag { - pub network_id: NetworkId, + pub network_id: u64, // legacy 64-bit network ID pub timestamp: i64, - pub issued_to: LegacyAddress, + pub issued_to: u64, // legacy 40-bit address pub id: u32, pub value: u32, pub signature: Blob<96>, @@ -24,16 +24,16 @@ impl Tag { pub fn new( id: u32, value: u32, - issuer_address: LegacyAddress, + issuer_address: &Address, issuer: &IdentitySecret, - network_id: NetworkId, + network_id: &NetworkId, issued_to: &Identity, timestamp: i64, ) -> Self { let mut tag = Self { - network_id, + network_id: network_id.to_legacy_u64(), timestamp, - issued_to: issued_to.address.legacy_address(), + issued_to: issued_to.address.legacy_u64(), id, value, signature: Blob::default(), @@ -43,17 +43,17 @@ impl Tag { tag } - fn internal_to_bytes(&self, for_sign: bool, signed_by: LegacyAddress) -> 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(&self.network_id.to_bytes()); + let _ = v.write_all(&self.network_id.to_be_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.as_bytes()); - let _ = v.write_all(signed_by.as_bytes()); + let _ = v.write_all(&self.issued_to.to_be_bytes()[3..8]); + let _ = v.write_all(signed_by.legacy_bytes()); if !for_sign { v.push(1); v.push(0); @@ -69,7 +69,7 @@ impl Tag { } #[inline(always)] - pub fn to_bytes(&self, signed_by: LegacyAddress) -> ArrayVec { + pub fn to_bytes(&self, signed_by: &Address) -> ArrayVec { self.internal_to_bytes(false, signed_by) } @@ -80,9 +80,9 @@ impl Tag { } Ok(( Self { - network_id: NetworkId::from_bytes(&b[0..8]).ok_or(InvalidParameterError("invalid network ID"))?, + network_id: NetworkId::from_bytes(&b[0..8])?.to_legacy_u64(), timestamp: i64::from_be_bytes(b[8..16].try_into().unwrap()), - issued_to: LegacyAddress::from_bytes(&b[24..29]).ok_or(InvalidParameterError("invalid address"))?, + issued_to: PartialAddress::from_bytes(&b[24..29])?.legacy_u64(), 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/src/base24.rs b/utils/src/base24.rs index f75c0dfe7..3ed2bce72 100644 --- a/utils/src/base24.rs +++ b/utils/src/base24.rs @@ -10,10 +10,14 @@ use std::io::Write; use crate::error::InvalidParameterError; -// All unambiguous letters, thus easy to type on the alphabetic keyboards on phones without extra shift taps. -const BASE24_ALPHABET: [u8; 24] = *(b"abcdefghjkmnopqrstuvwxyz"); // avoids 'i' and 'l' +/// All unambiguous letters, thus easy to type on the alphabetic keyboards on phones without extra shift taps. +/// The letters 'l' and 'v' are skipped. +const BASE24_ALPHABET: [u8; 24] = [ + b'a', b'b', b'c', b'd', b'e', b'f', b'g', b'h', b'i', b'j', b'k', b'm', b'n', b'o', b'p', b'q', b'r', b's', b't', b'v', b'w', b'x', b'y', b'z', +]; +/// Reverse table for BASE24 alphabet, indexed relative to 'a' or 'A'. const BASE24_ALPHABET_INV: [u8; 26] = [ - 0, 1, 2, 3, 4, 5, 6, 7, 255, 8, 9, 255, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 255, 11, 12, 13, 14, 15, 16, 17, 18, 255, 19, 20, 21, 22, 23, ]; /// Encode a byte slice into base24 ASCII format (no padding) @@ -62,8 +66,8 @@ fn decode_up_to_u32(s: &[u8]) -> Result { } /// Decode a base24 ASCII slice into bytes (no padding, length determines output length) -pub fn decode_into(s: &str, b: &mut Vec) -> Result<(), InvalidParameterError> { - let mut s = s.as_bytes(); +pub fn decode_into(s: &[u8], b: &mut Vec) -> Result<(), InvalidParameterError> { + let mut s = s.as_ref(); while s.len() >= 7 { let _ = b.write_all(&decode_up_to_u32(&s[..7])?.to_le_bytes()); @@ -84,6 +88,18 @@ pub fn decode_into(s: &str, b: &mut Vec) -> Result<(), InvalidParameterError return Ok(()); } +pub fn encode(b: &[u8]) -> String { + let mut tmp = String::with_capacity(((b.len() / 4) * 7) + 2); + encode_into(b, &mut tmp); + tmp +} + +pub fn decode(s: &[u8]) -> Result, InvalidParameterError> { + let mut tmp = Vec::with_capacity(((s.len() / 7) * 4) + 2); + decode_into(s, &mut tmp)?; + Ok(tmp) +} + #[cfg(test)] mod tests { use super::*; @@ -99,7 +115,7 @@ mod tests { encode_into(&tmp[..i], &mut s); //println!("{}", s); v.clear(); - decode_into(s.as_str(), &mut v).expect("decode error"); + decode_into(s.as_str().as_bytes(), &mut v).expect("decode error"); assert!(v.as_slice().eq(&tmp[..i])); } for b in tmp.iter_mut() { diff --git a/utils/src/hex.rs b/utils/src/hex.rs index d9438e0c9..e563c47fe 100644 --- a/utils/src/hex.rs +++ b/utils/src/hex.rs @@ -78,6 +78,38 @@ pub fn from_string(s: &str) -> Vec { b } +pub fn from_string_u64(s: &str) -> u64 { + let mut n = 0u64; + let mut byte = 0_u8; + let mut have_8: bool = false; + for cc in s.as_bytes() { + let c = *cc; + if c >= 48 && c <= 57 { + byte = (byte.wrapping_shl(4)) | (c - 48); + if have_8 { + n = n.wrapping_shl(8); + n |= byte as u64; + } + have_8 = !have_8; + } else if c >= 65 && c <= 70 { + byte = (byte.wrapping_shl(4)) | (c - 55); + if have_8 { + n = n.wrapping_shl(8); + n |= byte as u64; + } + have_8 = !have_8; + } else if c >= 97 && c <= 102 { + byte = (byte.wrapping_shl(4)) | (c - 87); + if have_8 { + n = n.wrapping_shl(8); + n |= byte as u64; + } + have_8 = !have_8; + } + } + n +} + /// Encode bytes from 'b' into hex characters in 'dest' and return the number of hex characters written. /// This will panic if the destination slice is smaller than twice the length of the source. pub fn to_hex_bytes(b: &[u8], dest: &mut [u8]) -> usize {