Lots of Rust stuff.

This commit is contained in:
Adam Ierymenko 2021-08-04 12:31:46 -04:00
parent 617b7c86b6
commit a8da84c055
No known key found for this signature in database
GPG key ID: C8877CF2D7A5D7F3
23 changed files with 613 additions and 191 deletions

View file

@ -7,6 +7,13 @@ edition = "2018"
opt-level = 3 opt-level = 3
lto = true lto = true
codegen-units = 1 codegen-units = 1
panic = 'abort'
[profile.release]
opt-level = 3
lto = true
codegen-units = 1
panic = 'abort'
[target."cfg(not(any(target_os = \"macos\", target_os = \"ios\")))".dependencies] [target."cfg(not(any(target_os = \"macos\", target_os = \"ios\")))".dependencies]
gcrypt = "^0" gcrypt = "^0"

View file

@ -190,12 +190,6 @@ dependencies = [
"cfg-if 1.0.0", "cfg-if 1.0.0",
] ]
[[package]]
name = "itoa"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.98" version = "0.2.98"
@ -369,12 +363,6 @@ dependencies = [
"bitflags", "bitflags",
] ]
[[package]]
name = "ryu"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
[[package]] [[package]]
name = "scopeguard" name = "scopeguard"
version = "1.1.0" version = "1.1.0"
@ -386,31 +374,6 @@ name = "serde"
version = "1.0.126" version = "1.0.126"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec7505abeacaec74ae4778d9d9328fe5a5d04253220a85c4ee022239fc996d03" checksum = "ec7505abeacaec74ae4778d9d9328fe5a5d04253220a85c4ee022239fc996d03"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.126"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "963a7dbc9895aeac7ac90e74f34a5d5261828f79df35cbed41e10189d3804d43"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.66"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "336b10da19a12ad094b59d870ebde26a45402e5b470add4b5fd03c5048a32127"
dependencies = [
"itoa",
"ryu",
"serde",
]
[[package]] [[package]]
name = "sha2" name = "sha2"
@ -588,8 +551,6 @@ dependencies = [
"lz4_flex", "lz4_flex",
"parking_lot", "parking_lot",
"rand_core", "rand_core",
"serde",
"serde_json",
"urlencoding", "urlencoding",
"winapi", "winapi",
"x25519-dalek", "x25519-dalek",

View file

@ -5,7 +5,7 @@ edition = "2018"
[profile.release] [profile.release]
lto = true lto = true
opt-level = 'z' opt-level = 3
codegen-units = 1 codegen-units = 1
panic = 'abort' panic = 'abort'
@ -20,8 +20,6 @@ urlencoding = "^2"
lz4_flex = { version = "^0", features = ["safe-encode", "safe-decode", "checked-decode"] } lz4_flex = { version = "^0", features = ["safe-encode", "safe-decode", "checked-decode"] }
dashmap = "^4" dashmap = "^4"
parking_lot = "^0" parking_lot = "^0"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
[target."cfg(not(windows))".dependencies] [target."cfg(not(windows))".dependencies]
libc = "^0" libc = "^0"

View file

@ -15,7 +15,6 @@ impl SHA512 {
h h
} }
/// Compute HMAC-SHA512(key, msg)
#[inline(always)] #[inline(always)]
pub fn hmac(key: &[u8], msg: &[u8]) -> [u8; SHA512_HASH_SIZE] { pub fn hmac(key: &[u8], msg: &[u8]) -> [u8; SHA512_HASH_SIZE] {
let mut m = gcrypt::mac::Mac::new(gcrypt::mac::Algorithm::HmacSha512).unwrap(); let mut m = gcrypt::mac::Mac::new(gcrypt::mac::Algorithm::HmacSha512).unwrap();
@ -47,8 +46,6 @@ impl SHA512 {
self.0.get_only_digest().unwrap().try_into().unwrap() self.0.get_only_digest().unwrap().try_into().unwrap()
} }
/// Return a reference to an internally stored result.
/// This saves a copy, but the returned result is only valid so long as no other methods are called.
#[inline(always)] #[inline(always)]
pub fn finish_get_ref(&mut self) -> &[u8] { pub fn finish_get_ref(&mut self) -> &[u8] {
self.0.finish(); self.0.finish();
@ -79,7 +76,6 @@ impl SHA384 {
h h
} }
/// Compute HMAC-SHA384(key, msg)
#[inline(always)] #[inline(always)]
pub fn hmac(key: &[u8], msg: &[u8]) -> [u8; SHA384_HASH_SIZE] { pub fn hmac(key: &[u8], msg: &[u8]) -> [u8; SHA384_HASH_SIZE] {
let mut m = gcrypt::mac::Mac::new(gcrypt::mac::Algorithm::HmacSha384).unwrap(); let mut m = gcrypt::mac::Mac::new(gcrypt::mac::Algorithm::HmacSha384).unwrap();
@ -111,8 +107,6 @@ impl SHA384 {
self.0.get_only_digest().unwrap().try_into().unwrap() self.0.get_only_digest().unwrap().try_into().unwrap()
} }
/// Return a reference to an internally stored result.
/// This saves a copy, but the returned result is only valid so long as no other methods are called.
#[inline(always)] #[inline(always)]
pub fn finish_get_ref(&mut self) -> &[u8] { pub fn finish_get_ref(&mut self) -> &[u8] {
self.0.finish(); self.0.finish();

View file

@ -5,8 +5,10 @@ use crate::crypto::secret::Secret;
/// This is a fixed cost key derivation function used to derive sub-keys from a single original /// This is a fixed cost key derivation function used to derive sub-keys from a single original
/// shared secret for different uses, such as the K0/K1 in AES-GMAC-SIV. /// shared secret for different uses, such as the K0/K1 in AES-GMAC-SIV.
/// Key must be 384 bits in length. /// Key must be 384 bits in length.
#[inline(always)]
pub fn zt_kbkdf_hmac_sha384(key: &[u8], label: u8, context: u8, iter: u32) -> Secret<{ SHA384_HASH_SIZE }> { pub fn zt_kbkdf_hmac_sha384(key: &[u8], label: u8, context: u8, iter: u32) -> Secret<{ SHA384_HASH_SIZE }> {
debug_assert!(key.len() == SHA384_HASH_SIZE); debug_assert_eq!(key.len(), SHA384_HASH_SIZE);
// HMAC'd message is: preface | iteration[4], preface[2], label, 0x00, context, hash size[4] // HMAC'd message is: preface | iteration[4], preface[2], label, 0x00, context, hash size[4]
// See: https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-108.pdf // See: https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-108.pdf
Secret(SHA384::hmac(key, &[(iter >> 24) as u8, (iter >> 16) as u8, (iter >> 8) as u8, iter as u8, b'Z', b'T', label, 0, context, 0, 0, 0x01, 0x80])) Secret(SHA384::hmac(key, &[(iter >> 24) as u8, (iter >> 16) as u8, (iter >> 8) as u8, iter as u8, b'Z', b'T', label, 0, context, 0, 0, 0x01, 0x80]))

View file

@ -9,8 +9,3 @@ pub mod random;
pub mod secret; pub mod secret;
pub use aes_gmac_siv; pub use aes_gmac_siv;
pub fn init() {
// We always run gcrypt in "FIPS mode," but it doesn't count as fully compliant unless it's a FIPS-certified library.
let _ = gcrypt::init_fips_mode(|_| -> Result<(), std::convert::Infallible> { Ok(()) });
}

View file

@ -39,3 +39,22 @@ impl RngCore for SecureRandom {
} }
impl CryptoRng for SecureRandom {} impl CryptoRng for SecureRandom {}
#[inline(always)]
pub(crate) fn next_u32_secure() -> u32 {
let mut tmp = 0_u32;
randomize(Level::Strong, unsafe { &mut *(&mut tmp as *mut u32).cast::<[u8; 4]>() });
tmp
}
#[inline(always)]
pub(crate) fn next_u64_secure() -> u64 {
let mut tmp = 0_u64;
randomize(Level::Strong, unsafe { &mut *(&mut tmp as *mut u64).cast::<[u8; 8]>() });
tmp
}
#[inline(always)]
pub(crate) fn fill_bytes_secure(dest: &mut [u8]) {
randomize(Level::Strong, dest);
}

View file

@ -3,7 +3,7 @@ use std::ptr::write_volatile;
/// Container for secrets that clears them on drop. /// Container for secrets that clears them on drop.
#[derive(Clone, PartialEq, Eq)] #[derive(Clone, PartialEq, Eq)]
pub struct Secret<const L: usize>(pub [u8; L]); pub struct Secret<const L: usize>(pub(crate) [u8; L]);
impl<const L: usize> Secret<L> { impl<const L: usize> Secret<L> {
#[inline(always)] #[inline(always)]

View file

@ -10,8 +10,9 @@ impl Display for InvalidFormatError {
} }
impl Debug for InvalidFormatError { impl Debug for InvalidFormatError {
#[inline(always)]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("InvalidFormatError") <Self as Display>::fmt(self, f)
} }
} }
@ -26,8 +27,9 @@ impl Display for InvalidParameterError {
} }
impl Debug for InvalidParameterError { impl Debug for InvalidParameterError {
#[inline(always)]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "InvalidParameterError: {}", self.0) <Self as Display>::fmt(self, f)
} }
} }

View file

@ -5,6 +5,6 @@ pub mod vl1;
pub mod vl2; pub mod vl2;
pub const VERSION_MAJOR: u8 = 1; pub const VERSION_MAJOR: u8 = 1;
pub const VERSION_MINOR: u8 = 9; pub const VERSION_MINOR: u8 = 99;
pub const VERSION_REVISION: u8 = 1; pub const VERSION_REVISION: u8 = 1;
pub const VERSION_STR: &'static str = "1.9.1"; pub const VERSION_STR: &'static str = "1.99.1";

View file

@ -36,6 +36,7 @@ impl<O: Reusable> Pooled<O> {
pub unsafe fn into_raw(self) -> *mut O { pub unsafe fn into_raw(self) -> *mut O {
debug_assert!(!self.0.is_null()); debug_assert!(!self.0.is_null());
debug_assert_eq!(self.0.cast::<u8>(), (&mut (*self.0).obj as *mut O).cast::<u8>()); debug_assert_eq!(self.0.cast::<u8>(), (&mut (*self.0).obj as *mut O).cast::<u8>());
std::mem::forget(self);
self.0.cast() self.0.cast()
} }

View file

@ -1,9 +1,9 @@
use std::str::FromStr;
use std::hash::{Hash, Hasher}; use std::hash::{Hash, Hasher};
use std::str::FromStr;
use crate::vl1::constants::{ADDRESS_RESERVED_PREFIX, ADDRESS_SIZE};
use crate::error::InvalidFormatError; use crate::error::InvalidFormatError;
use crate::util::hex::HEX_CHARS; use crate::util::hex::HEX_CHARS;
use crate::vl1::constants::{ADDRESS_RESERVED_PREFIX, ADDRESS_SIZE};
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct Address(u64); pub struct Address(u64);

View file

@ -50,31 +50,42 @@ impl<const L: usize> Buffer<L> {
} }
} }
/// Get a slice containing the entire buffer in raw form including the header.
#[inline(always)] #[inline(always)]
pub fn as_bytes(&self) -> &[u8] { pub fn as_bytes(&self) -> &[u8] {
&self.1[0..self.0] &self.1[0..self.0]
} }
/// Get a slice containing the entire buffer in raw form including the header.
#[inline(always)] #[inline(always)]
pub fn as_bytes_mut(&mut self) -> &mut [u8] { pub fn as_bytes_mut(&mut self) -> &mut [u8] {
&mut self.1[0..self.0] &mut self.1[0..self.0]
} }
/// Erase contents and zero size. /// Get all bytes after a given position.
#[inline(always)]
pub fn as_bytes_after(&self, start: usize) -> std::io::Result<&[u8]> {
if start <= self.0 {
Ok(&self.1[start..])
} else {
Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG))
}
}
#[inline(always)] #[inline(always)]
pub fn clear(&mut self) { pub fn clear(&mut self) {
self.0 = 0; self.0 = 0;
self.1.fill(0); self.1.fill(0);
} }
/// Get the length of this buffer (including header, if any).
#[inline(always)] #[inline(always)]
pub fn len(&self) -> usize { pub fn len(&self) -> usize {
self.0 self.0
} }
#[inline(always)]
pub fn is_empty(&self) -> bool {
self.0 == 0
}
/// Append a packed structure and call a function to initialize it in place. /// Append a packed structure and call a function to initialize it in place.
/// Anything not initialized will be zero. /// Anything not initialized will be zero.
#[inline(always)] #[inline(always)]
@ -87,7 +98,7 @@ impl<const L: usize> Buffer<L> {
Ok(initializer(&mut *self.1.as_mut_ptr().cast::<u8>().offset(ptr as isize).cast::<T>())) Ok(initializer(&mut *self.1.as_mut_ptr().cast::<u8>().offset(ptr as isize).cast::<T>()))
} }
} else { } else {
std::io::Result::Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG)) Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG))
} }
} }
@ -104,7 +115,7 @@ impl<const L: usize> Buffer<L> {
Ok(initializer(&mut *self.1.as_mut_ptr().cast::<u8>().offset(ptr as isize).cast::<[u8; N]>())) Ok(initializer(&mut *self.1.as_mut_ptr().cast::<u8>().offset(ptr as isize).cast::<[u8; N]>()))
} }
} else { } else {
std::io::Result::Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG)) Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG))
} }
} }
@ -118,7 +129,7 @@ impl<const L: usize> Buffer<L> {
self.0 = end; self.0 = end;
Ok(initializer(&mut self.1[ptr..end])) Ok(initializer(&mut self.1[ptr..end]))
} else { } else {
std::io::Result::Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG)) Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG))
} }
} }
@ -133,7 +144,7 @@ impl<const L: usize> Buffer<L> {
self.1[ptr..end].copy_from_slice(buf); self.1[ptr..end].copy_from_slice(buf);
Ok(()) Ok(())
} else { } else {
std::io::Result::Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG)) Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG))
} }
} }
@ -148,7 +159,7 @@ impl<const L: usize> Buffer<L> {
self.1[ptr..end].copy_from_slice(buf); self.1[ptr..end].copy_from_slice(buf);
Ok(()) Ok(())
} else { } else {
std::io::Result::Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG)) Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG))
} }
} }
@ -161,7 +172,7 @@ impl<const L: usize> Buffer<L> {
self.1[ptr] = i; self.1[ptr] = i;
Ok(()) Ok(())
} else { } else {
std::io::Result::Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG)) Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG))
} }
} }
@ -175,7 +186,7 @@ impl<const L: usize> Buffer<L> {
crate::util::integer_store_be_u16(i, &mut self.1[ptr..end]); crate::util::integer_store_be_u16(i, &mut self.1[ptr..end]);
Ok(()) Ok(())
} else { } else {
std::io::Result::Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG)) Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG))
} }
} }
@ -189,7 +200,7 @@ impl<const L: usize> Buffer<L> {
crate::util::integer_store_be_u32(i, &mut self.1[ptr..end]); crate::util::integer_store_be_u32(i, &mut self.1[ptr..end]);
Ok(()) Ok(())
} else { } else {
std::io::Result::Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG)) Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG))
} }
} }
@ -203,7 +214,7 @@ impl<const L: usize> Buffer<L> {
crate::util::integer_store_be_u64(i, &mut self.1[ptr..end]); crate::util::integer_store_be_u64(i, &mut self.1[ptr..end]);
Ok(()) Ok(())
} else { } else {
std::io::Result::Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG)) Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG))
} }
} }
@ -215,7 +226,7 @@ impl<const L: usize> Buffer<L> {
Ok(&*self.1.as_ptr().cast::<u8>().offset(ptr as isize).cast::<T>()) Ok(&*self.1.as_ptr().cast::<u8>().offset(ptr as isize).cast::<T>())
} }
} else { } else {
std::io::Result::Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG)) Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG))
} }
} }
@ -227,7 +238,17 @@ impl<const L: usize> Buffer<L> {
Ok(&mut *self.1.as_mut_ptr().cast::<u8>().offset(ptr as isize).cast::<T>()) Ok(&mut *self.1.as_mut_ptr().cast::<u8>().offset(ptr as isize).cast::<T>())
} }
} else { } else {
std::io::Result::Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG)) Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG))
}
}
/// Get a byte at a fixed position.
#[inline(always)]
pub fn u8_at(&self, ptr: usize) -> std::io::Result<u8> {
if ptr < self.0 {
Ok(self.1[ptr])
} else {
Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG))
} }
} }
@ -243,7 +264,7 @@ impl<const L: usize> Buffer<L> {
Ok(&*self.1.as_ptr().cast::<u8>().offset(ptr as isize).cast::<T>()) Ok(&*self.1.as_ptr().cast::<u8>().offset(ptr as isize).cast::<T>())
} }
} else { } else {
std::io::Result::Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG)) Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG))
} }
} }
@ -260,7 +281,7 @@ impl<const L: usize> Buffer<L> {
Ok(&*self.1.as_ptr().cast::<u8>().offset(ptr as isize).cast::<[u8; S]>()) Ok(&*self.1.as_ptr().cast::<u8>().offset(ptr as isize).cast::<[u8; S]>())
} }
} else { } else {
std::io::Result::Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG)) Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG))
} }
} }
@ -274,7 +295,7 @@ impl<const L: usize> Buffer<L> {
*cursor = end; *cursor = end;
Ok(&self.1[ptr..end]) Ok(&self.1[ptr..end])
} else { } else {
std::io::Result::Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG)) Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG))
} }
} }
@ -287,7 +308,7 @@ impl<const L: usize> Buffer<L> {
*cursor = ptr + 1; *cursor = ptr + 1;
Ok(self.1[ptr]) Ok(self.1[ptr])
} else { } else {
std::io::Result::Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG)) Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG))
} }
} }
@ -301,7 +322,7 @@ impl<const L: usize> Buffer<L> {
*cursor = end; *cursor = end;
Ok(crate::util::integer_load_be_u16(&self.1[ptr..end])) Ok(crate::util::integer_load_be_u16(&self.1[ptr..end]))
} else { } else {
std::io::Result::Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG)) Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG))
} }
} }
@ -315,7 +336,7 @@ impl<const L: usize> Buffer<L> {
*cursor = end; *cursor = end;
Ok(crate::util::integer_load_be_u32(&self.1[ptr..end])) Ok(crate::util::integer_load_be_u32(&self.1[ptr..end]))
} else { } else {
std::io::Result::Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG)) Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG))
} }
} }
@ -329,7 +350,7 @@ impl<const L: usize> Buffer<L> {
*cursor = end; *cursor = end;
Ok(crate::util::integer_load_be_u64(&self.1[ptr..end])) Ok(crate::util::integer_load_be_u64(&self.1[ptr..end]))
} else { } else {
std::io::Result::Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG)) Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG))
} }
} }
} }
@ -344,7 +365,7 @@ impl<const L: usize> Write for Buffer<L> {
self.1[ptr..end].copy_from_slice(buf); self.1[ptr..end].copy_from_slice(buf);
Ok(buf.len()) Ok(buf.len())
} else { } else {
std::io::Result::Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG)) Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG))
} }
} }

