Tetanus adam (#1906)

* Move some stuff around in prep for a VL2 rework and identity rework.

* Mix ephemeral keys into "h"

* More topology stuff for VL2.

* Simplify key queue, fix macOS issues with bindings, and no need to cache PSK forever.

* Some more merge fixes.

* A bunch of ZSSP cleanup and optimization. Runs a bit faster now.
This commit is contained in:
Adam Ierymenko 2023-03-10 17:03:22 -05:00 committed by GitHub
parent f2e2944658
commit f66a2a7ef9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 610 additions and 407 deletions

View file

@ -12,8 +12,9 @@ use zerotier_network_hypervisor::protocol::{PacketBuffer, DEFAULT_MULTICAST_LIMI
use zerotier_network_hypervisor::vl1::*;
use zerotier_network_hypervisor::vl2;
use zerotier_network_hypervisor::vl2::multicastauthority::MulticastAuthority;
use zerotier_network_hypervisor::vl2::networkconfig::*;
use zerotier_network_hypervisor::vl2::{NetworkId, Revocation};
use zerotier_network_hypervisor::vl2::v1::networkconfig::*;
use zerotier_network_hypervisor::vl2::v1::Revocation;
use zerotier_network_hypervisor::vl2::NetworkId;
use zerotier_utils::blob::Blob;
use zerotier_utils::buffer::OutOfBoundsError;
use zerotier_utils::error::InvalidParameterError;

View file

@ -11,7 +11,7 @@ use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use zerotier_network_hypervisor::vl1::{Address, Endpoint};
use zerotier_network_hypervisor::vl2::networkconfig::NetworkConfig;
use zerotier_network_hypervisor::vl2::v1::networkconfig::NetworkConfig;
use zerotier_network_hypervisor::vl2::NetworkId;
use zerotier_utils::blob::Blob;

View file

@ -6,9 +6,8 @@ use std::hash::Hash;
use serde::{Deserialize, Serialize};
use zerotier_network_hypervisor::vl1::InetAddress;
use zerotier_network_hypervisor::vl2::networkconfig::IpRoute;
use zerotier_network_hypervisor::vl2::rule::Rule;
use zerotier_network_hypervisor::vl2::NetworkId;
use zerotier_network_hypervisor::vl2::{IpRoute, NetworkId};
use crate::database::Database;
use crate::model::Member;

View file

@ -13,9 +13,8 @@ use zerotier_crypto::secure_eq;
use zerotier_crypto::typestate::Valid;
use zerotier_network_hypervisor::vl1::{Address, Identity, InetAddress};
use zerotier_network_hypervisor::vl2::networkconfig::IpRoute;
use zerotier_network_hypervisor::vl2::rule::Rule;
use zerotier_network_hypervisor::vl2::NetworkId;
use zerotier_network_hypervisor::vl2::{IpRoute, NetworkId};
use zerotier_utils::futures_util::{Stream, StreamExt};
use zerotier_utils::tokio;

View file

@ -3,7 +3,7 @@
// MacOS implementation of AES primitives since CommonCrypto seems to be faster than OpenSSL, especially on ARM64.
use std::os::raw::{c_int, c_void};
use std::ptr::{null, null_mut};
use std::sync::Mutex;
use std::sync::atomic::AtomicPtr;
use crate::secret::Secret;
use crate::secure_eq;
@ -172,14 +172,26 @@ impl AesGcm<false> {
}
}
pub struct Aes(Mutex<*mut c_void>, Mutex<*mut c_void>);
pub struct Aes(AtomicPtr<c_void>, AtomicPtr<c_void>);
impl Drop for Aes {
#[inline(always)]
fn drop(&mut self) {
unsafe {
CCCryptorRelease(*self.0.lock().unwrap());
CCCryptorRelease(*self.1.lock().unwrap());
loop {
let p = self.0.load(std::sync::atomic::Ordering::Acquire);
if !p.is_null() {
CCCryptorRelease(p);
break;
}
}
loop {
let p = self.1.load(std::sync::atomic::Ordering::Acquire);
if !p.is_null() {
CCCryptorRelease(p);
break;
}
}
}
}
}
@ -191,7 +203,7 @@ impl Aes {
KEY_SIZE == 32 || KEY_SIZE == 24 || KEY_SIZE == 16,
"AES supports 128, 192, or 256 bits keys"
);
let aes: Self = std::mem::zeroed();
let (mut p0, mut p1) = (null_mut(), null_mut());
assert_eq!(
CCCryptorCreateWithMode(
kCCEncrypt,
@ -205,7 +217,7 @@ impl Aes {
0,
0,
kCCOptionECBMode,
&mut *aes.0.lock().unwrap()
&mut p0,
),
0
);
@ -222,11 +234,11 @@ impl Aes {
0,
0,
kCCOptionECBMode,
&mut *aes.1.lock().unwrap()
&mut p1,
),
0
);
aes
Self(AtomicPtr::new(p0), AtomicPtr::new(p1))
}
}
@ -235,8 +247,16 @@ impl Aes {
assert_eq!(data.len(), 16);
unsafe {
let mut data_out_written = 0;
let e = self.0.lock().unwrap();
CCCryptorUpdate(*e, data.as_ptr().cast(), 16, data.as_mut_ptr().cast(), 16, &mut data_out_written);
loop {
let p = self.0.load(std::sync::atomic::Ordering::Acquire);
if !p.is_null() {
CCCryptorUpdate(p, data.as_ptr().cast(), 16, data.as_mut_ptr().cast(), 16, &mut data_out_written);
self.0.store(p, std::sync::atomic::Ordering::Release);
break;
} else {
std::thread::yield_now();
}
}
}
}
@ -245,8 +265,16 @@ impl Aes {
assert_eq!(data.len(), 16);
unsafe {
let mut data_out_written = 0;
let d = self.1.lock().unwrap();
CCCryptorUpdate(*d, data.as_ptr().cast(), 16, data.as_mut_ptr().cast(), 16, &mut data_out_written);
loop {
let p = self.1.load(std::sync::atomic::Ordering::Acquire);
if !p.is_null() {
CCCryptorUpdate(p, data.as_ptr().cast(), 16, data.as_mut_ptr().cast(), 16, &mut data_out_written);
self.1.store(p, std::sync::atomic::Ordering::Release);
break;
} else {
std::thread::yield_now();
}
}
}
}
}

