From 7fa60b10a32aaa960b8ed2e0139a21ee5ed304a7 Mon Sep 17 00:00:00 2001 From: Adam Ierymenko Date: Thu, 23 Jun 2022 16:40:47 -0400 Subject: [PATCH] IT TALKS! (HELLO, OK) --- zerotier-core-crypto/src/salsa.rs | 6 +- zerotier-network-hypervisor/src/vl1/peer.rs | 107 ++++++++++-------- .../src/vl1/protocol.rs | 23 +++- zerotier-system-service/src/utils.rs | 8 +- 4 files changed, 83 insertions(+), 61 deletions(-) diff --git a/zerotier-core-crypto/src/salsa.rs b/zerotier-core-crypto/src/salsa.rs index 664aeb132..bee30d6a0 100644 --- a/zerotier-core-crypto/src/salsa.rs +++ b/zerotier-core-crypto/src/salsa.rs @@ -118,6 +118,9 @@ impl Salsa { x14 = x14.wrapping_add(j14); x15 = x15.wrapping_add(j15); + j8 = j8.wrapping_add(1); + j9 = j9.wrapping_add((j8 == 0) as u32); + if plaintext.len() >= 64 { #[cfg(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64"))] { @@ -162,9 +165,6 @@ impl Salsa { ciphertext[60..64].copy_from_slice(&(u32::from_ne_bytes(unsafe { *plaintext.as_ptr().add(60).cast::<[u8; 4]>() }) ^ x15.to_le()).to_ne_bytes()); } - j8 = j8.wrapping_add(1); - j9 = j9.wrapping_add((j8 == 0) as u32); - plaintext = &plaintext[64..]; ciphertext = &mut ciphertext[64..]; } else { diff --git a/zerotier-network-hypervisor/src/vl1/peer.rs b/zerotier-network-hypervisor/src/vl1/peer.rs index cd5a782c4..aa66daa51 100644 --- a/zerotier-network-hypervisor/src/vl1/peer.rs +++ b/zerotier-network-hypervisor/src/vl1/peer.rs @@ -80,61 +80,68 @@ fn salsa_poly_create(secret: &SymmetricSecret, header: &PacketHeader, packet_siz } /// Attempt AEAD packet encryption and MAC validation. Returns message ID on success. -fn try_aead_decrypt(secret: &SymmetricSecret, packet_frag0_payload_bytes: &[u8], header: &PacketHeader, fragments: &[Option], payload: &mut PacketBuffer) -> Option { - packet_frag0_payload_bytes.get(0).map_or(None, |verb| { - let cipher = header.cipher(); - match cipher { - security_constants::CIPHER_NOCRYPT_POLY1305 | security_constants::CIPHER_SALSA2012_POLY1305 => { - if (verb & packet_constants::VERB_MASK) == verbs::VL1_HELLO { - let mut total_packet_len = packet_frag0_payload_bytes.len() + packet_constants::HEADER_SIZE; - for f in fragments.iter() { - total_packet_len += f.as_ref().map_or(0, |f| f.len()); - } - let _ = payload.append_bytes(packet_frag0_payload_bytes); - for f in fragments.iter() { - let _ = f.as_ref().map(|f| f.as_bytes_starting_at(packet_constants::HEADER_SIZE).map(|f| payload.append_bytes(f))); - } - let (mut salsa, poly1305_key) = salsa_poly_create(secret, header, total_packet_len); - let mac = zerotier_core_crypto::poly1305::compute(&poly1305_key, &payload.as_bytes()); - if mac[0..8].eq(&header.mac) { - if cipher == security_constants::CIPHER_SALSA2012_POLY1305 { - salsa.crypt_in_place(payload.as_bytes_mut()); - } - Some(u64::from_ne_bytes(header.id)) - } else { - None +fn try_aead_decrypt(secret: &SymmetricSecret, packet_frag0_payload_bytes: &[u8], packet_header: &PacketHeader, fragments: &[Option], payload: &mut PacketBuffer) -> Option { + let cipher = packet_header.cipher(); + match cipher { + security_constants::CIPHER_NOCRYPT_POLY1305 | security_constants::CIPHER_SALSA2012_POLY1305 => { + let _ = payload.append_bytes(packet_frag0_payload_bytes); + for f in fragments.iter() { + if let Some(f) = f.as_ref() { + if let Ok(f) = f.as_bytes_starting_at(packet_constants::FRAGMENT_HEADER_SIZE) { + let _ = payload.append_bytes(f); } + } + } + + let (mut salsa, poly1305_key) = salsa_poly_create(secret, packet_header, payload.len() + packet_constants::HEADER_SIZE); + let mac = zerotier_core_crypto::poly1305::compute(&poly1305_key, &payload.as_bytes()); + if mac[0..8].eq(&packet_header.mac) { + let message_id = u64::from_ne_bytes(packet_header.id); + if cipher == security_constants::CIPHER_SALSA2012_POLY1305 { + salsa.crypt_in_place(payload.as_bytes_mut()); + Some(message_id) + } else if (payload.u8_at(0).unwrap_or(0) & packet_constants::VERB_MASK) == verbs::VL1_HELLO { + Some(message_id) } else { - // Only HELLO is permitted without payload encryption. Drop other packet types if sent this way. + // SECURITY: fail if there is no encryption and the message is not HELLO. No other types are allowed + // to be sent without full packet encryption. None } + } else { + None } - - security_constants::CIPHER_AES_GMAC_SIV => { - let mut aes = secret.aes_gmac_siv.get(); - aes.decrypt_init(&header.aes_gmac_siv_tag()); - aes.decrypt_set_aad(&header.aad_bytes()); - // NOTE: if there are somehow missing fragments this part will silently fail, - // but the packet will fail MAC check in decrypt_finish() so meh. - let _ = payload.append_bytes_get_mut(packet_frag0_payload_bytes.len()).map(|b| aes.decrypt(packet_frag0_payload_bytes, b)); - for f in fragments.iter() { - f.as_ref().map(|f| { - f.as_bytes_starting_at(packet_constants::FRAGMENT_HEADER_SIZE).map(|f| { - let _ = payload.append_bytes_get_mut(f.len()).map(|b| aes.decrypt(f, b)); - }) - }); - } - aes.decrypt_finish().map_or(None, |tag| { - // AES-GMAC-SIV encrypts the packet ID too as part of its computation of a single - // opaque 128-bit tag, so to get the original packet ID we have to grab it from the - // decrypted tag. - Some(u64::from_ne_bytes(*byte_array_range::<16, 0, 8>(tag))) - }) - } - - _ => None, } - }) + + security_constants::CIPHER_AES_GMAC_SIV => { + let mut aes = secret.aes_gmac_siv.get(); + aes.decrypt_init(&packet_header.aes_gmac_siv_tag()); + aes.decrypt_set_aad(&packet_header.aad_bytes()); + + if let Ok(b) = payload.append_bytes_get_mut(packet_frag0_payload_bytes.len()) { + aes.decrypt(packet_frag0_payload_bytes, b); + } + for f in fragments.iter() { + if let Some(f) = f.as_ref() { + if let Ok(f) = f.as_bytes_starting_at(packet_constants::FRAGMENT_HEADER_SIZE) { + if let Ok(b) = payload.append_bytes_get_mut(f.len()) { + aes.decrypt(f, b); + } + } + } + } + + if let Some(tag) = aes.decrypt_finish() { + // AES-GMAC-SIV encrypts the packet ID too as part of its computation of a single + // opaque 128-bit tag, so to get the original packet ID we have to grab it from the + // decrypted tag. + Some(u64::from_ne_bytes(*byte_array_range::<16, 0, 8>(tag))) + } else { + None + } + } + + _ => None, + } } impl Peer { @@ -287,7 +294,7 @@ impl Peer { // If we made it here it decrypted and passed authentication. // --------------------------------------------------------------- - debug_event!(si, "[vl1] #{:0>16x} decrypted and authenticated, verb: {:0>2x}", u64::from_be_bytes(packet_header.id), (verb & packet_constants::VERB_MASK) as u32); + debug_event!(si, "[vl1] #{:0>16x} decrypted and authenticated, verb: {} ({:0>2x})", u64::from_be_bytes(packet_header.id), verbs::name(verb & packet_constants::VERB_MASK), (verb & packet_constants::VERB_MASK) as u32); if (verb & packet_constants::VERB_FLAG_COMPRESSED) != 0 { let mut decompressed_payload: [u8; packet_constants::SIZE_MAX] = unsafe { MaybeUninit::uninit().assume_init() }; diff --git a/zerotier-network-hypervisor/src/vl1/protocol.rs b/zerotier-network-hypervisor/src/vl1/protocol.rs index 758a853e3..a9d5b510a 100644 --- a/zerotier-network-hypervisor/src/vl1/protocol.rs +++ b/zerotier-network-hypervisor/src/vl1/protocol.rs @@ -74,6 +74,21 @@ pub mod verbs { pub const VL1_ECHO: u8 = 0x08; pub const VL1_PUSH_DIRECT_PATHS: u8 = 0x10; pub const VL1_USER_MESSAGE: u8 = 0x14; + + pub fn name(verb: u8) -> &'static str { + match verb { + VL1_NOP => "VL1_NOP", + VL1_HELLO => "VL1_HELLO", + VL1_ERROR => "VL1_ERROR", + VL1_OK => "VL1_OK", + VL1_WHOIS => "VL1_WHOIS", + VL1_RENDEZVOUS => "VL1_RENDEZVOUS", + VL1_ECHO => "VL1_ECHO", + VL1_PUSH_DIRECT_PATHS => "VL1_PUSH_DIRECT_PATHS", + VL1_USER_MESSAGE => "VL1_USER_MESSAGE", + _ => "???", + } + } } /// Default maximum payload size for UDP transport. @@ -122,7 +137,7 @@ pub mod packet_constants { pub const MAC_FIELD_INDEX: usize = 19; /// Mask to select cipher from header flags field. - pub const FLAGS_FIELD_MASK_CIPHER: u8 = 0x30; + pub const FLAGS_FIELD_MASK_CIPHER: u8 = 0x38; /// Mask to select packet hops from header flags field. pub const FLAGS_FIELD_MASK_HOPS: u8 = 0x07; @@ -178,13 +193,13 @@ pub mod security_constants { /// 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; + pub const CIPHER_SALSA2012_POLY1305: u8 = 0x08; /// Formerly 'NONE' which is deprecated; reserved for future use. - pub const CIPHER_RESERVED: u8 = 0x20; + pub const CIPHER_RESERVED: u8 = 0x10; /// Packet is encrypted and authenticated with AES-GMAC-SIV (AES-256). - pub const CIPHER_AES_GMAC_SIV: u8 = 0x30; + pub const CIPHER_AES_GMAC_SIV: u8 = 0x18; /// KBKDF usage label indicating a key used to HMAC packets for extended authentication. pub const KBKDF_KEY_USAGE_LABEL_PACKET_HMAC: u8 = b'M'; diff --git a/zerotier-system-service/src/utils.rs b/zerotier-system-service/src/utils.rs index 63c0feb8e..841619681 100644 --- a/zerotier-system-service/src/utils.rs +++ b/zerotier-system-service/src/utils.rs @@ -183,10 +183,10 @@ pub async fn parse_cli_identity(input: &str, validate: bool) -> Result String { - unsafe { std::ffi::CStr::from_ptr(libc::strerror(*libc::__error()).cast()).to_string_lossy().to_string() } -} +//#[cfg(unix)] +//pub fn c_strerror() -> String { +// unsafe { std::ffi::CStr::from_ptr(libc::strerror(*libc::__error()).cast()).to_string_lossy().to_string() } +//} #[cfg(test)] mod tests {