Formatting.

This commit is contained in:
Adam Ierymenko 2021-08-23 14:46:34 -04:00
parent bcfd35a1f7
commit d2e19c889f
No known key found for this signature in database
GPG key ID: C8877CF2D7A5D7F3
19 changed files with 210 additions and 239 deletions

View file

@ -66,6 +66,8 @@ NOP, as the name suggests, does nothing. Any payload is ignored.
| [2] u16 | Length of encrypted Dictionary in bytes |
| Dictionary | Key/value dictionary containing additional fields |
| -- | -- END of AES-256-CTR encrypted section -- |
| [2] u16 | Length of unencrypted Dictionary in bytes |
| Dictionary | Unencrypted dictionary (not currently used) |
HELLO establishes a full session with another peer and carries information such as protocol and software versions, the full identity of the peer, and ephemeral keys for forward secrecy. Without a HELLO exchange only limited communication with the most conservative assumptions is possible, and communication without a session may be completely removed in the future. (It's only allowed now for backward compatibility with ZeroTier 1.x, and must be disabled in FIPS mode.)
@ -91,7 +93,7 @@ OK(HELLO) response payload, which must be sent if the HELLO receipient wishes to
| [2] u16 | Length of encrypted Dictionary in bytes |
| Dictionary | Key/value dictionary containing additional fields |
Recommended dictionary fields in both HELLO and OK(HELLO):
The unencrypted dictionary is not currently used. The encrypted dictionary can contain the following fields in both HELLO and OK(HELLO):
| Name | Key | Type | Description |
| -------------------- | --- | ------------ | ------------------------------------------------ |
@ -120,7 +122,7 @@ Optional dictionary fields that can be included in either HELLO or OK(HELLO):
| VENDOR | `V` | string | Node software vendor if not ZeroTier, Inc. |
| FLAGS | `+` | string | Flags (see below) |
FLAGS is a string that can contain the following boolean flags: `F` to indicate that the node is running in FIPS compliant mode, and `w` to indicate that the node is a "wimp." "Wimpy" nodes are things like mobile phones, and this flag can be used to exempt these devices from selection for any intensive role (such as use in VL2 to propagate multicasts).
FLAGS is a string that can contain the following boolean flags: `F` to indicate that the node is running in FIPS compliant mode.
System information such as OS_NAME is currently only sent to roots and not to any other node. This allows roots to collect a bit of very generic statistical and diagnostic telemtry about the nodes using them.

View file

@ -38,14 +38,10 @@ impl C25519KeyPair {
}
#[inline(always)]
pub fn public_bytes(&self) -> [u8; C25519_PUBLIC_KEY_SIZE] {
self.1.to_bytes()
}
pub fn public_bytes(&self) -> [u8; C25519_PUBLIC_KEY_SIZE] { self.1.to_bytes() }
#[inline(always)]
pub fn secret_bytes(&self) -> Secret<{ C25519_SECRET_KEY_SIZE }> {
Secret(self.0.to_bytes())
}
pub fn secret_bytes(&self) -> Secret<{ C25519_SECRET_KEY_SIZE }> { Secret(self.0.to_bytes()) }
/// Execute ECDH agreement and return a raw (un-hashed) shared secret key.
#[inline(always)]
@ -86,14 +82,10 @@ impl Ed25519KeyPair {
}
#[inline(always)]
pub fn public_bytes(&self) -> [u8; ED25519_PUBLIC_KEY_SIZE] {
self.0.public.to_bytes()
}
pub fn public_bytes(&self) -> [u8; ED25519_PUBLIC_KEY_SIZE] { self.0.public.to_bytes() }
#[inline(always)]
pub fn secret_bytes(&self) -> Secret<{ ED25519_SECRET_KEY_SIZE }> {
Secret(self.0.secret.to_bytes())
}
pub fn secret_bytes(&self) -> Secret<{ ED25519_SECRET_KEY_SIZE }> { Secret(self.0.secret.to_bytes()) }
#[inline(always)]
pub fn sign(&self, msg: &[u8]) -> [u8; ED25519_SIGNATURE_SIZE] {

View file

@ -26,19 +26,13 @@ impl SHA512 {
}
#[inline(always)]
pub fn new() -> Self {
Self(gcrypt::digest::MessageDigest::new(gcrypt::digest::Algorithm::Sha512).unwrap())
}
pub fn new() -> Self { Self(gcrypt::digest::MessageDigest::new(gcrypt::digest::Algorithm::Sha512).unwrap()) }
#[inline(always)]
pub fn reset(&mut self) {
self.0.reset();
}
pub fn reset(&mut self) { self.0.reset(); }
#[inline(always)]
pub fn update(&mut self, b: &[u8]) {
self.0.update(b);
}
pub fn update(&mut self, b: &[u8]) { self.0.update(b); }
#[inline(always)]
pub fn finish(&mut self) -> [u8; SHA512_HASH_SIZE] {
@ -87,19 +81,13 @@ impl SHA384 {
}
#[inline(always)]
pub fn new() -> Self {
Self(gcrypt::digest::MessageDigest::new(gcrypt::digest::Algorithm::Sha384).unwrap())
}
pub fn new() -> Self { Self(gcrypt::digest::MessageDigest::new(gcrypt::digest::Algorithm::Sha384).unwrap()) }
#[inline(always)]
pub fn reset(&mut self) {
self.0.reset();
}
pub fn reset(&mut self) { self.0.reset(); }
#[inline(always)]
pub fn update(&mut self, b: &[u8]) {
self.0.update(b);
}
pub fn update(&mut self, b: &[u8]) { self.0.update(b); }
#[inline(always)]
pub fn finish(&mut self) -> [u8; SHA384_HASH_SIZE] {

View file

@ -130,23 +130,17 @@ impl P521KeyPair {
}
#[inline(always)]
pub fn public_key(&self) -> &P521PublicKey {
&self.public_key
}
pub fn public_key(&self) -> &P521PublicKey { &self.public_key }
/// Get the raw ECC public "q" point for this key pair.
/// The returned point is not compressed. To use this with other interfaces that expect a format
/// prefix, prepend 0x04 to the beginning of this public key. This prefix is always the same in
/// our system and so is omitted.
#[inline(always)]
pub fn public_key_bytes(&self) -> &[u8; P521_PUBLIC_KEY_SIZE] {
&self.public_key.public_key_bytes
}
pub fn public_key_bytes(&self) -> &[u8; P521_PUBLIC_KEY_SIZE] { &self.public_key.public_key_bytes }
#[inline(always)]
pub fn secret_key_bytes(&self) -> &Secret<{ P521_SECRET_KEY_SIZE }> {
&self.secret_key_bytes
}
pub fn secret_key_bytes(&self) -> &Secret<{ P521_SECRET_KEY_SIZE }> { &self.secret_key_bytes }
/// Create an ECDSA signature of the input message.
/// Message data does not need to be pre-hashed.
@ -213,17 +207,13 @@ impl P521PublicKey {
impl PartialEq for P521PublicKey {
#[inline(always)]
fn eq(&self, other: &Self) -> bool {
self.public_key_bytes.eq(&other.public_key_bytes)
}
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 {
P521PublicKey::from_bytes(&self.public_key_bytes).unwrap()
}
fn clone(&self) -> Self { P521PublicKey::from_bytes(&self.public_key_bytes).unwrap() }
}
#[cfg(test)]

View file

@ -16,34 +16,21 @@ pub struct Secret<const L: usize>(pub(crate) [u8; L]);
impl<const L: usize> Secret<L> {
#[inline(always)]
pub fn new() -> Self {
Self([0_u8; L])
}
pub fn new() -> Self { Self([0_u8; L]) }
/// Copy bytes into secret, will panic if size does not match.
#[inline(always)]
pub fn from_bytes(b: &[u8]) -> Self {
Self(b.try_into().unwrap())
}
pub fn from_bytes(b: &[u8]) -> Self { Self(b.try_into().unwrap()) }
#[inline(always)]
pub fn as_bytes(&self) -> &[u8; L] {
return &self.0
}
pub fn as_bytes(&self) -> &[u8; L] { return &self.0 }
}
impl<const L: usize> Drop for Secret<L> {
fn drop(&mut self) {
unsafe {
let p = self.0.as_mut_ptr();
if (L % size_of::<usize>()) == 0 {
for i in 0..(L / size_of::<usize>()) {
write_volatile(p.cast::<usize>().offset(i as isize), 0_usize);
}
} else {
for i in 0..L {
write_volatile(p.offset(i as isize), 0_u8);
}
for i in 0..L {
write_volatile(self.0.as_mut_ptr().offset(i as isize), 0_u8);
}
}
}
@ -51,35 +38,25 @@ impl<const L: usize> Drop for Secret<L> {
impl<const L: usize> Default for Secret<L> {
#[inline(always)]
fn default() -> Self {
Self([0_u8; L])
}
fn default() -> Self { Self([0_u8; L]) }
}
impl<const L: usize> AsRef<[u8]> for Secret<L> {
#[inline(always)]
fn as_ref(&self) -> &[u8] {
&self.0
}
fn as_ref(&self) -> &[u8] { &self.0 }
}
impl<const L: usize> AsRef<[u8; L]> for Secret<L> {
#[inline(always)]
fn as_ref(&self) -> &[u8; L] {
&self.0
}
fn as_ref(&self) -> &[u8; L] { &self.0 }
}
impl<const L: usize> AsMut<[u8]> for Secret<L> {
#[inline(always)]
fn as_mut(&mut self) -> &mut [u8] {
&mut self.0
}
fn as_mut(&mut self) -> &mut [u8] { &mut self.0 }
}
impl<const L: usize> AsMut<[u8; L]> for Secret<L> {
#[inline(always)]
fn as_mut(&mut self) -> &mut [u8; L] {
&mut self.0
}
fn as_mut(&mut self) -> &mut [u8; L] { &mut self.0 }
}

View file

@ -14,9 +14,10 @@ pub struct Address(NonZeroU64);
impl Address {
/// Get an address from a 64-bit integer or return None if it is zero or reserved.
#[inline(always)]
pub fn from_u64(i: u64) -> Option<Address> {
pub fn from_u64(mut i: u64) -> Option<Address> {
i &= 0xffffffffff;
if i != 0 && (i >> 32) != ADDRESS_RESERVED_PREFIX as u64 {
Some(Address(unsafe { NonZeroU64::new_unchecked(i & 0xffffffffff) }))
Some(Address(unsafe { NonZeroU64::new_unchecked(i) }))
} else {
None
}

View file

@ -70,6 +70,14 @@ impl<const L: usize> Buffer<L> {
#[inline(always)]
pub fn is_empty(&self) -> bool { self.0 == 0 }
/// Set the size of this buffer's data.
///
/// This is marked unsafe because no bounds checking is done here and because it
/// technically violates the assurance that all data in the buffer is valid. Use
/// with care.
#[inline(always)]
pub unsafe fn set_size(&mut self, s: usize) { self.0 = s; }
/// Append a packed structure and call a function to initialize it in place.
/// Anything not initialized will be zero.
#[inline(always)]
@ -305,7 +313,7 @@ impl<const L: usize> Buffer<L> {
loop {
let b = self.read_u8(cursor)?;
if (b & 0x80) == 0 {
i |= (b as u64) << p;
i |= (b as u64).wrapping_shl(p);
p += 7;
} else {
i |= ((b & 0x7f) as u64) << p;

View file

@ -47,9 +47,7 @@ pub enum Endpoint {
impl Default for Endpoint {
#[inline(always)]
fn default() -> Endpoint {
Endpoint::Nil
}
fn default() -> Endpoint { Endpoint::Nil }
}
impl Endpoint {

View file

@ -6,6 +6,8 @@ use std::io::Write;
use std::ptr::{slice_from_raw_parts, slice_from_raw_parts_mut};
use std::str::FromStr;
use concat_arrays::concat_arrays;
use crate::crypto::balloon;
use crate::crypto::c25519::*;
use crate::crypto::hash::*;
@ -17,12 +19,12 @@ use crate::vl1::Address;
use crate::vl1::buffer::Buffer;
use crate::vl1::protocol::PACKET_SIZE_MAX;
use concat_arrays::concat_arrays;
pub const IDENTITY_TYPE_0_SIGNATURE_SIZE: usize = 96;
pub const IDENTITY_TYPE_1_SIGNATURE_SIZE: usize = P521_ECDSA_SIGNATURE_SIZE + ED25519_SIGNATURE_SIZE;
// Memory parameter for V0 address derivation work function.
const V0_IDENTITY_GEN_MEMORY: usize = 2097152;
// Balloon hash parameters for V1 address derivation work function.
const V0_POW_MEMORY: usize = 2097152;
const V0_POW_THRESHOLD: u8 = 17;
const V1_POW_THRESHOLD: u8 = 5;
const V1_BALLOON_SPACE_COST: usize = 16384;
const V1_BALLOON_TIME_COST: usize = 3;
const V1_BALLOON_DELTA: usize = 3;
@ -30,12 +32,6 @@ const V1_BALLOON_SALT: &'static [u8] = b"zt_id_v1";
const V1_PUBLIC_KEYS_SIGNATURE_AND_POW_SIZE: usize = C25519_PUBLIC_KEY_SIZE + ED25519_PUBLIC_KEY_SIZE + P521_PUBLIC_KEY_SIZE + P521_PUBLIC_KEY_SIZE + P521_ECDSA_SIGNATURE_SIZE + SHA384_HASH_SIZE;
pub const IDENTITY_TYPE_0_SIGNATURE_SIZE: usize = 96;
pub const IDENTITY_TYPE_1_SIGNATURE_SIZE: usize = P521_ECDSA_SIGNATURE_SIZE + ED25519_SIGNATURE_SIZE;
const IDENTITY_V0_POW_THRESHOLD: u8 = 17;
const IDENTITY_V1_POW_THRESHOLD: u8 = 5;
#[derive(Copy, Clone)]
#[repr(u8)]
pub enum Type {
@ -62,24 +58,24 @@ pub struct Identity {
/// 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_IDENTITY_GEN_MEMORY), &*slice_from_raw_parts(genmem_ptr, V0_IDENTITY_GEN_MEMORY)) };
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::<u64>();
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_IDENTITY_GEN_MEMORY {
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_IDENTITY_GEN_MEMORY / 8) {
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_IDENTITY_GEN_MEMORY as u64 / 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::<u64>();
@ -93,7 +89,7 @@ fn v0_frankenhash(digest: &mut [u8; 64], genmem_ptr: *mut u8) {
impl Identity {
fn generate_c25519() -> Identity {
let genmem_layout = Layout::from_size_align(V0_IDENTITY_GEN_MEMORY, 8).unwrap();
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");
@ -111,7 +107,7 @@ impl Identity {
let mut digest = sha.finish();
v0_frankenhash(&mut digest, genmem_ptr);
if digest[0] < IDENTITY_V0_POW_THRESHOLD {
if digest[0] < V0_POW_THRESHOLD {
let addr = Address::from_bytes(&digest[59..64]);
if addr.is_some() {
unsafe { dealloc(genmem_ptr, genmem_layout) };
@ -145,7 +141,7 @@ impl Identity {
// ECDSA is a randomized signature algorithm, so each signature will be different.
let sig = p521_ecdsa.sign(&sign_buf).unwrap();
let bh = balloon::hash::<{ V1_BALLOON_SPACE_COST }, { V1_BALLOON_TIME_COST }, { V1_BALLOON_DELTA }>(&sig, V1_BALLOON_SALT);
if bh[0] < IDENTITY_V1_POW_THRESHOLD {
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();
@ -210,7 +206,7 @@ impl Identity {
/// 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_IDENTITY_GEN_MEMORY, 8).unwrap();
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();
@ -219,7 +215,7 @@ impl Identity {
let mut digest = sha.finish();
v0_frankenhash(&mut digest, genmem_ptr);
unsafe { dealloc(genmem_ptr, genmem_layout) };
(digest[0] < IDENTITY_V0_POW_THRESHOLD) && Address::from_bytes(&digest[59..64]).unwrap().eq(&self.address)
(digest[0] < V0_POW_THRESHOLD) && Address::from_bytes(&digest[59..64]).unwrap().eq(&self.address)
} else {
false
}
@ -232,7 +228,7 @@ impl Identity {
signing_buf[(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)].copy_from_slice((*p521).1.public_key_bytes());
if (*p521).1.verify(&signing_buf, &(*p521).2) {
let bh = balloon::hash::<{ V1_BALLOON_SPACE_COST }, { V1_BALLOON_TIME_COST }, { V1_BALLOON_DELTA }>(&(*p521).2, V1_BALLOON_SALT);
(bh[0] < IDENTITY_V1_POW_THRESHOLD) && bh.eq(&(*p521).3) && Address::from_bytes(&bh[43..48]).unwrap().eq(&self.address)
(bh[0] < V1_POW_THRESHOLD) && bh.eq(&(*p521).3) && Address::from_bytes(&bh[43..48]).unwrap().eq(&self.address)
} else {
false
}
@ -247,12 +243,8 @@ impl Identity {
pub fn agree(&self, other_identity: &Identity) -> Option<Secret<48>> {
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| {
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
@ -285,25 +277,21 @@ impl Identity {
pub fn sign(&self, msg: &[u8]) -> Option<Vec<u8>> {
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)
})
})
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)
}))
})
}
@ -312,34 +300,20 @@ impl Identity {
self.v1.as_ref().map_or_else(|| {
crate::crypto::c25519::ed25519_verify(&self.ed25519, signature, msg)
}, |p521| {
if signature.len() == IDENTITY_TYPE_1_SIGNATURE_SIZE {
(*p521).1.verify(msg, &signature[0..P521_ECDSA_SIGNATURE_SIZE]) && crate::crypto::c25519::ed25519_verify(&self.ed25519, &signature[P521_ECDSA_SIGNATURE_SIZE..], msg)
} else {
false
}
signature.len() == IDENTITY_TYPE_1_SIGNATURE_SIZE && (*p521).1.verify(msg, &signature[0..P521_ECDSA_SIGNATURE_SIZE]) && crate::crypto::c25519::ed25519_verify(&self.ed25519, &signature[P521_ECDSA_SIGNATURE_SIZE..], msg)
})
}
/// Get this identity's type.
#[inline(always)]
pub fn id_type(&self) -> Type {
if self.v1.is_some() {
Type::P521
} else {
Type::C25519
}
}
pub fn id_type(&self) -> Type { if self.v1.is_some() { Type::P521 } else { Type::C25519 } }
/// Returns true if this identity also holds its secret keys.
#[inline(always)]
pub fn has_secrets(&self) -> bool {
self.secrets.is_some()
}
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();
}
pub fn forget_secrets(&mut self) { let _ = self.secrets.take(); }
/// Append this in binary format to a buffer.
pub fn marshal<const BL: usize>(&self, buf: &mut Buffer<BL>, include_private: bool) -> std::io::Result<()> {
@ -598,9 +572,7 @@ impl Eq for Identity {}
impl PartialOrd for Identity {
#[inline(always)]
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
fn partial_cmp(&self, other: &Self) -> Option<Ordering> { Some(self.cmp(other)) }
}
impl Ord for Identity {

View file

@ -348,12 +348,8 @@ impl InetAddress {
let ip = &*(&self.sin.sin_addr.s_addr as *const u32).cast::<[u8; 4]>();
format!("{}.{}.{}.{}", ip[0], ip[1], ip[2], ip[3])
}
AF_INET6 => {
Ipv6Addr::from(*(&(self.sin6.sin6_addr) as *const in6_addr).cast::<[u8; 16]>()).to_string()
}
_ => {
String::from("(null)")
}
AF_INET6 => Ipv6Addr::from(*(&(self.sin6.sin6_addr) as *const in6_addr).cast::<[u8; 16]>()).to_string(),
_ => String::from("(null)")
}
}
}
@ -377,9 +373,7 @@ impl InetAddress {
b[18] = *(&self.sin6.sin6_port as *const u16).cast::<u8>().offset(1);
})
}
_ => {
buf.append_u8(0)
}
_ => buf.append_u8(0)
}
}
}
@ -455,7 +449,6 @@ impl FromStr for InetAddress {
}
impl PartialEq for InetAddress {
#[inline(always)]
fn eq(&self, other: &Self) -> bool {
unsafe {
if self.sa.sa_family == other.sa.sa_family {

View file

@ -94,13 +94,13 @@ impl Locator {
self.subject.marshal(buf)?;
self.signer.marshal(buf)?;
buf.append_u64(self.timestamp as u64)?;
buf.append_varint(self.endpoints.len() as u64);
buf.append_varint(self.endpoints.len() as u64)?;
for e in self.endpoints.iter() {
e.marshal(buf)?;
}
buf.append_varint(0); // length of any additional fields
buf.append_varint(0)?; // length of any additional fields
if !exclude_signature {
buf.append_varint(self.signature.len() as u64);
buf.append_varint(self.signature.len() as u64)?;
buf.append_bytes(self.signature.as_slice())?;
}
Ok(())

View file

@ -11,23 +11,12 @@ pub struct MAC(NonZeroU64);
impl MAC {
#[inline(always)]
pub fn from_u64(i: u64) -> Option<MAC> {
if i != 0 {
Some(MAC(unsafe { NonZeroU64::new_unchecked(i & 0xffffffffffff) }))
} else {
None
}
}
pub fn from_u64(i: u64) -> Option<MAC> { NonZeroU64::new(i & 0xffffffffffff).map_or(None, |i| Some(MAC(i))) }
#[inline(always)]
pub fn from_bytes(b: &[u8]) -> Option<MAC> {
if b.len() >= 6 {
let i = (b[0] as u64) << 40 | (b[1] as u64) << 32 | (b[2] as u64) << 24 | (b[3] as u64) << 16 as u64 | (b[4] as u64) << 8 | b[5] as u64;
if i != 0 {
Some(MAC(unsafe { NonZeroU64::new_unchecked(i) }))
} else {
None
}
NonZeroU64::new((b[0] as u64) << 40 | (b[1] as u64) << 32 | (b[2] as u64) << 24 | (b[3] as u64) << 16 as u64 | (b[4] as u64) << 8 | b[5] as u64).map_or(None, |i| Some(MAC(i)))
} else {
None
}

View file

@ -6,6 +6,7 @@ pub mod locator;
pub mod rootset;
// These are either only used inside network-hypervisor or are selectively exported below.
#[allow(unused)]
pub(crate) mod protocol;
pub(crate) mod buffer;
pub(crate) mod node;

View file

@ -86,6 +86,9 @@ pub trait VL1CallerInterface {
}
/// Trait implemented by VL2 to handle messages after they are unwrapped by VL1.
///
/// This normally isn't used from outside this crate except for testing or if you want to harness VL1
/// for some entirely unrelated purpose.
pub trait VL1PacketHandler {
/// Handle a packet, returning true if the verb was recognized.
///
@ -95,10 +98,10 @@ pub trait VL1PacketHandler {
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;
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 }>, cursor: &mut usize) -> 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;
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 }>, cursor: &mut usize) -> bool;
}
#[derive(Default)]
@ -120,8 +123,7 @@ pub struct Node {
whois: WhoisQueue,
buffer_pool: Pool<Buffer<{ PACKET_SIZE_MAX }>, PooledBufferFactory<{ PACKET_SIZE_MAX }>>,
secure_prng: SecureRandom,
pub(crate) fips_mode: bool,
pub(crate) wimp: bool,
fips_mode: bool,
}
impl Node {
@ -164,19 +166,15 @@ impl Node {
buffer_pool: Pool::new(64, PooledBufferFactory),
secure_prng: SecureRandom::get(),
fips_mode: false,
wimp: false,
})
}
/// Get address, short for .identity().address()
#[inline(always)]
pub fn address(&self) -> Address { self.identity.address() }
/// Get identity, which includes secret keys.
#[inline(always)]
pub fn identity(&self) -> &Identity { &self.identity }
/// Get this node's current locator or None if no locator created.
#[inline(always)]
pub fn locator(&self) -> Option<Arc<Locator>> { self.locator.lock().clone() }
@ -187,6 +185,9 @@ impl Node {
/// Get a peer by address.
pub fn peer(&self, a: Address) -> Option<Arc<Peer>> { self.peers.get(&a).map(|peer| peer.value().clone()) }
#[inline(always)]
pub fn fips_mode(&self) -> bool { self.fips_mode }
/// Get all peers currently in the peer cache.
pub fn peers(&self) -> Vec<Arc<Peer>> {
let mut v: Vec<Arc<Peer>> = Vec::new();
@ -319,9 +320,7 @@ impl Node {
self.paths.get(ep).map_or_else(|| {
let p = Arc::new(Path::new(ep.clone(), local_socket, local_interface));
self.paths.insert(ep.clone(), p.clone()).unwrap_or(p) // if another thread added one, return that instead
}, |path| {
path.value().clone()
})
}, |path| path.value().clone())
}
}

View file

@ -36,7 +36,7 @@ impl Path {
local_interface,
last_send_time_ticks: AtomicI64::new(0),
last_receive_time_ticks: AtomicI64::new(0),
fragmented_packets: Mutex::new(HashMap::with_capacity_and_hasher(8, U64PassThroughHasher::new())),
fragmented_packets: Mutex::new(HashMap::with_capacity_and_hasher(4, U64PassThroughHasher::new())),
}
}

View file

@ -10,7 +10,7 @@ use aes_gmac_siv::{AesCtr, AesGmacSiv};
use crate::{VERSION_MAJOR, VERSION_MINOR, VERSION_PROTO, VERSION_REVISION};
use crate::crypto::c25519::C25519KeyPair;
use crate::crypto::hash::SHA384;
use crate::crypto::hash::{SHA384, SHA384_HASH_SIZE};
use crate::crypto::kbkdf::zt_kbkdf_hmac_sha384;
use crate::crypto::p521::P521KeyPair;
use crate::crypto::poly1305::Poly1305;
@ -31,14 +31,10 @@ struct AesGmacSivPoolFactory(Secret<48>, Secret<48>);
impl PoolFactory<AesGmacSiv> for AesGmacSivPoolFactory {
#[inline(always)]
fn create(&self) -> AesGmacSiv {
AesGmacSiv::new(&self.0.0[0..32], &self.1.0[0..32])
}
fn create(&self) -> AesGmacSiv { AesGmacSiv::new(&self.0.0[0..32], &self.1.0[0..32]) }
#[inline(always)]
fn reset(&self, obj: &mut AesGmacSiv) {
obj.reset();
}
fn reset(&self, obj: &mut AesGmacSiv) { obj.reset(); }
}
struct PeerSecret {
@ -92,7 +88,7 @@ pub struct Peer {
// Either None or the current ephemeral key pair whose public keys are on offer.
ephemeral_pair: Mutex<Option<EphemeralKeyPair>>,
// Paths sorted in ascending order of quality / preference.
// Paths sorted in descending order of quality / preference.
paths: Mutex<Vec<Arc<Path>>>,
// Local external address most recently reported by this peer (IP transport only).
@ -144,8 +140,7 @@ fn salsa_poly_create(secret: &PeerSecret, header: &PacketHeader, packet_size: us
let mut salsa = Salsa::new(&key.0[0..32], header.id_bytes(), true).unwrap();
let mut poly1305_key = [0_u8; 32];
salsa.crypt_in_place(&mut poly1305_key);
let mut poly = Poly1305::new(&poly1305_key).unwrap();
(salsa, poly)
(salsa, Poly1305::new(&poly1305_key).unwrap())
}
impl Peer {
@ -192,9 +187,7 @@ impl Peer {
/// Get the next packet ID / IV.
#[inline(always)]
pub(crate) fn next_packet_id(&self) -> PacketID {
self.packet_id_counter.fetch_add(1, Ordering::Relaxed)
}
pub(crate) fn next_packet_id(&self) -> PacketID { self.packet_id_counter.fetch_add(1, Ordering::Relaxed) }
/// Receive, decrypt, authenticate, and process an incoming packet from this peer.
/// If the packet comes in multiple fragments, the fragments slice should contain all
@ -287,15 +280,34 @@ impl Peer {
self.total_bytes_received.fetch_add((payload.len() + PACKET_HEADER_SIZE) as u64, Ordering::Relaxed);
let _ = payload.u8_at(0).map(|verb| {
let mut extended_authentication = false;
if (verb & VERB_FLAG_EXTENDED_AUTHENTICATION) != 0 {
let auth_bytes = payload.as_bytes();
if auth_bytes.len() >= (1 + SHA384_HASH_SIZE) {
let packet_hmac_start = auth_bytes.len() - SHA384_HASH_SIZE;
if !SHA384::hmac(self.static_secret_packet_hmac.as_ref(), &auth_bytes[1..packet_hmac_start]).eq(&auth_bytes[packet_hmac_start..]) {
return;
}
extended_authentication = true;
unsafe { payload.set_size(payload.len() - SHA384_HASH_SIZE) };
} else {
return;
}
}
if (verb & VERB_FLAG_COMPRESSED) != 0 {
}
let verb = verb & VERB_MASK;
// For performance reasons we let VL2 handle packets first. It returns false
// if it didn't handle the packet, in which case it's handled at VL1.
let verb = verb & VERB_MASK;
if !ph.handle_packet(self, source_path, forward_secrecy, verb, &payload) {
match verb {
//VERB_VL1_NOP => {}
VERB_VL1_HELLO => self.receive_hello(ci, node, time_ticks, source_path, &payload),
VERB_VL1_ERROR => self.receive_error(ci, node, time_ticks, source_path, &payload),
VERB_VL1_OK => self.receive_ok(ci, node, time_ticks, source_path, &payload),
VERB_VL1_ERROR => self.receive_error(ci, ph, node, time_ticks, source_path, forward_secrecy, &payload),
VERB_VL1_OK => self.receive_ok(ci, ph, node, time_ticks, source_path, forward_secrecy, &payload),
VERB_VL1_WHOIS => self.receive_whois(ci, node, time_ticks, source_path, &payload),
VERB_VL1_RENDEZVOUS => self.receive_rendezvous(ci, node, time_ticks, source_path, &payload),
VERB_VL1_ECHO => self.receive_echo(ci, node, time_ticks, source_path, &payload),
@ -406,7 +418,7 @@ impl Peer {
header.flags_cipher_hops = CIPHER_NOCRYPT_POLY1305;
}).is_ok());
debug_assert!(packet.append_and_init_struct(|header: &mut message_component_structs::HelloFixedHeaderFields| {
header.verb = VERB_VL1_HELLO | VERB_FLAG_HMAC;
header.verb = VERB_VL1_HELLO | VERB_FLAG_EXTENDED_AUTHENTICATION;
header.version_proto = VERSION_PROTO;
header.version_major = VERSION_MAJOR;
header.version_minor = VERSION_MINOR;
@ -456,12 +468,9 @@ impl Peer {
dict.set_str(HELLO_DICT_KEY_OS_NAME, std::env::consts::OS);
}
let mut flags = String::new();
if node.fips_mode {
if node.fips_mode() {
flags.push('F');
}
if node.wimp {
flags.push('w');
}
dict.set_str(HELLO_DICT_KEY_FLAGS, flags.as_str());
debug_assert!(dict.write_to(&mut packet).is_ok());
@ -470,7 +479,9 @@ impl Peer {
dict_aes.crypt_in_place(&mut packet.as_bytes_mut()[dict_start_position..]);
drop(dict_aes);
debug_assert!(packet.append_bytes_fixed(&SHA384::hmac(self.static_secret_packet_hmac.as_ref(), &packet.as_bytes()[PACKET_HEADER_SIZE + 1..])).is_ok());
debug_assert!(packet.append_u16(0).is_ok());
debug_assert!(packet.append_bytes_fixed(&SHA384::hmac(self.static_secret_packet_hmac.as_ref(), packet.as_bytes_starting_at(PACKET_HEADER_SIZE).unwrap())).is_ok());
let (_, mut poly) = salsa_poly_create(&self.static_secret, packet.struct_at::<PacketHeader>(0).unwrap(), packet.len());
poly.update(packet.as_bytes_starting_at(PACKET_HEADER_SIZE).unwrap());
@ -497,10 +508,48 @@ impl Peer {
fn receive_hello<CI: VL1CallerInterface>(&self, ci: &CI, node: &Node, time_ticks: i64, source_path: &Arc<Path>, payload: &Buffer<{ PACKET_SIZE_MAX }>) {}
#[inline(always)]
fn receive_error<CI: VL1CallerInterface>(&self, ci: &CI, node: &Node, time_ticks: i64, source_path: &Arc<Path>, payload: &Buffer<{ PACKET_SIZE_MAX }>) {}
fn receive_error<CI: VL1CallerInterface, PH: VL1PacketHandler>(&self, ci: &CI, ph: &PH, node: &Node, time_ticks: i64, source_path: &Arc<Path>, forward_secrecy: bool, payload: &Buffer<{ PACKET_SIZE_MAX }>) {
let mut cursor: usize = 0;
let _ = payload.read_struct::<message_component_structs::ErrorHeader>(&mut cursor).map(|error_header| {
let in_re_packet_id = error_header.in_re_packet_id;
let current_packet_id_counter = self.packet_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
}) {
match error_header.in_re_verb {
_ => {
ph.handle_error(self, source_path, forward_secrecy, error_header.in_re_verb, in_re_packet_id, error_header.error_code, payload, &mut cursor);
}
}
}
});
}
#[inline(always)]
fn receive_ok<CI: VL1CallerInterface>(&self, ci: &CI, node: &Node, time_ticks: i64, source_path: &Arc<Path>, payload: &Buffer<{ PACKET_SIZE_MAX }>) {}
fn receive_ok<CI: VL1CallerInterface, PH: VL1PacketHandler>(&self, ci: &CI, ph: &PH, node: &Node, time_ticks: i64, source_path: &Arc<Path>, forward_secrecy: bool, payload: &Buffer<{ PACKET_SIZE_MAX }>) {
let mut cursor: usize = 0;
let _ = payload.read_struct::<message_component_structs::OkHeader>(&mut cursor).map(|ok_header| {
let in_re_packet_id = ok_header.in_re_packet_id;
let current_packet_id_counter = self.packet_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
}) {
match ok_header.in_re_verb {
VERB_VL1_HELLO => {
}
VERB_VL1_WHOIS => {
}
_ => {
ph.handle_ok(self, source_path, forward_secrecy, ok_header.in_re_verb, in_re_packet_id, payload, &mut cursor);
}
}
}
});
}
#[inline(always)]
fn receive_whois<CI: VL1CallerInterface>(&self, ci: &CI, node: &Node, time_ticks: i64, source_path: &Arc<Path>, payload: &Buffer<{ PACKET_SIZE_MAX }>) {}
@ -518,9 +567,7 @@ impl Peer {
fn receive_user_message<CI: VL1CallerInterface>(&self, ci: &CI, node: &Node, time_ticks: i64, source_path: &Arc<Path>, payload: &Buffer<{ PACKET_SIZE_MAX }>) {}
/// Get current best path or None if there are no direct paths to this peer.
pub fn direct_path(&self) -> Option<Arc<Path>> {
self.paths.lock().last().map(|p| p.clone())
}
pub fn direct_path(&self) -> Option<Arc<Path>> { self.paths.lock().first().map(|p| p.clone()) }
/// Get either the current best direct path or an indirect path.
pub fn path(&self, node: &Node) -> Option<Arc<Path>> {

View file

@ -131,8 +131,8 @@ pub const FRAGMENT_INDICATOR: u8 = 0xff;
/// Verb (inner) flag indicating that the packet's payload (after the verb) is LZ4 compressed.
pub const VERB_FLAG_COMPRESSED: u8 = 0x80;
/// Verb (inner) flag indicating that payload after verb is authenticated with HMAC-SHA384.
pub const VERB_FLAG_HMAC: u8 = 0x40;
/// Verb (inner) flag indicating that payload is authenticated with HMAC-SHA384.
pub const VERB_FLAG_EXTENDED_AUTHENTICATION: u8 = 0x40;
/// Mask to get only the verb from the verb + verb flags byte.
pub const VERB_MASK: u8 = 0x1f;
@ -146,8 +146,8 @@ pub const PROTOCOL_MAX_HOPS: u8 = 7;
/// Maximum number of hops to allow.
pub const FORWARD_MAX_HOPS: u8 = 3;
/// Maximum difference between an OK in-re packet ID and the current packet ID counter.
pub const OK_PACKET_SEQUENCE_CUTOFF: u64 = 1000;
/// Maximum difference between current packet ID counter and OK/ERROR in-re packet ID.
pub const PACKET_RESPONSE_COUNTER_DELTA_MAX: u64 = 1024;
/// Frequency for WHOIS retries
pub const WHOIS_RETRY_INTERVAL: i64 = 1000;
@ -267,6 +267,24 @@ impl FragmentHeader {
pub(crate) mod message_component_structs {
use crate::vl1::buffer::RawObject;
use crate::vl1::protocol::PacketID;
#[repr(packed)]
pub struct OkHeader {
pub in_re_verb: u8,
pub in_re_packet_id: PacketID,
}
unsafe impl RawObject for OkHeader {}
#[repr(packed)]
pub struct ErrorHeader {
pub in_re_verb: u8,
pub in_re_packet_id: PacketID,
pub error_code: u8,
}
unsafe impl RawObject for ErrorHeader {}
#[repr(packed)]
pub struct HelloFixedHeaderFields {
@ -300,6 +318,8 @@ mod tests {
#[test]
fn representation() {
assert_eq!(size_of::<message_component_structs::OkHeader>(), 9);
assert_eq!(size_of::<message_component_structs::ErrorHeader>(), 10);
assert_eq!(size_of::<PacketHeader>(), PACKET_HEADER_SIZE);
assert_eq!(size_of::<FragmentHeader>(), FRAGMENT_HEADER_SIZE);

View file

@ -103,10 +103,10 @@ pub struct Root {
/// as at least one of the old roots is up to distribute the new ones.
#[derive(PartialEq, Eq)]
pub struct RootSet {
pub timestamp: i64,
pub name: String,
pub contact: String,
pub roots: BTreeSet<Root>,
timestamp: i64,
name: String,
contact: String,
roots: BTreeSet<Root>,
signer: Vec<u8>,
signature: Vec<u8>,
root_set_type: Type,
@ -185,9 +185,9 @@ impl RootSet {
buf.append_u8(ROOT_SET_TYPE_ED25519_P521)?;
buf.append_u64(self.timestamp as u64)?;
buf.append_u8(name.len() as u8)?;
buf.append_bytes(name);
buf.append_bytes(name)?;
buf.append_u8(contact.len() as u8)?;
buf.append_bytes(contact);
buf.append_bytes(contact)?;
if self.signer.len() != (ED25519_PUBLIC_KEY_SIZE + P521_PUBLIC_KEY_SIZE) {
return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "signer can only be 164 bytes"));
}

View file

@ -19,21 +19,15 @@ struct WhoisQueueItem {
retry_count: u16,
}
pub(crate) struct WhoisQueue {
queue: Mutex<HashMap<Address, WhoisQueueItem>>
}
pub(crate) struct WhoisQueue(Mutex<HashMap<Address, WhoisQueueItem>>);
impl WhoisQueue {
pub(crate) const INTERVAL: i64 = WHOIS_RETRY_INTERVAL;
pub fn new() -> Self {
Self {
queue: Mutex::new(HashMap::new())
}
}
pub fn new() -> Self { Self(Mutex::new(HashMap::new())) }
pub fn query<CI: VL1CallerInterface>(&self, node: &Node, ci: &CI, target: Address, packet: Option<QueuedPacket>) {
let mut q = self.queue.lock();
let mut q = self.0.lock();
let qi = q.entry(target).or_insert_with(|| WhoisQueueItem {
packet_queue: LinkedList::new(),
@ -55,14 +49,14 @@ impl WhoisQueue {
/// Remove a WHOIS request from the queue and call the supplied function for all queued packets.
pub fn response_received_get_packets<F: FnMut(&mut QueuedPacket)>(&self, address: Address, packet_handler: F) {
let mut qi = self.queue.lock().remove(&address);
let mut qi = self.0.lock().remove(&address);
let _ = qi.map(|mut qi| qi.packet_queue.iter_mut().for_each(packet_handler));
}
/// Called every INTERVAL during background tasks.
pub fn on_interval<CI: VL1CallerInterface>(&self, node: &Node, ci: &CI, time_ticks: i64) {
let mut targets: Vec<Address> = Vec::new();
self.queue.lock().retain(|target, qi| {
self.0.lock().retain(|target, qi| {
if qi.retry_count < WHOIS_RETRY_MAX {
if qi.retry_gate.gate(time_ticks) {
qi.retry_count += 1;