A lot of VL1 work including Node, Peer, etc., and wrap secrets in a container to zero them on drop.

This commit is contained in:
Adam Ierymenko 2021-07-30 21:39:21 -04:00
parent da5dcc9d9b
commit f989690785
No known key found for this signature in database
GPG key ID: C8877CF2D7A5D7F3
28 changed files with 572 additions and 133 deletions

View file

@ -190,6 +190,12 @@ dependencies = [
"cfg-if 1.0.0",
]
[[package]]
name = "itoa"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736"
[[package]]
name = "libc"
version = "0.2.98"
@ -363,6 +369,12 @@ dependencies = [
"bitflags",
]
[[package]]
name = "ryu"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
[[package]]
name = "scopeguard"
version = "1.1.0"
@ -374,6 +386,31 @@ name = "serde"
version = "1.0.126"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec7505abeacaec74ae4778d9d9328fe5a5d04253220a85c4ee022239fc996d03"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.126"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "963a7dbc9895aeac7ac90e74f34a5d5261828f79df35cbed41e10189d3804d43"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.66"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "336b10da19a12ad094b59d870ebde26a45402e5b470add4b5fd03c5048a32127"
dependencies = [
"itoa",
"ryu",
"serde",
]
[[package]]
name = "sha2"
@ -551,6 +588,8 @@ dependencies = [
"lz4_flex",
"parking_lot",
"rand_core",
"serde",
"serde_json",
"urlencoding",
"winapi",
"x25519-dalek",

View file

@ -20,6 +20,8 @@ urlencoding = "^2"
lz4_flex = { version = "^0", features = ["safe-encode", "safe-decode", "checked-decode"] }
dashmap = "^4"
parking_lot = "^0"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
[target."cfg(not(windows))".dependencies]
libc = "^0"

View file

@ -15,8 +15,10 @@ fn hash_int_le(sha: &mut crate::crypto::hash::SHA512, i: u64) {
/// SPACE_COST must be a multiple of 64. This is checked with an assertion.
/// DELTA is usually 3.
pub fn hash<const SPACE_COST: usize, const TIME_COST: usize, const DELTA: usize>(password: &[u8], salt: &[u8]) -> [u8; crate::crypto::hash::SHA512_HASH_SIZE] {
assert_ne!(SPACE_COST, 0);
assert_eq!((SPACE_COST % 64), 0);
debug_assert_ne!(SPACE_COST, 0);
debug_assert_ne!(TIME_COST, 0);
debug_assert_ne!(DELTA, 0);
debug_assert_eq!((SPACE_COST % 64), 0);
let mut buf: [u8; SPACE_COST] = unsafe { MaybeUninit::uninit().assume_init() };
let zero64 = [0_u8; 8];

View file

@ -3,6 +3,9 @@ use std::io::Write;
use ed25519_dalek::Digest;
use crate::crypto::random::SecureRandom;
use crate::crypto::secret::Secret;
pub const C25519_PUBLIC_KEY_SIZE: usize = 32;
pub const C25519_SECRET_KEY_SIZE: usize = 32;
pub const C25519_SHARED_SECRET_SIZE: usize = 32;
@ -15,8 +18,8 @@ pub struct C25519KeyPair(x25519_dalek::StaticSecret, x25519_dalek::PublicKey);
impl C25519KeyPair {
#[inline(always)]
pub fn generate() -> C25519KeyPair {
let sk = x25519_dalek::StaticSecret::new(rand_core::OsRng);
pub fn generate(_transient: bool) -> C25519KeyPair {
let sk = x25519_dalek::StaticSecret::new(SecureRandom::get());
let pk = x25519_dalek::PublicKey::from(&sk);
C25519KeyPair(sk, pk)
}
@ -40,17 +43,17 @@ impl C25519KeyPair {
}
#[inline(always)]
pub fn secret_bytes(&self) -> [u8; C25519_SECRET_KEY_SIZE] {
self.0.to_bytes()
pub fn secret_bytes(&self) -> Secret<{ C25519_SECRET_KEY_SIZE }> {
Secret(self.0.to_bytes())
}
/// Execute ECDH agreement and return a raw (un-hashed) shared secret key.
#[inline(always)]
pub fn agree(&self, their_public: &[u8]) -> [u8; C25519_SHARED_SECRET_SIZE] {
pub fn agree(&self, their_public: &[u8]) -> Secret<{ C25519_SHARED_SECRET_SIZE }> {
let pk: [u8; 32] = their_public.try_into().unwrap();
let pk = x25519_dalek::PublicKey::from(pk);
let sec = self.0.diffie_hellman(&pk);
sec.to_bytes()
Secret(sec.to_bytes())
}
}
@ -59,8 +62,8 @@ pub struct Ed25519KeyPair(ed25519_dalek::Keypair);
impl Ed25519KeyPair {
#[inline(always)]
pub fn generate() -> Ed25519KeyPair {
let mut rng = rand_core::OsRng::default();
pub fn generate(_transient: bool) -> Ed25519KeyPair {
let mut rng = SecureRandom::get();
Ed25519KeyPair(ed25519_dalek::Keypair::generate(&mut rng))
}
@ -88,8 +91,8 @@ impl Ed25519KeyPair {
}
#[inline(always)]
pub fn secret_bytes(&self) -> [u8; ED25519_SECRET_KEY_SIZE] {
self.0.secret.to_bytes()
pub fn secret_bytes(&self) -> Secret<{ ED25519_SECRET_KEY_SIZE }> {
Secret(self.0.secret.to_bytes())
}
#[inline(always)]

View file

@ -1,12 +1,13 @@
use crate::crypto::hash::SHA384;
use crate::crypto::hash::{SHA384, SHA384_HASH_SIZE};
use crate::crypto::secret::Secret;
/// Derive a key using KBKDF prefaced by the bytes 'ZT' for use in ZeroTier.
/// This is a fixed cost key derivation function used to derive sub-keys from a single original
/// shared secret for different uses, such as the K0/K1 in AES-GMAC-SIV.
/// Key must be 384 bits in length.
pub fn zt_kbkdf_hmac_sha384(key: &[u8], label: u8, context: u8, iter: u32) -> [u8; 48] {
debug_assert!(key.len() == 48);
pub fn zt_kbkdf_hmac_sha384(key: &[u8], label: u8, context: u8, iter: u32) -> Secret<{ SHA384_HASH_SIZE }> {
debug_assert!(key.len() == SHA384_HASH_SIZE);
// HMAC'd message is: preface | iteration[4], preface[2], label, 0x00, context, hash size[4]
// See: https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-108.pdf
SHA384::hmac(key, &[(iter >> 24) as u8, (iter >> 16) as u8, (iter >> 8) as u8, iter as u8, b'Z', b'T', label, 0, context, 0, 0, 0x01, 0x80])
Secret(SHA384::hmac(key, &[(iter >> 24) as u8, (iter >> 16) as u8, (iter >> 8) as u8, iter as u8, b'Z', b'T', label, 0, context, 0, 0, 0x01, 0x80]))
}

View file

@ -5,5 +5,25 @@ pub mod salsa;
pub mod poly1305;
pub mod balloon;
pub mod kbkdf;
pub mod random;
pub mod secret;
pub use aes_gmac_siv;
use std::convert::Infallible;
static mut SALT64: u64 = 0;
pub fn init() {
unsafe {
// We always run gcrypt in "FIPS mode," but it doesn't count as fully compliant unless it's a FIPS-certified library.
let _ = gcrypt::init_fips_mode(|_| -> Result<(), Infallible> { Ok(()) });
while SALT64 == 0 {
let mut tmp = 0_u64;
gcrypt::rand::randomize(gcrypt::rand::Level::Strong, &mut *((&mut tmp as *mut u64).cast::<[u8; 8]>()));
SALT64 = tmp;
}
}
}
pub fn salt64() -> u64 { unsafe { SALT64 } }

View file

@ -2,6 +2,7 @@ use std::str::FromStr;
use std::convert::TryInto;
use gcrypt::sexp::SExpression;
use crate::crypto::secret::Secret;
pub const P521_PUBLIC_KEY_SIZE: usize = 132;
pub const P521_SECRET_KEY_SIZE: usize = 66;
@ -67,7 +68,7 @@ pub struct P521KeyPair {
public_key: P521PublicKey,
secret_key_for_ecdsa: SExpression, // secret key as a private-key S-expression
secret_key_for_ecdh: SExpression, // the same secret key as a "data" S-expression for the weird gcrypt ECDH interface
secret_key_bytes: [u8; P521_SECRET_KEY_SIZE],
secret_key_bytes: Secret<{ P521_SECRET_KEY_SIZE }>,
}
impl P521KeyPair {
@ -99,10 +100,10 @@ impl P521KeyPair {
},
secret_key_for_ecdsa: SExpression::from_str(format!("(private-key(ecc(curve nistp521)(q #{}#)(d #{}#)))", crate::util::hex::to_string(pk), crate::util::hex::to_string(sk)).as_str()).unwrap(),
secret_key_for_ecdh: SExpression::from_str(format!("(data(flags raw)(value #{}#))", crate::util::hex::to_string(sk)).as_str()).unwrap(),
secret_key_bytes: [0_u8; P521_SECRET_KEY_SIZE],
secret_key_bytes: Secret::default(),
};
kp.public_key.public_key_bytes[((P521_PUBLIC_KEY_SIZE + 1) - pk.len())..P521_PUBLIC_KEY_SIZE].copy_from_slice(&pk[1..]);
kp.secret_key_bytes[(P521_SECRET_KEY_SIZE - sk.len())..P521_SECRET_KEY_SIZE].copy_from_slice(sk);
kp.secret_key_bytes.0[(P521_SECRET_KEY_SIZE - sk.len())..P521_SECRET_KEY_SIZE].copy_from_slice(sk);
return Some(kp);
}
}
@ -124,7 +125,7 @@ impl P521KeyPair {
public_key: public_key.unwrap(),
secret_key_for_ecdsa: SExpression::from_str(format!("(private-key(ecc(curve nistp521)(q #04{}#)(d #{}#)))", crate::util::hex::to_string(public_bytes), crate::util::hex::to_string(secret_bytes)).as_str()).unwrap(),
secret_key_for_ecdh: SExpression::from_str(format!("(data(flags raw)(value #{}#))", crate::util::hex::to_string(secret_bytes)).as_str()).unwrap(),
secret_key_bytes: secret_bytes.try_into().unwrap(),
secret_key_bytes: Secret::from_bytes(secret_bytes),
})
}
@ -143,7 +144,7 @@ impl P521KeyPair {
}
#[inline(always)]
pub fn secret_key_bytes(&self) -> &[u8; P521_SECRET_KEY_SIZE] {
pub fn secret_key_bytes(&self) -> &Secret<{ P521_SECRET_KEY_SIZE }> {
&self.secret_key_bytes
}
@ -168,10 +169,10 @@ impl P521KeyPair {
}
/// Execute ECDH key agreement, returning a raw (un-hashed) shared secret.
pub fn agree(&self, other_public: &P521PublicKey) -> Option<[u8; P521_ECDH_SHARED_SECRET_SIZE]> {
pub fn agree(&self, other_public: &P521PublicKey) -> Option<Secret<{ P521_ECDH_SHARED_SECRET_SIZE }>> {
gcrypt::pkey::encrypt(&other_public.public_key, &self.secret_key_for_ecdh).map_or(None, |k| {
k.find_token("s").map_or(None, |s| s.get_bytes(1).map_or(None, |sb| {
Some(sb[1..].try_into().unwrap())
Some(Secret(sb[1..].try_into().unwrap()))
}))
})
}
@ -248,7 +249,7 @@ mod tests {
panic!("ECDH secrets do not match");
}
let kp3 = P521KeyPair::from_bytes(kp.public_key_bytes(), kp.secret_key_bytes()).unwrap();
let kp3 = P521KeyPair::from_bytes(kp.public_key_bytes(), kp.secret_key_bytes().as_ref()).unwrap();
let sig = kp3.sign(&[3_u8]).unwrap();
if !kp.public_key().verify(&[3_u8], &sig) {
panic!("ECDSA verify failed (from key reconstructed from bytes)");

View file

@ -27,4 +27,10 @@ impl Poly1305 {
let _ = self.0.get_mac(&mut mac);
mac
}
#[inline(always)]
pub fn finish_into(&mut self, mac: &mut [u8]) {
debug_assert_eq!(mac.len(), 16);
let _ = self.0.get_mac(mac);
}
}

View file

@ -0,0 +1,41 @@
use rand_core::{RngCore, Error};
use rand_core::CryptoRng;
use gcrypt::rand::{Level, randomize};
pub struct SecureRandom;
impl SecureRandom {
#[inline(always)]
pub fn get() -> Self {
Self
}
}
impl RngCore for SecureRandom {
#[inline(always)]
fn next_u32(&mut self) -> u32 {
let mut tmp = 0_u32;
randomize(Level::Strong, unsafe { &mut *(&mut tmp as *mut u32).cast::<[u8; 4]>() });
tmp
}
#[inline(always)]
fn next_u64(&mut self) -> u64 {
let mut tmp = 0_u64;
randomize(Level::Strong, unsafe { &mut *(&mut tmp as *mut u64).cast::<[u8; 8]>() });
tmp
}
#[inline(always)]
fn fill_bytes(&mut self, dest: &mut [u8]) {
randomize(Level::Strong, dest);
}
#[inline(always)]
fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> {
randomize(Level::Strong, dest);
Ok(())
}
}
impl CryptoRng for SecureRandom {}

View file

@ -0,0 +1,63 @@
use std::convert::TryInto;
use std::ptr::write_volatile;
/// Container for secrets that clears them on drop.
#[derive(Clone, PartialEq, Eq)]
pub struct Secret<const L: usize>(pub [u8; L]);
impl<const L: usize> Secret<L> {
#[inline(always)]
pub fn new() -> Self {
Self([0_u8; L])
}
/// Copy bytes into secret, will panic if size does not match.
#[inline(always)]
pub fn from_bytes(b: &[u8]) -> Self {
Self(b.try_into().unwrap())
}
}
impl<const L: usize> Drop for Secret<L> {
fn drop(&mut self) {
let p = self.0.as_mut_ptr();
for i in 0..L {
unsafe { write_volatile(p.offset(i as isize), 0_u8) };
}
}
}
impl<const L: usize> Default for Secret<L> {
#[inline(always)]
fn default() -> Self {
Self([0_u8; L])
}
}
impl<const L: usize> AsRef<[u8]> for Secret<L> {
#[inline(always)]
fn as_ref(&self) -> &[u8] {
&self.0
}
}
impl<const L: usize> AsRef<[u8; L]> for Secret<L> {
#[inline(always)]
fn as_ref(&self) -> &[u8; L] {
&self.0
}
}
impl<const L: usize> AsMut<[u8]> for Secret<L> {
#[inline(always)]
fn as_mut(&mut self) -> &mut [u8] {
&mut self.0
}
}
impl<const L: usize> AsMut<[u8; L]> for Secret<L> {
#[inline(always)]
fn as_mut(&mut self) -> &mut [u8; L] {
&mut self.0
}
}

View file

@ -16,3 +16,19 @@ impl Debug for InvalidFormatError {
}
impl Error for InvalidFormatError {}
pub struct InvalidParameterError(pub(crate) &'static str);
impl Display for InvalidParameterError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "InvalidParameterError: {}", self.0)
}
}
impl Debug for InvalidParameterError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "InvalidParameterError: {}", self.0)
}
}
impl Error for InvalidParameterError {}

View file

@ -1,5 +1,5 @@
pub mod hex;
pub(crate) mod pool;
pub mod pool;
pub(crate) const ZEROES: [u8; 64] = [0_u8; 64];
@ -53,3 +53,24 @@ pub(crate) fn integer_load_be_u32(d: &[u8]) -> u32 {
pub(crate) fn integer_load_be_u64(d: &[u8]) -> u64 {
(d[0] as u64) << 56 | (d[1] as u64) << 48 | (d[2] as u64) << 40 | (d[3] as u64) << 32 | (d[4] as u64) << 24 | (d[5] as u64) << 16 | (d[6] as u64) << 8 | (d[7] as u64)
}
/// Mix bits in a 64-bit integer.
/// https://nullprogram.com/blog/2018/07/31/
pub(crate) fn hash64(mut x: u64) -> u64 {
x ^= x.wrapping_shr(30);
x = x.wrapping_mul(0xbf58476d1ce4e5b9);
x ^= x.wrapping_shr(27);
x = x.wrapping_mul(0x94d049bb133111eb);
x ^ x.wrapping_shr(31)
}
/// Mix bits in 32-bit integer.
/// https://nullprogram.com/blog/2018/07/31/
#[inline(always)]
pub(crate) fn hash32(mut x: u32) -> u32 {
x ^= x.wrapping_shr(16);
x = x.wrapping_mul(0x7feb352d);
x ^= x.wrapping_shr(15);
x = x.wrapping_mul(0x846ca68b);
x ^ x.wrapping_shr(16)
}

View file

@ -1,8 +1,8 @@
use std::mem::size_of;
use std::ops::{Deref, DerefMut};
use std::sync::{Arc, Weak};
use parking_lot::Mutex;
use std::mem::size_of;
/// Trait for objects that can be used with Pool.
pub trait Reusable: Default + Sized {
@ -17,12 +17,15 @@ struct PoolEntry<O: Reusable> {
type PoolInner<O> = Mutex<Vec<*mut PoolEntry<O>>>;
/// Container for pooled objects that have been checked out of the pool.
///
/// When this is dropped the object is returned to the pool or if the pool or is
/// dropped if the pool has been dropped. There is also an into_raw() and from_raw()
/// functionality that allows conversion to/from naked pointers to O for
/// interoperation with C/C++ APIs.
///
/// Note that pooled objects are not clonable. If you want to share them use Rc<>
/// or Arc<>.
#[repr(transparent)]
#[derive(Clone)]
pub struct Pooled<O: Reusable>(*mut PoolEntry<O>);
impl<O: Reusable> Pooled<O> {

View file

@ -1,6 +1,8 @@
use std::mem::size_of;
use std::io::Write;
use crate::util::pool::Reusable;
const OVERFLOW_ERR_MSG: &'static str = "overflow";
/// Annotates a type as containing only primitive types like integers and arrays.
@ -21,6 +23,13 @@ impl<const L: usize> Default for Buffer<L> {
}
}
impl<const L: usize> Reusable for Buffer<L> {
#[inline(always)]
fn reset(&mut self) {
self.clear();
}
}
impl<const L: usize> Buffer<L> {
#[inline(always)]
pub fn new() -> Self {
@ -55,7 +64,7 @@ impl<const L: usize> Buffer<L> {
/// Erase contents and zero size.
#[inline(always)]
pub fn reset(&mut self) {
pub fn clear(&mut self) {
self.0 = 0;
self.1.fill(0);
}

View file

@ -1,44 +0,0 @@
// This just defines a ConcurrentMap type, selecting standard locked HashMap for smaller systems
// or DashMap on larger ones where it would be faster. It also defines some wrappers for read
// and write locking that do nothing for DashMap and return a lock guard for RwLock<HashMap<>>.
#[allow(unused_imports)]
use std::sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard};
#[allow(unused_imports)]
use std::collections::HashMap;
#[cfg(any(target_arch = "aarch64", target_arch = "x86_64", target_arch = "powerpc64"))]
use dashmap::DashMap;
#[cfg(not(any(target_arch = "aarch64", target_arch = "x86_64", target_arch = "powerpc64")))]
pub type ConcurrentMap<K, V> = RwLock<HashMap<K, V>>;
#[cfg(any(target_arch = "aarch64", target_arch = "x86_64", target_arch = "powerpc64"))]
pub type ConcurrentMap<K, V> = DashMap<K, V>;
/// Wrapper to get a read lock guard on a concurrent map.
#[cfg(not(any(target_arch = "aarch64", target_arch = "x86_64", target_arch = "powerpc64")))]
#[inline(always)]
pub fn read<K, V>(m: &Arc<ConcurrentMap<K, V>>) -> RwLockReadGuard<HashMap<K, V>> {
m.read().unwrap()
}
/// Wrapper to get a read lock guard on a concurrent map.
#[cfg(any(target_arch = "aarch64", target_arch = "x86_64", target_arch = "powerpc64"))]
#[inline(always)]
pub fn read<K, V>(m: &Arc<ConcurrentMap<K, V>>) -> &ConcurrentMap<K, V> {
m.as_ref()
}
/// Wrapper to get a write lock guard on a concurrent map.
#[cfg(not(any(target_arch = "aarch64", target_arch = "x86_64", target_arch = "powerpc64")))]
#[inline(always)]
pub fn write<K, V>(m: &Arc<ConcurrentMap<K, V>>) -> RwLockWriteGuard<HashMap<K, V>> {
m.write().unwrap()
}
/// Wrapper to get a write lock guard on a concurrent map.
#[cfg(any(target_arch = "aarch64", target_arch = "x86_64", target_arch = "powerpc64"))]
#[inline(always)]
pub fn write<K, V>(m: &Arc<ConcurrentMap<K, V>>) -> &ConcurrentMap<K, V> {
m.as_ref()
}

View file

@ -45,6 +45,13 @@ pub const HEADER_FLAG_FRAGMENTED: u8 = 0x40;
/// Minimum size of a fragment.
pub const FRAGMENT_SIZE_MIN: usize = 16;
/// Maximum allowed number of fragments.
pub const FRAGMENT_COUNT_MAX: usize = 16;
/// Maximum number of fragmented packets in flight from a peer.
/// Usually there should only be one at a time, so this is overkill.
pub const PEER_DEFRAGMENT_MAX_PACKETS_IN_FLIGHT: usize = 4;
/// Verb (inner) flag indicating that the packet's payload (after the verb) is LZ4 compressed.
pub const VERB_FLAG_COMPRESSED: u8 = 0x80;

View file

@ -95,22 +95,18 @@ impl Dictionary {
})
}
#[inline(always)]
pub fn set_str(&mut self, k: &str, v: &str) {
let _ = self.0.insert(String::from(k), v.as_bytes().to_vec());
}
#[inline(always)]
pub fn set_u64(&mut self, k: &str, v: u64) {
let _ = self.0.insert(String::from(k), crate::util::hex::to_vec_u64(v, true));
}
#[inline(always)]
pub fn set_bytes(&mut self, k: &str, v: Vec<u8>) {
let _ = self.0.insert(String::from(k), v);
}
#[inline(always)]
pub fn set_bool(&mut self, k: &str, v: bool) {
let _ = self.0.insert(String::from(k), (if v { [b'1'] } else { [b'0'] }).to_vec());
}

View file

@ -1,7 +1,8 @@
use std::hash::{Hash, Hasher};
use crate::vl1::{Address, MAC};
use crate::vl1::inetaddress::InetAddress;
use crate::vl1::buffer::Buffer;
use std::hash::{Hash, Hasher};
const TYPE_NIL: u8 = 0;
const TYPE_ZEROTIER: u8 = 1;
@ -50,6 +51,7 @@ impl Default for Endpoint {
}
impl Endpoint {
#[inline(always)]
pub fn ep_type(&self) -> Type {
match self {
Endpoint::Nil => Type::Nil,

View file

@ -9,9 +9,10 @@ use crate::vl1::Address;
use crate::vl1::buffer::Buffer;
use crate::crypto::c25519::{C25519_PUBLIC_KEY_SIZE, ED25519_PUBLIC_KEY_SIZE, C25519_SECRET_KEY_SIZE, ED25519_SECRET_KEY_SIZE, C25519KeyPair, Ed25519KeyPair, ED25519_SIGNATURE_SIZE};
use crate::crypto::p521::{P521KeyPair, P521PublicKey, P521_ECDSA_SIGNATURE_SIZE, P521_PUBLIC_KEY_SIZE, P521_SECRET_KEY_SIZE};
use crate::crypto::hash::{SHA384, SHA512, SHA512_HASH_SIZE};
use crate::crypto::hash::{SHA384, SHA512, SHA512_HASH_SIZE, SHA384_HASH_SIZE};
use crate::crypto::balloon;
use crate::crypto::salsa::Salsa;
use crate::crypto::secret::Secret;
use crate::error::InvalidFormatError;
// Memory parameter for V0 address derivation work function.
@ -88,11 +89,11 @@ impl Identity {
panic!("unable to allocate memory for V0 identity generation");
}
let ed25519 = Ed25519KeyPair::generate();
let ed25519 = Ed25519KeyPair::generate(false);
let ed25519_pub_bytes = ed25519.public_bytes();
let mut sha = SHA512::new();
loop {
let c25519 = C25519KeyPair::generate();
let c25519 = C25519KeyPair::generate(false);
let c25519_pub_bytes = c25519.public_bytes();
sha.update(&c25519_pub_bytes);
@ -123,8 +124,8 @@ impl Identity {
}
fn generate_p521() -> Identity {
let c25519 = C25519KeyPair::generate();
let ed25519 = Ed25519KeyPair::generate();
let c25519 = C25519KeyPair::generate(false);
let ed25519 = Ed25519KeyPair::generate(false);
let p521_ecdh = P521KeyPair::generate(false).unwrap();
let p521_ecdsa = P521KeyPair::generate(false).unwrap();
@ -191,11 +192,11 @@ impl Identity {
sha.update((*p521).1.public_key_bytes());
});
self.secrets.as_ref().map(|secrets| {
sha.update(&secrets.c25519.secret_bytes());
sha.update(&secrets.ed25519.secret_bytes());
sha.update(&secrets.c25519.secret_bytes().as_ref());
sha.update(&secrets.ed25519.secret_bytes().as_ref());
secrets.v1.as_ref().map(|p521_secrets| {
sha.update((*p521_secrets).0.secret_key_bytes());
sha.update((*p521_secrets).1.secret_key_bytes());
sha.update((*p521_secrets).0.secret_key_bytes().as_ref());
sha.update((*p521_secrets).1.secret_key_bytes().as_ref());
});
});
sha.finish()
@ -243,14 +244,14 @@ impl Identity {
/// If both keys are type 1, key agreement is done with NIST P-521. Otherwise it's done
/// with Curve25519. None is returned if there is an error such as this identity missing
/// its secrets or a key being invalid.
pub fn agree(&self, other_identity: &Identity) -> Option<[u8; 48]> {
pub fn agree(&self, other_identity: &Identity) -> Option<Secret<48>> {
self.secrets.as_ref().map_or(None, |secrets| {
let c25519_secret = SHA384::hash(&secrets.c25519.agree(&other_identity.c25519));
let c25519_secret = || Secret::<48>(SHA384::hash(&secrets.c25519.agree(&other_identity.c25519).as_ref()));
secrets.v1.as_ref().map_or_else(|| {
Some(c25519_secret)
Some(c25519_secret())
}, |p521_secret| {
other_identity.v1.as_ref().map_or_else(|| {
Some(c25519_secret)
Some(c25519_secret())
}, |other_p521_public| {
p521_secret.0.agree(&other_p521_public.0).map_or(None, |p521_secret| {
//
@ -270,7 +271,7 @@ impl Identity {
// as the stronger of the two algorithms. This should make the FIPS people happy and the
// people who are paranoid about NIST curves happy.
//
Some(SHA384::hmac(&c25519_secret, &p521_secret))
Some(Secret(SHA384::hmac(c25519_secret().as_ref(), p521_secret.as_ref())))
})
})
})
@ -356,10 +357,10 @@ impl Identity {
if secrets.v1.is_some() {
let p521_secrets = secrets.v1.as_ref().unwrap();
buf.append_u8((C25519_SECRET_KEY_SIZE + ED25519_SECRET_KEY_SIZE + P521_SECRET_KEY_SIZE + P521_SECRET_KEY_SIZE) as u8)?;
buf.append_bytes_fixed(&secrets.c25519.secret_bytes())?;
buf.append_bytes_fixed(&secrets.ed25519.secret_bytes())?;
buf.append_bytes_fixed((*p521_secrets).0.secret_key_bytes())?;
buf.append_bytes_fixed((*p521_secrets).1.secret_key_bytes())?;
buf.append_bytes_fixed(&secrets.c25519.secret_bytes().as_ref())?;
buf.append_bytes_fixed(&secrets.ed25519.secret_bytes().as_ref())?;
buf.append_bytes_fixed((*p521_secrets).0.secret_key_bytes().as_ref())?;
buf.append_bytes_fixed((*p521_secrets).1.secret_key_bytes().as_ref())?;
}
} else {
buf.append_u8(0)?; // 0 secret bytes if not adding any
@ -371,8 +372,8 @@ impl Identity {
if include_private && self.secrets.is_some() {
let secrets = self.secrets.as_ref().unwrap();
buf.append_u8((C25519_SECRET_KEY_SIZE + ED25519_SECRET_KEY_SIZE) as u8)?;
buf.append_bytes_fixed(&secrets.c25519.secret_bytes())?;
buf.append_bytes_fixed(&secrets.ed25519.secret_bytes())?;
buf.append_bytes_fixed(&secrets.c25519.secret_bytes().as_ref())?;
buf.append_bytes_fixed(&secrets.ed25519.secret_bytes().as_ref())?;
} else {
buf.append_u8(0)?; // 0 secret bytes if not adding any
}
@ -479,14 +480,14 @@ impl Identity {
pub fn to_secret_string(&self) -> String {
self.secrets.as_ref().map_or_else(|| self.to_string(), |secrets| {
secrets.v1.as_ref().map_or_else(|| {
format!("{}:{}{}", self.to_string(), crate::util::hex::to_string(&secrets.c25519.secret_bytes()), crate::util::hex::to_string(&secrets.ed25519.secret_bytes()))
format!("{}:{}{}", self.to_string(), crate::util::hex::to_string(secrets.c25519.secret_bytes().as_ref()), crate::util::hex::to_string(secrets.ed25519.secret_bytes().as_ref()))
}, |p521_secret| {
let mut secret_key_blob: Vec<u8> = Vec::new();
secret_key_blob.reserve(C25519_SECRET_KEY_SIZE + ED25519_SECRET_KEY_SIZE + P521_SECRET_KEY_SIZE + P521_SECRET_KEY_SIZE);
let _ = secret_key_blob.write_all(&secrets.c25519.secret_bytes());
let _ = secret_key_blob.write_all(&secrets.ed25519.secret_bytes());
let _ = secret_key_blob.write_all(p521_secret.0.secret_key_bytes());
let _ = secret_key_blob.write_all(p521_secret.1.secret_key_bytes());
let _ = secret_key_blob.write_all(&secrets.c25519.secret_bytes().as_ref());
let _ = secret_key_blob.write_all(&secrets.ed25519.secret_bytes().as_ref());
let _ = secret_key_blob.write_all(p521_secret.0.secret_key_bytes().as_ref());
let _ = secret_key_blob.write_all(p521_secret.1.secret_key_bytes().as_ref());
format!("{}:{}", self.to_string(), base64::encode_config(secret_key_blob.as_slice(), base64::URL_SAFE_NO_PAD))
})
})

View file

@ -225,24 +225,6 @@ impl InetAddress {
}
}
/// Fills in the InetAddress specific parts of a path lookup key.
/// This assumes that the key's default contents are zero bits and does not clear unused regions.
pub(crate) fn fill_path_lookup_key(&self, k: &mut [u64; 4]) {
unsafe {
match self.sa.sa_family as u8 {
AF_INET => {
k[1] |= self.sin.sin_port as u64 | 0x40000; // OR because most significant 32 bits contain endpoint info
k[2] = self.sin.sin_addr.s_addr as u64;
}
AF_INET6 => {
k[1] |= self.sin6.sin6_port as u64 | 0x60000; // OR because most significant 32 bits contain endpoint info
copy_nonoverlapping((&(self.sin6.sin6_addr) as *const in6_addr).cast::<u8>(), k.as_mut_ptr().cast::<u8>().offset(16), 16);
}
_ => {}
}
}
}
/// Set the IP port.
#[inline(always)]
pub fn set_port(&mut self, port: u16) {
@ -256,6 +238,24 @@ impl InetAddress {
}
}
#[inline(always)]
pub(crate) fn local_lookup_key(&self) -> u128 {
unsafe {
match self.sa.sa_family as u8 {
AF_INET => {
((self.sin.sin_addr.s_addr as u64).wrapping_shl(16) | self.sin.sin_port as u64) as u128
}
AF_INET6 => {
let mut tmp: [u64; 2] = MaybeUninit::uninit().assume_init();
copy_nonoverlapping((&self.sin6.sin6_addr as *const in6_addr).cast::<u8>(), tmp.as_mut_ptr().cast::<u8>(), 16);
tmp[1] = tmp[1].wrapping_add((self.sin6.sin6_port as u64) ^ crate::crypto::salt64());
(*tmp.as_ptr().cast::<u128>()).wrapping_mul(0x0fc94e3bf4e9ab32866458cd56f5e605)
}
_ => 0
}
}
}
/// Get this IP address's scope as per RFC documents and what is advertised via BGP.
pub fn scope(&self) -> IpScope {
unsafe {

View file

@ -0,0 +1 @@
pub struct Locator;

View file

@ -1,15 +1,14 @@
pub(crate) mod concurrentmap;
pub(crate) mod constants;
pub(crate) mod headers;
pub(crate) mod protocol;
pub(crate) mod buffer;
pub(crate) mod node;
pub(crate) mod path;
pub(crate) mod peer;
pub(crate) mod state;
pub mod constants;
pub mod identity;
pub mod inetaddress;
pub mod endpoint;
pub mod locator;
mod dictionary;
mod address;
@ -20,3 +19,7 @@ pub use mac::MAC;
pub use identity::Identity;
pub use endpoint::Endpoint;
pub use dictionary::Dictionary;
pub use inetaddress::InetAddress;
pub use locator::Locator;
pub use peer::Peer;
pub use path::Path;

View file

@ -1,12 +1,181 @@
use std::sync::Arc;
use std::str::FromStr;
use std::time::Duration;
use std::marker::PhantomData;
use std::hash::Hash;
use crate::vl1::{Address, Identity};
use crate::vl1::concurrentmap::ConcurrentMap;
use crate::crypto::random::SecureRandom;
use crate::error::InvalidParameterError;
use crate::util::pool::{Pool, Pooled};
use crate::vl1::{Address, Identity, Endpoint, Locator};
use crate::vl1::buffer::Buffer;
use crate::vl1::constants::{PACKET_SIZE_MAX, FRAGMENT_COUNT_MAX};
use crate::vl1::path::Path;
use crate::vl1::peer::Peer;
use parking_lot::Mutex;
use dashmap::DashMap;
/// Standard packet buffer type including pool container.
pub type PacketBuffer = Pooled<Buffer<{ PACKET_SIZE_MAX }>>;
/// Callback interface and call context for calls to the node (for VL1).
/// Every non-trivial call takes a reference to this, which it passes all the way through
/// the call stack. This can be used to call back into the caller to send packets, get or
/// store data, report events, etc.
pub trait VL1CallerInterface {
/// Node is up and ready for operation.
fn event_node_is_up(&self);
/// Node is shutting down.
fn event_node_is_down(&self);
/// A root signaled an identity collision.
/// This should cause the external code to shut down this node, delete its identity, and recreate.
fn event_identity_collision(&self);
/// Node has gone online or offline.
fn event_online_status_change(&self, online: bool);
/// A USER_MESSAGE packet was received.
fn event_user_message(&self, source: &Identity, message_type: u64, message: &[u8]);
/// Load this node's identity from the data store.
fn load_identity(&self) -> Option<&[u8]>;
/// Save this node's identity.
/// Note that this is only called on first startup (after up) and after identity_changed.
fn save_identity(&self, id: &Identity, public: &[u8], secret: &[u8]);
/// Load this node's latest locator.
fn load_locator(&self) -> Option<&[u8]>;
/// Save this node's latest locator.
fn save_locator(&self, locator: &[u8]);
/// Load a peer's latest saved state. (A remote peer, not this one.)
fn load_peer(&self, address: Address) -> Option<&[u8]>;
/// Save a peer's state.
/// The state contains the identity, so there's no need to save that separately.
/// It's just supplied for the address and if the external code wants it.
fn save_peer(&self, id: &Identity, peer: &[u8]);
/// Load network configuration.
fn load_network_config(&self, id: u64) -> Option<&[u8]>;
/// Save network configuration.
fn save_network_config(&self, id: u64, config: &[u8]);
/// Called to send a packet over the physical network (virtual -> physical).
///
/// This may return false if the send definitely failed, and may return true if the send
/// succeeded or may have succeeded (in the case of UDP and similar).
///
/// If local socket and/or local interface are None, the sending code should make its
/// own decision about what local socket or interface to use. It may send on a random
/// one, the best fit, or all at once.
///
/// If packet TTL is non-zero it should be used to set the packet TTL for outgoing packets
/// for supported protocols such as UDP, but otherwise it can be ignored. It can also be
/// ignored if the platform does not support setting the TTL.
fn wire_send(&self, endpoint: &Endpoint, local_socket: Option<i64>, local_interface: Option<i64>, data: PacketBuffer, packet_ttl: u8) -> bool;
/// Called to check and see if a physical address should be used for ZeroTier traffic to a node.
fn check_path(&self, id: &Identity, endpoint: &Endpoint, local_socket: Option<i64>, local_interface: Option<i64>) -> bool;
/// Called to look up a path to a known node.
/// If a path is found, this returns a tuple of an endpoint and optional local socket and local
/// interface IDs. If these are None they will be None when this is sent with wire_send.
fn get_path_hints(&self, id: &Identity) -> Option<&[(&Endpoint, Option<i64>, Option<i64>)]>;
/// Called to get the current time in milliseconds from the system monotonically increasing clock.
fn time_ticks(&self) -> i64;
/// Called to get the current time in milliseconds since epoch from the real-time clock.
fn time_clock(&self) -> i64;
}
pub struct Node {
identity: Identity,
paths: ConcurrentMap<[u64; 4], Arc<Path>>,
peers: ConcurrentMap<Address, Arc<Peer>>,
locator: Mutex<Option<Locator>>,
paths_by_inaddr: DashMap<u128, Arc<Path>>,
peers: DashMap<Address, Arc<Peer>>,
peer_vec: Mutex<Vec<Arc<Peer>>>, // for rapid iteration through all peers
buffer_pool: Pool<Buffer<{ PACKET_SIZE_MAX }>>,
secure_prng: SecureRandom,
}
impl Node {
/// Create a new Node.
/// If the auto-generate identity type is not None, a new identity will be generated if
/// no identity is currently stored in the data store.
pub fn new<CI: VL1CallerInterface>(ci: &CI, auto_generate_identity_type: Option<crate::vl1::identity::Type>) -> Result<Self, InvalidParameterError> {
crate::crypto::init(); // make sure this is initialized, okay to call more than once
let id = {
let id_str = ci.load_identity();
if id_str.is_none() {
if auto_generate_identity_type.is_none() {
return Err(InvalidParameterError("no identity found and auto-generate not specified"));
} else {
let id = Identity::generate(auto_generate_identity_type.unwrap());
ci.save_identity(&id, id.to_string().as_bytes(), id.to_secret_string().as_bytes());
id
}
} else {
let id_str = String::from_utf8_lossy(id_str.unwrap());
let id = Identity::from_str(id_str.as_ref());
if id.is_err() {
return Err(InvalidParameterError("invalid identity"));
} else {
id.unwrap()
}
}
};
Ok(Self {
identity: id,
locator: Mutex::new(None),
paths_by_inaddr: DashMap::new(),
peers: DashMap::new(),
peer_vec: Mutex::new(Vec::new()),
buffer_pool: Pool::new(64),
secure_prng: SecureRandom::get(),
})
}
/// Get address, short for .identity().address()
#[inline(always)]
pub fn address(&self) -> Address {
self.identity.address()
}
/// Get identity, which includes secret keys.
#[inline(always)]
pub fn identity(&self) -> &Identity {
&self.identity
}
/// Get a reusable packet buffer.
/// The buffer will automatically be returned to the pool if it is dropped.
#[inline(always)]
pub fn get_packet_buffer(&self) -> PacketBuffer {
self.buffer_pool.get()
}
/// Run background tasks and return desired delay until next call in milliseconds.
/// This should only be called once at a time. It technically won't hurt anything to
/// call concurrently but it will waste CPU cycles.
pub fn do_background_tasks<CI: VL1CallerInterface>(&self, ci: &CI) -> Duration {
Duration::from_millis(1000)
}
/// Called when a packet is received on the physical wire.
pub fn wire_receive<CI: VL1CallerInterface>(&self, ci: &CI, endpoint: &Endpoint, local_socket: i64, local_interface: i64, data: PacketBuffer) {
}
}
unsafe impl Send for Node {}
unsafe impl Sync for Node {}

View file

@ -1 +1,38 @@
pub struct Path;
use std::sync::atomic::{AtomicI64, Ordering};
use crate::vl1::Endpoint;
pub struct Path {
pub(crate) endpoint: Endpoint,
pub(crate) local_socket: i64,
pub(crate) local_interface: i64,
last_send_time_ticks: AtomicI64,
last_receive_time_ticks: AtomicI64,
}
impl Path {
#[inline(always)]
pub fn new(endpoint: Endpoint, local_socket: i64, local_interface: i64) -> Self {
Self {
endpoint,
local_socket,
local_interface,
last_send_time_ticks: AtomicI64::new(0),
last_receive_time_ticks: AtomicI64::new(0),
}
}
#[inline(always)]
pub fn last_send_time_ticks(&self) -> i64 {
self.last_send_time_ticks.load(Ordering::Relaxed)
}
#[inline(always)]
pub fn send_receive_time_ticks(&self) -> i64 {
self.last_receive_time_ticks.load(Ordering::Relaxed)
}
}
unsafe impl Send for Path {}
unsafe impl Sync for Path {}

View file

@ -1 +1,41 @@
pub struct Peer;
use std::sync::Arc;
use std::sync::atomic::{AtomicI64, AtomicU64, AtomicU8};
use crate::vl1::protocol::PacketID;
use crate::vl1::node::PacketBuffer;
use crate::vl1::constants::{FRAGMENT_COUNT_MAX, PEER_DEFRAGMENT_MAX_PACKETS_IN_FLIGHT};
use crate::vl1::{Identity, Path};
use parking_lot::Mutex;
struct FragmentedPacket {
pub id: PacketID,
pub frag: [Option<PacketBuffer>; FRAGMENT_COUNT_MAX],
}
pub struct Peer {
// This peer's identity.
identity: Identity,
// Primary static secret resulting from key agreement with identity.
identity_static_secret: [u8; 48],
// Outgoing packet IV counter used to generate packet IDs to this peer.
packet_iv_counter: AtomicU64,
// Paths sorted in ascending order of quality / preference.
paths: Mutex<Vec<Arc<Path>>>,
// Incoming fragmented packet defragment buffer.
fragmented_packets: Mutex<[FragmentedPacket; PEER_DEFRAGMENT_MAX_PACKETS_IN_FLIGHT]>,
// Last send and receive time in millisecond ticks (not wall clock).
last_send_time_ticks: AtomicI64,
last_receive_time_ticks: AtomicI64,
// Most recent remote version (most to least significant bytes: major, minor, revision, build)
remote_version: AtomicU64,
// Most recent remote protocol version
remote_protocol_version: AtomicU8,
}

View file

@ -9,7 +9,7 @@ use crate::vl1::Address;
/// [u8; 8] fields in that their endianness is "wire" endian. If for some reason
/// packet IDs need to be portably compared or shared across systems they should
/// be treated as bytes not integers.
type PacketID = u64;
pub type PacketID = u64;
/// ZeroTier unencrypted outer header
/// This is the header for a complete packet. If the fragmented flag is set, it will
@ -113,7 +113,7 @@ impl FragmentHeader {
#[cfg(test)]
mod tests {
use std::mem::size_of;
use crate::vl1::headers::{PacketHeader, FragmentHeader};
use crate::vl1::protocol::{PacketHeader, FragmentHeader};
use crate::vl1::constants::{PACKET_HEADER_SIZE, FRAGMENT_SIZE_MIN};
#[test]

View file

@ -188,7 +188,7 @@ impl MacFethTap {
// Set sysctl for max if_fake MTU. This is allowed to fail since this sysctl doesn't
// exist on older versions of MacOS (and isn't required there). 16000 is larger than
// anything ZeroTier supports. OS max is 16384 - some overhead.
let _ = Command::new(SYSCTL).arg("net.link.fake.max_mtu").arg("16000").spawn().map(|mut c| { let _ = c.wait(); });
let _ = Command::new(SYSCTL).arg("net.link.fake.max_mtu").arg("10000").spawn().map(|mut c| { let _ = c.wait(); });
// Create pair of feth interfaces and create MacFethDevice struct.
let cmd = Command::new(IFCONFIG).arg(&device_name).arg("create").spawn();