Cleanup and implement some serde handlers for some common core data structures.

This commit is contained in:
Adam Ierymenko 2022-04-27 15:09:21 -04:00
parent b346f3ff07
commit 6e55419581
No known key found for this signature in database
GPG key ID: C8877CF2D7A5D7F3
8 changed files with 208 additions and 25 deletions

View file

@ -18,9 +18,7 @@ lz4_flex = { version = "^0", features = ["safe-encode", "safe-decode", "checked-
dashmap = "^4"
parking_lot = "^0"
lazy_static = "^1"
serde = "^1"
serde_derive = "^1"
serde_json = "^1"
serde = { version = "^1", features = ["derive"], default-features = false }
[target."cfg(not(windows))".dependencies]
libc = "^0"

View file

@ -10,14 +10,15 @@ use std::hash::{Hash, Hasher};
use std::num::NonZeroU64;
use std::str::FromStr;
use serde_derive::{Deserialize, Serialize};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use crate::error::InvalidFormatError;
use crate::util::buffer::Buffer;
use crate::util::hex::HEX_CHARS;
use crate::vl1::protocol::{ADDRESS_RESERVED_PREFIX, ADDRESS_SIZE};
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
/// A unique address on the global ZeroTier VL1 network.
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
#[repr(transparent)]
pub struct Address(NonZeroU64);
@ -91,3 +92,57 @@ impl Hash for Address {
state.write_u64(self.0.get());
}
}
impl Serialize for Address {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
if serializer.is_human_readable() {
serializer.serialize_str(self.to_string().as_str())
} else {
serializer.serialize_bytes(&self.to_bytes())
}
}
}
struct AddressVisitor;
impl<'de> serde::de::Visitor<'de> for AddressVisitor {
type Value = Address;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("a ZeroTier address")
}
fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
if v.len() == ADDRESS_SIZE {
Address::from_bytes(v).map_or_else(|| Err(E::custom("object too large")), |a| Ok(a))
} else {
Err(E::custom("object too large"))
}
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Address::from_str(v).map_err(|e| E::custom(e.to_string()))
}
}
impl<'de> Deserialize<'de> for Address {
fn deserialize<D>(deserializer: D) -> Result<Address, D::Error>
where
D: Deserializer<'de>,
{
if deserializer.is_human_readable() {
deserializer.deserialize_str(AddressVisitor)
} else {
deserializer.deserialize_bytes(AddressVisitor)
}
}
}

View file

@ -17,7 +17,7 @@ use std::str::FromStr;
use lazy_static::lazy_static;
use serde_derive::{Deserialize, Serialize};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use zerotier_core_crypto::c25519::*;
use zerotier_core_crypto::hash::*;
@ -45,12 +45,14 @@ pub const IDENTITY_ALGORITHM_ALL: u8 = 0xff;
pub const MAX_MARSHAL_SIZE: usize =
ADDRESS_SIZE + C25519_PUBLIC_KEY_SIZE + ED25519_PUBLIC_KEY_SIZE + C25519_SECRET_KEY_SIZE + ED25519_SECRET_KEY_SIZE + P384_PUBLIC_KEY_SIZE + P384_PUBLIC_KEY_SIZE + P384_SECRET_KEY_SIZE + P384_SECRET_KEY_SIZE + P384_ECDSA_SIGNATURE_SIZE + ED25519_SIGNATURE_SIZE + 16;
/// Secret keys associated with NIST P-384 public keys.
#[derive(Clone)]
pub struct IdentityP384Secret {
pub ecdh: P384KeyPair,
pub ecdsa: P384KeyPair,
}
/// NIST P-384 public keys and signatures binding them bidirectionally to V0 c25519 keys.
#[derive(Clone)]
pub struct IdentityP384Public {
pub ecdh: P384PublicKey,
@ -59,6 +61,7 @@ pub struct IdentityP384Public {
pub ed25519_self_signature: [u8; ED25519_SIGNATURE_SIZE],
}
/// Secret keys associated with an identity.
#[derive(Clone)]
pub struct IdentitySecret {
pub c25519: C25519KeyPair,
@ -66,6 +69,14 @@ pub struct IdentitySecret {
pub p384: Option<IdentityP384Secret>,
}
/// A unique identity on the global VL1 network.
///
/// Identity implements serde Serialize and Deserialize. Identities are serialized as strings
/// for human-readable formats and binary otherwise.
///
/// SECURITY NOTE: for security reasons secret keys are NOT exported by default by to_string()
/// or by the serde serializer. If you want secrets you must use to_string_with_options() or
/// marshal(). This is to prevent accidental leakage of secrets by naive code.
#[derive(Clone)]
pub struct Identity {
pub address: Address,
@ -76,9 +87,6 @@ pub struct Identity {
pub fingerprint: [u8; SHA512_HASH_SIZE],
}
#[derive(Eq, PartialEq, Clone, Debug, Ord, PartialOrd, Deserialize, Serialize)]
pub struct NetworkId(pub u64);
#[inline(always)]
fn concat_arrays_2<const A: usize, const B: usize, const S: usize>(a: &[u8; A], b: &[u8; B]) -> [u8; S] {
assert_eq!(A + B, S);
@ -726,6 +734,65 @@ impl Hash for Identity {
}
}
impl Serialize for Identity {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
if serializer.is_human_readable() {
serializer.serialize_str(self.to_string_with_options(IDENTITY_ALGORITHM_ALL, false).as_str())
} else {
let mut tmp: Buffer<MAX_MARSHAL_SIZE> = Buffer::new();
assert!(self.marshal(&mut tmp, IDENTITY_ALGORITHM_ALL, false).is_ok());
serializer.serialize_bytes(tmp.as_bytes())
}
}
}
struct IdentityVisitor;
impl<'de> serde::de::Visitor<'de> for IdentityVisitor {
type Value = Identity;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("a ZeroTier identity")
}
fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
if v.len() <= MAX_MARSHAL_SIZE {
let mut tmp: Buffer<MAX_MARSHAL_SIZE> = Buffer::new();
let _ = tmp.append_bytes(v);
let mut cursor = 0;
Identity::unmarshal(&tmp, &mut cursor).map_err(|e| E::custom(e.to_string()))
} else {
Err(E::custom("object too large"))
}
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Identity::from_str(v).map_err(|e| E::custom(e.to_string()))
}
}
impl<'de> Deserialize<'de> for Identity {
fn deserialize<D>(deserializer: D) -> Result<Identity, D::Error>
where
D: Deserializer<'de>,
{
if deserializer.is_human_readable() {
deserializer.deserialize_str(IdentityVisitor)
} else {
deserializer.deserialize_bytes(IdentityVisitor)
}
}
}
const ADDRESS_DERIVATION_HASH_MEMORY_SIZE: usize = 2097152;
/// This is a compound hasher used for the work function that derives an address.