View file

@ -4,6 +4,18 @@ pub const ADDRESS_SIZE: usize = 5;
/// Prefix indicating reserved addresses (that can't actually be addresses). /// Prefix indicating reserved addresses (that can't actually be addresses).
pub const ADDRESS_RESERVED_PREFIX: u8 = 0xff; pub const ADDRESS_RESERVED_PREFIX: u8 = 0xff;
/// 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.
pub const KBKDF_KEY_USAGE_LABEL_PACKET_HMAC: u8 = b'M';
/// KBKDF usage label for the first AES-GMAC-SIV key.
pub const KBKDF_KEY_USAGE_LABEL_AES_GMAC_SIV_K0: u8 = b'0';
/// KBKDF usage label for the second AES-GMAC-SIV key.
pub const KBKDF_KEY_USAGE_LABEL_AES_GMAC_SIV_K1: u8 = b'1';
/// Size of packet header that lies outside the encryption envelope. /// Size of packet header that lies outside the encryption envelope.
pub const PACKET_HEADER_SIZE: usize = 27; pub const PACKET_HEADER_SIZE: usize = 27;
@ -22,15 +34,27 @@ pub const PACKET_SIZE_MIN: usize = PACKET_HEADER_SIZE + 1;
/// Maximum size of an entire packet. /// Maximum size of an entire packet.
pub const PACKET_SIZE_MAX: usize = PACKET_HEADER_SIZE + PACKET_PAYLOAD_SIZE_MAX; pub const PACKET_SIZE_MAX: usize = PACKET_HEADER_SIZE + PACKET_PAYLOAD_SIZE_MAX;
/// Index of packet verb after header.
pub const PACKET_VERB_INDEX: usize = 27;
/// Index of destination in both fragment and full packet headers. /// Index of destination in both fragment and full packet headers.
pub const PACKET_DESTINATION_INDEX: usize = 8; pub const PACKET_DESTINATION_INDEX: usize = 8;
/// Maximum number of paths to a remote peer.
pub const PEER_MAX_PATHS: usize = 16;
/// Mask to select cipher from header flags field. /// Mask to select cipher from header flags field.
pub const HEADER_FLAGS_FIELD_MASK_CIPHER: u8 = 0x30; pub const HEADER_FLAGS_FIELD_MASK_CIPHER: u8 = 0x30;
/// Mask to select packet hops from header flags field. /// Mask to select packet hops from header flags field.
pub const HEADER_FLAGS_FIELD_MASK_HOPS: u8 = 0x07; pub const HEADER_FLAGS_FIELD_MASK_HOPS: u8 = 0x07;
/// Mask to select packet hops from header flags field.
pub const HEADER_FLAGS_FIELD_MASK_HIDE_HOPS: u8 = 0xf8;
/// Index of hops/flags field
pub const HEADER_FLAGS_FIELD_INDEX: usize = 18;
/// Packet is not encrypted but contains a Poly1305 MAC of the plaintext. /// Packet is not encrypted but contains a Poly1305 MAC of the plaintext.
/// Poly1305 is initialized with Salsa20/12 in the same manner as SALSA2012_POLY1305. /// Poly1305 is initialized with Salsa20/12 in the same manner as SALSA2012_POLY1305.
pub const CIPHER_NOCRYPT_POLY1305: u8 = 0x00; pub const CIPHER_NOCRYPT_POLY1305: u8 = 0x00;
@ -51,6 +75,9 @@ pub const HEADER_FLAG_FRAGMENTED: u8 = 0x40;
/// Minimum size of a fragment. /// Minimum size of a fragment.
pub const FRAGMENT_SIZE_MIN: usize = 16; pub const FRAGMENT_SIZE_MIN: usize = 16;
/// Size of fragment header after which data begins.
pub const FRAGMENT_HEADER_SIZE: usize = 16;
/// Maximum allowed number of fragments. /// Maximum allowed number of fragments.
pub const FRAGMENT_COUNT_MAX: usize = 16; pub const FRAGMENT_COUNT_MAX: usize = 16;
@ -63,9 +90,15 @@ pub const FRAGMENT_INDICATOR: u8 = 0xff;
/// Maximum number of inbound fragments to handle at once per path. /// Maximum number of inbound fragments to handle at once per path.
pub const FRAGMENT_MAX_PER_PATH: usize = 64; pub const FRAGMENT_MAX_PER_PATH: usize = 64;
/// Time after which an incomplete fragmented packet expires.
pub const FRAGMENT_EXPIRATION: i64 = 1500;
/// Verb (inner) flag indicating that the packet's payload (after the verb) is LZ4 compressed. /// Verb (inner) flag indicating that the packet's payload (after the verb) is LZ4 compressed.
pub const VERB_FLAG_COMPRESSED: u8 = 0x80; pub const VERB_FLAG_COMPRESSED: u8 = 0x80;
/// Mask to get only the verb from the verb + verb flags byte.
pub const VERB_MASK: u8 = 0x1f;
/// Maximum number of packet hops allowed by the protocol. /// Maximum number of packet hops allowed by the protocol.
pub const PROTOCOL_MAX_HOPS: usize = 7; pub const PROTOCOL_MAX_HOPS: usize = 7;