View file

@ -125,6 +125,7 @@ impl Aes {
let ptr = data.as_mut_ptr();
unsafe { self.0.update::<true>(data, ptr).unwrap() }
}
/// Do not ever encrypt the same plaintext twice. Make sure data is always different between calls.
#[inline(always)]
pub fn decrypt_block_in_place(&self, data: &mut [u8]) {

View file

@ -109,6 +109,7 @@ impl CipherCtxRef {
}
/// Sets the authentication tag for verification during decryption.
#[allow(unused)]
pub fn set_tag(&self, tag: &[u8]) -> Result<(), ErrorStack> {
unsafe {
cvt(ffi::EVP_CIPHER_CTX_ctrl(

View file

@ -14,10 +14,13 @@ pub mod salsa;
pub mod typestate;
pub mod x25519;
#[cfg(target_os = "macos")]
pub mod aes_fruity;
pub mod aes_openssl;
#[cfg(target_os = "macos")]
pub use aes_fruity as aes;
#[cfg(not(target_os = "macos"))]
pub mod aes_openssl;
#[cfg(not(target_os = "macos"))]
pub use aes_openssl as aes;

View file

@ -0,0 +1,17 @@
use crate::vl1::InetAddress;
use serde::{Deserialize, Serialize};
/// ZeroTier-managed L3 route on a virtual network.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct IpRoute {
pub target: InetAddress,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub via: Option<InetAddress>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub flags: Option<u16>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub metric: Option<u16>,
}

View file

@ -1,16 +1,16 @@
// (c) 2020-2022 ZeroTier, Inc. -- currently proprietary pending actual release and licensing. See LICENSE.md.
mod iproute;
mod multicastgroup;
mod networkid;
mod revocation;
mod switch;
mod topology;
pub mod multicastauthority;
pub mod networkconfig;
pub mod rule;
pub mod v1;
pub use iproute::IpRoute;
pub use multicastgroup::MulticastGroup;
pub use networkid::NetworkId;
pub use revocation::Revocation;
pub use switch::{Switch, SwitchInterface};

View file

@ -0,0 +1,61 @@
use std::borrow::Cow;
use zerotier_utils::blob::Blob;
use zerotier_utils::flatsortedmap::FlatSortedMap;
use serde::{Deserialize, Serialize};
use crate::vl1::identity::IDENTITY_FINGERPRINT_SIZE;
use crate::vl1::inetaddress::InetAddress;
use crate::vl2::rule::Rule;
#[derive(Serialize, Deserialize, Eq, PartialEq, Clone)]
pub struct Member<'a> {
#[serde(skip_serializing_if = "u64_zero")]
#[serde(default)]
pub flags: u64,
#[serde(skip_serializing_if = "cow_str_is_empty")]
#[serde(default)]
pub name: Cow<'a, str>,
}
#[derive(Serialize, Deserialize, Eq, PartialEq, Clone)]
pub struct Topology<'a> {
pub timestamp: i64,
#[serde(skip_serializing_if = "cow_str_is_empty")]
#[serde(default)]
pub name: Cow<'a, str>,
#[serde(skip_serializing_if = "slice_is_empty")]
#[serde(default)]
pub rules: Cow<'a, [Rule]>,
#[serde(skip_serializing_if = "FlatSortedMap::is_empty")]
#[serde(default)]
pub dns_resolvers: FlatSortedMap<'a, Cow<'a, str>, InetAddress>,
#[serde(skip_serializing_if = "FlatSortedMap::is_empty")]
#[serde(default)]
pub dns_names: FlatSortedMap<'a, Cow<'a, str>, InetAddress>,
#[serde(skip_serializing_if = "FlatSortedMap::is_empty")]
#[serde(default)]
pub members: FlatSortedMap<'a, Blob<IDENTITY_FINGERPRINT_SIZE>, Member<'a>>,
}
#[inline(always)]
fn u64_zero(i: &u64) -> bool {
*i == 0
}
#[inline(always)]
fn cow_str_is_empty<'a>(s: &Cow<'a, str>) -> bool {
s.is_empty()
}
#[inline(always)]
fn slice_is_empty<T, S: AsRef<[T]>>(x: &S) -> bool {
x.as_ref().is_empty()
}

View file

@ -1,5 +1,7 @@
mod certificateofmembership;
mod certificateofownership;
pub mod networkconfig;
mod revocation;
mod tag;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
@ -15,4 +17,5 @@ pub enum CredentialType {
pub use certificateofmembership::CertificateOfMembership;
pub use certificateofownership::{CertificateOfOwnership, Thing};
pub use revocation::Revocation;
pub use tag::Tag;

View file

@ -7,6 +7,7 @@ use std::str::FromStr;
use serde::{Deserialize, Serialize};
use crate::vl1::{Address, Identity, InetAddress};
use crate::vl2::iproute::IpRoute;
use crate::vl2::rule::Rule;
use crate::vl2::v1::{CertificateOfMembership, CertificateOfOwnership, Tag};
use crate::vl2::NetworkId;
@ -30,11 +31,6 @@ pub struct NetworkConfig {
#[serde(default)]
pub name: String,
/// A human-readable message for members of this network (V2 only)
#[serde(skip_serializing_if = "String::is_empty")]
#[serde(default)]
pub motd: String,
/// True if network has access control (the default)
pub private: bool,
@ -94,7 +90,6 @@ impl NetworkConfig {
network_id,
issued_to,
name: String::new(),
motd: String::new(),
private: true,
timestamp: 0,
mtu: 0,
@ -436,21 +431,6 @@ pub struct V1Credentials {
pub tags: HashMap<u32, Tag>,
}
/// Statically pushed L3 IP routes included with a network configuration.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct IpRoute {
pub target: InetAddress,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub via: Option<InetAddress>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub flags: Option<u16>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub metric: Option<u16>,
}
impl Marshalable for IpRoute {
const MAX_MARSHAL_SIZE: usize = (InetAddress::MAX_MARSHAL_SIZE * 2) + 2 + 2;

View file

@ -7,6 +7,7 @@
*/
use std::fmt::Debug;
use std::hash::Hash;
use serde::ser::SerializeTuple;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
@ -72,6 +73,27 @@ impl<const L: usize> ToString for Blob<L> {
}
}
impl<const L: usize> PartialOrd for Blob<L> {
#[inline(always)]
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
self.0.partial_cmp(&other.0)
}
}
impl<const L: usize> Ord for Blob<L> {
#[inline(always)]
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.0.cmp(&other.0)
}
}
impl<const L: usize> Hash for Blob<L> {
#[inline(always)]
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.0.hash(state);
}
}
impl<const L: usize> Debug for Blob<L> {
#[inline]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
@ -118,7 +140,7 @@ impl<'de, const L: usize> serde::de::Visitor<'de> for BlobVisitor<L> {
impl<'de, const L: usize> Deserialize<'de> for Blob<L> {
#[inline]
fn deserialize<D>(deserializer: D) -> Result<Blob<L>, D::Error>
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{

View file

@ -0,0 +1,86 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* (c) ZeroTier, Inc.
* https://www.zerotier.com/
*/
use std::borrow::Cow;
use std::iter::{FromIterator, Iterator};
use serde::{Deserialize, Serialize};
/// A simple flat sorted map backed by a vector and binary search.
///
/// This doesn't support gradual adding of keys or removal of keys, but only construction
/// from an iterator of keys and values. It also implements Serialize and Deserialize and
/// is mainly intended for memory and space efficient serializable lookup tables.
///
/// If the iterator supplies more than one key with different values, which of these is
/// included is undefined.
#[derive(Serialize, Deserialize, PartialEq, Eq, Clone)]
#[repr(transparent)]
pub struct FlatSortedMap<'a, K: Eq + Ord + Clone, V: Clone>(Cow<'a, [(K, V)]>);
impl<'a, K: Eq + Ord + Clone, V: Clone> FromIterator<(K, V)> for FlatSortedMap<'a, K, V> {
#[inline]
fn from_iter<T: IntoIterator<Item = (K, V)>>(iter: T) -> Self {
let mut tmp = Vec::from_iter(iter);
tmp.sort_unstable_by(|a, b| a.0.cmp(&b.0));
tmp.dedup_by(|a, b| a.0.eq(&b.0));
Self(Cow::Owned(tmp))
}
}
impl<'a, K: Eq + Ord + Clone, V: Clone> Default for FlatSortedMap<'a, K, V> {
#[inline(always)]
fn default() -> Self {
Self(Cow::Owned(Vec::new()))
}
}
impl<'a, K: Eq + Ord + Clone, V: Clone> FlatSortedMap<'a, K, V> {
#[inline]
pub fn get(&self, k: &K) -> Option<&V> {
if let Ok(idx) = self.0.binary_search_by(|a| a.0.cmp(k)) {
Some(unsafe { &self.0.get_unchecked(idx).1 })
} else {
None
}
}
#[inline]
pub fn contains(&self, k: &K) -> bool {
self.0.binary_search_by(|a| a.0.cmp(k)).is_ok()
}
/// Returns true if this map is valid, meaning that it contains only one of each key and is sorted.
#[inline]
pub fn is_valid(&self) -> bool {
let l = self.0.len();
if l > 1 {
for i in 1..l {
if unsafe { !self.0.get_unchecked(i - 1).0.cmp(&self.0.get_unchecked(i).0).is_lt() } {
return false;
}
}
}
return true;
}
#[inline(always)]
pub fn iter(&self) -> impl Iterator<Item = &(K, V)> {
self.0.iter()
}
#[inline(always)]
pub fn len(&self) -> usize {
self.0.len()
}
#[inline(always)]
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
}

View file

@ -14,6 +14,7 @@ pub mod dictionary;
pub mod error;
#[allow(unused)]
pub mod exitcode;
pub mod flatsortedmap;
pub mod gate;
pub mod gatherarray;
pub mod hex;

View file

@ -46,7 +46,7 @@ fn alice_main(
alice_out: mpsc::SyncSender<Vec<u8>>,
alice_in: mpsc::Receiver<Vec<u8>>,
) {
let context = zssp::Context::<TestApplication>::new(16);
let context = zssp::Context::<TestApplication>::new(16, TEST_MTU);
let mut data_buf = [0u8; 65536];
let mut next_service = ms_monotonic() + 500;
let mut last_ratchet_count = 0;
@ -88,7 +88,6 @@ fn alice_main(
&0,
&mut data_buf,
pkt,
TEST_MTU,
current_time,
) {
Ok(zssp::ReceiveResult::Ok(_)) => {
@ -144,7 +143,6 @@ fn alice_main(
|_, b| {
let _ = alice_out.send(b.to_vec());
},
TEST_MTU,
current_time,
);
}
@ -159,7 +157,7 @@ fn bob_main(
bob_out: mpsc::SyncSender<Vec<u8>>,
bob_in: mpsc::Receiver<Vec<u8>>,
) {
let context = zssp::Context::<TestApplication>::new(16);
let context = zssp::Context::<TestApplication>::new(16, TEST_MTU);
let mut data_buf = [0u8; 65536];
let mut data_buf_2 = [0u8; TEST_MTU];
let mut last_ratchet_count = 0;
@ -186,7 +184,6 @@ fn bob_main(
&0,
&mut data_buf,
pkt,
TEST_MTU,
current_time,
) {
Ok(zssp::ReceiveResult::Ok(_)) => {
@ -246,7 +243,6 @@ fn bob_main(
|_, b| {
let _ = bob_out.send(b.to_vec());
},
TEST_MTU,
current_time,
);
}

View file

@ -10,7 +10,6 @@ use std::fmt::Display;
use std::num::NonZeroU64;
use zerotier_crypto::random;
use zerotier_utils::memory::{array_range, as_byte_array};
/// 48-bit session ID (most significant 16 bits of u64 are unused)
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
@ -25,6 +24,7 @@ impl SessionId {
pub const MAX: u64 = 0xffffffffffff;
/// Create a new session ID, panicing if 'i' is zero or exceeds MAX.
#[inline(always)]
pub fn new(i: u64) -> SessionId {
assert!(i <= Self::MAX);
Self(NonZeroU64::new(i.to_le()).unwrap())
@ -35,22 +35,23 @@ impl SessionId {
Self(NonZeroU64::new(((random::xorshift64_random() % (Self::MAX - 1)) + 1).to_le()).unwrap())
}
pub(crate) fn new_from_bytes(b: &[u8; Self::SIZE]) -> Option<SessionId> {
let mut tmp = [0u8; 8];
#[inline(always)]
pub fn to_bytes(&self) -> [u8; Self::SIZE] {
self.0.get().to_ne_bytes()[..Self::SIZE].try_into().unwrap()
}
#[inline(always)]
pub fn new_from_bytes(b: &[u8]) -> Option<SessionId> {
let mut tmp = 0u64.to_ne_bytes();
tmp[..SESSION_ID_SIZE_BYTES].copy_from_slice(b);
Self::new_from_u64_le(u64::from_ne_bytes(tmp))
NonZeroU64::new(u64::from_ne_bytes(tmp)).map(|i| Self(i))
}
/// Create from a u64 that is already in little-endian byte order.
#[inline(always)]
pub(crate) fn new_from_u64_le(i: u64) -> Option<SessionId> {
NonZeroU64::new(i & Self::MAX.to_le()).map(|i| Self(i))
}
/// Get this session ID as a little-endian byte array.
#[inline(always)]
pub(crate) fn as_bytes(&self) -> &[u8; Self::SIZE] {
array_range::<u8, 8, 0, SESSION_ID_SIZE_BYTES>(as_byte_array(&self.0))
pub fn new_from_array(b: &[u8; Self::SIZE]) -> Option<SessionId> {
let mut tmp = 0u64.to_ne_bytes();
tmp[..SESSION_ID_SIZE_BYTES].copy_from_slice(b);
NonZeroU64::new(u64::from_ne_bytes(tmp)).map(|i| Self(i))
}
}

File diff suppressed because it is too large Load diff