View file

@ -11,9 +11,12 @@ use std::hash::{Hash, Hasher};
use std::num::NonZeroU64;
use std::str::FromStr;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use crate::error::InvalidFormatError;
use crate::util::buffer::Buffer;
/// An Ethernet MAC address.
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
#[repr(transparent)]
pub struct MAC(NonZeroU64);
@ -87,3 +90,57 @@ impl Hash for MAC {
state.write_u64(self.0.get());
}
}
impl Serialize for MAC {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
if serializer.is_human_readable() {
serializer.serialize_str(self.to_string().as_str())
} else {
serializer.serialize_bytes(&self.to_bytes())
}
}
}
struct MACVisitor;
impl<'de> serde::de::Visitor<'de> for MACVisitor {
type Value = MAC;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("a ZeroTier address")
}
fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
if v.len() == 6 {
MAC::from_bytes(v).map_or_else(|| Err(E::custom("object too large")), |a| Ok(a))
} else {
Err(E::custom("object too large"))
}
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
MAC::from_str(v).map_err(|e| E::custom(e.to_string()))
}
}
impl<'de> Deserialize<'de> for MAC {
fn deserialize<D>(deserializer: D) -> Result<MAC, D::Error>
where
D: Deserializer<'de>,
{
if deserializer.is_human_readable() {
deserializer.deserialize_str(MACVisitor)
} else {
deserializer.deserialize_bytes(MACVisitor)
}
}
}

View file

