From 83778c3ebc0e578d75c170bc893af8a999df5e34 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Mon, 26 Feb 2024 12:08:45 -0500 Subject: [PATCH 01/10] broke apart files --- .gitignore | 1 + src/p384.rs | 865 ------------------------------------ src/p384/address.rs | 218 +++++++++ src/p384/identity.rs | 223 ++++++++++ src/p384/identity_secret.rs | 187 ++++++++ src/p384/mod.rs | 87 ++++ src/p384/short_address.rs | 180 ++++++++ 7 files changed, 896 insertions(+), 865 deletions(-) delete mode 100644 src/p384.rs create mode 100644 src/p384/address.rs create mode 100644 src/p384/identity.rs create mode 100644 src/p384/identity_secret.rs create mode 100644 src/p384/mod.rs create mode 100644 src/p384/short_address.rs 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()) + } +} From b123891bd80043d1313e48979c8381fc37b733fa Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Mon, 26 Feb 2024 12:28:44 -0500 Subject: [PATCH 02/10] added identifier --- Cargo.toml | 2 +- src/p384/address.rs | 13 --- src/p384/identifier.rs | 170 ++++++++++++++++++++++++++++++++++++ src/p384/identity.rs | 7 ++ src/p384/identity_secret.rs | 7 ++ src/p384/mod.rs | 5 +- src/p384/short_address.rs | 7 ++ 7 files changed, 195 insertions(+), 16 deletions(-) create mode 100644 src/p384/identifier.rs diff --git a/Cargo.toml b/Cargo.toml index 824346c..e794939 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ authors = ["ZeroTier, Inc. "] edition = "2021" license = "MPL-2.0" name = "zerotier-identity" -version = "0.1.1" +version = "0.1.2" [dependencies] serde = { version = "1.0.183", features = ["derive"], default-features = false } diff --git a/src/p384/address.rs b/src/p384/address.rs index fc7a019..82635b7 100644 --- a/src/p384/address.rs +++ b/src/p384/address.rs @@ -5,20 +5,7 @@ * (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. diff --git a/src/p384/identifier.rs b/src/p384/identifier.rs new file mode 100644 index 0000000..5932eb3 --- /dev/null +++ b/src/p384/identifier.rs @@ -0,0 +1,170 @@ +/* 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 crate::p384::*; + +#[derive(Debug, Clone, Hash)] +pub enum PeerIdentifier { + Identity(Identity), + Address(Address), + Short(ShortAddress), +} +#[derive(Debug, Clone, Hash)] +pub enum PeerIdentifierRef<'a> { + Identity(&'a Identity), + Address(&'a Address), + Short(ShortAddress), +} + +impl PeerIdentifier { + pub fn matches(&self, identity: &Identity) -> bool { + match self { + PeerIdentifier::Identity(id) => id.eq(identity), + PeerIdentifier::Address(id) => identity.address.eq(id), + PeerIdentifier::Short(id) => identity.address.prefix().eq(id), + } + } + + pub fn prefix(&self) -> &ShortAddress { + match self { + PeerIdentifier::Identity(id) => id.address.prefix(), + PeerIdentifier::Address(id) => id.prefix(), + PeerIdentifier::Short(id) => id, + } + } + + pub fn address(&self) -> AnyAddress { + match self { + PeerIdentifier::Identity(id) => id.address.into(), + PeerIdentifier::Address(id) => id.into(), + PeerIdentifier::Short(id) => id.into(), + } + } + + /// Returns `true` if `other` contains the most complete version of the identity between + /// `self` and `other`. + /// Otherwise returns `false`. + /// This function will debug panic if `self` and `other` are not identifiers for the same peer. + /// The caller must check for equality before calling this function. + /// TODO: make this function take into account identity lifetime. + pub fn upgrade_check(&self, other: &Self) -> bool { + debug_assert_eq!(self, other); + use PeerIdentifier::*; + match (self, other) { + (Identity(_), _) => false, + (_, Identity(_)) => true, + (Short(_), Address(_)) => true, + _ => false, + } + } +} +/// Does not preserve transitivity. +impl std::cmp::PartialEq for PeerIdentifier { + fn eq(&self, other: &Self) -> bool { + use PeerIdentifier::*; + match (self, other) { + (Identity(identity), id) | (id, Identity(identity)) => id.matches(identity), + (Address(addr0), Address(addr1)) => addr0.eq(addr1), + (Address(addr0), Short(addr1)) => addr0.prefix().eq(addr1), + (Short(addr0), Address(addr1)) => addr0.eq(addr1.prefix()), + (Short(addr0), Short(addr1)) => addr0.eq(addr1), + } + } +} +impl From
for PeerIdentifier { + fn from(value: Address) -> Self { + Self::Address(value) + } +} +impl From for PeerIdentifier { + fn from(value: ShortAddress) -> Self { + Self::Short(value) + } +} +impl From<&Address> for PeerIdentifier { + fn from(value: &Address) -> Self { + Self::Address(*value) + } +} +impl From<&ShortAddress> for PeerIdentifier { + fn from(value: &ShortAddress) -> Self { + Self::Short(*value) + } +} + +impl From for PeerIdentifier { + fn from(value: Identity) -> Self { + Self::Identity(value) + } +} + +#[derive(serde::Deserialize, serde::Serialize, Debug, Clone, Copy, Hash)] +pub enum AnyAddress { + Address(Address), + Short(ShortAddress), +} +impl AnyAddress { + pub fn matches(&self, identity: &Identity) -> bool { + match self { + AnyAddress::Address(id) => identity.address.eq(id), + AnyAddress::Short(id) => identity.address.prefix().eq(id), + } + } + + pub fn prefix(&self) -> &ShortAddress { + match self { + AnyAddress::Address(id) => id.prefix(), + AnyAddress::Short(id) => id, + } + } + /// Returns `true` if `other` contains the most complete version of the identity between + /// `self` and `other`. + /// Otherwise returns `false`. + /// This function will debug panic if `self` and `other` are not identifiers for the same peer. + /// The caller must check for equality before calling this function. + /// TODO: make this function take into account identity lifetime. + pub fn upgrade_check(&self, other: &Self) -> bool { + debug_assert_eq!(self, other); + use AnyAddress::*; + match (self, other) { + (Short(_), Address(_)) => true, + _ => false, + } + } +} +/// Does not preserve transitivity. +impl std::cmp::PartialEq for AnyAddress { + fn eq(&self, other: &Self) -> bool { + use AnyAddress::*; + match (self, other) { + (Address(addr0), Address(addr1)) => addr0.eq(addr1), + (Address(addr0), Short(addr1)) => addr0.prefix().eq(addr1), + (Short(addr0), Address(addr1)) => addr0.eq(addr1.prefix()), + (Short(addr0), Short(addr1)) => addr0.eq(addr1), + } + } +} +impl From
for AnyAddress { + fn from(value: Address) -> Self { + Self::Address(value) + } +} +impl From for AnyAddress { + fn from(value: ShortAddress) -> Self { + Self::Short(value) + } +} +impl From<&Address> for AnyAddress { + fn from(value: &Address) -> Self { + Self::Address(*value) + } +} +impl From<&ShortAddress> for AnyAddress { + fn from(value: &ShortAddress) -> Self { + Self::Short(*value) + } +} diff --git a/src/p384/identity.rs b/src/p384/identity.rs index d138d54..204c6c2 100644 --- a/src/p384/identity.rs +++ b/src/p384/identity.rs @@ -1,3 +1,10 @@ +/* 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 crate::p384::*; const TIMESTAMP_START: usize = P384_PUBLIC_KEY_SIZE; diff --git a/src/p384/identity_secret.rs b/src/p384/identity_secret.rs index 876acdf..870d542 100644 --- a/src/p384/identity_secret.rs +++ b/src/p384/identity_secret.rs @@ -1,3 +1,10 @@ +/* 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 crate::p384::*; /// Secret NIST P-384 identity (also contains public). diff --git a/src/p384/mod.rs b/src/p384/mod.rs index 542638a..971cd7c 100644 --- a/src/p384/mod.rs +++ b/src/p384/mod.rs @@ -5,7 +5,6 @@ * (c) ZeroTier, Inc. * https://www.zerotier.com/ */ - use std::fmt::Debug; use std::hash::Hash; use std::io::Write; @@ -18,15 +17,17 @@ 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::{base24, base62}; use crate::{ADDRESS_ERR, IDENTITY_ERR}; mod address; +mod identifier; mod identity; mod identity_secret; mod short_address; pub use address::*; +pub use identifier::*; pub use identity::*; pub use identity_secret::*; pub use short_address::*; diff --git a/src/p384/short_address.rs b/src/p384/short_address.rs index 387f7bc..c1a00fb 100644 --- a/src/p384/short_address.rs +++ b/src/p384/short_address.rs @@ -1,3 +1,10 @@ +/* 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 crate::p384::*; /// 128-bit short address prefix. From 7009b517ef39fdf40577e67019b40e08a03b6cdf Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Mon, 26 Feb 2024 13:04:08 -0500 Subject: [PATCH 03/10] added conversions --- src/p384/identifier.rs | 207 ++++++++++++++++++++++++++++++----------- 1 file changed, 154 insertions(+), 53 deletions(-) diff --git a/src/p384/identifier.rs b/src/p384/identifier.rs index 5932eb3..f0f4e7f 100644 --- a/src/p384/identifier.rs +++ b/src/p384/identifier.rs @@ -13,35 +13,44 @@ pub enum PeerIdentifier { Address(Address), Short(ShortAddress), } -#[derive(Debug, Clone, Hash)] +#[derive(Debug, Clone, Copy, Hash)] pub enum PeerIdentifierRef<'a> { Identity(&'a Identity), Address(&'a Address), Short(ShortAddress), } -impl PeerIdentifier { - pub fn matches(&self, identity: &Identity) -> bool { +#[derive(Debug, Clone, Copy, Hash)] +pub enum AnyAddress { + Address(Address), + Short(ShortAddress), +} + +impl<'a> PeerIdentifierRef<'a> { + pub fn matches(self, identity: &Identity) -> bool { + use PeerIdentifierRef::*; match self { - PeerIdentifier::Identity(id) => id.eq(identity), - PeerIdentifier::Address(id) => identity.address.eq(id), - PeerIdentifier::Short(id) => identity.address.prefix().eq(id), + Identity(id) => (*id).eq(identity), + Address(id) => identity.address.eq(id), + Short(id) => identity.address.prefix().eq(&id), } } pub fn prefix(&self) -> &ShortAddress { + use PeerIdentifierRef::*; match self { - PeerIdentifier::Identity(id) => id.address.prefix(), - PeerIdentifier::Address(id) => id.prefix(), - PeerIdentifier::Short(id) => id, + Identity(id) => id.address.prefix(), + Address(id) => id.prefix(), + Short(id) => id, } } - pub fn address(&self) -> AnyAddress { + pub fn address(self) -> AnyAddress { + use PeerIdentifierRef::*; match self { - PeerIdentifier::Identity(id) => id.address.into(), - PeerIdentifier::Address(id) => id.into(), - PeerIdentifier::Short(id) => id.into(), + Identity(id) => id.address.into(), + Address(id) => (*id).into(), + Short(id) => id.into(), } } @@ -51,9 +60,9 @@ impl PeerIdentifier { /// This function will debug panic if `self` and `other` are not identifiers for the same peer. /// The caller must check for equality before calling this function. /// TODO: make this function take into account identity lifetime. - pub fn upgrade_check(&self, other: &Self) -> bool { + pub fn upgrade_check(self, other: Self) -> bool { debug_assert_eq!(self, other); - use PeerIdentifier::*; + use PeerIdentifierRef::*; match (self, other) { (Identity(_), _) => false, (_, Identity(_)) => true, @@ -62,51 +71,39 @@ impl PeerIdentifier { } } } -/// Does not preserve transitivity. -impl std::cmp::PartialEq for PeerIdentifier { - fn eq(&self, other: &Self) -> bool { + +impl PeerIdentifier { + pub fn matches(&self, identity: &Identity) -> bool { + let r: PeerIdentifierRef = self.into(); + r.matches(identity) + } + + pub fn prefix(&self) -> &ShortAddress { use PeerIdentifier::*; - match (self, other) { - (Identity(identity), id) | (id, Identity(identity)) => id.matches(identity), - (Address(addr0), Address(addr1)) => addr0.eq(addr1), - (Address(addr0), Short(addr1)) => addr0.prefix().eq(addr1), - (Short(addr0), Address(addr1)) => addr0.eq(addr1.prefix()), - (Short(addr0), Short(addr1)) => addr0.eq(addr1), + match self { + Identity(id) => id.address.prefix(), + Address(id) => id.prefix(), + Short(id) => id, } } -} -impl From
for PeerIdentifier { - fn from(value: Address) -> Self { - Self::Address(value) + + pub fn address(&self) -> AnyAddress { + let r: PeerIdentifierRef = self.into(); + r.address() } -} -impl From for PeerIdentifier { - fn from(value: ShortAddress) -> Self { - Self::Short(value) - } -} -impl From<&Address> for PeerIdentifier { - fn from(value: &Address) -> Self { - Self::Address(*value) - } -} -impl From<&ShortAddress> for PeerIdentifier { - fn from(value: &ShortAddress) -> Self { - Self::Short(*value) + + /// Returns `true` if `other` contains the most complete version of the identity between + /// `self` and `other`. + /// Otherwise returns `false`. + /// This function will debug panic if `self` and `other` are not identifiers for the same peer. + /// The caller must check for equality before calling this function. + /// TODO: make this function take into account identity lifetime. + pub fn upgrade_check(&self, other: &Self) -> bool { + let r: PeerIdentifierRef = self.into(); + r.upgrade_check(other.into()) } } -impl From for PeerIdentifier { - fn from(value: Identity) -> Self { - Self::Identity(value) - } -} - -#[derive(serde::Deserialize, serde::Serialize, Debug, Clone, Copy, Hash)] -pub enum AnyAddress { - Address(Address), - Short(ShortAddress), -} impl AnyAddress { pub fn matches(&self, identity: &Identity) -> bool { match self { @@ -136,6 +133,27 @@ impl AnyAddress { } } } + +/// Does not preserve transitivity. +impl<'a> std::cmp::PartialEq for PeerIdentifierRef<'a> { + fn eq(&self, other: &Self) -> bool { + use PeerIdentifierRef::*; + match (self, other) { + (Identity(identity), id) | (id, Identity(identity)) => id.matches(identity), + (Address(addr0), Address(addr1)) => addr0.eq(addr1), + (Address(addr0), Short(addr1)) => addr0.prefix().eq(addr1), + (Short(addr0), Address(addr1)) => addr0.eq(addr1.prefix()), + (Short(addr0), Short(addr1)) => addr0.eq(addr1), + } + } +} +/// Does not preserve transitivity. +impl std::cmp::PartialEq for PeerIdentifier { + fn eq(&self, other: &Self) -> bool { + let r: PeerIdentifierRef = self.into(); + r.eq(&other.into()) + } +} /// Does not preserve transitivity. impl std::cmp::PartialEq for AnyAddress { fn eq(&self, other: &Self) -> bool { @@ -148,6 +166,47 @@ impl std::cmp::PartialEq for AnyAddress { } } } + +impl<'a> From<&'a PeerIdentifier> for PeerIdentifierRef<'a> { + #[inline] + fn from(value: &'a PeerIdentifier) -> Self { + match value { + PeerIdentifier::Identity(v) => PeerIdentifierRef::Identity(v), + PeerIdentifier::Address(v) => PeerIdentifierRef::Address(v), + PeerIdentifier::Short(v) => PeerIdentifierRef::Short(*v), + } + } +} +impl<'a> From> for PeerIdentifier { + #[inline] + fn from(value: PeerIdentifierRef<'a>) -> Self { + match value { + PeerIdentifierRef::Identity(v) => PeerIdentifier::Identity(v.clone()), + PeerIdentifierRef::Address(v) => PeerIdentifier::Address(v.clone()), + PeerIdentifierRef::Short(v) => PeerIdentifier::Short(v), + } + } +} + +impl From for PeerIdentifier { + #[inline] + fn from(value: AnyAddress) -> Self { + match value { + AnyAddress::Address(v) => PeerIdentifier::Address(v), + AnyAddress::Short(v) => PeerIdentifier::Short(v), + } + } +} +impl<'a> From<&'a AnyAddress> for PeerIdentifierRef<'a> { + #[inline] + fn from(value: &'a AnyAddress) -> Self { + match value { + AnyAddress::Address(v) => PeerIdentifierRef::Address(v), + AnyAddress::Short(v) => PeerIdentifierRef::Short(*v), + } + } +} + impl From
for AnyAddress { fn from(value: Address) -> Self { Self::Address(value) @@ -168,3 +227,45 @@ impl From<&ShortAddress> for AnyAddress { Self::Short(*value) } } + +impl From
for PeerIdentifier { + fn from(value: Address) -> Self { + Self::Address(value) + } +} +impl From for PeerIdentifier { + fn from(value: ShortAddress) -> Self { + Self::Short(value) + } +} +impl From<&Address> for PeerIdentifier { + fn from(value: &Address) -> Self { + Self::Address(*value) + } +} +impl From<&ShortAddress> for PeerIdentifier { + fn from(value: &ShortAddress) -> Self { + Self::Short(*value) + } +} +impl From for PeerIdentifier { + fn from(value: Identity) -> Self { + Self::Identity(value) + } +} + +impl<'a> From<&'a Address> for PeerIdentifierRef<'a> { + fn from(value: &'a Address) -> Self { + Self::Address(value) + } +} +impl<'a> From<&ShortAddress> for PeerIdentifierRef<'a> { + fn from(value: &ShortAddress) -> Self { + Self::Short(*value) + } +} +impl<'a> From<&'a Identity> for PeerIdentifierRef<'a> { + fn from(value: &'a Identity) -> Self { + Self::Identity(value) + } +} From 30a9bc89f40b20e09065566f73ae4a1c20578c8e Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Mon, 26 Feb 2024 13:13:24 -0500 Subject: [PATCH 04/10] simplified conversions --- src/p384/identifier.rs | 71 +++++++++++++++++------------------------- 1 file changed, 28 insertions(+), 43 deletions(-) diff --git a/src/p384/identifier.rs b/src/p384/identifier.rs index f0f4e7f..a5305d6 100644 --- a/src/p384/identifier.rs +++ b/src/p384/identifier.rs @@ -197,6 +197,15 @@ impl From for PeerIdentifier { } } } +impl From<&AnyAddress> for PeerIdentifier { + #[inline] + fn from(value: &AnyAddress) -> Self { + match value { + AnyAddress::Address(v) => PeerIdentifier::Address(*v), + AnyAddress::Short(v) => PeerIdentifier::Short(*v), + } + } +} impl<'a> From<&'a AnyAddress> for PeerIdentifierRef<'a> { #[inline] fn from(value: &'a AnyAddress) -> Self { @@ -207,52 +216,28 @@ impl<'a> From<&'a AnyAddress> for PeerIdentifierRef<'a> { } } -impl From
for AnyAddress { - fn from(value: Address) -> Self { - Self::Address(value) - } -} -impl From for AnyAddress { - fn from(value: ShortAddress) -> Self { - Self::Short(value) - } -} -impl From<&Address> for AnyAddress { - fn from(value: &Address) -> Self { - Self::Address(*value) - } -} -impl From<&ShortAddress> for AnyAddress { - fn from(value: &ShortAddress) -> Self { - Self::Short(*value) +macro_rules! impl_from { + ($ft:ident, $tt:ident::$ev:ident) => { + impl From<$ft> for $tt { + #[inline] + fn from(v: $ft) -> Self { + Self::$ev(v) + } + } + impl From<&$ft> for $tt { + #[inline] + fn from(v: &$ft) -> Self { + Self::$ev(v.clone()) + } + } } } -impl From
for PeerIdentifier { - fn from(value: Address) -> Self { - Self::Address(value) - } -} -impl From for PeerIdentifier { - fn from(value: ShortAddress) -> Self { - Self::Short(value) - } -} -impl From<&Address> for PeerIdentifier { - fn from(value: &Address) -> Self { - Self::Address(*value) - } -} -impl From<&ShortAddress> for PeerIdentifier { - fn from(value: &ShortAddress) -> Self { - Self::Short(*value) - } -} -impl From for PeerIdentifier { - fn from(value: Identity) -> Self { - Self::Identity(value) - } -} +impl_from!(Address, AnyAddress::Address); +impl_from!(ShortAddress, AnyAddress::Short); +impl_from!(Address, PeerIdentifier::Address); +impl_from!(ShortAddress, PeerIdentifier::Short); +impl_from!(Identity, PeerIdentifier::Identity); impl<'a> From<&'a Address> for PeerIdentifierRef<'a> { fn from(value: &'a Address) -> Self { From 515447eb3f33ee833f0dd33de8d74299fa4f9147 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Mon, 26 Feb 2024 13:15:59 -0500 Subject: [PATCH 05/10] improved macro --- src/p384/identifier.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/p384/identifier.rs b/src/p384/identifier.rs index a5305d6..b1d1c27 100644 --- a/src/p384/identifier.rs +++ b/src/p384/identifier.rs @@ -217,7 +217,7 @@ impl<'a> From<&'a AnyAddress> for PeerIdentifierRef<'a> { } macro_rules! impl_from { - ($ft:ident, $tt:ident::$ev:ident) => { + ($ft:ident for $tt:ident::$ev:ident) => { impl From<$ft> for $tt { #[inline] fn from(v: $ft) -> Self { @@ -233,11 +233,11 @@ macro_rules! impl_from { } } -impl_from!(Address, AnyAddress::Address); -impl_from!(ShortAddress, AnyAddress::Short); -impl_from!(Address, PeerIdentifier::Address); -impl_from!(ShortAddress, PeerIdentifier::Short); -impl_from!(Identity, PeerIdentifier::Identity); +impl_from!(Address for AnyAddress::Address); +impl_from!(ShortAddress for AnyAddress::Short); +impl_from!(Address for PeerIdentifier::Address); +impl_from!(ShortAddress for PeerIdentifier::Short); +impl_from!(Identity for PeerIdentifier::Identity); impl<'a> From<&'a Address> for PeerIdentifierRef<'a> { fn from(value: &'a Address) -> Self { From 69435b9569df7743b8f9806247e635f4c33f0be4 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Mon, 26 Feb 2024 13:38:28 -0500 Subject: [PATCH 06/10] removed errors --- rustfmt.toml | 5 +++-- src/p384/identifier.rs | 34 ++++++++++++++++++---------------- src/p384/mod.rs | 4 ++-- 3 files changed, 23 insertions(+), 20 deletions(-) diff --git a/rustfmt.toml b/rustfmt.toml index 2f5a5fe..1416336 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -10,7 +10,8 @@ use_small_heuristics = "Default" #fn_single_line = true #hex_literal_case = "Lower" #merge_imports = false -group_imports = "StdExternalCrate" single_line_if_else_max_width = 0 use_try_shorthand = true -imports_granularity = "Module" +# The following cause warnings on non-nightly channels +# group_imports = "StdExternalCrate" +# imports_granularity = "Module" diff --git a/src/p384/identifier.rs b/src/p384/identifier.rs index b1d1c27..3f827cc 100644 --- a/src/p384/identifier.rs +++ b/src/p384/identifier.rs @@ -17,7 +17,7 @@ pub enum PeerIdentifier { pub enum PeerIdentifierRef<'a> { Identity(&'a Identity), Address(&'a Address), - Short(ShortAddress), + Short(&'a ShortAddress), } #[derive(Debug, Clone, Copy, Hash)] @@ -32,7 +32,7 @@ impl<'a> PeerIdentifierRef<'a> { match self { Identity(id) => (*id).eq(identity), Address(id) => identity.address.eq(id), - Short(id) => identity.address.prefix().eq(&id), + Short(id) => identity.address.prefix().eq(id), } } @@ -135,27 +135,27 @@ impl AnyAddress { } /// Does not preserve transitivity. -impl<'a> std::cmp::PartialEq for PeerIdentifierRef<'a> { +impl<'a> PartialEq for PeerIdentifierRef<'a> { fn eq(&self, other: &Self) -> bool { use PeerIdentifierRef::*; match (self, other) { (Identity(identity), id) | (id, Identity(identity)) => id.matches(identity), (Address(addr0), Address(addr1)) => addr0.eq(addr1), (Address(addr0), Short(addr1)) => addr0.prefix().eq(addr1), - (Short(addr0), Address(addr1)) => addr0.eq(addr1.prefix()), + (Short(addr0), Address(addr1)) => (*addr0).eq(addr1.prefix()), (Short(addr0), Short(addr1)) => addr0.eq(addr1), } } } /// Does not preserve transitivity. -impl std::cmp::PartialEq for PeerIdentifier { +impl PartialEq for PeerIdentifier { fn eq(&self, other: &Self) -> bool { let r: PeerIdentifierRef = self.into(); r.eq(&other.into()) } } /// Does not preserve transitivity. -impl std::cmp::PartialEq for AnyAddress { +impl PartialEq for AnyAddress { fn eq(&self, other: &Self) -> bool { use AnyAddress::*; match (self, other) { @@ -167,13 +167,15 @@ impl std::cmp::PartialEq for AnyAddress { } } +/* Start of Conversions */ + impl<'a> From<&'a PeerIdentifier> for PeerIdentifierRef<'a> { #[inline] fn from(value: &'a PeerIdentifier) -> Self { match value { PeerIdentifier::Identity(v) => PeerIdentifierRef::Identity(v), PeerIdentifier::Address(v) => PeerIdentifierRef::Address(v), - PeerIdentifier::Short(v) => PeerIdentifierRef::Short(*v), + PeerIdentifier::Short(v) => PeerIdentifierRef::Short(v), } } } @@ -182,8 +184,8 @@ impl<'a> From> for PeerIdentifier { fn from(value: PeerIdentifierRef<'a>) -> Self { match value { PeerIdentifierRef::Identity(v) => PeerIdentifier::Identity(v.clone()), - PeerIdentifierRef::Address(v) => PeerIdentifier::Address(v.clone()), - PeerIdentifierRef::Short(v) => PeerIdentifier::Short(v), + PeerIdentifierRef::Address(v) => PeerIdentifier::Address(*v), + PeerIdentifierRef::Short(v) => PeerIdentifier::Short(*v), } } } @@ -211,7 +213,7 @@ impl<'a> From<&'a AnyAddress> for PeerIdentifierRef<'a> { fn from(value: &'a AnyAddress) -> Self { match value { AnyAddress::Address(v) => PeerIdentifierRef::Address(v), - AnyAddress::Short(v) => PeerIdentifierRef::Short(*v), + AnyAddress::Short(v) => PeerIdentifierRef::Short(v), } } } @@ -230,23 +232,23 @@ macro_rules! impl_from { Self::$ev(v.clone()) } } - } + }; } -impl_from!(Address for AnyAddress::Address); -impl_from!(ShortAddress for AnyAddress::Short); impl_from!(Address for PeerIdentifier::Address); impl_from!(ShortAddress for PeerIdentifier::Short); impl_from!(Identity for PeerIdentifier::Identity); +impl_from!(Address for AnyAddress::Address); +impl_from!(ShortAddress for AnyAddress::Short); impl<'a> From<&'a Address> for PeerIdentifierRef<'a> { fn from(value: &'a Address) -> Self { Self::Address(value) } } -impl<'a> From<&ShortAddress> for PeerIdentifierRef<'a> { - fn from(value: &ShortAddress) -> Self { - Self::Short(*value) +impl<'a> From<&'a ShortAddress> for PeerIdentifierRef<'a> { + fn from(value: &'a ShortAddress) -> Self { + Self::Short(value) } } impl<'a> From<&'a Identity> for PeerIdentifierRef<'a> { diff --git a/src/p384/mod.rs b/src/p384/mod.rs index 971cd7c..39b3507 100644 --- a/src/p384/mod.rs +++ b/src/p384/mod.rs @@ -32,8 +32,8 @@ 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"; +const DOMAIN_MASTER_SIG: &[u8] = b"ZTID_MASTERSIG_P384"; +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); From b1930f7665dc2e37df2ed805f51bcb8c8f5c62d0 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Wed, 28 Feb 2024 13:12:46 -0500 Subject: [PATCH 07/10] implemented serialization --- src/p384/address.rs | 23 +++-- src/p384/identifier.rs | 176 +++++++++++++++++++++++++++++++++++++- src/p384/identity.rs | 36 +++++--- src/p384/mod.rs | 13 ++- src/p384/short_address.rs | 12 ++- 5 files changed, 237 insertions(+), 23 deletions(-) diff --git a/src/p384/address.rs b/src/p384/address.rs index 82635b7..3011e35 100644 --- a/src/p384/address.rs +++ b/src/p384/address.rs @@ -28,6 +28,7 @@ impl Address { /// force, but untargeted birthday type collisions are possible with sufficient storage.) pub const REQUIRED_PREFIX: u8 = 0xfc; + pub const SIZE: usize = 48; /// Length of a full address in string format. pub const STRING_SIZE: usize = 76; @@ -57,6 +58,18 @@ impl Address { pub(crate) fn is_valid(&self) -> bool { self.as_bytes()[0] == Self::REQUIRED_PREFIX } + + pub fn write_to_string(&self, s: &mut String, prefix: bool) { + if prefix { + s.push_str(PREFIX_ADDRESS); + } + first_128_to_string(&self.as_bytes()[..16], s); + s.push('.'); + base62::encode_8to11(u64::from_be(self.0[2]), s); + base62::encode_8to11(u64::from_be(self.0[3]), s); + base62::encode_8to11(u64::from_be(self.0[4]), s); + base62::encode_8to11(u64::from_be(self.0[5]), s); + } } impl TryFrom<[u8; 48]> for Address { @@ -101,12 +114,7 @@ impl ToFromBytes for Address { 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); + self.write_to_string(&mut s, true); s } } @@ -123,8 +131,9 @@ impl FromStr for Address { fn from_str(s: &str) -> Result { let s = s.trim(); + let s = s.strip_prefix(PREFIX_ADDRESS).unwrap_or(s); let sb = s.as_bytes(); - if s.len() == sb.len() && sb.len() == Self::STRING_SIZE && sb[31] == b'.' { + if sb.len() == Self::STRING_SIZE && sb[31] == b'.' { let prefix = ShortAddress::from_str(&s[..31])?; Ok(Address([ prefix.0[0], diff --git a/src/p384/identifier.rs b/src/p384/identifier.rs index 3f827cc..8162066 100644 --- a/src/p384/identifier.rs +++ b/src/p384/identifier.rs @@ -59,17 +59,25 @@ impl<'a> PeerIdentifierRef<'a> { /// Otherwise returns `false`. /// This function will debug panic if `self` and `other` are not identifiers for the same peer. /// The caller must check for equality before calling this function. - /// TODO: make this function take into account identity lifetime. - pub fn upgrade_check(self, other: Self) -> bool { + pub fn upgrade_check(&self, other: &Self) -> bool { debug_assert_eq!(self, other); use PeerIdentifierRef::*; match (self, other) { + (Identity(id1), Identity(id2)) => id1.timestamp < id2.timestamp, (Identity(_), _) => false, (_, Identity(_)) => true, (Short(_), Address(_)) => true, _ => false, } } + + pub fn write_to_string(&self, s: &mut String, prefix: bool) { + match self { + Self::Identity(a) => a.write_to_string(s, prefix), + Self::Address(a) => a.write_to_string(s, prefix), + Self::Short(a) => a.write_to_string(s, prefix), + } + } } impl PeerIdentifier { @@ -100,7 +108,15 @@ impl PeerIdentifier { /// TODO: make this function take into account identity lifetime. pub fn upgrade_check(&self, other: &Self) -> bool { let r: PeerIdentifierRef = self.into(); - r.upgrade_check(other.into()) + r.upgrade_check(&other.into()) + } + + pub fn write_to_string(&self, s: &mut String, prefix: bool) { + match self { + Self::Identity(a) => a.write_to_string(s, prefix), + Self::Address(a) => a.write_to_string(s, prefix), + Self::Short(a) => a.write_to_string(s, prefix), + } } } @@ -132,6 +148,13 @@ impl AnyAddress { _ => false, } } + + pub fn write_to_string(&self, s: &mut String, prefix: bool) { + match self { + Self::Address(a) => a.write_to_string(s, prefix), + Self::Short(a) => a.write_to_string(s, prefix), + } + } } /// Does not preserve transitivity. @@ -167,6 +190,153 @@ impl PartialEq for AnyAddress { } } +impl<'a> ToString for PeerIdentifierRef<'a> { + fn to_string(&self) -> String { + match self { + Self::Identity(id) => id.to_string(), + Self::Address(addr) => addr.to_string(), + Self::Short(addr) => addr.to_string(), + } + } +} +impl ToString for PeerIdentifier { + fn to_string(&self) -> String { + match self { + Self::Identity(id) => id.to_string(), + Self::Address(addr) => addr.to_string(), + Self::Short(addr) => addr.to_string(), + } + } +} +impl ToString for AnyAddress { + fn to_string(&self) -> String { + match self { + Self::Address(addr) => addr.to_string(), + Self::Short(addr) => addr.to_string(), + } + } +} +impl FromStr for PeerIdentifier { + type Err = InvalidParameterError; + + fn from_str(s: &str) -> Result { + let l = s.len(); + if l >= Identity::STRING_SIZE { + Identity::from_str(s).map(Self::Identity) + } else if l >= Address::STRING_SIZE { + Address::from_str(s).map(Self::Address) + } else { + ShortAddress::from_str(s).map(Self::Short) + } + } +} +impl FromStr for AnyAddress { + type Err = InvalidParameterError; + + fn from_str(s: &str) -> Result { + let l = s.len(); + if l >= Address::STRING_SIZE { + Address::from_str(s).map(Self::Address) + } else { + ShortAddress::from_str(s).map(Self::Short) + } + } +} + +impl<'a> serde::Serialize for PeerIdentifierRef<'a> { + fn serialize(&self, s: S) -> Result { + match self { + Self::Identity(id) => id.serialize(s), + Self::Address(addr) => addr.serialize(s), + Self::Short(addr) => addr.serialize(s), + } + } +} +impl serde::Serialize for PeerIdentifier { + fn serialize(&self, s: S) -> Result { + match self { + Self::Identity(id) => id.serialize(s), + Self::Address(addr) => addr.serialize(s), + Self::Short(addr) => addr.serialize(s), + } + } +} +impl serde::Serialize for AnyAddress { + fn serialize(&self, s: S) -> Result { + match self { + Self::Address(addr) => addr.serialize(s), + Self::Short(addr) => addr.serialize(s), + } + } +} + +impl<'de> Deserialize<'de> for PeerIdentifier { + #[inline] + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + if deserializer.is_human_readable() { + Self::from_str(<&str>::deserialize(deserializer)?).map_err(|_| serde::de::Error::custom(ADDRESS_ERR.0)) + } else { + struct Visitor; + + impl<'de> serde::de::Visitor<'de> for Visitor { + type Value = PeerIdentifier; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("a zerotier identifier") + } + + fn visit_bytes(self, v: &[u8]) -> Result + { + match v.len() { + Identity::SIZE => Identity::from_bytes(v).map(Self::Value::Identity), + Address::SIZE => Address::from_bytes(v).map(Self::Value::Address), + ShortAddress::SIZE => ShortAddress::from_bytes(v).map(Self::Value::Short), + _ => return Err(serde::de::Error::custom(ADDRESS_ERR.0)) + } + .map_err(|_| serde::de::Error::custom(ADDRESS_ERR.0)) + } + } + deserializer.deserialize_bytes(Visitor) + } + } +} +impl<'de> Deserialize<'de> for AnyAddress { + #[inline] + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + if deserializer.is_human_readable() { + Self::from_str(<&str>::deserialize(deserializer)?).map_err(|_| serde::de::Error::custom(ADDRESS_ERR.0)) + } else { + struct Visitor; + + impl<'de> serde::de::Visitor<'de> for Visitor { + type Value = AnyAddress; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("a zerotier identifier") + } + + fn visit_bytes(self, v: &[u8]) -> Result + { + match v.len() { + Address::SIZE => Address::from_bytes(v).map(Self::Value::Address), + ShortAddress::SIZE => ShortAddress::from_bytes(v).map(Self::Value::Short), + _ => return Err(serde::de::Error::custom(ADDRESS_ERR.0)) + } + .map_err(|_| serde::de::Error::custom(ADDRESS_ERR.0)) + } + } + deserializer.deserialize_bytes(Visitor) + } + } +} + + /* Start of Conversions */ impl<'a> From<&'a PeerIdentifier> for PeerIdentifierRef<'a> { diff --git a/src/p384/identity.rs b/src/p384/identity.rs index 204c6c2..0c7d800 100644 --- a/src/p384/identity.rs +++ b/src/p384/identity.rs @@ -7,7 +7,8 @@ */ use crate::p384::*; -const TIMESTAMP_START: usize = P384_PUBLIC_KEY_SIZE; +const MASTER_KEY_START: usize = 1; +const TIMESTAMP_START: usize = MASTER_KEY_START + 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; @@ -27,6 +28,11 @@ pub struct Identity { } impl Identity { + pub const SIZE: usize = P384_IDENTITY_SIZE; + + pub const STRING_SIZE: usize = 547; + pub const STRING_SIZE_NO_PREFIX: usize = 542; + pub(crate) fn locally_validate(&self) -> bool { let to_sign: &[&[u8]] = &[ self.master_signing_key.as_bytes(), @@ -48,15 +54,22 @@ impl Identity { pub fn replaces(&self, other: &Identity) -> bool { self.address == other.address && self.timestamp > other.timestamp } + + pub fn write_to_string(&self, s: &mut String, prefix: bool) { + if prefix { + s.push_str(PREFIX_IDENTITY); + } + self.address.write_to_string(s, false); + s.push_str(":1:"); + s.push_str(base64::to_string(self.to_bytes_on_stack::<1024>().as_bytes()).as_str()); + } } 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 + let mut s = String::with_capacity(Self::STRING_SIZE); + self.write_to_string(&mut s, true); + s } } @@ -78,6 +91,8 @@ impl FromStr for Identity { type Err = InvalidParameterError; fn from_str(s: &str) -> Result { + let s = s.trim(); + let s = s.strip_prefix(PREFIX_IDENTITY).unwrap_or(s); 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()) { @@ -85,7 +100,7 @@ impl FromStr for Identity { } } } - return Err(IDENTITY_ERR); + Err(IDENTITY_ERR) } } @@ -94,7 +109,7 @@ impl ToFromBytes for Identity { 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[MASTER_KEY_START..TIMESTAMP_START]), P384PublicKey::from_bytes(&tmp[SUBKEY_ECDH_START..SUBKEY_ECDSA_START]), P384PublicKey::from_bytes(&tmp[SUBKEY_ECDSA_START..MASTER_SIG_START]), ) { @@ -111,13 +126,14 @@ impl ToFromBytes for Identity { return Ok(id); } } - return Err(std::io::Error::new(std::io::ErrorKind::Other, IDENTITY_ERR.0)); + 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(&[IDENTITY_VARIANT_P384])?; w.write_all(self.master_signing_key.as_bytes())?; w.write_all(&self.timestamp.to_be_bytes())?; w.write_all(self.ecdh.as_bytes())?; @@ -156,7 +172,7 @@ impl<'de> Deserialize<'de> for Identity { type Value = Identity; fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("a pair of ratchet states") + formatter.write_str("a zerotier identifier") } fn visit_bytes(self, v: &[u8]) -> Result diff --git a/src/p384/mod.rs b/src/p384/mod.rs index 39b3507..fe8ff28 100644 --- a/src/p384/mod.rs +++ b/src/p384/mod.rs @@ -32,8 +32,17 @@ pub use identity::*; pub use identity_secret::*; pub use short_address::*; -const DOMAIN_MASTER_SIG: &[u8] = b"ZTID_MASTERSIG_P384"; -const DOMAIN_SUBKEY_SIG: &[u8] = b"ZTID_SUBKEYSIG_P384"; +/// We won't have more than one identity type for a while, but putting a variant number will allow +/// us to treat an identity as a discriminated union when we add more. +/// For security purposes this must be a statically assigned unique number. +const IDENTITY_VARIANT_P384: u8 = 1; + +const DOMAIN_MASTER_SIG: &[u8] = b"ZTID1_MASTERSIG"; +const DOMAIN_SUBKEY_SIG: &[u8] = b"ZTID1_SUBKEYSIG"; + +const PREFIX_IDENTITY: &str = "zt:i:"; +const PREFIX_ADDRESS: &str = "zt:a:"; +const PREFIX_SHORT: &str = "zt:s:"; fn first_128_to_string(b: &[u8], s: &mut String) { base24::encode_4to7(&b[0..4], s); diff --git a/src/p384/short_address.rs b/src/p384/short_address.rs index c1a00fb..a891062 100644 --- a/src/p384/short_address.rs +++ b/src/p384/short_address.rs @@ -18,6 +18,7 @@ pub struct ShortAddress(pub(crate) [u64; 2]); // treated as [u8; 16] impl ShortAddress { pub const SIZE: usize = 16; + pub const STRING_SIZE: usize = 31; #[inline(always)] pub fn as_bytes(&self) -> &[u8; Self::SIZE] { @@ -35,6 +36,13 @@ impl ShortAddress { fn is_valid(&self) -> bool { self.as_bytes()[0] == Address::REQUIRED_PREFIX } + + pub fn write_to_string(&self, s: &mut String, prefix: bool) { + if prefix { + s.push_str(PREFIX_SHORT); + } + first_128_to_string(self.as_bytes(), s); + } } impl TryFrom<[u8; 16]> for ShortAddress { @@ -79,6 +87,7 @@ impl ToFromBytes for ShortAddress { impl ToString for ShortAddress { fn to_string(&self) -> String { let mut s = String::with_capacity(32); + s.push_str(PREFIX_SHORT); first_128_to_string(self.as_bytes(), &mut s); s } @@ -96,7 +105,8 @@ impl FromStr for ShortAddress { fn from_str(s: &str) -> Result { let s = s.trim(); - if s.len() == 31 { + let s = s.strip_prefix(PREFIX_SHORT).unwrap_or(s); + if s.len() == Self::STRING_SIZE { let mut tmp = [0u8; 16]; let mut w = &mut tmp[..]; for ss in s.split('.') { From 14d188bd4e634478dc19fe91388fca557821100f Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Wed, 28 Feb 2024 13:26:28 -0500 Subject: [PATCH 08/10] corrected constants --- src/p384/address.rs | 3 ++- src/p384/identifier.rs | 23 ++++++++++------------- src/p384/identity.rs | 8 ++++++-- src/p384/identity_secret.rs | 2 +- src/p384/mod.rs | 3 ++- src/p384/short_address.rs | 5 +++-- 6 files changed, 24 insertions(+), 20 deletions(-) diff --git a/src/p384/address.rs b/src/p384/address.rs index 3011e35..061a0a8 100644 --- a/src/p384/address.rs +++ b/src/p384/address.rs @@ -30,7 +30,8 @@ impl Address { pub const SIZE: usize = 48; /// Length of a full address in string format. - pub const STRING_SIZE: usize = 76; + pub const STRING_SIZE: usize = 81; + pub const STRING_SIZE_NO_PREFIX: usize = 76; /// Get this address as a raw byte array. #[inline(always)] diff --git a/src/p384/identifier.rs b/src/p384/identifier.rs index 8162066..fb25088 100644 --- a/src/p384/identifier.rs +++ b/src/p384/identifier.rs @@ -195,7 +195,7 @@ impl<'a> ToString for PeerIdentifierRef<'a> { match self { Self::Identity(id) => id.to_string(), Self::Address(addr) => addr.to_string(), - Self::Short(addr) => addr.to_string(), + Self::Short(addr) => addr.to_string(), } } } @@ -204,7 +204,7 @@ impl ToString for PeerIdentifier { match self { Self::Identity(id) => id.to_string(), Self::Address(addr) => addr.to_string(), - Self::Short(addr) => addr.to_string(), + Self::Short(addr) => addr.to_string(), } } } @@ -212,7 +212,7 @@ impl ToString for AnyAddress { fn to_string(&self) -> String { match self { Self::Address(addr) => addr.to_string(), - Self::Short(addr) => addr.to_string(), + Self::Short(addr) => addr.to_string(), } } } @@ -248,7 +248,7 @@ impl<'a> serde::Serialize for PeerIdentifierRef<'a> { match self { Self::Identity(id) => id.serialize(s), Self::Address(addr) => addr.serialize(s), - Self::Short(addr) => addr.serialize(s), + Self::Short(addr) => addr.serialize(s), } } } @@ -257,7 +257,7 @@ impl serde::Serialize for PeerIdentifier { match self { Self::Identity(id) => id.serialize(s), Self::Address(addr) => addr.serialize(s), - Self::Short(addr) => addr.serialize(s), + Self::Short(addr) => addr.serialize(s), } } } @@ -265,7 +265,7 @@ impl serde::Serialize for AnyAddress { fn serialize(&self, s: S) -> Result { match self { Self::Address(addr) => addr.serialize(s), - Self::Short(addr) => addr.serialize(s), + Self::Short(addr) => addr.serialize(s), } } } @@ -288,13 +288,12 @@ impl<'de> Deserialize<'de> for PeerIdentifier { formatter.write_str("a zerotier identifier") } - fn visit_bytes(self, v: &[u8]) -> Result - { + fn visit_bytes(self, v: &[u8]) -> Result { match v.len() { Identity::SIZE => Identity::from_bytes(v).map(Self::Value::Identity), Address::SIZE => Address::from_bytes(v).map(Self::Value::Address), ShortAddress::SIZE => ShortAddress::from_bytes(v).map(Self::Value::Short), - _ => return Err(serde::de::Error::custom(ADDRESS_ERR.0)) + _ => return Err(serde::de::Error::custom(ADDRESS_ERR.0)), } .map_err(|_| serde::de::Error::custom(ADDRESS_ERR.0)) } @@ -321,12 +320,11 @@ impl<'de> Deserialize<'de> for AnyAddress { formatter.write_str("a zerotier identifier") } - fn visit_bytes(self, v: &[u8]) -> Result - { + fn visit_bytes(self, v: &[u8]) -> Result { match v.len() { Address::SIZE => Address::from_bytes(v).map(Self::Value::Address), ShortAddress::SIZE => ShortAddress::from_bytes(v).map(Self::Value::Short), - _ => return Err(serde::de::Error::custom(ADDRESS_ERR.0)) + _ => return Err(serde::de::Error::custom(ADDRESS_ERR.0)), } .map_err(|_| serde::de::Error::custom(ADDRESS_ERR.0)) } @@ -336,7 +334,6 @@ impl<'de> Deserialize<'de> for AnyAddress { } } - /* Start of Conversions */ impl<'a> From<&'a PeerIdentifier> for PeerIdentifierRef<'a> { diff --git a/src/p384/identity.rs b/src/p384/identity.rs index 0c7d800..111f38e 100644 --- a/src/p384/identity.rs +++ b/src/p384/identity.rs @@ -30,8 +30,12 @@ pub struct Identity { impl Identity { pub const SIZE: usize = P384_IDENTITY_SIZE; - pub const STRING_SIZE: usize = 547; - pub const STRING_SIZE_NO_PREFIX: usize = 542; + pub const STRING_SIZE: usize = 548; + pub const STRING_SIZE_NO_PREFIX: usize = 543; + + pub fn prefix(&self) -> &ShortAddress { + self.address.prefix() + } pub(crate) fn locally_validate(&self) -> bool { let to_sign: &[&[u8]] = &[ diff --git a/src/p384/identity_secret.rs b/src/p384/identity_secret.rs index 870d542..993f6db 100644 --- a/src/p384/identity_secret.rs +++ b/src/p384/identity_secret.rs @@ -122,7 +122,7 @@ impl<'de> Deserialize<'de> for IdentitySecret { } } } - return Err(serde::de::Error::custom(IDENTITY_ERR.0)); + Err(serde::de::Error::custom(IDENTITY_ERR.0)) } } diff --git a/src/p384/mod.rs b/src/p384/mod.rs index fe8ff28..1b07fdc 100644 --- a/src/p384/mod.rs +++ b/src/p384/mod.rs @@ -64,7 +64,8 @@ mod tests { let start = ms_monotonic(); for _ in 0..3 { let secret = p384::IdentitySecret::generate(1); - println!("P: {}", secret.public.to_string()); + let s = secret.public.to_string(); + println!("P: {} ({})", s, s.len()); } let end = ms_monotonic(); println!("p384 generation time: {} ms/identity", ((end - start) as f64) / 3.0); diff --git a/src/p384/short_address.rs b/src/p384/short_address.rs index a891062..0e1dea8 100644 --- a/src/p384/short_address.rs +++ b/src/p384/short_address.rs @@ -18,7 +18,8 @@ pub struct ShortAddress(pub(crate) [u64; 2]); // treated as [u8; 16] impl ShortAddress { pub const SIZE: usize = 16; - pub const STRING_SIZE: usize = 31; + pub const STRING_SIZE: usize = 36; + pub const STRING_SIZE_NO_PREFIX: usize = 31; #[inline(always)] pub fn as_bytes(&self) -> &[u8; Self::SIZE] { @@ -120,7 +121,7 @@ impl FromStr for ShortAddress { } } } - return Err(ADDRESS_ERR); + Err(ADDRESS_ERR) } } From d905b65c5073a690e20c77ad41f9addc43273fb4 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Wed, 28 Feb 2024 13:29:46 -0500 Subject: [PATCH 09/10] fixed constants --- src/p384/address.rs | 2 +- src/p384/short_address.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/p384/address.rs b/src/p384/address.rs index 061a0a8..e8846fe 100644 --- a/src/p384/address.rs +++ b/src/p384/address.rs @@ -134,7 +134,7 @@ impl FromStr for Address { let s = s.trim(); let s = s.strip_prefix(PREFIX_ADDRESS).unwrap_or(s); let sb = s.as_bytes(); - if sb.len() == Self::STRING_SIZE && sb[31] == b'.' { + if sb.len() == Self::STRING_SIZE_NO_PREFIX && sb[31] == b'.' { let prefix = ShortAddress::from_str(&s[..31])?; Ok(Address([ prefix.0[0], diff --git a/src/p384/short_address.rs b/src/p384/short_address.rs index 0e1dea8..6bca8fe 100644 --- a/src/p384/short_address.rs +++ b/src/p384/short_address.rs @@ -107,7 +107,7 @@ impl FromStr for ShortAddress { fn from_str(s: &str) -> Result { let s = s.trim(); let s = s.strip_prefix(PREFIX_SHORT).unwrap_or(s); - if s.len() == Self::STRING_SIZE { + if s.len() == Self::STRING_SIZE_NO_PREFIX { let mut tmp = [0u8; 16]; let mut w = &mut tmp[..]; for ss in s.split('.') { From daebb4884176e58da58fb397d0a7ddef83d31ed8 Mon Sep 17 00:00:00 2001 From: Monica Moniot Date: Wed, 28 Feb 2024 13:50:19 -0500 Subject: [PATCH 10/10] improved prefixes --- src/p384/address.rs | 4 ++-- src/p384/identity.rs | 2 +- src/p384/mod.rs | 6 +++--- src/p384/short_address.rs | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/p384/address.rs b/src/p384/address.rs index e8846fe..875b40d 100644 --- a/src/p384/address.rs +++ b/src/p384/address.rs @@ -29,9 +29,9 @@ impl Address { pub const REQUIRED_PREFIX: u8 = 0xfc; pub const SIZE: usize = 48; - /// Length of a full address in string format. - pub const STRING_SIZE: usize = 81; pub const STRING_SIZE_NO_PREFIX: usize = 76; + /// Length of a full address in string format. + pub const STRING_SIZE: usize = Self::STRING_SIZE_NO_PREFIX + PREFIX_ADDRESS.len(); /// Get this address as a raw byte array. #[inline(always)] diff --git a/src/p384/identity.rs b/src/p384/identity.rs index 111f38e..9dd1a10 100644 --- a/src/p384/identity.rs +++ b/src/p384/identity.rs @@ -30,8 +30,8 @@ pub struct Identity { impl Identity { pub const SIZE: usize = P384_IDENTITY_SIZE; - pub const STRING_SIZE: usize = 548; pub const STRING_SIZE_NO_PREFIX: usize = 543; + pub const STRING_SIZE: usize = Self::STRING_SIZE_NO_PREFIX + PREFIX_IDENTITY.len(); pub fn prefix(&self) -> &ShortAddress { self.address.prefix() diff --git a/src/p384/mod.rs b/src/p384/mod.rs index 1b07fdc..468694a 100644 --- a/src/p384/mod.rs +++ b/src/p384/mod.rs @@ -40,9 +40,9 @@ const IDENTITY_VARIANT_P384: u8 = 1; const DOMAIN_MASTER_SIG: &[u8] = b"ZTID1_MASTERSIG"; const DOMAIN_SUBKEY_SIG: &[u8] = b"ZTID1_SUBKEYSIG"; -const PREFIX_IDENTITY: &str = "zt:i:"; -const PREFIX_ADDRESS: &str = "zt:a:"; -const PREFIX_SHORT: &str = "zt:s:"; +const PREFIX_IDENTITY: &str = "zt:id:"; +const PREFIX_ADDRESS: &str = "zt:addr:"; +const PREFIX_SHORT: &str = "zt:addr:"; fn first_128_to_string(b: &[u8], s: &mut String) { base24::encode_4to7(&b[0..4], s); diff --git a/src/p384/short_address.rs b/src/p384/short_address.rs index 6bca8fe..cd2842d 100644 --- a/src/p384/short_address.rs +++ b/src/p384/short_address.rs @@ -18,8 +18,8 @@ pub struct ShortAddress(pub(crate) [u64; 2]); // treated as [u8; 16] impl ShortAddress { pub const SIZE: usize = 16; - pub const STRING_SIZE: usize = 36; pub const STRING_SIZE_NO_PREFIX: usize = 31; + pub const STRING_SIZE: usize = Self::STRING_SIZE_NO_PREFIX + PREFIX_SHORT.len(); #[inline(always)] pub fn as_bytes(&self) -> &[u8; Self::SIZE] {