From 4a9938dfd375a08694e937ad99abf0324acfeeb0 Mon Sep 17 00:00:00 2001 From: Adam Ierymenko Date: Fri, 10 Dec 2021 21:57:50 -0500 Subject: [PATCH] Implement identity V1 so as to make it backward compatibile with old versions, and tons of build fixes. --- aes-gmac-siv/src/impl_gcrypt.rs | 8 + aes-gmac-siv/src/impl_macos.rs | 8 + aes-gmac-siv/src/impl_openssl.rs | 8 + aes-gmac-siv/src/lib.rs | 8 + zerotier-core-crypto/src/array_concat.rs | 72 - zerotier-core-crypto/src/c25519.rs | 8 + zerotier-core-crypto/src/lib.rs | 2 +- zerotier-core-crypto/src/p521.rs | 100 +- zerotier-network-hypervisor/Cargo.toml | 1 + zerotier-network-hypervisor/src/lib.rs | 3 + .../src/networkhypervisor.rs | 4 +- .../src/util/buffer.rs | 125 +- zerotier-network-hypervisor/src/util/mod.rs | 5 +- zerotier-network-hypervisor/src/util/pool.rs | 56 +- .../src/vl1/address.rs | 16 +- .../src/vl1/endpoint.rs | 4 +- .../src/vl1/ephemeral.rs | 8 +- .../src/vl1/identity.rs | 1184 ++++++++--------- .../src/vl1/inetaddress.rs | 28 +- zerotier-network-hypervisor/src/vl1/mac.rs | 18 +- zerotier-network-hypervisor/src/vl1/mod.rs | 4 +- zerotier-network-hypervisor/src/vl1/node.rs | 21 +- zerotier-network-hypervisor/src/vl1/path.rs | 2 +- zerotier-network-hypervisor/src/vl1/peer.rs | 179 ++- .../src/vl1/protocol.rs | 43 +- .../src/vl1/rootset.rs | 3 +- .../src/vl1/symmetricsecret.rs | 5 - zerotier-network-hypervisor/src/vl2/switch.rs | 6 +- zerotier-system-service/Cargo.lock | 1 + 29 files changed, 960 insertions(+), 970 deletions(-) delete mode 100644 zerotier-core-crypto/src/array_concat.rs diff --git a/aes-gmac-siv/src/impl_gcrypt.rs b/aes-gmac-siv/src/impl_gcrypt.rs index 3ba20cf9b..3231f0673 100644 --- a/aes-gmac-siv/src/impl_gcrypt.rs +++ b/aes-gmac-siv/src/impl_gcrypt.rs @@ -1,3 +1,11 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * (c)2021 ZeroTier, Inc. + * https://www.zerotier.com/ + */ + // AES-GMAC-SIV implemented using libgcrypt. use std::io::Write; diff --git a/aes-gmac-siv/src/impl_macos.rs b/aes-gmac-siv/src/impl_macos.rs index 65b549063..3ebafdb87 100644 --- a/aes-gmac-siv/src/impl_macos.rs +++ b/aes-gmac-siv/src/impl_macos.rs @@ -1,3 +1,11 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * (c)2021 ZeroTier, Inc. + * https://www.zerotier.com/ + */ + // AES-GMAC-SIV implemented using MacOS/iOS CommonCrypto (MacOS 10.13 or newer required). use std::os::raw::{c_void, c_int}; diff --git a/aes-gmac-siv/src/impl_openssl.rs b/aes-gmac-siv/src/impl_openssl.rs index 04025d42e..fa3c8d187 100644 --- a/aes-gmac-siv/src/impl_openssl.rs +++ b/aes-gmac-siv/src/impl_openssl.rs @@ -1,3 +1,11 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * (c)2021 ZeroTier, Inc. + * https://www.zerotier.com/ + */ + // AES-GMAC-SIV implemented using OpenSSL. use openssl::symm::{Crypter, Cipher, Mode}; diff --git a/aes-gmac-siv/src/lib.rs b/aes-gmac-siv/src/lib.rs index bbb5b20b2..b47f5da0e 100644 --- a/aes-gmac-siv/src/lib.rs +++ b/aes-gmac-siv/src/lib.rs @@ -1,3 +1,11 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * (c)2021 ZeroTier, Inc. + * https://www.zerotier.com/ + */ + #[cfg(any(target_os = "macos", target_os = "ios"))] mod impl_macos; diff --git a/zerotier-core-crypto/src/array_concat.rs b/zerotier-core-crypto/src/array_concat.rs deleted file mode 100644 index 187b578d9..000000000 --- a/zerotier-core-crypto/src/array_concat.rs +++ /dev/null @@ -1,72 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. - * - * (c)2021 ZeroTier, Inc. - * https://www.zerotier.com/ - */ - -use std::mem::MaybeUninit; - -#[inline(always)] -pub fn concat_2_slices(s0: &[u8], s1: &[u8]) -> [u8; S] { - debug_assert_eq!(S0 + S1, S); - let mut tmp: [u8; S] = unsafe { MaybeUninit::uninit().assume_init() }; - tmp[..S0].copy_from_slice(s0); - tmp[S0..].copy_from_slice(s1); - tmp -} - -#[inline(always)] -pub fn concat_2_arrays(s0: &[u8; S0], s1: &[u8; S1]) -> [u8; S] { - concat_2_slices::(s0, s1) -} - -#[inline(always)] -pub fn concat_3_slices(s0: &[u8], s1: &[u8], s2: &[u8]) -> [u8; S] { - debug_assert_eq!(S0 + S1 + S2, S); - let mut tmp: [u8; S] = unsafe { MaybeUninit::uninit().assume_init() }; - tmp[..S0].copy_from_slice(s0); - tmp[S0..S1].copy_from_slice(s1); - tmp[(S0 + S1)..].copy_from_slice(s2); - tmp -} - -#[inline(always)] -pub fn concat_3_arrays(s0: &[u8; S0], s1: &[u8; S1], s2: &[u8; S2]) -> [u8; S] { - concat_3_slices::(s0, s1, s2) -} - -#[inline(always)] -pub fn concat_4_slices(s0: &[u8], s1: &[u8], s2: &[u8], s3: &[u8]) -> [u8; S] { - debug_assert_eq!(S0 + S1 + S2 + S3, S); - let mut tmp: [u8; S] = unsafe { MaybeUninit::uninit().assume_init() }; - tmp[..S0].copy_from_slice(s0); - tmp[S0..S1].copy_from_slice(s1); - tmp[(S0 + S1)..(S0 + S1 + S2)].copy_from_slice(s2); - tmp[(S0 + S1 + S2)..].copy_from_slice(s3); - tmp -} - -#[inline(always)] -pub fn concat_4_arrays(s0: &[u8; S0], s1: &[u8; S1], s2: &[u8; S2], s3: &[u8; S3]) -> [u8; S] { - concat_4_slices::(s0, s1, s2, s3) -} - - -#[inline(always)] -pub fn concat_5_slices(s0: &[u8], s1: &[u8], s2: &[u8], s3: &[u8], s4: &[u8]) -> [u8; S] { - debug_assert_eq!(S0 + S1 + S2 + S3 + S4, S); - let mut tmp: [u8; S] = unsafe { MaybeUninit::uninit().assume_init() }; - tmp[..S0].copy_from_slice(s0); - tmp[S0..S1].copy_from_slice(s1); - tmp[(S0 + S1)..(S0 + S1 + S2)].copy_from_slice(s2); - tmp[(S0 + S1 + S2)..(S0 + S1 + S2 + S3)].copy_from_slice(s3); - tmp[(S0 + S1 + S2 + S3)..].copy_from_slice(s4); - tmp -} - -#[inline(always)] -pub fn concat_5_arrays(s0: &[u8; S0], s1: &[u8; S1], s2: &[u8; S2], s3: &[u8; S3], s4: &[u8; S4]) -> [u8; S] { - concat_5_slices::(s0, s1, s2, s3, s4) -} diff --git a/zerotier-core-crypto/src/c25519.rs b/zerotier-core-crypto/src/c25519.rs index 8785be12c..f3ad96bc5 100644 --- a/zerotier-core-crypto/src/c25519.rs +++ b/zerotier-core-crypto/src/c25519.rs @@ -59,6 +59,10 @@ impl C25519KeyPair { } } +impl Clone for C25519KeyPair { + fn clone(&self) -> Self { Self(x25519_dalek::StaticSecret::from(self.0.to_bytes()), x25519_dalek::PublicKey::from(self.1.to_bytes())) } +} + /// Ed25519 key pair for EDDSA signatures. pub struct Ed25519KeyPair(ed25519_dalek::Keypair); @@ -113,6 +117,10 @@ impl Ed25519KeyPair { } } +impl Clone for Ed25519KeyPair { + fn clone(&self) -> Self { Self(ed25519_dalek::Keypair::from_bytes(&self.0.to_bytes()).unwrap()) } +} + pub fn ed25519_verify(public_key: &[u8], signature: &[u8], msg: &[u8]) -> bool { if public_key.len() == 32 && signature.len() >= 64 { ed25519_dalek::PublicKey::from_bytes(public_key).map_or(false, |pk| { diff --git a/zerotier-core-crypto/src/lib.rs b/zerotier-core-crypto/src/lib.rs index 5e5fb4d47..f0afd3e38 100644 --- a/zerotier-core-crypto/src/lib.rs +++ b/zerotier-core-crypto/src/lib.rs @@ -18,7 +18,7 @@ pub mod secret; pub mod hex; pub mod varint; pub mod sidhp751; -pub mod array_concat; pub use aes_gmac_siv; pub use rand_core; +pub use subtle; diff --git a/zerotier-core-crypto/src/p521.rs b/zerotier-core-crypto/src/p521.rs index 5abeb9edf..01e85bd4f 100644 --- a/zerotier-core-crypto/src/p521.rs +++ b/zerotier-core-crypto/src/p521.rs @@ -70,6 +70,55 @@ pub struct P521PublicKey { public_key_bytes: [u8; P521_PUBLIC_KEY_SIZE], } +impl P521PublicKey { + /// Construct a public key from a byte serialized representation. + /// None is returned if the input is not valid. No advanced checking such as + /// determining if this is a point on the curve is performed. + pub fn from_bytes(b: &[u8]) -> Option { + if b.len() == P521_PUBLIC_KEY_SIZE { + Some(P521PublicKey { + public_key: SExpression::from_str(format!("(public-key(ecc(curve nistp521)(q #04{}#)))", crate::hex::to_string(b)).as_str()).unwrap(), + public_key_bytes: b.try_into().unwrap(), + }) + } else { + None + } + } + + /// Verify a signature. + /// Message data does not need to be pre-hashed. + pub fn verify(&self, msg: &[u8], signature: &[u8]) -> bool { + if signature.len() == P521_ECDSA_SIGNATURE_SIZE { + let data = SExpression::from_str(unsafe { std::str::from_utf8_unchecked(&hash_to_data_sexp(msg)) }).unwrap(); + let sig = SExpression::from_str(format!("(sig-val(ecdsa(r #{}#)(s #{}#)))", crate::hex::to_string(&signature[0..66]), crate::hex::to_string(&signature[66..132])).as_str()).unwrap(); + gcrypt::pkey::verify(&self.public_key, &data, &sig).is_ok() + } else { + false + } + } + + #[inline(always)] + pub fn public_key_bytes(&self) -> &[u8; P521_PUBLIC_KEY_SIZE] { + &self.public_key_bytes + } +} + +impl PartialEq for P521PublicKey { + #[inline(always)] + fn eq(&self, other: &Self) -> bool { self.public_key_bytes.eq(&other.public_key_bytes) } +} + +impl Eq for P521PublicKey {} + +impl Clone for P521PublicKey { + fn clone(&self) -> Self { + Self { + public_key: SExpression::from_bytes(self.public_key.get_bytes(0).unwrap()).unwrap(), + public_key_bytes: self.public_key_bytes.clone() + } + } +} + /// NIST P-521 elliptic curve key pair. /// This supports both ECDSA signing and ECDH key agreement. In practice the same key pair /// is not used for both functions as this is considred bad practice. @@ -181,48 +230,21 @@ impl P521KeyPair { } } -impl P521PublicKey { - /// Construct a public key from a byte serialized representation. - /// None is returned if the input is not valid. No advanced checking such as - /// determining if this is a point on the curve is performed. - pub fn from_bytes(b: &[u8]) -> Option { - if b.len() == P521_PUBLIC_KEY_SIZE { - Some(P521PublicKey { - public_key: SExpression::from_str(format!("(public-key(ecc(curve nistp521)(q #04{}#)))", crate::hex::to_string(b)).as_str()).unwrap(), - public_key_bytes: b.try_into().unwrap(), - }) - } else { - None - } - } - - /// Verify a signature. - /// Message data does not need to be pre-hashed. - pub fn verify(&self, msg: &[u8], signature: &[u8]) -> bool { - if signature.len() == P521_ECDSA_SIGNATURE_SIZE { - let data = SExpression::from_str(unsafe { std::str::from_utf8_unchecked(&hash_to_data_sexp(msg)) }).unwrap(); - let sig = SExpression::from_str(format!("(sig-val(ecdsa(r #{}#)(s #{}#)))", crate::hex::to_string(&signature[0..66]), crate::hex::to_string(&signature[66..132])).as_str()).unwrap(); - gcrypt::pkey::verify(&self.public_key, &data, &sig).is_ok() - } else { - false - } - } - - #[inline(always)] - pub fn public_key_bytes(&self) -> &[u8; P521_PUBLIC_KEY_SIZE] { - &self.public_key_bytes - } +impl PartialEq for P521KeyPair { + fn eq(&self, other: &Self) -> bool { self.secret_key_bytes.0.eq(&other.secret_key_bytes.0) } } -impl PartialEq for P521PublicKey { - #[inline(always)] - fn eq(&self, other: &Self) -> bool { self.public_key_bytes.eq(&other.public_key_bytes) } -} +impl Eq for P521KeyPair {} -impl Eq for P521PublicKey {} - -impl Clone for P521PublicKey { - fn clone(&self) -> Self { P521PublicKey::from_bytes(&self.public_key_bytes).unwrap() } +impl Clone for P521KeyPair { + fn clone(&self) -> Self { + Self { + public_key: self.public_key.clone(), + secret_key_for_ecdsa: SExpression::from_bytes(self.secret_key_for_ecdsa.get_bytes(0).unwrap()).unwrap(), + secret_key_for_ecdh: SExpression::from_bytes(self.secret_key_for_ecdh.get_bytes(0).unwrap()).unwrap(), + secret_key_bytes: self.secret_key_bytes.clone(), + } + } } #[cfg(test)] diff --git a/zerotier-network-hypervisor/Cargo.toml b/zerotier-network-hypervisor/Cargo.toml index ca12737a4..29f5b2c2e 100644 --- a/zerotier-network-hypervisor/Cargo.toml +++ b/zerotier-network-hypervisor/Cargo.toml @@ -17,6 +17,7 @@ base64 = "^0" lz4_flex = { version = "^0", features = ["safe-encode", "safe-decode", "checked-decode"] } dashmap = "^4" parking_lot = "^0" +lazy_static = "^1" [target."cfg(not(windows))".dependencies] libc = "^0" diff --git a/zerotier-network-hypervisor/src/lib.rs b/zerotier-network-hypervisor/src/lib.rs index 16a99ed95..2247f733b 100644 --- a/zerotier-network-hypervisor/src/lib.rs +++ b/zerotier-network-hypervisor/src/lib.rs @@ -6,6 +6,9 @@ * https://www.zerotier.com/ */ +#[macro_use] +extern crate lazy_static; + pub mod util; pub mod error; pub mod vl1; diff --git a/zerotier-network-hypervisor/src/networkhypervisor.rs b/zerotier-network-hypervisor/src/networkhypervisor.rs index 27c47fd0e..14a2839ec 100644 --- a/zerotier-network-hypervisor/src/networkhypervisor.rs +++ b/zerotier-network-hypervisor/src/networkhypervisor.rs @@ -23,9 +23,9 @@ pub struct NetworkHypervisor { } impl NetworkHypervisor { - pub fn new(ci: &CI, auto_generate_identity_type: Option) -> Result { + pub fn new(ci: &CI, auto_generate_identity: bool) -> Result { Ok(NetworkHypervisor { - vl1: Node::new(ci, auto_generate_identity_type)?, + vl1: Node::new(ci, auto_generate_identity)?, vl2: Switch::new(), }) } diff --git a/zerotier-network-hypervisor/src/util/buffer.rs b/zerotier-network-hypervisor/src/util/buffer.rs index b54428bc9..4b18e600a 100644 --- a/zerotier-network-hypervisor/src/util/buffer.rs +++ b/zerotier-network-hypervisor/src/util/buffer.rs @@ -39,11 +39,10 @@ impl Buffer { #[inline(always)] pub fn new() -> Self { Self(0, [0_u8; L]) } - /// Create a zero size buffer without zeroing its actual memory. + /// Create an empty buffer without zeroing its memory (saving a bit of CPU). #[inline(always)] - pub unsafe fn new_nozero() -> Self { Self(0, MaybeUninit::uninit().assume_init()) } + pub unsafe fn new_without_memzero() -> Self { Self(0, MaybeUninit::uninit().assume_init()) } - /// Get a Buffer initialized with a copy of a byte slice. #[inline(always)] pub fn from_bytes(b: &[u8]) -> std::io::Result { let l = b.len(); @@ -155,7 +154,7 @@ impl Buffer { /// Append a runtime sized array and return a mutable reference to its memory. pub fn append_bytes_get_mut(&mut self, s: usize) -> std::io::Result<&mut [u8]> { let ptr = self.0; - let end = ptr + l; + let end = ptr + s; if end <= L { self.0 = end; Ok(&mut self.1[ptr..end]) @@ -164,7 +163,6 @@ impl Buffer { } } - /// Append a dynamic byte slice (copy into buffer). #[inline(always)] pub fn append_bytes(&mut self, buf: &[u8]) -> std::io::Result<()> { let ptr = self.0; @@ -178,7 +176,6 @@ impl Buffer { } } - /// Append a fixed length byte array (copy into buffer). #[inline(always)] pub fn append_bytes_fixed(&mut self, buf: &[u8; S]) -> std::io::Result<()> { let ptr = self.0; @@ -192,13 +189,11 @@ impl Buffer { } } - /// Append a variable length integer to this buffer. #[inline(always)] pub fn append_varint(&mut self, mut i: u64) -> std::io::Result<()> { crate::util::varint::write(self, i) } - /// Append a byte #[inline(always)] pub fn append_u8(&mut self, i: u8) -> std::io::Result<()> { let ptr = self.0; @@ -211,42 +206,84 @@ impl Buffer { } } - /// Append a 16-bit integer (in big-endian form) + #[cfg(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64"))] #[inline(always)] pub fn append_u16(&mut self, i: u16) -> std::io::Result<()> { let ptr = self.0; let end = ptr + 2; if end <= L { self.0 = end; - crate::util::store_u16_be(i, &mut self.1[ptr..end]); + unsafe { *self.1.as_mut_ptr().add(ptr).cast::() = i }; Ok(()) } else { Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG)) } } - /// Append a 32-bit integer (in big-endian form) + #[cfg(not(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64")))] + #[inline(always)] + pub fn append_u16(&mut self, i: u16) -> std::io::Result<()> { + let ptr = self.0; + let end = ptr + 2; + if end <= L { + self.0 = end; + self.1[ptr..end].copy_from_slice(&i.to_be_bytes()); + Ok(()) + } else { + Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG)) + } + } + + #[cfg(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64"))] #[inline(always)] pub fn append_u32(&mut self, i: u32) -> std::io::Result<()> { let ptr = self.0; let end = ptr + 4; if end <= L { self.0 = end; - crate::util::store_u32_be(i, &mut self.1[ptr..end]); + unsafe { *self.1.as_mut_ptr().add(ptr).cast::() = i }; Ok(()) } else { Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG)) } } - /// Append a 64-bit integer (in big-endian form) + #[cfg(not(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64")))] + #[inline(always)] + pub fn append_u32(&mut self, i: u32) -> std::io::Result<()> { + let ptr = self.0; + let end = ptr + 4; + if end <= L { + self.0 = end; + self.1[ptr..end].copy_from_slice(&i.to_be_bytes()); + Ok(()) + } else { + Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG)) + } + } + + #[cfg(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64"))] #[inline(always)] pub fn append_u64(&mut self, i: u64) -> std::io::Result<()> { let ptr = self.0; let end = ptr + 8; if end <= L { self.0 = end; - crate::util::store_u64_be(i, &mut self.1[ptr..end]); + unsafe { *self.1.as_mut_ptr().add(ptr).cast::() = i }; + Ok(()) + } else { + Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG)) + } + } + + #[cfg(not(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64")))] + #[inline(always)] + pub fn append_u64(&mut self, i: u64) -> std::io::Result<()> { + let ptr = self.0; + let end = ptr + 8; + if end <= L { + self.0 = end; + self.1[ptr..end].copy_from_slice(&i.to_be_bytes()); Ok(()) } else { Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG)) @@ -277,7 +314,6 @@ impl Buffer { } } - /// Get a byte at a fixed position. #[inline(always)] pub fn u8_at(&self, ptr: usize) -> std::io::Result { if ptr < self.0 { @@ -303,8 +339,6 @@ impl Buffer { } } - /// Get a fixed length byte array and advance the cursor. - /// This is slightly more efficient than reading a runtime sized byte slice. #[inline(always)] pub fn read_bytes_fixed(&self, cursor: &mut usize) -> std::io::Result<&[u8; S]> { let ptr = *cursor; @@ -320,7 +354,6 @@ impl Buffer { } } - /// Get a runtime specified length byte slice and advance the cursor. #[inline(always)] pub fn read_bytes(&self, l: usize, cursor: &mut usize) -> std::io::Result<&[u8]> { let ptr = *cursor; @@ -334,7 +367,6 @@ impl Buffer { } } - /// Get the next variable length integer and advance the cursor by its length in bytes. #[inline(always)] pub fn read_varint(&self, cursor: &mut usize) -> std::io::Result { let c = *cursor; @@ -350,7 +382,6 @@ impl Buffer { } } - /// Get the next u8 and advance the cursor. #[inline(always)] pub fn read_u8(&self, cursor: &mut usize) -> std::io::Result { let ptr = *cursor; @@ -363,7 +394,7 @@ impl Buffer { } } - /// Get the next u16 and advance the cursor. + #[cfg(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64"))] #[inline(always)] pub fn read_u16(&self, cursor: &mut usize) -> std::io::Result { let ptr = *cursor; @@ -371,13 +402,27 @@ impl Buffer { debug_assert!(end <= L); if end <= self.0 { *cursor = end; - Ok(u16::from_be_bytes(unsafe { *self.1.as_ptr().add(ptr).cast() })) + Ok((unsafe { *self.1.as_ptr().add(ptr).cast::() }).to_be()) } else { Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG)) } } - /// Get the next u32 and advance the cursor. + #[cfg(not(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64")))] + #[inline(always)] + pub fn read_u16(&self, cursor: &mut usize) -> std::io::Result { + let ptr = *cursor; + let end = ptr + 2; + debug_assert!(end <= L); + if end <= self.0 { + *cursor = end; + Ok(u16::from_be_bytes(*self.1[ptr..end])) + } else { + Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG)) + } + } + + #[cfg(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64"))] #[inline(always)] pub fn read_u32(&self, cursor: &mut usize) -> std::io::Result { let ptr = *cursor; @@ -385,13 +430,27 @@ impl Buffer { debug_assert!(end <= L); if end <= self.0 { *cursor = end; - Ok(u32::from_be_bytes(unsafe { *self.1.as_ptr().add(ptr).cast() })) + Ok((unsafe { *self.1.as_ptr().add(ptr).cast::() }).to_be()) } else { Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG)) } } - /// Get the next u64 and advance the cursor. + #[cfg(not(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64")))] + #[inline(always)] + pub fn read_u32(&self, cursor: &mut usize) -> std::io::Result { + let ptr = *cursor; + let end = ptr + 4; + debug_assert!(end <= L); + if end <= self.0 { + *cursor = end; + Ok(u32::from_be_bytes(*self.1[ptr..end])) + } else { + Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG)) + } + } + + #[cfg(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64"))] #[inline(always)] pub fn read_u64(&self, cursor: &mut usize) -> std::io::Result { let ptr = *cursor; @@ -399,7 +458,21 @@ impl Buffer { debug_assert!(end <= L); if end <= self.0 { *cursor = end; - Ok(u64::from_be_bytes(unsafe { *self.1.as_ptr().add(ptr).cast() })) + Ok((unsafe { *self.1.as_ptr().add(ptr).cast::() }).to_be()) + } else { + Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG)) + } + } + + #[cfg(not(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64")))] + #[inline(always)] + pub fn read_u64(&self, cursor: &mut usize) -> std::io::Result { + let ptr = *cursor; + let end = ptr + 8; + debug_assert!(end <= L); + if end <= self.0 { + *cursor = end; + Ok(u64::from_be_bytes(*self.1[ptr..end])) } else { Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG)) } diff --git a/zerotier-network-hypervisor/src/util/mod.rs b/zerotier-network-hypervisor/src/util/mod.rs index de818db5d..8874f4f81 100644 --- a/zerotier-network-hypervisor/src/util/mod.rs +++ b/zerotier-network-hypervisor/src/util/mod.rs @@ -16,15 +16,12 @@ pub use zerotier_core_crypto::varint; pub(crate) const ZEROES: [u8; 64] = [0_u8; 64]; /// Obtain a reference to a sub-array within an existing array. -/// Attempts to violate array bounds will panic or fail to compile. #[inline(always)] pub(crate) fn array_range(a: &[T; A]) -> &[T; LEN] { assert!((START + LEN) <= A); - unsafe { &*a.as_ptr().add(std::mem::size_of::() * start_index).cast::<[T; LEN]>() } + unsafe { &*a.as_ptr().add(START).cast::<[T; LEN]>() } } -/// Cast a u64 reference to a byte array in place. -/// Going the other direction is not safe on some architectures, but this should be safe everywhere. #[inline(always)] pub(crate) fn u64_as_bytes(i: &u64) -> &[u8; 8] { unsafe { &*(i as *const u64).cast() } } diff --git a/zerotier-network-hypervisor/src/util/pool.rs b/zerotier-network-hypervisor/src/util/pool.rs index c3118402b..208ce74d1 100644 --- a/zerotier-network-hypervisor/src/util/pool.rs +++ b/zerotier-network-hypervisor/src/util/pool.rs @@ -8,6 +8,7 @@ use std::ptr::NonNull; use std::sync::{Arc, Weak}; +use std::sync::atomic::{AtomicIsize, Ordering}; use parking_lot::Mutex; @@ -22,7 +23,11 @@ struct PoolEntry> { return_pool: Weak>, } -struct PoolInner>(F, Mutex>>>); +struct PoolInner> { + factory: F, + pool: Mutex>>>, + //outstanding_count: AtomicIsize +} /// Container for pooled objects that have been checked out of the pool. /// @@ -100,9 +105,9 @@ impl> Drop for Pooled { let p = Weak::upgrade(&self.0.as_ref().return_pool); if p.is_some() { let p = p.unwrap(); - p.0.reset(&mut self.0.as_mut().obj); - let mut q = p.1.lock(); - q.push(self.0.clone()) + p.factory.reset(&mut self.0.as_mut().obj); + p.pool.lock().push(self.0); + //let _ = p.outstanding_count.fetch_sub(1, Ordering::Release); } else { drop(Box::from_raw(self.0.as_ptr())) } @@ -117,21 +122,52 @@ pub struct Pool>(Arc>); impl> Pool { pub fn new(initial_stack_capacity: usize, factory: F) -> Self { - Self(Arc::new(PoolInner::(factory, Mutex::new(Vec::with_capacity(initial_stack_capacity))))) + Self(Arc::new(PoolInner:: { + factory, + pool: Mutex::new(Vec::with_capacity(initial_stack_capacity)), + //outstanding_count: AtomicIsize::new(0) + })) } /// Get a pooled object, or allocate one if the pool is empty. #[inline(always)] pub fn get(&self) -> Pooled { - unsafe { - Pooled::(self.0.1.lock().pop().unwrap_or_else(|| { + //let _ = self.0.outstanding_count.fetch_add(1, Ordering::Acquire); + Pooled::(self.0.pool.lock().pop().unwrap_or_else(|| { + unsafe { NonNull::new_unchecked(Box::into_raw(Box::new(PoolEntry:: { - obj: self.0.0.create(), + obj: self.0.factory.create(), return_pool: Arc::downgrade(&self.0), }))) - })) + } + })) + } + + /* + /// Get a pooled object, or allocate one if the pool is empty. + /// This will return None if there are more outstanding pooled objects than the limit. + /// The limit is exclusive, so a value of 0 will mean that only one outstanding + /// object will be permitted as in this case there were zero outstanding at time + /// of checkout. + #[inline(always)] + pub fn try_get(&self, outstanding_pooled_object_limit: usize) -> Option> { + let outstanding = self.0.outstanding_count.fetch_add(1, Ordering::Acquire); + debug_assert!(outstanding >= 0); + if outstanding as usize > outstanding_pooled_object_limit { + let _ = self.0.outstanding_count.fetch_sub(1, Ordering::Release); + None + } else { + Some(Pooled::(self.0.pool.lock().pop().unwrap_or_else(|| { + unsafe { + NonNull::new_unchecked(Box::into_raw(Box::new(PoolEntry:: { + obj: self.0.pool.create(), + return_pool: Arc::downgrade(&self.0), + }))) + } + }))) } } + */ /// Dispose of all pooled objects, freeing any memory they use. /// @@ -139,7 +175,7 @@ impl> Pool { /// objects will still be returned on drop unless the pool itself is dropped. This can /// be done to free some memory if there has been a spike in memory use. pub fn purge(&self) { - let mut p = self.0.1.lock(); + let mut p = self.0.pool.lock(); loop { let o = p.pop(); if o.is_some() { diff --git a/zerotier-network-hypervisor/src/vl1/address.rs b/zerotier-network-hypervisor/src/vl1/address.rs index 524a4d379..60c7936a3 100644 --- a/zerotier-network-hypervisor/src/vl1/address.rs +++ b/zerotier-network-hypervisor/src/vl1/address.rs @@ -58,14 +58,14 @@ impl Address { #[inline(always)] pub(crate) fn marshal(&self, buf: &mut Buffer) -> std::io::Result<()> { - buf.append_and_init_bytes_fixed(|b: &mut [u8; ADDRESS_SIZE]| { - let i = self.0.get(); - b[0] = (i >> 32) as u8; - b[1] = (i >> 24) as u8; - b[2] = (i >> 16) as u8; - b[3] = (i >> 8) as u8; - b[4] = i as u8; - }) + let b = buf.append_bytes_fixed_get_mut::()?; + let i = self.0.get(); + (*b)[0] = (i >> 32) as u8; + (*b)[1] = (i >> 24) as u8; + (*b)[2] = (i >> 16) as u8; + (*b)[3] = (i >> 8) as u8; + (*b)[4] = i as u8; + Ok(()) } #[inline(always)] diff --git a/zerotier-network-hypervisor/src/vl1/endpoint.rs b/zerotier-network-hypervisor/src/vl1/endpoint.rs index da638c934..46b1ca4d3 100644 --- a/zerotier-network-hypervisor/src/vl1/endpoint.rs +++ b/zerotier-network-hypervisor/src/vl1/endpoint.rs @@ -142,10 +142,10 @@ impl Endpoint { if type_byte < 16 { if type_byte == 4 { let b: &[u8; 6] = buf.read_bytes_fixed(cursor)?; - Ok(Endpoint::IpUdp(InetAddress::from_ip_port(&b[0..4], crate::util::load_u16_be(&b[4..6])))) + Ok(Endpoint::IpUdp(InetAddress::from_ip_port(&b[0..4], u16::from_be_bytes(b[4..6].try_into().unwrap())))) } else if type_byte == 6 { let b: &[u8; 18] = buf.read_bytes_fixed(cursor)?; - Ok(Endpoint::IpUdp(InetAddress::from_ip_port(&b[0..16], crate::util::load_u16_be(&b[16..18])))) + Ok(Endpoint::IpUdp(InetAddress::from_ip_port(&b[0..16], u16::from_be_bytes(b[16..18].try_into().unwrap())))) } else { Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "unrecognized endpoint type in stream")) } diff --git a/zerotier-network-hypervisor/src/vl1/ephemeral.rs b/zerotier-network-hypervisor/src/vl1/ephemeral.rs index ee7a17684..9217802e9 100644 --- a/zerotier-network-hypervisor/src/vl1/ephemeral.rs +++ b/zerotier-network-hypervisor/src/vl1/ephemeral.rs @@ -36,8 +36,10 @@ impl EphemeralKeyPairSet { /// This contains key pairs for the asymmetric key agreement algorithms used and a /// timestamp used to enforce TTL. /// - /// SIDH is only used the first time and then 1/255 of remaining ratchet clicks because - /// it's slower than the others. + /// SIDH is only used once per ratchet sequence because it's much more CPU intensive + /// than ECDH. The threat model for SIDH is forward secrecy on the order of 5-15 years + /// from now when a quantum computer capable of attacking elliptic curve may exist, + /// it's incredibly unlikely that a p2p link would ever persist that long. pub fn new(local_address: Address, remote_address: Address, previous_ephemeral_secret: Option<&EphemeralSymmetricSecret>) -> Self { let (sidhp751, previous_ratchet_state) = previous_ephemeral_secret.map_or_else(|| { ( @@ -46,7 +48,7 @@ impl EphemeralKeyPairSet { ) }, |previous_ephemeral_secret| { ( - if previous_ephemeral_secret.ratchet_state[0] == 0 { Some(SIDHEphemeralKeyPair::generate(local_address, remote_address)) } else { None }, + None, Some(previous_ephemeral_secret.ratchet_state.clone()) ) }); diff --git a/zerotier-network-hypervisor/src/vl1/identity.rs b/zerotier-network-hypervisor/src/vl1/identity.rs index 9bbb059f6..8f19972de 100644 --- a/zerotier-network-hypervisor/src/vl1/identity.rs +++ b/zerotier-network-hypervisor/src/vl1/identity.rs @@ -6,561 +6,466 @@ * https://www.zerotier.com/ */ -use std::ptr::{slice_from_raw_parts, slice_from_raw_parts_mut}; -use zerotier_core_crypto::c25519::{C25519_PUBLIC_KEY_SIZE, C25519KeyPair, ED25519_PUBLIC_KEY_SIZE, Ed25519KeyPair}; -use zerotier_core_crypto::p521::{P521_ECDSA_SIGNATURE_SIZE, P521_PUBLIC_KEY_SIZE, P521KeyPair}; -use zerotier_core_crypto::salsa::Salsa; - -// Work function used to derive address from keys. -fn zt_frankenhash(digest: &mut [u8; 64], genmem_ptr: *mut u8) { - let (genmem, genmem_alias_hack) = unsafe { (&mut *slice_from_raw_parts_mut(genmem_ptr, V0_POW_MEMORY), &*slice_from_raw_parts(genmem_ptr, V0_POW_MEMORY)) }; - let genmem_u64_ptr = genmem_ptr.cast::(); - - let mut s20 = Salsa::new(&digest[0..32], &digest[32..40], false).unwrap(); - - s20.crypt(&crate::util::ZEROES[0..64], &mut genmem[0..64]); - let mut i: usize = 64; - while i < V0_POW_MEMORY { - let ii = i + 64; - s20.crypt(&genmem_alias_hack[(i - 64)..i], &mut genmem[i..ii]); - i = ii; - } - - i = 0; - while i < (V0_POW_MEMORY / 8) { - unsafe { - let idx1 = (((*genmem_u64_ptr.offset(i as isize)).to_be() % 8) * 8) as usize; - let idx2 = ((*genmem_u64_ptr.offset((i + 1) as isize)).to_be() % (V0_POW_MEMORY as u64 / 8)) as usize; - let genmem_u64_at_idx2_ptr = genmem_u64_ptr.offset(idx2 as isize); - let tmp = *genmem_u64_at_idx2_ptr; - let digest_u64_ptr = digest.as_mut_ptr().offset(idx1 as isize).cast::(); - *genmem_u64_at_idx2_ptr = *digest_u64_ptr; - *digest_u64_ptr = tmp; - } - s20.crypt_in_place(digest); - i += 2; - } -} - -struct P521Secret { - ecdh: P521KeyPair, - ecdsa: P521KeyPair, -} - -struct P521Public { - ecdh: [u8; P521_PUBLIC_KEY_SIZE], - ecdsa: [u8; P521_PUBLIC_KEY_SIZE], - self_signature: [u8; P521_ECDSA_SIGNATURE_SIZE] -} - -pub struct IdentitySecrets { - c25519: C25519KeyPair, - ed25519: Ed25519KeyPair, - p521: Option, -} - -pub struct Identity { - c25519: [u8; C25519_PUBLIC_KEY_SIZE], - ed25519: [u8; ED25519_PUBLIC_KEY_SIZE], - p521: Option, -} - -/* - use std::alloc::{alloc, dealloc, Layout}; use std::cmp::Ordering; use std::convert::TryInto; -use std::hash::{Hash, Hasher}; use std::io::Write; +use std::mem::MaybeUninit; use std::ptr::{slice_from_raw_parts, slice_from_raw_parts_mut}; use std::str::FromStr; -use zerotier_core_crypto::balloon; -use zerotier_core_crypto::c25519::*; -use zerotier_core_crypto::hash::*; -use zerotier_core_crypto::p521::*; +use lazy_static::lazy_static; + +use zerotier_core_crypto::c25519::{C25519_PUBLIC_KEY_SIZE, C25519_SECRET_KEY_SIZE, C25519KeyPair, ED25519_PUBLIC_KEY_SIZE, ED25519_SECRET_KEY_SIZE, ED25519_SIGNATURE_SIZE, ed25519_verify, Ed25519KeyPair}; +use zerotier_core_crypto::hash::{SHA384, SHA384_HASH_SIZE, SHA512}; +use zerotier_core_crypto::hex; +use zerotier_core_crypto::p521::{P521_ECDSA_SIGNATURE_SIZE, P521_PUBLIC_KEY_SIZE, P521_SECRET_KEY_SIZE, P521KeyPair, P521PublicKey}; use zerotier_core_crypto::salsa::Salsa; use zerotier_core_crypto::secret::Secret; use crate::error::InvalidFormatError; -use crate::vl1::Address; -use crate::vl1::protocol::{PACKET_SIZE_MAX, ADDRESS_SIZE}; +use crate::util::array_range; use crate::util::buffer::Buffer; +use crate::util::pool::{Pool, Pooled, PoolFactory}; +use crate::vl1::Address; +use crate::vl1::protocol::{ADDRESS_SIZE, ADDRESS_SIZE_STRING, IDENTITY_V0_POW_THRESHOLD, IDENTITY_V1_POW_THRESHOLD}; -pub const IDENTITY_TYPE_0_SIGNATURE_SIZE: usize = 96; -pub const IDENTITY_TYPE_1_SIGNATURE_SIZE: usize = P521_ECDSA_SIGNATURE_SIZE + ED25519_SIGNATURE_SIZE; +/// X25519 cipher suite (present in all identities, so no actual flag). +pub const IDENTITY_CIPHER_SUITE_X25519: u8 = 0x00; -const V0_POW_MEMORY: usize = 2097152; -const V0_POW_THRESHOLD: u8 = 17; -const V1_POW_THRESHOLD: u8 = 5; -const V1_BALLOON_SPACE_COST: usize = 262144; -const V1_BALLOON_TIME_COST: usize = 3; -const V1_BALLOON_DELTA: usize = 3; +/// NIST P-521 ECDH/ECDSA cipher suite. +/// +/// Sooo.... why 0x03 and not 0x01 or some other value? It's to compensate at the cost of +/// one wasted bit for a short-sighted aspect of the old identity encoding and HELLO packet +/// encoding. +/// +/// The old identity encoding contains no provision for skipping data it doesn't understand +/// nor any provision for an upgrade. That's dumb, but there it is on millions of nodes. The +/// place where this matters in terms of identities the most is the HELLO packet. +/// +/// In HELLO the identity is sent immediately followed by an endpoint, namely the InetAddress to +/// which the packet was sent. The old InetAddress DOES have a provision for extension. The type +/// byte 0x04 is followed by a 16-bit size for an "unknown address type" so it can be skipped if +/// it is not understood. +/// +/// If we preface the x25519 key with 0x00 as normal and then preface the next key with 0x04, +/// we can follow that by what should have been there in the first place: a size for the remaining +/// identity fields. +/// +/// When old nodes parse a HELLO they will interpret this as an x25519 identity followed by an +/// unrecognized InetAddress type that will be silently ignored. +/// +/// The one wasted bit (0x02) is in a field with only one bit in use anyway. It means we can only +/// add six more cipher suites in the future, and if we're adding that many something is wrong. +/// There will probably only be one more ever, a post-quantum long term key. +pub const IDENTITY_CIPHER_SUITE_EC_NIST_P521: u8 = 0x03; -const V1_PUBLIC_KEYS_SIZE: usize = C25519_PUBLIC_KEY_SIZE + ED25519_PUBLIC_KEY_SIZE + P521_PUBLIC_KEY_SIZE + P521_PUBLIC_KEY_SIZE; -const V1_PUBLIC_KEYS_SIGNATURE_AND_POW_SIZE: usize = V1_PUBLIC_KEYS_SIZE + P521_ECDSA_SIGNATURE_SIZE + SHA384_HASH_SIZE; -const V1_SECRET_KEYS_SIZE: usize = C25519_SECRET_KEY_SIZE + ED25519_SECRET_KEY_SIZE + P521_SECRET_KEY_SIZE + P521_SECRET_KEY_SIZE; +/// Mask for functions that take a cipher mask to use all available ciphers or the best available cipher. +pub const IDENTITY_CIPHER_SUITE_INCLUDE_ALL: u8 = 0xff; -fn concat_v1_public_keys(c25519: &[u8], ed25519: &[u8], p521_ecdh: &[u8], p521_ecdsa: &[u8]) -> [u8; V1_PUBLIC_KEYS_SIZE] { - let mut k = [0_u8; V1_PUBLIC_KEYS_SIZE]; - debug_assert!(c25519.len() <= C25519_PUBLIC_KEY_SIZE); - debug_assert!(ed25519.len() <= ED25519_PUBLIC_KEY_SIZE); - debug_assert!(p521_ecdh.len() <= P521_PUBLIC_KEY_SIZE); - debug_assert!(p521_ecdsa.len() <= P521_PUBLIC_KEY_SIZE); - k[(C25519_PUBLIC_KEY_SIZE - c25519.len())..C25519_PUBLIC_KEY_SIZE].copy_from_slice(c25519); - k[(C25519_PUBLIC_KEY_SIZE + (ED25519_PUBLIC_KEY_SIZE - ed25519.len()))..(C25519_PUBLIC_KEY_SIZE + ED25519_PUBLIC_KEY_SIZE)].copy_from_slice(ed25519); - k[(C25519_PUBLIC_KEY_SIZE + ED25519_PUBLIC_KEY_SIZE + (P521_PUBLIC_KEY_SIZE - p521_ecdh.len()))..(C25519_PUBLIC_KEY_SIZE + ED25519_PUBLIC_KEY_SIZE + P521_PUBLIC_KEY_SIZE)].copy_from_slice(p521_ecdh); - k[(C25519_PUBLIC_KEY_SIZE + ED25519_PUBLIC_KEY_SIZE + P521_PUBLIC_KEY_SIZE + (P521_PUBLIC_KEY_SIZE - p521_ecdsa.len()))..(C25519_PUBLIC_KEY_SIZE + ED25519_PUBLIC_KEY_SIZE + P521_PUBLIC_KEY_SIZE + P521_PUBLIC_KEY_SIZE)].copy_from_slice(p521_ecdsa); - k +#[derive(Clone)] +pub struct IdentityP521Secret { + pub ecdh: P521KeyPair, + pub ecdsa: P521KeyPair, } -fn concat_v1_public_all(c25519: &[u8], ed25519: &[u8], p521_ecdh: &[u8], p521_ecdsa: &[u8], signature: &[u8], balloon_hash_digest: &[u8]) -> [u8; V1_PUBLIC_KEYS_SIGNATURE_AND_POW_SIZE] { - let mut k = [0_u8; V1_PUBLIC_KEYS_SIGNATURE_AND_POW_SIZE]; - debug_assert!(c25519.len() <= C25519_PUBLIC_KEY_SIZE); - debug_assert!(ed25519.len() <= ED25519_PUBLIC_KEY_SIZE); - debug_assert!(p521_ecdh.len() <= P521_PUBLIC_KEY_SIZE); - debug_assert!(p521_ecdsa.len() <= P521_PUBLIC_KEY_SIZE); - debug_assert!(signature.len() <= P521_ECDSA_SIGNATURE_SIZE); - debug_assert_eq!(balloon_hash_digest.len(), SHA384_HASH_SIZE); - k[(C25519_PUBLIC_KEY_SIZE - c25519.len())..C25519_PUBLIC_KEY_SIZE].copy_from_slice(c25519); - k[(C25519_PUBLIC_KEY_SIZE + (ED25519_PUBLIC_KEY_SIZE - ed25519.len()))..(C25519_PUBLIC_KEY_SIZE + ED25519_PUBLIC_KEY_SIZE)].copy_from_slice(ed25519); - k[(C25519_PUBLIC_KEY_SIZE + ED25519_PUBLIC_KEY_SIZE + (P521_PUBLIC_KEY_SIZE - p521_ecdh.len()))..(C25519_PUBLIC_KEY_SIZE + ED25519_PUBLIC_KEY_SIZE + P521_PUBLIC_KEY_SIZE)].copy_from_slice(p521_ecdh); - k[(C25519_PUBLIC_KEY_SIZE + ED25519_PUBLIC_KEY_SIZE + P521_PUBLIC_KEY_SIZE + (P521_PUBLIC_KEY_SIZE - p521_ecdsa.len()))..(C25519_PUBLIC_KEY_SIZE + ED25519_PUBLIC_KEY_SIZE + P521_PUBLIC_KEY_SIZE + P521_PUBLIC_KEY_SIZE)].copy_from_slice(p521_ecdsa); - k[(C25519_PUBLIC_KEY_SIZE + ED25519_PUBLIC_KEY_SIZE + P521_PUBLIC_KEY_SIZE + P521_PUBLIC_KEY_SIZE + (P521_ECDSA_SIGNATURE_SIZE - signature.len()))..(C25519_PUBLIC_KEY_SIZE + ED25519_PUBLIC_KEY_SIZE + P521_PUBLIC_KEY_SIZE + P521_PUBLIC_KEY_SIZE + P521_ECDSA_SIGNATURE_SIZE)].copy_from_slice(signature); - k[(C25519_PUBLIC_KEY_SIZE + ED25519_PUBLIC_KEY_SIZE + P521_PUBLIC_KEY_SIZE + P521_PUBLIC_KEY_SIZE + P521_ECDSA_SIGNATURE_SIZE)..].copy_from_slice(balloon_hash_digest); - k +#[derive(Clone)] +pub struct IdentityP521Public { + pub ecdh: [u8; P521_PUBLIC_KEY_SIZE], + pub ecdsa: [u8; P521_PUBLIC_KEY_SIZE], + pub ecdsa_self_signature: [u8; P521_ECDSA_SIGNATURE_SIZE], + pub ed25519_self_signature: [u8; ED25519_SIGNATURE_SIZE], } -fn concat_v1_secret_keys(c25519: &[u8], ed25519: &[u8], p521_ecdh: &[u8], p521_ecdsa: &[u8]) -> [u8; V1_SECRET_KEYS_SIZE] { - let mut k = [0_u8; V1_SECRET_KEYS_SIZE]; - debug_assert!(c25519.len() <= C25519_SECRET_KEY_SIZE); - debug_assert!(ed25519.len() <= ED25519_SECRET_KEY_SIZE); - debug_assert!(p521_ecdh.len() <= P521_SECRET_KEY_SIZE); - debug_assert!(p521_ecdsa.len() <= P521_SECRET_KEY_SIZE); - k[(C25519_SECRET_KEY_SIZE - c25519.len())..C25519_SECRET_KEY_SIZE].copy_from_slice(c25519); - k[(C25519_SECRET_KEY_SIZE + (ED25519_SECRET_KEY_SIZE - ed25519.len()))..(C25519_SECRET_KEY_SIZE + ED25519_SECRET_KEY_SIZE)].copy_from_slice(ed25519); - k[(C25519_SECRET_KEY_SIZE + ED25519_SECRET_KEY_SIZE + (P521_SECRET_KEY_SIZE - p521_ecdh.len()))..(C25519_SECRET_KEY_SIZE + ED25519_SECRET_KEY_SIZE + P521_SECRET_KEY_SIZE)].copy_from_slice(p521_ecdh); - k[(C25519_SECRET_KEY_SIZE + ED25519_SECRET_KEY_SIZE + P521_SECRET_KEY_SIZE + (P521_SECRET_KEY_SIZE - p521_ecdsa.len()))..(C25519_SECRET_KEY_SIZE + ED25519_SECRET_KEY_SIZE + P521_SECRET_KEY_SIZE + P521_SECRET_KEY_SIZE)].copy_from_slice(p521_ecdsa); - k -} - -#[derive(Copy, Clone)] -#[repr(u8)] -pub enum IdentityType { - /// Curve25519 / Ed25519 identity (type 0) - C25519 = 0, - /// Dual NIST P-521 ECDH / ECDSA + Curve25519 / Ed25519 (type 1) - P521 = 1, -} - -struct IdentitySecrets { - c25519: C25519KeyPair, - ed25519: Ed25519KeyPair, - v1: Option<(P521KeyPair, P521KeyPair)>, // ecdh key, ecdsa key +#[derive(Clone)] +pub struct IdentitySecret { + pub c25519: C25519KeyPair, + pub ed25519: Ed25519KeyPair, + pub p521: Option, } +#[derive(Clone)] pub struct Identity { - address: Address, - c25519: [u8; C25519_PUBLIC_KEY_SIZE], - ed25519: [u8; ED25519_PUBLIC_KEY_SIZE], - v1: Option<(P521PublicKey, P521PublicKey, [u8; P521_ECDSA_SIGNATURE_SIZE], [u8; SHA384_HASH_SIZE])>, - secrets: Option, + pub address: Address, + pub c25519: [u8; C25519_PUBLIC_KEY_SIZE], + pub ed25519: [u8; ED25519_PUBLIC_KEY_SIZE], + pub p521: Option, + pub secret: Option, } -/// Compute result from the bespoke "frankenhash" from the old V0 work function. -/// The supplied genmem_ptr must be of size V0_IDENTITY_GEN_MEMORY and aligned to an 8-byte boundary. -fn v0_frankenhash(digest: &mut [u8; 64], genmem_ptr: *mut u8) { - let (genmem, genmem_alias_hack) = unsafe { (&mut *slice_from_raw_parts_mut(genmem_ptr, V0_POW_MEMORY), &*slice_from_raw_parts(genmem_ptr, V0_POW_MEMORY)) }; - let genmem_u64_ptr = genmem_ptr.cast::(); +#[inline(always)] +fn concat_arrays_2(a: &[u8; A], b: &[u8; B]) -> [u8; S] { + debug_assert_eq!(A + B, S); + let mut tmp: [u8; S] = unsafe { MaybeUninit::uninit().assume_init() }; + tmp[..A].copy_from_slice(a); + tmp[A..].copy_from_slice(b); + tmp +} - let mut s20 = Salsa::new(&digest[0..32], &digest[32..40], false).unwrap(); - - s20.crypt(&crate::util::ZEROES[0..64], &mut genmem[0..64]); - let mut i: usize = 64; - while i < V0_POW_MEMORY { - let ii = i + 64; - s20.crypt(&genmem_alias_hack[(i - 64)..i], &mut genmem[i..ii]); - i = ii; - } - - i = 0; - while i < (V0_POW_MEMORY / 8) { - unsafe { - let idx1 = (((*genmem_u64_ptr.offset(i as isize)).to_be() % 8) * 8) as usize; - let idx2 = ((*genmem_u64_ptr.offset((i + 1) as isize)).to_be() % (V0_POW_MEMORY as u64 / 8)) as usize; - let genmem_u64_at_idx2_ptr = genmem_u64_ptr.offset(idx2 as isize); - let tmp = *genmem_u64_at_idx2_ptr; - let digest_u64_ptr = digest.as_mut_ptr().offset(idx1 as isize).cast::(); - *genmem_u64_at_idx2_ptr = *digest_u64_ptr; - *digest_u64_ptr = tmp; - } - s20.crypt_in_place(digest); - i += 2; - } +#[inline(always)] +fn concat_arrays_4(a: &[u8; A], b: &[u8; B], c: &[u8; C], d: &[u8; D]) -> [u8; S] { + debug_assert_eq!(A + B + C + D, S); + let mut tmp: [u8; S] = unsafe { MaybeUninit::uninit().assume_init() }; + tmp[..A].copy_from_slice(a); + tmp[A..(A + B)].copy_from_slice(b); + tmp[(A + B)..(A + B + C)].copy_from_slice(c); + tmp[(A + B + C)..].copy_from_slice(d); + tmp } impl Identity { - fn generate_c25519() -> Identity { - let genmem_layout = Layout::from_size_align(V0_POW_MEMORY, 8).unwrap(); - let genmem_ptr = unsafe { alloc(genmem_layout) }; - if genmem_ptr.is_null() { - panic!("unable to allocate memory for V0 identity generation"); - } - - let ed25519 = Ed25519KeyPair::generate(false); - let ed25519_pub_bytes = ed25519.public_bytes(); + /// Generate a new identity. + pub fn generate() -> Self { let mut sha = SHA512::new(); + let ed25519 = Ed25519KeyPair::generate(false); + let ed25519_pub = ed25519.public_bytes(); + let mut address; + let mut c25519; + let mut c25519_pub; + let mut genmem_pool_obj = unsafe { FRANKENHASH_POW_MEMORY_POOL.get() }; loop { - let c25519 = C25519KeyPair::generate(false); - let c25519_pub_bytes = c25519.public_bytes(); + c25519 = C25519KeyPair::generate(false); + c25519_pub = c25519.public_bytes(); - sha.update(&c25519_pub_bytes); - sha.update(&ed25519_pub_bytes); + sha.update(&c25519_pub); + sha.update(&ed25519_pub); let mut digest = sha.finish(); + zt_frankenhash(&mut digest, &mut genmem_pool_obj); - v0_frankenhash(&mut digest, genmem_ptr); - if digest[0] < V0_POW_THRESHOLD { + if digest[0] < IDENTITY_V1_POW_THRESHOLD { let addr = Address::from_bytes(&digest[59..64]); if addr.is_some() { - unsafe { dealloc(genmem_ptr, genmem_layout) }; - return Identity { - address: addr.unwrap(), - c25519: c25519_pub_bytes, - ed25519: ed25519_pub_bytes, - v1: None, - secrets: Some(IdentitySecrets { - c25519, - ed25519, - v1: None, - }), - }; + address = addr.unwrap(); + break; } } sha.reset(); } - } + drop(genmem_pool_obj); - fn generate_p521() -> Identity { - let c25519 = C25519KeyPair::generate(false); - let ed25519 = Ed25519KeyPair::generate(false); let p521_ecdh = P521KeyPair::generate(false).unwrap(); let p521_ecdsa = P521KeyPair::generate(false).unwrap(); - let c25519_pub_bytes = c25519.public_bytes(); - let ed25519_pub_bytes = ed25519.public_bytes(); - let signing_buf = concat_v1_public_keys(&c25519_pub_bytes, &ed25519_pub_bytes, p521_ecdh.public_key_bytes(), p521_ecdsa.public_key_bytes()); - loop { - // ECDSA is a randomized signature algorithm, so each signature will be different. - let sig = p521_ecdsa.sign(&signing_buf).unwrap(); - let bh = balloon::zt_variant_hash::<{ V1_BALLOON_SPACE_COST }, { V1_BALLOON_TIME_COST }, { V1_BALLOON_DELTA }>(&sig); - if bh[0] < V1_POW_THRESHOLD { - let addr = Address::from_bytes(&bh[43..48]); - if addr.is_some() { - let p521_ecdh_pub = p521_ecdh.public_key().clone(); - let p521_ecdsa_pub = p521_ecdsa.public_key().clone(); - return Identity { - address: addr.unwrap(), - c25519: c25519_pub_bytes, - ed25519: ed25519_pub_bytes, - v1: Some((p521_ecdh_pub, p521_ecdsa_pub, sig, bh)), - secrets: Some(IdentitySecrets { - c25519, - ed25519, - v1: Some((p521_ecdh, p521_ecdsa)), - }), - }; - } - } + let p521_ecdh_pub = p521_ecdh.public_key_bytes().clone(); + let p521_ecdsa_pub = p521_ecdsa.public_key_bytes().clone(); + + let mut self_sign_buf: Vec = Vec::with_capacity(ADDRESS_SIZE + 1 + C25519_PUBLIC_KEY_SIZE + ED25519_PUBLIC_KEY_SIZE + 1 + P521_PUBLIC_KEY_SIZE + P521_PUBLIC_KEY_SIZE); + let _ = self_sign_buf.write_all(&address.to_bytes()); + self_sign_buf.push(IDENTITY_CIPHER_SUITE_X25519 | IDENTITY_CIPHER_SUITE_EC_NIST_P521); + let _ = self_sign_buf.write_all(&c25519_pub); + let _ = self_sign_buf.write_all(&ed25519_pub); + let _ = self_sign_buf.write_all(&p521_ecdh_pub); + let _ = self_sign_buf.write_all(&p521_ecdsa_pub); + + Self { + address, + c25519: c25519_pub, + ed25519: ed25519_pub, + p521: Some(IdentityP521Public { + ecdh: p521_ecdh_pub, + ecdsa: p521_ecdsa_pub, + ecdsa_self_signature: p521_ecdsa.sign(self_sign_buf.as_slice()).expect("NIST P-521 signature failed in identity generation"), + ed25519_self_signature: ed25519.sign(self_sign_buf.as_slice()) + }), + secret: Some(IdentitySecret { + c25519, + ed25519, + p521: Some(IdentityP521Secret { + ecdh: p521_ecdh, + ecdsa: p521_ecdsa, + }), + }), } } - /// Generate a new identity. - /// - /// This is time consuming due to the one-time anti-collision proof of work required - /// to generate an address corresponding with a set of identity keys. V0 identities - /// take tens to hundreds of milliseconds on a typical 2020 system, while V1 identites - /// take about 500ms. Generation can take a lot longer on low power devices, but only - /// has to be done once. - pub fn generate(id_type: IdentityType) -> Identity { - match id_type { - IdentityType::C25519 => Self::generate_c25519(), - IdentityType::P521 => Self::generate_p521() - } - } - - /// Get this identity's 40-bit address. + /// Get a bit mask of this identity's available cipher suites. #[inline(always)] - pub fn address(&self) -> Address { self.address } - - /// Locally validate this identity. - /// - /// This can take a few milliseconds, especially on slower systems. V0 identities are slower - /// to fully validate than V1 identities. - pub fn locally_validate(&self) -> bool { - if self.v1.is_none() { - let genmem_layout = Layout::from_size_align(V0_POW_MEMORY, 8).unwrap(); - let genmem_ptr = unsafe { alloc(genmem_layout) }; - if !genmem_ptr.is_null() { - let mut sha = SHA512::new(); - sha.update(&self.c25519); - sha.update(&self.ed25519); - let mut digest = sha.finish(); - v0_frankenhash(&mut digest, genmem_ptr); - unsafe { dealloc(genmem_ptr, genmem_layout) }; - (digest[0] < V0_POW_THRESHOLD) && Address::from_bytes(&digest[59..64]).unwrap().eq(&self.address) - } else { - false - } + pub fn cipher_suites(&self) -> u8 { + if self.p521.is_some() { + IDENTITY_CIPHER_SUITE_X25519 | IDENTITY_CIPHER_SUITE_EC_NIST_P521 } else { - let p521 = self.v1.as_ref().unwrap(); - let signing_buf = concat_v1_public_keys(&self.c25519, &self.ed25519, (*p521).0.public_key_bytes(), (*p521).1.public_key_bytes()); - if (*p521).1.verify(&signing_buf, &(*p521).2) { - let bh = balloon::zt_variant_hash::<{ V1_BALLOON_SPACE_COST }, { V1_BALLOON_TIME_COST }, { V1_BALLOON_DELTA }>(&(*p521).2); - (bh[0] < V1_POW_THRESHOLD) && bh.eq(&(*p521).3) && Address::from_bytes(&bh[43..48]).unwrap().eq(&self.address) - } else { - false - } + IDENTITY_CIPHER_SUITE_X25519 } } - /// Execute ECDH key agreement and return SHA384(shared secret). + /// Get a SHA384 hash of this identity's address and public keys. + /// This provides a globally unique 384-bit fingerprint of this identity. + pub fn hash(&self) -> [u8; SHA384_HASH_SIZE] { + let mut sha = SHA384::new(); + sha.update(&self.address.to_bytes()); + // don't prefix x25519 with cipher suite 0 for backward compatibility + sha.update(&self.c25519); + sha.update(&self.ed25519); + let _ = self.p521.as_ref().map(|p521| { + sha.update(&[IDENTITY_CIPHER_SUITE_EC_NIST_P521]); + sha.update(&p521.ecdh); + sha.update(&p521.ecdsa); + sha.update(&p521.ecdsa_self_signature); + }); + sha.finish() + } + + /// Locally check the validity of this identity. + /// This is somewhat time consuming. + pub fn validate_identity(&self) -> bool { + let pow_threshold = if self.p521.is_some() { + let p521 = self.p521.as_ref().unwrap(); + let mut self_sign_buf: Vec = Vec::with_capacity(ADDRESS_SIZE + 1 + C25519_PUBLIC_KEY_SIZE + ED25519_PUBLIC_KEY_SIZE + P521_PUBLIC_KEY_SIZE + P521_PUBLIC_KEY_SIZE); + let _ = self_sign_buf.write_all(&self.address.to_bytes()); + self_sign_buf.push(IDENTITY_CIPHER_SUITE_X25519 | IDENTITY_CIPHER_SUITE_EC_NIST_P521); + let _ = self_sign_buf.write_all(&self.c25519); + let _ = self_sign_buf.write_all(&self.ed25519); + let _ = self_sign_buf.write_all(&p521.ecdh); + let _ = self_sign_buf.write_all(&p521.ecdsa); + + if !P521PublicKey::from_bytes(&p521.ecdsa).map_or(false, |ecdsa_pub| ecdsa_pub.verify(self_sign_buf.as_slice(), &p521.ecdsa_self_signature)) { + return false; + } + if !ed25519_verify(&self.ed25519, &p521.ed25519_self_signature, self_sign_buf.as_slice()) { + return false; + } + + IDENTITY_V1_POW_THRESHOLD + } else { + IDENTITY_V0_POW_THRESHOLD + }; + + let mut sha = SHA512::new(); + sha.update(&self.c25519); + sha.update(&self.ed25519); + let mut digest = sha.finish(); + let mut genmem_pool_obj = unsafe { FRANKENHASH_POW_MEMORY_POOL.get() }; + zt_frankenhash(&mut digest, &mut genmem_pool_obj); + drop(genmem_pool_obj); + + return digest[0] < pow_threshold && Address::from_bytes(&digest[59..64]).map_or(false, |a| a == self.address); + } + + /// Perform ECDH key agreement, returning a shared secret or None on error. /// - /// If both keys are type 1, key agreement is done with NIST P-521. Otherwise it's done - /// with Curve25519. None is returned if there is an error such as this identity missing - /// its secrets or a key being invalid. - pub fn agree(&self, other_identity: &Identity) -> Option> { - self.secrets.as_ref().map_or(None, |secrets| { - let c25519_secret = || Secret::<48>(SHA384::hash(&secrets.c25519.agree(&other_identity.c25519).as_ref())); - secrets.v1.as_ref().map_or_else(|| Some(c25519_secret()), |p521_secret| { - other_identity.v1.as_ref().map_or_else(|| Some(c25519_secret()), |other_p521_public| { - p521_secret.0.agree(&other_p521_public.0).map_or(None, |p521_secret| { - // - // For NIST P-521 key agreement, we use a single step key derivation function to derive - // the final shared secret using the C25519 shared secret as a "salt." This should be - // FIPS-compliant as per section 8.2 of: - // - // https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-56Cr2.pdf - // - // This is also stated in the following FAQ, which though it pertains to post-quantum - // algorithms states that non-FIPS derived shared secrets can be used as salts in key - // derivation from a FIPS-compliant algorithm derived shared secret: - // - // https://csrc.nist.gov/Projects/post-quantum-cryptography/faqs - // - // Hashing the C25519 secret with the P521 secret results in a secret that is as strong - // as the stronger of the two algorithms. This should make the FIPS people happy and the - // people who are paranoid about NIST curves happy. - // - Some(Secret(SHA384::hmac(c25519_secret().as_ref(), p521_secret.as_ref()))) - }) - }) - }) + /// An error can occur if this identity does not hold its secret portion or if either key is invalid. + /// + /// If both sides have NIST P-521 keys then key agreement is performed using both Curve25519 and + /// NIST P-521 and the result is HMAC(Curve25519 secret, NIST P-521 secret). + pub fn agree(&self, other: &Identity) -> Option> { + self.secret.as_ref().and_then(|secret| { + let c25519_secret = Secret(SHA512::hash(&secret.c25519.agree(&other.c25519).0)); + // FIPS note: FIPS-compliant exchange algorithms must be the last algorithms in any HKDF chain + // for the final result to be technically FIPS compliant. Non-FIPS algorithm secrets are considered + // a salt in the HMAC(salt, key) HKDF construction. + if secret.p521.is_some() && other.p521.is_some() { + P521PublicKey::from_bytes(&other.p521.as_ref().unwrap().ecdh).and_then(|other_p521| secret.p521.as_ref().unwrap().ecdh.agree(&other_p521).map(|p521_secret| Secret(SHA384::hmac(&c25519_secret.0[0..48], &p521_secret.0)))) + } else { + Some(Secret(array_range::(&c25519_secret.0).clone())) + } }) } - /// Sign this message with this identity. - pub fn sign(&self, msg: &[u8]) -> Option> { - self.secrets.as_ref().map_or(None, |secrets| { - let c25519_sig = secrets.ed25519.sign_zt(msg); - secrets.v1.as_ref().map_or_else(|| Some(c25519_sig.to_vec()), |p521_secret| p521_secret.1.sign(msg).map_or(None, |p521_sig| { - // - // For type 1 identity signatures we sign with both algorithms and append the Ed25519 - // signature to the NIST P-521 signature. The Ed25519 signature is only checked if the - // P-521 signature validates. Note that we only append the first 64 bytes of sign_zt() - // output. For legacy reasons type 0 signatures include the first 32 bytes of the message - // hash after the signature, but this is not required and isn't included here. - // - // This should once again make both the FIPS people and the people paranoid about NIST - // curves happy. - // - let mut p521_sig = p521_sig.to_vec(); - let _ = p521_sig.write_all(&c25519_sig[0..64]); - Some(p521_sig) - })) + /// Sign a message with this identity. + /// A return of None happens if we don't have our secret key(s) or some other error occurs. + pub fn sign(&self, msg: &[u8], use_cipher_suites: u8) -> Option> { + self.secret.as_ref().and_then(|secret| { + if (use_cipher_suites & IDENTITY_CIPHER_SUITE_EC_NIST_P521) != 0 && secret.p521.is_some() { + secret.p521.as_ref().unwrap().ecdsa.sign(msg).map(|sig| sig.to_vec()) + } else { + Some(secret.ed25519.sign_zt(msg).to_vec()) + } }) } - /// Verify a signature. + /// Verify a signature against this identity. pub fn verify(&self, msg: &[u8], signature: &[u8]) -> bool { - self.v1.as_ref().map_or_else(|| { - zerotier_core_crypto::c25519::ed25519_verify(&self.ed25519, signature, msg) - }, |p521| { - signature.len() == IDENTITY_TYPE_1_SIGNATURE_SIZE && (*p521).1.verify(msg, &signature[0..P521_ECDSA_SIGNATURE_SIZE]) && zerotier_core_crypto::c25519::ed25519_verify(&self.ed25519, &signature[P521_ECDSA_SIGNATURE_SIZE..], msg) - }) + if signature.len() == 64 || signature.len() == 96 { + ed25519_verify(&self.ed25519, signature, msg) + } else if signature.len() == P521_ECDSA_SIGNATURE_SIZE && self.p521.is_some() { + P521PublicKey::from_bytes(&self.p521.as_ref().unwrap().ecdsa).map_or(false, |p521_public| p521_public.verify(msg, signature)) + } else { + false + } } - /// Get this identity's type. - #[inline(always)] - pub fn id_type(&self) -> IdentityType { if self.v1.is_some() { IdentityType::P521 } else { IdentityType::C25519 } } + pub fn marshal(&self, buf: &mut Buffer, include_cipher_suites: u8, include_private: bool) -> std::io::Result<()> { + let cipher_suites = self.cipher_suites() & include_cipher_suites; - /// Returns true if this identity also holds its secret keys. - #[inline(always)] - pub fn has_secrets(&self) -> bool { self.secrets.is_some() } - - /// Erase secrets from this identity object, if present. - pub fn forget_secrets(&mut self) { let _ = self.secrets.take(); } - - /// Append this in binary format to a buffer. - pub fn marshal(&self, buf: &mut Buffer, include_private: bool) -> std::io::Result<()> { buf.append_bytes_fixed(&self.address.to_bytes())?; - if self.v1.is_some() { - let p521 = self.v1.as_ref().unwrap(); - buf.append_u8(1)?; // type 1 - buf.append_bytes_fixed(&self.c25519)?; - buf.append_bytes_fixed(&self.ed25519)?; - buf.append_bytes_fixed((*p521).0.public_key_bytes())?; - buf.append_bytes_fixed((*p521).1.public_key_bytes())?; - buf.append_bytes_fixed(&(*p521).2)?; - buf.append_bytes_fixed(&(*p521).3)?; - if include_private && self.secrets.is_some() { - let secrets = self.secrets.as_ref().unwrap(); - if secrets.v1.is_some() { - let p521_secrets = secrets.v1.as_ref().unwrap(); - buf.append_u8(V1_SECRET_KEYS_SIZE as u8)?; - buf.append_bytes_fixed(&secrets.c25519.secret_bytes().as_ref())?; - buf.append_bytes_fixed(&secrets.ed25519.secret_bytes().as_ref())?; - buf.append_bytes_fixed((*p521_secrets).0.secret_key_bytes().as_ref())?; - buf.append_bytes_fixed((*p521_secrets).1.secret_key_bytes().as_ref())?; - } - } else { - buf.append_u8(0)?; // 0 secret bytes if not adding any - } + buf.append_u8(IDENTITY_CIPHER_SUITE_X25519)?; + buf.append_bytes_fixed(&self.c25519)?; + buf.append_bytes_fixed(&self.ed25519)?; + if include_private && self.secret.is_some() { + let secret = self.secret.as_ref().unwrap(); + buf.append_u8((C25519_SECRET_KEY_SIZE + ED25519_SECRET_KEY_SIZE) as u8)?; + buf.append_bytes_fixed(&secret.c25519.secret_bytes().0)?; + buf.append_bytes_fixed(&secret.ed25519.secret_bytes().0)?; } else { - buf.append_u8(0)?; // type 0 - buf.append_bytes_fixed(&self.c25519)?; - buf.append_bytes_fixed(&self.ed25519)?; - if include_private && self.secrets.is_some() { - let secrets = self.secrets.as_ref().unwrap(); - buf.append_u8((C25519_SECRET_KEY_SIZE + ED25519_SECRET_KEY_SIZE) as u8)?; - buf.append_bytes_fixed(&secrets.c25519.secret_bytes().as_ref())?; - buf.append_bytes_fixed(&secrets.ed25519.secret_bytes().as_ref())?; + buf.append_u8(0)?; + } + + if (cipher_suites & IDENTITY_CIPHER_SUITE_EC_NIST_P521) == IDENTITY_CIPHER_SUITE_EC_NIST_P521 && self.p521.is_some() { + let p521 = self.p521.as_ref().unwrap(); + let size = if include_private && self.secret.map_or(false, |s| s.p521.is_some()) { + (P521_PUBLIC_KEY_SIZE + P521_PUBLIC_KEY_SIZE + P521_ECDSA_SIGNATURE_SIZE + ED25519_SIGNATURE_SIZE + P521_SECRET_KEY_SIZE + P521_SECRET_KEY_SIZE) as u16 } else { - buf.append_u8(0)?; // 0 secret bytes if not adding any + (P521_PUBLIC_KEY_SIZE + P521_PUBLIC_KEY_SIZE + P521_ECDSA_SIGNATURE_SIZE + ED25519_SIGNATURE_SIZE) as u16 + }; + buf.append_u8(IDENTITY_CIPHER_SUITE_EC_NIST_P521)?; + buf.append_u16(size)?; + buf.append_bytes_fixed(&p521.ecdh)?; + buf.append_bytes_fixed(&p521.ecdsa)?; + buf.append_bytes_fixed(&p521.ecdsa_self_signature)?; + buf.append_bytes_fixed(&p521.ed25519_self_signature)?; + if size > (P521_PUBLIC_KEY_SIZE + P521_PUBLIC_KEY_SIZE + P521_ECDSA_SIGNATURE_SIZE + ED25519_SIGNATURE_SIZE) as u16 { + let p521s = self.secret.as_ref().unwrap().p521.as_ref().unwrap(); + buf.append_bytes_fixed(&p521s.ecdh.secret_key_bytes().0)?; + buf.append_bytes_fixed(&p521s.ecdsa.secret_key_bytes().0)?; } } + Ok(()) } - /// Deserialize an Identity from a buffer. - /// The supplied cursor is advanced. pub fn unmarshal(buf: &Buffer, cursor: &mut usize) -> std::io::Result { - let addr = Address::from_bytes(buf.read_bytes_fixed::<{ ADDRESS_SIZE }>(cursor)?); - if addr.is_none() { + let address = Address::from_bytes(buf.read_bytes_fixed::(cursor)?); + if !address.is_some() { return std::io::Result::Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "invalid address")); } - let addr = addr.unwrap(); + let address = address.unwrap(); - let id_type = buf.read_u8(cursor)?; - if id_type == IdentityType::C25519 as u8 { + let mut x25519_public: Option<([u8; C25519_PUBLIC_KEY_SIZE], [u8; ED25519_PUBLIC_KEY_SIZE])> = None; + let mut x25519_secret: Option<([u8; C25519_SECRET_KEY_SIZE], [u8; ED25519_SECRET_KEY_SIZE])> = None; + let mut p521_ecdh_ecdsa_public: Option<([u8; P521_PUBLIC_KEY_SIZE], [u8; P521_PUBLIC_KEY_SIZE], [u8; P521_ECDSA_SIGNATURE_SIZE], [u8; ED25519_SIGNATURE_SIZE])> = None; + let mut p521_ecdh_ecdsa_secret: Option<([u8; P521_SECRET_KEY_SIZE], [u8; P521_SECRET_KEY_SIZE])> = None; - let c25519_public_bytes = buf.read_bytes_fixed::<{ C25519_PUBLIC_KEY_SIZE }>(cursor)?; - let ed25519_public_bytes = buf.read_bytes_fixed::<{ ED25519_PUBLIC_KEY_SIZE }>(cursor)?; - let secrets_len = buf.read_u8(cursor)?; - if secrets_len == (C25519_SECRET_KEY_SIZE + ED25519_SECRET_KEY_SIZE) as u8 { - let c25519_secret_bytes = buf.read_bytes_fixed::<{ C25519_SECRET_KEY_SIZE }>(cursor)?; - let ed25519_secret_bytes = buf.read_bytes_fixed::<{ ED25519_SECRET_KEY_SIZE }>(cursor)?; - Ok(Identity { - address: addr, - c25519: c25519_public_bytes.clone(), - ed25519: ed25519_public_bytes.clone(), - v1: None, - secrets: Some(IdentitySecrets { - c25519: C25519KeyPair::from_bytes(c25519_public_bytes, c25519_secret_bytes).unwrap(), - ed25519: Ed25519KeyPair::from_bytes(ed25519_public_bytes, ed25519_secret_bytes).unwrap(), - v1: None, - }), - }) - } else if secrets_len == 0 { - Ok(Identity { - address: addr, - c25519: c25519_public_bytes.clone(), - ed25519: ed25519_public_bytes.clone(), - v1: None, - secrets: None, + loop { + let cipher_suite = buf.read_u8(cursor); + if cipher_suite.is_err() { + break; + } + match cipher_suite.unwrap() { + IDENTITY_CIPHER_SUITE_X25519 => { + let a = buf.read_bytes_fixed::(cursor)?; + let b = buf.read_bytes_fixed::(cursor)?; + let _ = x25519_public.replace((a.clone(), b.clone())); + let sec_size = buf.read_u8(cursor)?; + if sec_size == (C25519_SECRET_KEY_SIZE + ED25519_SECRET_KEY_SIZE) as u8 { + let a = buf.read_bytes_fixed::(cursor)?; + let b = buf.read_bytes_fixed::(cursor)?; + let _ = x25519_secret.replace((a.clone(), b.clone())); + } else if sec_size != 0 { + return std::io::Result::Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "invalid x25519 secret")); + } + }, + IDENTITY_CIPHER_SUITE_EC_NIST_P521 => { + let size = buf.read_u16(cursor)?; + if size < (P521_PUBLIC_KEY_SIZE + P521_PUBLIC_KEY_SIZE + P521_ECDSA_SIGNATURE_SIZE + ED25519_SIGNATURE_SIZE) as u16 { + return std::io::Result::Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "invalid p521 key")); + } + let a = buf.read_bytes_fixed::(cursor)?; + let b = buf.read_bytes_fixed::(cursor)?; + let c = buf.read_bytes_fixed::(cursor)?; + let d = buf.read_bytes_fixed::(cursor)?; + let _ = p521_ecdh_ecdsa_public.replace((a.clone(), b.clone(), c.clone(), d.clone())); + if size > (P521_PUBLIC_KEY_SIZE + P521_PUBLIC_KEY_SIZE + P521_ECDSA_SIGNATURE_SIZE + ED25519_SIGNATURE_SIZE) as u16 { + if size != (P521_PUBLIC_KEY_SIZE + P521_PUBLIC_KEY_SIZE + P521_ECDSA_SIGNATURE_SIZE + ED25519_SIGNATURE_SIZE + P521_SECRET_KEY_SIZE + P521_SECRET_KEY_SIZE) as u16 { + return std::io::Result::Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "invalid p521 key")); + } + let a = buf.read_bytes_fixed::(cursor)?; + let b = buf.read_bytes_fixed::(cursor)?; + let _ = p521_ecdh_ecdsa_secret.replace((a.clone(), b.clone())); + } + }, + _ => { + // Skip any unrecognized cipher suites, all of which will be prefixed by a size. + *cursor += buf.read_u16(cursor)? as usize; + if *cursor > buf.len() { + return std::io::Result::Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "invalid field length")); + } + } + } + } + + if x25519_public.is_none() { + return std::io::Result::Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "x25519 key missing")); + } + let x25519_public = x25519_public.unwrap(); + Ok(Identity { + address, + c25519: x25519_public.0.clone(), + ed25519: x25519_public.1.clone(), + p521: if p521_ecdh_ecdsa_public.is_some() { + let p521_ecdh_ecdsa_public = p521_ecdh_ecdsa_public.as_ref().unwrap(); + Some(IdentityP521Public { + ecdh: p521_ecdh_ecdsa_public.0.clone(), + ecdsa: p521_ecdh_ecdsa_public.1.clone(), + ecdsa_self_signature: p521_ecdh_ecdsa_public.2.clone(), + ed25519_self_signature: p521_ecdh_ecdsa_public.3.clone() }) } else { - std::io::Result::Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "unrecognized secret key length (type 0)")) - } - - } else if id_type == IdentityType::P521 as u8 { - - let c25519_public_bytes = buf.read_bytes_fixed::<{ C25519_PUBLIC_KEY_SIZE }>(cursor)?; - let ed25519_public_bytes = buf.read_bytes_fixed::<{ ED25519_PUBLIC_KEY_SIZE }>(cursor)?; - let p521_ecdh_public_bytes = buf.read_bytes_fixed::<{ P521_PUBLIC_KEY_SIZE }>(cursor)?; - let p521_ecdsa_public_bytes = buf.read_bytes_fixed::<{ P521_PUBLIC_KEY_SIZE }>(cursor)?; - let p521_signature = buf.read_bytes_fixed::<{ P521_ECDSA_SIGNATURE_SIZE }>(cursor)?; - let bh_digest = buf.read_bytes_fixed::<{ SHA384_HASH_SIZE }>(cursor)?; - let secrets_len = buf.read_u8(cursor)?; - if secrets_len == (C25519_SECRET_KEY_SIZE + ED25519_SECRET_KEY_SIZE + P521_SECRET_KEY_SIZE + P521_SECRET_KEY_SIZE) as u8 { - let c25519_secret_bytes = buf.read_bytes_fixed::<{ C25519_SECRET_KEY_SIZE }>(cursor)?; - let ed25519_secret_bytes = buf.read_bytes_fixed::<{ ED25519_SECRET_KEY_SIZE }>(cursor)?; - let p521_ecdh_secret_bytes = buf.read_bytes_fixed::<{ P521_SECRET_KEY_SIZE }>(cursor)?; - let p521_ecdsa_secret_bytes = buf.read_bytes_fixed::<{ P521_SECRET_KEY_SIZE }>(cursor)?; - Ok(Identity { - address: addr, - c25519: c25519_public_bytes.clone(), - ed25519: ed25519_public_bytes.clone(), - v1: Some((P521PublicKey::from_bytes(p521_ecdh_public_bytes).unwrap(), P521PublicKey::from_bytes(p521_ecdsa_public_bytes).unwrap(), p521_signature.clone(), bh_digest.clone())), - secrets: Some(IdentitySecrets { - c25519: C25519KeyPair::from_bytes(c25519_public_bytes, c25519_secret_bytes).unwrap(), - ed25519: Ed25519KeyPair::from_bytes(ed25519_public_bytes, ed25519_secret_bytes).unwrap(), - v1: Some((P521KeyPair::from_bytes(p521_ecdh_public_bytes, p521_ecdh_secret_bytes).unwrap(), P521KeyPair::from_bytes(p521_ecdsa_public_bytes, p521_ecdsa_secret_bytes).unwrap())), - }), - }) - } else if secrets_len == 0 { - Ok(Identity { - address: addr, - c25519: c25519_public_bytes.clone(), - ed25519: ed25519_public_bytes.clone(), - v1: Some((P521PublicKey::from_bytes(p521_ecdh_public_bytes).unwrap(), P521PublicKey::from_bytes(p521_ecdsa_public_bytes).unwrap(), p521_signature.clone(), bh_digest.clone())), - secrets: None, + None + }, + secret: if x25519_secret.is_some() { + let x25519_secret = x25519_secret.unwrap(); + let c25519_secret = C25519KeyPair::from_bytes(&x25519_public.0, &x25519_secret.0); + let ed25519_secret = Ed25519KeyPair::from_bytes(&x25519_public.1, &x25519_secret.1); + if c25519_secret.is_none() || ed25519_secret.is_none() { + return std::io::Result::Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "x25519 public key invalid")); + } + Some(IdentitySecret { + c25519: c25519_secret.unwrap(), + ed25519: ed25519_secret.unwrap(), + p521: if p521_ecdh_ecdsa_secret.is_some() && p521_ecdh_ecdsa_public.is_some() { + let p521_ecdh_ecdsa_public = p521_ecdh_ecdsa_public.as_ref().unwrap(); + let p521_ecdh_ecdsa_secret = p521_ecdh_ecdsa_secret.as_ref().unwrap(); + let p521_ecdh_secret = P521KeyPair::from_bytes(&p521_ecdh_ecdsa_public.0, &p521_ecdh_ecdsa_secret.0); + let p521_ecdsa_secret = P521KeyPair::from_bytes(&p521_ecdh_ecdsa_public.1, &p521_ecdh_ecdsa_secret.1); + if p521_ecdh_secret.is_none() || p521_ecdsa_secret.is_none() { + return std::io::Result::Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "p521 secret key invalid")); + } + Some(IdentityP521Secret { + ecdh: p521_ecdh_secret.unwrap(), + ecdsa: p521_ecdsa_secret.unwrap() + }) + } else { + None + } }) } else { - std::io::Result::Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "invalid secret key length (type 1)")) + None } + }) + } + /// Marshal this identity as a string with options to control which ciphers are included and whether private keys are included. + /// Note that x25519 ciphers are always included as they are required. Use IDENTITY_CIPHER_SUITE_INCLUDE_ALL for all. + pub fn to_string_with_options(&self, include_cipher_suites: u8, include_private: bool) -> String { + if include_private && self.secret.is_some() { + let secret = self.secret.as_ref().unwrap(); + if (include_cipher_suites & IDENTITY_CIPHER_SUITE_EC_NIST_P521) == IDENTITY_CIPHER_SUITE_EC_NIST_P521 && secret.p521.is_some() && self.p521.is_some() { + let p521_secret = secret.p521.as_ref().unwrap(); + let p521 = self.p521.as_ref().unwrap(); + let p521_secret_joined: [u8; P521_SECRET_KEY_SIZE + P521_SECRET_KEY_SIZE] = concat_arrays_2(p521_secret.ecdh.public_key_bytes(), p521_secret.ecdsa.public_key_bytes()); + let p521_joined: [u8; P521_PUBLIC_KEY_SIZE + P521_PUBLIC_KEY_SIZE + P521_ECDSA_SIGNATURE_SIZE + ED25519_SIGNATURE_SIZE] = concat_arrays_4(&p521.ecdh, &p521.ecdsa, &p521.ecdsa_self_signature, &p521.ed25519_self_signature); + format!("{}:0:{}{}:{}{}:1:{}:{}", self.address.to_string(), hex::to_string(&self.c25519), hex::to_string(&self.ed25519), hex::to_string(&secret.c25519.secret_bytes().0), hex::to_string(&secret.ed25519.secret_bytes().0), base64::encode_config(p521_joined, base64::URL_SAFE_NO_PAD), base64::encode_config(p521_secret_joined, base64::URL_SAFE_NO_PAD)) + } else { + format!("{}:0:{}{}:{}{}", self.address.to_string(), hex::to_string(&self.c25519), hex::to_string(&self.ed25519), hex::to_string(&secret.c25519.secret_bytes().0), hex::to_string(&secret.ed25519.secret_bytes().0)) + } } else { - std::io::Result::Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "unrecognized identity type")) + self.p521.as_ref().map_or_else(|| { + format!("{}:0:{}{}", self.address.to_string(), hex::to_string(&self.c25519), hex::to_string(&self.ed25519)) + }, |p521| { + let p521_joined: [u8; P521_PUBLIC_KEY_SIZE + P521_PUBLIC_KEY_SIZE + P521_ECDSA_SIGNATURE_SIZE + ED25519_SIGNATURE_SIZE] = concat_arrays_4(&p521.ecdh, &p521.ecdsa, &p521.ecdsa_self_signature, &p521.ed25519_self_signature); + format!("{}:0:{}{}::1:{}", self.address.to_string(), hex::to_string(&self.c25519), hex::to_string(&self.ed25519), base64::encode_config(p521_joined, base64::URL_SAFE_NO_PAD)) + }) } } - /// Get this identity in byte array format. - pub fn marshal_to_bytes(&self, include_private: bool) -> Vec { - let mut buf: Buffer<{ PACKET_SIZE_MAX }> = Buffer::new(); - self.marshal(&mut buf, include_private).expect("overflow"); - buf.as_bytes().to_vec() - } - - /// Unmarshal an identity from a byte slice. - /// On success the identity and the number of bytes actually read from the slice are returned. - pub fn unmarshal_from_bytes(bytes: &[u8]) -> std::io::Result<(Identity, usize)> { - let mut cursor: usize = 0; - let id = Self::unmarshal(&Buffer::<2048>::from_bytes(bytes)?, &mut cursor)?; - Ok((id, cursor)) - } - - /// Get this identity in string format, including its secret keys. - pub fn to_secret_string(&self) -> String { - self.secrets.as_ref().map_or_else(|| self.to_string(), |secrets| { - secrets.v1.as_ref().map_or_else(|| { - format!("{}:{}{}", self.to_string(), crate::util::hex::to_string(secrets.c25519.public_bytes().as_ref()), crate::util::hex::to_string(secrets.ed25519.secret_bytes().as_ref())) - }, |p521_secret| { - let secrets_concat = concat_v1_secret_keys(&secrets.c25519.secret_bytes().0, &secrets.ed25519.secret_bytes().0, &p521_secret.0.secret_key_bytes().0, &p521_secret.1.secret_key_bytes().0); - format!("{}:{}", self.to_string(), base64::encode_config(secrets_concat, base64::URL_SAFE_NO_PAD)) - }) - }) - } + /// Get this identity in string form with all ciphers and with secrets (if present) + pub fn to_secret_string(&self) -> String { self.to_string_with_options(IDENTITY_CIPHER_SUITE_INCLUDE_ALL, true) } } impl ToString for Identity { - fn to_string(&self) -> String { - self.v1.as_ref().map_or_else(|| { - format!("{:0>10x}:0:{}{}", self.address.to_u64(), crate::util::hex::to_string(&self.c25519), crate::util::hex::to_string(&self.ed25519)) - }, |p521_public| { - let public_concat = concat_v1_public_all(&self.c25519, &self.ed25519, (*p521_public).0.public_key_bytes(), (*p521_public).1.public_key_bytes(), &(*p521_public).2, &(*p521_public).3); - format!("{:0>10x}:1:{}", self.address.to_u64(), base64::encode_config(public_concat, base64::URL_SAFE_NO_PAD)) - }) - } + /// Get only the public portion of this identity as a string, including all cipher suites. + #[inline(always)] + fn to_string(&self) -> String { self.to_string_with_options(IDENTITY_CIPHER_SUITE_INCLUDE_ALL, false) } } impl FromStr for Identity { @@ -569,201 +474,206 @@ impl FromStr for Identity { fn from_str(s: &str) -> Result { let fields_v: Vec<&str> = s.split(':').collect(); let fields = fields_v.as_slice(); - if fields.len() == 3 || fields.len() == 4 { - let addr = Address::from_str(fields[0])?; - if fields[1] == "0" { - let public_keys = crate::util::hex::from_string(fields[2]); - if public_keys.len() == (C25519_PUBLIC_KEY_SIZE + ED25519_PUBLIC_KEY_SIZE) { - let mut secrets: Option = None; - if fields.len() == 4 { - let secret_keys = crate::util::hex::from_string(fields[3]); - if secret_keys.len() == (C25519_SECRET_KEY_SIZE + ED25519_SECRET_KEY_SIZE) { - let c25519_secret = C25519KeyPair::from_bytes(&public_keys.as_slice()[0..32], &secret_keys.as_slice()[0..32]); - let ed25519_secret = Ed25519KeyPair::from_bytes(&public_keys.as_slice()[32..64], &secret_keys.as_slice()[32..64]); - if c25519_secret.is_some() && ed25519_secret.is_some() { - secrets = Some(IdentitySecrets { - c25519: c25519_secret.unwrap(), - ed25519: ed25519_secret.unwrap(), - v1: None, - }); - } else { - return Err(InvalidFormatError); - } - } else { - return Err(InvalidFormatError); - } - } - return Ok(Identity { - address: addr, - c25519: public_keys.as_slice()[0..32].try_into().unwrap(), - ed25519: public_keys.as_slice()[32..64].try_into().unwrap(), - v1: None, - secrets, - }); - } - } else if fields[1] == "1" { - let public_keys_and_sig = base64::decode_config(fields[2], base64::URL_SAFE_NO_PAD); - if public_keys_and_sig.is_ok() { - let public_keys_and_sig = public_keys_and_sig.unwrap(); - if public_keys_and_sig.len() == V1_PUBLIC_KEYS_SIGNATURE_AND_POW_SIZE { - let p521_ecdh_public = P521PublicKey::from_bytes(&public_keys_and_sig.as_slice()[(C25519_PUBLIC_KEY_SIZE + ED25519_PUBLIC_KEY_SIZE)..(C25519_PUBLIC_KEY_SIZE + ED25519_PUBLIC_KEY_SIZE + P521_PUBLIC_KEY_SIZE)]); - let p521_ecdsa_public = P521PublicKey::from_bytes(&public_keys_and_sig.as_slice()[(C25519_PUBLIC_KEY_SIZE + ED25519_PUBLIC_KEY_SIZE + P521_PUBLIC_KEY_SIZE)..(C25519_PUBLIC_KEY_SIZE + ED25519_PUBLIC_KEY_SIZE + P521_PUBLIC_KEY_SIZE + P521_PUBLIC_KEY_SIZE)]); - if p521_ecdh_public.is_some() && p521_ecdsa_public.is_some() { - let p521_ecdh_public = p521_ecdh_public.unwrap(); - let p521_ecdsa_public = p521_ecdsa_public.unwrap(); - let mut secrets: Option = None; - if fields.len() == 4 { - let secret_keys = base64::decode_config(fields[3], base64::URL_SAFE_NO_PAD); - if secret_keys.is_ok() { - let secret_keys = secret_keys.unwrap(); - if secret_keys.len() == (C25519_SECRET_KEY_SIZE + ED25519_SECRET_KEY_SIZE + P521_SECRET_KEY_SIZE + P521_SECRET_KEY_SIZE) { - let p521_ecdh_secret = P521KeyPair::from_bytes(p521_ecdh_public.public_key_bytes(), &secret_keys.as_slice()[(C25519_SECRET_KEY_SIZE + ED25519_SECRET_KEY_SIZE)..(C25519_SECRET_KEY_SIZE + ED25519_SECRET_KEY_SIZE + P521_SECRET_KEY_SIZE)]); - let p521_ecdsa_secret = P521KeyPair::from_bytes(p521_ecdsa_public.public_key_bytes(), &secret_keys.as_slice()[(C25519_SECRET_KEY_SIZE + ED25519_SECRET_KEY_SIZE + P521_SECRET_KEY_SIZE)..(C25519_SECRET_KEY_SIZE + ED25519_SECRET_KEY_SIZE + P521_SECRET_KEY_SIZE + P521_SECRET_KEY_SIZE)]); - if p521_ecdh_secret.is_some() && p521_ecdsa_secret.is_some() { - secrets = Some(IdentitySecrets { - c25519: C25519KeyPair::from_bytes(&public_keys_and_sig.as_slice()[0..32], &secret_keys.as_slice()[0..32]).unwrap(), - ed25519: Ed25519KeyPair::from_bytes(&public_keys_and_sig.as_slice()[32..64], &secret_keys.as_slice()[32..64]).unwrap(), - v1: Some((p521_ecdh_secret.unwrap(), p521_ecdsa_secret.unwrap())), - }); - } else { - return Err(InvalidFormatError); - } - } else { - return Err(InvalidFormatError); - } - } else { - return Err(InvalidFormatError); - } - } - return Ok(Identity { - address: addr, - c25519: public_keys_and_sig.as_slice()[0..32].try_into().unwrap(), - ed25519: public_keys_and_sig.as_slice()[32..64].try_into().unwrap(), - v1: Some(( - p521_ecdh_public, - p521_ecdsa_public, - public_keys_and_sig.as_slice()[(C25519_PUBLIC_KEY_SIZE + ED25519_PUBLIC_KEY_SIZE + P521_PUBLIC_KEY_SIZE + P521_PUBLIC_KEY_SIZE)..(C25519_PUBLIC_KEY_SIZE + ED25519_PUBLIC_KEY_SIZE + P521_PUBLIC_KEY_SIZE + P521_PUBLIC_KEY_SIZE + P521_ECDSA_SIGNATURE_SIZE)].try_into().unwrap(), - public_keys_and_sig.as_slice()[(C25519_PUBLIC_KEY_SIZE + ED25519_PUBLIC_KEY_SIZE + P521_PUBLIC_KEY_SIZE + P521_PUBLIC_KEY_SIZE + P521_ECDSA_SIGNATURE_SIZE)..].try_into().unwrap() - )), - secrets, - }); - } + + if fields.len() < 3 || fields[0].len() != ADDRESS_SIZE_STRING { + return Err(InvalidFormatError); + } + let address = Address::from_str(fields[0]).map_err(|_| InvalidFormatError)?; + + // x25519 public, x25519 secret, p521 public, p521 secret + let mut keys: [Option<&str>; 4] = [None, None, None, None]; + + let mut ptr = 1; + let mut state = 0; + let mut key_ptr = 0; + while ptr < fields.len() { + match state { + 0 => { + if fields[ptr] == "0" { + key_ptr = 0; + } else if fields[ptr] == "1" { + key_ptr = 2; + } else { + return Err(InvalidFormatError); } + state = 1; + }, + 1 | 2 => { + let _ = keys[key_ptr].replace(fields[ptr]); + key_ptr += 1; + state = (state + 1) % 3; + }, + _ => { + return Err(InvalidFormatError); } } + ptr += 1; } - Err(InvalidFormatError) - } -} -impl Clone for Identity { - fn clone(&self) -> Self { - let bytes = self.marshal_to_bytes(true); - Identity::unmarshal_from_bytes(bytes.as_slice()).unwrap().0 + let keys = [hex::from_string(keys[0].unwrap_or("")), hex::from_string(keys[1].unwrap_or("")), base64::decode_config(keys[2].unwrap_or(""), base64::URL_SAFE_NO_PAD).unwrap_or_else(|_| Vec::new()), base64::decode_config(keys[3].unwrap_or(""), base64::URL_SAFE_NO_PAD).unwrap_or_else(|_| Vec::new())]; + if keys[0].len() != C25519_PUBLIC_KEY_SIZE + ED25519_PUBLIC_KEY_SIZE { + return Err(InvalidFormatError); + } + Ok(Identity { + address, + c25519: keys[0].as_slice()[0..32].try_into().unwrap(), + ed25519: keys[0].as_slice()[32..64].try_into().unwrap(), + p521: if keys[2].is_empty() { + None + } else { + if keys[2].len() != P521_PUBLIC_KEY_SIZE + P521_PUBLIC_KEY_SIZE + P521_ECDSA_SIGNATURE_SIZE + ED25519_SIGNATURE_SIZE { + return Err(InvalidFormatError); + } + Some(IdentityP521Public { + ecdh: keys[2].as_slice()[0..132].try_into().unwrap(), + ecdsa: keys[2].as_slice()[132..264].try_into().unwrap(), + ecdsa_self_signature: keys[2].as_slice()[264..396].try_into().unwrap(), + ed25519_self_signature: keys[2].as_slice()[396..460].try_into().unwrap(), + }) + }, + secret: if keys[1].is_empty() { + None + } else { + if keys[1].len() != C25519_SECRET_KEY_SIZE + ED25519_SECRET_KEY_SIZE { + return Err(InvalidFormatError); + } + Some(IdentitySecret { + c25519: { + let tmp = C25519KeyPair::from_bytes(&keys[0].as_slice()[0..32], &keys[1].as_slice()[0..32]); + if tmp.is_none() { + return Err(InvalidFormatError); + } + tmp.unwrap() + }, + ed25519: { + let tmp = Ed25519KeyPair::from_bytes(&keys[0].as_slice()[32..64], &keys[1].as_slice()[32..64]); + if tmp.is_none() { + return Err(InvalidFormatError); + } + tmp.unwrap() + }, + p521: if keys[3].is_empty() { + None + } else { + if keys[2].len() != P521_PUBLIC_KEY_SIZE + P521_PUBLIC_KEY_SIZE + P521_ECDSA_SIGNATURE_SIZE + ED25519_SIGNATURE_SIZE { + return Err(InvalidFormatError); + } + if keys[3].len() != P521_SECRET_KEY_SIZE + P521_SECRET_KEY_SIZE { + return Err(InvalidFormatError); + } + Some(IdentityP521Secret { + ecdh: { + let tmp = P521KeyPair::from_bytes(&keys[2].as_slice()[0..132], &keys[3].as_slice()[..P521_SECRET_KEY_SIZE]); + if tmp.is_none() { + return Err(InvalidFormatError); + } + tmp.unwrap() + }, + ecdsa: { + let tmp = P521KeyPair::from_bytes(&keys[2].as_slice()[132..264], &keys[3].as_slice()[P521_SECRET_KEY_SIZE..]); + if tmp.is_none() { + return Err(InvalidFormatError); + } + tmp.unwrap() + } + }) + } + }) + } + }) } } impl PartialEq for Identity { fn eq(&self, other: &Self) -> bool { - self.address.eq(&other.address) && self.c25519.eq(&other.c25519) && self.ed25519.eq(&other.ed25519) && self.v1.as_ref().map_or_else(|| other.v1.is_none(), |v1| other.v1.as_ref().map_or(false, |other_v1| (*v1).0.eq(&(*other_v1).0) && (*v1).1.eq(&(*other_v1).1))) + self.address == other.address && + self.c25519 == other.c25519 && + self.ed25519 == other.ed25519 && + self.p521.map_or(other.p521.is_none(), |p521| { + other.p521.map_or(false, |other_p521| { + p521.ecdh == other_p521.ecdh && p521.ecdsa == other_p521.ecdsa + }) + }) } } impl Eq for Identity {} +impl Ord for Identity { + fn cmp(&self, other: &Self) -> Ordering { + let addr_ord = self.address.cmp(&other.address); + match addr_ord { + Ordering::Equal => self.c25519.cmp(&other.c25519), + _ => addr_ord + } + } +} + impl PartialOrd for Identity { #[inline(always)] fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } -impl Ord for Identity { +const FRANKENHASH_POW_MEMORY_SIZE: usize = 2097152; + +/// This is a compound hasher used for the work function that derives an address. +/// +/// FIPS note: addresses are just unique identifiers based on a hash. The actual key is +/// what truly determines node identity. For FIPS purposes this can be considered a +/// non-cryptographic hash. Its memory hardness and use in a work function is a defense +/// in depth feature rather than a primary security feature. +fn zt_frankenhash(digest: &mut [u8; 64], genmem_pool_obj: &mut Pooled) { + let genmem_ptr = genmem_pool_obj.0.as_mut_ptr().cast::(); + let (genmem, genmem_alias_hack) = unsafe { (&mut *slice_from_raw_parts_mut(genmem_ptr, FRANKENHASH_POW_MEMORY_SIZE), &*slice_from_raw_parts(genmem_ptr, FRANKENHASH_POW_MEMORY_SIZE)) }; + let genmem_u64_ptr = genmem_ptr.cast::(); + + let mut s20 = Salsa::new(&digest[0..32], &digest[32..40], false).unwrap(); + + s20.crypt(&crate::util::ZEROES[0..64], &mut genmem[0..64]); + let mut i: usize = 64; + while i < FRANKENHASH_POW_MEMORY_SIZE { + let ii = i + 64; + s20.crypt(&genmem_alias_hack[(i - 64)..i], &mut genmem[i..ii]); + i = ii; + } + + i = 0; + while i < (FRANKENHASH_POW_MEMORY_SIZE / 8) { + unsafe { + let idx1 = (((*genmem_u64_ptr.add(i)).to_be() & 7) * 8) as usize; + let idx2 = ((*genmem_u64_ptr.add(i + 1)).to_be() % (FRANKENHASH_POW_MEMORY_SIZE as u64 / 8)) as usize; + let genmem_u64_at_idx2_ptr = genmem_u64_ptr.add(idx2); + let tmp = *genmem_u64_at_idx2_ptr; + let digest_u64_ptr = digest.as_mut_ptr().add(idx1).cast::(); + *genmem_u64_at_idx2_ptr = *digest_u64_ptr; + *digest_u64_ptr = tmp; + } + s20.crypt_in_place(digest); + i += 2; + } +} + +#[repr(transparent)] +struct FrankenhashMemory([u128; FRANKENHASH_POW_MEMORY_SIZE / 16]); // use u128 to align by 16 bytes + +struct FrankenhashMemoryFactory; + +impl PoolFactory for FrankenhashMemoryFactory { #[inline(always)] - fn cmp(&self, other: &Self) -> Ordering { - let c = self.address.cmp(&other.address); - if c.is_eq() { - self.c25519.cmp(&other.c25519) - } else { - c - } - } -} + fn create(&self) -> FrankenhashMemory { FrankenhashMemory([0_u128; FRANKENHASH_POW_MEMORY_SIZE / 16]) } -impl Hash for Identity { #[inline(always)] - fn hash(&self, state: &mut H) { - state.write_u64(self.address.to_u64()); - } + fn reset(&self, _: &mut FrankenhashMemory) {} } -#[cfg(test)] -mod tests { - use std::str::FromStr; - - use crate::vl1::identity::{Identity, IdentityType}; - - #[test] - fn type0() { - let id = Identity::generate(IdentityType::C25519); - //println!("V0: {}", id.to_string()); - if !id.locally_validate() { - panic!("new V0 identity validation failed"); - } - let sig = id.sign(&[1_u8]).unwrap(); - //println!("sig: {}", crate::util::hex::to_string(sig.as_slice())); - if !id.verify(&[1_u8], sig.as_slice()) { - panic!("valid signature verification failed"); - } - if id.verify(&[0_u8], sig.as_slice()) { - panic!("invalid signature verification succeeded"); - } - for good_id in [ - "7f3a8e50db:0:936b698c68f51508e9184f7510323a01da0e5778158244c83520614822e2352855ff4d82443823b866cdb553d02d8fa5da833fbee62472e666a60605b76194b9:0d46684e30d561c859bf7d530d2de0452605d8cf392db4beb2768ceda55e63673f11d84a9f31ce7504f0e3ce5dc9ab7ecf9662e555846d130422916482be5fbb", - "8bd225d6a9:0:08e7fc755ee0aa2e10bf37c0b8dd6f33b3164de04cf3f716584ee44df1fe9506ce1f3f2874c6d1450fc8fab339a95092ec7e628cddd26af93c4392e6564d9ee7:431bb44d22734d925538cbcdc7c2a80c0f71968041949f76ccb6f690f01b6cf45976071c86fcf2ddda2d463c8cfe6444b36c8ee0d057d665350acdcb86dff06f" - ] { - let id = Identity::from_str(good_id).unwrap(); - if !id.locally_validate() { - panic!("known-good V0 identity failed local validation"); - } - let id_bytes = id.marshal_to_bytes(true); - let id2 = Identity::unmarshal_from_bytes(id_bytes.as_slice()).unwrap().0; - if !id.eq(&id2) { - panic!("identity V0 marshal/unmarshal failed"); - } - } - } - - #[test] - fn type1() { - let start = std::time::SystemTime::now(); - let id = Identity::generate(IdentityType::P521); - let end = std::time::SystemTime::now(); - println!("V1 generate: {}ms {}", end.duration_since(start).unwrap().as_millis(), id.to_string()); - if !id.locally_validate() { - panic!("new V1 identity validation failed"); - } - let sig = id.sign(&[1_u8]).unwrap(); - //println!("sig: {}", crate::util::hex::to_string(sig.as_slice())); - if !id.verify(&[1_u8], sig.as_slice()) { - panic!("valid signature verification failed"); - } - if id.verify(&[0_u8], sig.as_slice()) { - panic!("invalid signature verification succeeded"); - } - for good_id in [ - "39ac93e603:1:kbBpk8QuSmNuSJFMLLXLbXcQMLXTMQOfGB8phFansHwxFrXO0RLNJB4HX4bHwq1UScYv2gVlwqmluH1J9_zp2gDaXD1c4jS17Zfdwz672wvkEMIRLUMxxxS4Cc4G7Xfhwpeot2qsslOTVw6h0FTM5izdH6ddnMmJGEg-84a_eQwSQgCNAYvczolmQg9D_MRp6wPdJdBFsnUMmj4F73jro4EGSAhE39AE8hPSxnLNOdhyRHriV5kMkIMsukJrQ09ZxKTmrQCvPzrzUrugkSu6BAqrXSm-h8S9tJo1TVIG0cSbBOK1nJsAoYhOq-T-hBvyqvKsFi9Wkz5bnG6NiYN95Yd38d2VRQBm4Xx7MPQDH0Yk10A1hc_dgyHtFGKjyTrpkKIBHICx_ijg3iS8Uut0aZpIb6pnOs1mZyjSCDLkSKJoNkxE8btHOwHzDIY0utnmsh6sCZ8wWUzYyXKfA506o7z1x57U4qxSI8-_cqZg6DUEoLb0idYanObcN9YmXdUtacpRoLhJTMlowQHqJK3_i0xh5P4UoBHuK7xST1Yu411y9keyZAie771kJFttzR3AHP89IEn0rbfbUZmAL1icL2LxWIq6kIXF6vdfgAHngQ-C1kjC3pXoWyvglJ7B33AfejmktuytTdGxe0ve1MhsXk0J3WIgokQ5rJPmAw", - "953f45090f:1:Ghn8_lcEX4bovP90tjJQ4oTXoCf5muJYVaC599_5RW6BN7IgGCsxodiN9U1Pq1_IDYnuJ98KmlPXksE77kvGrQCWK1BnhUFVthtkk7CkEsLdtMu5NqNK_1SVuxnfVCNNELBBIZMIl22YVw8PLTwlPcWUn16m9sAD9jqptUrwAmGUcQANpUiED6k3heqszrR9IjGVVYAcIOOHYJrVz13dv0-Ty6iyoNYG12AuSPd-7suG1Ztgt2Zll8c80BGUAib0-dgJBwF8fV8Bn_Sji6kz7qyp8566U6NkbAA4znM5UvrUPIw6O5016RuO8YzDLE_LKI_erwB2CjZsC0TDOFEppkPLSxDBWgBe1PRwnKTUhP9Bq7vYVbpBs_mEd4b7a-ilce8dX-dxJJwIXIioCdL6fZzA5KqfjW1pG-M72ct6iLCmq1R4b26eiwDG9z-HwX-fRVEkzDeE1rQ2bAEnqTNtzMTItAdzF62LiPOZlZ8LQ-fNTbdThphx58uKczo8Dl44R89d1l81yTKFbgGrUU6lw5qByH6VuLuprOn96sQLv4w6M8DPVnS_5E5AOIoXJ7KmEltIUz_B45ZzgDgPrBjE3-E2ipSSxQe72ADAkQL-D2uCtZVa-UhRFbjUqzBMoTu2NqHYotUoxCsbTVI1d3duD5NefnkjjmqVP0UJDw" - ] { - let id = Identity::from_str(good_id).unwrap(); - if !id.locally_validate() { - panic!("known-good V1 identity failed local validation"); - } - let id_bytes = id.marshal_to_bytes(true); - let id2 = Identity::unmarshal_from_bytes(id_bytes.as_slice()).unwrap().0; - if !id.eq(&id2) { - panic!("identity V1 marshal/unmarshal failed"); - } - } - } +lazy_static! { + static ref FRANKENHASH_POW_MEMORY_POOL: Pool = Pool::new(0, FrankenhashMemoryFactory); } - */ \ No newline at end of file +/// Purge the memory pool used to verify identities. This can be called periodically +/// from the maintenance function to prevent memory buildup from bursts of identity +/// verification. +#[inline(always)] +pub(crate) fn purge_verification_memory_pool() { + unsafe { FRANKENHASH_POW_MEMORY_POOL.purge() }; +} diff --git a/zerotier-network-hypervisor/src/vl1/inetaddress.rs b/zerotier-network-hypervisor/src/vl1/inetaddress.rs index 9ae9a4096..0ad50c4a7 100644 --- a/zerotier-network-hypervisor/src/vl1/inetaddress.rs +++ b/zerotier-network-hypervisor/src/vl1/inetaddress.rs @@ -365,20 +365,20 @@ impl InetAddress { unsafe { match self.sa.sa_family as u8 { AF_INET => { - buf.append_and_init_bytes_fixed(|b: &mut [u8; 7]| { - b[0] = 4; - copy_nonoverlapping((&self.sin.sin_addr.s_addr as *const u32).cast::(), b.as_mut_ptr().offset(1), 4); - b[5] = *(&self.sin.sin_port as *const u16).cast::(); - b[6] = *(&self.sin.sin_port as *const u16).cast::().offset(1); - }) + let b = buf.append_bytes_fixed_get_mut::<7>()?; + b[0] = 4; + copy_nonoverlapping((&self.sin.sin_addr.s_addr as *const u32).cast::(), b.as_mut_ptr().offset(1), 4); + b[5] = *(&self.sin.sin_port as *const u16).cast::(); + b[6] = *(&self.sin.sin_port as *const u16).cast::().offset(1); + Ok(()) } AF_INET6 => { - buf.append_and_init_bytes_fixed(|b: &mut [u8; 19]| { - b[0] = 6; - copy_nonoverlapping((&(self.sin6.sin6_addr) as *const in6_addr).cast::(), b.as_mut_ptr().offset(1), 16); - b[17] = *(&self.sin6.sin6_port as *const u16).cast::(); - b[18] = *(&self.sin6.sin6_port as *const u16).cast::().offset(1); - }) + let b = buf.append_bytes_fixed_get_mut::<19>()?; + b[0] = 6; + copy_nonoverlapping((&(self.sin6.sin6_addr) as *const in6_addr).cast::(), b.as_mut_ptr().offset(1), 16); + b[17] = *(&self.sin6.sin6_port as *const u16).cast::(); + b[18] = *(&self.sin6.sin6_port as *const u16).cast::().offset(1); + Ok(()) } _ => buf.append_u8(0) } @@ -389,10 +389,10 @@ impl InetAddress { let t = buf.read_u8(cursor)?; if t == 4 { let b: &[u8; 6] = buf.read_bytes_fixed(cursor)?; - Ok(InetAddress::from_ip_port(&b[0..4], crate::util::load_u16_be(&b[4..6]))) + Ok(InetAddress::from_ip_port(&b[0..4], u16::from_be_bytes(b[4..6].try_into().unwrap()))) } else if t == 6 { let b: &[u8; 18] = buf.read_bytes_fixed(cursor)?; - Ok(InetAddress::from_ip_port(&b[0..16], crate::util::load_u16_be(&b[16..18]))) + Ok(InetAddress::from_ip_port(&b[0..16], u16::from_be_bytes(b[16..18].try_into().unwrap()))) } else { Ok(InetAddress::new()) } diff --git a/zerotier-network-hypervisor/src/vl1/mac.rs b/zerotier-network-hypervisor/src/vl1/mac.rs index 4dd1075a6..9043278cc 100644 --- a/zerotier-network-hypervisor/src/vl1/mac.rs +++ b/zerotier-network-hypervisor/src/vl1/mac.rs @@ -46,15 +46,15 @@ impl MAC { #[inline(always)] pub(crate) fn marshal(&self, buf: &mut Buffer) -> std::io::Result<()> { - buf.append_and_init_bytes_fixed(|b: &mut [u8; 6]| { - let i = self.0.get(); - b[0] = (i >> 40) as u8; - b[1] = (i >> 32) as u8; - b[2] = (i >> 24) as u8; - b[3] = (i >> 16) as u8; - b[4] = (i >> 8) as u8; - b[5] = i as u8; - }) + let b = buf.append_bytes_fixed_get_mut::<6>()?; + let i = self.0.get(); + (*b)[0] = (i >> 40) as u8; + (*b)[1] = (i >> 32) as u8; + (*b)[2] = (i >> 24) as u8; + (*b)[3] = (i >> 16) as u8; + (*b)[4] = (i >> 8) as u8; + (*b)[5] = i as u8; + Ok(()) } #[inline(always)] diff --git a/zerotier-network-hypervisor/src/vl1/mod.rs b/zerotier-network-hypervisor/src/vl1/mod.rs index 0f7e452de..9b0036396 100644 --- a/zerotier-network-hypervisor/src/vl1/mod.rs +++ b/zerotier-network-hypervisor/src/vl1/mod.rs @@ -9,9 +9,9 @@ pub mod inetaddress; pub mod endpoint; pub mod rootset; +pub mod identity; #[allow(unused)] -pub(crate) mod identity; pub(crate) mod protocol; pub(crate) mod node; pub(crate) mod path; @@ -26,7 +26,7 @@ pub(crate) mod symmetricsecret; pub use address::Address; pub use mac::MAC; -pub use identity::{Identity, IdentityType, IDENTITY_TYPE_0_SIGNATURE_SIZE, IDENTITY_TYPE_1_SIGNATURE_SIZE}; +pub use identity::Identity; pub use endpoint::Endpoint; pub use dictionary::Dictionary; pub use inetaddress::InetAddress; diff --git a/zerotier-network-hypervisor/src/vl1/node.rs b/zerotier-network-hypervisor/src/vl1/node.rs index f8ac7e43d..b890fe1c8 100644 --- a/zerotier-network-hypervisor/src/vl1/node.rs +++ b/zerotier-network-hypervisor/src/vl1/node.rs @@ -21,7 +21,7 @@ use crate::error::InvalidParameterError; use crate::util::gate::IntervalGate; use crate::util::pool::{Pool, Pooled}; use crate::util::buffer::Buffer; -use crate::vl1::{Address, Endpoint, Identity, IdentityType}; +use crate::vl1::{Address, Endpoint, Identity}; use crate::vl1::path::Path; use crate::vl1::peer::Peer; use crate::vl1::protocol::*; @@ -106,10 +106,10 @@ pub trait VL1PacketHandler { fn handle_packet(&self, peer: &Peer, source_path: &Arc, forward_secrecy: bool, extended_authentication: bool, verb: u8, payload: &Buffer<{ PACKET_SIZE_MAX }>) -> bool; /// Handle errors, returning true if the error was recognized. - fn handle_error(&self, peer: &Peer, source_path: &Arc, forward_secrecy: bool, extended_authentication: bool, in_re_verb: u8, in_re_packet_id: PacketID, error_code: u8, payload: &Buffer<{ PACKET_SIZE_MAX }>, cursor: &mut usize) -> bool; + fn handle_error(&self, peer: &Peer, source_path: &Arc, forward_secrecy: bool, extended_authentication: bool, in_re_verb: u8, in_re_message_id: u64, error_code: u8, payload: &Buffer<{ PACKET_SIZE_MAX }>, cursor: &mut usize) -> bool; /// Handle an OK, returing true if the OK was recognized. - fn handle_ok(&self, peer: &Peer, source_path: &Arc, forward_secrecy: bool, extended_authentication: bool, in_re_verb: u8, in_re_packet_id: PacketID, payload: &Buffer<{ PACKET_SIZE_MAX }>, cursor: &mut usize) -> bool; + fn handle_ok(&self, peer: &Peer, source_path: &Arc, forward_secrecy: bool, extended_authentication: bool, in_re_verb: u8, in_re_message_id: u64, payload: &Buffer<{ PACKET_SIZE_MAX }>, cursor: &mut usize) -> bool; } #[derive(Default)] @@ -134,17 +134,14 @@ pub struct Node { impl Node { /// Create a new Node. - /// - /// If the auto-generate identity type is not None, a new identity will be generated if - /// no identity is currently stored in the data store. - pub fn new(ci: &I, auto_generate_identity_type: Option) -> Result { + pub fn new(ci: &I, auto_generate_identity: bool) -> Result { let id = { let id_str = ci.load_node_identity(); if id_str.is_none() { - if auto_generate_identity_type.is_none() { + if !auto_generate_identity { return Err(InvalidParameterError("no identity found and auto-generate not enabled")); } else { - let id = Identity::generate(auto_generate_identity_type.unwrap()); + let id = Identity::generate(); ci.save_node_identity(&id, id.to_string().as_bytes(), id.to_secret_string().as_bytes()); id } @@ -180,7 +177,7 @@ impl Node { pub fn packet_buffer_pool(&self) -> &Arc { &self.buffer_pool } #[inline(always)] - pub fn address(&self) -> Address { self.identity.address() } + pub fn address(&self) -> Address { self.identity.address } #[inline(always)] pub fn identity(&self) -> &Identity { &self.identity } @@ -235,7 +232,7 @@ impl Node { if dest.is_some() { let time_ticks = ci.time_ticks(); let dest = dest.unwrap(); - if dest == self.identity.address() { + if dest == self.identity.address { // Handle packets addressed to this node. let path = self.path(source_endpoint, source_local_socket, source_local_interface); @@ -243,7 +240,7 @@ impl Node { if fragment_header.is_fragment() { - let _ = path.receive_fragment(fragment_header.id, fragment_header.fragment_no(), fragment_header.total_fragments(), data, time_ticks).map(|assembled_packet| { + let _ = path.receive_fragment(u64::from_ne_bytes(fragment_header.id), fragment_header.fragment_no(), fragment_header.total_fragments(), data, time_ticks).map(|assembled_packet| { if assembled_packet.frags[0].is_some() { let frag0 = assembled_packet.frags[0].as_ref().unwrap(); let packet_header = frag0.struct_at::(0); diff --git a/zerotier-network-hypervisor/src/vl1/path.rs b/zerotier-network-hypervisor/src/vl1/path.rs index a3ceda254..4e4b85f7a 100644 --- a/zerotier-network-hypervisor/src/vl1/path.rs +++ b/zerotier-network-hypervisor/src/vl1/path.rs @@ -66,7 +66,7 @@ impl Path { /// Receive a fragment and return a FragmentedPacket if the entire packet was assembled. /// This returns None if more fragments are needed to assemble the packet. #[inline(always)] - pub(crate) fn receive_fragment(&self, packet_id: PacketID, fragment_no: u8, fragment_expecting_count: u8, packet: PacketBuffer, time_ticks: i64) -> Option { + pub(crate) fn receive_fragment(&self, packet_id: u64, fragment_no: u8, fragment_expecting_count: u8, packet: PacketBuffer, time_ticks: i64) -> Option { let mut fp = self.fragmented_packets.lock(); // Discard some old waiting packets if the total incoming fragments for a path exceeds a diff --git a/zerotier-network-hypervisor/src/vl1/peer.rs b/zerotier-network-hypervisor/src/vl1/peer.rs index eb154711c..f8b37797d 100644 --- a/zerotier-network-hypervisor/src/vl1/peer.rs +++ b/zerotier-network-hypervisor/src/vl1/peer.rs @@ -7,12 +7,12 @@ */ use std::convert::TryInto; -use std::intrinsics::try; use std::mem::MaybeUninit; use std::num::NonZeroI64; use std::ptr::copy_nonoverlapping; use std::sync::Arc; use std::sync::atomic::{AtomicI64, AtomicU64, AtomicU8, Ordering}; +use libc::uname; use parking_lot::Mutex; @@ -32,6 +32,7 @@ use crate::util::buffer::Buffer; use crate::util::pool::{Pool, PoolFactory}; use crate::vl1::{Dictionary, Endpoint, Identity, InetAddress, Path}; use crate::vl1::ephemeral::EphemeralSymmetricSecret; +use crate::vl1::identity::{IDENTITY_CIPHER_SUITE_INCLUDE_ALL, IDENTITY_CIPHER_SUITE_X25519}; use crate::vl1::node::*; use crate::vl1::protocol::*; use crate::vl1::symmetricsecret::SymmetricSecret; @@ -72,12 +73,6 @@ pub struct Peer { // Static shared secret computed from agreement with identity. static_secret: SymmetricSecret, - // Derived static secret (in initialized cipher) used to encrypt the dictionary part of HELLO. - static_secret_hello_dictionary: Mutex, - - // Derived static secret used to add full HMAC-SHA384 to packets, currently just HELLO. - static_secret_packet_hmac: Secret<48>, - // Latest ephemeral secret acknowledged with OK(HELLO). ephemeral_secret: Mutex>>, @@ -130,7 +125,7 @@ fn salsa_derive_per_packet_key(key: &Secret<48>, header: &PacketHeader, packet_s #[inline(always)] fn salsa_poly_create(secret: &SymmetricSecret, header: &PacketHeader, packet_size: usize) -> (Salsa, Poly1305) { let key = salsa_derive_per_packet_key(&secret.key, header, packet_size); - let mut salsa = Salsa::new(&key.0[0..32], header.id_bytes(), true).unwrap(); + let mut salsa = Salsa::new(&key.0[0..32], &header.id, true).unwrap(); let mut poly1305_key = [0_u8; 32]; salsa.crypt_in_place(&mut poly1305_key); (salsa, Poly1305::new(&poly1305_key).unwrap()) @@ -142,11 +137,15 @@ fn try_aead_decrypt(secret: &SymmetricSecret, packet_frag0_payload_bytes: &[u8], match header.cipher() { CIPHER_NOCRYPT_POLY1305 => { if (verb & VERB_MASK) == VERB_VL1_HELLO { + let mut total_packet_len = packet_frag0_payload_bytes.len() + PACKET_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(FRAGMENT_HEADER_SIZE).map(|f| payload.append_bytes(f))); } - let (_, mut poly) = salsa_poly_create(secret, header, packet.len()); + let (_, mut poly) = salsa_poly_create(secret, header, total_packet_len); poly.update(payload.as_bytes()); if poly.finish()[0..8].eq(&header.mac) { *message_id = u64::from_ne_bytes(header.id); @@ -161,7 +160,11 @@ fn try_aead_decrypt(secret: &SymmetricSecret, packet_frag0_payload_bytes: &[u8], } CIPHER_SALSA2012_POLY1305 => { - let (mut salsa, mut poly) = salsa_poly_create(secret, header, packet.len()); + let mut total_packet_len = packet_frag0_payload_bytes.len() + PACKET_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() { @@ -196,7 +199,7 @@ fn try_aead_decrypt(secret: &SymmetricSecret, packet_frag0_payload_bytes: &[u8], // 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. - *mesasge_id = u64::from_ne_bytes(*array_range::(tag)); + *message_id = u64::from_ne_bytes(*array_range::(tag)); true }) } @@ -215,8 +218,6 @@ impl Peer { Peer { identity: id, static_secret: SymmetricSecret::new(static_secret), - static_secret_hello_dictionary: Mutex::new(AesCtr::new(&static_secret_hello_dictionary.0[0..32])), - static_secret_packet_hmac, ephemeral_secret: Mutex::new(None), paths: Mutex::new(Vec::new()), reported_local_ip: Mutex::new(None), @@ -244,7 +245,7 @@ impl Peer { /// those fragments after the main packet header and first chunk. pub(crate) fn receive(&self, node: &Node, ci: &CI, ph: &PH, time_ticks: i64, source_path: &Arc, header: &PacketHeader, packet: &Buffer<{ PACKET_SIZE_MAX }>, fragments: &[Option]) { let _ = packet.as_bytes_starting_at(PACKET_VERB_INDEX).map(|packet_frag0_payload_bytes| { - let mut payload: Buffer = unsafe { Buffer::new_nozero() }; + let mut payload: Buffer = unsafe { Buffer::new_without_memzero() }; let mut message_id = 0_u64; let ephemeral_secret: Option> = self.ephemeral_secret.lock().clone(); let forward_secrecy = if !ephemeral_secret.map_or(false, |ephemeral_secret| try_aead_decrypt(&ephemeral_secret.secret, packet_frag0_payload_bytes, header, fragments, &mut payload, &mut message_id)) { @@ -269,7 +270,7 @@ impl Peer { if extended_authentication { if payload.len() >= (1 + SHA384_HASH_SIZE) { let actual_end_of_payload = payload.len() - SHA384_HASH_SIZE; - let hmac = SHA384::hmac_multipart(self.static_secret_packet_hmac.as_ref(), &[u64_as_bytes(&message_id), payload.as_bytes()]); + let hmac = SHA384::hmac_multipart(self.static_secret.packet_hmac_key.as_ref(), &[u64_as_bytes(&message_id), payload.as_bytes()]); if !hmac.eq(&(payload.as_bytes()[actual_end_of_payload..])) { return; } @@ -399,82 +400,78 @@ impl Peer { /// 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. - /// - /// This has its own send logic so it can handle either an explicit endpoint or a - /// known one. - pub(crate) fn send_hello(&self, ci: &CI, node: &Node, explicit_endpoint: Option) -> bool { - let path = if explicit_endpoint.is_none() { self.path(node) } else { None }; - explicit_endpoint.as_ref().map_or_else(|| Some(path.as_ref().unwrap().endpoint()), |ep| Some(ep)).map_or(false, |endpoint| { - let mut packet: Buffer<{ PACKET_SIZE_MAX }> = Buffer::new(); - let time_ticks = ci.time_ticks(); + /// If explicit_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 known. + pub(crate) fn send_hello(&self, ci: &CI, node: &Node, explicit_endpoint: Option<&Endpoint>) -> bool { + let (path, endpoint) = if explicit_endpoint.is_some() { + (None, explicit_endpoint.unwrap()) + } else { + let p = self.path(node); + if p.is_none() { + return false; + } + (p, p.as_ref().unwrap().endpoint()) + }; - let message_id = self.next_message_id(); - let packet_header: &mut PacketHeader = packet.append_struct_get_mut().unwrap(); - let hello_fixed_headers: &mut message_component_structs::HelloFixedHeaderFields = packet.append_struct_get_mut().unwrap(); - packet_header.id = message_id.to_ne_bytes(); // packet ID and message ID are the same when Poly1305 MAC is used - packet_header.dest = self.identity.address().to_bytes(); - packet_header.src = node.address().to_bytes(); - packet_header.flags_cipher_hops = CIPHER_NOCRYPT_POLY1305; - hello_fixed_headers.verb = VERB_VL1_HELLO | VERB_FLAG_EXTENDED_AUTHENTICATION; - hello_fixed_headers.version_proto = VERSION_PROTO; - hello_fixed_headers.version_major = VERSION_MAJOR; - hello_fixed_headers.version_minor = VERSION_MINOR; - hello_fixed_headers.version_revision = (VERSION_REVISION as u16).to_be(); - hello_fixed_headers.timestamp = (time_ticks as u64).to_be(); + let mut packet: Buffer<{ PACKET_SIZE_MAX }> = Buffer::new(); + let time_ticks = ci.time_ticks(); - debug_assert!(self.identity.marshal(&mut packet, false).is_ok()); - debug_assert!(endpoint.marshal(&mut packet).is_ok()); + let message_id = self.next_message_id(); + let packet_header: &mut PacketHeader = packet.append_struct_get_mut().unwrap(); + let hello_fixed_headers: &mut message_component_structs::HelloFixedHeaderFields = packet.append_struct_get_mut().unwrap(); - // Write an IV for AES-CTR encryption of the dictionary and allocate two more - // bytes for reserved legacy use below. - let aes_ctr_iv_position = packet.len(); - let aes_ctr_iv: &mut [u8; 18] = packet.append_bytes_fixed_get_mut().unwrap(); - zerotier_core_crypto::random::fill_bytes_secure(&mut aes_ctr_iv[0..16]); - aes_ctr_iv[12] &= 0x7f; // mask off MSB of counter in iv to play nice with some AES-CTR implementations + packet_header.id = message_id.to_ne_bytes(); // packet ID and message ID are the same when Poly1305 MAC is used + packet_header.dest = self.identity.address.to_bytes(); + packet_header.src = node.address().to_bytes(); + packet_header.flags_cipher_hops = CIPHER_NOCRYPT_POLY1305; - // LEGACY: create a 16-bit encrypted field that specifies zero "moons." This is ignored now - // but causes old nodes to be able to parse this packet properly. This is not significant in - // terms of encryption or authentication and can disappear once old versions are dead. Newer - // versions ignore these bytes. - let mut salsa_iv = message_id.to_ne_bytes(); - salsa_iv[7] &= 0xf8; - Salsa::new(&self.static_secret.secret.0[0..32], &salsa_iv, true).unwrap().crypt(&[0_u8, 0_u8], &mut aes_ctr_iv[16..18]); + hello_fixed_headers.verb = VERB_VL1_HELLO | VERB_FLAG_EXTENDED_AUTHENTICATION; + hello_fixed_headers.version_proto = VERSION_PROTO; + hello_fixed_headers.version_major = VERSION_MAJOR; + hello_fixed_headers.version_minor = VERSION_MINOR; + hello_fixed_headers.version_revision = (VERSION_REVISION as u16).to_be_bytes(); + hello_fixed_headers.timestamp = (time_ticks as u64).to_be_bytes(); - // Create dictionary that contains extended HELLO fields. - let dict_start_position = packet.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); - debug_assert!(dict.write_to(&mut packet).is_ok()); + assert!(self.identity.marshal(&mut packet, IDENTITY_CIPHER_SUITE_INCLUDE_ALL, false).is_ok()); + if self.identity.cipher_suites() == IDENTITY_CIPHER_SUITE_X25519 { + // LEGACY: append an extra zero when marshaling identities containing only + // x25519 keys. This is interpreted as an empty InetAddress by old nodes. + // This isn't needed if a NIST P-521 key or other new key types are present. + // See comments before IDENTITY_CIPHER_SUITE_EC_NIST_P521 in identity.rs. + assert!(packet.append_u8(0).is_ok()); + } - // Encrypt extended fields with AES-CTR. - let mut dict_aes = self.static_secret_hello_dictionary.lock(); - dict_aes.init(&packet.as_bytes()[aes_ctr_iv_position..aes_ctr_iv_position + 16]); - dict_aes.crypt_in_place(&mut packet.as_bytes_mut()[dict_start_position..]); - drop(dict_aes); + assert!(packet.append_u64(0).is_ok()); // reserved, must be zero for legacy compatibility + assert!(packet.append_u64(node.instance_id).is_ok()); - // Append extended authentication HMAC. - debug_assert!(packet.append_bytes_fixed(&SHA384::hmac_multipart(self.static_secret_packet_hmac.as_ref(), &[u64_as_bytes(&message_id), &packet.as_bytes()[PACKET_HEADER_SIZE..]])).is_ok()); + // LEGACY: create a 16-bit encrypted field that specifies zero "moons." This is ignored now + // but causes old nodes to be able to parse this packet properly. This is not significant in + // terms of encryption or authentication and can disappear once old versions are dead. Newer + // versions ignore these bytes. + let zero_moon_count = packet.append_bytes_fixed_get_mut::<2>().unwrap(); + let mut salsa_iv = message_id.to_ne_bytes(); + salsa_iv[7] &= 0xf8; + Salsa::new(&self.static_secret.key.0[0..32], &salsa_iv, true).unwrap().crypt(&[0_u8, 0_u8], zero_moon_count); - // Set outer packet MAC. We use legacy poly1305 for HELLO for backward - // compatibility, but note that newer nodes and roots will check the full - // HMAC-SHA384 above. - let (_, mut poly) = salsa_poly_create(&self.static_secret, packet.struct_at::(0).unwrap(), packet.len()); - poly.update(packet.as_bytes_starting_at(PACKET_HEADER_SIZE).unwrap()); - packet_header.mac.copy_from_slice(&poly.finish()[0..8]); + // Size of dictionary with optional fields, currently none. For future use. + assert!(packet.append_u16(0).is_ok()); - self.static_secret.encrypt_count.fetch_add(1, Ordering::Relaxed); - self.last_send_time_ticks.store(time_ticks, Ordering::Relaxed); - self.total_bytes_sent.fetch_add(packet.len() as u64, Ordering::Relaxed); + // Add full HMAC for strong authentication with newer nodes. + assert!(packet.append_bytes_fixed(&SHA384::hmac_multipart(&self.static_secret.packet_hmac_key.0, &[u64_as_bytes(&message_id), &packet.as_bytes()[PACKET_HEADER_SIZE..]])).is_ok()); - path.as_ref().map_or_else(|| { - self.send_to_endpoint(ci, endpoint, None, None, &packet) - }, |path| { - path.log_send(time_ticks); - self.send_to_endpoint(ci, endpoint, path.local_socket(), path.local_interface(), &packet) - }) + // LEGACY: set MAC field in header to poly1305 for older nodes. + let (_, mut poly) = salsa_poly_create(&self.static_secret, packet.struct_at::(0).unwrap(), packet.len()); + poly.update(packet.as_bytes_starting_at(PACKET_HEADER_SIZE).unwrap()); + packet_header.mac.copy_from_slice(&poly.finish()[0..8]); + + self.last_send_time_ticks.store(time_ticks, Ordering::Relaxed); + self.total_bytes_sent.fetch_add(packet.len() as u64, Ordering::Relaxed); + + path.as_ref().map_or_else(|| { + self.send_to_endpoint(ci, endpoint, None, None, &packet) + }, |path| { + path.log_send(time_ticks); + self.send_to_endpoint(ci, endpoint, path.local_socket(), path.local_interface(), &packet) }) } @@ -491,16 +488,12 @@ impl Peer { fn receive_error(&self, ci: &CI, ph: &PH, node: &Node, time_ticks: i64, source_path: &Arc, forward_secrecy: bool, extended_authentication: bool, payload: &Buffer<{ PACKET_SIZE_MAX }>) { let mut cursor: usize = 0; let _ = payload.read_struct::(&mut cursor).map(|error_header| { - let in_re_packet_id = error_header.in_re_packet_id; + let in_re_message_id = u64::from_ne_bytes(error_header.in_re_message_id); let current_packet_id_counter = self.message_id_counter.load(Ordering::Relaxed); - if current_packet_id_counter.checked_sub(in_re_packet_id).map_or_else(|| { - (!in_re_packet_id).wrapping_add(current_packet_id_counter) < PACKET_RESPONSE_COUNTER_DELTA_MAX - }, |packets_ago| { - packets_ago <= PACKET_RESPONSE_COUNTER_DELTA_MAX - }) { + if current_packet_id_counter.wrapping_sub(in_re_message_id) <= PACKET_RESPONSE_COUNTER_DELTA_MAX { match error_header.in_re_verb { _ => { - ph.handle_error(self, source_path, forward_secrecy, extended_authentication, error_header.in_re_verb, in_re_packet_id, error_header.error_code, payload, &mut cursor); + ph.handle_error(self, source_path, forward_secrecy, extended_authentication, error_header.in_re_verb, in_re_message_id, error_header.error_code, payload, &mut cursor); } } } @@ -511,20 +504,16 @@ impl Peer { fn receive_ok(&self, ci: &CI, ph: &PH, node: &Node, time_ticks: i64, source_path: &Arc, forward_secrecy: bool, extended_authentication: bool, payload: &Buffer<{ PACKET_SIZE_MAX }>) { let mut cursor: usize = 0; let _ = payload.read_struct::(&mut cursor).map(|ok_header| { - let in_re_packet_id = ok_header.in_re_packet_id; + let in_re_message_id = u64::from_ne_bytes(ok_header.in_re_message_id); let current_packet_id_counter = self.message_id_counter.load(Ordering::Relaxed); - if current_packet_id_counter.checked_sub(in_re_packet_id).map_or_else(|| { - (!in_re_packet_id).wrapping_add(current_packet_id_counter) < PACKET_RESPONSE_COUNTER_DELTA_MAX - }, |packets_ago| { - packets_ago <= PACKET_RESPONSE_COUNTER_DELTA_MAX - }) { + if current_packet_id_counter.wrapping_sub(in_re_message_id) <= PACKET_RESPONSE_COUNTER_DELTA_MAX { match ok_header.in_re_verb { VERB_VL1_HELLO => { } VERB_VL1_WHOIS => { } _ => { - ph.handle_ok(self, source_path, forward_secrecy, extended_authentication, ok_header.in_re_verb, in_re_packet_id, payload, &mut cursor); + ph.handle_ok(self, source_path, forward_secrecy, extended_authentication, ok_header.in_re_verb, in_re_message_id, payload, &mut cursor); } } } diff --git a/zerotier-network-hypervisor/src/vl1/protocol.rs b/zerotier-network-hypervisor/src/vl1/protocol.rs index 82d01be30..6da39813d 100644 --- a/zerotier-network-hypervisor/src/vl1/protocol.rs +++ b/zerotier-network-hypervisor/src/vl1/protocol.rs @@ -22,9 +22,6 @@ 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 const HELLO_DICT_KEY_INSTANCE_ID: &'static str = "I"; -pub const HELLO_DICT_KEY_CLOCK: &'static str = "C"; - /// Default maximum payload size for UDP transport. /// /// This is small enough to traverse numerous weird networks including PPPoE and Google Cloud's @@ -32,10 +29,7 @@ pub const HELLO_DICT_KEY_CLOCK: &'static str = "C"; /// two fragments. pub const UDP_DEFAULT_MTU: usize = 1432; -/// KBKDF usage label indicating a key used to encrypt the dictionary inside HELLO. -pub const KBKDF_KEY_USAGE_LABEL_HELLO_DICTIONARY_ENCRYPT: u8 = b'H'; - -/// KBKDF usage label indicating a key used to HMAC packets, which is currently only used for HELLO. +/// KBKDF usage label indicating a key used to HMAC packets for extended authentication. pub const KBKDF_KEY_USAGE_LABEL_PACKET_HMAC: u8 = b'M'; /// KBKDF usage label for the first AES-GMAC-SIV key. @@ -62,6 +56,9 @@ pub const EPHEMERAL_SECRET_REJECT_AFTER_USES: u32 = 2147483648; // NIST/FIPS sec /// Length of an address in bytes. pub const ADDRESS_SIZE: usize = 5; +/// Length of an address in string format. +pub const ADDRESS_SIZE_STRING: usize = 10; + /// Prefix indicating reserved addresses (that can't actually be addresses). pub const ADDRESS_RESERVED_PREFIX: u8 = 0xff; @@ -174,6 +171,14 @@ pub const WHOIS_RETRY_MAX: u16 = 3; /// Maximum number of packets to queue up behind a WHOIS. pub const WHOIS_MAX_WAITING_PACKETS: usize = 64; +/// Proof of work difficulty (threshold) for old v0 identities. +pub const IDENTITY_V0_POW_THRESHOLD: u8 = 17; + +/// Proof of work difficulty (threshold) for new v1 identities. +/// This is lower than the V0 threshold, causing the V0 part of V1 identities to +/// verify on old nodes. +pub const IDENTITY_V1_POW_THRESHOLD: u8 = 5; + #[derive(Clone, Copy)] #[repr(u8)] pub enum EphemeralKeyAgreementAlgorithm { @@ -277,7 +282,7 @@ impl PacketHeader { #[inline(always)] pub fn aes_gmac_siv_tag(&self) -> [u8; 16] { let mut id = unsafe { MaybeUninit::<[u8; 16]>::uninit().assume_init() }; - id[0..8].copy_from_slice(self.id_bytes()); + id[0..8].copy_from_slice(&self.id); id[8..16].copy_from_slice(&self.mac); id } @@ -327,12 +332,11 @@ impl FragmentHeader { pub(crate) mod message_component_structs { use crate::util::buffer::RawObject; - use crate::vl1::protocol::PacketID; #[repr(packed)] pub struct OkHeader { pub in_re_verb: u8, - pub in_re_packet_id: PacketID, + pub in_re_message_id: [u8; 8], } unsafe impl RawObject for OkHeader {} @@ -340,7 +344,7 @@ pub(crate) mod message_component_structs { #[repr(packed)] pub struct ErrorHeader { pub in_re_verb: u8, - pub in_re_packet_id: PacketID, + pub in_re_message_id: [u8; 8], pub error_code: u8, } @@ -352,19 +356,19 @@ pub(crate) mod message_component_structs { pub version_proto: u8, pub version_major: u8, pub version_minor: u8, - pub version_revision: u16, - pub timestamp: u64, + pub version_revision: [u8; 2], // u16 + pub timestamp: [u8; 8], // u64 } unsafe impl RawObject for HelloFixedHeaderFields {} #[repr(packed)] pub struct OkHelloFixedHeaderFields { - pub timestamp_echo: u64, + pub timestamp_echo: [u8; 8], // u64 pub version_proto: u8, pub version_major: u8, pub version_minor: u8, - pub version_revision: u16, + pub version_revision: [u8; 2], // u16 } unsafe impl RawObject for OkHelloFixedHeaderFields {} @@ -388,14 +392,5 @@ mod tests { (*foo.as_mut_ptr().cast::()).src[0] = 0xff; assert_eq!((*foo.as_ptr().cast::()).fragment_indicator, 0xff); } - - let bar = PacketHeader{ - id: [1_u8, 2, 3, 4, 5, 6, 7, 8], - dest: [0_u8; 5], - src: [0_u8; 5], - flags_cipher_hops: 0, - mac: [0_u8; 8], - }; - assert_eq!(bar.id_bytes().clone(), [1_u8, 2, 3, 4, 5, 6, 7, 8]); } } diff --git a/zerotier-network-hypervisor/src/vl1/rootset.rs b/zerotier-network-hypervisor/src/vl1/rootset.rs index 23215e647..f65b04f51 100644 --- a/zerotier-network-hypervisor/src/vl1/rootset.rs +++ b/zerotier-network-hypervisor/src/vl1/rootset.rs @@ -17,6 +17,7 @@ use zerotier_core_crypto::secret::Secret; use zerotier_core_crypto::hash::SHA384; use std::cmp::Ordering; +use crate::vl1::identity::IDENTITY_CIPHER_SUITE_INCLUDE_ALL; /// Old "planet" type with Ed25519 authenticated updates from ZeroTier v1. const ROOT_SET_TYPE_LEGACY_PLANET: u8 = 1; @@ -174,7 +175,7 @@ impl RootSet { buf.append_varint(self.roots.len() as u64)?; for root in self.roots.iter() { - root.identity.marshal(buf, false)?; + root.identity.marshal(buf, IDENTITY_CIPHER_SUITE_INCLUDE_ALL, false)?; if (self.type_ == ROOT_SET_TYPE_LEGACY_PLANET || self.type_ == ROOT_SET_TYPE_LEGACY_MOON) && root.endpoints.len() > 127 { return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "invalid legacy type root set")); } diff --git a/zerotier-network-hypervisor/src/vl1/symmetricsecret.rs b/zerotier-network-hypervisor/src/vl1/symmetricsecret.rs index e42344020..9e91da95e 100644 --- a/zerotier-network-hypervisor/src/vl1/symmetricsecret.rs +++ b/zerotier-network-hypervisor/src/vl1/symmetricsecret.rs @@ -39,9 +39,6 @@ pub struct SymmetricSecret { /// A key used as input to the ephemeral key ratcheting mechanism. pub next_ephemeral_ratchet_key: Secret, - /// A key used to encrypt the secret portion of a HELLO packet. - pub hello_dictionary_keyed_cipher: Mutex, - /// A pool of reusable keyed and initialized AES-GMAC-SIV ciphers. pub aes_gmac_siv: Pool, } @@ -58,7 +55,6 @@ impl SymmetricSecret { pub fn new(base_key: Secret) -> SymmetricSecret { let usage_packet_hmac = zt_kbkdf_hmac_sha384(&base_key.0, KBKDF_KEY_USAGE_LABEL_PACKET_HMAC, 0, 0); let usage_ephemeral_ratchet = zt_kbkdf_hmac_sha384(&base_key.0, KBKDF_KEY_USAGE_LABEL_EPHEMERAL_RATCHET, 0, 0); - let usage_hello_dictionary_key = zt_kbkdf_hmac_sha384(&base_key.0, KBKDF_KEY_USAGE_LABEL_HELLO_DICTIONARY_ENCRYPT, 0, 0); let aes_factory = AesGmacSivPoolFactory( zt_kbkdf_hmac_sha384(&base_key.0, KBKDF_KEY_USAGE_LABEL_AES_GMAC_SIV_K0, 0, 0), zt_kbkdf_hmac_sha384(&base_key.0, KBKDF_KEY_USAGE_LABEL_AES_GMAC_SIV_K1, 0, 0)); @@ -66,7 +62,6 @@ impl SymmetricSecret { key: base_key, packet_hmac_key: usage_packet_hmac, next_ephemeral_ratchet_key: usage_ephemeral_ratchet, - hello_dictionary_keyed_cipher: Mutex::new(AesCtr::new(&usage_hello_dictionary_key.0[0..32])), aes_gmac_siv: Pool::new(2, aes_factory), } } diff --git a/zerotier-network-hypervisor/src/vl2/switch.rs b/zerotier-network-hypervisor/src/vl2/switch.rs index a7eea73bc..f2d29fe84 100644 --- a/zerotier-network-hypervisor/src/vl2/switch.rs +++ b/zerotier-network-hypervisor/src/vl2/switch.rs @@ -11,7 +11,7 @@ use std::sync::Arc; use crate::util::buffer::Buffer; use crate::vl1::node::VL1PacketHandler; use crate::vl1::{Peer, Path}; -use crate::vl1::protocol::{PACKET_SIZE_MAX, PacketID}; +use crate::vl1::protocol::*; pub trait SwitchInterface { } @@ -24,11 +24,11 @@ impl VL1PacketHandler for Switch { false } - fn handle_error(&self, peer: &Peer, source_path: &Arc, forward_secrecy: bool, extended_authentication: bool, in_re_verb: u8, in_re_packet_id: PacketID, error_code: u8, payload: &Buffer<{ PACKET_SIZE_MAX }>, cursor: &mut usize) -> bool { + fn handle_error(&self, peer: &Peer, source_path: &Arc, forward_secrecy: bool, extended_authentication: bool, in_re_verb: u8, in_re_message_id: u64, error_code: u8, payload: &Buffer<{ PACKET_SIZE_MAX }>, cursor: &mut usize) -> bool { false } - fn handle_ok(&self, peer: &Peer, source_path: &Arc, forward_secrecy: bool, extended_authentication: bool, in_re_verb: u8, in_re_packet_id: PacketID, payload: &Buffer<{ PACKET_SIZE_MAX }>, cursor: &mut usize) -> bool { + fn handle_ok(&self, peer: &Peer, source_path: &Arc, forward_secrecy: bool, extended_authentication: bool, in_re_verb: u8, in_re_message_id: u64, payload: &Buffer<{ PACKET_SIZE_MAX }>, cursor: &mut usize) -> bool { false } } diff --git a/zerotier-system-service/Cargo.lock b/zerotier-system-service/Cargo.lock index afc629700..27c05a084 100644 --- a/zerotier-system-service/Cargo.lock +++ b/zerotier-system-service/Cargo.lock @@ -2219,6 +2219,7 @@ version = "2.0.0" dependencies = [ "base64", "dashmap", + "lazy_static", "libc", "lz4_flex", "parking_lot",