This commit is contained in:
Adam Ierymenko 2022-11-28 12:44:29 -05:00
parent 7522282c2e
commit 42178d1716
7 changed files with 80 additions and 53 deletions

View file

@ -123,6 +123,22 @@ impl Controller {
}));
}
/// Launched as a task when the DB informs us of a change.
async fn handle_change_notification(self: Arc<Self>, change: Change) {
match change {
Change::NetworkCreated(_) => {}
Change::NetworkChanged(_, _) => {}
Change::NetworkDeleted(_, _) => {} // TODO: somehow poison whole network
Change::MemberCreated(_) => {}
Change::MemberChanged(old_member, new_member) => {
if !new_member.authorized() && old_member.authorized() {
self.deauthorize_member(&new_member).await;
}
}
Change::MemberDeleted(member) => self.deauthorize_member(&member).await,
}
}
/// Compose and send network configuration packet (either V1 or V2)
fn send_network_config(
&self,
@ -182,8 +198,8 @@ impl Controller {
}
}
/// Send one or more revocation object(s) to a peer.
fn send_revocations(&self, peer: &Peer, mut revocations: Vec<Revocation>) {
/// Send one or more revocation object(s) to a peer. The provided vector is drained.
fn v1_proto_send_revocations(&self, peer: &Peer, revocations: &mut Vec<Revocation>) {
if let Some(host_system) = self.service.read().unwrap().upgrade() {
let time_ticks = ms_monotonic();
while !revocations.is_empty() {
@ -218,8 +234,26 @@ impl Controller {
}
}
/// Called when the DB informs us of a change.
async fn handle_change_notification(self: Arc<Self>, change: Change) {}
async fn deauthorize_member(&self, member: &Member) {
let time_clock = ms_since_epoch();
let mut revocations = Vec::with_capacity(1);
if let Ok(all_network_members) = self.database.list_members(member.network_id).await {
for m in all_network_members.iter() {
if member.node_id != *m {
if let Some(peer) = self.service.read().unwrap().upgrade().and_then(|s| s.node().peer(*m)) {
if peer.is_v2() {
todo!();
} else {
revocations.clear();
Revocation::new(member.network_id, time_clock, member.node_id, *m, &self.local_identity, false)
.map(|r| revocations.push(r));
self.v1_proto_send_revocations(&peer, &mut revocations);
}
}
}
}
}
}
/// Attempt to create a network configuration and return the result.
///
@ -231,11 +265,11 @@ impl Controller {
///
/// An error is only returned if a database or other unusual error occurs. Otherwise a rejection
/// reason is returned with None or an acceptance reason with a network configuration is returned.
async fn get_network_config(
async fn authorize(
self: &Arc<Self>,
source_identity: &Verified<Identity>,
network_id: NetworkId,
now: i64,
time_clock: i64,
) -> Result<(AuthorizationResult, Option<NetworkConfig>, Option<Vec<vl2::v1::Revocation>>), Box<dyn Error + Send + Sync>> {
let network = self.database.get_network(network_id).await?;
if network.is_none() {
@ -246,8 +280,8 @@ impl Controller {
let mut member = self.database.get_member(network_id, source_identity.address).await?;
let mut member_changed = false;
// WARNING: this is where members are verified before they get admitted to a network. Read and edit
// very carefully!
// SECURITY WARNING: this is a critical code path where members of networks are authorized.
// Read and modify with extreme care.
// If we have a member object and a pinned identity, check to make sure it matches. Also accept
// upgraded identities to replace old versions if they are properly formed and inherit.
@ -264,7 +298,9 @@ impl Controller {
}
} else if let Some(pinned_fingerprint) = member.identity_fingerprint.as_ref() {
if pinned_fingerprint.as_bytes().eq(&source_identity.fingerprint) {
// Learn the FULL identity if the fingerprint is pinned and they match.
// Learn the FULL identity if the fingerprint is pinned and they match. This
// lets us add membrers by address/fingerprint with full SHA384 identity
// verification instead of just the address.
let _ = member.identity.replace(source_identity.clone_without_secret());
member_changed = true;
} else {
@ -273,7 +309,6 @@ impl Controller {
}
}
// This will be the final verdict after everything has been checked.
let mut authorization_result = AuthorizationResult::Rejected;
// This is the main "authorized" flag on the member record. If it is true then
@ -297,7 +332,7 @@ impl Controller {
// TODO: check token authorization
} else {
authorization_result = AuthorizationResult::ApprovedOnPublicNetwork;
member.as_mut().unwrap().last_authorized_time = Some(now);
member.as_mut().unwrap().last_authorized_time = Some(time_clock);
member_authorized = true;
member_changed = true;
}
@ -334,7 +369,7 @@ impl Controller {
nc.name = network.name.clone();
nc.private = network.private;
nc.timestamp = now;
nc.timestamp = time_clock;
nc.multicast_limit = network.multicast_limit.unwrap_or(DEFAULT_MULTICAST_LIMIT as u32);
nc.multicast_like_expire = Some(protocol::VL2_DEFAULT_MULTICAST_LIKE_EXPIRE as u32);
nc.mtu = network.mtu.unwrap_or(ZEROTIER_VIRTUAL_NETWORK_DEFAULT_MTU as u16);
@ -348,10 +383,10 @@ impl Controller {
// the overhead (bandwidth and CPU) of generating these.
if let Some(com) =
vl2::v1::CertificateOfMembership::new(&self.local_identity, network_id, &source_identity, now, credential_ttl)
vl2::v1::CertificateOfMembership::new(&self.local_identity, network_id, &source_identity, time_clock, credential_ttl)
{
let mut v1cred = V1Credentials {
revision: now as u64,
revision: time_clock as u64,
max_delta: credential_ttl,
certificate_of_membership: com,
certificates_of_ownership: Vec::new(),
@ -359,7 +394,7 @@ impl Controller {
};
if !nc.static_ips.is_empty() {
let mut coo = vl2::v1::CertificateOfOwnership::new(network_id, now, source_identity.address);
let mut coo = vl2::v1::CertificateOfOwnership::new(network_id, time_clock, source_identity.address);
for ip in nc.static_ips.iter() {
coo.add_ip(ip);
}
@ -370,39 +405,33 @@ impl Controller {
}
for (id, value) in member.tags.iter() {
let tag = vl2::v1::Tag::new(*id, *value, &self.local_identity, network_id, &source_identity, now);
let tag = vl2::v1::Tag::new(*id, *value, &self.local_identity, network_id, &source_identity, time_clock);
if tag.is_none() {
return Ok((AuthorizationResult::RejectedDueToError, None, None));
}
let _ = v1cred.tags.insert(*id, tag.unwrap());
}
nc.v1_credentials = Some(v1cred);
// For anyone who has been deauthorized but is still in the window, send revocations.
if let Ok(deauthed_members_still_in_window) = self
.database
.list_members_deauthorized_after(network.id, now - (credential_ttl as i64))
.list_members_deauthorized_after(network.id, time_clock - (credential_ttl as i64))
.await
{
if !deauthed_members_still_in_window.is_empty() {
let mut revs = Vec::with_capacity(deauthed_members_still_in_window.len());
for dm in deauthed_members_still_in_window.iter() {
if let Some(rev) = Revocation::new(
network_id,
now,
*dm,
source_identity.address,
&self.local_identity,
vl2::v1::CredentialType::CertificateOfMembership,
false,
) {
if let Some(rev) =
Revocation::new(network_id, time_clock, *dm, source_identity.address, &self.local_identity, false)
{
revs.push(rev);
}
}
revocations = Some(revs);
}
}
nc.v1_credentials = Some(v1cred);
} else {
return Ok((AuthorizationResult::RejectedDueToError, None, None));
}
@ -493,12 +522,12 @@ impl InnerProtocol for Controller {
let node_fingerprint = Blob::from(source.identity.fingerprint);
let now = ms_since_epoch();
let (result, config) = match self2.get_network_config(&source.identity, network_id, now).await {
let (result, config) = match self2.authorize(&source.identity, network_id, now).await {
Result::Ok((result, Some(config), revocations)) => {
//println!("{}", serde_yaml::to_string(&config).unwrap());
self2.send_network_config(source.as_ref(), &config, Some(message_id));
if let Some(revocations) = revocations {
self2.send_revocations(source.as_ref(), revocations);
if let Some(mut revocations) = revocations {
self2.v1_proto_send_revocations(source.as_ref(), &mut revocations);
}
(result, Some(config))
}

View file

@ -13,7 +13,7 @@ use crate::model::*;
pub enum Change {
NetworkCreated(Network),
NetworkChanged(Network, Network),
NetworkDeleted(Network),
NetworkDeleted(Network, Vec<Member>),
MemberCreated(Member),
MemberChanged(Member, Member),
MemberDeleted(Member),

View file

@ -119,13 +119,11 @@ impl FileDatabase {
if deleted.is_some() {
match record_type {
RecordType::Network => {
if let Some((network, mut members)) =
if let Some((network, members)) =
db.cache.on_network_deleted(network_id)
{
for m in members.drain(..) {
let _ = db.change_sender.send(Change::MemberDeleted(m));
}
let _ = db.change_sender.send(Change::NetworkDeleted(network));
let _ =
db.change_sender.send(Change::NetworkDeleted(network, members));
}
}
RecordType::Member => {

View file

@ -81,7 +81,6 @@ impl AuthorizationResult {
}
impl ToString for AuthorizationResult {
#[inline(always)]
fn to_string(&self) -> String {
self.as_str().to_string()
}

View file

@ -158,15 +158,15 @@ pub enum Error {
/// Data object is too large to send, even fragmented.
DataTooLarge,
/// An unexpected error occurred elsewhere in the code (may indicate a bug).
OtherError(Box<dyn std::error::Error>),
/// An unexpected I/O error such as a buffer overrun occurred.
IoError(std::io::Error),
}
impl From<std::io::Error> for Error {
#[cold]
#[inline(never)]
fn from(e: std::io::Error) -> Self {
Self::OtherError(Box::new(e))
Self::IoError(e)
}
}
@ -184,7 +184,7 @@ impl std::fmt::Display for Error {
Self::UnknownProtocolVersion => f.write_str("UnknownProtocolVersion"),
Self::DataBufferTooSmall => f.write_str("DataBufferTooSmall"),
Self::DataTooLarge => f.write_str("DataTooLarge"),
Self::OtherError(e) => f.write_str(format!("OtherError({})", e.to_string()).as_str()),
Self::IoError(e) => f.write_str(format!("OtherError({})", e.to_string()).as_str()),
}
}
}
@ -261,7 +261,7 @@ impl From<SessionId> for u64 {
///
/// This holds the data structures used to defragment incoming packets that are not associated with an
/// existing session, which would be new attempts to create sessions. Typically one of these is associated
/// with a single listen socket or other inbound endpoint.
/// with a single listen socket, local bound port, or other inbound endpoint.
pub struct ReceiveContext<H: Host> {
initial_offer_defrag: Mutex<RingBufferMap<u32, GatherArray<H::IncomingPacketBuffer, KEY_EXCHANGE_MAX_FRAGMENTS>, 1024, 128>>,
incoming_init_header_check_cipher: Aes,
@ -348,6 +348,7 @@ pub struct Session<H: Host> {
state: RwLock<SessionMutableState>, // Mutable parts of state (other than defrag buffers)
remote_s_public_hash: [u8; 48], // SHA384(remote static public key blob)
remote_s_public_p384: [u8; P384_PUBLIC_KEY_SIZE], // Remote NIST P-384 static public key
defrag: Mutex<RingBufferMap<u32, GatherArray<H::IncomingPacketBuffer, MAX_FRAGMENTS>, 16, 4>>,
}

View file

@ -328,6 +328,8 @@ impl Identity {
}
/// Returns true if this identity was upgraded from another older version.
///
/// This does NOT validate either identity. Ensure that validation has been performed.
pub fn is_upgraded_from(&self, other: &Identity) -> bool {
self.address == other.address
&& self.x25519 == other.x25519

View file

@ -1,8 +1,8 @@
use std::io::Write;
use zerotier_crypto::random;
use zerotier_crypto::verified::Verified;
use zerotier_utils::arrayvec::ArrayVec;
use zerotier_utils::blob::Blob;
use serde::{Deserialize, Serialize};
@ -17,8 +17,7 @@ pub struct Revocation {
pub threshold: i64,
pub target: Address,
pub issued_to: Address,
pub signature: Blob<96>,
pub type_being_revoked: u8,
pub signature: ArrayVec<u8, 96>,
pub fast_propagate: bool,
}
@ -28,8 +27,7 @@ impl Revocation {
threshold: i64,
target: Address,
issued_to: Address,
signer: &Identity,
type_being_revoked: CredentialType,
signer: &Verified<Identity>,
fast_propagate: bool,
) -> Option<Self> {
let mut r = Self {
@ -38,8 +36,7 @@ impl Revocation {
threshold,
target,
issued_to,
signature: Blob::default(),
type_being_revoked: type_being_revoked as u8,
signature: ArrayVec::new(),
fast_propagate,
};
if let Some(sig) = signer.sign(r.internal_to_bytes(true, signer.address).as_bytes(), true) {
@ -64,14 +61,15 @@ impl Revocation {
let _ = v.write_all(&(self.fast_propagate as u64).to_be_bytes()); // 0x1 is the flag for this
let _ = v.write_all(&self.target.to_bytes());
let _ = v.write_all(&signed_by.to_bytes());
v.push(self.type_being_revoked);
v.push(CredentialType::CertificateOfMembership as u8);
if for_sign {
let _ = v.write_all(&[0x7f; 8]);
} else {
v.push(1); // ed25519 signature
let _ = v.write_all(&[0u8, 96u8]);
let _ = v.write_all(self.signature.as_bytes());
assert!(self.signature.len() <= 255);
let _ = v.write_all(&[0u8, self.signature.len() as u8]);
let _ = v.write_all(self.signature.as_ref());
}
v