VL1 now says HELLO!!!

This commit is contained in:
Adam Ierymenko 2022-06-22 16:29:51 -04:00
parent 36a105ecbf
commit a61bcaf0f7
No known key found for this signature in database
GPG key ID: C8877CF2D7A5D7F3
15 changed files with 180 additions and 115 deletions

View file

@ -1,21 +1,21 @@
AES-GMAC-SIV AES-GMAC-SIV
====== ======
This is a Rust implementation of AES-GMAC-SIV, a FIPS-compliant SIV mode for AES-256, with underlying cryptographic primitives provided by either macOS CryptoCore, OpenSSL, or (eventually, not implemented yet) WinCrypt. The appropriate API is automatically selected at compile time. This is a Rust implementation of AES-GMAC-SIV, a FIPS-compliant SIV AEAD construction for AES-256. An interface to simple AES-CTR mode is also included.
This is designed for use with the ZeroTier protocol but could be used for other message based protocols where a secure authenticated cipher construction is needed. It's implemented in terms of OpenSSL (libcrypto), macOS/iOS CryptoCore, or (soon) WinCrypt, with the latter two automatically selected at compile time on those platforms.
## Introduction ## Introduction
AES-GMAC-SIV is a "synthetic IV" (SIV) cipher construction implemented using only FIPS and NIST approved cryptographic building blocks: AES and GMAC (the MAC component of GCM). It can for FIPS purposes be described as "AES-CTR authenticated with GMAC" both of which are permitted algorithms. It was created because while similar to [AES-GCM-SIV](https://en.wikipedia.org/wiki/AES-GCM-SIV) that mode uses a non-standard MAC called POLYVAL in place of GMAC. POLYVAL is just GMAC in little-endian, but the fact that it is not standard GMAC means it's not found in most cryptographic libraries and is not approved by FIPS and most other cryptographic standards. AES-GMAC-SIV is a "synthetic IV" (SIV) cipher construction implemented using only FIPS and NIST approved cryptographic building blocks: AES and GMAC (the MAC component of GCM). It can for FIPS purposes be described as "AES-CTR authenticated with GMAC" both of which are permitted algorithms. It was created because while similar to [AES-GCM-SIV](https://en.wikipedia.org/wiki/AES-GCM-SIV) that mode uses a non-standard MAC called POLYVAL in place of GMAC. POLYVAL is just GMAC in little-endian, but the fact that it is not standard GMAC means it's not found in most cryptographic libraries and is not approved by FIPS and most other cryptographic standards.
## Why SIV? ## About SIV Modes
Stream ciphers like AES-CTR, ChaCha20, and others require a number called an initialization vector (IV) for each use. These and most other stream ciphers work by XORing a key stream with plaintext, so if an IV is used more than once security is compromised. Since XOR is commutative, if two different messages are encrypted with the same key stream a simple XOR can reveal that key stream and decrypt both messages. This is a common pitfall with any XOR based symmetric cipher construction. Stream ciphers like AES-CTR, ChaCha20, and others require a number called an initialization vector (IV) for each use. These and most other stream ciphers work by XORing a key stream with plaintext, so if an IV is used more than once security is compromised. Since XOR is commutative, if two different messages are encrypted with the same key stream a simple XOR can reveal that key stream and decrypt both messages. This is a common pitfall with any XOR based symmetric cipher construction.
Repeating the IV is dangerous with many MAC functions in authenticated constructions as well. For AES-GCM the use of a duplicate IV with GMAC can allow an attacker to forge messages, which for many use cases is a worse security failure than letting an attacker decrypt a few messages. Repeating the IV is dangerous with many MAC (message authentication) functions as well. [It's particularly dangerous in AES-GCM](https://crypto.stackexchange.com/questions/26790/how-bad-it-is-using-the-same-iv-twice-with-aes-gcm), where one nonce reuse with GMAC (the MAC function in AES-GCM) could allow an attacker to forge messages.
SIV modes provide strong protection against IV reuse and improve security bounds for protocols using small IVs (like ZeroTier's 64-bit message ID) by generating a larger *synthetic IV* from a random or sequential external IV and the message plaintext. Even if the externally provided IV is duplicated, the larger SIV IV won't repeat unless the plaintext is also identical. In this case all you've done is leaked the fact that you have sent a duplicate message. The security of that message and its authentication function is not compromised. SIV stands for *synthetic IV*. SIV modes work by applying a MAC function to the plaintext first, then using the resulting authentication code plus an IV to initialize a stream cipher. This provides much stronger protection against IV reuse by making the actual IV dependent on the plaintext. Changes to the plaintext will therefore change the IV even if the one supplied to the function is duplicated.
SIV modes might seem like paranoia, but accidental IV reuse is easier than you might think. Here's a few scenarios where it might happen: SIV modes might seem like paranoia, but accidental IV reuse is easier than you might think. Here's a few scenarios where it might happen:
@ -30,11 +30,13 @@ SIV modes might seem like paranoia, but accidental IV reuse is easier than you m
... and so on. "Sudden death" on IV re-use is a foot-gun that's worth removing. ... and so on. "Sudden death" on IV re-use is a foot-gun that's worth removing.
## AES-GMAC-SIV construction ## AES-GMAC-SIV
![AES-GMAC-SIV block diagram](AES-GMAC-SIV.png) ![AES-GMAC-SIV block diagram](AES-GMAC-SIV.png)
Two initialization keys, which can be derived from one using a parameterized KDF: *Inputs are green, outputs are blue. Grey indicates simple non-cryptographic operations. Red indicates cryptographic steps.*
Two initialization keys, which can be derived from a single key using a key derivation function or hashing with a 512-bit hash function and using the first and second 256 bits:
1. K0, a 256-bit AES key used to initialize AES-GMAC. 1. K0, a 256-bit AES key used to initialize AES-GMAC.
2. K1, a second (and different) 256-bit AES key used to initialize AES-ECB and AES-CTR. 2. K1, a second (and different) 256-bit AES key used to initialize AES-ECB and AES-CTR.
@ -53,7 +55,7 @@ Encryption steps:
4. Feed plaintext into GMAC to compute final MAC. 4. Feed plaintext into GMAC to compute final MAC.
5. XOR lower 64 bits and higher 64 bits of 128-bit GMAC tag to yield a 64-bit tag. 5. XOR lower 64 bits and higher 64 bits of 128-bit GMAC tag to yield a 64-bit tag.
6. Concatenate original 64-bit input IV and 64-bit shortened tag to form a 128-bit block. 6. Concatenate original 64-bit input IV and 64-bit shortened tag to form a 128-bit block.
7. AES-ECB encrypt this IV+tag, yielding an opaque 128-bit message tag and AES-CTR IV. (AES-ECB is perfectly safe if only one block is encrypted.) 7. AES-ECB encrypt this IV+tag, yielding an opaque 128-bit message tag and AES-CTR IV. (ECB is secure if only one block is encrypted.)
8. Clear bit 31 (from the right) in the tag and use this to initialize AES-CTR with the first 96 bits being the AES-CTR IV and the remaining 31 bits being the AES-CTR "index" or counter. This provides what amounts to a 127-bit AES-CTR IV. The most significant bit of the counter is cleared so that poor quality AES-CTR implementations that only use a 32-bit wrapping counter will not wrap at message sizes up to 2^31 bytes. Wrapping technically wouldn't hurt anything unless the implementation generates a fault on wrap, but avoid this in case some cryptographic accelerator somewhere does so. 8. Clear bit 31 (from the right) in the tag and use this to initialize AES-CTR with the first 96 bits being the AES-CTR IV and the remaining 31 bits being the AES-CTR "index" or counter. This provides what amounts to a 127-bit AES-CTR IV. The most significant bit of the counter is cleared so that poor quality AES-CTR implementations that only use a 32-bit wrapping counter will not wrap at message sizes up to 2^31 bytes. Wrapping technically wouldn't hurt anything unless the implementation generates a fault on wrap, but avoid this in case some cryptographic accelerator somewhere does so.
9. Encrypt plaintext with AES-CTR and send this along with the encrypted IV+tag from step 7 (without CTR counter bit 31 cleared). The per-message unique 64-bit IV supplied by the caller at encryption **should not** be sent as it is recovered during decryption by decrypting the IV+tag blob. Sending it wastes space and reveals slightly more state information to an attacker, since without the input IV an attacker doesn't know if it has in fact been duplicated. 9. Encrypt plaintext with AES-CTR and send this along with the encrypted IV+tag from step 7 (without CTR counter bit 31 cleared). The per-message unique 64-bit IV supplied by the caller at encryption **should not** be sent as it is recovered during decryption by decrypting the IV+tag blob. Sending it wastes space and reveals slightly more state information to an attacker, since without the input IV an attacker doesn't know if it has in fact been duplicated.

View file

@ -20,6 +20,11 @@ mod tests {
use sha2::Digest; use sha2::Digest;
use std::time::SystemTime; use std::time::SystemTime;
const TV0_KEYS: [&'static [u8]; 2] = ["00000000000000000000000000000000".as_bytes(), "11111111111111111111111111111111".as_bytes()];
/// Test vectors consist of a series of input sizes, a SHA384 hash of a resulting ciphertext, and an expected tag.
/// Input is a standard byte array consisting of bytes 0, 1, 2, 3, ..., 255 and then cycling back to 0 over and over
/// and is provided both as ciphertext and associated data (AAD).
#[allow(unused)] #[allow(unused)]
const TEST_VECTORS: [(usize, &'static str, &'static str); 85] = [ const TEST_VECTORS: [(usize, &'static str, &'static str); 85] = [
(0, "38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b", "43847e644239134deccf5538162c861e"), (0, "38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b", "43847e644239134deccf5538162c861e"),
@ -117,6 +122,7 @@ mod tests {
s s
} }
/// Run a bunch of test vectors.
#[test] #[test]
fn test_vectors() { fn test_vectors() {
let mut test_pt = [0_u8; 65536]; let mut test_pt = [0_u8; 65536];
@ -126,7 +132,7 @@ mod tests {
test_pt[i] = i as u8; test_pt[i] = i as u8;
test_aad[i] = i as u8; test_aad[i] = i as u8;
} }
let mut c = AesGmacSiv::new("00000000000000000000000000000000".as_bytes(), "11111111111111111111111111111111".as_bytes()); let mut c = AesGmacSiv::new(TV0_KEYS[0], TV0_KEYS[1]);
for (test_length, expected_ct_sha384, expected_tag) in TEST_VECTORS.iter() { for (test_length, expected_ct_sha384, expected_tag) in TEST_VECTORS.iter() {
test_ct.fill(0); test_ct.fill(0);
c.reset(); c.reset();
@ -147,6 +153,7 @@ mod tests {
} }
} }
/// Test repeated encrypt/decrypt and run a benchmark. Run with --nocapture to see it.
#[test] #[test]
fn encrypt_decrypt() { fn encrypt_decrypt() {
let aes_key_0: [u8; 32] = [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, 26, 27, 28, 29, 30, 31, 32]; let aes_key_0: [u8; 32] = [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, 26, 27, 28, 29, 30, 31, 32];

View file

@ -1,30 +1,33 @@
// (c) 2020-2022 ZeroTier, Inc. -- currently propritery pending actual release and licensing. See LICENSE.md. // (c) 2020-2022 ZeroTier, Inc. -- currently propritery pending actual release and licensing. See LICENSE.md.
use poly1305::universal_hash::{NewUniversalHash, UniversalHash}; use poly1305::universal_hash::NewUniversalHash;
/// The poly1305 message authentication function. /// The poly1305 message authentication function.
#[repr(transparent)] pub struct Poly1305(poly1305::Poly1305, [u8; 16], usize);
pub struct Poly1305(poly1305::Poly1305);
pub const POLY1305_ONE_TIME_KEY_SIZE: usize = 32; pub const POLY1305_ONE_TIME_KEY_SIZE: usize = 32;
pub const POLY1305_MAC_SIZE: usize = 16; pub const POLY1305_MAC_SIZE: usize = 16;
impl Poly1305 { #[inline(always)]
#[inline(always)] pub fn compute(one_time_key: &[u8], message: &[u8]) -> [u8; POLY1305_MAC_SIZE] {
pub fn new(key: &[u8]) -> Poly1305 { poly1305::Poly1305::new(poly1305::Key::from_slice(one_time_key)).compute_unpadded(message).into_bytes().into()
assert_eq!(key.len(), 32);
Self(poly1305::Poly1305::new(poly1305::Key::from_slice(key)))
}
#[inline(always)]
pub fn update(&mut self, data: &[u8]) {
self.0.update_padded(data);
}
#[inline(always)]
pub fn finish(self) -> [u8; POLY1305_MAC_SIZE] {
self.0.finalize().into_bytes().into()
}
} }
unsafe impl Send for Poly1305 {} #[cfg(test)]
mod tests {
use crate::poly1305::*;
const TV0_INPUT: [u8; 32] = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
const TV0_KEY: [u8; 32] = [0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x33, 0x32, 0x2d, 0x62, 0x79, 0x74, 0x65, 0x20, 0x6b, 0x65, 0x79, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x50, 0x6f, 0x6c, 0x79, 0x31, 0x33, 0x30, 0x35];
const TV0_TAG: [u8; 16] = [0x49, 0xec, 0x78, 0x09, 0x0e, 0x48, 0x1e, 0xc6, 0xc2, 0x6b, 0x33, 0xb9, 0x1c, 0xcc, 0x03, 0x07];
const TV1_INPUT: [u8; 12] = [0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x21];
const TV1_KEY: [u8; 32] = [0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x33, 0x32, 0x2d, 0x62, 0x79, 0x74, 0x65, 0x20, 0x6b, 0x65, 0x79, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x50, 0x6f, 0x6c, 0x79, 0x31, 0x33, 0x30, 0x35];
const TV1_TAG: [u8; 16] = [0xa6, 0xf7, 0x45, 0x00, 0x8f, 0x81, 0xc9, 0x16, 0xa2, 0x0d, 0xcc, 0x74, 0xee, 0xf2, 0xb2, 0xf0];
#[test]
fn poly1305() {
assert_eq!(TV0_TAG, compute(&TV0_KEY, &TV0_INPUT));
assert_eq!(TV1_TAG, compute(&TV1_KEY, &TV1_INPUT));
}
}

View file

@ -161,11 +161,12 @@ impl<const ROUNDS: usize> Salsa<ROUNDS> {
ciphertext[56..60].copy_from_slice(&(u32::from_ne_bytes(unsafe { *plaintext.as_ptr().add(56).cast::<[u8; 4]>() }) ^ x14.to_le()).to_ne_bytes()); ciphertext[56..60].copy_from_slice(&(u32::from_ne_bytes(unsafe { *plaintext.as_ptr().add(56).cast::<[u8; 4]>() }) ^ x14.to_le()).to_ne_bytes());
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()); 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());
} }
plaintext = &plaintext[64..];
ciphertext = &mut ciphertext[64..];
j8 = j8.wrapping_add(1); j8 = j8.wrapping_add(1);
j9 = j9.wrapping_add((j8 == 0) as u32); j9 = j9.wrapping_add((j8 == 0) as u32);
plaintext = &plaintext[64..];
ciphertext = &mut ciphertext[64..];
} else { } else {
if !plaintext.is_empty() { if !plaintext.is_empty() {
let remainder = [x0.to_le(), x1.to_le(), x2.to_le(), x3.to_le(), x4.to_le(), x5.to_le(), x6.to_le(), x7.to_le(), x8.to_le(), x9.to_le(), x10.to_le(), x11.to_le(), x12.to_le(), x13.to_le(), x14.to_le(), x15.to_le()]; let remainder = [x0.to_le(), x1.to_le(), x2.to_le(), x3.to_le(), x4.to_le(), x5.to_le(), x6.to_le(), x7.to_le(), x8.to_le(), x9.to_le(), x10.to_le(), x11.to_le(), x12.to_le(), x13.to_le(), x14.to_le(), x15.to_le()];
@ -187,3 +188,28 @@ impl<const ROUNDS: usize> Salsa<ROUNDS> {
unsafe { self.crypt(&*slice_from_raw_parts(data.as_ptr(), data.len()), &mut *slice_from_raw_parts_mut(data.as_mut_ptr(), data.len())) } unsafe { self.crypt(&*slice_from_raw_parts(data.as_ptr(), data.len()), &mut *slice_from_raw_parts_mut(data.as_mut_ptr(), data.len())) }
} }
} }
#[cfg(test)]
mod tests {
use crate::salsa::*;
const SALSA_20_TV0_KEY: [u8; 32] = [0x0f, 0x62, 0xb5, 0x08, 0x5b, 0xae, 0x01, 0x54, 0xa7, 0xfa, 0x4d, 0xa0, 0xf3, 0x46, 0x99, 0xec, 0x3f, 0x92, 0xe5, 0x38, 0x8b, 0xde, 0x31, 0x84, 0xd7, 0x2a, 0x7d, 0xd0, 0x23, 0x76, 0xc9, 0x1c];
const SALSA_20_TV0_IV: [u8; 8] = [0x28, 0x8f, 0xf6, 0x5d, 0xc4, 0x2b, 0x92, 0xf9];
const SALSA_20_TV0_KS: [u8; 64] = [
0x5e, 0x5e, 0x71, 0xf9, 0x01, 0x99, 0x34, 0x03, 0x04, 0xab, 0xb2, 0x2a, 0x37, 0xb6, 0x62, 0x5b, 0xf8, 0x83, 0xfb, 0x89, 0xce, 0x3b, 0x21, 0xf5, 0x4a, 0x10, 0xb8, 0x10, 0x66, 0xef, 0x87, 0xda, 0x30, 0xb7, 0x76, 0x99, 0xaa, 0x73, 0x79, 0xda, 0x59,
0x5c, 0x77, 0xdd, 0x59, 0x54, 0x2d, 0xa2, 0x08, 0xe5, 0x95, 0x4f, 0x89, 0xe4, 0x0e, 0xb7, 0xaa, 0x80, 0xa8, 0x4a, 0x61, 0x76, 0x66, 0x3f,
];
#[test]
fn salsa20() {
let mut s20 = Salsa::<20>::new(&SALSA_20_TV0_KEY, &SALSA_20_TV0_IV);
let mut ks = [0_u8; 64];
s20.crypt_in_place(&mut ks);
assert_eq!(ks, SALSA_20_TV0_KS);
let mut s20 = Salsa::<20>::new(&SALSA_20_TV0_KEY, &SALSA_20_TV0_IV);
let mut ks = [0_u8; 32];
s20.crypt_in_place(&mut ks);
assert_eq!(ks, &SALSA_20_TV0_KS[..32]);
}
}

View file

@ -20,7 +20,7 @@ zerotier-core-crypto = { path = "../zerotier-core-crypto" }
async-trait = "^0" async-trait = "^0"
base64 = "^0" base64 = "^0"
lz4_flex = { version = "^0", features = ["safe-encode", "safe-decode", "checked-decode"] } lz4_flex = { version = "^0", features = ["safe-encode", "safe-decode", "checked-decode"] }
parking_lot = "^0" parking_lot = { version = "^0", features = [], default-features = false }
lazy_static = "^1" lazy_static = "^1"
serde = { version = "^1", features = ["derive"], default-features = false } serde = { version = "^1", features = ["derive"], default-features = false }

View file

@ -0,0 +1,11 @@
{
"name": "local-parellels-test",
"url": "http://root.zerotier.com/root.zerotier.com.json",
"revision": 1,
"members": [ {
"identity": "bc47f54ab2:0:cfb92160bab1da37f31247ded76d8327c00c4d3e49d8a424c6ba16fe3e77b949ab782426584b0169e7b38f7679ea24f38cea637a7a93a9272bfcb0ff461c1e97",
"endpoints": [ "udp:10.211.55.3/9993" ],
"signature": [ 1, 21, 160, 59, 81, 229, 164, 176, 225, 189, 66, 211, 25, 209, 30, 130, 254, 218, 121, 84, 129, 109, 18, 0, 156, 97, 56, 40, 165, 114, 211, 89, 25, 114, 144, 93, 237, 206, 52, 219, 60, 173, 178, 40, 22, 76, 42, 215, 158, 82, 12, 218, 128, 28, 158, 167, 108, 68, 80, 6, 211, 53, 70, 22, 5 ],
"priority": 0
} ]
}

View file

@ -80,6 +80,7 @@ impl<I: Interface> NetworkHypervisor<I> {
pub fn add_update_default_root_set(&self) -> bool { pub fn add_update_default_root_set(&self) -> bool {
let mut buf: Buffer<4096> = Buffer::new(); let mut buf: Buffer<4096> = Buffer::new();
buf.set_to(include_bytes!("../default-rootset/root.zerotier.com.bin")); buf.set_to(include_bytes!("../default-rootset/root.zerotier.com.bin"));
//buf.set_to(include_bytes!("../default-rootset/local-parallels-test.bin"));
let mut cursor = 0; let mut cursor = 0;
self.add_update_root_set(RootSet::unmarshal(&buf, &mut cursor).unwrap()) self.add_update_root_set(RootSet::unmarshal(&buf, &mut cursor).unwrap())
} }

View file

@ -116,7 +116,7 @@ impl<const L: usize> Buffer<L> {
#[inline(always)] #[inline(always)]
pub fn as_bytes_starting_at(&self, start: usize) -> std::io::Result<&[u8]> { pub fn as_bytes_starting_at(&self, start: usize) -> std::io::Result<&[u8]> {
if start <= self.0 { if start <= self.0 {
Ok(&self.1[start..]) Ok(&self.1[start..self.0])
} else { } else {
Err(overflow_err()) Err(overflow_err())
} }

View file

@ -150,6 +150,13 @@ impl Identity {
/// ///
/// The boolean indicates whether or not an upgrade occurred. An error occurs if this identity is /// The boolean indicates whether or not an upgrade occurred. An error occurs if this identity is
/// invalid or missing its private key(s). This does nothing if no upgrades are possible. /// invalid or missing its private key(s). This does nothing if no upgrades are possible.
///
/// NOTE: upgrading is not deterministic. This generates a new set of NIST P-384 keys and the new
/// identity contains these and a signature by the original keys and by the new keys to bind them
/// together. However repeated calls to upgrade() will generate different secondary keys. This should
/// only be used once to upgrade and then save a new identity.
///
/// It would be possible to change this in the future, with care.
pub fn upgrade(&mut self) -> Result<bool, InvalidParameterError> { pub fn upgrade(&mut self) -> Result<bool, InvalidParameterError> {
if self.secret.is_none() { if self.secret.is_none() {
return Err(InvalidParameterError("an identity can only be upgraded if it includes its private key")); return Err(InvalidParameterError("an identity can only be upgraded if it includes its private key"));
@ -301,25 +308,20 @@ impl Identity {
} }
/// Verify a signature against this identity. /// Verify a signature against this identity.
pub fn verify(&self, msg: &[u8], mut signature: &[u8]) -> bool { pub fn verify(&self, msg: &[u8], signature: &[u8]) -> bool {
if signature.len() == 96 { if signature.len() == 96 {
// legacy ed25519-only signature with hash included detected by their unique size. // LEGACY: ed25519-only signature with hash included, detected by having a unique size of 96 bytes
ed25519_verify(&self.ed25519, &signature[..64], msg) return ed25519_verify(&self.ed25519, &signature[..64], msg);
} else if let Some(algorithm) = signature.get(0) { } else if let Some(algorithm) = signature.get(0) {
if *algorithm == Self::ALGORITHM_EC_NIST_P384 && signature.len() == (1 + P384_ECDSA_SIGNATURE_SIZE) { if *algorithm == Self::ALGORITHM_EC_NIST_P384 && signature.len() == (1 + P384_ECDSA_SIGNATURE_SIZE) {
if let Some(p384) = self.p384.as_ref() { if let Some(p384) = self.p384.as_ref() {
p384.ecdsa.verify(msg, &signature[1..]) return p384.ecdsa.verify(msg, &signature[1..]);
} else {
false
} }
} else if *algorithm == Self::ALGORITHM_X25519 && signature.len() == (1 + ED25519_SIGNATURE_SIZE) { } else if *algorithm == Self::ALGORITHM_X25519 && signature.len() == (1 + ED25519_SIGNATURE_SIZE) {
ed25519_verify(&self.ed25519, &signature[1..], msg) return ed25519_verify(&self.ed25519, &signature[1..], msg);
} else {
false
} }
} else {
false
} }
return false;
} }
pub fn to_buffer_with_options(&self, include_algorithms: u8, include_private: bool) -> Buffer<{ Self::MAX_MARSHAL_SIZE }> { pub fn to_buffer_with_options(&self, include_algorithms: u8, include_private: bool) -> Buffer<{ Self::MAX_MARSHAL_SIZE }> {
@ -349,6 +351,8 @@ impl Identity {
} }
/* /*
* LEGACY:
*
* The prefix of 0x03 is for backward compatibility. Older nodes will interpret this as * The prefix of 0x03 is for backward compatibility. Older nodes will interpret this as
* an empty unidentified InetAddress object and will skip the number of bytes following it. * an empty unidentified InetAddress object and will skip the number of bytes following it.
* *
@ -909,6 +913,26 @@ mod tests {
#[allow(unused_imports)] #[allow(unused_imports)]
use zerotier_core_crypto::hex; use zerotier_core_crypto::hex;
#[test]
fn v0_identity() {
let self_agree_expected = hex::from_string("de904fc90ff3a2b96b739b926e623113f5334c80841b654509b77916c4c4a6eb0ca69ec6ed01a7f04aee17c546b30ba4");
// Test self-agree with a known good x25519-only (v0) identity.
let id = Identity::from_str("728efdb79d:0:3077ed0084d8d48a3ac628af6b45d9351e823bff34bc4376cddfc77a3d73a966c7d347bdcc1244d0e99e1b9c961ff5e963092e90ca43b47ff58c114d2d699664:2afaefcd1dca336ed59957eb61919b55009850b0b7088af3ee142672b637d1d49cc882b30a006f9eee42f2211ef8fe1cbe99a16a4436737fc158ce2243c15f12").unwrap();
let self_agree = id.agree(&id).unwrap();
assert!(self_agree_expected.as_slice().eq(&self_agree.as_bytes()[..48]));
// Identity should be upgradable.
let mut upgraded = id.clone();
assert!(upgraded.upgrade().unwrap());
// Upgraded identity should generate the same result when agreeing with the old non-upgraded identity.
let self_agree = id.agree(&upgraded).unwrap();
assert!(self_agree_expected.as_slice().eq(&self_agree.as_bytes()[..48]));
let self_agree = upgraded.agree(&id).unwrap();
assert!(self_agree_expected.as_slice().eq(&self_agree.as_bytes()[..48]));
}
const GOOD_V0_IDENTITIES: [&'static str; 10] = [ const GOOD_V0_IDENTITIES: [&'static str; 10] = [
"51ef313c3a:0:79fee239cf79833be3a9068565661dc33e04759fa0f7e2218d10f1a51d441f1bf71332eba26dfc3755ce60e14650fe68dede66cf145e429972a7f51e026374de:6d12b1c5e0eae3983a5ee5872fa9061963d9e2f8cdd85adab54bdec4bd67f538cafc91b8b5b93fca658a630aab030ec10d66235f2443ccf362c55c41ae01b46e", "51ef313c3a:0:79fee239cf79833be3a9068565661dc33e04759fa0f7e2218d10f1a51d441f1bf71332eba26dfc3755ce60e14650fe68dede66cf145e429972a7f51e026374de:6d12b1c5e0eae3983a5ee5872fa9061963d9e2f8cdd85adab54bdec4bd67f538cafc91b8b5b93fca658a630aab030ec10d66235f2443ccf362c55c41ae01b46e",
"9532db97eb:0:86a2c3a7d08be09f794188ef86014f54b699577536db1ded58537c9159020b48c962ff7f25501ada8ef20b604dd29fb1a915966aaffe1ef6a589527525599f10:06ab13d2704583451bb326feb5c3d9bfe7879aa327669ff33150a42c04464aa5435cec79d952e0af970142e9d8c8a0dd26deadf9b9ba2f1cb454bf2ac22e53e6", "9532db97eb:0:86a2c3a7d08be09f794188ef86014f54b699577536db1ded58537c9159020b48c962ff7f25501ada8ef20b604dd29fb1a915966aaffe1ef6a589527525599f10:06ab13d2704583451bb326feb5c3d9bfe7879aa327669ff33150a42c04464aa5435cec79d952e0af970142e9d8c8a0dd26deadf9b9ba2f1cb454bf2ac22e53e6",

View file

@ -497,11 +497,11 @@ impl<SI: SystemInterface> Node<SI> {
pub async fn handle_incoming_physical_packet<PH: InnerProtocolInterface>(&self, si: &SI, ph: &PH, source_endpoint: &Endpoint, source_local_socket: &SI::LocalSocket, source_local_interface: &SI::LocalInterface, mut data: PooledPacketBuffer) { pub async fn handle_incoming_physical_packet<PH: InnerProtocolInterface>(&self, si: &SI, ph: &PH, source_endpoint: &Endpoint, source_local_socket: &SI::LocalSocket, source_local_interface: &SI::LocalInterface, mut data: PooledPacketBuffer) {
debug_event!( debug_event!(
si, si,
"[vl1] #{} {}->{} via {} length {} via socket {}@{}", "[vl1] {} -> #{} {}->{} length {} (on socket {}@{})",
source_endpoint.to_string(),
data.bytes_fixed_at::<8>(0).map_or("????????????????".into(), |pid| zerotier_core_crypto::hex::to_string(pid)), data.bytes_fixed_at::<8>(0).map_or("????????????????".into(), |pid| zerotier_core_crypto::hex::to_string(pid)),
data.bytes_fixed_at::<5>(13).map_or("??????????".into(), |src| zerotier_core_crypto::hex::to_string(src)), data.bytes_fixed_at::<5>(13).map_or("??????????".into(), |src| zerotier_core_crypto::hex::to_string(src)),
data.bytes_fixed_at::<5>(8).map_or("??????????".into(), |dest| zerotier_core_crypto::hex::to_string(dest)), data.bytes_fixed_at::<5>(8).map_or("??????????".into(), |dest| zerotier_core_crypto::hex::to_string(dest)),
source_endpoint.to_string(),
data.len(), data.len(),
source_local_socket.to_string(), source_local_socket.to_string(),
source_local_interface.to_string() source_local_interface.to_string()
@ -517,11 +517,11 @@ impl<SI: SystemInterface> Node<SI> {
if fragment_header.is_fragment() { if fragment_header.is_fragment() {
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
let fragment_header_id = u64::from_be_bytes(fragment_header.id); let fragment_header_id = u64::from_be_bytes(fragment_header.id);
debug_event!(si, "-- #{:0>16x} fragment {} of {} received", u64::from_be_bytes(fragment_header.id), fragment_header.fragment_no(), fragment_header.total_fragments()); debug_event!(si, "[vl1] #{:0>16x} fragment {} of {} received", u64::from_be_bytes(fragment_header.id), fragment_header.fragment_no(), fragment_header.total_fragments());
if let Some(assembled_packet) = path.receive_fragment(fragment_header.packet_id(), fragment_header.fragment_no(), fragment_header.total_fragments(), data, time_ticks) { if let Some(assembled_packet) = path.receive_fragment(fragment_header.packet_id(), fragment_header.fragment_no(), fragment_header.total_fragments(), data, time_ticks) {
if let Some(frag0) = assembled_packet.frags[0].as_ref() { if let Some(frag0) = assembled_packet.frags[0].as_ref() {
debug_event!(si, "-- #{:0>16x} packet fully assembled!", fragment_header_id); debug_event!(si, "[vl1] #{:0>16x} packet fully assembled!", fragment_header_id);
if let Ok(packet_header) = frag0.struct_at::<PacketHeader>(0) { if let Ok(packet_header) = frag0.struct_at::<PacketHeader>(0) {
if let Some(source) = Address::from_bytes(&packet_header.src) { if let Some(source) = Address::from_bytes(&packet_header.src) {
@ -537,7 +537,7 @@ impl<SI: SystemInterface> Node<SI> {
} else { } else {
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
if let Ok(packet_header) = data.struct_at::<PacketHeader>(0) { if let Ok(packet_header) = data.struct_at::<PacketHeader>(0) {
debug_event!(si, "-- #{:0>16x} is unfragmented", u64::from_be_bytes(packet_header.id)); debug_event!(si, "[vl1] #{:0>16x} is unfragmented", u64::from_be_bytes(packet_header.id));
if let Some(source) = Address::from_bytes(&packet_header.src) { if let Some(source) = Address::from_bytes(&packet_header.src) {
if let Some(peer) = self.peer(source) { if let Some(peer) = self.peer(source) {
@ -557,9 +557,9 @@ impl<SI: SystemInterface> Node<SI> {
{ {
debug_packet_id = u64::from_be_bytes(fragment_header.id); debug_packet_id = u64::from_be_bytes(fragment_header.id);
} }
debug_event!(si, "-- #{:0>16x} forwarding packet fragment to {}", debug_packet_id, dest.to_string()); debug_event!(si, "[vl1] #{:0>16x} forwarding packet fragment to {}", debug_packet_id, dest.to_string());
if fragment_header.increment_hops() > FORWARD_MAX_HOPS { if fragment_header.increment_hops() > FORWARD_MAX_HOPS {
debug_event!(si, "-- #{:0>16x} discarded: max hops exceeded!", debug_packet_id); debug_event!(si, "[vl1] #{:0>16x} discarded: max hops exceeded!", debug_packet_id);
return; return;
} }
} else { } else {
@ -568,9 +568,9 @@ impl<SI: SystemInterface> Node<SI> {
{ {
debug_packet_id = u64::from_be_bytes(packet_header.id); debug_packet_id = u64::from_be_bytes(packet_header.id);
} }
debug_event!(si, "-- #{:0>16x} forwarding packet to {}", debug_packet_id, dest.to_string()); debug_event!(si, "[vl1] #{:0>16x} forwarding packet to {}", debug_packet_id, dest.to_string());
if packet_header.increment_hops() > FORWARD_MAX_HOPS { if packet_header.increment_hops() > FORWARD_MAX_HOPS {
debug_event!(si, "-- #{:0>16x} discarded: max hops exceeded!", u64::from_be_bytes(packet_header.id)); debug_event!(si, "[vl1] #{:0>16x} discarded: max hops exceeded!", u64::from_be_bytes(packet_header.id));
return; return;
} }
} else { } else {
@ -581,7 +581,7 @@ impl<SI: SystemInterface> Node<SI> {
if let Some(peer) = self.peer(dest) { if let Some(peer) = self.peer(dest) {
// TODO: SHOULD we forward? Need a way to check. // TODO: SHOULD we forward? Need a way to check.
peer.forward(si, time_ticks, data.as_ref()).await; peer.forward(si, time_ticks, data.as_ref()).await;
debug_event!(si, "-- #{:0>16x} forwarded successfully", debug_packet_id); debug_event!(si, "[vl1] #{:0>16x} forwarded successfully", debug_packet_id);
} }
} }
} }
@ -618,9 +618,8 @@ impl<SI: SystemInterface> Node<SI> {
pub fn canonical_path(&self, ep: &Endpoint, local_socket: &SI::LocalSocket, local_interface: &SI::LocalInterface, time_ticks: i64) -> Arc<Path<SI>> { pub fn canonical_path(&self, ep: &Endpoint, local_socket: &SI::LocalSocket, local_interface: &SI::LocalInterface, time_ticks: i64) -> Arc<Path<SI>> {
if let Some(path) = self.paths.read().get(&PathKey::Ref(ep, local_socket)) { if let Some(path) = self.paths.read().get(&PathKey::Ref(ep, local_socket)) {
path.clone() return path.clone();
} else {
self.paths.write().entry(PathKey::Copied(ep.clone(), local_socket.clone())).or_insert_with(|| Arc::new(Path::new(ep.clone(), local_socket.clone(), local_interface.clone(), time_ticks))).clone()
} }
return self.paths.write().entry(PathKey::Copied(ep.clone(), local_socket.clone())).or_insert_with(|| Arc::new(Path::new(ep.clone(), local_socket.clone(), local_interface.clone(), time_ticks))).clone();
} }
} }

View file

@ -9,7 +9,6 @@ use parking_lot::{Mutex, RwLock};
use zerotier_core_crypto::aes_gmac_siv::AesCtr; use zerotier_core_crypto::aes_gmac_siv::AesCtr;
use zerotier_core_crypto::hash::*; use zerotier_core_crypto::hash::*;
use zerotier_core_crypto::poly1305::Poly1305;
use zerotier_core_crypto::random::{get_bytes_secure, next_u64_secure}; use zerotier_core_crypto::random::{get_bytes_secure, next_u64_secure};
use zerotier_core_crypto::salsa::Salsa; use zerotier_core_crypto::salsa::Salsa;
use zerotier_core_crypto::secret::Secret; use zerotier_core_crypto::secret::Secret;
@ -62,32 +61,30 @@ pub struct Peer<SI: SystemInterface> {
remote_protocol_version: AtomicU8, remote_protocol_version: AtomicU8,
} }
/// Create initialized instances of Salsa20/12 and Poly1305 for a packet. /// Create initialized instances of Salsa20/12 and Poly1305 for a packet (LEGACY).
/// This is deprecated and is not used with AES-GMAC-SIV. fn salsa_poly_create(secret: &SymmetricSecret, header: &PacketHeader, packet_size: usize) -> (Salsa<12>, [u8; 32]) {
fn salsa_poly_create(secret: &SymmetricSecret, header: &PacketHeader, packet_size: usize) -> (Salsa<12>, Poly1305) {
// Create a per-packet key from the IV, source, destination, and packet size. // Create a per-packet key from the IV, source, destination, and packet size.
let mut key: Secret<32> = secret.key.first_n(); let mut key: Secret<32> = secret.key.first_n();
let hb = header.as_bytes(); let hb = header.as_bytes();
for i in 0..18 { for i in 0..18 {
key.0[i] ^= hb[i]; key.0[i] ^= hb[i];
} }
key.0[18] ^= hb[packet_constants::FLAGS_FIELD_INDEX] & packet_constants::FLAGS_FIELD_MASK_HIDE_HOPS; key.0[18] ^= header.flags_cipher_hops & packet_constants::FLAGS_FIELD_MASK_HIDE_HOPS;
key.0[19] ^= (packet_size >> 8) as u8; key.0[19] ^= packet_size as u8;
key.0[20] ^= packet_size as u8; key.0[20] ^= packet_size.wrapping_shr(8) as u8;
let mut salsa = Salsa::<12>::new(&key.0, &header.id); let mut salsa = Salsa::<12>::new(&key.0, &header.id);
let mut poly1305_key = [0_u8; 32]; let mut poly1305_key = [0_u8; 32];
salsa.crypt_in_place(&mut poly1305_key); salsa.crypt_in_place(&mut poly1305_key);
(salsa, poly1305_key)
(salsa, Poly1305::new(&poly1305_key))
} }
/// Attempt AEAD packet encryption and MAC validation. Returns message ID on success. /// 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<PooledPacketBuffer>], payload: &mut PacketBuffer) -> Option<MessageId> { fn try_aead_decrypt(secret: &SymmetricSecret, packet_frag0_payload_bytes: &[u8], header: &PacketHeader, fragments: &[Option<PooledPacketBuffer>], payload: &mut PacketBuffer) -> Option<MessageId> {
packet_frag0_payload_bytes.get(0).map_or(None, |verb| { packet_frag0_payload_bytes.get(0).map_or(None, |verb| {
match header.cipher() { let cipher = header.cipher();
security_constants::CIPHER_NOCRYPT_POLY1305 => { match cipher {
security_constants::CIPHER_NOCRYPT_POLY1305 | security_constants::CIPHER_SALSA2012_POLY1305 => {
if (verb & packet_constants::VERB_MASK) == verbs::VL1_HELLO { if (verb & packet_constants::VERB_MASK) == verbs::VL1_HELLO {
let mut total_packet_len = packet_frag0_payload_bytes.len() + packet_constants::HEADER_SIZE; let mut total_packet_len = packet_frag0_payload_bytes.len() + packet_constants::HEADER_SIZE;
for f in fragments.iter() { for f in fragments.iter() {
@ -97,9 +94,12 @@ fn try_aead_decrypt(secret: &SymmetricSecret, packet_frag0_payload_bytes: &[u8],
for f in fragments.iter() { 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 _ = f.as_ref().map(|f| f.as_bytes_starting_at(packet_constants::HEADER_SIZE).map(|f| payload.append_bytes(f)));
} }
let (_, mut poly) = salsa_poly_create(secret, header, total_packet_len); let (mut salsa, poly1305_key) = salsa_poly_create(secret, header, total_packet_len);
poly.update(payload.as_bytes()); let mac = zerotier_core_crypto::poly1305::compute(&poly1305_key, &payload.as_bytes());
if poly.finish()[0..8].eq(&header.mac) { 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)) Some(u64::from_ne_bytes(header.id))
} else { } else {
None None
@ -110,29 +110,6 @@ fn try_aead_decrypt(secret: &SymmetricSecret, packet_frag0_payload_bytes: &[u8],
} }
} }
security_constants::CIPHER_SALSA2012_POLY1305 => {
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 (mut salsa, mut poly) = salsa_poly_create(secret, header, total_packet_len);
poly.update(packet_frag0_payload_bytes);
let _ = payload.append_bytes_get_mut(packet_frag0_payload_bytes.len()).map(|b| salsa.crypt(packet_frag0_payload_bytes, b));
for f in fragments.iter() {
let _ = f.as_ref().map(|f| {
f.as_bytes_starting_at(packet_constants::FRAGMENT_HEADER_SIZE).map(|f| {
poly.update(f);
let _ = payload.append_bytes_get_mut(f.len()).map(|b| salsa.crypt(f, b));
})
});
}
if poly.finish()[0..8].eq(&header.mac) {
Some(u64::from_ne_bytes(header.id))
} else {
None
}
}
security_constants::CIPHER_AES_GMAC_SIV => { security_constants::CIPHER_AES_GMAC_SIV => {
let mut aes = secret.aes_gmac_siv.get(); let mut aes = secret.aes_gmac_siv.get();
aes.decrypt_init(&header.aes_gmac_siv_tag()); aes.decrypt_init(&header.aes_gmac_siv_tag());
@ -255,14 +232,14 @@ impl<SI: SystemInterface> Peer<SI> {
/// those fragments after the main packet header and first chunk. /// those fragments after the main packet header and first chunk.
/// ///
/// This returns true if the packet decrypted and passed authentication. /// This returns true if the packet decrypted and passed authentication.
pub(crate) async fn receive<PH: InnerProtocolInterface>(&self, node: &Node<SI>, si: &SI, ph: &PH, time_ticks: i64, source_path: &Arc<Path<SI>>, header: &PacketHeader, frag0: &PacketBuffer, fragments: &[Option<PooledPacketBuffer>]) { pub(crate) async fn receive<PH: InnerProtocolInterface>(&self, node: &Node<SI>, si: &SI, ph: &PH, time_ticks: i64, source_path: &Arc<Path<SI>>, packet_header: &PacketHeader, frag0: &PacketBuffer, fragments: &[Option<PooledPacketBuffer>]) {
if let Ok(packet_frag0_payload_bytes) = frag0.as_bytes_starting_at(packet_constants::VERB_INDEX) { if let Ok(packet_frag0_payload_bytes) = frag0.as_bytes_starting_at(packet_constants::VERB_INDEX) {
//let mut payload = unsafe { PacketBuffer::new_without_memzero() }; //let mut payload = unsafe { PacketBuffer::new_without_memzero() };
let mut payload = PacketBuffer::new(); let mut payload = PacketBuffer::new();
// First try decrypting and authenticating with an ephemeral secret if one is negotiated. // First try decrypting and authenticating with an ephemeral secret if one is negotiated.
let (forward_secrecy, mut message_id) = if let Some(ephemeral_secret) = self.ephemeral_symmetric_key.read().as_ref() { let (forward_secrecy, mut message_id) = if let Some(ephemeral_secret) = self.ephemeral_symmetric_key.read().as_ref() {
if let Some(message_id) = try_aead_decrypt(&ephemeral_secret.secret, packet_frag0_payload_bytes, header, fragments, &mut payload) { if let Some(message_id) = try_aead_decrypt(&ephemeral_secret.secret, packet_frag0_payload_bytes, packet_header, fragments, &mut payload) {
// Decryption successful with ephemeral secret // Decryption successful with ephemeral secret
(true, message_id) (true, message_id)
} else { } else {
@ -276,11 +253,13 @@ impl<SI: SystemInterface> Peer<SI> {
// If forward_secrecy is false it means the ephemeral key failed. Try decrypting with the permanent key. // If forward_secrecy is false it means the ephemeral key failed. Try decrypting with the permanent key.
if !forward_secrecy { if !forward_secrecy {
if let Some(message_id2) = try_aead_decrypt(&self.identity_symmetric_key, packet_frag0_payload_bytes, header, fragments, &mut payload) { payload.clear();
if let Some(message_id2) = try_aead_decrypt(&self.identity_symmetric_key, packet_frag0_payload_bytes, packet_header, fragments, &mut payload) {
// Decryption successful with static secret. // Decryption successful with static secret.
message_id = message_id2; message_id = message_id2;
} else { } else {
// Packet failed to decrypt using either ephemeral or permament key, reject. // Packet failed to decrypt using either ephemeral or permament key, reject.
debug_event!(si, "[vl1] #{:0>16x} failed authentication", u64::from_be_bytes(packet_header.id));
return; return;
} }
} }
@ -308,6 +287,8 @@ impl<SI: SystemInterface> Peer<SI> {
// If we made it here it decrypted and passed authentication. // 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);
if (verb & packet_constants::VERB_FLAG_COMPRESSED) != 0 { if (verb & packet_constants::VERB_FLAG_COMPRESSED) != 0 {
let mut decompressed_payload: [u8; packet_constants::SIZE_MAX] = unsafe { MaybeUninit::uninit().assume_init() }; let mut decompressed_payload: [u8; packet_constants::SIZE_MAX] = unsafe { MaybeUninit::uninit().assume_init() };
decompressed_payload[0] = verb; decompressed_payload[0] = verb;
@ -473,8 +454,10 @@ impl<SI: SystemInterface> Peer<SI> {
hello_fixed_headers.timestamp = si.time_clock().to_be_bytes(); hello_fixed_headers.timestamp = si.time_clock().to_be_bytes();
} }
// Full identity of the node establishing the session. assert_eq!(packet.len(), 41);
assert!(self.identity.marshal_with_options(&mut packet, Identity::ALGORITHM_ALL, false).is_ok());
// Full identity of this node.
assert!(node.identity.marshal_with_options(&mut packet, Identity::ALGORITHM_ALL, false).is_ok());
// Append two reserved bytes, currently always zero. // Append two reserved bytes, currently always zero.
assert!(packet.append_padding(0, 2).is_ok()); assert!(packet.append_padding(0, 2).is_ok());
@ -525,9 +508,9 @@ impl<SI: SystemInterface> Peer<SI> {
drop(hmac); drop(hmac);
// Set poly1305 in header, which is the only authentication for old nodes. // Set poly1305 in header, which is the only authentication for old nodes.
let (_, mut poly) = salsa_poly_create(&self.identity_symmetric_key, packet.struct_at::<PacketHeader>(0).unwrap(), packet.len()); let (_, poly1305_key) = salsa_poly_create(&self.identity_symmetric_key, packet.struct_at::<PacketHeader>(0).unwrap(), packet.len());
poly.update(packet.as_bytes_starting_at(packet_constants::HEADER_SIZE).unwrap()); let mac = zerotier_core_crypto::poly1305::compute(&poly1305_key, packet.as_bytes_starting_at(packet_constants::HEADER_SIZE).unwrap());
packet.as_mut()[packet_constants::MAC_FIELD_INDEX..packet_constants::MAC_FIELD_INDEX + 8].copy_from_slice(&poly.finish()[0..8]); packet.as_mut()[packet_constants::MAC_FIELD_INDEX..packet_constants::MAC_FIELD_INDEX + 8].copy_from_slice(&mac[0..8]);
self.last_send_time_ticks.store(time_ticks, Ordering::Relaxed); self.last_send_time_ticks.store(time_ticks, Ordering::Relaxed);

View file

@ -19,7 +19,7 @@ num-traits = "^0"
tokio = { version = "^1", features = ["full"], default-features = false } tokio = { version = "^1", features = ["full"], default-features = false }
serde = { version = "^1", features = ["derive"], default-features = false } serde = { version = "^1", features = ["derive"], default-features = false }
serde_json = { version = "^1", features = ["std"], default-features = false } serde_json = { version = "^1", features = ["std"], default-features = false }
parking_lot = "^0" parking_lot = { version = "^0", features = [], default-features = false }
lazy_static = "^1" lazy_static = "^1"
clap = { version = "^3", features = ["std", "suggestions"], default-features = false } clap = { version = "^3", features = ["std", "suggestions"], default-features = false }
log = "^0" log = "^0"

View file

@ -111,10 +111,10 @@ impl Default for GlobalSettings {
impl GlobalSettings { impl GlobalSettings {
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
pub const DEFAULT_PREFIX_BLACKLIST: [&'static str; 9] = ["lo", "utun", "gif", "stf", "iptap", "pktap", "feth", "zt", "llw"]; pub const DEFAULT_PREFIX_BLACKLIST: [&'static str; 10] = ["lo", "utun", "gif", "stf", "iptap", "pktap", "feth", "zt", "llw", "anpi"];
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
pub const DEFAULT_PREFIX_BLACKLIST: [&'static str; 5] = ["lo", "tun", "tap", "ipsec", "zt", "tailscale"]; pub const DEFAULT_PREFIX_BLACKLIST: [&'static str; 5] = ["lo", "tun", "tap", "ipsec", "zt"];
#[cfg(windows)] #[cfg(windows)]
pub const DEFAULT_PREFIX_BLACKLIST: [&'static str; 0] = []; pub const DEFAULT_PREFIX_BLACKLIST: [&'static str; 0] = [];

View file

@ -197,11 +197,16 @@ impl SystemInterface for ServiceImpl {
if !sockets.is_empty() { if !sockets.is_empty() {
if let Some(specific_interface) = local_interface { if let Some(specific_interface) = local_interface {
for (_, p) in sockets.iter() { for (_, p) in sockets.iter() {
for s in p.sockets.iter() { if !p.sockets.is_empty() {
if s.interface.eq(specific_interface) { let mut i = (random::next_u32_secure() as usize) % p.sockets.len();
if s.send_sync_nonblock(&self.rt, address, data, packet_ttl) { for _ in 0..p.sockets.len() {
return true; let s = p.sockets.get(i).unwrap();
if s.interface.eq(specific_interface) {
if s.send_sync_nonblock(&self.rt, address, data, packet_ttl) {
return true;
}
} }
i = (i + 1) % p.sockets.len();
} }
} }
} }
@ -211,12 +216,16 @@ impl SystemInterface for ServiceImpl {
let rn = random::xorshift64_random() as usize; let rn = random::xorshift64_random() as usize;
for i in 0..bound_ports.len() { for i in 0..bound_ports.len() {
let p = sockets.get(*bound_ports.get(rn.wrapping_add(i) % bound_ports.len()).unwrap()).unwrap(); let p = sockets.get(*bound_ports.get(rn.wrapping_add(i) % bound_ports.len()).unwrap()).unwrap();
for s in p.sockets.iter() { if !p.sockets.is_empty() {
if !sent_on_interfaces.contains(&s.interface) { let mut i = (random::next_u32_secure() as usize) % p.sockets.len();
if s.send_sync_nonblock(&self.rt, address, data, packet_ttl) { for _ in 0..p.sockets.len() {
sent_on_interfaces.insert(s.interface.clone()); let s = p.sockets.get(i).unwrap();
break; if !sent_on_interfaces.contains(&s.interface) {
if s.send_sync_nonblock(&self.rt, address, data, packet_ttl) {
sent_on_interfaces.insert(s.interface.clone());
}
} }
i = (i + 1) % p.sockets.len();
} }
} }
} }