View file

@ -66,6 +66,11 @@ impl Dictionary {
self.0.len() self.0.len()
} }
#[inline(always)]
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn get_str(&self, k: &str) -> Option<&str> { pub fn get_str(&self, k: &str) -> Option<&str> {
self.0.get(k).map_or(None, |v| std::str::from_utf8(v.as_slice()).map_or(None, |s| Some(s))) self.0.get(k).map_or(None, |v| std::str::from_utf8(v.as_slice()).map_or(None, |s| Some(s)))
} }

View file

@ -17,16 +17,16 @@ const TYPE_WEBRTC: u8 = 9;
#[repr(u8)] #[repr(u8)]
pub enum Type { pub enum Type {
Nil = 0, Nil = TYPE_NIL,
ZeroTier = 1, ZeroTier = TYPE_ZEROTIER,
Ethernet = 2, Ethernet = TYPE_ETHERNET,
WifiDirect = 3, WifiDirect = TYPE_WIFIDIRECT,
Bluetooth = 4, Bluetooth = TYPE_BLUETOOTH,
Ip = 5, Ip = TYPE_IP,
IpUdp = 6, IpUdp = TYPE_IPUDP,
IpTcp = 7, IpTcp = TYPE_IPTCP,
Http = 8, Http = TYPE_HTTP,
WebRTC = 9, WebRTC = TYPE_WEBRTC,
} }
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)] #[derive(Clone, PartialEq, Eq, PartialOrd, Ord)]
@ -165,39 +165,39 @@ impl Hash for Endpoint {
match self { match self {
Endpoint::Nil => { Endpoint::Nil => {
state.write_u8(Type::Nil as u8); state.write_u8(Type::Nil as u8);
}, }
Endpoint::ZeroTier(a) => { Endpoint::ZeroTier(a) => {
state.write_u8(Type::ZeroTier as u8); state.write_u8(Type::ZeroTier as u8);
state.write_u64(a.to_u64()) state.write_u64(a.to_u64())
}, }
Endpoint::Ethernet(m) => { Endpoint::Ethernet(m) => {
state.write_u8(Type::Ethernet as u8); state.write_u8(Type::Ethernet as u8);
state.write_u64(m.to_u64()) state.write_u64(m.to_u64())
}, }
Endpoint::WifiDirect(m) => { Endpoint::WifiDirect(m) => {
state.write_u8(Type::WifiDirect as u8); state.write_u8(Type::WifiDirect as u8);
state.write_u64(m.to_u64()) state.write_u64(m.to_u64())
}, }
Endpoint::Bluetooth(m) => { Endpoint::Bluetooth(m) => {
state.write_u8(Type::Bluetooth as u8); state.write_u8(Type::Bluetooth as u8);
state.write_u64(m.to_u64()) state.write_u64(m.to_u64())
}, }
Endpoint::Ip(ip) => { Endpoint::Ip(ip) => {
state.write_u8(Type::Ip as u8); state.write_u8(Type::Ip as u8);
ip.hash(state); ip.hash(state);
}, }
Endpoint::IpUdp(ip) => { Endpoint::IpUdp(ip) => {
state.write_u8(Type::IpUdp as u8); state.write_u8(Type::IpUdp as u8);
ip.hash(state); ip.hash(state);
}, }
Endpoint::IpTcp(ip) => { Endpoint::IpTcp(ip) => {
state.write_u8(Type::IpTcp as u8); state.write_u8(Type::IpTcp as u8);
ip.hash(state); ip.hash(state);
}, }
Endpoint::Http(url) => { Endpoint::Http(url) => {
state.write_u8(Type::Http as u8); state.write_u8(Type::Http as u8);
url.hash(state); url.hash(state);
}, }
Endpoint::WebRTC(offer) => { Endpoint::WebRTC(offer) => {
state.write_u8(Type::WebRTC as u8); state.write_u8(Type::WebRTC as u8);
offer.hash(state); offer.hash(state);

View file

@ -5,12 +5,14 @@ use crate::vl1::constants::FRAGMENT_COUNT_MAX;
use crate::vl1::Path; use crate::vl1::Path;
use crate::vl1::protocol::PacketID; use crate::vl1::protocol::PacketID;
pub struct FragmentedPacket { /// Packet fragment re-assembler and container.
/// This is only used in the receive path.
pub(crate) struct FragmentedPacket {
pub id: PacketID, pub id: PacketID,
pub ts_ticks: i64, pub ts_ticks: i64,
frags: [Option<PacketBuffer>; FRAGMENT_COUNT_MAX], pub frags: [Option<PacketBuffer>; FRAGMENT_COUNT_MAX],
have: u8, pub have: u8,
expecting: u8, pub expecting: u8,
} }
impl Default for FragmentedPacket { impl Default for FragmentedPacket {
@ -26,29 +28,32 @@ impl Default for FragmentedPacket {
} }
impl FragmentedPacket { impl FragmentedPacket {
/// Reset this fragmented packet for re-use. /// Return fragments to pool and reset id and ts_ticks to 0 and -1 respectively.
#[inline(always)] #[inline(always)]
pub fn reset(&mut self) { pub fn clear(&mut self) {
self.id = 0; self.id = 0;
self.ts_ticks = -1; self.ts_ticks = -1;
self.frags.fill(None); self.frags.fill(None);
self.have = 0;
self.expecting = 0;
} }
/// Initialize for a new packet. /// Initialize for a new packet and log the first fragment.
/// This will panic if 'no' is out of bounds.
#[inline(always)] #[inline(always)]
pub fn init(&mut self, id: PacketID, ts_ticks: i64) { pub fn first_fragment(&mut self, id: PacketID, ts_ticks: i64, frag: PacketBuffer, no: u8, expecting: u8) {
self.id = id; self.id = id;
self.ts_ticks = ts_ticks; self.ts_ticks = ts_ticks;
let _ = self.frags[no as usize].replace(frag);
self.have = 1;
self.expecting = expecting;
} }
/// Add a fragment to this fragment set and return true if the packet appears complete. /// Add a fragment to this fragment set and return true if the packet appears complete.
/// This will panic if 'no' is out of bounds.
#[inline(always)] #[inline(always)]
pub fn add(&mut self, frag: PacketBuffer, no: u8, expecting: u8) -> bool { pub fn add_fragment(&mut self, frag: PacketBuffer, no: u8, expecting: u8) -> bool {
if self.frags[no].replace(frag).is_none() { if self.frags[no as usize].replace(frag).is_none() {
self.have = self.have.wrapping_add(1); self.have = self.have.wrapping_add(1);
self.expecting |= expecting; self.expecting |= expecting; // in valid streams expecting is either 0 or the (same) total
self.have == self.expecting self.have == self.expecting
} else { } else {
false false

View file

@ -7,7 +7,7 @@ pub(crate) mod dictionary;
pub(crate) mod address; pub(crate) mod address;
pub(crate) mod mac; pub(crate) mod mac;
pub(crate) mod fragmentedpacket; pub(crate) mod fragmentedpacket;
mod(crate) mod whois; pub(crate) mod whois;
pub mod constants; pub mod constants;
pub mod identity; pub mod identity;

View file

@ -17,7 +17,7 @@ use crate::vl1::constants::PACKET_SIZE_MAX;
use crate::vl1::path::Path; use crate::vl1::path::Path;
use crate::vl1::peer::Peer; use crate::vl1::peer::Peer;
use crate::vl1::protocol::{FragmentHeader, is_fragment, PacketHeader, PacketID}; use crate::vl1::protocol::{FragmentHeader, is_fragment, PacketHeader, PacketID};
use crate::vl1::whois::Whois; use crate::vl1::whois::WhoisQueue;
/// Standard packet buffer type including pool container. /// Standard packet buffer type including pool container.
pub type PacketBuffer = Pooled<Buffer<{ PACKET_SIZE_MAX }>>; pub type PacketBuffer = Pooled<Buffer<{ PACKET_SIZE_MAX }>>;
@ -99,9 +99,24 @@ pub trait VL1CallerInterface {
fn time_clock(&self) -> i64; fn time_clock(&self) -> i64;
} }
/// Trait implemented by VL2 to handle messages after they are unwrapped by VL1.
pub(crate) trait VL1PacketHandler {
/// Handle a packet, returning true if the verb was recognized.
/// True should be returned even if the packet is not valid, since the return value is used
/// to determine if this is a VL2 or VL1 packet. ERROR and OK should not be handled here but
/// in handle_error() and handle_ok() instead.
fn handle_packet(&self, peer: &Peer, source_path: &Arc<Path>, forward_secrecy: 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<Path>, forward_secrecy: bool, in_re_verb: u8, in_re_packet_id: PacketID, error_code: u8, payload: &Buffer<{ PACKET_SIZE_MAX }>) -> bool;
/// Handle an OK, returing true if the OK was recognized.
fn handle_ok(&self, peer: &Peer, source_path: &Arc<Path>, forward_secrecy: bool, in_re_verb: u8, in_re_packet_id: PacketID, payload: &Buffer<{ PACKET_SIZE_MAX }>) -> bool;
}
#[derive(Default)] #[derive(Default)]
struct BackgroundTaskIntervals { struct BackgroundTaskIntervals {
whois: IntervalGate<{ Whois::INTERVAL }>, whois: IntervalGate<{ WhoisQueue::INTERVAL }>,
} }
pub struct Node { pub struct Node {
@ -110,7 +125,7 @@ pub struct Node {
locator: Mutex<Option<Locator>>, locator: Mutex<Option<Locator>>,
paths: DashMap<Endpoint, Arc<Path>>, paths: DashMap<Endpoint, Arc<Path>>,
peers: DashMap<Address, Arc<Peer>>, peers: DashMap<Address, Arc<Peer>>,
whois: Whois, whois: WhoisQueue,
buffer_pool: Pool<Buffer<{ PACKET_SIZE_MAX }>>, buffer_pool: Pool<Buffer<{ PACKET_SIZE_MAX }>>,
secure_prng: SecureRandom, secure_prng: SecureRandom,
} }
@ -149,7 +164,7 @@ impl Node {
locator: Mutex::new(None), locator: Mutex::new(None),
paths: DashMap::new(), paths: DashMap::new(),
peers: DashMap::new(), peers: DashMap::new(),
whois: Whois::new(), whois: WhoisQueue::new(),
buffer_pool: Pool::new(64), buffer_pool: Pool::new(64),
secure_prng: SecureRandom::get(), secure_prng: SecureRandom::get(),
}) })
@ -205,7 +220,8 @@ impl Node {
} }
/// Called when a packet is received on the physical wire. /// Called when a packet is received on the physical wire.
pub fn wire_receive<CI: VL1CallerInterface>(&self, ci: &CI, source_endpoint: &Endpoint, source_local_socket: i64, source_local_interface: i64, mut data: PacketBuffer) { pub fn wire_receive<CI: VL1CallerInterface, PH: VL1PacketHandler>(&self, ci: &CI, ph: &PH, source_endpoint: &Endpoint, source_local_socket: i64, source_local_interface: i64, mut data: PacketBuffer) {
/*
let _ = data.struct_mut_at::<FragmentHeader>(0).map(|fragment_header| { let _ = data.struct_mut_at::<FragmentHeader>(0).map(|fragment_header| {
// NOTE: destination address is located at the same index in both the fragment // NOTE: destination address is located at the same index in both the fragment
// header and the full packet header, allowing us to make this decision once. // header and the full packet header, allowing us to make this decision once.
@ -216,19 +232,13 @@ impl Node {
let path = self.path(source_endpoint, source_local_socket, source_local_interface); let path = self.path(source_endpoint, source_local_socket, source_local_interface);
if fragment_header.is_fragment() { if fragment_header.is_fragment() {
} else { } else {
data.struct_mut_at::<PacketHeader>(0).map(|header| {
let source = Address::from(&header.src);
if header.is_fragmented() {
} else {
}
});
} }
} else { } else {
// Packet or fragment is addressed to another node. // Packet or fragment is addressed to another node.
} }
}); });
*/
} }
/// Get the canonical Path object for a given endpoint and local socket information. /// Get the canonical Path object for a given endpoint and local socket information.

View file

@ -1,7 +1,7 @@
use std::sync::atomic::{AtomicI64, Ordering}; use std::sync::atomic::{AtomicI64, Ordering};
use crate::vl1::Endpoint; use crate::vl1::Endpoint;
use crate::vl1::constants::FRAGMENT_COUNT_MAX; use crate::vl1::constants::{FRAGMENT_COUNT_MAX, FRAGMENT_EXPIRATION};
use crate::vl1::fragmentedpacket::FragmentedPacket; use crate::vl1::fragmentedpacket::FragmentedPacket;
use crate::vl1::protocol::{FragmentHeader, PacketID}; use crate::vl1::protocol::{FragmentHeader, PacketID};
use crate::vl1::node::PacketBuffer; use crate::vl1::node::PacketBuffer;
@ -48,45 +48,72 @@ impl Path {
self.rxs.lock().last_receive_time_ticks self.rxs.lock().last_receive_time_ticks
} }
/// Receive a fragment and invoke the handler if a packet appears fully assembled.
/// This also updates last receive time, etc.
#[inline(always)] #[inline(always)]
pub(crate) fn receive_fragment<F: FnOnce(&mut FragmentedPacket)>(&self, packet_id: PacketID, fragment_no: u8, fragment_expecting_count: u8, packet: PacketBuffer, time_ticks: i64, assembled_packet_handler: F) { pub(crate) fn receive_fragment<F: FnOnce(&mut FragmentedPacket)>(&self, packet_id: PacketID, fragment_no: u8, fragment_expecting_count: u8, packet: PacketBuffer, time_ticks: i64, assembled_packet_handler: F) {
if fragment_no < FRAGMENT_COUNT_MAX as u8 { if fragment_no < FRAGMENT_COUNT_MAX as u8 {
let mut rxs = self.rxs.lock(); let mut rxs = self.rxs.lock();
rxs.last_receive_time_ticks = time_ticks; rxs.last_receive_time_ticks = time_ticks;
let mut fpcnt = rxs.fragmented_packet_count; // In most situlations this algorithms runs right through and doesn't need to iterate.
let mut fidx = 0; // If there are no fragments fpcnt will be 0 and the first loop will skip. If there are
while fpcnt > 0 { // no fragments then the second loop won't be needed either since the first slot will
let mut f = &mut rxs.fragmented_packets[fidx]; // be open. Looping only happens when there are multiple fragments in flight, which is
// not a common scenario for peer-to-peer links. The maximum iteration count in the
// worst case is only 2*FRAGMENT_COUNT_MAX and the loops are only doing integer
// comparisons, so the worst case is still linear.
let mut fragmented_packets_to_check = rxs.fragmented_packet_count;
let mut i = 0;
while fragmented_packets_to_check > 0 {
let mut f = &mut rxs.fragmented_packets[i];
if f.id == packet_id { if f.id == packet_id {
if f.add(packet, fragment_no, fragment_expecting_count) { if f.add_fragment(packet, fragment_no, fragment_expecting_count) {
assembled_packet_handler(f); assembled_packet_handler(f);
f.reset(); f.clear();
rxs.fragmented_packet_count = rxs.fragmented_packet_count.wrapping_sub(1); rxs.fragmented_packet_count -= 1;
} }
return; return;
} else if f.ts_ticks >= 0 { } else if f.ts_ticks >= 0 {
fpcnt = fpcnt.wrapping_sub(1); if (time_ticks - f.ts_ticks) > FRAGMENT_EXPIRATION {
f.clear();
rxs.fragmented_packet_count -= 1;
} }
fidx = fidx.wrapping_add(1); fragmented_packets_to_check -= 1;
}
i += 1;
} }
let mut oldest_ts = rxs.fragmented_packets[0].ts_ticks; let mut oldest_ts = &mut rxs.fragmented_packets[0];
let mut oldest_idx = 0; let mut oldest_ts_ticks = oldest_ts.ts_ticks;
if oldest_ts >= 0 { if oldest_ts_ticks >= 0 {
for fidx in 1..FRAGMENT_COUNT_MAX { for fidx in 1..FRAGMENT_COUNT_MAX {
let ts = rxs.fragmented_packets[fidx].ts_ticks; let ts = &mut rxs.fragmented_packets[fidx];
if ts < oldest_ts { let tst = ts.ts_ticks;
if tst < oldest_ts_ticks {
oldest_ts = ts; oldest_ts = ts;
oldest_idx = fidx; oldest_ts_ticks = tst;
if tst < 0 {
break;
}
} }
} }
} }
let mut f = &mut rxs.fragmented_packets[oldest_idx]; if oldest_ts_ticks < 0 {
f.init(packet_id, time_ticks); rxs.fragmented_packet_count += 1;
let _ = f.add(packet, fragment_no, fragment_expecting_count); } else {
oldest_ts.clear();
} }
rxs.fragmented_packets[oldest_idx].init(packet_id, time_ticks, packet, fragment_no, fragment_expecting_count);
}
}
/// Register receipt of "anything" else which right now includes unfragmented packets and keepalives.
#[inline(always)]
pub(crate) fn receive_other(&self, time_ticks: i64) {
self.rxs.lock().last_receive_time_ticks = time_ticks;
} }
} }

View file

@ -1,24 +1,93 @@
use std::ops::DerefMut;
use std::sync::Arc; use std::sync::Arc;
use std::sync::atomic::{AtomicI64, AtomicU64, AtomicU8};
use crate::vl1::{Identity, Path};
use crate::vl1::fragmentedpacket::FragmentedPacket;
use crate::vl1::protocol::{PacketID, PacketHeader};
use crate::vl1::node::{VL1CallerInterface, PacketBuffer, Node};
use parking_lot::Mutex; use parking_lot::Mutex;
use aes_gmac_siv::AesGmacSiv;
const MAX_PATHS: usize = 16; use crate::crypto::c25519::C25519KeyPair;
use crate::crypto::hash::SHA384_HASH_SIZE;
use crate::crypto::kbkdf::zt_kbkdf_hmac_sha384;
use crate::crypto::p521::P521KeyPair;
use crate::crypto::poly1305::Poly1305;
use crate::crypto::random::next_u64_secure;
use crate::crypto::salsa::Salsa;
use crate::crypto::secret::Secret;
use crate::vl1::{Identity, Path};
use crate::vl1::buffer::Buffer;
use crate::vl1::constants::*;
use crate::vl1::fragmentedpacket::FragmentedPacket;
use crate::vl1::node::*;
use crate::vl1::protocol::*;
struct PeerSecrets {
// Time secret was created in ticks or -1 for static secrets.
create_time_ticks: i64,
// Number of time secret has been used to encrypt something during this session.
encrypt_count: u64,
// Raw secret itself.
secret: Secret<48>,
// Reusable AES-GMAC-SIV initialized with secret.
aes: AesGmacSiv,
}
struct EphemeralKeyPair {
// Time ephemeral key pair was created.
create_time_ticks: i64,
// SHA384(c25519 public | p521 public)
public_keys_hash: [u8; 48],
// Curve25519 ECDH key pair.
c25519: C25519KeyPair,
// NIST P-521 ECDH key pair.
p521: P521KeyPair,
}
struct TxState { struct TxState {
packet_iv_counter: u64, // Time we last sent something to this peer.
last_send_time_ticks: i64, last_send_time_ticks: i64,
paths: [Arc<Path>; MAX_PATHS],
// Outgoing packet IV counter, starts at a random position.
packet_iv_counter: u64,
// Total bytes sent to this peer during this session.
total_bytes: u64,
// "Eternal" static secret created via identity agreement.
static_secret: PeerSecrets,
// The most recently negotiated ephemeral secret.
ephemeral_secret: Option<PeerSecrets>,
// The current ephemeral key pair we will share with HELLO.
ephemeral_pair: Option<EphemeralKeyPair>,
// Paths to this peer sorted in descending order of quality with None entries at the end.
paths: [Option<Arc<Path>>; PEER_MAX_PATHS],
} }
struct RxState { struct RxState {
// Time we last received something (authenticated) from this peer.
last_receive_time_ticks: i64, last_receive_time_ticks: i64,
remote_version: [u8; 4],
// Total bytes received from this peer during this session.
total_bytes: u64,
// "Eternal" static secret created via identity agreement.
static_secret: PeerSecrets,
// The most recently negotiated ephemeral secret.
ephemeral_secret: Option<PeerSecrets>,
// Remote version as major, minor, revision, build in most-to-least-significant 16-bit chunks.
// This is the user-facing software version and is zero if not yet known.
remote_version: u64,
// Remote protocol version or zero if not yet known.
remote_protocol_version: u8, remote_protocol_version: u8,
} }
@ -26,22 +95,245 @@ struct RxState {
/// Sending-related and receiving-related fields are locked separately since concurrent /// Sending-related and receiving-related fields are locked separately since concurrent
/// send/receive is not uncommon. /// send/receive is not uncommon.
pub struct Peer { pub struct Peer {
// This peer's identity.
identity: Identity, identity: Identity,
// Static shared secret computed from agreement with identity. // Static shared secret computed from agreement with identity.
identity_static_secret: [u8; 48], static_secret: Secret<48>,
// Derived static secret used to encrypt the dictionary part of HELLO.
static_secret_hello_dictionary_encrypt: Secret<48>,
// Derived static secret used to add full HMAC-SHA384 to packets, currently just HELLO.
static_secret_packet_hmac: Secret<48>,
// State used primarily when sending to this peer. // State used primarily when sending to this peer.
txs: Mutex<TxState>, tx: Mutex<TxState>,
// State used primarily when receiving from this peer. // State used primarily when receiving from this peer.
rxs: Mutex<RxState>, rx: Mutex<RxState>,
}
/// Derive per-packet key for Sals20/12 encryption (and Poly1305 authentication).
///
/// This effectively adds a few additional bits of entropy to the IV from packet
/// characteristics such as its size and direction of communication. It also
/// effectively incorporates header information as AAD, since if the header info
/// is different the key will be wrong and MAC will fail.
///
/// This is only used for Salsa/Poly modes.
#[inline(always)]
fn salsa_derive_per_packet_key(key: &Secret<48>, header: &PacketHeader, packet_size: usize) -> Secret<48> {
let hb = header.as_bytes();
let mut k = key.clone();
for i in 0..18 {
k.0[i] ^= hb[i];
}
k.0[18] ^= hb[HEADER_FLAGS_FIELD_INDEX] & HEADER_FLAGS_FIELD_MASK_HIDE_HOPS;
k.0[19] ^= (packet_size >> 8) as u8;
k.0[20] ^= packet_size as u8;
k
} }
impl Peer { impl Peer {
pub(crate) fn receive_from_singular<CI: VL1CallerInterface>(&self, node: &Node, ci: &CI, header: &PacketHeader, packet: &PacketBuffer) { /// Create a new peer.
/// This only returns None if this_node_identity does not have its secrets or if some
/// fatal error occurs performing key agreement between the two identities.
pub(crate) fn new(this_node_identity: &Identity, id: Identity) -> Option<Peer> {
this_node_identity.agree(&id).map(|static_secret| {
let aes_k0 = zt_kbkdf_hmac_sha384(&static_secret.0, KBKDF_KEY_USAGE_LABEL_AES_GMAC_SIV_K0, 0, 0);
let aes_k1 = zt_kbkdf_hmac_sha384(&static_secret.0, KBKDF_KEY_USAGE_LABEL_AES_GMAC_SIV_K1, 0, 0);
let static_secret_hello_dictionary_encrypt = zt_kbkdf_hmac_sha384(&static_secret.0, KBKDF_KEY_USAGE_LABEL_HELLO_DICTIONARY_ENCRYPT, 0, 0);
let static_secret_packet_hmac = zt_kbkdf_hmac_sha384(&static_secret.0, KBKDF_KEY_USAGE_LABEL_PACKET_HMAC, 0, 0);
Peer {
identity: id,
static_secret: static_secret.clone(),
static_secret_hello_dictionary_encrypt,
static_secret_packet_hmac,
tx: Mutex::new(TxState {
last_send_time_ticks: 0,
packet_iv_counter: next_u64_secure(),
total_bytes: 0,
static_secret: TxSecret {
create_time_ticks: -1,
usage_count: 0,
secret: static_secret.clone(),
aes: AesGmacSiv::new(&aes_k0.0, &aes_k1.0),
},
ephemeral_secret: None,
paths: [None; PEER_MAX_PATHS],
ephemeral_pair: None,
}),
rx: Mutex::new(RxState {
last_receive_time_ticks: 0,
total_bytes: 0,
static_secret: PeerSecrets {
create_time_ticks: -1,
encrypt_count: 0,
secret: static_secret,
aes: AesGmacSiv::new(&aes_k0.0, &aes_k1.0),
},
ephemeral_secret: None,
remote_version: 0,
remote_protocol_version: 0,
}),
}
})
} }
pub(crate) fn receive_from_fragmented<CI: VL1CallerInterface>(&self, node: &Node, ci: CI, header: &PacketHeader, packet: &FragmentedPacket) { /// Receive, decrypt, authenticate, and process an incoming packet from this peer.
/// If the packet comes in multiple fragments, the fragments slice should contain all
/// those fragments after the main packet header and first chunk.
pub(crate) fn receive<CI: VL1CallerInterface, PH: VL1PacketHandler>(&self, node: &Node, ci: &CI, ph: &PH, time_ticks: i64, source_path: &Arc<Path>, header: &PacketHeader, packet: &Buffer<{ PACKET_SIZE_MAX }>, fragments: &[Option<PacketBuffer>]) {
let packet_frag0_payload_bytes = packet.as_bytes_after(PACKET_VERB_INDEX).unwrap_or(&[]);
if !packet_frag0_payload_bytes.is_empty() {
let mut payload: Buffer<{ PACKET_SIZE_MAX }> = Buffer::new();
let mut rx = self.rx.lock();
// When handling incoming packets we try any current ephemeral secret first, and if that
// fails we fall back to the static secret. If decryption with an ephemeral secret succeeds
// the forward secrecy flag in the receive path is set.
let mut secret = rx.ephemeral_secret.as_mut().unwrap_or(&mut rx.static_secret);
loop {
match header.cipher() {
CIPHER_NOCRYPT_POLY1305 => {
// Only HELLO is allowed in the clear (but still authenticated).
if (packet_frag0_payload_bytes[0] & VERB_MASK) == VERB_VL1_HELLO {
let _ = payload.append_bytes(packet_frag0_payload_bytes);
for f in fragments.iter() {
let _ = f.as_ref().map(|f| {
let _ = f.as_bytes_after(FRAGMENT_HEADER_SIZE).map(|f| {
let _ = payload.append_bytes(f);
});
});
}
// FIPS note: for FIPS purposes the HMAC-SHA384 tag at the end of V2 HELLOs
// will be considered the "real" handshake authentication.
let key = salsa_derive_per_packet_key(&secret.secret, header, payload.len());
let mut salsa = Salsa::new(&key.0[0..32], header.id_bytes(), true).unwrap();
let mut poly1305_key = [0_u8; 32];
salsa.crypt_in_place(&mut poly1305_key);
let mut poly = Poly1305::new(&poly1305_key).unwrap();
poly.update(packet_frag0_payload_bytes);
if poly.finish()[0..8].eq(&header.message_auth) {
break;
}
}
}
CIPHER_SALSA2012_POLY1305 => {
// FIPS note: support for this mode would have to be disabled in FIPS compliant
// modes of operation.
let key = salsa_derive_per_packet_key(&secret.secret, header, payload.len());
let mut salsa = Salsa::new(&key.0[0..32], header.id_bytes(), true).unwrap();
let mut poly1305_key = [0_u8; 32];
salsa.crypt_in_place(&mut poly1305_key);
let mut poly = Poly1305::new(&poly1305_key).unwrap();
poly.update(packet_frag0_payload_bytes);
let _ = payload.append_and_init_bytes(packet_frag0_payload_bytes.len(), |b| salsa.crypt(packet_frag0_payload_bytes, b));
for f in fragments.iter() {
let _ = f.as_ref().map(|f| {
let _ = f.as_bytes_after(FRAGMENT_HEADER_SIZE).map(|f| {
poly.update(f);
let _ = payload.append_and_init_bytes(f.len(), |b| salsa.crypt(f, b));
});
});
}
if poly.finish()[0..8].eq(&header.message_auth) {
break;
}
}
CIPHER_AES_GMAC_SIV => {
secret.aes.reset();
secret.aes.decrypt_init(&header.aes_gmac_siv_tag());
secret.aes.decrypt_set_aad(&header.aad_bytes());
let _ = payload.append_and_init_bytes(packet_frag0_payload_bytes.len(), |b| secret.aes.decrypt(packet_frag0_payload_bytes, b));
for f in fragments.iter() {
let _ = f.as_ref().map(|f| {
let _ = f.as_bytes_after(FRAGMENT_HEADER_SIZE).map(|f| {
let _ = payload.append_and_init_bytes(f.len(), |b| secret.aes.decrypt(f, b));
});
});
}
if secret.aes.decrypt_finish() {
break;
}
}
_ => {}
}
if (secret as *const PeerSecrets) != (&rx.static_secret as *const PeerSecrets) {
payload.clear();
secret = &mut rx.static_secret;
} else {
// Both ephemeral (if any) and static secret have failed, drop packet.
return;
}
}
// If we make it here we've successfully decrypted and authenticated the packet.
rx.last_receive_time_ticks = time_ticks;
rx.total_bytes += payload.len() as u64;
let forward_secrecy = (secret as *const PeerSecrets) != (&(rx.static_secret) as *const PeerSecrets);
// Unlock rx state mutex.
drop(rx);
let _ = payload.u8_at(0).map(|verb| {
// For performance reasons we let VL2 handle packets first. It returns false
// if it didn't pick up anything.
if !ph.handle_packet(self, source_path, forward_secrecy, verb, &payload) {
match verb {
VERB_VL1_NOP => {}
VERB_VL1_HELLO => {}
VERB_VL1_ERROR => {}
VERB_VL1_OK => {}
VERB_VL1_WHOIS => {}
VERB_VL1_RENDEZVOUS => {}
VERB_VL1_ECHO => {}
VERB_VL1_PUSH_DIRECT_PATHS => {}
VERB_VL1_USER_MESSAGE => {}
VERB_VL1_REMOTE_TRACE => {}
_ => {}
}
}
});
}
}
/// Get the remote version of this peer: major, minor, revision, and build.
/// Returns None if it's not yet known.
pub fn version(&self) -> Option<[u16; 4]> {
let rv = self.rx.lock().remote_version;
if rv != 0 {
Some([(rv >> 48) as u16, (rv >> 32) as u16, (rv >> 16) as u16, rv as u16])
} else {
None
}
}
/// Get the remote protocol version of this peer or None if not yet known.
pub fn protocol_version(&self) -> Option<u8> {
let pv = self.rx.lock().remote_protocol_version;
if pv != 0 {
Some(pv)
} else {
None
}
} }
} }

View file

@ -1,20 +1,33 @@
use std::ops::Not; use std::intrinsics::size_of;
use std::mem::MaybeUninit;
use crate::vl1::buffer::{RawObject, Buffer};
use crate::vl1::constants::{HEADER_FLAGS_FIELD_MASK_CIPHER, HEADER_FLAGS_FIELD_MASK_HOPS, HEADER_FLAG_FRAGMENTED, FRAGMENT_INDICATOR};
use crate::vl1::Address; use crate::vl1::Address;
use crate::vl1::buffer::{Buffer, RawObject};
use crate::vl1::constants::*;
pub const VERB_VL1_NOP: u8 = 0x00;
pub const VERB_VL1_HELLO: u8 = 0x01;
pub const VERB_VL1_ERROR: u8 = 0x02;
pub const VERB_VL1_OK: u8 = 0x03;
pub const VERB_VL1_WHOIS: u8 = 0x04;
pub const VERB_VL1_RENDEZVOUS: u8 = 0x05;
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 VERB_VL1_REMOTE_TRACE: u8 = 0x15;
/// A unique packet identifier, also the cryptographic nonce. /// A unique packet identifier, also the cryptographic nonce.
///
/// Packet IDs are stored as u64s for efficiency but they should be treated as /// Packet IDs are stored as u64s for efficiency but they should be treated as
/// [u8; 8] fields in that their endianness is "wire" endian. If for some reason /// [u8; 8] fields in that their endianness is "wire" endian. If for some reason
/// packet IDs need to be portably compared or shared across systems they should /// packet IDs need to be portably compared or shared across systems they should
/// be treated as bytes not integers. /// be treated as bytes not integers.
pub type PacketID = u64; pub type PacketID = u64;
/// ZeroTier unencrypted outer header /// ZeroTier unencrypted outer packet header
///
/// This is the header for a complete packet. If the fragmented flag is set, it will /// This is the header for a complete packet. If the fragmented flag is set, it will
/// arrive with one or more fragments that must be assembled to complete it. /// arrive with one or more fragments that must be assembled to complete it.
#[derive(Clone)]
#[repr(packed)] #[repr(packed)]
pub struct PacketHeader { pub struct PacketHeader {
pub id: PacketID, pub id: PacketID,
@ -32,9 +45,6 @@ impl PacketHeader {
self.flags_cipher_hops & HEADER_FLAGS_FIELD_MASK_CIPHER self.flags_cipher_hops & HEADER_FLAGS_FIELD_MASK_CIPHER
} }
/// Get this packet's hops field.
/// This is the only field in the unencrypted header that is not authenticated, allowing intermediate
/// nodes to increment it as they forward packets between indirectly connected peers.
#[inline(always)] #[inline(always)]
pub fn hops(&self) -> u8 { pub fn hops(&self) -> u8 {
self.flags_cipher_hops & HEADER_FLAGS_FIELD_MASK_HOPS self.flags_cipher_hops & HEADER_FLAGS_FIELD_MASK_HOPS
@ -43,12 +53,9 @@ impl PacketHeader {
#[inline(always)] #[inline(always)]
pub fn increment_hops(&mut self) { pub fn increment_hops(&mut self) {
let f = self.flags_cipher_hops; let f = self.flags_cipher_hops;
self.flags_cipher_hops = (f & HEADER_FLAGS_FIELD_MASK_HOPS.not()) | ((f + 1) & HEADER_FLAGS_FIELD_MASK_HOPS); self.flags_cipher_hops = (f & HEADER_FLAGS_FIELD_MASK_HIDE_HOPS) | ((f + 1) & HEADER_FLAGS_FIELD_MASK_HOPS);
} }
/// If true, this packet requires one or more fragments to fully assemble.
/// The one with the full header is always fragment 0. Note that is_fragment() is checked first
/// to see if this IS a fragment.
#[inline(always)] #[inline(always)]
pub fn is_fragmented(&self) -> bool { pub fn is_fragmented(&self) -> bool {
(self.flags_cipher_hops & HEADER_FLAG_FRAGMENTED) != 0 (self.flags_cipher_hops & HEADER_FLAG_FRAGMENTED) != 0
@ -63,14 +70,41 @@ impl PacketHeader {
pub fn source(&self) -> Address { pub fn source(&self) -> Address {
Address::from(&self.src) Address::from(&self.src)
} }
#[inline(always)]
pub fn id_bytes(&self) -> &[u8; 8] {
unsafe { &*(self as *const Self).cast::<[u8; 8]>() }
}
#[inline(always)]
pub fn as_bytes(&self) -> &[u8; PACKET_HEADER_SIZE] {
unsafe { &*(self as *const Self).cast::<[u8; PACKET_HEADER_SIZE]>() }
}
#[inline(always)]
pub fn aad_bytes(&self) -> [u8; 11] {
let mut id = unsafe { MaybeUninit::<[u8; 11]>::uninit().assume_init() };
id[0..5].copy_from_slice(&self.dest);
id[5..10].copy_from_slice(&self.src);
id[10] = self.flags_cipher_hops & HEADER_FLAGS_FIELD_MASK_HIDE_HOPS;
id
}
#[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[8..16].copy_from_slice(&self.message_auth);
id
}
} }
/// ZeroTier fragment header /// ZeroTier fragment header
///
/// Fragments are indicated by byte 0xff at the start of the source address, which /// Fragments are indicated by byte 0xff at the start of the source address, which
/// is normally illegal since addresses can't begin with that. Fragmented packets /// is normally illegal since addresses can't begin with that. Fragmented packets
/// will arrive with the first fragment carrying a normal header with the fragment /// will arrive with the first fragment carrying a normal header with the fragment
/// bit set and remaining fragments being these. /// bit set and remaining fragments being these.
#[derive(Clone)]
#[repr(packed)] #[repr(packed)]
pub struct FragmentHeader { pub struct FragmentHeader {
pub id: PacketID, // packet ID pub id: PacketID, // packet ID
@ -80,7 +114,7 @@ pub struct FragmentHeader {
pub reserved_hops: u8, // rrrrrHHH (3 hops bits, rest reserved) pub reserved_hops: u8, // rrrrrHHH (3 hops bits, rest reserved)
} }
unsafe impl crate::vl1::buffer::RawObject for FragmentHeader {} unsafe impl RawObject for FragmentHeader {}
impl FragmentHeader { impl FragmentHeader {
#[inline(always)] #[inline(always)]
@ -106,7 +140,7 @@ impl FragmentHeader {
#[inline(always)] #[inline(always)]
pub fn increment_hops(&mut self) { pub fn increment_hops(&mut self) {
let f = self.reserved_hops; let f = self.reserved_hops;
self.reserved_hops = (f & HEADER_FLAGS_FIELD_MASK_HOPS.not()) | ((f + 1) & HEADER_FLAGS_FIELD_MASK_HOPS); self.reserved_hops = (f & HEADER_FLAGS_FIELD_MASK_HIDE_HOPS) | ((f + 1) & HEADER_FLAGS_FIELD_MASK_HOPS);
} }
#[inline(always)] #[inline(always)]
@ -118,12 +152,28 @@ impl FragmentHeader {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::mem::size_of; use std::mem::size_of;
use crate::vl1::protocol::{PacketHeader, FragmentHeader};
use crate::vl1::constants::{PACKET_HEADER_SIZE, FRAGMENT_SIZE_MIN}; use crate::vl1::constants::{FRAGMENT_HEADER_SIZE, PACKET_HEADER_SIZE};
use crate::vl1::protocol::{FragmentHeader, PacketHeader};
#[test] #[test]
fn object_sizing() { fn representation() {
assert_eq!(size_of::<PacketHeader>(), PACKET_HEADER_SIZE); assert_eq!(size_of::<PacketHeader>(), PACKET_HEADER_SIZE);
assert_eq!(size_of::<FragmentHeader>(), FRAGMENT_SIZE_MIN); assert_eq!(size_of::<FragmentHeader>(), FRAGMENT_HEADER_SIZE);
let mut foo = [0_u8; 32];
unsafe {
(*foo.as_mut_ptr().cast::<PacketHeader>()).src[0] = 0xff;
assert_eq!((*foo.as_ptr().cast::<FragmentHeader>()).fragment_indicator, 0xff);
}
let bar = PacketHeader{
id: 0x0102030405060708_u64.to_be(),
dest: [0_u8; 5],
src: [0_u8; 5],
flags_cipher_hops: 0,
message_auth: [0_u8; 8],
};
assert_eq!(bar.id_bytes(), [1_u8, 2, 3, 4, 5, 6, 7, 8]);
} }
} }

View file

@ -1,14 +1,14 @@
use std::collections::HashMap; use std::collections::HashMap;
use crate::vl1::Address;
use crate::vl1::fragmentedpacket::FragmentedPacket;
use crate::vl1::node::{VL1CallerInterface, Node, PacketBuffer};
use crate::util::gate::IntervalGate;
use parking_lot::Mutex; use parking_lot::Mutex;
use crate::vl1::constants::{WHOIS_RETRY_INTERVAL, WHOIS_RETRY_MAX};
pub enum QueuedPacket { use crate::util::gate::IntervalGate;
use crate::vl1::Address;
use crate::vl1::constants::*;
use crate::vl1::fragmentedpacket::FragmentedPacket;
use crate::vl1::node::{Node, PacketBuffer, VL1CallerInterface};
pub(crate) enum QueuedPacket {
Singular(PacketBuffer), Singular(PacketBuffer),
Fragmented(FragmentedPacket) Fragmented(FragmentedPacket)
} }
@ -19,11 +19,11 @@ struct WhoisQueueItem {
packet_queue: Vec<QueuedPacket> packet_queue: Vec<QueuedPacket>
} }
pub struct Whois { pub(crate) struct WhoisQueue {
queue: Mutex<HashMap<Address, WhoisQueueItem>> queue: Mutex<HashMap<Address, WhoisQueueItem>>
} }
impl Whois { impl WhoisQueue {
pub const INTERVAL: i64 = WHOIS_RETRY_INTERVAL; pub const INTERVAL: i64 = WHOIS_RETRY_INTERVAL;
pub fn new() -> Self { pub fn new() -> Self {