@ -509,7 +509,7 @@ impl Peer {
// Set legacy poly1305 MAC in packet header. Newer nodes check HMAC-SHA512 but older ones only use this.
let (_, mut poly) = salsa_poly_create(&self.identity_symmetric_key, packet.struct_at::<PacketHeader>(0).unwrap(), packet.len());
poly.update(packet.as_bytes_starting_at(PACKET_HEADER_SIZE).unwrap());
packet.as_mut_range_fixed::<HEADER_MAC_FIELD_INDEX, { HEADER_MAC_FIELD_INDEX + 8 }>().copy_from_slice(&poly.finish()[0..8]);
packet.as_mut()[HEADER_MAC_FIELD_INDEX..HEADER_MAC_FIELD_INDEX + 8].copy_from_slice(&poly.finish()[0..8]);
self.last_send_time_ticks.store(time_ticks, Ordering::Relaxed);
self.total_bytes_sent.fetch_add(packet.len() as u64, Ordering::Relaxed);

View file

@ -48,13 +48,13 @@ pub const KBKDF_KEY_USAGE_LABEL_EPHEMERAL_RATCHET_KEY: u8 = b'e';
pub const EPHEMERAL_SECRET_REKEY_AFTER_TIME: i64 = 300000; // 5 minutes
/// Maximum number of times to use an ephemeral secret before trying to replace it.
pub const EPHEMERAL_SECRET_REKEY_AFTER_USES: u32 = 536870912; // 1/4 the NIST/FIPS security bound of 2^31
pub const EPHEMERAL_SECRET_REKEY_AFTER_USES: usize = 536870912; // 1/4 the NIST/FIPS security bound of 2^31
/// Ephemeral secret reject after time.
pub const EPHEMERAL_SECRET_REJECT_AFTER_TIME: i64 = EPHEMERAL_SECRET_REKEY_AFTER_TIME * 2;
/// Ephemeral secret reject after uses.
pub const EPHEMERAL_SECRET_REJECT_AFTER_USES: u32 = 2147483648; // NIST/FIPS security bound
pub const EPHEMERAL_SECRET_REJECT_AFTER_USES: usize = 2147483648; // NIST/FIPS security bound
pub const SESSION_METADATA_INSTANCE_ID: &'static str = "i";
pub const SESSION_METADATA_CLOCK: &'static str = "t";
@ -211,7 +211,7 @@ pub fn compress_packet(src: &[u8], dest: &mut Buffer<{ PACKET_SIZE_MAX }>) -> bo
///
/// 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.
#[repr(packed)]
#[repr(C, packed)]
pub struct PacketHeader {
pub id: [u8; 8],
pub dest: [u8; 5],
@ -275,7 +275,7 @@ impl PacketHeader {
/// 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
/// bit set and remaining fragments being these.
#[repr(packed)]
#[repr(C, packed)]
pub struct FragmentHeader {
pub id: [u8; 8], // (outer) packet ID
pub dest: [u8; 5], // destination address
@ -324,7 +324,7 @@ impl FragmentHeader {
pub(crate) mod message_component_structs {
use crate::util::buffer::RawObject;
#[repr(packed)]
#[repr(C, packed)]
pub struct OkHeader {
pub in_re_verb: u8,
pub in_re_message_id: [u8; 8],
@ -332,7 +332,7 @@ pub(crate) mod message_component_structs {
unsafe impl RawObject for OkHeader {}
#[repr(packed)]
#[repr(C, packed)]
pub struct ErrorHeader {
pub in_re_verb: u8,
pub in_re_message_id: [u8; 8],
@ -341,7 +341,7 @@ pub(crate) mod message_component_structs {
unsafe impl RawObject for ErrorHeader {}
#[repr(packed)]
#[repr(C, packed)]
pub struct HelloFixedHeaderFields {
pub verb: u8,
pub version_proto: u8,
@ -353,7 +353,7 @@ pub(crate) mod message_component_structs {
unsafe impl RawObject for HelloFixedHeaderFields {}
#[repr(packed)]
#[repr(C, packed)]
pub struct OkHelloFixedHeaderFields {
pub timestamp_echo: [u8; 8], // u64
pub version_proto: u8,

View file

@ -6,7 +6,8 @@
* https://www.zerotier.com/
*/
use std::sync::atomic::{AtomicU32, Ordering};
use std::sync::atomic::{AtomicUsize, Ordering};
use zerotier_core_crypto::aes_gmac_siv::AesGmacSiv;
use zerotier_core_crypto::kbkdf::*;
use zerotier_core_crypto::secret::Secret;
@ -20,7 +21,7 @@ pub(crate) struct AesGmacSivPoolFactory(Secret<32>, Secret<32>);
impl PoolFactory<AesGmacSiv> for AesGmacSivPoolFactory {
#[inline(always)]
fn create(&self) -> AesGmacSiv {
AesGmacSiv::new(&self.0 .0, &self.1 .0)
AesGmacSiv::new(self.0.as_bytes(), self.1.as_bytes())
}
#[inline(always)]
@ -33,16 +34,23 @@ impl PoolFactory<AesGmacSiv> for AesGmacSivPoolFactory {
///
/// This contains the key and several sub-keys and ciphers keyed with sub-keys.
pub(crate) struct SymmetricSecret {
/// Master key from which other keys are derived.
pub key: Secret<64>,
/// Key used for HMAC extended validation on packets like HELLO.
pub packet_hmac_key: Secret<64>,
/// Key used with ephemeral keying/re-keying.
pub ephemeral_ratchet_key: Secret<64>,
/// Pool of keyed AES-GMAC-SIV engines (pooled to avoid AES re-init every time).
pub aes_gmac_siv: Pool<AesGmacSiv, AesGmacSivPoolFactory>,
}
impl PartialEq for SymmetricSecret {
#[inline(always)]
fn eq(&self, other: &Self) -> bool {
self.key.0.eq(&other.key.0)
self.key == other.key
}
}
@ -70,8 +78,8 @@ pub(crate) struct EphemeralSymmetricSecret {
pub rekey_time: i64,
pub expire_time: i64,
pub ratchet_count: u64,
pub encrypt_uses: AtomicU32,
pub decrypt_uses: AtomicU32,
pub encrypt_uses: AtomicUsize,
pub decrypt_uses: AtomicUsize,
pub fips_compliant_exchange: bool,
}

View file

@ -868,8 +868,6 @@ dependencies = [
"lz4_flex",
"parking_lot",
"serde",
"serde_derive",
"serde_json",
"winapi",
"zerotier-core-crypto",
]