diff --git a/protocol.md b/PROTOCOL.md similarity index 95% rename from protocol.md rename to PROTOCOL.md index 849cc4cde..326e373a2 100644 --- a/protocol.md +++ b/PROTOCOL.md @@ -66,7 +66,6 @@ NOP, as the name suggests, does nothing. Any payload is ignored. | [2] u16 | Length of encrypted Dictionary in bytes | | Dictionary | Key/value dictionary containing additional fields | | -- | -- END of AES-256-CTR encrypted section -- | -| [48] [u8; 48] | HMAC-SHA384 extended strength MAC | HELLO establishes a full session with another peer and carries information such as protocol and software versions, the full identity of the peer, and ephemeral keys for forward secrecy. Without a HELLO exchange only limited communication with the most conservative assumptions is possible, and communication without a session may be completely removed in the future. (It's only allowed now for backward compatibility with ZeroTier 1.x, and must be disabled in FIPS mode.) @@ -91,9 +90,6 @@ OK(HELLO) response payload, which must be sent if the HELLO receipient wishes to | [2] u16 | *(reserved)* (set to zero for legacy reasons) | | [2] u16 | Length of encrypted Dictionary in bytes | | Dictionary | Key/value dictionary containing additional fields | -| [48] [u8; 48] | HMAC-SHA384 extended strength MAC | - -HMAC-SHA384 authentication is computed over the payload of HELLO and OK(HELLO). For HELLO it is computed after AES-256-CTR encryption is applied to the dictionary section. and is checked before anything is done with a payload. For OK(HELLO) it is computed prior to normal packet armoring and is itself included in the encrypted payload. Recommended dictionary fields in both HELLO and OK(HELLO): @@ -118,13 +114,16 @@ Optional dictionary fields that can be included in either HELLO or OK(HELLO): | -------------------- | --- | ------------ | ------------------------------------------------------------ | | SYS_ARCH | `Sa` | string | Host architecture (e.g. x86_64, aarch64) | | SYS_BITS | `Sb` | u64 | sizeof(pointer), e.g. 32 or 64 | -| OS_NAME | `On` | string | Name of host operating system | -| OS_VERSION | `Ov` | string | Operating system version | +| OS_NAME | `So` | string | Name of host operating system | +| OS_VERSION | `Sv` | string | Operating system version | +| OS_VARIANT | `St` | string | Operating system variant (e.g. Linux distribution) | | VENDOR | `V` | string | Node software vendor if not ZeroTier, Inc. | | FLAGS | `+` | string | Flags (see below) | FLAGS is a string that can contain the following boolean flags: `F` to indicate that the node is running in FIPS compliant mode, and `w` to indicate that the node is a "wimp." "Wimpy" nodes are things like mobile phones, and this flag can be used to exempt these devices from selection for any intensive role (such as use in VL2 to propagate multicasts). +System information such as OS_NAME is currently only sent to roots and not to any other node. This allows roots to collect a bit of very generic statistical and diagnostic telemtry about the nodes using them. + #### 0x02 / ERROR | [Size] Type | Description | diff --git a/network-hypervisor/src/crypto/poly1305.rs b/network-hypervisor/src/crypto/poly1305.rs index e00691a5b..a5f49cb9f 100644 --- a/network-hypervisor/src/crypto/poly1305.rs +++ b/network-hypervisor/src/crypto/poly1305.rs @@ -27,10 +27,4 @@ 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); - } } diff --git a/network-hypervisor/src/lib.rs b/network-hypervisor/src/lib.rs index e74876654..42c5687e6 100644 --- a/network-hypervisor/src/lib.rs +++ b/network-hypervisor/src/lib.rs @@ -8,3 +8,46 @@ pub const VERSION_MAJOR: u8 = 1; pub const VERSION_MINOR: u8 = 99; pub const VERSION_REVISION: u8 = 1; pub const VERSION_STR: &'static str = "1.99.1"; + +/* + * Protocol versions + * + * 1 - 0.2.0 ... 0.2.5 + * 2 - 0.3.0 ... 0.4.5 + * + Added signature and originating peer to multicast frame + * + Double size of multicast frame bloom filter + * 3 - 0.5.0 ... 0.6.0 + * + Yet another multicast redesign + * + New crypto completely changes key agreement cipher + * 4 - 0.6.0 ... 1.0.6 + * + BREAKING CHANGE: New identity format based on hashcash design + * 5 - 1.1.0 ... 1.1.5 + * + Supports echo + * + Supports in-band world (root server definition) updates + * + Clustering! (Though this will work with protocol v4 clients.) + * + Otherwise backward compatible with protocol v4 + * 6 - 1.1.5 ... 1.1.10 + * + Network configuration format revisions including binary values + * 7 - 1.1.10 ... 1.1.17 + * + Introduce trusted paths for local SDN use + * 8 - 1.1.17 ... 1.2.0 + * + Multipart network configurations for large network configs + * + Tags and Capabilities + * + inline push of CertificateOfMembership deprecated + * 9 - 1.2.0 ... 1.2.14 + * 10 - 1.4.0 ... 1.4.6 + * + Contained early pre-alpha versions of multipath, which are deprecated + * 11 - 1.6.0 ... 2.0.0 + * + Supports AES-GMAC-SIV symmetric crypto, backported from v2 tree. + * 20 - 2.0.0 ... CURRENT + * + New more WAN-efficient P2P-assisted multicast algorithm + * + HELLO and OK(HELLO) include an extra HMAC to harden authentication + * + HELLO and OK(HELLO) carry meta-data in a dictionary that's encrypted + * + Forward secrecy, key lifetime management + * + Old planet/moon stuff is DEAD! Independent roots are easier. + * + AES encryption with the SIV construction AES-GMAC-SIV + * + New combined Curve25519/NIST P-384 identity type (type 1) + * + Short probe packets to reduce probe bandwidth + * + More aggressive NAT traversal techniques for IPv4 symmetric NATs + */ +pub const VERSION_PROTO: u8 = 20; diff --git a/network-hypervisor/src/vl1/buffer.rs b/network-hypervisor/src/vl1/buffer.rs index 6b056c841..e0b948789 100644 --- a/network-hypervisor/src/vl1/buffer.rs +++ b/network-hypervisor/src/vl1/buffer.rs @@ -68,7 +68,9 @@ impl Buffer { #[inline(always)] pub fn clear(&mut self) { - unsafe { write_bytes((self as *mut Self).cast::(), 0, L); } + let prev_len = self.0; + self.0 = 0; + self.1[0..prev_len].fill(0); } #[inline(always)] diff --git a/network-hypervisor/src/vl1/constants.rs b/network-hypervisor/src/vl1/constants.rs index 86fe12ffa..a0af194a5 100644 --- a/network-hypervisor/src/vl1/constants.rs +++ b/network-hypervisor/src/vl1/constants.rs @@ -38,56 +38,6 @@ pub const PACKET_SIZE_MIN: usize = PACKET_HEADER_SIZE + 1; /// Maximum size of an entire packet. pub const PACKET_SIZE_MAX: usize = PACKET_HEADER_SIZE + PACKET_PAYLOAD_SIZE_MAX; -/// Index of packet verb after header. -pub const PACKET_VERB_INDEX: usize = 27; - -/// Index of destination in both fragment and full packet headers. -pub const PACKET_DESTINATION_INDEX: usize = 8; - -/// Mask to select cipher from header flags field. -pub const HEADER_FLAGS_FIELD_MASK_CIPHER: u8 = 0x30; - -/// Mask to select packet hops from header flags field. -pub const HEADER_FLAGS_FIELD_MASK_HOPS: u8 = 0x07; - -/// Mask to select packet hops from header flags field. -pub const HEADER_FLAGS_FIELD_MASK_HIDE_HOPS: u8 = 0xf8; - -/// Index of hops/flags field -pub const HEADER_FLAGS_FIELD_INDEX: usize = 18; - -/// Packet is not encrypted but contains a Poly1305 MAC of the plaintext. -/// Poly1305 is initialized with Salsa20/12 in the same manner as SALSA2012_POLY1305. -pub const CIPHER_NOCRYPT_POLY1305: u8 = 0x00; - -/// Packet is encrypted and authenticated with Salsa20/12 and Poly1305. -/// Construction is the same as that which is used in the NaCl secret box functions. -pub const CIPHER_SALSA2012_POLY1305: u8 = 0x10; - -/// Formerly 'NONE' which is deprecated; reserved for future use. -pub const CIPHER_RESERVED: u8 = 0x20; - -/// Packet is encrypted and authenticated with AES-GMAC-SIV (AES-256). -pub const CIPHER_AES_GMAC_SIV: u8 = 0x30; - -/// Header (outer) flag indicating that this packet has additional fragments. -pub const HEADER_FLAG_FRAGMENTED: u8 = 0x40; - -/// Minimum size of a fragment. -pub const FRAGMENT_SIZE_MIN: usize = 16; - -/// Size of fragment header after which data begins. -pub const FRAGMENT_HEADER_SIZE: usize = 16; - -/// Maximum allowed number of fragments. -pub const FRAGMENT_COUNT_MAX: usize = 8; - -/// Index of packet fragment indicator byte to detect fragments. -pub const FRAGMENT_INDICATOR_INDEX: usize = 13; - -/// Byte found at FRAGMENT_INDICATOR_INDEX to indicate a fragment. -pub const FRAGMENT_INDICATOR: u8 = 0xff; - /// Maximum number of inbound fragmented packets to handle at once per path. /// This is a sanity limit to prevent memory exhaustion due to DOS attacks or broken peers. pub const FRAGMENT_MAX_INBOUND_PACKETS_PER_PATH: usize = 256; @@ -95,21 +45,6 @@ pub const FRAGMENT_MAX_INBOUND_PACKETS_PER_PATH: usize = 256; /// Time after which an incomplete fragmented packet expires. pub const FRAGMENT_EXPIRATION: i64 = 1500; -/// Verb (inner) flag indicating that the packet's payload (after the verb) is LZ4 compressed. -pub const VERB_FLAG_COMPRESSED: u8 = 0x80; - -/// Mask to get only the verb from the verb + verb flags byte. -pub const VERB_MASK: u8 = 0x1f; - -/// Maximum number of verbs that the protocol can support. -pub const VERB_MAX_COUNT: usize = 32; - -/// Maximum number of packet hops allowed by the protocol. -pub const PROTOCOL_MAX_HOPS: u8 = 7; - -/// Maximum number of hops to allow. -pub const FORWARD_MAX_HOPS: u8 = 3; - /// Frequency for WHOIS retries pub const WHOIS_RETRY_INTERVAL: i64 = 1000; diff --git a/network-hypervisor/src/vl1/locator.rs b/network-hypervisor/src/vl1/locator.rs index 224e94b1f..6bace8080 100644 --- a/network-hypervisor/src/vl1/locator.rs +++ b/network-hypervisor/src/vl1/locator.rs @@ -149,6 +149,15 @@ impl Locator { signature: signature.to_vec(), }) } + + pub fn to_bytes(&self) -> Option> { + let mut buf: Buffer<{ PACKET_SIZE_MAX }> = Buffer::new(); + if self.marshal_internal(&mut buf, false).is_ok() { + Some(buf.as_bytes().to_vec()) + } else { + None + } + } } impl Hash for Locator { diff --git a/network-hypervisor/src/vl1/node.rs b/network-hypervisor/src/vl1/node.rs index 17442741a..1532c8ee6 100644 --- a/network-hypervisor/src/vl1/node.rs +++ b/network-hypervisor/src/vl1/node.rs @@ -43,11 +43,11 @@ pub trait VL1CallerInterface { 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]>; + fn load_node_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]); + fn save_node_identity(&self, id: &Identity, public: &[u8], secret: &[u8]); /// Load this node's latest locator. fn load_locator(&self) -> Option<&[u8]>; @@ -55,21 +55,6 @@ pub trait VL1CallerInterface { /// 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 @@ -124,16 +109,18 @@ struct BackgroundTaskIntervals { } pub struct Node { - instance_id: u64, + pub(crate) instance_id: u64, identity: Identity, intervals: Mutex, - locator: Mutex>, + locator: Mutex>>, paths: DashMap>, peers: DashMap>, roots: Mutex>>, whois: WhoisQueue, buffer_pool: Pool, PooledBufferFactory<{ PACKET_SIZE_MAX }>>, secure_prng: SecureRandom, + pub(crate) fips_mode: bool, + pub(crate) wimp: bool, } impl Node { @@ -143,13 +130,13 @@ impl Node { /// no identity is currently stored in the data store. pub fn new(ci: &CI, auto_generate_identity_type: Option) -> Result { let id = { - let id_str = ci.load_identity(); + let id_str = ci.load_node_identity(); if id_str.is_none() { if auto_generate_identity_type.is_none() { - return Err(InvalidParameterError("no identity found and auto-generate not specified")); + return Err(InvalidParameterError("no identity found and auto-generate not enabled")); } 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()); + ci.save_node_identity(&id, id.to_string().as_bytes(), id.to_secret_string().as_bytes()); id } } else { @@ -174,6 +161,8 @@ impl Node { whois: WhoisQueue::new(), buffer_pool: Pool::new(64, PooledBufferFactory), secure_prng: SecureRandom::get(), + fips_mode: false, + wimp: false, }) } @@ -189,6 +178,12 @@ impl Node { &self.identity } + /// Get this node's current locator or None if no locator created. + #[inline(always)] + pub fn locator(&self) -> Option> { + self.locator.lock().clone() + } + /// Get a reusable packet buffer. /// The buffer will automatically be returned to the pool if it is dropped. pub fn get_packet_buffer(&self) -> PacketBuffer { @@ -210,6 +205,17 @@ impl Node { v } + /// Get the current best root peer that we should use for WHOIS, relaying, etc. + pub(crate) fn root(&self) -> Option> { + self.roots.lock().first().map(|p| p.clone()) + } + + /// Determine if a given peer is a root. + pub(crate) fn is_root(&self, peer: &Peer) -> bool { + let pptr = peer as *const Peer; + self.roots.lock().iter().any(|p| Arc::as_ptr(p) == pptr) + } + /// 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 @@ -309,11 +315,6 @@ impl Node { } } - /// Get the current best root peer that we should use for WHOIS, relaying, etc. - pub(crate) fn root(&self) -> Option> { - self.roots.lock().first().map(|p| p.clone()) - } - /// Get the canonical Path object for a given endpoint and local socket information. /// /// This is a canonicalizing function that returns a unique path object for every tuple diff --git a/network-hypervisor/src/vl1/peer.rs b/network-hypervisor/src/vl1/peer.rs index 9caf4ad2a..45126930c 100644 --- a/network-hypervisor/src/vl1/peer.rs +++ b/network-hypervisor/src/vl1/peer.rs @@ -12,11 +12,13 @@ use crate::crypto::random::next_u64_secure; use crate::crypto::salsa::Salsa; use crate::crypto::secret::Secret; use crate::util::pool::{Pool, PoolFactory}; -use crate::vl1::{Identity, Path, Endpoint}; +use crate::vl1::{Identity, Path, Endpoint, InetAddress, Dictionary}; use crate::vl1::buffer::Buffer; use crate::vl1::constants::*; use crate::vl1::node::*; use crate::vl1::protocol::*; +use crate::{VERSION_PROTO, VERSION_MAJOR, VERSION_MINOR, VERSION_REVISION}; +use crate::crypto::hash::SHA384; struct AesGmacSivPoolFactory(Secret<48>, Secret<48>); @@ -86,6 +88,9 @@ pub struct Peer { // Paths sorted in ascending order of quality / preference. paths: Mutex>>, + // Local external address most recently reported by this peer (IP transport only). + reported_local_ip: Mutex>, + // Statistics and times of events. last_send_time_ticks: AtomicI64, last_receive_time_ticks: AtomicI64, @@ -124,6 +129,16 @@ fn salsa_derive_per_packet_key(key: &Secret<48>, header: &PacketHeader, packet_s k } +/// Create initialized instances of Salsa20/12 and Poly1305 for a packet. +fn salsa_poly_create(secret: &PeerSecret, header: &PacketHeader, packet_size: usize) -> (Salsa, Poly1305) { + let key = salsa_derive_per_packet_key(&secret.secret, header, payload.len()); + let mut salsa = Salsa::new(&key.0[0..32], header.id_bytes(), true).unwrap(); + let mut poly1305_key = [0_u8; 32]; + salsa.crypt_in_place(&mut poly1305_key); + let mut poly = Poly1305::new(&poly1305_key).unwrap(); + (salsa, poly) +} + impl Peer { pub(crate) const INTERVAL: i64 = PEER_SERVICE_INTERVAL; @@ -150,6 +165,7 @@ impl Peer { ephemeral_secret: Mutex::new(None), ephemeral_pair: Mutex::new(None), paths: Mutex::new(Vec::new()), + reported_local_ip: Mutex::new(None), last_send_time_ticks: AtomicI64::new(0), last_receive_time_ticks: AtomicI64::new(0), last_forward_time_ticks: AtomicI64::new(0), @@ -165,6 +181,16 @@ impl Peer { }) } + /// Get the next packet initialization vector. + /// + /// For Salsa20/12 with Poly1305 this is the packet ID. For AES-GMAC-SIV the packet ID is + /// not known until the packet is encrypted, since it's the first 64 bits of the GMAC-SIV + /// tag. + #[inline(always)] + pub(crate) fn next_packet_iv(&self) -> PacketID { + self.packet_iv_counter.fetch_add(1, Ordering::Relaxed) + } + /// Receive, decrypt, authenticate, and process an incoming packet from this peer. /// If the packet comes in multiple fragments, the fragments slice should contain all /// those fragments after the main packet header and first chunk. @@ -185,12 +211,9 @@ impl Peer { } // FIPS note: for FIPS purposes the HMAC-SHA384 tag at the end of V2 HELLOs - // will be considered the "real" handshake authentication. - let key = salsa_derive_per_packet_key(&secret.secret, header, payload.len()); - let mut salsa = Salsa::new(&key.0[0..32], header.id_bytes(), true).unwrap(); - let mut poly1305_key = [0_u8; 32]; - salsa.crypt_in_place(&mut poly1305_key); - let mut poly = Poly1305::new(&poly1305_key).unwrap(); + // will be considered the "real" handshake authentication. This authentication + // is technically deprecated in V2. + let (_, mut poly) = salsa_poly_create(secret, header, packet.len()); poly.update(payload.as_bytes()); if poly.finish()[0..8].eq(&header.message_auth) { @@ -203,12 +226,7 @@ impl Peer { } CIPHER_SALSA2012_POLY1305 => { - let key = salsa_derive_per_packet_key(&secret.secret, header, payload.len()); - let mut salsa = Salsa::new(&key.0[0..32], header.id_bytes(), true).unwrap(); - let mut poly1305_key = [0_u8; 32]; - salsa.crypt_in_place(&mut poly1305_key); - let mut poly = Poly1305::new(&poly1305_key).unwrap(); - + let (mut salsa, mut poly) = salsa_poly_create(secret, header, packet.len()); poly.update(packet_frag0_payload_bytes); let _ = payload.append_and_init_bytes(packet_frag0_payload_bytes.len(), |b| salsa.crypt(packet_frag0_payload_bytes, b)); for f in fragments.iter() { @@ -217,7 +235,6 @@ impl Peer { let _ = payload.append_and_init_bytes(f.len(), |b| salsa.crypt(f, b)); })); } - if poly.finish()[0..8].eq(&header.message_auth) { break; } @@ -227,12 +244,10 @@ impl Peer { let mut aes = secret.aes.get(); aes.decrypt_init(&header.aes_gmac_siv_tag()); aes.decrypt_set_aad(&header.aad_bytes()); - let _ = payload.append_and_init_bytes(packet_frag0_payload_bytes.len(), |b| aes.decrypt(packet_frag0_payload_bytes, b)); for f in fragments.iter() { let _ = f.as_ref().map(|f| f.as_bytes_starting_at(FRAGMENT_HEADER_SIZE).map(|f| payload.append_and_init_bytes(f.len(), |b| aes.decrypt(f, b)))); } - if aes.decrypt_finish() { break; } @@ -283,6 +298,12 @@ impl Peer { }); } + /// Get current best path or None if there are no direct paths to this peer. + #[inline(always)] + pub(crate) fn best_path(&self) -> Option> { + self.paths.lock().last().map(|p| p.clone()) + } + /// Send a packet to this peer. /// /// This will go directly if there is an active path, or otherwise indirectly @@ -290,6 +311,7 @@ impl Peer { pub(crate) fn send(&self, ci: &CI, time_ticks: i64, data: PacketBuffer) { self.last_send_time_ticks.store(time_ticks, Ordering::Relaxed); let _ = self.total_bytes_sent.fetch_add(data.len() as u64, Ordering::Relaxed); + todo!() } /// Forward a packet to this peer. @@ -305,6 +327,123 @@ impl Peer { todo!() } + /// Send a HELLO to this peer. + /// + /// If try_new_endpoint is not None the packet will be sent directly to this endpoint. + /// Otherwise it will be sent via the best direct or indirect path. + pub(crate) fn send_hello(&self, ci: &CI, node: &Node, try_new_endpoint: Option) { + let (endpoint, path) = try_new_endpoint.as_ref().map_or_else(|| { + self.best_path().map_or_else(|| { + node.root().map_or_else(|| (None, None), |root| { + root.best_path().map_or_else(|| (None, None), |bp| (Some(&bp.endpoint), Some(bp))) + }) + }, |bp| (Some(&bp.endpoint), Some(bp))) + }, |ep| (Some(ep), None)); + let _ = endpoint.map(|endpoint| { + let mut buf = node.get_packet_buffer(); + let this_peer_is_root = node.is_root(self); + + debug_assert!(buf.append_and_init_struct(|header: &mut PacketHeader| { + header.id = self.next_packet_iv(); + header.dest = self.identity.address().to_bytes(); + header.src = node.address().to_bytes(); + header.flags_cipher_hops = CIPHER_NOCRYPT_POLY1305; + }).is_ok()); + debug_assert!(buf.append_and_init_struct(|header: &mut message_component_structs::HelloFixedHeaderFields| { + header.verb = VERB_VL1_HELLO | VERB_FLAG_HMAC; + header.version_proto = VERSION_PROTO; + header.version_major = VERSION_MAJOR; + header.version_minor = VERSION_MINOR; + header.version_revision = (VERSION_REVISION as u16).to_be(); + header.timestamp = (ci.time_ticks() as u64).to_be(); + }).is_ok()); + + debug_assert!(self.identity.marshal(&mut buf, false).is_ok()); + debug_assert!(endpoint.marshal(&mut buf).is_ok()); + + let aes_ctr_iv_position = buf.len(); + debug_assert!(buf.append_and_init_bytes_fixed(|iv: &mut [u8; 18]| { + crate::crypto::random::fill_bytes_secure(&mut buf[0..12]); + todo!() + })); + let dictionary_position = buf.len(); + let mut dict = Dictionary::new(); + dict.set_u64(HELLO_DICT_KEY_INSTANCE_ID, node.instance_id); + dict.set_u64(HELLO_DICT_KEY_CLOCK, ci.time_clock() as u64); + let _ = node.locator().map(|loc| loc.to_bytes().map(|loc| dict.set_bytes(HELLO_DICT_KEY_LOCATOR, loc))); + let _ = self.ephemeral_pair.lock().map(|ephemeral_pair| { + dict.set_bytes(HELLO_DICT_KEY_EPHEMERAL_C25519, ephemeral_pair.c25519.public_bytes().to_vec()); + dict.set_bytes(HELLO_DICT_KEY_EPHEMERAL_P521, ephemeral_pair.p521.public_key_bytes().to_vec()); + }); + if this_peer_is_root { + // If the peer is a root we include some extra information for diagnostic and statistics + // purposes such as the CPU type, bits, and OS info. This is not sent to other peers. + dict.set_str(HELLO_DICT_KEY_SYS_ARCH, std::env::consts::ARCH); + dict.set_u64(HELLO_DICT_KEY_SYS_BITS, (std::mem::size_of::<*const ()>() * 8) as u64); + dict.set_str(HELLO_DICT_KEY_OS_NAME, std::env::consts::OS); + } + let mut flags = String::new(); + if node.fips_mode { + flags.push('F'); + } + if node.wimp { + flags.push('w'); + } + dict.set_str(HELLO_DICT_KEY_FLAGS, flags.as_str()); + assert!(dict.write_to(&mut buf).is_ok()); + + let mut dict_aes = self.static_secret_hello_dictionary.lock(); + dict_aes.init(&buf.as_bytes()[aes_ctr_iv_position..aes_ctr_iv_position + 12]); + dict_aes.crypt_in_place(&mut buf.as_bytes_mut()[dictionary_position..]); + drop(dict_aes); + + assert!(buf.append_bytes_fixed(&SHA384::hmac(self.static_secret_packet_hmac.as_ref(), &buf.as_bytes()[PACKET_HEADER_SIZE + 1..])).is_ok()); + + let (_, mut poly) = salsa_poly_create(secret, header, packet.len()); + poly.update(buf.as_bytes_starting_at(PACKET_HEADER_SIZE).unwrap()); + buf.as_bytes_mut()[HEADER_MAC_FIELD_INDEX..HEADER_MAC_FIELD_INDEX + 8].copy_from_slice(&poly.finish()[0..8]); + + ci.wire_send(endpoint, path.map(|p| p.local_socket), path.map(|p| p.local_interface), buf, 0); + }); + } + + /// Called every INTERVAL during background tasks. + #[inline(always)] + pub(crate) fn on_interval(&self, ct: &CI, time_ticks: i64) { + } + + #[inline(always)] + fn receive_hello(&self, ci: &CI, node: &Node, time_ticks: i64, source_path: &Arc, payload: &Buffer<{ PACKET_SIZE_MAX }>) { + } + + #[inline(always)] + fn receive_error(&self, ci: &CI, node: &Node, time_ticks: i64, source_path: &Arc, payload: &Buffer<{ PACKET_SIZE_MAX }>) { + } + + #[inline(always)] + fn receive_ok(&self, ci: &CI, node: &Node, time_ticks: i64, source_path: &Arc, payload: &Buffer<{ PACKET_SIZE_MAX }>) { + } + + #[inline(always)] + fn receive_whois(&self, ci: &CI, node: &Node, time_ticks: i64, source_path: &Arc, payload: &Buffer<{ PACKET_SIZE_MAX }>) { + } + + #[inline(always)] + fn receive_rendezvous(&self, ci: &CI, node: &Node, time_ticks: i64, source_path: &Arc, payload: &Buffer<{ PACKET_SIZE_MAX }>) { + } + + #[inline(always)] + fn receive_echo(&self, ci: &CI, node: &Node, time_ticks: i64, source_path: &Arc, payload: &Buffer<{ PACKET_SIZE_MAX }>) { + } + + #[inline(always)] + fn receive_push_direct_paths(&self, ci: &CI, node: &Node, time_ticks: i64, source_path: &Arc, payload: &Buffer<{ PACKET_SIZE_MAX }>) { + } + + #[inline(always)] + fn receive_user_message(&self, ci: &CI, node: &Node, time_ticks: i64, source_path: &Arc, payload: &Buffer<{ PACKET_SIZE_MAX }>) { + } + /// Get the remote version of this peer: major, minor, revision, and build. /// Returns None if it's not yet known. pub fn version(&self) -> Option<[u16; 4]> { @@ -325,41 +464,4 @@ impl Peer { None } } - - /// Called every INTERVAL during background tasks. - #[inline(always)] - pub fn on_interval(&self, ct: &CI, time_ticks: i64) { - } - - #[inline(always)] - fn receive_hello(&self, ci: &CI, node: &Node, time_ticks: i64, source_path: &Arc, packet: &Buffer<{ PACKET_SIZE_MAX }>) { - } - - #[inline(always)] - fn receive_error(&self, ci: &CI, node: &Node, time_ticks: i64, source_path: &Arc, packet: &Buffer<{ PACKET_SIZE_MAX }>) { - } - - #[inline(always)] - fn receive_ok(&self, ci: &CI, node: &Node, time_ticks: i64, source_path: &Arc, packet: &Buffer<{ PACKET_SIZE_MAX }>) { - } - - #[inline(always)] - fn receive_whois(&self, ci: &CI, node: &Node, time_ticks: i64, source_path: &Arc, packet: &Buffer<{ PACKET_SIZE_MAX }>) { - } - - #[inline(always)] - fn receive_rendezvous(&self, ci: &CI, node: &Node, time_ticks: i64, source_path: &Arc, packet: &Buffer<{ PACKET_SIZE_MAX }>) { - } - - #[inline(always)] - fn receive_echo(&self, ci: &CI, node: &Node, time_ticks: i64, source_path: &Arc, packet: &Buffer<{ PACKET_SIZE_MAX }>) { - } - - #[inline(always)] - fn receive_push_direct_paths(&self, ci: &CI, node: &Node, time_ticks: i64, source_path: &Arc, packet: &Buffer<{ PACKET_SIZE_MAX }>) { - } - - #[inline(always)] - fn receive_user_message(&self, ci: &CI, node: &Node, time_ticks: i64, source_path: &Arc, packet: &Buffer<{ PACKET_SIZE_MAX }>) { - } } diff --git a/network-hypervisor/src/vl1/protocol.rs b/network-hypervisor/src/vl1/protocol.rs index dd0387d04..d39de21a0 100644 --- a/network-hypervisor/src/vl1/protocol.rs +++ b/network-hypervisor/src/vl1/protocol.rs @@ -14,6 +14,92 @@ pub const VERB_VL1_ECHO: u8 = 0x08; pub const VERB_VL1_PUSH_DIRECT_PATHS: u8 = 0x10; pub const VERB_VL1_USER_MESSAGE: u8 = 0x14; +pub(crate) const HELLO_DICT_KEY_INSTANCE_ID: &'static str = "I"; +pub(crate) const HELLO_DICT_KEY_CLOCK: &'static str = "C"; +pub(crate) const HELLO_DICT_KEY_LOCATOR: &'static str = "L"; +pub(crate) const HELLO_DICT_KEY_EPHEMERAL_C25519: &'static str = "E0"; +pub(crate) const HELLO_DICT_KEY_EPHEMERAL_P521: &'static str = "E1"; +pub(crate) const HELLO_DICT_KEY_EPHEMERAL_ACK: &'static str = "e"; +pub(crate) const HELLO_DICT_KEY_HELLO_ORIGIN: &'static str = "@"; +pub(crate) const HELLO_DICT_KEY_SYS_ARCH: &'static str = "Sa"; +pub(crate) const HELLO_DICT_KEY_SYS_BITS: &'static str = "Sb"; +pub(crate) const HELLO_DICT_KEY_OS_NAME: &'static str = "So"; +pub(crate) const HELLO_DICT_KEY_OS_VERSION: &'static str = "Sv"; +pub(crate) const HELLO_DICT_KEY_OS_VARIANT: &'static str = "St"; +pub(crate) const HELLO_DICT_KEY_VENDOR: &'static str = "V"; +pub(crate) const HELLO_DICT_KEY_FLAGS: &'static str = "+"; + +/// Index of packet verb after header. +pub const PACKET_VERB_INDEX: usize = 27; + +/// Index of destination in both fragment and full packet headers. +pub const PACKET_DESTINATION_INDEX: usize = 8; + +/// Index of 8-byte MAC field in packet header. +pub const HEADER_MAC_FIELD_INDEX: usize = 19; + +/// Mask to select cipher from header flags field. +pub const HEADER_FLAGS_FIELD_MASK_CIPHER: u8 = 0x30; + +/// Mask to select packet hops from header flags field. +pub const HEADER_FLAGS_FIELD_MASK_HOPS: u8 = 0x07; + +/// Mask to select packet hops from header flags field. +pub const HEADER_FLAGS_FIELD_MASK_HIDE_HOPS: u8 = 0xf8; + +/// Index of hops/flags field +pub const HEADER_FLAGS_FIELD_INDEX: usize = 18; + +/// Packet is not encrypted but contains a Poly1305 MAC of the plaintext. +/// Poly1305 is initialized with Salsa20/12 in the same manner as SALSA2012_POLY1305. +pub const CIPHER_NOCRYPT_POLY1305: u8 = 0x00; + +/// Packet is encrypted and authenticated with Salsa20/12 and Poly1305. +/// Construction is the same as that which is used in the NaCl secret box functions. +pub const CIPHER_SALSA2012_POLY1305: u8 = 0x10; + +/// Formerly 'NONE' which is deprecated; reserved for future use. +pub const CIPHER_RESERVED: u8 = 0x20; + +/// Packet is encrypted and authenticated with AES-GMAC-SIV (AES-256). +pub const CIPHER_AES_GMAC_SIV: u8 = 0x30; + +/// Header (outer) flag indicating that this packet has additional fragments. +pub const HEADER_FLAG_FRAGMENTED: u8 = 0x40; + +/// Minimum size of a fragment. +pub const FRAGMENT_SIZE_MIN: usize = 16; + +/// Size of fragment header after which data begins. +pub const FRAGMENT_HEADER_SIZE: usize = 16; + +/// Maximum allowed number of fragments. +pub const FRAGMENT_COUNT_MAX: usize = 8; + +/// Index of packet fragment indicator byte to detect fragments. +pub const FRAGMENT_INDICATOR_INDEX: usize = 13; + +/// Byte found at FRAGMENT_INDICATOR_INDEX to indicate a fragment. +pub const FRAGMENT_INDICATOR: u8 = 0xff; + +/// Verb (inner) flag indicating that the packet's payload (after the verb) is LZ4 compressed. +pub const VERB_FLAG_COMPRESSED: u8 = 0x80; + +/// Verb (inner) flag indicating that payload after verb is authenticated with HMAC-SHA384. +pub const VERB_FLAG_HMAC: u8 = 0x40; + +/// Mask to get only the verb from the verb + verb flags byte. +pub const VERB_MASK: u8 = 0x1f; + +/// Maximum number of verbs that the protocol can support. +pub const VERB_MAX_COUNT: usize = 32; + +/// Maximum number of packet hops allowed by the protocol. +pub const PROTOCOL_MAX_HOPS: u8 = 7; + +/// Maximum number of hops to allow. +pub const FORWARD_MAX_HOPS: u8 = 3; + /// A unique packet identifier, also the cryptographic nonce. /// /// Packet IDs are stored as u64s for efficiency but they should be treated as @@ -151,6 +237,27 @@ impl FragmentHeader { } } +pub(crate) mod message_component_structs { + #[repr(packed)] + pub struct HelloFixedHeaderFields { + pub verb: u8, + pub version_proto: u8, + pub version_major: u8, + pub version_minor: u8, + pub version_revision: u16, + pub timestamp: u64, + } + + #[repr(packed)] + pub struct OkHelloFixedHeaderFields { + pub timestamp_echo: u64, + pub version_proto: u8, + pub version_major: u8, + pub version_minor: u8, + pub version_revision: u16, + } +} + #[cfg(test)] mod tests { use std::mem::size_of;