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('.') {