diff --git a/.gitignore b/.gitignore index 7dd2bd4..f0805b3 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,4 @@ /.idea /.nova *.secret +.vscode diff --git a/src/p384.rs b/src/p384.rs deleted file mode 100644 index 623cf05..0000000 --- a/src/p384.rs +++ /dev/null @@ -1,865 +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::fmt::Debug; -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 crate::zeroize::{Zeroize, ZeroizeOnDrop}; -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}; - -const DOMAIN_MASTER_SIG: &[u8] = b"ZTID_MASTERSIG_P384"; -const DOMAIN_SUBKEY_SIG: &[u8] = b"ZTID_SUBKEYSIG_P384"; - -// Implementation note: the addresses use u64 arrays that are actually treated as flat byte -// array memory arenas in order to optimize for fast lookup when these are used as map keys. -// This reduces the number of instructions required to perform equality comparisons and -// simplifies the implementation of Hash. The effect is small but might matter at scale. - -/// 384-bit ZeroTier address. -/// An address is the SHA384(public master signing key) of an identity. -#[repr(transparent)] -#[derive(Clone, Copy, PartialEq, Eq)] -pub struct Address([u64; 6]); // treated as [u8; 48] - -/// 128-bit short address prefix. -/// -/// Short addresses are primarily for cases where humans need to type addresses or where -/// they need to be mapped onto an IPv6 address. The fully qualified 384-bit address -/// should be preferred if address transfer is automated or via cut/paste. -#[repr(transparent)] -#[derive(Clone, Copy, PartialEq, Eq)] -pub struct ShortAddress([u64; 2]); // treated as [u8; 16] - -impl Address { - /// The first byte of a valid address must be 0xfc. - /// - /// This allows the 128-bit prefix of every address to also be a valid private IPv6 address, - /// which is useful for a number of purposes. It also imposes a small extra computational cost - /// on the generation of new identities with a given short address, making it slightly harder - /// to brute force the short address space. (A 128-bit space is already impractical to brute - /// force, but untargeted birthday type collisions are possible with sufficient storage.) - pub const REQUIRED_PREFIX: u8 = 0xfc; - - /// Length of a full address in string format. - pub const STRING_SIZE: usize = 76; - - /// Get this address as a raw byte array. - #[inline(always)] - pub fn as_bytes(&self) -> &[u8; 48] { - debug_assert_eq!(size_of::<[u8; 48]>(), size_of::()); - unsafe { &*self.0.as_ptr().cast::<[u8; 48]>() } - } - - /// Get this address's 128-bit short prefix. - #[inline(always)] - pub fn prefix(&self) -> &ShortAddress { - unsafe { transmute(&self.0) } - } - - /// Get mutable bytes. - /// This is private because it should be impossible for external code to create an invalid address. - #[inline(always)] - fn as_mut_bytes(&mut self) -> &mut [u8; 48] { - debug_assert_eq!(size_of::<[u8; 48]>(), size_of::()); - unsafe { &mut *self.0.as_mut_ptr().cast::<[u8; 48]>() } - } - - /// Check address validity, used in deserialization code. - #[inline(always)] - fn is_valid(&self) -> bool { - self.as_bytes()[0] == Self::REQUIRED_PREFIX - } -} - -impl ShortAddress { - pub const SIZE: usize = 16; - - #[inline(always)] - pub fn as_bytes(&self) -> &[u8; Self::SIZE] { - debug_assert_eq!(size_of::<[u8; Self::SIZE]>(), size_of::()); - unsafe { &*(&self.0 as *const [u64; 2]).cast() } - } - - #[inline(always)] - fn as_mut_bytes(&mut self) -> &mut [u8; Self::SIZE] { - debug_assert_eq!(size_of::<[u8; Self::SIZE]>(), size_of::()); - 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(Self::STRING_SIZE); - 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 Debug for Address { - #[inline] - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(self.to_string().as_str()) - } -} - -impl Debug for ShortAddress { - #[inline] - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(self.to_string().as_str()) - } -} - -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() == Self::STRING_SIZE && sb[31] == b'.' { - let prefix = ShortAddress::from_str(&s[..31])?; - Ok(Address([ - prefix.0[0], - prefix.0[1], - base62::decode_11to8(&sb[32..43])?.to_be(), - base62::decode_11to8(&sb[43..54])?.to_be(), - base62::decode_11to8(&sb[54..65])?.to_be(), - base62::decode_11to8(&sb[65..76])?.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 From for std::net::Ipv6Addr { - fn from(value: ShortAddress) -> Self { - std::net::Ipv6Addr::from(*value.as_bytes()) - } -} -impl From<&ShortAddress> for std::net::Ipv6Addr { - fn from(value: &ShortAddress) -> Self { - std::net::Ipv6Addr::from(*value.as_bytes()) - } -} - -impl crate::Address for Address { - const SIZE: usize = 48; -} - -const TIMESTAMP_START: usize = P384_PUBLIC_KEY_SIZE; -const SUBKEY_ECDH_START: usize = TIMESTAMP_START + 8; -const SUBKEY_ECDSA_START: usize = SUBKEY_ECDH_START + P384_PUBLIC_KEY_SIZE; -const MASTER_SIG_START: usize = SUBKEY_ECDSA_START + P384_PUBLIC_KEY_SIZE; -const SUBKEY_SIG_START: usize = MASTER_SIG_START + P384_ECDSA_SIGNATURE_SIZE; -const P384_IDENTITY_SIZE: usize = SUBKEY_SIG_START + P384_ECDSA_SIGNATURE_SIZE; - -/// NIST P-384 based new format identity with key upgrade capability. -#[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], - pub ecdsa_signature: [u8; P384_ECDSA_SIGNATURE_SIZE], -} - -impl Identity { - fn locally_validate(&self) -> bool { - let to_sign: &[&[u8]] = &[ - self.master_signing_key.as_bytes(), - &self.timestamp.to_be_bytes(), - self.ecdh.as_bytes(), - self.ecdsa.as_bytes(), - ]; - - self.address.is_valid() - && self - .master_signing_key - .verify_all(DOMAIN_MASTER_SIG, to_sign, &self.master_signature) - && self.ecdsa.verify_all(DOMAIN_SUBKEY_SIG, to_sign, &self.ecdsa_signature) - } - - /// Returns true if this identity should replace the other. - /// This just returns true if the timestamp is newer and the address (master signing key hash) is the same. - #[inline(always)] - pub fn replaces(&self, other: &Identity) -> bool { - self.address == other.address && self.timestamp > other.timestamp - } -} - -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 Debug for Identity { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("x25519::Identity") - .field("address", &self.address) - .field("master_signing_key", self.master_signing_key.as_bytes()) - .field("timestamp", &self.timestamp) - .field("ecdh", self.ecdh.as_bytes()) - .field("ecdsa", self.ecdsa.as_bytes()) - .field("master_signature", &self.master_signature) - .field("ecdsa_signature", &self.ecdsa_signature) - .finish() - } -} - -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_IDENTITY_SIZE]; - r.read_exact(&mut tmp)?; - if let (Some(master_signing_key), Some(ecdh), Some(ecdsa)) = ( - P384PublicKey::from_bytes(&tmp[..TIMESTAMP_START]), - P384PublicKey::from_bytes(&tmp[SUBKEY_ECDH_START..SUBKEY_ECDSA_START]), - P384PublicKey::from_bytes(&tmp[SUBKEY_ECDSA_START..MASTER_SIG_START]), - ) { - let id = Self { - address: Address(unsafe { transmute(SHA384::hash(master_signing_key.as_bytes())) }), - master_signing_key, - timestamp: u64::from_be_bytes(tmp[TIMESTAMP_START..SUBKEY_ECDH_START].try_into().unwrap()), - ecdh, - ecdsa, - master_signature: tmp[MASTER_SIG_START..SUBKEY_SIG_START].try_into().unwrap(), - ecdsa_signature: tmp[SUBKEY_SIG_START..P384_IDENTITY_SIZE].try_into().unwrap(), - }; - if id.locally_validate() { - return Ok(id); - } - } - return Err(std::io::Error::new(std::io::ErrorKind::Other, IDENTITY_ERR.0)); - } - - /// This function cannot rollback changes to `w` if an error occurs. - 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)?; - w.write_all(&self.ecdsa_signature) - } -} - -impl Serialize for Identity { - #[inline] - 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::().as_ref()) - } - } -} - -impl<'de> Deserialize<'de> for Identity { - #[inline] - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - if deserializer.is_human_readable() { - Identity::from_str(<&str>::deserialize(deserializer)?).map_err(|_| serde::de::Error::custom(IDENTITY_ERR.0)) - } else { - struct Visitor; - - impl<'de> serde::de::Visitor<'de> for Visitor { - type Value = Identity; - - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("a pair of ratchet states") - } - - fn visit_bytes(self, v: &[u8]) -> Result - where - E: serde::de::Error, - { - Identity::from_bytes(v).map_err(|_| serde::de::Error::custom(IDENTITY_ERR.0)) - } - } - deserializer.deserialize_bytes(Visitor) - } - } -} - -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_IDENTITY_SIZE; - const SIGNATURE_SIZE: usize = P384_ECDSA_SIGNATURE_SIZE; - - type Secret = IdentitySecret; - - #[inline(always)] - fn verify_signature(&self, data: &[u8], signature: &[u8]) -> bool { - if let Ok(sig) = signature.try_into() { - self.ecdsa.verify_raw(data, sig) - } else { - false - } - } - - #[inline(always)] - fn verify_domain_restricted_signature(&self, domain: &[u8], data: &[u8], signature: &[u8]) -> bool { - if let Ok(sig) = signature.try_into() { - self.ecdsa.verify(domain, data, sig) - } else { - false - } - } -} - -/// Secret NIST P-384 identity (also contains public). -/// -/// The master signing key is optional to allow it to be removed and placed in cold storage. -/// It's only needed if the identity is to have its regular working keys upgraded. -pub struct IdentitySecret { - pub public: Identity, - pub master_signing_key: Option, - pub ecdh: P384KeyPair, - pub ecdsa: P384KeyPair, -} - -impl PartialEq for IdentitySecret { - #[inline(always)] - 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, Zeroize, ZeroizeOnDrop)] -struct IdentitySecretSerialized { - #[zeroize(skip)] - a: Address, - #[zeroize(skip)] - pm: Blob, - sm: Option>, - #[zeroize(skip)] - ts: u64, - #[zeroize(skip)] - p0: Blob, - s0: Blob, - #[zeroize(skip)] - p1: Blob, - s1: Blob, - #[zeroize(skip)] - ms: Blob, - #[zeroize(skip)] - ss: Blob, -} - -impl Serialize for IdentitySecret { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - let mut tmp = IdentitySecretSerialized { - a: self.public.address, - pm: (*self.public.master_signing_key.as_bytes()).into(), - sm: None, - ts: self.public.timestamp, - p0: (*self.public.ecdh.as_bytes()).into(), - s0: Blob::default(), - p1: (*self.public.ecdsa.as_bytes()).into(), - s1: Blob::default(), - ms: self.public.master_signature.into(), - ss: self.public.ecdsa_signature.into(), - }; - self.ecdh.secret_key_bytes(&mut tmp.s0); - self.ecdsa.secret_key_bytes(&mut tmp.s1); - if let Some(ecdsa) = self.master_signing_key.as_ref() { - tmp.sm = Some(Blob::default()); - ecdsa.secret_key_bytes(tmp.sm.as_mut().unwrap()); - } - tmp.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(), - ecdsa_signature: *d.ss.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 to_sign: &[&[u8]] = &[ - master_signing_key.public_key_bytes(), - ×tamp.to_be_bytes(), - ecdh.public_key_bytes(), - 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_all(DOMAIN_MASTER_SIG, to_sign), - ecdsa_signature: ecdsa.sign_all(DOMAIN_SUBKEY_SIG, to_sign), - }, - 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_raw(data) - } - - #[inline(always)] - fn sign_domain_restricted(&self, domain: &[u8], data: &[u8]) -> Self::Signature { - self.ecdsa.sign(domain, 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!("p384 generation time: {} ms/identity", ((end - start) as f64) / 3.0); - } - - #[test] - fn tostring_fromstring() { - let secret = p384::IdentitySecret::generate(0); - assert!(p384::Address::from_str(secret.public.address.to_string().as_str()) - .unwrap() - .eq(&secret.public.address)); - assert!(p384::Identity::from_str(secret.public.to_string().as_str()) - .unwrap() - .eq(&secret.public)); - } - - #[test] - fn tobytes_frombytes() { - let secret = p384::IdentitySecret::generate(0); - assert!(p384::Address::from_bytes(secret.public.address.to_bytes().as_slice()) - .unwrap() - .eq(&secret.public.address)); - assert!(p384::Identity::from_bytes(secret.public.to_bytes().as_slice()) - .unwrap() - .eq(&secret.public)); - assert!(p384::IdentitySecret::from_bytes(secret.to_bytes().as_slice()) - .unwrap() - .eq(&secret)); - } -} diff --git a/src/p384/address.rs b/src/p384/address.rs new file mode 100644 index 0000000..fc7a019 --- /dev/null +++ b/src/p384/address.rs @@ -0,0 +1,218 @@ +/* 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::fmt::Debug; +use std::hash::Hash; +use std::mem::{size_of, transmute}; + +use serde::{Deserialize, Deserializer, Serialize, Serializer}; + +use crate::p384::*; +use zerotier_common_utils::blob::Blob; +use zerotier_common_utils::error::InvalidParameterError; +use zerotier_common_utils::tofrombytes::ToFromBytes; + +use crate::base62; +use crate::ADDRESS_ERR; + +// Implementation note: the addresses use u64 arrays that are actually treated as flat byte +// array memory arenas in order to optimize for fast lookup when these are used as map keys. +// This reduces the number of instructions required to perform equality comparisons and +// simplifies the implementation of Hash. The effect is small but might matter at scale. + +/// 384-bit ZeroTier address. +/// An address is the SHA384(public master signing key) of an identity. +#[repr(transparent)] +#[derive(Clone, Copy, PartialEq, Eq)] +pub struct Address(pub(crate) [u64; 6]); // treated as [u8; 48] + +impl Address { + /// The first byte of a valid address must be 0xfc. + /// + /// This allows the 128-bit prefix of every address to also be a valid private IPv6 address, + /// which is useful for a number of purposes. It also imposes a small extra computational cost + /// on the generation of new identities with a given short address, making it slightly harder + /// to brute force the short address space. (A 128-bit space is already impractical to brute + /// force, but untargeted birthday type collisions are possible with sufficient storage.) + pub const REQUIRED_PREFIX: u8 = 0xfc; + + /// Length of a full address in string format. + pub const STRING_SIZE: usize = 76; + + /// Get this address as a raw byte array. + #[inline(always)] + pub fn as_bytes(&self) -> &[u8; 48] { + debug_assert_eq!(size_of::<[u8; 48]>(), size_of::()); + unsafe { &*self.0.as_ptr().cast::<[u8; 48]>() } + } + + /// Get this address's 128-bit short prefix. + #[inline(always)] + pub fn prefix(&self) -> &ShortAddress { + unsafe { transmute(&self.0) } + } + + /// Get mutable bytes. + /// This is private because it should be impossible for external code to create an invalid address. + #[inline(always)] + pub(crate) fn as_mut_bytes(&mut self) -> &mut [u8; 48] { + debug_assert_eq!(size_of::<[u8; 48]>(), size_of::()); + unsafe { &mut *self.0.as_mut_ptr().cast::<[u8; 48]>() } + } + + /// Check address validity, used in deserialization code. + #[inline(always)] + pub(crate) fn is_valid(&self) -> bool { + self.as_bytes()[0] == Self::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 From
for [u8; 48] { + #[inline(always)] + fn from(value: Address) -> Self { + unsafe { transmute(value) } + } +} + +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 ToString for Address { + fn to_string(&self) -> String { + let mut s = String::with_capacity(Self::STRING_SIZE); + 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 Debug for Address { + #[inline] + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(self.to_string().as_str()) + } +} + +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() == Self::STRING_SIZE && sb[31] == b'.' { + let prefix = ShortAddress::from_str(&s[..31])?; + Ok(Address([ + prefix.0[0], + prefix.0[1], + base62::decode_11to8(&sb[32..43])?.to_be(), + base62::decode_11to8(&sb[43..54])?.to_be(), + base62::decode_11to8(&sb[54..65])?.to_be(), + base62::decode_11to8(&sb[65..76])?.to_be(), + ])) + } else { + Err(ADDRESS_ERR) + } + } +} + +impl AsRef<[u8]> for Address { + #[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 PartialOrd for Address { + #[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 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 crate::Address for Address { + const SIZE: usize = 48; +} diff --git a/src/p384/identity.rs b/src/p384/identity.rs new file mode 100644 index 0000000..d138d54 --- /dev/null +++ b/src/p384/identity.rs @@ -0,0 +1,223 @@ +use crate::p384::*; + +const TIMESTAMP_START: usize = P384_PUBLIC_KEY_SIZE; +const SUBKEY_ECDH_START: usize = TIMESTAMP_START + 8; +const SUBKEY_ECDSA_START: usize = SUBKEY_ECDH_START + P384_PUBLIC_KEY_SIZE; +const MASTER_SIG_START: usize = SUBKEY_ECDSA_START + P384_PUBLIC_KEY_SIZE; +const SUBKEY_SIG_START: usize = MASTER_SIG_START + P384_ECDSA_SIGNATURE_SIZE; +const P384_IDENTITY_SIZE: usize = SUBKEY_SIG_START + P384_ECDSA_SIGNATURE_SIZE; + +/// NIST P-384 based new format identity with key upgrade capability. +#[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], + pub ecdsa_signature: [u8; P384_ECDSA_SIGNATURE_SIZE], +} + +impl Identity { + pub(crate) fn locally_validate(&self) -> bool { + let to_sign: &[&[u8]] = &[ + self.master_signing_key.as_bytes(), + &self.timestamp.to_be_bytes(), + self.ecdh.as_bytes(), + self.ecdsa.as_bytes(), + ]; + + self.address.is_valid() + && self + .master_signing_key + .verify_all(DOMAIN_MASTER_SIG, to_sign, &self.master_signature) + && self.ecdsa.verify_all(DOMAIN_SUBKEY_SIG, to_sign, &self.ecdsa_signature) + } + + /// Returns true if this identity should replace the other. + /// This just returns true if the timestamp is newer and the address (master signing key hash) is the same. + #[inline(always)] + pub fn replaces(&self, other: &Identity) -> bool { + self.address == other.address && self.timestamp > other.timestamp + } +} + +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 Debug for Identity { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("x25519::Identity") + .field("address", &self.address) + .field("master_signing_key", self.master_signing_key.as_bytes()) + .field("timestamp", &self.timestamp) + .field("ecdh", self.ecdh.as_bytes()) + .field("ecdsa", self.ecdsa.as_bytes()) + .field("master_signature", &self.master_signature) + .field("ecdsa_signature", &self.ecdsa_signature) + .finish() + } +} + +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_IDENTITY_SIZE]; + r.read_exact(&mut tmp)?; + if let (Some(master_signing_key), Some(ecdh), Some(ecdsa)) = ( + P384PublicKey::from_bytes(&tmp[..TIMESTAMP_START]), + P384PublicKey::from_bytes(&tmp[SUBKEY_ECDH_START..SUBKEY_ECDSA_START]), + P384PublicKey::from_bytes(&tmp[SUBKEY_ECDSA_START..MASTER_SIG_START]), + ) { + let id = Self { + address: Address(unsafe { transmute(SHA384::hash(master_signing_key.as_bytes())) }), + master_signing_key, + timestamp: u64::from_be_bytes(tmp[TIMESTAMP_START..SUBKEY_ECDH_START].try_into().unwrap()), + ecdh, + ecdsa, + master_signature: tmp[MASTER_SIG_START..SUBKEY_SIG_START].try_into().unwrap(), + ecdsa_signature: tmp[SUBKEY_SIG_START..P384_IDENTITY_SIZE].try_into().unwrap(), + }; + if id.locally_validate() { + return Ok(id); + } + } + return Err(std::io::Error::new(std::io::ErrorKind::Other, IDENTITY_ERR.0)); + } + + /// This function cannot rollback changes to `w` if an error occurs. + 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)?; + w.write_all(&self.ecdsa_signature) + } +} + +impl Serialize for Identity { + #[inline] + 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::().as_ref()) + } + } +} + +impl<'de> Deserialize<'de> for Identity { + #[inline] + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + if deserializer.is_human_readable() { + Identity::from_str(<&str>::deserialize(deserializer)?).map_err(|_| serde::de::Error::custom(IDENTITY_ERR.0)) + } else { + struct Visitor; + + impl<'de> serde::de::Visitor<'de> for Visitor { + type Value = Identity; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("a pair of ratchet states") + } + + fn visit_bytes(self, v: &[u8]) -> Result + where + E: serde::de::Error, + { + Identity::from_bytes(v).map_err(|_| serde::de::Error::custom(IDENTITY_ERR.0)) + } + } + deserializer.deserialize_bytes(Visitor) + } + } +} + +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_IDENTITY_SIZE; + const SIGNATURE_SIZE: usize = P384_ECDSA_SIGNATURE_SIZE; + + type Secret = IdentitySecret; + + #[inline(always)] + fn verify_signature(&self, data: &[u8], signature: &[u8]) -> bool { + if let Ok(sig) = signature.try_into() { + self.ecdsa.verify_raw(data, sig) + } else { + false + } + } + + #[inline(always)] + fn verify_domain_restricted_signature(&self, domain: &[u8], data: &[u8], signature: &[u8]) -> bool { + if let Ok(sig) = signature.try_into() { + self.ecdsa.verify(domain, data, sig) + } else { + false + } + } +} diff --git a/src/p384/identity_secret.rs b/src/p384/identity_secret.rs new file mode 100644 index 0000000..876acdf --- /dev/null +++ b/src/p384/identity_secret.rs @@ -0,0 +1,187 @@ +use crate::p384::*; + +/// Secret NIST P-384 identity (also contains public). +/// +/// The master signing key is optional to allow it to be removed and placed in cold storage. +/// It's only needed if the identity is to have its regular working keys upgraded. +pub struct IdentitySecret { + pub public: Identity, + pub master_signing_key: Option, + pub ecdh: P384KeyPair, + pub ecdsa: P384KeyPair, +} + +impl PartialEq for IdentitySecret { + #[inline(always)] + 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, Zeroize, ZeroizeOnDrop)] +struct IdentitySecretSerialized { + #[zeroize(skip)] + a: Address, + #[zeroize(skip)] + pm: Blob, + sm: Option>, + #[zeroize(skip)] + ts: u64, + #[zeroize(skip)] + p0: Blob, + s0: Blob, + #[zeroize(skip)] + p1: Blob, + s1: Blob, + #[zeroize(skip)] + ms: Blob, + #[zeroize(skip)] + ss: Blob, +} + +impl Serialize for IdentitySecret { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut tmp = IdentitySecretSerialized { + a: self.public.address, + pm: (*self.public.master_signing_key.as_bytes()).into(), + sm: None, + ts: self.public.timestamp, + p0: (*self.public.ecdh.as_bytes()).into(), + s0: Blob::default(), + p1: (*self.public.ecdsa.as_bytes()).into(), + s1: Blob::default(), + ms: self.public.master_signature.into(), + ss: self.public.ecdsa_signature.into(), + }; + self.ecdh.secret_key_bytes(&mut tmp.s0); + self.ecdsa.secret_key_bytes(&mut tmp.s1); + if let Some(ecdsa) = self.master_signing_key.as_ref() { + tmp.sm = Some(Blob::default()); + ecdsa.secret_key_bytes(tmp.sm.as_mut().unwrap()); + } + tmp.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(), + ecdsa_signature: *d.ss.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 to_sign: &[&[u8]] = &[ + master_signing_key.public_key_bytes(), + ×tamp.to_be_bytes(), + ecdh.public_key_bytes(), + 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_all(DOMAIN_MASTER_SIG, to_sign), + ecdsa_signature: ecdsa.sign_all(DOMAIN_SUBKEY_SIG, to_sign), + }, + 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_raw(data) + } + + #[inline(always)] + fn sign_domain_restricted(&self, domain: &[u8], data: &[u8]) -> Self::Signature { + self.ecdsa.sign(domain, 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())) + } +} diff --git a/src/p384/mod.rs b/src/p384/mod.rs new file mode 100644 index 0000000..542638a --- /dev/null +++ b/src/p384/mod.rs @@ -0,0 +1,87 @@ +/* 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::fmt::Debug; +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 crate::zeroize::{Zeroize, ZeroizeOnDrop}; +use zerotier_common_utils::{base64, blob::Blob, error::InvalidParameterError, tofrombytes::ToFromBytes}; +use zerotier_crypto_glue::{hash::SHA384, p384::*}; + +use crate::base24; +use crate::{ADDRESS_ERR, IDENTITY_ERR}; + +mod address; +mod identity; +mod identity_secret; +mod short_address; + +pub use address::*; +pub use identity::*; +pub use identity_secret::*; +pub use short_address::*; + +pub(crate) const DOMAIN_MASTER_SIG: &[u8] = b"ZTID_MASTERSIG_P384"; +pub(crate) const DOMAIN_SUBKEY_SIG: &[u8] = b"ZTID_SUBKEYSIG_P384"; + +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); +} + +#[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!("p384 generation time: {} ms/identity", ((end - start) as f64) / 3.0); + } + + #[test] + fn tostring_fromstring() { + let secret = p384::IdentitySecret::generate(0); + assert!(p384::Address::from_str(secret.public.address.to_string().as_str()) + .unwrap() + .eq(&secret.public.address)); + assert!(p384::Identity::from_str(secret.public.to_string().as_str()) + .unwrap() + .eq(&secret.public)); + } + + #[test] + fn tobytes_frombytes() { + let secret = p384::IdentitySecret::generate(0); + assert!(p384::Address::from_bytes(secret.public.address.to_bytes().as_slice()) + .unwrap() + .eq(&secret.public.address)); + assert!(p384::Identity::from_bytes(secret.public.to_bytes().as_slice()) + .unwrap() + .eq(&secret.public)); + assert!(p384::IdentitySecret::from_bytes(secret.to_bytes().as_slice()) + .unwrap() + .eq(&secret)); + } +} diff --git a/src/p384/short_address.rs b/src/p384/short_address.rs new file mode 100644 index 0000000..387f7bc --- /dev/null +++ b/src/p384/short_address.rs @@ -0,0 +1,180 @@ +use crate::p384::*; + +/// 128-bit short address prefix. +/// +/// Short addresses are primarily for cases where humans need to type addresses or where +/// they need to be mapped onto an IPv6 address. The fully qualified 384-bit address +/// should be preferred if address transfer is automated or via cut/paste. +#[repr(transparent)] +#[derive(Clone, Copy, PartialEq, Eq)] +pub struct ShortAddress(pub(crate) [u64; 2]); // treated as [u8; 16] + +impl ShortAddress { + pub const SIZE: usize = 16; + + #[inline(always)] + pub fn as_bytes(&self) -> &[u8; Self::SIZE] { + debug_assert_eq!(size_of::<[u8; Self::SIZE]>(), size_of::()); + unsafe { &*(&self.0 as *const [u64; 2]).cast() } + } + + #[inline(always)] + fn as_mut_bytes(&mut self) -> &mut [u8; Self::SIZE] { + debug_assert_eq!(size_of::<[u8; Self::SIZE]>(), size_of::()); + 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; 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; 16] { + #[inline(always)] + fn from(value: ShortAddress) -> Self { + unsafe { transmute(value) } + } +} + +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 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 Debug for ShortAddress { + #[inline] + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(self.to_string().as_str()) + } +} + +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 AsRef<[u8]> for ShortAddress { + #[inline(always)] + fn as_ref(&self) -> &[u8] { + self.as_bytes() + } +} + +impl Hash for ShortAddress { + #[inline(always)] + fn hash(&self, state: &mut H) { + state.write_usize(self.0[0] as usize) + } +} + +impl PartialOrd for ShortAddress { + #[inline(always)] + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +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 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 From for std::net::Ipv6Addr { + fn from(value: ShortAddress) -> Self { + std::net::Ipv6Addr::from(*value.as_bytes()) + } +} +impl From<&ShortAddress> for std::net::Ipv6Addr { + fn from(value: &ShortAddress) -> Self { + std::net::Ipv6Addr::from(*value.as_bytes()) + } +}