diff --git a/src/address.rs b/src/address.rs deleted file mode 100644 index a1d73a1..0000000 --- a/src/address.rs +++ /dev/null @@ -1,328 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. - * - * (c) ZeroTier, Inc. - * https://www.zerotier.com/ - */ - -use std::array::TryFromSliceError; -use std::fmt::Debug; -use std::hash::Hash; -use std::mem::{size_of, transmute}; -use std::str::FromStr; - -use serde::{Deserialize, Serialize}; - -use zerotier_utils::error::InvalidParameterError; -use zerotier_utils::hex::{self, HEX_CHARS}; -use zerotier_utils::tofrombytes::ToFromBytes; -use zerotier_utils::{base24, memory}; - -/// A unique identifier for an identity on the ZeroTier VL1 network -#[repr(transparent)] -#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)] -pub struct Address(pub(crate) [u8; Self::SIZE]); - -impl Address { - /// The size of a full address, 384 bits. - pub const SIZE: usize = 48; - - /// The size of a short address. - pub const SHORT_SIZE: usize = 16; - - /// The size of a legacy ZeroTier One short address. - pub const LEGACY_SHORT_SIZE: usize = 5; - - /// Legacy ZeroTier One addresses may not begin with 0xff. - pub const LEGACY_RESERVED_PREFIX: u8 = 0xff; - - #[inline(always)] - pub(crate) fn new_uninit() -> Self { - Self([0u8; Self::SIZE]) - } - - #[inline(always)] - pub fn from_short_bytes(b: &[u8]) -> Option { - if b.len() == Self::SHORT_SIZE { - let mut a = Address([0u8; Self::SIZE]); - a.0[..Self::SHORT_SIZE].copy_from_slice(b); - Some(a) - } else { - None - } - } - - #[inline(always)] - pub fn as_bytes(&self) -> &[u8; Self::SIZE] { - &self.0 - } - - #[inline(always)] - pub fn as_short_bytes(&self) -> &[u8; Self::SHORT_SIZE] { - memory::array_range::(&self.0) - } - - #[inline(always)] - pub fn as_legacy_short_bytes(&self) -> &[u8; Self::LEGACY_SHORT_SIZE] { - memory::array_range::(&self.0) - } - - /// True if this is a full-length 384-bit address. - #[inline] - pub fn is_complete(&self) -> bool { - self.0[Self::SHORT_SIZE..].iter().any(|i| *i != 0) - } - - /// True if this is a short (128-bit or legacy 40-bit) address. - #[inline] - pub fn is_short(&self) -> bool { - self.0[Self::SHORT_SIZE..].iter().all(|i| *i == 0) - } - - /// Append a string representation of this address to a mutable string. - pub fn to_string_append(&self, s: &mut String) { - let mut i = 0; - while i < Self::SHORT_SIZE { - let ii = i + 4; - if i > 0 { - s.push('-'); - } - base24::encode_4to7(&self.0[i..ii], s); - i = ii; - } - if self.is_complete() { - s.push('.'); - while i < Self::SIZE { - let ii = i + 4; - if i > 16 { - s.push('-'); - } - base24::encode_4to7(&self.0[i..ii], s); - i = ii; - } - } - } - - /// Get the legacy 40-bit ZeroTier One address in the least significant 40 bits of a u64. - #[cfg(feature = "legacy_zt1")] - #[inline] - pub fn to_legacy_short_u64(&self) -> u64 { - u64::from_be_bytes(self.0[..8].try_into().unwrap()).wrapping_shr(24) - } - - #[cfg(feature = "legacy_zt1")] - #[inline] - pub fn from_legacy_short_u64(i: u64) -> Self { - let mut a = Self([0u8; Self::SIZE]); - a.0[..Self::LEGACY_SHORT_SIZE].copy_from_slice(&i.to_be_bytes()[3..]); - a - } - - #[cfg(feature = "legacy_zt1")] - #[inline(always)] - pub fn from_legacy_short_bytes(b: &[u8]) -> Option { - if b.len() == Self::LEGACY_SHORT_SIZE { - let mut a = Address([0u8; Self::SIZE]); - a.0[..Self::LEGACY_SHORT_SIZE].copy_from_slice(b); - Some(a) - } else { - None - } - } - - /// Output a legacy short ZeroTier One address (first 5 bytes) in string form. - /// This is only meaningful if this address belongs to an Identity with the appropriate flag set. - #[cfg(feature = "legacy_zt1")] - pub fn to_legacy_short_string(&self) -> String { - let mut s = String::with_capacity(Self::LEGACY_SHORT_SIZE * 2); - for b in self.0[..Self::LEGACY_SHORT_SIZE].iter() { - let b = *b; - s.push(HEX_CHARS[b.wrapping_shr(4) as usize] as char); - s.push(HEX_CHARS[(b & 0xf) as usize] as char); - } - s - } - - /// Parse a legacy 10-digit hex ZeroTier One address. - /// - /// This must be used instead of from_str() to parse these short addresses. None is returned if - /// the provided address is not valid. - #[cfg(feature = "legacy_zt1")] - pub fn from_legacy_short_string(s: &str) -> Result { - let i = hex::from_string_u64(s) & 0xffffffffff; - if i == 0 || s.len() != (Self::LEGACY_SHORT_SIZE * 2) { - return Err(InvalidParameterError("invalid legacy address")); - } - let i = i.to_be_bytes(); - if i[3] == Self::LEGACY_RESERVED_PREFIX { - return Err(InvalidParameterError("invalid legacy address")); - } - let mut a = Address([0u8; Self::SIZE]); - a.0[..Self::LEGACY_SHORT_SIZE].copy_from_slice(&i[3..]); - Ok(a) - } -} - -impl From<[u8; Address::SIZE]> for Address { - #[inline(always)] - fn from(value: [u8; Address::SIZE]) -> Self { - Self(value) - } -} - -impl From<&[u8; Address::SIZE]> for &Address { - #[inline(always)] - fn from(value: &[u8; Address::SIZE]) -> Self { - assert_eq!(size_of::<[u8; Address::SIZE]>(), size_of::
()); - unsafe { transmute(value) } - } -} - -impl From
for [u8; Address::SIZE] { - #[inline(always)] - fn from(value: Address) -> Self { - value.0 - } -} - -impl From<&Address> for [u8; Address::SIZE] { - #[inline(always)] - fn from(value: &Address) -> Self { - value.0 - } -} - -impl TryFrom<&[u8]> for Address { - type Error = TryFromSliceError; - - #[inline(always)] - fn try_from(value: &[u8]) -> Result { - value.try_into().map(|a| Self(a)) - } -} - -impl ToString for Address { - fn to_string(&self) -> String { - let mut s = String::with_capacity(96); - self.to_string_append(&mut s); - s - } -} - -impl FromStr for Address { - type Err = InvalidParameterError; - - fn from_str(s: &str) -> Result { - let mut a = Self([0u8; Self::SIZE]); - let mut i = 0; - for ss in s.split(&['-', '.']) { - if ss.len() == 7 { - for b in base24::decode_7to4(ss.as_bytes())? { - if i >= Self::SIZE { - return Err(InvalidParameterError("invalid address")); - } - a.0[i] = b; - i += 1; - } - } else { - return Err(InvalidParameterError("invalid address")); - } - } - return Ok(a); - } -} - -impl Debug for Address { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(self.to_string().as_str()) - } -} - -impl ToFromBytes for Address { - #[inline(always)] - fn read_bytes(r: &mut R) -> std::io::Result { - let mut tmp = Address([0u8; Self::SIZE]); - r.read_exact(&mut tmp.0)?; - Ok(tmp) - } - - #[inline(always)] - fn from_bytes(b: &[u8]) -> std::io::Result { - Ok(Self( - b.try_into() - .map_err(|_| std::io::Error::new(std::io::ErrorKind::Other, "invalid address"))?, - )) - } - - #[inline(always)] - fn write_bytes(&self, w: &mut W) -> std::io::Result<()> { - w.write_all(&self.0) - } - - #[inline(always)] - fn to_bytes(&self) -> Vec { - self.0.to_vec() - } -} - -impl Hash for Address { - #[inline(always)] - fn hash(&self, state: &mut H) { - state.write(&self.0[..8]) - } -} - -impl Serialize for Address { - #[inline] - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - if serializer.is_human_readable() { - self.to_string().serialize(serializer) - } else { - serializer.serialize_bytes(&self.0) - } - } -} - -struct AddressDeserializeVisitor; - -impl<'de> serde::de::Visitor<'de> for AddressDeserializeVisitor { - type Value = Address; - - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("ZeroTier address") - } - - fn visit_str(self, v: &str) -> Result - where - E: serde::de::Error, - { - Address::from_str(v.trim()).map_err(|e| serde::de::Error::custom(e.to_string())) - } - - fn visit_bytes(self, v: &[u8]) -> Result - where - E: serde::de::Error, - { - v.try_into() - .map(|b| Address(b)) - .map_err(|_| serde::de::Error::invalid_length(v.len(), &self)) - } -} - -impl<'de> Deserialize<'de> for Address { - #[inline] - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - if deserializer.is_human_readable() { - deserializer.deserialize_str(AddressDeserializeVisitor) - } else { - deserializer.deserialize_bytes(AddressDeserializeVisitor) - } - } -} diff --git a/src/base24.rs b/src/base24.rs new file mode 100644 index 0000000..cd83cd7 --- /dev/null +++ b/src/base24.rs @@ -0,0 +1,55 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * (c) ZeroTier, Inc. + * https://www.zerotier.com/ + */ + +use zerotier_common_utils::error::InvalidParameterError; + +/// All unambiguous letters, thus easy to type on the alphabetic keyboards on phones without extra shift taps. +/// The letters 'l' and 'u' are skipped. +const BASE24_ALPHABET: [u8; 24] = [ + b'a', b'b', b'c', b'd', b'e', b'f', b'g', b'h', b'i', b'j', b'k', b'm', b'n', b'o', b'p', b'q', b'r', b's', b't', b'v', b'w', b'x', b'y', b'z', +]; + +/// Reverse table for BASE24 alphabet, indexed relative to 'a' or 'A'. +const BASE24_ALPHABET_INV: [u8; 26] = [ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 255, 11, 12, 13, 14, 15, 16, 17, 18, 255, 19, 20, 21, 22, 23, +]; + +/// Encode 4 binary bytes into 7 base24 characters. +pub fn encode_4to7(b: &[u8], s: &mut String) { + let mut n = u32::from_be_bytes(b[..4].try_into().unwrap()); + for _ in 0..6 { + let (d, r) = (n / 24, n % 24); + n = d; + s.push(BASE24_ALPHABET[r as usize] as char); + } + s.push(BASE24_ALPHABET[n as usize] as char); +} + +pub fn decode_7to4(mut s: &[u8]) -> Result<[u8; 4], InvalidParameterError> { + let mut n = 0u32; + if s.len() > 7 { + s = &s[..7]; + } + for c in s.iter().rev() { + let mut c = *c; + if c >= 97 && c <= 122 { + c -= 97; + } else if c >= 65 && c <= 90 { + c -= 65; + } else { + return Err(InvalidParameterError("invalid base24")); + } + let i = BASE24_ALPHABET_INV[c as usize]; + if i == 255 { + return Err(InvalidParameterError("invalid base24")); + } + n *= 24; + n = n.wrapping_add(i as u32); + } + return Ok(n.to_be_bytes()); +} diff --git a/src/base62.rs b/src/base62.rs new file mode 100644 index 0000000..fdb2375 --- /dev/null +++ b/src/base62.rs @@ -0,0 +1,42 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * (c) ZeroTier, Inc. + * https://www.zerotier.com/ + */ + +use zerotier_common_utils::error::InvalidParameterError; + +pub fn encode_8to11(mut n: u64, s: &mut String) { + for _ in 0..11 { + let (d, r) = (n / 62, n % 62); + n = d; + let r = r as u8; + s.push(if r < 26 { + r + 97 // a..z + } else if r < 52 { + r + 39 // A..Z + } else { + r - 4 // 0..9 + } as char); + } +} + +pub fn decode_11to8(s: &[u8]) -> Result { + let mut n = 0u64; + for c in s.iter().rev() { + let c = *c; + n *= 62; + n = n.wrapping_add(if c >= 97 && c <= 122 { + n - 97 + } else if c >= 65 && c <= 90 { + n - 39 + } else if c >= 48 && c <= 57 { + n + 4 + } else { + return Err(InvalidParameterError("invalid base62")); + } as u64); + } + return Ok(n); +} diff --git a/src/identity.rs b/src/identity.rs deleted file mode 100644 index 20b0d81..0000000 --- a/src/identity.rs +++ /dev/null @@ -1,647 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. - * - * (c) ZeroTier, Inc. - * https://www.zerotier.com/ - */ - -use std::alloc::{alloc, Layout}; -use std::fmt::Debug; -use std::hash::Hash; -use std::io::{Read, Write}; -use std::mem::transmute_copy; -use std::ptr::copy_nonoverlapping; -use std::str::FromStr; -use std::sync::Mutex; - -use serde::{Deserialize, Deserializer, Serialize, Serializer}; - -use zerotier_crypto::hash::{SHA384, SHA512}; -use zerotier_crypto::p384::{P384PublicKey, P384_ECDSA_SIGNATURE_SIZE, P384_PUBLIC_KEY_SIZE}; -use zerotier_crypto::salsa::Salsa; -use zerotier_crypto::x25519::*; -use zerotier_utils::arrayvec::ArrayVec; -use zerotier_utils::error::InvalidParameterError; -use zerotier_utils::tofrombytes::ToFromBytes; -use zerotier_utils::{base64, hex, parallelism}; - -use crate::address::Address; -use crate::identitysecret::IdentitySecret; -use crate::signature::parse_signature; - -pub const TYPE_NAME_X25519: &'static str = "x25519"; -pub const TYPE_NAME_X25519P384: &'static str = "x25519p384"; -pub const TYPE_NAME_P384: &'static str = "p384"; - -#[derive(Clone)] -pub enum Identity { - // type 0 - legacy ZeroTier One identity - X25519 { - address: Address, - x25519_ecdh: [u8; C25519_PUBLIC_KEY_SIZE], - x25519_eddsa: [u8; ED25519_PUBLIC_KEY_SIZE], - }, - // type 1 - p384 with legacy backward compatibility - X25519P384 { - address: Address, - master_signing_key: P384PublicKey, - timestamp: i64, - x25519_ecdh: [u8; C25519_PUBLIC_KEY_SIZE], - x25519_eddsa: [u8; ED25519_PUBLIC_KEY_SIZE], - p384_ecdh: P384PublicKey, - p384_ecdsa: P384PublicKey, - x25519_signature: [u8; ED25519_SIGNATURE_SIZE], - master_signature: [u8; P384_ECDSA_SIGNATURE_SIZE], - }, - // type 2 - just p384, no legacy - P384 { - address: Address, - master_signing_key: P384PublicKey, - timestamp: i64, - p384_ecdh: P384PublicKey, - p384_ecdsa: P384PublicKey, - master_signature: [u8; P384_ECDSA_SIGNATURE_SIZE], - }, -} - -impl Identity { - pub(crate) const LEGACY_ADDRESS_POW_THRESHOLD: u8 = 17; - - #[inline(always)] - pub fn address(&self) -> &Address { - match self { - Self::X25519 { address, .. } | Self::X25519P384 { address, .. } | Self::P384 { address, .. } => address, - } - } - - /// Get a human readable name for this identity's type. - pub fn type_name(&self) -> &'static str { - match self { - Self::X25519 { .. } => TYPE_NAME_X25519, - Self::X25519P384 { .. } => TYPE_NAME_X25519P384, - Self::P384 { .. } => TYPE_NAME_P384, - } - } - - /// True if the first 5 bytes of this identity's address are a ZeroTier One compatible legacy short address. - #[inline] - pub fn is_legacy_compatible(&self) -> bool { - matches!(self, Self::X25519 { .. } | Self::X25519P384 { .. }) - } - - /// True if this is a legacy-only identity - #[inline] - pub fn is_legacy_only(&self) -> bool { - matches!(self, Self::X25519 { .. }) - } - - /// Write legacy ZeroTier One binary serialized identity (if this identity is backward compatible). - #[cfg(feature = "legacy_zt1")] - pub fn write_legacy_bytes(&self, w: &mut W) -> std::io::Result<()> { - let (legacy_address, ecdh, eddsa) = match self { - Self::X25519 { address, x25519_ecdh, x25519_eddsa } => (address.as_legacy_short_bytes(), x25519_ecdh, x25519_eddsa), - Self::X25519P384 { address, x25519_ecdh, x25519_eddsa, .. } => (address.as_legacy_short_bytes(), x25519_ecdh, x25519_eddsa), - _ => return Err(std::io::Error::new(std::io::ErrorKind::Other, "not a legacy compatible identity")), - }; - w.write_all(legacy_address)?; - w.write_all(&[0])?; - w.write_all(ecdh)?; - w.write_all(eddsa)?; - w.write_all(&[0]) - } - - /// Read legacy ZeroTier One binary serialized identity. - /// A secret is also returned if it is present in the stream. - #[cfg(feature = "legacy_zt1")] - pub fn read_legacy_bytes(r: &mut R) -> std::io::Result<(Self, Option)> { - let mut tmp = [0u8; Address::LEGACY_SHORT_SIZE + 1 + C25519_PUBLIC_KEY_SIZE + ED25519_PUBLIC_KEY_SIZE + 1]; - r.read_exact(&mut tmp)?; - if tmp[Address::LEGACY_SHORT_SIZE] != 0 { - return Err(std::io::Error::new(std::io::ErrorKind::Other, "invalid identity type")); - } - - let x25519_ecdh: [u8; C25519_PUBLIC_KEY_SIZE] = tmp[Address::LEGACY_SHORT_SIZE + 1..Address::LEGACY_SHORT_SIZE + 1 + C25519_PUBLIC_KEY_SIZE] - .try_into() - .unwrap(); - let x25519_eddsa: [u8; ED25519_PUBLIC_KEY_SIZE] = tmp[Address::LEGACY_SHORT_SIZE + 1 + C25519_PUBLIC_KEY_SIZE - ..Address::LEGACY_SHORT_SIZE + 1 + C25519_PUBLIC_KEY_SIZE + ED25519_PUBLIC_KEY_SIZE] - .try_into() - .unwrap(); - - let public = Self::X25519 { - address: { - let mut address_hasher = SHA384::new(); - address_hasher.update(&x25519_ecdh); - address_hasher.update(&x25519_eddsa); - let mut address = Address(address_hasher.finish()); - address.0[..Address::LEGACY_SHORT_SIZE].copy_from_slice(&tmp[..Address::LEGACY_SHORT_SIZE]); - address - }, - x25519_ecdh, - x25519_eddsa, - }; - - let secret_bytes = tmp[Address::LEGACY_SHORT_SIZE + 1 + C25519_PUBLIC_KEY_SIZE + ED25519_PUBLIC_KEY_SIZE]; - let secret = if secret_bytes == (C25519_SECRET_KEY_SIZE + ED25519_SECRET_KEY_SIZE) as u8 { - r.read_exact(&mut tmp[..C25519_SECRET_KEY_SIZE + ED25519_SECRET_KEY_SIZE])?; - Some(IdentitySecret { - public: public.clone(), - secret: crate::identitysecret::SecretKeys::X25519 { - x25519_ecdh: X25519KeyPair::from_bytes(&x25519_ecdh, &tmp[..C25519_SECRET_KEY_SIZE]) - .ok_or(std::io::Error::new(std::io::ErrorKind::Other, "invalid key"))?, - x25519_eddsa: Ed25519KeyPair::from_bytes( - &x25519_eddsa, - &tmp[C25519_SECRET_KEY_SIZE..C25519_SECRET_KEY_SIZE + ED25519_SECRET_KEY_SIZE], - ) - .ok_or(std::io::Error::new(std::io::ErrorKind::Other, "invalid key"))?, - }, - }) - } else if secret_bytes != 0 { - return Err(std::io::Error::new(std::io::ErrorKind::Other, "invalid key")); - } else { - None - }; - - if public.internal_validate() { - return Ok((public, secret)); - } else { - return Err(std::io::Error::new(std::io::ErrorKind::Other, "invalid identity")); - } - } - - /// Get NIST P-384 ECDH and ECDSA public keys if present. - pub fn p384(&self) -> Option<(&P384PublicKey, &P384PublicKey)> { - match self { - Self::X25519P384 { p384_ecdh, p384_ecdsa, .. } => Some((p384_ecdh, p384_ecdsa)), - Self::P384 { p384_ecdh, p384_ecdsa, .. } => Some((p384_ecdh, p384_ecdsa)), - _ => None, - } - } - - /// Get x25519 ECDH and EDDSA public keys if present. - pub fn x25519(&self) -> Option<(&[u8; C25519_PUBLIC_KEY_SIZE], &[u8; ED25519_PUBLIC_KEY_SIZE])> { - match self { - Self::X25519 { x25519_ecdh, x25519_eddsa, .. } => Some((x25519_ecdh, x25519_eddsa)), - Self::X25519P384 { x25519_ecdh, x25519_eddsa, .. } => Some((x25519_ecdh, x25519_eddsa)), - _ => None, - } - } - - /// Verify a legacy ZeroTier One signature (if we have x25519 keys). - pub fn verify_legacy(&self, signature: &[u8], data: &[u8]) -> bool { - if signature.len() == 64 || signature.len() == 96 { - match self { - Self::X25519 { x25519_eddsa, .. } => return ed25519_verify(x25519_eddsa, signature, data), - Self::X25519P384 { x25519_eddsa, .. } => return ed25519_verify(x25519_eddsa, signature, data), - _ => return false, - } - } else { - return false; - } - } - - /// Verify a signature. - pub fn verify(&self, signature: &[u8], data: &[u8]) -> bool { - let (p384_sig, x25519_sig) = parse_signature(signature); - match self { - Self::X25519 { x25519_eddsa, .. } => { - if let Some(x25519_sig) = x25519_sig.as_ref() { - ed25519_verify(x25519_eddsa, x25519_sig, data) - } else { - false - } - } - Self::X25519P384 { x25519_eddsa, p384_ecdsa, .. } => { - if let (Some(p384_sig), Some(x25519_sig)) = (p384_sig, x25519_sig) { - p384_ecdsa.verify(data, p384_sig) && ed25519_verify(x25519_eddsa, x25519_sig, data) - } else { - false - } - } - Self::P384 { p384_ecdsa, .. } => { - if let Some(p384_sig) = p384_sig { - p384_ecdsa.verify(data, p384_sig) - } else { - false - } - } - } - } - - /// Called internally on all code paths that return an Identity from deserialization, from_str(), etc. - fn internal_validate(&self) -> bool { - let mut legacy_address_hash = None; - let mut master_signed = ArrayVec::::new(); - - let (address, mut address_should_be) = match self { - Self::X25519 { address, x25519_ecdh, x25519_eddsa } => { - let mut address_hasher = SHA384::new(); - address_hasher.update(x25519_ecdh); - address_hasher.update(x25519_eddsa); - - let mut legacy_address_hasher = SHA512::new(); - legacy_address_hasher.update(x25519_ecdh); - legacy_address_hasher.update(x25519_eddsa); - let _ = legacy_address_hash.insert(legacy_address_hasher.finish()); - - (address, address_hasher.finish()) - } - Self::X25519P384 { - address, - master_signing_key, - timestamp, - x25519_ecdh, - x25519_eddsa, - p384_ecdh, - p384_ecdsa, - x25519_signature, - master_signature, - } => { - if !ed25519_verify(x25519_eddsa, x25519_signature, master_signing_key.as_bytes()) { - return false; - } - - master_signed.push_slice(&address.0); - master_signed.push_slice(×tamp.to_be_bytes()); - master_signed.push_slice(x25519_ecdh); - master_signed.push_slice(x25519_eddsa); - master_signed.push_slice(p384_ecdh.as_bytes()); - master_signed.push_slice(p384_ecdsa.as_bytes()); - master_signed.push_slice(x25519_signature); - if !master_signing_key.verify(master_signed.as_ref(), master_signature) { - return false; - } - - let mut legacy_address_hasher = SHA512::new(); - legacy_address_hasher.update(x25519_ecdh); - legacy_address_hasher.update(x25519_eddsa); - let _ = legacy_address_hash.insert(legacy_address_hasher.finish()); - - (address, SHA384::hash(master_signing_key.as_bytes())) - } - Self::P384 { - address, - master_signing_key, - timestamp, - p384_ecdh, - p384_ecdsa, - master_signature, - } => { - master_signed.push_slice(&address.0); - master_signed.push_slice(×tamp.to_be_bytes()); - master_signed.push_slice(p384_ecdh.as_bytes()); - master_signed.push_slice(p384_ecdsa.as_bytes()); - if !master_signing_key.verify(master_signed.as_ref(), master_signature) { - return false; - } - - (address, SHA384::hash(master_signing_key.as_bytes())) - } - }; - - if let Some(legacy_address_hash) = legacy_address_hash.as_mut() { - // Check the part of the address that does not require computation of the work function first, - // since this saves time in the obviously corrupt case. - if !address_should_be[Address::LEGACY_SHORT_SIZE..].eq(&address.0[Address::LEGACY_SHORT_SIZE..]) { - return false; - } - - legacy_address_derivation_work_function(legacy_address_hash); - if legacy_address_hash[0] >= Self::LEGACY_ADDRESS_POW_THRESHOLD || legacy_address_hash[59] == Address::LEGACY_RESERVED_PREFIX { - return false; - } - address_should_be[..Address::LEGACY_SHORT_SIZE].copy_from_slice(&legacy_address_hash[59..64]); - } - - return address.0.eq(&address_should_be); - } -} - -impl ToString for Identity { - fn to_string(&self) -> String { - let (address, type_name) = match self { - Self::X25519 { address, x25519_ecdh, x25519_eddsa } => { - // For type 0 output the identity in legacy compatible string format - return format!( - "{}:0:{}{}", - address.to_legacy_short_string(), - hex::to_string(x25519_ecdh), - hex::to_string(x25519_eddsa) - ); - } - Self::X25519P384 { address, .. } => (address, TYPE_NAME_X25519P384), - Self::P384 { address, .. } => (address, TYPE_NAME_P384), - }; - return format!( - "{}:{}:{}", - address.to_string(), - type_name, - base64::to_string(self.to_bytes_on_stack::<16384>().as_ref()) - ); - } -} - -impl FromStr for Identity { - type Err = InvalidParameterError; - - fn from_str(s: &str) -> Result { - let mut fi = s.split(':'); - let address_str = fi.next().ok_or(InvalidParameterError("incomplete"))?; - let type_str = fi.next().ok_or(InvalidParameterError("incomplete"))?; - let data_str = fi.next().ok_or(InvalidParameterError("incomplete"))?; - - if type_str == "0" { - let keys = hex::from_string(data_str); - if keys.len() == (C25519_PUBLIC_KEY_SIZE + ED25519_PUBLIC_KEY_SIZE) { - let mut address_hasher = SHA384::new(); - address_hasher.update(keys.as_slice()); - let mut address = Address::from_legacy_short_string(address_str)?; - address.0[Address::LEGACY_SHORT_SIZE..].copy_from_slice(&address_hasher.finish()[Address::LEGACY_SHORT_SIZE..]); - - let id = Self::X25519 { - address, - x25519_ecdh: keys[..C25519_PUBLIC_KEY_SIZE].try_into().unwrap(), - x25519_eddsa: keys[C25519_PUBLIC_KEY_SIZE..].try_into().unwrap(), - }; - - if !id.internal_validate() { - return Err(InvalidParameterError("invalid identity")); - } - - return Ok(id); - } else { - return Err(InvalidParameterError("invalid key")); - } - } else { - let id = Self::from_bytes( - base64::from_string(data_str.trim().as_bytes()) - .ok_or(InvalidParameterError("invalid base64"))? - .as_slice(), - ) - .map_err(|_| InvalidParameterError("invalid serialized data"))?; - if !id.address().eq(&Address::from_str(address_str)?) { - return Err(InvalidParameterError("invalid address")); - } - // The deserializer (via from_bytes()) will already have internally validated the identity. - return Ok(id); - } - } -} - -impl Debug for Identity { - #[inline] - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(self.to_string().as_str()) - } -} - -impl ToFromBytes for Identity { - fn read_bytes(r: &mut R) -> std::io::Result { - let mut tmp = [0u8; 512]; - r.read_exact(&mut tmp[..1])?; - let id = match tmp[0] { - 0 => { - r.read_exact(&mut tmp[..Address::SIZE + C25519_PUBLIC_KEY_SIZE + ED25519_PUBLIC_KEY_SIZE])?; - Identity::X25519 { - address: Address(tmp[..Address::SIZE].try_into().unwrap()), - x25519_ecdh: tmp[Address::SIZE..Address::SIZE + C25519_PUBLIC_KEY_SIZE].try_into().unwrap(), - x25519_eddsa: tmp[Address::SIZE + C25519_PUBLIC_KEY_SIZE..Address::SIZE + C25519_PUBLIC_KEY_SIZE + ED25519_PUBLIC_KEY_SIZE] - .try_into() - .unwrap(), - } - } - 1 => { - const F0: usize = Address::SIZE; - const F1: usize = F0 + P384_PUBLIC_KEY_SIZE; - const F2: usize = F1 + 8; - const F3: usize = F2 + C25519_PUBLIC_KEY_SIZE; - const F4: usize = F3 + ED25519_PUBLIC_KEY_SIZE; - const F5: usize = F4 + P384_PUBLIC_KEY_SIZE; - const F6: usize = F5 + P384_PUBLIC_KEY_SIZE; - const F7: usize = F6 + ED25519_SIGNATURE_SIZE; - const F8: usize = F7 + P384_ECDSA_SIGNATURE_SIZE; - r.read_exact(&mut tmp[..F8])?; - Identity::X25519P384 { - address: Address(tmp[..F0].try_into().unwrap()), - master_signing_key: P384PublicKey::from_bytes(&tmp[F0..F1]) - .ok_or(std::io::Error::new(std::io::ErrorKind::Other, "invalid key"))?, - timestamp: i64::from_be_bytes(tmp[F1..F2].try_into().unwrap()), - x25519_ecdh: tmp[F2..F3].try_into().unwrap(), - x25519_eddsa: tmp[F3..F4].try_into().unwrap(), - p384_ecdh: P384PublicKey::from_bytes(&tmp[F4..F5]).ok_or(std::io::Error::new(std::io::ErrorKind::Other, "invalid key"))?, - p384_ecdsa: P384PublicKey::from_bytes(&tmp[F5..F6]).ok_or(std::io::Error::new(std::io::ErrorKind::Other, "invalid key"))?, - x25519_signature: tmp[F6..F7].try_into().unwrap(), - master_signature: tmp[F7..F8].try_into().unwrap(), - } - } - 2 => { - const F0: usize = Address::SIZE; - const F1: usize = F0 + P384_PUBLIC_KEY_SIZE; - const F2: usize = F1 + 8; - const F3: usize = F2 + P384_PUBLIC_KEY_SIZE; - const F4: usize = F3 + P384_PUBLIC_KEY_SIZE; - const F5: usize = F4 + P384_ECDSA_SIGNATURE_SIZE; - r.read_exact(&mut tmp[..F5])?; - Identity::P384 { - address: Address(tmp[..F0].try_into().unwrap()), - master_signing_key: P384PublicKey::from_bytes(&tmp[F0..F1]) - .ok_or(std::io::Error::new(std::io::ErrorKind::Other, "invalid key"))?, - timestamp: i64::from_be_bytes(tmp[F1..F2].try_into().unwrap()), - p384_ecdh: P384PublicKey::from_bytes(&tmp[F2..F3]).ok_or(std::io::Error::new(std::io::ErrorKind::Other, "invalid key"))?, - p384_ecdsa: P384PublicKey::from_bytes(&tmp[F3..F4]).ok_or(std::io::Error::new(std::io::ErrorKind::Other, "invalid key"))?, - master_signature: tmp[F4..F5].try_into().unwrap(), - } - } - _ => return Err(std::io::Error::new(std::io::ErrorKind::Other, "unsupported identity type")), - }; - if !id.internal_validate() { - return Err(std::io::Error::new(std::io::ErrorKind::Other, "invalid identity")); - } - return Ok(id); - } - - fn write_bytes(&self, w: &mut W) -> std::io::Result<()> { - match self { - Identity::X25519 { address, x25519_ecdh, x25519_eddsa } => { - w.write_all(&[0u8])?; - w.write_all(&address.0)?; - w.write_all(x25519_ecdh)?; - w.write_all(x25519_eddsa)?; - } - Identity::X25519P384 { - address, - master_signing_key, - timestamp, - x25519_ecdh, - x25519_eddsa, - p384_ecdh, - p384_ecdsa, - x25519_signature, - master_signature, - } => { - w.write_all(&[1u8])?; - w.write_all(&address.0)?; - w.write_all(master_signing_key.as_bytes())?; - w.write_all(×tamp.to_be_bytes())?; - w.write_all(x25519_ecdh)?; - w.write_all(x25519_eddsa)?; - w.write_all(p384_ecdh.as_bytes())?; - w.write_all(p384_ecdsa.as_bytes())?; - w.write_all(x25519_signature)?; - w.write_all(master_signature)?; - } - Identity::P384 { - address, - master_signing_key, - timestamp, - p384_ecdh, - p384_ecdsa, - master_signature, - } => { - w.write_all(&[2u8])?; - w.write_all(&address.0)?; - w.write_all(master_signing_key.as_bytes())?; - w.write_all(×tamp.to_be_bytes())?; - w.write_all(p384_ecdh.as_bytes())?; - w.write_all(p384_ecdsa.as_bytes())?; - w.write_all(master_signature)?; - } - } - Ok(()) - } -} - -impl Serialize for Identity { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - if serializer.is_human_readable() { - self.to_string().serialize(serializer) - } else { - serializer.serialize_bytes(self.to_bytes_on_stack::<8192>().as_bytes()) - } - } -} - -struct IdentityDeserializeVisitor; - -impl<'de> serde::de::Visitor<'de> for IdentityDeserializeVisitor { - type Value = Identity; - - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("ZeroTier identity") - } - - fn visit_bytes(self, v: &[u8]) -> Result - where - E: serde::de::Error, - { - Identity::from_bytes(v).map_err(|e| serde::de::Error::custom(e.to_string())) - } - - fn visit_str(self, v: &str) -> Result - where - E: serde::de::Error, - { - Identity::from_str(v).map_err(|e| serde::de::Error::custom(e.to_string())) - } -} - -impl<'de> Deserialize<'de> for Identity { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - if deserializer.is_human_readable() { - deserializer.deserialize_str(IdentityDeserializeVisitor) - } else { - deserializer.deserialize_bytes(IdentityDeserializeVisitor) - } - } -} - -impl PartialEq for Identity { - #[inline(always)] - fn eq(&self, other: &Self) -> bool { - self.address().eq(other.address()) - } -} - -impl Eq for Identity {} - -impl PartialOrd for Identity { - #[inline] - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -impl Ord for Identity { - #[inline] - fn cmp(&self, other: &Self) -> std::cmp::Ordering { - self.address().cmp(other.address()) - } -} - -impl Hash for Identity { - #[inline] - fn hash(&self, state: &mut H) { - self.address().hash(state) - } -} - -// This is the memory-intensive hash used for address derivation in ZeroTier v1 -pub(crate) fn legacy_address_derivation_work_function(digest_bytes: &mut [u8; 64]) { - const ADDRESS_DERIVATION_HASH_MEMORY_SIZE: usize = 2097152; - const ADDRESS_DERIVATION_HASH_MEMORY_SIZE_U64: usize = 2097152 / 8; - - static ADDRESS_DERIVATION_HASH_MEMORY: Mutex>> = Mutex::new(Vec::new()); - - let genmem = ADDRESS_DERIVATION_HASH_MEMORY.lock().unwrap().pop(); - let mut genmem = - genmem.unwrap_or_else(|| unsafe { Box::from_raw(alloc(Layout::new::<[u64; ADDRESS_DERIVATION_HASH_MEMORY_SIZE_U64]>()).cast()) }); - - let mut salsa: Salsa<20> = Salsa::new(&digest_bytes[..32], &digest_bytes[32..40]); - genmem[..8].fill(0); - salsa.crypt_in_place(unsafe { &mut *genmem.as_mut_ptr().cast::<[u8; 64]>() }); - let mut k = 0; - while k < (ADDRESS_DERIVATION_HASH_MEMORY_SIZE - 64) { - let kk = k + 64; - unsafe { - salsa.crypt( - &*genmem.as_ptr().cast::().add(k).cast::<[u8; 64]>(), - &mut *genmem.as_mut_ptr().cast::().add(kk).cast::<[u8; 64]>(), - ); - } - k = kk; - } - - let mut digest: [u64; 8] = unsafe { transmute_copy(digest_bytes) }; - let mut i = 0; - while i < ADDRESS_DERIVATION_HASH_MEMORY_SIZE_U64 { - unsafe { - let idx1 = ((*genmem.as_mut_ptr().cast::().add((i * 8) + 7)) & 7) as usize; - let idx2 = (u64::from_be(*genmem.get_unchecked(i + 1)) as usize) % ADDRESS_DERIVATION_HASH_MEMORY_SIZE_U64; - i += 2; - debug_assert!(idx1 < digest.len()); - debug_assert!(idx2 < genmem.len()); - let genmem_idx2 = genmem.get_unchecked_mut(idx2); - let digest_idx1 = digest.get_unchecked_mut(idx1); - let tmp = *genmem_idx2; - *genmem_idx2 = *digest_idx1; - *digest_idx1 = tmp; - salsa.crypt_in_place(&mut *digest.as_mut_ptr().cast::<[u8; 64]>()); - } - } - - { - let mut m = ADDRESS_DERIVATION_HASH_MEMORY.lock().unwrap(); - if m.len() < parallelism() { - m.push(genmem); - } - } - - unsafe { copy_nonoverlapping(digest.as_ptr().cast::(), digest_bytes.as_mut_ptr(), 64) }; -} diff --git a/src/identitysecret.rs b/src/identitysecret.rs deleted file mode 100644 index 4d3c86d..0000000 --- a/src/identitysecret.rs +++ /dev/null @@ -1,541 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. - * - * (c) ZeroTier, Inc. - * https://www.zerotier.com/ - */ - -use std::io::Write; -use std::str::FromStr; - -use serde::{Deserialize, Serialize}; - -use zerotier_crypto::hash::{SHA384, SHA512}; -use zerotier_crypto::p384::{P384KeyPair, P384_SECRET_KEY_SIZE}; -use zerotier_crypto::x25519::*; -use zerotier_utils::arrayvec::ArrayVec; -use zerotier_utils::error::InvalidParameterError; -use zerotier_utils::tofrombytes::ToFromBytes; -use zerotier_utils::{base64, hex}; - -use crate::address::Address; -use crate::identity::{self, legacy_address_derivation_work_function, Identity}; -use crate::signature::{make_signature, Signature}; - -pub struct IdentitySecret { - pub public: Identity, - pub(crate) secret: SecretKeys, -} - -pub(crate) enum SecretKeys { - X25519 { - x25519_ecdh: X25519KeyPair, - x25519_eddsa: Ed25519KeyPair, - }, - X25519P384 { - master_signing_key: Option, - x25519_ecdh: X25519KeyPair, - x25519_eddsa: Ed25519KeyPair, - p384_ecdh: P384KeyPair, - p384_ecdsa: P384KeyPair, - }, - P384 { - master_signing_key: Option, - p384_ecdh: P384KeyPair, - p384_ecdsa: P384KeyPair, - }, -} - -impl IdentitySecret { - /// Generate a legacy-only X25519 ZeroTier One identity. - pub fn generate_x25519() -> Self { - let (legacy_address, ecdh, eddsa) = Self::generate_legacy_x25519(); - let x25519_ecdh = ecdh.public_bytes(); - let x25519_eddsa = eddsa.public_bytes(); - - return Self { - public: Identity::X25519 { - address: { - let mut address = Address::new_uninit(); - let mut h = SHA384::new(); - h.update(&x25519_ecdh); - h.update(&x25519_eddsa); - address.0 = h.finish(); - address.0[..Address::LEGACY_SHORT_SIZE].copy_from_slice(&legacy_address); - address - }, - x25519_ecdh, - x25519_eddsa, - }, - secret: SecretKeys::X25519 { x25519_ecdh: ecdh, x25519_eddsa: eddsa }, - }; - } - - /// Generate a hybrid backward compatible identity. - /// If upgrade_from is not None it must contain a legacy x25519 type identity. - pub fn generate_x25519p384(timestamp: i64, upgrade_from: Option) -> Result { - let (legacy_address, x25519_ecdh_secret, x25519_eddsa_secret) = if let Some(upgrade_from) = upgrade_from { - match upgrade_from { - IdentitySecret { - public: Identity::X25519 { address, .. }, - secret: SecretKeys::X25519 { x25519_ecdh, x25519_eddsa }, - } => (address.0[..Address::LEGACY_SHORT_SIZE].try_into().unwrap(), x25519_ecdh, x25519_eddsa), - _ => { - return Err(InvalidParameterError("upgrade only allowed from X25519 to X25519P384")); - } - } - } else { - Self::generate_legacy_x25519() - }; - - let master_signing_key_secret = P384KeyPair::generate(); - let p384_ecdh_secret = P384KeyPair::generate(); - let p384_ecdsa_secret = P384KeyPair::generate(); - - let x25519_ecdh = x25519_ecdh_secret.public_bytes(); - let x25519_eddsa = x25519_eddsa_secret.public_bytes(); - let x25519_signature = x25519_eddsa_secret.sign(master_signing_key_secret.public_key_bytes()); - - let mut address = Address(SHA384::hash(master_signing_key_secret.public_key_bytes())); - address.0[..Address::LEGACY_SHORT_SIZE].copy_from_slice(&legacy_address); - - let mut master_signed = ArrayVec::::new(); - master_signed.push_slice(&address.0); - master_signed.push_slice(×tamp.to_be_bytes()); - master_signed.push_slice(&x25519_ecdh); - master_signed.push_slice(&x25519_eddsa); - master_signed.push_slice(p384_ecdh_secret.public_key_bytes()); - master_signed.push_slice(p384_ecdsa_secret.public_key_bytes()); - master_signed.push_slice(&x25519_signature); - - return Ok(Self { - public: Identity::X25519P384 { - address, - master_signing_key: master_signing_key_secret.to_public_key(), - timestamp, - x25519_ecdh, - x25519_eddsa, - p384_ecdh: p384_ecdh_secret.to_public_key(), - p384_ecdsa: p384_ecdsa_secret.to_public_key(), - x25519_signature, - master_signature: master_signing_key_secret.sign(master_signed.as_ref()), - }, - secret: SecretKeys::X25519P384 { - master_signing_key: Some(master_signing_key_secret), - x25519_ecdh: x25519_ecdh_secret, - x25519_eddsa: x25519_eddsa_secret, - p384_ecdh: p384_ecdh_secret, - p384_ecdsa: p384_ecdsa_secret, - }, - }); - } - - /// Generate a new format P-384 identity without backward compatibility. - pub fn generate_p384(timestamp: i64) -> Self { - let master_signing_key_secret = P384KeyPair::generate(); - let p384_ecdh_secret = P384KeyPair::generate(); - let p384_ecdsa_secret = P384KeyPair::generate(); - - let address = Address(SHA384::hash(master_signing_key_secret.public_key_bytes())); - - let mut master_signed = ArrayVec::::new(); - master_signed.push_slice(&address.0); - master_signed.push_slice(×tamp.to_be_bytes()); - master_signed.push_slice(p384_ecdh_secret.public_key_bytes()); - master_signed.push_slice(p384_ecdsa_secret.public_key_bytes()); - - return Self { - public: Identity::P384 { - address, - master_signing_key: master_signing_key_secret.to_public_key(), - timestamp, - p384_ecdh: p384_ecdh_secret.to_public_key(), - p384_ecdsa: p384_ecdsa_secret.to_public_key(), - master_signature: master_signing_key_secret.sign(master_signed.as_ref()), - }, - secret: SecretKeys::P384 { - master_signing_key: Some(master_signing_key_secret), - p384_ecdh: p384_ecdh_secret, - p384_ecdsa: p384_ecdsa_secret, - }, - }; - } - - fn generate_legacy_x25519() -> ([u8; Address::LEGACY_SHORT_SIZE], X25519KeyPair, Ed25519KeyPair) { - let mut ecdh = X25519KeyPair::generate(); - let eddsa = Ed25519KeyPair::generate(); - let mut legacy_address_hasher = SHA512::new(); - loop { - legacy_address_hasher.update(&ecdh.public_bytes()); - legacy_address_hasher.update(&eddsa.public_bytes()); - let mut legacy_address_hash = legacy_address_hasher.finish(); - legacy_address_derivation_work_function(&mut legacy_address_hash); - if legacy_address_hash[0] < Identity::LEGACY_ADDRESS_POW_THRESHOLD - && legacy_address_hash[59] != Address::LEGACY_RESERVED_PREFIX - && legacy_address_hash[59..64].iter().any(|i| *i != 0) - { - return (legacy_address_hash[59..64].try_into().unwrap(), ecdh, eddsa); - } else { - ecdh = X25519KeyPair::generate(); - legacy_address_hasher.reset(); - } - } - } - - /// Get NIST P-384 ECDH and ECDSA key pairs if present. - pub fn p384(&self) -> Option<(&P384KeyPair, &P384KeyPair)> { - match self { - Self { - public: identity::Identity::X25519P384 { .. }, - secret: SecretKeys::X25519P384 { p384_ecdh, p384_ecdsa, .. }, - } => Some((p384_ecdh, p384_ecdsa)), - Self { - public: identity::Identity::P384 { .. }, - secret: SecretKeys::P384 { p384_ecdh, p384_ecdsa, .. }, - } => Some((p384_ecdh, p384_ecdsa)), - _ => None, - } - } - - /// Get X25519 ECDH and EDDSA key pairs if present. - pub fn x25519(&self) -> Option<(&X25519KeyPair, &Ed25519KeyPair)> { - match self { - Self { - public: identity::Identity::X25519 { .. }, - secret: SecretKeys::X25519 { x25519_ecdh, x25519_eddsa, .. }, - } => Some((x25519_ecdh, x25519_eddsa)), - Self { - public: identity::Identity::X25519P384 { .. }, - secret: SecretKeys::X25519P384 { x25519_ecdh, x25519_eddsa, .. }, - } => Some((x25519_ecdh, x25519_eddsa)), - _ => None, - } - } - - /// Sign a message using all available keys for this identity. - pub fn sign(&self, data: &[u8]) -> Signature { - match self { - Self { - public: identity::Identity::X25519 { .. }, - secret: SecretKeys::X25519 { x25519_eddsa, .. }, - } => make_signature(&[], &x25519_eddsa.sign(data)), - Self { - public: identity::Identity::X25519P384 { .. }, - secret: SecretKeys::X25519P384 { p384_ecdsa, x25519_eddsa, .. }, - } => make_signature(&p384_ecdsa.sign(data), &x25519_eddsa.sign(data)), - Self { - public: identity::Identity::P384 { .. }, - secret: SecretKeys::P384 { p384_ecdsa, .. }, - } => make_signature(&p384_ecdsa.sign(data), &[]), - _ => panic!("IdentitySecret in invalid state (public/secret type mismatch)"), - } - } - - /// If the master signing key secret is included in this identity secret, remove and return it. - /// If this is a legacy identity or the key is not present, this does nothing. - /// This can be used to remove the master signing key from the secret for cold storage, returning - /// a secret that is fully usable as such but that lacks the secret part of the master key. - pub fn detach_master_signing_key(mut self) -> (Option, Self) { - match &mut self { - Self { - public: identity::Identity::X25519 { .. }, - secret: SecretKeys::X25519 { .. }, - } => (None, self), - Self { - public: identity::Identity::X25519P384 { .. }, - secret: SecretKeys::X25519P384 { master_signing_key, .. }, - } - | Self { - public: identity::Identity::P384 { .. }, - secret: SecretKeys::P384 { master_signing_key, .. }, - } => (master_signing_key.take(), self), - _ => panic!("IdentitySecret in invalid state (public/secret type mismatch)"), - } - } - - /// Re-attach a master signing key secret to this identity secret. - /// This will return an error if the supplied key does not match the public key in the public - /// identity or of this is a legacy identity that doesn't support master signing keys. - pub fn attach_master_signing_key(mut self, secret: P384KeyPair) -> Result { - match &mut self { - Self { - public: identity::Identity::X25519 { .. }, - secret: SecretKeys::X25519 { .. }, - } => Err(InvalidParameterError("legacy x25519 identities do not have master signing keys")), - Self { - public: identity::Identity::X25519P384 { master_signing_key, .. }, - secret: SecretKeys::X25519P384 { master_signing_key: master_signing_key_secret, .. }, - } - | Self { - public: identity::Identity::P384 { master_signing_key, .. }, - secret: SecretKeys::P384 { master_signing_key: master_signing_key_secret, .. }, - } => { - if secret.public_key_bytes().eq(master_signing_key.as_bytes()) { - let _ = master_signing_key_secret.insert(secret); - Ok(self) - } else { - Err(InvalidParameterError("master signing key secret does not match public")) - } - } - _ => panic!("IdentitySecret in invalid state (public/secret type mismatch)"), - } - } -} - -impl ToString for IdentitySecret { - fn to_string(&self) -> String { - match self { - Self { - public, - secret: SecretKeys::X25519 { x25519_ecdh, x25519_eddsa }, - } => { - // Type 0 identities convert to string form using the classical format. - let mut s = public.to_string(); - s.push(':'); - s.push_str(hex::to_string(x25519_ecdh.secret_bytes().as_bytes()).as_str()); - s.push_str(hex::to_string(x25519_eddsa.secret_bytes().as_bytes()).as_str()); - s - } - _ => { - // Other types just serialize as base64. - let mut s = String::with_capacity(1024); - s.push_str(self.public.address().to_string().as_str()); - s.push_str(":SECRET-"); - s.push_str(self.public.type_name()); - s.push(':'); - s.push_str(base64::to_string(self.to_bytes_on_stack::<32768>().as_bytes()).as_str()); - s - } - } - } -} - -impl FromStr for IdentitySecret { - type Err = InvalidParameterError; - - fn from_str(s: &str) -> Result { - let mut fi = s.trim().split(':'); - let _ = fi.next().ok_or(InvalidParameterError("incomplete"))?; - let type_str = fi.next().ok_or(InvalidParameterError("incomplete"))?; - let data_str = fi.next().ok_or(InvalidParameterError("incomplete"))?; - - if type_str == "0" { - let secret_data = hex::from_string(fi.next().ok_or(InvalidParameterError("incomplete"))?); - if secret_data.len() == (C25519_SECRET_KEY_SIZE + ED25519_SECRET_KEY_SIZE) { - let public_id = Identity::from_str(s)?; - if !matches!(&public_id, Identity::X25519 { .. }) { - return Err(InvalidParameterError("invalid type 0 identity")); - } - let x25519_public = public_id.x25519().unwrap(); - let x25519_ecdh = X25519KeyPair::from_bytes(x25519_public.0, &secret_data.as_slice()[..C25519_SECRET_KEY_SIZE]) - .ok_or(InvalidParameterError("invalid key"))?; - let x25519_eddsa = Ed25519KeyPair::from_bytes(x25519_public.1, &secret_data.as_slice()[C25519_SECRET_KEY_SIZE..]) - .ok_or(InvalidParameterError("invalid key"))?; - return Ok(Self { - public: public_id, - secret: SecretKeys::X25519 { x25519_ecdh, x25519_eddsa }, - }); - } else { - return Err(InvalidParameterError("invalid key")); - } - } else if type_str.starts_with("SECRET") { - let id = Self::from_bytes( - base64::from_string(data_str.trim().as_bytes()) - .ok_or(InvalidParameterError("invalid base64"))? - .as_slice(), - ) - .map_err(|e| { - println!("ERR: {}", e.to_string()); - InvalidParameterError("invalid identity") - })?; - return Ok(id); - } else { - return Err(InvalidParameterError("unrecognized type")); - } - } -} - -impl ToFromBytes for IdentitySecret { - fn read_bytes(r: &mut R) -> std::io::Result { - let mut tmp = [0u8; 256]; - let public = Identity::read_bytes(r)?; - let secret = match &public { - Identity::X25519 { x25519_ecdh, x25519_eddsa, .. } => { - const F0: usize = C25519_SECRET_KEY_SIZE; - const F1: usize = F0 + ED25519_SECRET_KEY_SIZE; - r.read_exact(&mut tmp[..F1])?; - SecretKeys::X25519 { - x25519_ecdh: X25519KeyPair::from_bytes(x25519_ecdh, &tmp[..F0]) - .ok_or(std::io::Error::new(std::io::ErrorKind::Other, "invalid key"))?, - x25519_eddsa: Ed25519KeyPair::from_bytes(x25519_eddsa, &tmp[F0..F1]) - .ok_or(std::io::Error::new(std::io::ErrorKind::Other, "invalid key"))?, - } - } - Identity::X25519P384 { - master_signing_key, - x25519_ecdh, - x25519_eddsa, - p384_ecdh, - p384_ecdsa, - .. - } => { - const F0: usize = 1 + P384_SECRET_KEY_SIZE; - const F1: usize = F0 + C25519_SECRET_KEY_SIZE; - const F2: usize = F1 + ED25519_SECRET_KEY_SIZE; - const F3: usize = F2 + P384_SECRET_KEY_SIZE; - const F4: usize = F3 + P384_SECRET_KEY_SIZE; - r.read_exact(&mut tmp[..F4])?; - SecretKeys::X25519P384 { - master_signing_key: if tmp[0] == (P384_SECRET_KEY_SIZE as u8) { - Some( - P384KeyPair::from_bytes(master_signing_key.as_bytes(), &tmp[1..F0]) - .ok_or(std::io::Error::new(std::io::ErrorKind::Other, "invalid key"))?, - ) - } else { - None - }, - x25519_ecdh: X25519KeyPair::from_bytes(x25519_ecdh, &tmp[F0..F1]) - .ok_or(std::io::Error::new(std::io::ErrorKind::Other, "invalid key"))?, - x25519_eddsa: Ed25519KeyPair::from_bytes(x25519_eddsa, &tmp[F1..F2]) - .ok_or(std::io::Error::new(std::io::ErrorKind::Other, "invalid key"))?, - p384_ecdh: P384KeyPair::from_bytes(p384_ecdh.as_bytes(), &tmp[F2..F3]) - .ok_or(std::io::Error::new(std::io::ErrorKind::Other, "invalid key"))?, - p384_ecdsa: P384KeyPair::from_bytes(p384_ecdsa.as_bytes(), &tmp[F3..F4]) - .ok_or(std::io::Error::new(std::io::ErrorKind::Other, "invalid key"))?, - } - } - Identity::P384 { master_signing_key, p384_ecdh, p384_ecdsa, .. } => { - const F0: usize = 1 + P384_SECRET_KEY_SIZE; - const F1: usize = F0 + P384_SECRET_KEY_SIZE; - const F2: usize = F1 + P384_SECRET_KEY_SIZE; - r.read_exact(&mut tmp[..F2])?; - SecretKeys::P384 { - master_signing_key: if tmp[0] == P384_SECRET_KEY_SIZE as u8 { - Some( - P384KeyPair::from_bytes(master_signing_key.as_bytes(), &tmp[1..F0]) - .ok_or(std::io::Error::new(std::io::ErrorKind::Other, "invalid key"))?, - ) - } else { - None - }, - p384_ecdh: P384KeyPair::from_bytes(p384_ecdh.as_bytes(), &tmp[F0..F1]) - .ok_or(std::io::Error::new(std::io::ErrorKind::Other, "invalid key"))?, - p384_ecdsa: P384KeyPair::from_bytes(p384_ecdsa.as_bytes(), &tmp[F1..F2]) - .ok_or(std::io::Error::new(std::io::ErrorKind::Other, "invalid key"))?, - } - } - }; - return Ok(IdentitySecret { public, secret }); - } - - fn write_bytes(&self, w: &mut W) -> std::io::Result<()> { - match self { - IdentitySecret { - public, - secret: - SecretKeys::X25519 { - x25519_ecdh: x25519_ecdh_secret, - x25519_eddsa: x25519_eddsa_secret, - }, - } => { - public.write_bytes(w)?; - w.write_all(x25519_ecdh_secret.secret_bytes().as_bytes())?; - w.write_all(x25519_eddsa_secret.secret_bytes().as_bytes())?; - } - IdentitySecret { - public, - secret: - SecretKeys::X25519P384 { - master_signing_key: master_signing_key_secret, - x25519_ecdh: x25519_ecdh_secret, - x25519_eddsa: x25519_eddsa_secret, - p384_ecdh: p384_ecdh_secret, - p384_ecdsa: p384_ecdsa_secret, - }, - } => { - public.write_bytes(w)?; - if let Some(master_signing_key_secret) = master_signing_key_secret.as_ref() { - w.write_all(&[P384_SECRET_KEY_SIZE as u8])?; - w.write_all(master_signing_key_secret.secret_key_bytes().as_bytes())?; - } else { - w.write_all(&[0])?; - } - w.write_all(x25519_ecdh_secret.secret_bytes().as_bytes())?; - w.write_all(x25519_eddsa_secret.secret_bytes().as_bytes())?; - w.write_all(p384_ecdh_secret.secret_key_bytes().as_bytes())?; - w.write_all(p384_ecdsa_secret.secret_key_bytes().as_bytes())?; - } - IdentitySecret { - public, - secret: - SecretKeys::P384 { - master_signing_key: master_signing_key_secret, - p384_ecdh: p384_ecdh_secret, - p384_ecdsa: p384_ecdsa_secret, - }, - } => { - public.write_bytes(w)?; - if let Some(master_signing_key_secret) = master_signing_key_secret.as_ref() { - w.write_all(&[P384_SECRET_KEY_SIZE as u8])?; - w.write_all(master_signing_key_secret.secret_key_bytes().as_bytes())?; - } else { - w.write_all(&[0])?; - } - w.write_all(p384_ecdh_secret.secret_key_bytes().as_bytes())?; - w.write_all(p384_ecdsa_secret.secret_key_bytes().as_bytes())?; - } - } - Ok(()) - } -} - -impl Serialize for IdentitySecret { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - if serializer.is_human_readable() { - self.to_string().serialize(serializer) - } else { - serializer.serialize_bytes(self.to_bytes_on_stack::<32768>().as_bytes()) - } - } -} - -struct IdentitySecretDeserializeVisitor; - -impl<'de> serde::de::Visitor<'de> for IdentitySecretDeserializeVisitor { - type Value = IdentitySecret; - - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("IdentitySecret") - } - - fn visit_bytes(self, v: &[u8]) -> Result - where - E: serde::de::Error, - { - IdentitySecret::from_bytes(v).map_err(|e| serde::de::Error::custom(e.to_string())) - } - - fn visit_str(self, v: &str) -> Result - where - E: serde::de::Error, - { - IdentitySecret::from_str(v).map_err(|e| serde::de::Error::custom(e.to_string())) - } -} - -impl<'de> Deserialize<'de> for IdentitySecret { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - if deserializer.is_human_readable() { - deserializer.deserialize_str(IdentitySecretDeserializeVisitor) - } else { - deserializer.deserialize_bytes(IdentitySecretDeserializeVisitor) - } - } -} diff --git a/src/lib.rs b/src/lib.rs index 1b96437..44f68e1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,6 +9,10 @@ use std::hash::Hash; use std::str::FromStr; +use serde::de::DeserializeOwned; +use serde::Serialize; + +use zerotier_common_utils::error::InvalidParameterError; use zerotier_common_utils::tofrombytes::ToFromBytes; /// A unique global identifier for a ZeroTier identity. @@ -33,7 +37,7 @@ pub trait Identity: ToString + FromStr + ToFromBytes + Sync + Send + Clone + Par } /// Secret keys that correspond to a public Identity. -pub trait IdentitySecret: Sync + Send + Clone + PartialEq + Eq + Hash + PartialOrd + Ord + 'static { +pub trait IdentitySecret: Sync + Send + Clone + PartialEq + Eq + Serialize + DeserializeOwned + 'static { type Public: Identity; /// Type returned by sign(), should typically be [u8; Public::SIGNATURE_SIZE]. @@ -42,7 +46,7 @@ pub trait IdentitySecret: Sync + Send + Clone + PartialEq + Eq + Hash + PartialO /// Generate a new identity. /// This may in some cases be a time consuming operation. - fn generate() -> Self; + fn generate(timestamp: u64) -> Self; /// Get the public portion of this secret identity. fn public(&self) -> &Self::Public; @@ -51,100 +55,10 @@ pub trait IdentitySecret: Sync + Send + Clone + PartialEq + Eq + Hash + PartialO fn sign(&self, data: &[u8]) -> Self::Signature; } +mod base24; +mod base62; pub mod p384; pub mod x25519; -//mod address; -//mod signature; - -//pub mod identity; -//pub mod identitysecret; - -//pub use address::Address; -//pub use identity::Identity; -//pub use identitysecret::IdentitySecret; -//pub use signature::Signature; - -/* -#[cfg(test)] -mod tests { - use super::*; - use zerotier_utils::tofrombytes::ToFromBytes; - - #[test] - fn identity_generate_sign_verify_serialize_deserialize() { - let id0 = IdentitySecret::generate_x25519(); - let id1 = IdentitySecret::generate_x25519p384(1, None).unwrap(); - let id2 = IdentitySecret::generate_p384(1); - - let sig_data = "hello".as_bytes(); - let sig_wrong_data = "goodbye".as_bytes(); - - let sig0 = id0.sign(sig_data); - let sig1 = id1.sign(sig_data); - let sig2 = id2.sign(sig_data); - - /* - println!( - "identity: signature lengths: x25519 {}, x25519p384 {}, p384 {}, p384pqc {}", - sig0.len(), - sig1.len(), - sig2.len(), - sig3.len() - ); - */ - - assert!(id0.public.verify(sig0.as_ref(), sig_data)); - assert!(id1.public.verify(sig1.as_ref(), sig_data)); - assert!(id2.public.verify(sig2.as_ref(), sig_data)); - - assert!(!id0.public.verify(sig0.as_ref(), sig_wrong_data)); - assert!(!id1.public.verify(sig1.as_ref(), sig_wrong_data)); - assert!(!id2.public.verify(sig2.as_ref(), sig_wrong_data)); - - let id0_bytes = id0.to_bytes(); - let id1_bytes = id1.to_bytes(); - let id2_bytes = id2.to_bytes(); - - /* - println!( - "identity: serialized secret lengths: x25519 {}, x25519p384 {}, p384 {}, p384pqc {}", - id0_bytes.len(), - id1_bytes.len(), - id2_bytes.len(), - id3_bytes.len() - ); - */ - - let id0_de = IdentitySecret::from_bytes(id0_bytes.as_slice()).unwrap(); - let id1_de = IdentitySecret::from_bytes(id1_bytes.as_slice()).unwrap(); - let id2_de = IdentitySecret::from_bytes(id2_bytes.as_slice()).unwrap(); - - assert_eq!(id0_de.to_bytes(), id0.to_bytes()); - assert_eq!(id1_de.to_bytes(), id1.to_bytes()); - assert_eq!(id2_de.to_bytes(), id2.to_bytes()); - - let id0_bytes = id0.public.to_bytes(); - let id1_bytes = id1.public.to_bytes(); - let id2_bytes = id2.public.to_bytes(); - - /* - println!( - "identity: serialized public lengths: x25519 {}, x25519p384 {}, p384 {}, p384pqc {}", - id0_bytes.len(), - id1_bytes.len(), - id2_bytes.len(), - id3_bytes.len() - ); - */ - - let id0_de = Identity::from_bytes(id0_bytes.as_slice()).unwrap(); - let id1_de = Identity::from_bytes(id1_bytes.as_slice()).unwrap(); - let id2_de = Identity::from_bytes(id2_bytes.as_slice()).unwrap(); - - assert_eq!(id0_de.to_bytes(), id0.public.to_bytes()); - assert_eq!(id1_de.to_bytes(), id1.public.to_bytes()); - assert_eq!(id2_de.to_bytes(), id2.public.to_bytes()); - } -} -*/ +pub(crate) const ADDRESS_ERR: InvalidParameterError = InvalidParameterError("invalid address"); +pub(crate) const IDENTITY_ERR: InvalidParameterError = InvalidParameterError("invalid identity"); diff --git a/src/p384.rs b/src/p384.rs index 8b13789..8d40df7 100644 --- a/src/p384.rs +++ b/src/p384.rs @@ -1 +1,648 @@ +use std::hash::Hash; +use std::io::Write; +use std::mem::{size_of, transmute}; +use std::str::FromStr; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; + +use zerotier_common_utils::arrayvec::ArrayVec; +use zerotier_common_utils::base64; +use zerotier_common_utils::blob::Blob; +use zerotier_common_utils::error::InvalidParameterError; +use zerotier_common_utils::tofrombytes::ToFromBytes; +use zerotier_crypto_glue::hash::SHA384; +use zerotier_crypto_glue::p384::*; + +use crate::{base24, base62}; +use crate::{ADDRESS_ERR, IDENTITY_ERR}; + +/// 384-bit ZeroTier address. +#[repr(transparent)] +#[derive(Clone, Copy, PartialEq, Eq)] +pub struct Address([u64; 6]); // treated as [u8; 48] + +/// 128-bit short address prefix. +#[repr(transparent)] +#[derive(Clone, Copy, PartialEq, Eq)] +pub struct ShortAddress([u64; 2]); // treated as [u8; 16] + +impl Address { + /// These addresses have the prefix 0xfc so their 128-bit short prefix is also a private IPv6 address. + pub const REQUIRED_PREFIX: u8 = 0xfc; + + #[inline(always)] + pub fn as_bytes(&self) -> &[u8; 48] { + debug_assert_eq!(size_of::<[u8; 48]>(), size_of::<[u64; 6]>()); + unsafe { &*self.0.as_ptr().cast::<[u8; 48]>() } + } + + #[inline(always)] + pub fn prefix(&self) -> &ShortAddress { + // Safe since this is a transparent u128. + unsafe { transmute(&self.0[0]) } + } + + #[inline(always)] + fn as_mut_bytes(&mut self) -> &mut [u8; 48] { + debug_assert_eq!(size_of::<[u8; 48]>(), size_of::<[u64; 6]>()); + unsafe { &mut *self.0.as_mut_ptr().cast::<[u8; 48]>() } + } + + #[inline(always)] + fn is_valid(&self) -> bool { + self.as_bytes()[0] == Self::REQUIRED_PREFIX + } +} + +impl ShortAddress { + #[inline(always)] + pub fn as_bytes(&self) -> &[u8; 16] { + debug_assert_eq!(size_of::<[u8; 16]>(), size_of::<[u64; 2]>()); + unsafe { &*(&self.0 as *const [u64; 2]).cast() } + } + + #[inline(always)] + fn as_mut_bytes(&mut self) -> &mut [u8; 16] { + debug_assert_eq!(size_of::<[u8; 16]>(), size_of::<[u64; 2]>()); + unsafe { &mut *(&mut self.0 as *mut [u64; 2]).cast() } + } + + #[inline(always)] + fn is_valid(&self) -> bool { + self.as_bytes()[0] == Address::REQUIRED_PREFIX + } +} + +impl TryFrom<[u8; 48]> for Address { + type Error = InvalidParameterError; + + #[inline] + fn try_from(value: [u8; 48]) -> Result { + let a = Self(unsafe { transmute(value) }); + if a.is_valid() { + Ok(a) + } else { + Err(ADDRESS_ERR) + } + } +} + +impl TryFrom<[u8; 16]> for ShortAddress { + type Error = InvalidParameterError; + + #[inline] + fn try_from(value: [u8; 16]) -> Result { + let a = Self(unsafe { transmute(value) }); + if a.is_valid() { + Ok(a) + } else { + Err(ADDRESS_ERR) + } + } +} + +impl From
for [u8; 48] { + #[inline(always)] + fn from(value: Address) -> Self { + unsafe { transmute(value) } + } +} + +impl From for [u8; 16] { + #[inline(always)] + fn from(value: ShortAddress) -> Self { + unsafe { transmute(value) } + } +} + +fn first_128_to_string(b: &[u8], s: &mut String) { + base24::encode_4to7(&b[0..4], s); + s.push('.'); + base24::encode_4to7(&b[4..8], s); + s.push('.'); + base24::encode_4to7(&b[8..12], s); + s.push('.'); + base24::encode_4to7(&b[12..16], s); +} + +impl ToString for Address { + fn to_string(&self) -> String { + let mut s = String::with_capacity(76); + first_128_to_string(&self.as_bytes()[..16], &mut s); + s.push('.'); + base62::encode_8to11(u64::from_be(self.0[2]), &mut s); + base62::encode_8to11(u64::from_be(self.0[3]), &mut s); + base62::encode_8to11(u64::from_be(self.0[4]), &mut s); + base62::encode_8to11(u64::from_be(self.0[5]), &mut s); + s + } +} + +impl ToString for ShortAddress { + fn to_string(&self) -> String { + let mut s = String::with_capacity(32); + first_128_to_string(self.as_bytes(), &mut s); + s + } +} + +impl FromStr for Address { + type Err = InvalidParameterError; + + fn from_str(s: &str) -> Result { + let s = s.trim(); + let sb = s.as_bytes(); + if s.len() == sb.len() && sb.len() == 76 && sb[32] == b'.' { + let prefix = ShortAddress::from_str(&s[..32])?; + Ok(Address([ + prefix.0[0], + prefix.0[1], + base62::decode_11to8(&sb[33..44])?.to_be(), + base62::decode_11to8(&sb[44..55])?.to_be(), + base62::decode_11to8(&sb[55..66])?.to_be(), + base62::decode_11to8(&sb[66..77])?.to_be(), + ])) + } else { + Err(ADDRESS_ERR) + } + } +} + +impl FromStr for ShortAddress { + type Err = InvalidParameterError; + + fn from_str(s: &str) -> Result { + let s = s.trim(); + if s.len() == 31 { + let mut tmp = [0u8; 16]; + let mut w = &mut tmp[..]; + for ss in s.split('.') { + if ss.len() == 7 { + let _ = w.write_all(&base24::decode_7to4(ss.as_bytes())?); + } else { + return Err(ADDRESS_ERR); + } + if w.is_empty() { + return Self::try_from(tmp); + } + } + } + return Err(ADDRESS_ERR); + } +} + +impl ToFromBytes for Address { + #[inline] + fn read_bytes(r: &mut R) -> std::io::Result { + let mut tmp = Self([0; 6]); + r.read_exact(tmp.as_mut_bytes())?; + if tmp.is_valid() { + Ok(tmp) + } else { + Err(std::io::Error::new(std::io::ErrorKind::Other, ADDRESS_ERR.0)) + } + } + + #[inline(always)] + fn write_bytes(&self, w: &mut W) -> std::io::Result<()> { + w.write_all(self.as_bytes()) + } +} + +impl ToFromBytes for ShortAddress { + #[inline] + fn read_bytes(r: &mut R) -> std::io::Result { + let mut tmp = Self([0; 2]); + r.read_exact(tmp.as_mut_bytes())?; + if tmp.is_valid() { + Ok(tmp) + } else { + Err(std::io::Error::new(std::io::ErrorKind::Other, ADDRESS_ERR.0)) + } + } + + #[inline(always)] + fn write_bytes(&self, w: &mut W) -> std::io::Result<()> { + w.write_all(self.as_bytes()) + } +} + +impl AsRef<[u8]> for Address { + #[inline(always)] + fn as_ref(&self) -> &[u8] { + self.as_bytes() + } +} + +impl AsRef<[u8]> for ShortAddress { + #[inline(always)] + fn as_ref(&self) -> &[u8] { + self.as_bytes() + } +} + +impl Hash for Address { + #[inline(always)] + fn hash(&self, state: &mut H) { + state.write_usize(self.0[0] as usize) + } +} + +impl Hash for ShortAddress { + #[inline(always)] + fn hash(&self, state: &mut H) { + state.write_usize(self.0[0] as usize) + } +} + +impl PartialOrd for Address { + #[inline(always)] + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl PartialOrd for ShortAddress { + #[inline(always)] + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for Address { + #[inline(always)] + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.0.iter().map(|i| u64::from_be(*i)).cmp(other.0.iter().map(|i| u64::from_be(*i))) + } +} + +impl Ord for ShortAddress { + #[inline(always)] + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.0.iter().map(|i| u64::from_be(*i)).cmp(other.0.iter().map(|i| u64::from_be(*i))) + } +} + +impl Serialize for Address { + #[inline] + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + if serializer.is_human_readable() { + self.to_string().serialize(serializer) + } else { + <&Blob<48>>::from(self.as_bytes()).serialize(serializer) + } + } +} + +impl<'de> Deserialize<'de> for Address { + #[inline] + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + if deserializer.is_human_readable() { + Address::from_str(<&str>::deserialize(deserializer)?).map_err(|_| serde::de::Error::custom(ADDRESS_ERR.0)) + } else { + Address::try_from(<[u8; 48]>::from(Blob::<48>::deserialize(deserializer)?)).map_err(|_| serde::de::Error::custom(ADDRESS_ERR.0)) + } + } +} + +impl Serialize for ShortAddress { + #[inline] + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + if serializer.is_human_readable() { + self.to_string().serialize(serializer) + } else { + <&Blob<16>>::from(self.as_bytes()).serialize(serializer) + } + } +} + +impl<'de> Deserialize<'de> for ShortAddress { + #[inline] + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + if deserializer.is_human_readable() { + ShortAddress::from_str(<&str>::deserialize(deserializer)?).map_err(|_| serde::de::Error::custom(ADDRESS_ERR.0)) + } else { + ShortAddress::try_from(<[u8; 16]>::from(Blob::<16>::deserialize(deserializer)?)).map_err(|_| serde::de::Error::custom(ADDRESS_ERR.0)) + } + } +} + +impl crate::Address for Address { + const SIZE: usize = 48; +} + +#[derive(Clone)] +pub struct Identity { + pub address: Address, + pub master_signing_key: P384PublicKey, + pub timestamp: u64, + pub ecdh: P384PublicKey, + pub ecdsa: P384PublicKey, + pub master_signature: [u8; P384_ECDSA_SIGNATURE_SIZE], +} + +impl Identity { + fn locally_validate(&self) -> bool { + let mut sign_tmp = ArrayVec::::new(); + sign_tmp.push_slice(self.address.as_bytes()); + sign_tmp.push_slice(&self.timestamp.to_be_bytes()); + sign_tmp.push_slice(self.ecdh.as_bytes()); + sign_tmp.push_slice(self.ecdsa.as_bytes()); + self.address.is_valid() && self.master_signing_key.verify(sign_tmp.as_bytes(), &self.master_signature) + } +} + +impl ToString for Identity { + fn to_string(&self) -> String { + let mut tmp = String::with_capacity(1024); + tmp.push_str(self.address.to_string().as_str()); + tmp.push_str(":1:"); + tmp.push_str(base64::to_string(self.to_bytes_on_stack::<1024>().as_bytes()).as_str()); + tmp + } +} + +impl FromStr for Identity { + type Err = InvalidParameterError; + + fn from_str(s: &str) -> Result { + if let Some(div_idx) = s.rfind(':') { + if div_idx > 0 && div_idx < s.len() { + if let Some(bytes) = base64::from_string(s[div_idx + 1..].as_bytes()) { + return Self::from_bytes(bytes.as_slice()).map_err(|_| IDENTITY_ERR); + } + } + } + return Err(IDENTITY_ERR); + } +} + +impl ToFromBytes for Identity { + fn read_bytes(r: &mut R) -> std::io::Result { + let mut tmp = [0u8; P384_PUBLIC_KEY_SIZE + 8 + P384_PUBLIC_KEY_SIZE + P384_PUBLIC_KEY_SIZE + P384_ECDSA_SIGNATURE_SIZE]; + r.read_exact(&mut tmp)?; + if let (Some(master_signing_key), Some(ecdh), Some(ecdsa)) = ( + P384PublicKey::from_bytes(&tmp[..P384_PUBLIC_KEY_SIZE]), + P384PublicKey::from_bytes(&tmp[P384_PUBLIC_KEY_SIZE + 8..P384_PUBLIC_KEY_SIZE + 8 + P384_PUBLIC_KEY_SIZE]), + P384PublicKey::from_bytes( + &tmp[P384_PUBLIC_KEY_SIZE + 8 + P384_PUBLIC_KEY_SIZE..P384_PUBLIC_KEY_SIZE + 8 + P384_PUBLIC_KEY_SIZE + P384_PUBLIC_KEY_SIZE], + ), + ) { + let id = Self { + address: Address(unsafe { transmute(SHA384::hash(master_signing_key.as_bytes())) }), + master_signing_key, + timestamp: u64::from_be_bytes(tmp[P384_PUBLIC_KEY_SIZE..P384_PUBLIC_KEY_SIZE + 8].try_into().unwrap()), + ecdh, + ecdsa, + master_signature: tmp[P384_PUBLIC_KEY_SIZE + 8 + P384_PUBLIC_KEY_SIZE + P384_PUBLIC_KEY_SIZE..] + .try_into() + .unwrap(), + }; + if id.locally_validate() { + return Ok(id); + } + } + return Err(std::io::Error::new(std::io::ErrorKind::Other, IDENTITY_ERR.0)); + } + + fn write_bytes(&self, w: &mut W) -> std::io::Result<()> { + // The address is SHA384(master_signing_key) so we do not need to output it. We will want + // to recalculate it to check it anyway. + w.write_all(self.master_signing_key.as_bytes())?; + w.write_all(&self.timestamp.to_be_bytes())?; + w.write_all(self.ecdh.as_bytes())?; + w.write_all(self.ecdsa.as_bytes())?; + w.write_all(&self.master_signature) + } +} + +impl PartialEq for Identity { + #[inline(always)] + fn eq(&self, other: &Self) -> bool { + // Two identities are equal if their addresses, which are SHA384(master signing key), match and + // if their signatures match. The latter is because differing signatures would indicate different + // revisions of the working keys within an identity. + self.address.eq(&other.address) && self.master_signature.eq(&other.master_signature) + } +} + +impl Eq for Identity {} + +impl PartialOrd for Identity { + #[inline(always)] + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.address.cmp(&other.address)) + } +} + +impl Ord for Identity { + #[inline(always)] + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.address.cmp(&other.address) + } +} + +impl Hash for Identity { + #[inline(always)] + fn hash(&self, state: &mut H) { + self.address.hash(state); + } +} + +impl crate::Identity for Identity { + const SIZE: usize = P384_PUBLIC_KEY_SIZE + 8 + P384_PUBLIC_KEY_SIZE + P384_PUBLIC_KEY_SIZE + P384_ECDSA_SIGNATURE_SIZE; + const SIGNATURE_SIZE: usize = P384_ECDSA_SIGNATURE_SIZE; + + type Secret = IdentitySecret; + + #[inline(always)] + fn verify_signature(&self, data: &[u8], signature: &[u8]) -> bool { + self.ecdsa.verify(data, signature) + } +} + +pub struct IdentitySecret { + pub public: Identity, + pub master_signing_key: Option, + pub ecdh: P384KeyPair, + pub ecdsa: P384KeyPair, +} + +impl PartialEq for IdentitySecret { + fn eq(&self, other: &Self) -> bool { + self.public == other.public + } +} + +impl Eq for IdentitySecret {} + +impl Clone for IdentitySecret { + fn clone(&self) -> Self { + Self::from_bytes(self.to_bytes_on_stack::<2048>().as_bytes()).unwrap() + } +} + +#[derive(Serialize, Deserialize)] +struct IdentitySecretSerialized { + a: Address, + pm: Blob, + sm: Option>, + ts: u64, + p0: Blob, + s0: Blob, + p1: Blob, + s1: Blob, + ms: Blob, +} + +impl Serialize for IdentitySecret { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + IdentitySecretSerialized { + a: self.public.address, + pm: (*self.public.master_signing_key.as_bytes()).into(), + sm: self.master_signing_key.as_ref().map(|sk| (*sk.secret_key_bytes().as_bytes()).into()), + ts: self.public.timestamp, + p0: (*self.public.ecdh.as_bytes()).into(), + s0: (*self.ecdh.secret_key_bytes().as_bytes()).into(), + p1: (*self.public.ecdsa.as_bytes()).into(), + s1: (*self.ecdsa.secret_key_bytes().as_bytes()).into(), + ms: self.public.master_signature.into(), + } + .serialize(serializer) + } +} + +impl<'de> Deserialize<'de> for IdentitySecret { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let d = ::deserialize(deserializer)?; + if let (Some(pm), Some(ecdh), Some(ecdsa)) = ( + P384PublicKey::from_bytes(d.pm.as_bytes()), + P384KeyPair::from_bytes(d.p0.as_bytes(), d.s0.as_bytes()), + P384KeyPair::from_bytes(d.p1.as_bytes(), d.s1.as_bytes()), + ) { + let mut master_signing_key_sec = None; + if let Some(sm) = d.sm.as_ref() { + if let Some(sm) = P384KeyPair::from_bytes(pm.as_bytes(), sm.as_bytes()) { + master_signing_key_sec = Some(sm); + } else { + return Err(serde::de::Error::custom(IDENTITY_ERR.0)); + } + } + if let Ok(address) = Address::try_from(SHA384::hash(pm.as_bytes())) { + if address.eq(&d.a) { + let id = Self { + public: Identity { + address, + master_signing_key: pm, + timestamp: d.ts, + ecdh: ecdh.to_public_key(), + ecdsa: ecdsa.to_public_key(), + master_signature: *d.ms.as_bytes(), + }, + master_signing_key: master_signing_key_sec, + ecdh, + ecdsa, + }; + if id.public.locally_validate() { + return Ok(id); + } + } + } + } + return Err(serde::de::Error::custom(IDENTITY_ERR.0)); + } +} + +impl crate::IdentitySecret for IdentitySecret { + type Public = Identity; + type Signature = [u8; 96]; + + fn generate(timestamp: u64) -> Self { + let mut address = Address([0; 6]); + let mut master_signing_key; + loop { + master_signing_key = P384KeyPair::generate(); + *address.as_mut_bytes() = SHA384::hash(master_signing_key.public_key_bytes()); + if address.is_valid() { + break; + } + } + + let ecdh = P384KeyPair::generate(); + let ecdsa = P384KeyPair::generate(); + + let mut sign_tmp = ArrayVec::::new(); + sign_tmp.push_slice(address.as_bytes()); + sign_tmp.push_slice(×tamp.to_be_bytes()); + sign_tmp.push_slice(ecdh.public_key_bytes()); + sign_tmp.push_slice(ecdsa.public_key_bytes()); + Self { + public: Identity { + address, + master_signing_key: master_signing_key.to_public_key(), + timestamp, + ecdh: ecdh.to_public_key(), + ecdsa: ecdsa.to_public_key(), + master_signature: master_signing_key.sign(sign_tmp.as_bytes()), + }, + master_signing_key: Some(master_signing_key), + ecdh, + ecdsa, + } + } + + #[inline(always)] + fn public(&self) -> &Self::Public { + &self.public + } + + #[inline(always)] + fn sign(&self, data: &[u8]) -> Self::Signature { + self.ecdsa.sign(data) + } +} + +impl ToFromBytes for IdentitySecret { + fn read_bytes(r: &mut R) -> std::io::Result { + serde_cbor::from_reader(r).map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e.to_string())) + } + + fn write_bytes(&self, w: &mut W) -> std::io::Result<()> { + serde_cbor::to_writer(w, self).map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e.to_string())) + } +} + +#[cfg(test)] +mod tests { + use crate::*; + use zerotier_common_utils::ms_monotonic; + + #[test] + fn generate() { + let start = ms_monotonic(); + for _ in 0..3 { + let secret = p384::IdentitySecret::generate(1); + println!("P: {}", secret.public.to_string()); + } + let end = ms_monotonic(); + println!("generation time: {} ms/identity", ((end - start) as f64) / 3.0); + } +} diff --git a/src/signature.rs b/src/signature.rs deleted file mode 100644 index b8db5de..0000000 --- a/src/signature.rs +++ /dev/null @@ -1,60 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. - * - * (c) ZeroTier, Inc. - * https://www.zerotier.com/ - */ - -use zerotier_crypto::p384::P384_ECDSA_SIGNATURE_SIZE; -use zerotier_crypto::x25519::ED25519_SIGNATURE_SIZE; -use zerotier_utils::arrayvec::ArrayVec; - -/// Buffer type large enough to store any possible signature combination. -/// This can be enlarged if necessary. -pub type Signature = ArrayVec; - -const SIGNATURE_FLAG_ECDSA_P384: u8 = 0x01; -const SIGNATURE_FLAG_EDDSA_ED25519: u8 = 0x02; - -pub(crate) fn make_signature(ecdsa_p384: &[u8], eddsa_ed25519: &[u8]) -> Signature { - let mut s = Signature::new(); - s.push(0); - let mut flags = 0; - - if ecdsa_p384.len() == P384_ECDSA_SIGNATURE_SIZE { - flags |= SIGNATURE_FLAG_ECDSA_P384; - s.push_slice(ecdsa_p384); - } - if eddsa_ed25519.len() == ED25519_SIGNATURE_SIZE { - flags |= SIGNATURE_FLAG_EDDSA_ED25519; - s.push_slice(eddsa_ed25519); - } - - s.as_mut()[0] = flags; - s -} - -/// Returns which signatures are present: (p384, ed25519) -pub(crate) fn parse_signature(mut s: &[u8]) -> (Option<&[u8]>, Option<&[u8]>) { - let mut sigs = (None, None); - - if !s.is_empty() { - let flags = s[0]; - s = &s[1..]; - if (flags & SIGNATURE_FLAG_ECDSA_P384) != 0 { - if s.len() >= P384_ECDSA_SIGNATURE_SIZE { - sigs.0 = Some(&s[..P384_ECDSA_SIGNATURE_SIZE]); - s = &s[P384_ECDSA_SIGNATURE_SIZE..]; - } - } - if (flags & SIGNATURE_FLAG_EDDSA_ED25519) != 0 { - if s.len() >= ED25519_SIGNATURE_SIZE { - sigs.1 = Some(&s[..ED25519_SIGNATURE_SIZE]); - //s = &s[ED25519_SIGNATURE_SIZE..]; - } - } - } - - sigs -} diff --git a/src/x25519.rs b/src/x25519.rs index f9a0bf2..186eb08 100644 --- a/src/x25519.rs +++ b/src/x25519.rs @@ -14,8 +14,7 @@ use zerotier_crypto_glue::hash::SHA512; use zerotier_crypto_glue::salsa::Salsa; use zerotier_crypto_glue::x25519::*; -const ADDRESS_ERR: InvalidParameterError = InvalidParameterError("invalid address"); -const IDENTITY_ERR: InvalidParameterError = InvalidParameterError("invalid identity"); +use crate::{ADDRESS_ERR, IDENTITY_ERR}; /// Legacy 40-bit ZeroTier address. #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -72,7 +71,7 @@ impl FromStr for Address { if s.len() == 10 { Self::try_from(hex::from_string_u64(s)) } else { - Err(InvalidParameterError("invalid address")) + Err(InvalidParameterError(ADDRESS_ERR.0)) } } } @@ -85,7 +84,7 @@ impl ToFromBytes for Address { if tmp.is_valid() { Ok(tmp) } else { - Err(std::io::Error::new(std::io::ErrorKind::Other, "invalid address")) + Err(std::io::Error::new(std::io::ErrorKind::Other, ADDRESS_ERR.0)) } } @@ -110,7 +109,7 @@ impl Serialize for Address { if serializer.is_human_readable() { self.to_string().serialize(serializer) } else { - serializer.serialize_bytes(&self.0) + (self.0[0], self.0[1], self.0[2], self.0[3], self.0[4]).serialize(serializer) } } } @@ -123,7 +122,13 @@ impl<'de> Deserialize<'de> for Address { if deserializer.is_human_readable() { Address::from_str(<&str>::deserialize(deserializer)?).map_err(|_| serde::de::Error::custom(ADDRESS_ERR.0)) } else { - Address::from_bytes(<&[u8]>::deserialize(deserializer)?).map_err(|_| serde::de::Error::custom(ADDRESS_ERR.0)) + let b = <(u8, u8, u8, u8, u8)>::deserialize(deserializer)?; + let a = Self([b.0, b.1, b.2, b.3, b.4]); + if a.is_valid() { + Ok(a) + } else { + Err(serde::de::Error::custom(ADDRESS_ERR.0)) + } } } } @@ -142,12 +147,16 @@ pub struct Identity { impl Identity { fn locally_validate(&self) -> bool { - let mut legacy_address_hasher = SHA512::new(); - legacy_address_hasher.update(&self.ecdh); - legacy_address_hasher.update(&self.eddsa); - let mut legacy_address_hash = legacy_address_hasher.finish(); - legacy_address_derivation_work_function(&mut legacy_address_hash); - legacy_address_hash[0] < LEGACY_ADDRESS_POW_THRESHOLD && legacy_address_hash[59..64].eq(&self.address.0) + if self.address.is_valid() { + let mut legacy_address_hasher = SHA512::new(); + legacy_address_hasher.update(&self.ecdh); + legacy_address_hasher.update(&self.eddsa); + let mut legacy_address_hash = legacy_address_hasher.finish(); + legacy_address_derivation_work_function(&mut legacy_address_hash); + legacy_address_hash[0] < LEGACY_ADDRESS_POW_THRESHOLD && legacy_address_hash[59..64].eq(&self.address.0) + } else { + false + } } } @@ -321,32 +330,11 @@ impl PartialEq for IdentitySecret { impl Eq for IdentitySecret {} -impl PartialOrd for IdentitySecret { - #[inline(always)] - fn partial_cmp(&self, other: &Self) -> Option { - self.public.partial_cmp(&other.public) - } -} - -impl Ord for IdentitySecret { - #[inline(always)] - fn cmp(&self, other: &Self) -> std::cmp::Ordering { - self.public.cmp(&other.public) - } -} - -impl Hash for IdentitySecret { - #[inline(always)] - fn hash(&self, state: &mut H) { - self.public.hash(state); - } -} - impl crate::IdentitySecret for IdentitySecret { type Public = Identity; type Signature = [u8; 96]; - fn generate() -> Self { + fn generate(_timestamp: u64) -> Self { let mut ecdh = X25519KeyPair::generate(); let eddsa = Ed25519KeyPair::generate(); let mut legacy_address_hasher = SHA512::new(); @@ -388,7 +376,7 @@ impl crate::IdentitySecret for IdentitySecret { #[derive(Serialize, Deserialize)] struct IdentitySecretSerialized { - a: Blob<5>, + a: Address, p0: Blob, s0: Blob, p1: Blob, @@ -401,7 +389,7 @@ impl Serialize for IdentitySecret { S: Serializer, { IdentitySecretSerialized { - a: self.public.address.0.into(), + a: self.public.address, p0: self.public.ecdh.into(), s0: (*self.ecdh.secret_bytes().as_bytes()).into(), p1: self.public.eddsa.into(), @@ -417,26 +405,24 @@ impl<'de> Deserialize<'de> for IdentitySecret { D: Deserializer<'de>, { let d = ::deserialize(deserializer)?; - let ecdh = X25519KeyPair::from_bytes(d.p0.as_bytes(), d.s0.as_bytes()); - let eddsa = Ed25519KeyPair::from_bytes(d.p1.as_bytes(), d.s1.as_bytes()); - if ecdh.is_none() || eddsa.is_none() { - return Err(serde::de::Error::custom(IDENTITY_ERR.0)); + if let (Some(ecdh), Some(eddsa)) = ( + X25519KeyPair::from_bytes(d.p0.as_bytes(), d.s0.as_bytes()), + Ed25519KeyPair::from_bytes(d.p1.as_bytes(), d.s1.as_bytes()), + ) { + let id = Self { + public: Identity { + address: d.a, + ecdh: ecdh.public_bytes(), + eddsa: eddsa.public_bytes(), + }, + ecdh, + eddsa, + }; + if id.public.locally_validate() { + return Ok(id); + } } - let ecdh = ecdh.unwrap(); - let eddsa = eddsa.unwrap(); - let id = Self { - public: Identity { - address: Address(d.a.into()), - ecdh: ecdh.public_bytes(), - eddsa: eddsa.public_bytes(), - }, - ecdh, - eddsa, - }; - if !id.public.address.is_valid() || !id.public.locally_validate() { - return Err(serde::de::Error::custom(IDENTITY_ERR.0)); - } - return Ok(id); + return Err(serde::de::Error::custom(IDENTITY_ERR.0)); } } @@ -506,7 +492,7 @@ mod tests { fn generate() { let start = ms_monotonic(); for _ in 0..3 { - let secret = x25519::IdentitySecret::generate(); + let secret = x25519::IdentitySecret::generate(0); println!("S: {}", secret.to_string()); println!("P: {}", secret.public.to_string()); } @@ -516,7 +502,7 @@ mod tests { #[test] fn tostring_fromstring() { - let secret = x25519::IdentitySecret::generate(); + let secret = x25519::IdentitySecret::generate(0); assert!(x25519::Address::from_str(secret.public.address.to_string().as_str()) .unwrap() .eq(&secret.public.address)); @@ -526,7 +512,7 @@ mod tests { #[test] fn tobytes_frombytes() { - let secret = x25519::IdentitySecret::generate(); + let secret = x25519::IdentitySecret::generate(0); assert!(x25519::Address::from_bytes(secret.public.address.to_bytes().as_slice()) .unwrap() .eq(&secret.public.address));