mirror of
https://github.com/zerotier/identity.git
synced 2026-05-22 16:29:03 -07:00
implemented serialization
This commit is contained in:
+16
-7
@@ -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<Self, Self::Err> {
|
||||
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],
|
||||
|
||||
+173
-3
@@ -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<Self, Self::Err> {
|
||||
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<Self, Self::Err> {
|
||||
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<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
|
||||
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<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
|
||||
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<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
|
||||
match self {
|
||||
Self::Address(addr) => addr.serialize(s),
|
||||
Self::Short(addr) => addr.serialize(s),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for PeerIdentifier {
|
||||
#[inline]
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
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<E: serde::de::Error>(self, v: &[u8]) -> Result<Self::Value, E>
|
||||
{
|
||||
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<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
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<E: serde::de::Error>(self, v: &[u8]) -> Result<Self::Value, E>
|
||||
{
|
||||
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> {
|
||||
|
||||
+26
-10
@@ -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<Self, Self::Err> {
|
||||
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<W: std::io::Write>(&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<E>(self, v: &[u8]) -> Result<Self::Value, E>
|
||||
|
||||
+11
-2
@@ -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);
|
||||
|
||||
@@ -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<Self, Self::Err> {
|
||||
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('.') {
|
||||
|
||||
Reference in New Issue
Block a user