Build fixes, add more parsing and serialization to Identity.

This commit is contained in:
Adam Ierymenko 2023-03-24 22:45:51 -04:00
parent ec7fae71b4
commit ed309b9f18
18 changed files with 333 additions and 244 deletions

View file

@ -10,9 +10,8 @@ use tokio_postgres::types::Type;
use tokio_postgres::{Client, Statement}; use tokio_postgres::{Client, Statement};
use zerotier_crypto::secure_eq; use zerotier_crypto::secure_eq;
use zerotier_crypto::typestate::Valid;
use zerotier_network_hypervisor::vl1::{Address, Identity, InetAddress}; use zerotier_network_hypervisor::vl1::{Address, InetAddress};
use zerotier_network_hypervisor::vl2::rule::Rule; use zerotier_network_hypervisor::vl2::rule::Rule;
use zerotier_network_hypervisor::vl2::{IpRoute, NetworkId}; use zerotier_network_hypervisor::vl2::{IpRoute, NetworkId};
@ -21,7 +20,6 @@ use zerotier_utils::tokio;
use zerotier_utils::tokio::runtime::Handle; use zerotier_utils::tokio::runtime::Handle;
use zerotier_utils::tokio::sync::broadcast::{channel, Receiver, Sender}; use zerotier_utils::tokio::sync::broadcast::{channel, Receiver, Sender};
use zerotier_utils::tokio::task::JoinHandle; use zerotier_utils::tokio::task::JoinHandle;
use zerotier_vl1_service::VL1DataStorage;
use crate::database::*; use crate::database::*;
use crate::model::{IpAssignmentPool, Member, Network, RequestLogItem}; use crate::model::{IpAssignmentPool, Member, Network, RequestLogItem};
@ -130,15 +128,14 @@ impl<'a> Drop for ConnectionHolder<'a> {
} }
pub struct PostgresDatabase { pub struct PostgresDatabase {
local_controller_id_str: String, local_controller: Address,
local_identity: Valid<Identity>,
connections: Mutex<(Vec<Box<PostgresConnection>>, Sender<()>)>, connections: Mutex<(Vec<Box<PostgresConnection>>, Sender<()>)>,
postgres_path: String, postgres_path: String,
runtime: Handle, runtime: Handle,
} }
impl PostgresDatabase { impl PostgresDatabase {
pub async fn new(runtime: Handle, postgres_path: String, num_connections: usize, local_identity: Valid<Identity>) -> Result<Arc<Self>, Error> { pub async fn new(runtime: Handle, postgres_path: String, num_connections: usize) -> Result<Arc<Self>, Error> {
assert!(num_connections > 0); assert!(num_connections > 0);
let (sender, _) = channel(4096); let (sender, _) = channel(4096);
let mut connections = Vec::with_capacity(num_connections); let mut connections = Vec::with_capacity(num_connections);
@ -147,7 +144,6 @@ impl PostgresDatabase {
} }
Ok(Arc::new(Self { Ok(Arc::new(Self {
local_controller_id_str: local_identity.address.to_string(), local_controller_id_str: local_identity.address.to_string(),
local_identity,
connections: Mutex::new((connections, sender)), connections: Mutex::new((connections, sender)),
postgres_path, postgres_path,
runtime, runtime,
@ -177,16 +173,6 @@ impl PostgresDatabase {
} }
} }
impl VL1DataStorage for PostgresDatabase {
fn load_node_identity(&self) -> Option<Valid<Identity>> {
Some(self.local_identity.clone())
}
fn save_node_identity(&self, _id: &Valid<Identity>) -> bool {
panic!("local identity saving not supported by PostgresDatabase")
}
}
#[async_trait] #[async_trait]
impl Database for PostgresDatabase { impl Database for PostgresDatabase {
async fn list_networks(&self) -> Result<Vec<NetworkId>, Error> { async fn list_networks(&self) -> Result<Vec<NetworkId>, Error> {

View file

@ -9,6 +9,7 @@ use tokio::time::{Duration, Instant};
use zerotier_crypto::secure_eq; use zerotier_crypto::secure_eq;
use zerotier_network_hypervisor::protocol; use zerotier_network_hypervisor::protocol;
use zerotier_network_hypervisor::protocol::{PacketBuffer, DEFAULT_MULTICAST_LIMIT, ZEROTIER_VIRTUAL_NETWORK_DEFAULT_MTU}; use zerotier_network_hypervisor::protocol::{PacketBuffer, DEFAULT_MULTICAST_LIMIT, ZEROTIER_VIRTUAL_NETWORK_DEFAULT_MTU};
use zerotier_network_hypervisor::vl1::identity::{Identity, IdentitySecret};
use zerotier_network_hypervisor::vl1::*; use zerotier_network_hypervisor::vl1::*;
use zerotier_network_hypervisor::vl2; use zerotier_network_hypervisor::vl2;
use zerotier_network_hypervisor::vl2::multicastauthority::MulticastAuthority; use zerotier_network_hypervisor::vl2::multicastauthority::MulticastAuthority;
@ -37,7 +38,6 @@ pub struct Controller {
reaper: Reaper, reaper: Reaper,
runtime: tokio::runtime::Handle, runtime: tokio::runtime::Handle,
database: Arc<dyn Database>, database: Arc<dyn Database>,
local_identity: Valid<Identity>,
/// Handler for MULTICAST_LIKE and MULTICAST_GATHER messages. /// Handler for MULTICAST_LIKE and MULTICAST_GATHER messages.
multicast_authority: MulticastAuthority, multicast_authority: MulticastAuthority,
@ -47,7 +47,7 @@ pub struct Controller {
/// Recently authorized network members and when that authorization expires (in monotonic ticks). /// Recently authorized network members and when that authorization expires (in monotonic ticks).
/// Note that this is not and should not be used for real authentication, just for locking up multicast info. /// Note that this is not and should not be used for real authentication, just for locking up multicast info.
recently_authorized: RwLock<HashMap<[u8; Identity::FINGERPRINT_SIZE], HashMap<NetworkId, i64>>>, recently_authorized: RwLock<HashMap<Address, HashMap<NetworkId, i64>>>,
} }
impl Controller { impl Controller {
@ -63,7 +63,6 @@ impl Controller {
reaper: Reaper::new(&runtime), reaper: Reaper::new(&runtime),
runtime, runtime,
database: database.clone(), database: database.clone(),
local_identity: local_identity,
multicast_authority: MulticastAuthority::new(), multicast_authority: MulticastAuthority::new(),
daemons: Mutex::new(Vec::with_capacity(2)), daemons: Mutex::new(Vec::with_capacity(2)),
recently_authorized: RwLock::new(HashMap::new()), recently_authorized: RwLock::new(HashMap::new()),
@ -232,7 +231,7 @@ impl Controller {
if member.node_id != *m { if member.node_id != *m {
if let Some(peer) = self.service.read().unwrap().upgrade().and_then(|s| s.node().peer(*m)) { if let Some(peer) = self.service.read().unwrap().upgrade().and_then(|s| s.node().peer(*m)) {
revocations.clear(); revocations.clear();
Revocation::new(member.network_id, time_clock, member.node_id, *m, &self.local_identity, false).map(|r| revocations.push(r)); Revocation::new(&member.network_id, time_clock, &member.node_id, m, &self.local_identity, false).map(|r| revocations.push(r));
self.send_revocations(&peer, &mut revocations); self.send_revocations(&peer, &mut revocations);
} }
} }
@ -427,7 +426,7 @@ impl Controller {
// the overhead (bandwidth and CPU) of generating these. // the overhead (bandwidth and CPU) of generating these.
if let Some(com) = if let Some(com) =
vl2::v1::CertificateOfMembership::new(&self.local_identity, network_id, &source_identity, time_clock, credential_ttl) vl2::v1::CertificateOfMembership::new(&self.local_identity, &network_id, &source_identity, time_clock, credential_ttl)
{ {
let mut v1cred = V1Credentials { let mut v1cred = V1Credentials {
revision: time_clock as u64, revision: time_clock as u64,
@ -438,7 +437,7 @@ impl Controller {
}; };
if !nc.static_ips.is_empty() { if !nc.static_ips.is_empty() {
let mut coo = vl2::v1::CertificateOfOwnership::new(network_id, time_clock, source_identity.address); let mut coo = vl2::v1::CertificateOfOwnership::new(&network_id, time_clock, &source_identity.address);
for ip in nc.static_ips.iter() { for ip in nc.static_ips.iter() {
coo.add_ip(ip); coo.add_ip(ip);
} }

View file

@ -4,7 +4,6 @@ use zerotier_crypto::secure_eq;
use zerotier_network_hypervisor::vl1::{Address, InetAddress}; use zerotier_network_hypervisor::vl1::{Address, InetAddress};
use zerotier_network_hypervisor::vl2::NetworkId; use zerotier_network_hypervisor::vl2::NetworkId;
use zerotier_utils::tokio::sync::broadcast::Receiver; use zerotier_utils::tokio::sync::broadcast::Receiver;
use zerotier_vl1_service::VL1DataStorage;
use crate::model::*; use crate::model::*;
@ -22,7 +21,7 @@ pub enum Change {
} }
#[async_trait] #[async_trait]
pub trait Database: Sync + Send + VL1DataStorage + 'static { pub trait Database: Sync + Send + 'static {
async fn list_networks(&self) -> Result<Vec<NetworkId>, Error>; async fn list_networks(&self) -> Result<Vec<NetworkId>, Error>;
async fn get_network(&self, id: NetworkId) -> Result<Option<Network>, Error>; async fn get_network(&self, id: NetworkId) -> Result<Option<Network>, Error>;
async fn save_network(&self, obj: Network, generate_change_notification: bool) -> Result<(), Error>; async fn save_network(&self, obj: Network, generate_change_notification: bool) -> Result<(), Error>;

View file

@ -5,7 +5,7 @@ use async_trait::async_trait;
use notify::{RecursiveMode, Watcher}; use notify::{RecursiveMode, Watcher};
use serde::de::DeserializeOwned; use serde::de::DeserializeOwned;
use zerotier_network_hypervisor::vl1::{Address, Identity, Valid}; use zerotier_network_hypervisor::vl1::Address;
use zerotier_network_hypervisor::vl2::NetworkId; use zerotier_network_hypervisor::vl2::NetworkId;
use zerotier_utils::reaper::Reaper; use zerotier_utils::reaper::Reaper;
use zerotier_utils::tokio::fs; use zerotier_utils::tokio::fs;
@ -13,8 +13,6 @@ use zerotier_utils::tokio::runtime::Handle;
use zerotier_utils::tokio::sync::broadcast::{channel, Receiver, Sender}; use zerotier_utils::tokio::sync::broadcast::{channel, Receiver, Sender};
use zerotier_utils::tokio::task::JoinHandle; use zerotier_utils::tokio::task::JoinHandle;
use zerotier_utils::tokio::time::{sleep, Duration, Instant}; use zerotier_utils::tokio::time::{sleep, Duration, Instant};
use zerotier_vl1_service::datadir::{load_node_identity, save_node_identity};
use zerotier_vl1_service::VL1DataStorage;
use crate::cache::Cache; use crate::cache::Cache;
use crate::database::{Change, Database, Error}; use crate::database::{Change, Database, Error};
@ -32,7 +30,7 @@ const EVENT_HANDLER_TASK_TIMEOUT: Duration = Duration::from_secs(10);
/// is different from V1 so it'll need a converter to use with V1 FileDb controller data. /// is different from V1 so it'll need a converter to use with V1 FileDb controller data.
pub struct FileDatabase { pub struct FileDatabase {
base_path: PathBuf, base_path: PathBuf,
local_identity: Valid<Identity>, controller_address: Address,
change_sender: Sender<Change>, change_sender: Sender<Change>,
tasks: Reaper, tasks: Reaper,
cache: Cache, cache: Cache,
@ -42,7 +40,7 @@ pub struct FileDatabase {
// TODO: should cache at least hashes and detect changes in the filesystem live. // TODO: should cache at least hashes and detect changes in the filesystem live.
impl FileDatabase { impl FileDatabase {
pub async fn new<P: AsRef<Path>>(runtime: Handle, base_path: P) -> Result<Arc<Self>, Error> { pub async fn new<P: AsRef<Path>>(runtime: Handle, base_path: P, controller_address: Address) -> Result<Arc<Self>, Error> {
let base_path: PathBuf = base_path.as_ref().into(); let base_path: PathBuf = base_path.as_ref().into();
let (change_sender, _) = channel(256); let (change_sender, _) = channel(256);
@ -50,13 +48,9 @@ impl FileDatabase {
let db_weak = db_weak_tmp.clone(); let db_weak = db_weak_tmp.clone();
let runtime2 = runtime.clone(); let runtime2 = runtime.clone();
let local_identity =
load_node_identity(base_path.as_path()).ok_or(std::io::Error::new(std::io::ErrorKind::NotFound, "identity.secret not found"))?;
let controller_address = local_identity.address;
let db = Arc::new(Self { let db = Arc::new(Self {
base_path: base_path.clone(), base_path: base_path.clone(),
local_identity, controller_address: controller_address.clone(),
change_sender, change_sender,
tasks: Reaper::new(&runtime2), tasks: Reaper::new(&runtime2),
cache: Cache::new(), cache: Cache::new(),
@ -246,16 +240,6 @@ impl Drop for FileDatabase {
} }
} }
impl VL1DataStorage for FileDatabase {
fn load_node_identity(&self) -> Option<Valid<Identity>> {
load_node_identity(self.base_path.as_path())
}
fn save_node_identity(&self, id: &Valid<Identity>) -> bool {
save_node_identity(self.base_path.as_path(), id)
}
}
#[async_trait] #[async_trait]
impl Database for FileDatabase { impl Database for FileDatabase {
async fn list_networks(&self) -> Result<Vec<NetworkId>, Error> { async fn list_networks(&self) -> Result<Vec<NetworkId>, Error> {
@ -362,6 +346,7 @@ mod tests {
#[allow(unused_imports)] #[allow(unused_imports)]
use super::*; use super::*;
use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::atomic::{AtomicUsize, Ordering};
use zerotier_network_hypervisor::vl1::identity::Identity;
#[allow(unused)] #[allow(unused)]
#[test] #[test]
@ -375,11 +360,14 @@ mod tests {
println!("test filedatabase is in: {}", test_dir.as_os_str().to_str().unwrap()); println!("test filedatabase is in: {}", test_dir.as_os_str().to_str().unwrap());
let _ = std::fs::remove_dir_all(&test_dir); let _ = std::fs::remove_dir_all(&test_dir);
let controller_id = Identity::generate(); let controller_id = Identity::generate(false);
assert!(fs::create_dir_all(&test_dir).await.is_ok()); assert!(fs::create_dir_all(&test_dir).await.is_ok());
assert!(save_node_identity(test_dir.as_path(), &controller_id)); let db = Arc::new(
let db = Arc::new(FileDatabase::new(tokio_runtime.handle().clone(), test_dir).await.expect("new db")); FileDatabase::new(tokio_runtime.handle().clone(), test_dir, controller_id.public.address.clone())
.await
.expect("new db"),
);
let change_count = Arc::new(AtomicUsize::new(0)); let change_count = Arc::new(AtomicUsize::new(0));

View file

@ -7,6 +7,5 @@ pub(crate) mod cache;
pub mod database; pub mod database;
pub mod filedatabase; pub mod filedatabase;
pub mod model; pub mod model;
pub mod postgresdatabase;
pub use controller::*; pub use controller::*;

View file

@ -5,7 +5,8 @@ use std::hash::Hash;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use zerotier_network_hypervisor::vl1::{Address, Identity, InetAddress}; use zerotier_network_hypervisor::vl1::identity::Identity;
use zerotier_network_hypervisor::vl1::{Address, InetAddress};
use zerotier_network_hypervisor::vl2::NetworkId; use zerotier_network_hypervisor::vl2::NetworkId;
use zerotier_utils::blob::Blob; use zerotier_utils::blob::Blob;

View file

@ -16,7 +16,7 @@ use zerotier_crypto::secret::Secret;
use zerotier_crypto::typestate::Valid; use zerotier_crypto::typestate::Valid;
use zerotier_crypto::x25519::*; use zerotier_crypto::x25519::*;
use zerotier_utils::arrayvec::ArrayVec; use zerotier_utils::arrayvec::ArrayVec;
use zerotier_utils::base24; use zerotier_utils::base64;
use zerotier_utils::buffer::{Buffer, OutOfBoundsError}; use zerotier_utils::buffer::{Buffer, OutOfBoundsError};
use zerotier_utils::error::InvalidFormatError; use zerotier_utils::error::InvalidFormatError;
use zerotier_utils::marshalable::{Marshalable, UnmarshalError}; use zerotier_utils::marshalable::{Marshalable, UnmarshalError};
@ -123,9 +123,7 @@ impl Identity {
} }
// Bits 40-384 of the address are filled from a SHA384 hash of all keys for a full length V2 address. // Bits 40-384 of the address are filled from a SHA384 hash of all keys for a full length V2 address.
let mut address = secret.public.address.clone(); secret.public.address = secret.public.populate_extended_address_bits();
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 // 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. // are properly bound together and can't be changed independently.
@ -148,7 +146,7 @@ impl Identity {
// First, check that the full SHA384 (bits 40-384) in the address is correct. Check first since this is fast. // 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(); let mut test_address = Address::new_uninitialized();
test_address.0[..PartialAddress::LEGACY_SIZE_BYTES].copy_from_slice(&self.address.0[..PartialAddress::LEGACY_SIZE_BYTES]); 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); test_address = self.populate_extended_address_bits();
if self.address != test_address { if self.address != test_address {
return None; return None;
} }
@ -190,8 +188,8 @@ impl Identity {
} }
} }
/// Populate bits 40-384 of the address with a hash of everything else. /// Populate bits 40-384 of the address with a hash of everything else and return the completed address.
fn populate_extended_address_bits(&self, address: &mut Address) { fn populate_extended_address_bits(&self) -> Address {
let mut sha = SHA384::new(); let mut sha = SHA384::new();
sha.update(&self.address.0[..PartialAddress::LEGACY_SIZE_BYTES]); // include short address in full hash sha.update(&self.address.0[..PartialAddress::LEGACY_SIZE_BYTES]); // include short address in full hash
sha.update(&[Self::ALGORITHM_X25519 sha.update(&[Self::ALGORITHM_X25519
@ -207,7 +205,9 @@ impl Identity {
sha.update(p384.ecdsa.as_bytes()); sha.update(p384.ecdsa.as_bytes());
} }
let sha = sha.finish(); let sha = sha.finish();
let mut address = self.address.clone();
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]);
address
} }
/// Encode for self-signing, used only with p384 keys enabled and panics otherwise. /// Encode for self-signing, used only with p384 keys enabled and panics otherwise.
@ -224,7 +224,6 @@ impl Identity {
/// Decode a byte serialized identity. /// Decode a byte serialized identity.
pub fn from_bytes(b: &[u8]) -> Result<Self, InvalidFormatError> { pub fn from_bytes(b: &[u8]) -> Result<Self, InvalidFormatError> {
let mut id; 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) { 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 p: &packed::V2Public = memory::cast_to_struct(b);
id = Self { id = Self {
@ -237,7 +236,7 @@ impl Identity {
p384_self_signature: p.p384_self_signature, p384_self_signature: p.p384_self_signature,
}), }),
}; };
address.0[..PartialAddress::LEGACY_SIZE_BYTES].copy_from_slice(&p.short_address); 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 { } 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 p: &packed::V1Public = memory::cast_to_struct(b);
id = Self { id = Self {
@ -245,12 +244,11 @@ impl Identity {
x25519: X25519 { ecdh: p.c25519, eddsa: p.ed25519 }, x25519: X25519 { ecdh: p.c25519, eddsa: p.ed25519 },
p384: None, p384: None,
}; };
address.0[..PartialAddress::LEGACY_SIZE_BYTES].copy_from_slice(&p.short_address); id.address.0[..PartialAddress::LEGACY_SIZE_BYTES].copy_from_slice(&p.short_address);
} else { } else {
return Err(InvalidFormatError); return Err(InvalidFormatError);
} }
id.populate_extended_address_bits(&mut address); id.address = id.populate_extended_address_bits();
id.address = address;
return Ok(id); return Ok(id);
} }
@ -283,23 +281,23 @@ impl ToString for Identity {
fn to_string(&self) -> String { fn to_string(&self) -> String {
if let Some(p384) = self.p384.as_ref() { if let Some(p384) = self.p384.as_ref() {
let mut s = String::with_capacity(1024); let mut s = String::with_capacity(1024);
base24::encode_into(self.address.as_bytes(), &mut s); s.push_str(self.address.to_string().as_str());
s.push_str(":1:"); s.push_str(":1:");
base24::encode_into(&self.x25519.ecdh, &mut s); base64::encode_into(&self.x25519.ecdh, &mut s);
s.push(':'); s.push(':');
base24::encode_into(&self.x25519.eddsa, &mut s); base64::encode_into(&self.x25519.eddsa, &mut s);
s.push(':'); s.push(':');
base24::encode_into(p384.ecdh.as_bytes(), &mut s); base64::encode_into(p384.ecdh.as_bytes(), &mut s);
s.push(':'); s.push(':');
base24::encode_into(p384.ecdsa.as_bytes(), &mut s); base64::encode_into(p384.ecdsa.as_bytes(), &mut s);
s.push(':'); s.push(':');
base24::encode_into(&p384.ed25519_self_signature, &mut s); base64::encode_into(&p384.ed25519_self_signature, &mut s);
s.push(':'); s.push(':');
base24::encode_into(&p384.p384_self_signature, &mut s); base64::encode_into(&p384.p384_self_signature, &mut s);
s s
} else { } else {
format!( format!(
"{}:0:{}:{}", "{}:0:{}{}",
hex::to_string(self.address.legacy_bytes()), hex::to_string(self.address.legacy_bytes()),
hex::to_string(&self.x25519.ecdh), hex::to_string(&self.x25519.ecdh),
hex::to_string(&self.x25519.eddsa) hex::to_string(&self.x25519.eddsa)
@ -314,54 +312,56 @@ impl FromStr for Identity {
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
let ss: Vec<&str> = s.split(':').collect(); let ss: Vec<&str> = s.split(':').collect();
if ss.len() >= 2 { if ss.len() >= 2 {
if ss[1] == "1" && ss.len() == 8 { if ss[1] == "1" && ss.len() >= 8 {
return Ok(Self { return Ok(Self {
address: Address::from_str(ss[0]).map_err(|_| InvalidFormatError)?, address: Address::from_str(ss[0]).map_err(|_| InvalidFormatError)?,
x25519: X25519 { x25519: X25519 {
ecdh: base24::decode(ss[2].as_bytes()) ecdh: base64::decode(ss[2].as_bytes())
.map_err(|_| InvalidFormatError)? .map_err(|_| InvalidFormatError)?
.try_into() .try_into()
.map_err(|_| InvalidFormatError)?, .map_err(|_| InvalidFormatError)?,
eddsa: base24::decode(ss[3].as_bytes()) eddsa: base64::decode(ss[3].as_bytes())
.map_err(|_| InvalidFormatError)? .map_err(|_| InvalidFormatError)?
.try_into() .try_into()
.map_err(|_| InvalidFormatError)?, .map_err(|_| InvalidFormatError)?,
}, },
p384: Some(P384 { p384: Some(P384 {
ecdh: P384PublicKey::from_bytes(base24::decode(ss[4].as_bytes()).map_err(|_| InvalidFormatError)?.as_slice()) ecdh: P384PublicKey::from_bytes(base64::decode(ss[4].as_bytes()).map_err(|_| InvalidFormatError)?.as_slice())
.ok_or(InvalidFormatError)?, .ok_or(InvalidFormatError)?,
ecdsa: P384PublicKey::from_bytes(base24::decode(ss[5].as_bytes()).map_err(|_| InvalidFormatError)?.as_slice()) ecdsa: P384PublicKey::from_bytes(base64::decode(ss[5].as_bytes()).map_err(|_| InvalidFormatError)?.as_slice())
.ok_or(InvalidFormatError)?, .ok_or(InvalidFormatError)?,
ed25519_self_signature: base24::decode(ss[6].as_bytes()) ed25519_self_signature: base64::decode(ss[6].as_bytes())
.map_err(|_| InvalidFormatError)? .map_err(|_| InvalidFormatError)?
.try_into() .try_into()
.map_err(|_| InvalidFormatError)?, .map_err(|_| InvalidFormatError)?,
p384_self_signature: base24::decode(ss[7].as_bytes()) p384_self_signature: base64::decode(ss[7].as_bytes())
.map_err(|_| InvalidFormatError)? .map_err(|_| InvalidFormatError)?
.try_into() .try_into()
.map_err(|_| InvalidFormatError)?, .map_err(|_| InvalidFormatError)?,
}), }),
}); });
} else if ss[1] == "0" && ss.len() == 4 { } else if ss[1] == "0" && ss.len() >= 3 {
let mut address = { let ecdh_eddsa = hex::from_string(ss[2]);
let legacy_address = hex::from_string(ss[0]); if ecdh_eddsa.len() != C25519_PUBLIC_KEY_SIZE + ED25519_PUBLIC_KEY_SIZE {
if legacy_address.len() != PartialAddress::LEGACY_SIZE_BYTES { return Err(InvalidFormatError);
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 { let mut a = Self {
address: Address::new_uninitialized(), 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)
},
x25519: X25519 { x25519: X25519 {
ecdh: hex::from_string(ss[2]).try_into().map_err(|_| InvalidFormatError)?, ecdh: ecdh_eddsa[..C25519_PUBLIC_KEY_SIZE].try_into().unwrap(),
eddsa: hex::from_string(ss[3]).try_into().map_err(|_| InvalidFormatError)?, eddsa: ecdh_eddsa[C25519_PUBLIC_KEY_SIZE..].try_into().unwrap(),
}, },
p384: None, p384: None,
}; };
a.populate_extended_address_bits(&mut address); a.address = a.populate_extended_address_bits();
a.address = address;
return Ok(a); return Ok(a);
} }
} }
@ -401,53 +401,6 @@ impl Marshalable for Identity {
} }
} }
impl IdentitySecret {
pub fn sign(&self, data: &[u8]) -> ArrayVec<u8, { Identity::MAX_SIGNATURE_SIZE }> {
let mut s = ArrayVec::new();
if let Some(p384) = self.p384.as_ref() {
s.push_slice(&p384.sign(data));
} else {
s.push_slice(&self.x25519.sign(data));
}
s
}
}
impl X25519Secret {
#[inline]
pub fn agree(&self, public: &Identity) -> Option<Secret<64>> {
Some(Secret(SHA512::hash(self.ecdh.agree(&public.x25519.ecdh).as_bytes())))
}
/// Sign with Ed25519 using the legacy signature format used by ZeroTier V1.
/// This just means the last 32 bytes of a 96-byte signature are the first 32 bytes of the
/// SHA512 hash. This isn't used even in V1 but was once used long ago to rapidly check
/// signatures as part of a different design. Some nodes still expect it to be there though.
#[inline(always)]
pub fn sign(&self, data: &[u8]) -> [u8; 96] {
self.eddsa.sign_zt(data)
}
}
impl P384Secret {
#[inline(always)]
pub fn sign(&self, data: &[u8]) -> [u8; P384_ECDSA_SIGNATURE_SIZE] {
self.ecdsa.sign(data)
}
}
impl Eq for P384 {}
impl PartialEq for P384 {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.ecdh.as_bytes() == other.ecdh.as_bytes()
&& self.ecdsa.as_bytes() == other.ecdsa.as_bytes()
&& self.ed25519_self_signature == other.ed25519_self_signature
&& self.p384_self_signature == other.p384_self_signature
}
}
impl Serialize for Identity { impl Serialize for Identity {
#[inline] #[inline]
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
@ -505,6 +458,127 @@ impl<'de> Deserialize<'de> for Identity {
} }
} }
impl Eq for P384 {}
impl PartialEq for P384 {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.ecdh.as_bytes() == other.ecdh.as_bytes()
&& self.ecdsa.as_bytes() == other.ecdsa.as_bytes()
&& self.ed25519_self_signature == other.ed25519_self_signature
&& self.p384_self_signature == other.p384_self_signature
}
}
impl IdentitySecret {
pub fn sign(&self, data: &[u8]) -> ArrayVec<u8, { Identity::MAX_SIGNATURE_SIZE }> {
let mut s = ArrayVec::new();
if let Some(p384) = self.p384.as_ref() {
s.push_slice(&p384.sign(data));
} else {
s.push_slice(&self.x25519.sign(data));
}
s
}
}
impl ToString for IdentitySecret {
fn to_string(&self) -> String {
let mut s = self.public.to_string();
if let Some(p384) = self.p384.as_ref() {
s.push(':');
base64::encode_into(self.x25519.ecdh.secret_bytes().as_bytes(), &mut s);
s.push(':');
base64::encode_into(self.x25519.eddsa.secret_bytes().as_bytes(), &mut s);
s.push(':');
base64::encode_into(p384.ecdh.secret_key_bytes().as_bytes(), &mut s);
s.push(':');
base64::encode_into(p384.ecdsa.secret_key_bytes().as_bytes(), &mut s);
} else {
s.push(':');
s.push_str(hex::to_string(self.x25519.ecdh.secret_bytes().as_bytes()).as_str());
s.push_str(hex::to_string(self.x25519.eddsa.secret_bytes().as_bytes()).as_str());
}
s
}
}
impl FromStr for IdentitySecret {
type Err = InvalidFormatError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let public = Identity::from_str(s)?;
let ss: Vec<&str> = s.split(':').collect();
if ss.len() >= 2 {
if ss[1] == "1" && ss.len() >= 12 && public.p384.is_some() {
let x25519_ecdh = X25519KeyPair::from_bytes(
&public.x25519.ecdh,
base64::decode(ss[8].as_bytes()).map_err(|_| InvalidFormatError)?.as_slice(),
)
.ok_or(InvalidFormatError)?;
let x25519_eddsa = Ed25519KeyPair::from_bytes(
&public.x25519.ecdh,
base64::decode(ss[9].as_bytes()).map_err(|_| InvalidFormatError)?.as_slice(),
)
.ok_or(InvalidFormatError)?;
let p384_ecdh = P384KeyPair::from_bytes(
public.p384.as_ref().unwrap().ecdh.as_bytes(),
base64::decode(ss[10].as_bytes()).map_err(|_| InvalidFormatError)?.as_slice(),
)
.ok_or(InvalidFormatError)?;
let p384_ecdsa = P384KeyPair::from_bytes(
public.p384.as_ref().unwrap().ecdh.as_bytes(),
base64::decode(ss[11].as_bytes()).map_err(|_| InvalidFormatError)?.as_slice(),
)
.ok_or(InvalidFormatError)?;
return Ok(Self {
public: public.validate().ok_or(InvalidFormatError)?,
x25519: X25519Secret { ecdh: x25519_ecdh, eddsa: x25519_eddsa },
p384: Some(P384Secret { ecdh: p384_ecdh, ecdsa: p384_ecdsa }),
});
} else if ss[1] == "0" && ss.len() >= 4 && public.p384.is_none() {
let ecdh_eddsa_secrets = hex::from_string(ss[3]);
if ecdh_eddsa_secrets.len() != C25519_SECRET_KEY_SIZE + ED25519_SECRET_KEY_SIZE {
return Err(InvalidFormatError);
}
let x25519_ecdh =
X25519KeyPair::from_bytes(&public.x25519.ecdh, &ecdh_eddsa_secrets[..C25519_SECRET_KEY_SIZE]).ok_or(InvalidFormatError)?;
let x25519_eddsa =
Ed25519KeyPair::from_bytes(&public.x25519.ecdh, &ecdh_eddsa_secrets[C25519_SECRET_KEY_SIZE..]).ok_or(InvalidFormatError)?;
return Ok(Self {
public: public.validate().ok_or(InvalidFormatError)?,
x25519: X25519Secret { ecdh: x25519_ecdh, eddsa: x25519_eddsa },
p384: None,
});
}
}
return Err(InvalidFormatError);
}
}
impl X25519Secret {
#[inline]
pub fn agree(&self, public: &Identity) -> Option<Secret<64>> {
Some(Secret(SHA512::hash(self.ecdh.agree(&public.x25519.ecdh).as_bytes())))
}
/// Sign with Ed25519 using the legacy signature format used by ZeroTier V1.
/// This just means the last 32 bytes of a 96-byte signature are the first 32 bytes of the
/// SHA512 hash. This isn't used even in V1 but was once used long ago to rapidly check
/// signatures as part of a different design. Some nodes still expect it to be there though.
#[inline(always)]
pub fn sign(&self, data: &[u8]) -> [u8; 96] {
self.eddsa.sign_zt(data)
}
}
impl P384Secret {
#[inline(always)]
pub fn sign(&self, data: &[u8]) -> [u8; P384_ECDSA_SIGNATURE_SIZE] {
self.ecdsa.sign(data)
}
}
/// The actual serialization format for secret identities. /// The actual serialization format for secret identities.
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
struct IdentitySecretForSerialization<'a> { struct IdentitySecretForSerialization<'a> {
@ -572,7 +646,7 @@ impl<'de> Deserialize<'de> for IdentitySecret {
let e2 = || D::Error::custom("invalid key"); let e2 = || D::Error::custom("invalid key");
let e = |_e: TryFromSliceError| e2(); let e = |_e: TryFromSliceError| e2();
Ok(IdentitySecret { Ok(IdentitySecret {
public: Valid::mark_valid(Identity { public: Identity {
address: Address::from_bytes(tmp.address).map_err(|_| e2())?, address: Address::from_bytes(tmp.address).map_err(|_| e2())?,
x25519: X25519 { x25519: X25519 {
ecdh: x25519_ecdh_public.try_into().map_err(e)?, ecdh: x25519_ecdh_public.try_into().map_err(e)?,
@ -588,7 +662,9 @@ impl<'de> Deserialize<'de> for IdentitySecret {
} else { } else {
None None
}, },
}), }
.validate()
.ok_or_else(e2)?,
x25519: X25519Secret { x25519: X25519Secret {
ecdh: X25519KeyPair::from_bytes(x25519_ecdh_public, x25519_ecdh_secret).ok_or_else(e2)?, 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)?, eddsa: Ed25519KeyPair::from_bytes(x25519_eddsa_public, x25519_eddsa_secret).ok_or_else(e2)?,

View file

@ -218,7 +218,7 @@ struct BackgroundTaskIntervals {
} }
pub struct Node<Application: ApplicationLayer + ?Sized> { pub struct Node<Application: ApplicationLayer + ?Sized> {
pub(super) identity_secret: IdentitySecret, pub identity: IdentitySecret,
intervals: Mutex<BackgroundTaskIntervals>, intervals: Mutex<BackgroundTaskIntervals>,
paths: RwLock<HashMap<PathKey<'static, 'static, Application::LocalSocket>, Arc<Path<Application>>>>, paths: RwLock<HashMap<PathKey<'static, 'static, Application::LocalSocket>, Arc<Path<Application>>>>,
pub(super) peers: PeerMap<Application>, pub(super) peers: PeerMap<Application>,
@ -229,7 +229,7 @@ pub struct Node<Application: ApplicationLayer + ?Sized> {
impl<Application: ApplicationLayer + ?Sized> Node<Application> { impl<Application: ApplicationLayer + ?Sized> Node<Application> {
pub fn new(identity_secret: IdentitySecret) -> Self { pub fn new(identity_secret: IdentitySecret) -> Self {
Self { Self {
identity_secret, identity: identity_secret,
intervals: Mutex::new(BackgroundTaskIntervals::default()), intervals: Mutex::new(BackgroundTaskIntervals::default()),
paths: RwLock::new(HashMap::new()), paths: RwLock::new(HashMap::new()),
peers: PeerMap::new(), peers: PeerMap::new(),
@ -244,11 +244,6 @@ impl<Application: ApplicationLayer + ?Sized> Node<Application> {
} }
} }
#[inline(always)]
pub fn identity(&self) -> &Valid<Identity> {
&self.identity_secret.public
}
#[inline(always)] #[inline(always)]
pub fn peer(&self, a: &Address) -> Option<Arc<Peer<Application>>> { pub fn peer(&self, a: &Address) -> Option<Arc<Peer<Application>>> {
self.peers.get_exact(a) self.peers.get_exact(a)
@ -353,7 +348,7 @@ impl<Application: ApplicationLayer + ?Sized> Node<Application> {
for (_, rs) in roots.sets.iter() { for (_, rs) in roots.sets.iter() {
for m in rs.members.iter() { for m in rs.members.iter() {
if m.identity.eq(&self.identity_secret.public) { if m.identity.eq(&self.identity.public) {
let _ = my_root_sets let _ = my_root_sets
.get_or_insert_with(|| Vec::new()) .get_or_insert_with(|| Vec::new())
.write_all(rs.to_buffer::<{ RootSet::MAX_MARSHAL_SIZE }>().unwrap().as_bytes()); .write_all(rs.to_buffer::<{ RootSet::MAX_MARSHAL_SIZE }>().unwrap().as_bytes());
@ -367,7 +362,7 @@ impl<Application: ApplicationLayer + ?Sized> Node<Application> {
if let Some(peer) = self.peers.get_exact(&m.identity.address) { if let Some(peer) = self.peers.get_exact(&m.identity.address) {
new_roots.insert(peer.clone(), m.endpoints.as_ref().unwrap().iter().cloned().collect()); new_roots.insert(peer.clone(), m.endpoints.as_ref().unwrap().iter().cloned().collect());
} else { } else {
if let Some(peer) = Peer::new(&self.identity_secret, Valid::mark_valid(m.identity.clone()), time_ticks) { if let Some(peer) = Peer::new(&self.identity, Valid::mark_valid(m.identity.clone()), time_ticks) {
new_roots.insert(self.peers.add(Arc::new(peer)).0, m.endpoints.as_ref().unwrap().iter().cloned().collect()); new_roots.insert(self.peers.add(Arc::new(peer)).0, m.endpoints.as_ref().unwrap().iter().cloned().collect());
} else { } else {
bad_identities.push(m.identity.clone()); bad_identities.push(m.identity.clone());
@ -584,7 +579,7 @@ impl<Application: ApplicationLayer + ?Sized> Node<Application> {
if let Ok(dest) = PartialAddress::from_legacy_address_bytes(&fragment_header.dest) { if let Ok(dest) = PartialAddress::from_legacy_address_bytes(&fragment_header.dest) {
// Packet is addressed to this node. // Packet is addressed to this node.
if dest.matches(&self.identity_secret.public.address) { if dest.matches(&self.identity.public.address) {
let fragment_header = &*fragment_header; // discard mut let fragment_header = &*fragment_header; // discard mut
let path = self.canonical_path(source_endpoint, source_local_socket, source_local_interface, time_ticks); let path = self.canonical_path(source_endpoint, source_local_socket, source_local_interface, time_ticks);
path.log_receive_anything(time_ticks); path.log_receive_anything(time_ticks);

View file

@ -326,7 +326,7 @@ impl<Application: ApplicationLayer + ?Sized> Peer<Application> {
aes_gmac_siv.encrypt_init(&self.v1_proto_next_message_id().to_be_bytes()); aes_gmac_siv.encrypt_init(&self.v1_proto_next_message_id().to_be_bytes());
aes_gmac_siv.encrypt_set_aad(&v1::get_packet_aad_bytes( aes_gmac_siv.encrypt_set_aad(&v1::get_packet_aad_bytes(
&self.identity.address, &self.identity.address,
&node.identity().address, &node.identity.public.address,
flags_cipher_hops, flags_cipher_hops,
)); ));
let payload = packet.as_bytes_starting_at_mut(v1::HEADER_SIZE).unwrap(); let payload = packet.as_bytes_starting_at_mut(v1::HEADER_SIZE).unwrap();
@ -338,7 +338,7 @@ impl<Application: ApplicationLayer + ?Sized> Peer<Application> {
let header = packet.struct_mut_at::<v1::PacketHeader>(0).unwrap(); let header = packet.struct_mut_at::<v1::PacketHeader>(0).unwrap();
header.id.copy_from_slice(&tag[0..8]); header.id.copy_from_slice(&tag[0..8]);
header.dest = *self.identity.address.legacy_bytes(); header.dest = *self.identity.address.legacy_bytes();
header.src = *node.identity().address.legacy_bytes(); header.src = *node.identity.public.address.legacy_bytes();
header.flags_cipher_hops = flags_cipher_hops; header.flags_cipher_hops = flags_cipher_hops;
header.mac.copy_from_slice(&tag[8..16]); header.mac.copy_from_slice(&tag[8..16]);
} else { } else {
@ -355,7 +355,7 @@ impl<Application: ApplicationLayer + ?Sized> Peer<Application> {
let header = packet.struct_mut_at::<v1::PacketHeader>(0).unwrap(); let header = packet.struct_mut_at::<v1::PacketHeader>(0).unwrap();
header.id = self.v1_proto_next_message_id().to_be_bytes(); header.id = self.v1_proto_next_message_id().to_be_bytes();
header.dest = *self.identity.address.legacy_bytes(); header.dest = *self.identity.address.legacy_bytes();
header.src = *node.identity().address.legacy_bytes(); header.src = *node.identity.public.address.legacy_bytes();
header.flags_cipher_hops = flags_cipher_hops; header.flags_cipher_hops = flags_cipher_hops;
header header
}, },
@ -413,7 +413,7 @@ impl<Application: ApplicationLayer + ?Sized> Peer<Application> {
let f: &mut (v1::PacketHeader, v1::message_component_structs::HelloFixedHeaderFields) = packet.append_struct_get_mut().unwrap(); let f: &mut (v1::PacketHeader, v1::message_component_structs::HelloFixedHeaderFields) = packet.append_struct_get_mut().unwrap();
f.0.id = message_id.to_ne_bytes(); f.0.id = message_id.to_ne_bytes();
f.0.dest = *self.identity.address.legacy_bytes(); f.0.dest = *self.identity.address.legacy_bytes();
f.0.src = *node.identity().address.legacy_bytes(); f.0.src = *node.identity.public.address.legacy_bytes();
f.0.flags_cipher_hops = v1::CIPHER_NOCRYPT_POLY1305; f.0.flags_cipher_hops = v1::CIPHER_NOCRYPT_POLY1305;
f.1.verb = message_type::VL1_HELLO; f.1.verb = message_type::VL1_HELLO;
f.1.version_proto = PROTOCOL_VERSION; f.1.version_proto = PROTOCOL_VERSION;
@ -424,7 +424,7 @@ impl<Application: ApplicationLayer + ?Sized> Peer<Application> {
} }
debug_assert_eq!(packet.len(), 41); debug_assert_eq!(packet.len(), 41);
assert!(node.identity().write_bytes(packet.as_mut(), !self.is_v2()).is_ok()); assert!(node.identity.public.write_bytes(packet.as_mut(), !self.is_v2()).is_ok());
let (_, poly1305_key) = v1_proto_salsa_poly_create( let (_, poly1305_key) = v1_proto_salsa_poly_create(
&self.v1_proto_static_secret, &self.v1_proto_static_secret,

View file

@ -154,10 +154,10 @@ impl RootSet {
/// ///
/// All current members must sign whether they are disabled (witnessing) or active. The verify() /// All current members must sign whether they are disabled (witnessing) or active. The verify()
/// method will return true when signing is complete. /// method will return true when signing is complete.
pub fn sign(&mut self, member_identity: &Identity, member_identity_secret: &IdentitySecret) -> bool { pub fn sign(&mut self, member_identity_secret: &IdentitySecret) -> bool {
let signature = member_identity_secret.sign(self.marshal_for_signing().as_bytes()); let signature = member_identity_secret.sign(self.marshal_for_signing().as_bytes());
let unsigned_entry = self.members.iter().find_map(|m| { let unsigned_entry = self.members.iter().find_map(|m| {
if m.identity.eq(member_identity) { if m.identity.eq(&member_identity_secret.public) {
Some(m.clone()) Some(m.clone())
} else { } else {
None None
@ -165,7 +165,7 @@ impl RootSet {
}); });
if unsigned_entry.is_some() { if unsigned_entry.is_some() {
let unsigned_entry = unsigned_entry.unwrap(); let unsigned_entry = unsigned_entry.unwrap();
self.members.retain(|m| !m.identity.eq(member_identity)); self.members.retain(|m| !m.identity.eq(&member_identity_secret.public));
let _ = self.members.push(Root { let _ = self.members.push(Root {
identity: unsigned_entry.identity, identity: unsigned_entry.identity,
endpoints: unsigned_entry.endpoints, endpoints: unsigned_entry.endpoints,

View file

@ -65,7 +65,7 @@ impl<Application: ApplicationLayer + ?Sized> Whois<Application> {
} }
} }
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, &identity, time_ticks) {
for qi in queued_items.iter() { for qi in queued_items.iter() {
for pkt in qi.pending_v1_packets.iter() { for pkt in qi.pending_v1_packets.iter() {
if let Some(source_path) = pkt.0.upgrade() { if let Some(source_path) = pkt.0.upgrade() {

View file

@ -12,7 +12,7 @@ use crate::vl2::NetworkId;
/// "Anti-credential" revoking a network member's permission to communicate on a network. /// "Anti-credential" revoking a network member's permission to communicate on a network.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct Revocation { pub struct Revocation {
pub network_id: NetworkId, pub network_id: u64, // legacy 64-bit network ID
pub threshold: i64, pub threshold: i64,
pub target: u64, // legacy 40-bit address pub target: u64, // legacy 40-bit address
pub issued_to: u64, // legacy 40-bit address pub issued_to: u64, // legacy 40-bit address
@ -22,7 +22,7 @@ pub struct Revocation {
impl Revocation { impl Revocation {
pub fn new( pub fn new(
network_id: NetworkId, network_id: &NetworkId,
threshold: i64, threshold: i64,
target: &Address, target: &Address,
issued_to: &Address, issued_to: &Address,
@ -31,7 +31,7 @@ impl Revocation {
fast_propagate: bool, fast_propagate: bool,
) -> Self { ) -> Self {
let mut r = Self { let mut r = Self {
network_id, network_id: network_id.to_legacy_u64(),
threshold, threshold,
target: target.legacy_u64(), target: target.legacy_u64(),
issued_to: issued_to.legacy_u64(), issued_to: issued_to.legacy_u64(),
@ -50,7 +50,7 @@ impl Revocation {
let _ = v.write_all(&[0; 4]); let _ = v.write_all(&[0; 4]);
let _ = v.write_all(&((self.threshold as u32) ^ (self.target as u32)).to_be_bytes()); // ID is 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(&self.network_id.to_be_bytes());
let _ = v.write_all(&[0; 8]); let _ = v.write_all(&[0; 8]);
let _ = v.write_all(&self.threshold.to_be_bytes()); 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.fast_propagate as u64).to_be_bytes()); // 0x1 is the flag for this

View file

@ -3,7 +3,7 @@
use std::path::Path; use std::path::Path;
use std::str::FromStr; use std::str::FromStr;
use zerotier_network_hypervisor::vl1::Identity; use zerotier_network_hypervisor::vl1::identity::Identity;
use zerotier_utils::io::read_limit; use zerotier_utils::io::read_limit;
/// Returns true if the string starts with [yY1tT] or false for [nN0fF]. /// Returns true if the string starts with [yY1tT] or false for [nN0fF].

View file

@ -105,7 +105,7 @@ mod tests {
use super::*; use super::*;
#[test] #[test]
fn base24_encode_decode() { fn encode_decode() {
let mut tmp = [0xffu8; 256]; let mut tmp = [0xffu8; 256];
for _ in 0..3 { for _ in 0..3 {
let mut s = String::with_capacity(1024); let mut s = String::with_capacity(1024);

115
utils/src/base64.rs Normal file
View file

@ -0,0 +1,115 @@
use crate::error::InvalidParameterError;
/// URL-safe base64 alphabet
const ALPHABET: [u8; 64] = *b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
const ALPHABET_INV: [u8; 256] = [
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 62, 255, 255, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 255, 255,
255, 255, 255, 255, 255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 255, 255, 255, 255, 63,
255, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
];
pub fn encode_into(mut b: &[u8], s: &mut String) {
while b.len() >= 3 {
let bits = (b[0] as usize) | (b[1] as usize).wrapping_shl(8) | (b[2] as usize).wrapping_shl(16);
b = &b[3..];
let (i0, i1, i2, i3) = (bits & 63, bits.wrapping_shr(6) & 63, bits.wrapping_shr(12) & 63, bits.wrapping_shr(18));
s.push(ALPHABET[i0] as char);
s.push(ALPHABET[i1] as char);
s.push(ALPHABET[i2] as char);
s.push(ALPHABET[i3] as char);
}
if b.len() == 2 {
let bits = (b[0] as usize) | (b[1] as usize).wrapping_shl(8);
s.push(ALPHABET[bits & 63] as char);
s.push(ALPHABET[bits.wrapping_shr(6) & 63] as char);
s.push(ALPHABET[bits.wrapping_shr(12)] as char);
} else if b.len() == 1 {
let bits = b[0] as usize;
s.push(ALPHABET[bits & 63] as char);
s.push(ALPHABET[bits.wrapping_shr(6)] as char);
}
}
pub fn decode_into(mut s: &[u8], b: &mut Vec<u8>) -> Result<(), InvalidParameterError> {
while s.len() >= 4 {
let (i0, i1, i2, i3) = (
ALPHABET_INV[s[0] as usize],
ALPHABET_INV[s[1] as usize],
ALPHABET_INV[s[2] as usize],
ALPHABET_INV[s[3] as usize],
);
s = &s[4..];
if (i0 | i1 | i2 | i3) > 64 {
return Err(InvalidParameterError("invalid base64 string"));
}
let bits = (i0 as usize) | (i1 as usize).wrapping_shl(6) | (i2 as usize).wrapping_shl(12) | (i3 as usize).wrapping_shl(18);
b.push((bits & 0xff) as u8);
b.push((bits.wrapping_shr(8) & 0xff) as u8);
b.push((bits.wrapping_shr(16) & 0xff) as u8);
}
match s.len() {
1 => return Err(InvalidParameterError("invalid base64 string")),
2 => {
let (i0, i1) = (ALPHABET_INV[s[0] as usize], ALPHABET_INV[s[1] as usize]);
if (i0 | i1) > 64 {
return Err(InvalidParameterError("invalid base64 string"));
}
let bits = (i0 as usize) | (i1 as usize).wrapping_shl(6);
b.push((bits & 0xff) as u8);
}
3 => {
let (i0, i1, i2) = (ALPHABET_INV[s[0] as usize], ALPHABET_INV[s[1] as usize], ALPHABET_INV[s[2] as usize]);
if (i0 | i1 | i2) > 64 {
return Err(InvalidParameterError("invalid base64 string"));
}
let bits = (i0 as usize) | (i1 as usize).wrapping_shl(6) | (i2 as usize).wrapping_shl(12);
b.push((bits & 0xff) as u8);
b.push((bits.wrapping_shr(8) & 0xff) as u8);
}
_ => {}
}
Ok(())
}
pub fn encode(b: &[u8]) -> String {
let mut tmp = String::with_capacity(((b.len() / 3) * 4) + 3);
encode_into(b, &mut tmp);
tmp
}
pub fn decode(s: &[u8]) -> Result<Vec<u8>, InvalidParameterError> {
let mut tmp = Vec::with_capacity(((s.len() / 4) * 3) + 3);
decode_into(s, &mut tmp)?;
Ok(tmp)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn encode_decode() {
let mut tmp = [0xffu8; 256];
for _ in 0..7 {
let mut s = String::with_capacity(1024);
let mut v: Vec<u8> = Vec::with_capacity(256);
for i in 1..256 {
s.clear();
encode_into(&tmp[..i], &mut s);
//println!("{}", s);
v.clear();
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() {
*b -= 13;
}
}
}
}

View file

@ -8,6 +8,7 @@
pub mod arrayvec; pub mod arrayvec;
pub mod base24; pub mod base24;
pub mod base64;
pub mod blob; pub mod blob;
pub mod buffer; pub mod buffer;
pub mod cast; pub mod cast;

View file

@ -1,19 +1,15 @@
// (c) 2020-2022 ZeroTier, Inc. -- currently proprietary pending actual release and licensing. See LICENSE.md. // (c) 2020-2022 ZeroTier, Inc. -- currently proprietary pending actual release and licensing. See LICENSE.md.
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::str::FromStr;
use std::sync::{Arc, Mutex, RwLock}; use std::sync::{Arc, Mutex, RwLock};
use serde::de::DeserializeOwned; use serde::de::DeserializeOwned;
use serde::Serialize; use serde::Serialize;
use zerotier_crypto::random::next_u32_secure; use zerotier_crypto::random::next_u32_secure;
use zerotier_network_hypervisor::vl1::{Identity, Valid};
use zerotier_utils::io::{fs_restrict_permissions, read_limit, DEFAULT_FILE_IO_READ_LIMIT}; use zerotier_utils::io::{fs_restrict_permissions, read_limit, DEFAULT_FILE_IO_READ_LIMIT};
use zerotier_utils::json::to_json_pretty; use zerotier_utils::json::to_json_pretty;
use crate::vl1service::VL1DataStorage;
pub const AUTH_TOKEN_FILENAME: &'static str = "authtoken.secret"; pub const AUTH_TOKEN_FILENAME: &'static str = "authtoken.secret";
pub const IDENTITY_PUBLIC_FILENAME: &'static str = "identity.public"; pub const IDENTITY_PUBLIC_FILENAME: &'static str = "identity.public";
pub const IDENTITY_SECRET_FILENAME: &'static str = "identity.secret"; pub const IDENTITY_SECRET_FILENAME: &'static str = "identity.secret";
@ -22,46 +18,12 @@ pub const CONFIG_FILENAME: &'static str = "local.conf";
const AUTH_TOKEN_DEFAULT_LENGTH: usize = 48; const AUTH_TOKEN_DEFAULT_LENGTH: usize = 48;
const AUTH_TOKEN_POSSIBLE_CHARS: &'static str = "0123456789abcdefghijklmnopqrstuvwxyz"; const AUTH_TOKEN_POSSIBLE_CHARS: &'static str = "0123456789abcdefghijklmnopqrstuvwxyz";
pub fn load_node_identity(base_path: &Path) -> Option<Valid<Identity>> {
let id_data = read_limit(base_path.join(IDENTITY_SECRET_FILENAME), 4096);
if id_data.is_err() {
return None;
}
let id_data = Identity::from_str(String::from_utf8_lossy(id_data.unwrap().as_slice()).as_ref());
if id_data.is_err() {
return None;
}
Some(Valid::mark_valid(id_data.unwrap()))
}
pub fn save_node_identity(base_path: &Path, id: &Valid<Identity>) -> bool {
assert!(id.secret.is_some());
let id_secret_str = id.to_secret_string();
let id_public_str = id.to_string();
let secret_path = base_path.join(IDENTITY_SECRET_FILENAME);
if std::fs::write(&secret_path, id_secret_str.as_bytes()).is_err() {
return false;
}
assert!(fs_restrict_permissions(&secret_path));
return std::fs::write(base_path.join(IDENTITY_PUBLIC_FILENAME), id_public_str.as_bytes()).is_ok();
}
pub struct DataDir<Config: PartialEq + Eq + Clone + Send + Sync + Default + Serialize + DeserializeOwned + 'static> { pub struct DataDir<Config: PartialEq + Eq + Clone + Send + Sync + Default + Serialize + DeserializeOwned + 'static> {
pub base_path: PathBuf, pub base_path: PathBuf,
config: RwLock<Arc<Config>>, config: RwLock<Arc<Config>>,
authtoken: Mutex<String>, authtoken: Mutex<String>,
} }
impl<Config: PartialEq + Eq + Clone + Send + Sync + Default + Serialize + DeserializeOwned + 'static> VL1DataStorage for DataDir<Config> {
fn load_node_identity(&self) -> Option<Valid<Identity>> {
load_node_identity(self.base_path.as_path())
}
fn save_node_identity(&self, id: &Valid<Identity>) -> bool {
save_node_identity(self.base_path.as_path(), id)
}
}
impl<Config: PartialEq + Eq + Clone + Send + Sync + Default + Serialize + DeserializeOwned + 'static> DataDir<Config> { impl<Config: PartialEq + Eq + Clone + Send + Sync + Default + Serialize + DeserializeOwned + 'static> DataDir<Config> {
pub fn open<P: AsRef<Path>>(path: P) -> std::io::Result<Self> { pub fn open<P: AsRef<Path>>(path: P) -> std::io::Result<Self> {
let base_path = path.as_ref().to_path_buf(); let base_path = path.as_ref().to_path_buf();

View file

@ -9,6 +9,7 @@ use std::time::Duration;
use zerotier_crypto::random; use zerotier_crypto::random;
use zerotier_network_hypervisor::protocol::{PacketBufferFactory, PacketBufferPool}; use zerotier_network_hypervisor::protocol::{PacketBufferFactory, PacketBufferPool};
use zerotier_network_hypervisor::vl1::identity::IdentitySecret;
use zerotier_network_hypervisor::vl1::*; use zerotier_network_hypervisor::vl1::*;
use zerotier_utils::{ms_monotonic, ms_since_epoch}; use zerotier_utils::{ms_monotonic, ms_since_epoch};
@ -20,12 +21,6 @@ use crate::LocalSocket;
/// Update UDP bindings every this many seconds. /// Update UDP bindings every this many seconds.
const UPDATE_UDP_BINDINGS_EVERY_SECS: usize = 10; const UPDATE_UDP_BINDINGS_EVERY_SECS: usize = 10;
/// Trait to implement to provide storage for VL1-related state information.
pub trait VL1DataStorage: Sync + Send {
fn load_node_identity(&self) -> Option<Valid<Identity>>;
fn save_node_identity(&self, id: &Valid<Identity>) -> bool;
}
/// VL1 service that connects to the physical network and hosts an inner protocol like ZeroTier VL2. /// VL1 service that connects to the physical network and hosts an inner protocol like ZeroTier VL2.
/// ///
/// This is the "outward facing" half of a full ZeroTier stack on a normal system. It binds sockets, /// This is the "outward facing" half of a full ZeroTier stack on a normal system. It binds sockets,
@ -33,11 +28,10 @@ pub trait VL1DataStorage: Sync + Send {
/// whatever inner protocol implementation is using it. This would typically be VL2 but could be /// whatever inner protocol implementation is using it. This would typically be VL2 but could be
/// a test harness or just the controller for a controller that runs stand-alone. /// a test harness or just the controller for a controller that runs stand-alone.
pub struct VL1Service<Inner: InnerProtocolLayer + ?Sized + 'static> { pub struct VL1Service<Inner: InnerProtocolLayer + ?Sized + 'static> {
pub node: Node<Self>,
state: RwLock<VL1ServiceMutableState>, state: RwLock<VL1ServiceMutableState>,
vl1_data_storage: Arc<dyn VL1DataStorage>,
inner: Arc<Inner>, inner: Arc<Inner>,
buffer_pool: Arc<PacketBufferPool>, buffer_pool: Arc<PacketBufferPool>,
node_container: Option<Node<Self>>, // never None, set in new()
} }
struct VL1ServiceMutableState { struct VL1ServiceMutableState {
@ -48,25 +42,21 @@ struct VL1ServiceMutableState {
} }
impl<Inner: InnerProtocolLayer + ?Sized + 'static> VL1Service<Inner> { impl<Inner: InnerProtocolLayer + ?Sized + 'static> VL1Service<Inner> {
pub fn new(vl1_data_storage: Arc<dyn VL1DataStorage>, inner: Arc<Inner>, settings: VL1Settings) -> Result<Arc<Self>, Box<dyn Error>> { pub fn new(identity: IdentitySecret, inner: Arc<Inner>, settings: VL1Settings) -> Result<Arc<Self>, Box<dyn Error>> {
let mut service = Self { let service = Arc::new(Self {
node: Node::<Self>::new(identity),
state: RwLock::new(VL1ServiceMutableState { state: RwLock::new(VL1ServiceMutableState {
daemons: Vec::with_capacity(2), daemons: Vec::with_capacity(2),
udp_sockets: HashMap::with_capacity(8), udp_sockets: HashMap::with_capacity(8),
settings, settings,
running: true, running: true,
}), }),
vl1_data_storage,
inner, inner,
buffer_pool: Arc::new(PacketBufferPool::new( buffer_pool: Arc::new(PacketBufferPool::new(
std::thread::available_parallelism().map_or(2, |c| c.get() + 2), std::thread::available_parallelism().map_or(2, |c| c.get() + 2),
PacketBufferFactory::new(), PacketBufferFactory::new(),
)), )),
node_container: None, });
};
service.node_container.replace(Node::new(&service, true, false)?);
let service = Arc::new(service);
let mut daemons = Vec::new(); let mut daemons = Vec::new();
let s = service.clone(); let s = service.clone();
@ -78,12 +68,6 @@ impl<Inner: InnerProtocolLayer + ?Sized + 'static> VL1Service<Inner> {
Ok(service) Ok(service)
} }
#[inline(always)]
pub fn node(&self) -> &Node<Self> {
debug_assert!(self.node_container.is_some());
unsafe { self.node_container.as_ref().unwrap_unchecked() }
}
pub fn bound_udp_ports(&self) -> Vec<u16> { pub fn bound_udp_ports(&self) -> Vec<u16> {
self.state.read().unwrap().udp_sockets.keys().cloned().collect() self.state.read().unwrap().udp_sockets.keys().cloned().collect()
} }
@ -173,7 +157,7 @@ impl<Inner: InnerProtocolLayer + ?Sized + 'static> VL1Service<Inner> {
self.update_udp_bindings(); self.update_udp_bindings();
} }
udp_binding_check_every = udp_binding_check_every.wrapping_add(1); udp_binding_check_every = udp_binding_check_every.wrapping_add(1);
std::thread::sleep(self.node().do_background_tasks(self.as_ref())); std::thread::sleep(self.node.do_background_tasks(self.as_ref()));
} }
} }
} }
@ -187,7 +171,7 @@ impl<Inner: InnerProtocolLayer + ?Sized + 'static> UdpPacketHandler for VL1Servi
source_address: &InetAddress, source_address: &InetAddress,
packet: zerotier_network_hypervisor::protocol::PooledPacketBuffer, packet: zerotier_network_hypervisor::protocol::PooledPacketBuffer,
) { ) {
self.node().handle_incoming_physical_packet( self.node.handle_incoming_physical_packet(
self.as_ref(), self.as_ref(),
self.inner.as_ref(), self.inner.as_ref(),
&Endpoint::IpUdp(source_address.clone()), &Endpoint::IpUdp(source_address.clone()),
@ -216,22 +200,6 @@ impl<Inner: InnerProtocolLayer + ?Sized + 'static> ApplicationLayer for VL1Servi
socket.is_valid() socket.is_valid()
} }
#[inline]
fn should_respond_to(&self, _: &Valid<Identity>) -> bool {
// TODO: provide a way for the user of VL1Service to control this
true
}
#[inline]
fn load_node_identity(&self) -> Option<Valid<Identity>> {
self.vl1_data_storage.load_node_identity()
}
#[inline]
fn save_node_identity(&self, id: &Valid<Identity>) -> bool {
self.vl1_data_storage.save_node_identity(id)
}
#[inline] #[inline]
fn get_buffer(&self) -> zerotier_network_hypervisor::protocol::PooledPacketBuffer { fn get_buffer(&self) -> zerotier_network_hypervisor::protocol::PooledPacketBuffer {
self.buffer_pool.get() self.buffer_pool.get()