diff --git a/.gitignore b/.gitignore index eb4397189..f8912667d 100644 --- a/.gitignore +++ b/.gitignore @@ -38,3 +38,5 @@ node_modules /*.db /*.opendb /rust-zerotier-core/src/bindings/capi.rs +/rust-zerotier-core/target +/rust-zerotier-service/target diff --git a/rust-zerotier-core/Cargo.lock b/rust-zerotier-core/Cargo.lock index 790a86743..52c15d043 100644 --- a/rust-zerotier-core/Cargo.lock +++ b/rust-zerotier-core/Cargo.lock @@ -6,6 +6,18 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" +[[package]] +name = "hex" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "644f9158b2f133fd50f5fb3242878846d9eb792e445c893805ff0e3824006e35" + +[[package]] +name = "itoa" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6" + [[package]] name = "num-derive" version = "0.3.3" @@ -48,8 +60,48 @@ dependencies = [ name = "rust-zerotier-core" version = "0.1.0" dependencies = [ + "hex", "num-derive", "num-traits", + "serde", + "serde_json", +] + +[[package]] +name = "ryu" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" + +[[package]] +name = "serde" +version = "1.0.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06c64263859d87aa2eb554587e2d23183398d617427327cf2b3d0ed8c69e4800" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c84d3526699cd55261af4b941e4e725444df67aa4f9e6a3564f18030d12672df" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1500e84d27fe482ed1dc791a56eddc2f230046a040fa908c08bda1d9fb615779" +dependencies = [ + "itoa", + "ryu", + "serde", ] [[package]] diff --git a/rust-zerotier-core/Cargo.toml b/rust-zerotier-core/Cargo.toml index d0c8e7864..39964dd2e 100644 --- a/rust-zerotier-core/Cargo.toml +++ b/rust-zerotier-core/Cargo.toml @@ -9,3 +9,6 @@ edition = "2018" [dependencies] num-traits = "0.2.14" num-derive = "0.3.3" +serde = { version = "1", features = ["derive"] } +serde_json = "1" +hex = "0.4.2" diff --git a/rust-zerotier-core/src/address.rs b/rust-zerotier-core/src/address.rs index 88bd9611b..590eafa86 100644 --- a/rust-zerotier-core/src/address.rs +++ b/rust-zerotier-core/src/address.rs @@ -1,9 +1,41 @@ -pub type Address = u64; +pub struct Address(pub u64); -pub fn address_to_string(a: Address) -> String { - format!("{:0>10x}", a as u64) +impl Address { + #[inline] + pub fn new_from_string(s: &str) -> Address { + return Address(u64::from_str_radix(s, 16).unwrap_or(0)); + } } -pub fn address_from_string(s: &str) -> Address { - return u64::from_str_radix(s, 16).unwrap_or(0) as Address; +impl ToString for Address { + #[inline] + fn to_string(&self) -> String { + format!("{:0>10x}", self.0) + } +} + +impl serde::Serialize for Address { + fn serialize(&self, serializer: S) -> Result where S: serde::Serializer { + serializer.serialize_str(self.to_string().as_str()) + } +} + +struct AddressVisitor; + +impl<'de> serde::de::Visitor<'de> for AddressVisitor { + type Value = Address; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("ZeroTier Address in string format") + } + + fn visit_str(self, s: &str) -> Result where E: serde::de::Error { + Ok(Address::new_from_string(s)) + } +} + +impl<'de> serde::Deserialize<'de> for Address { + fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de> { + deserializer.deserialize_str(AddressVisitor) + } } diff --git a/rust-zerotier-core/src/certificate.rs b/rust-zerotier-core/src/certificate.rs index 4435aacf2..cca2f98cc 100644 --- a/rust-zerotier-core/src/certificate.rs +++ b/rust-zerotier-core/src/certificate.rs @@ -2,55 +2,173 @@ use crate::*; use crate::bindings::capi as ztcore; use std::ffi::CStr; use std::ptr::copy_nonoverlapping; -use std::os::raw::c_char; +use std::os::raw::{c_char, c_void}; +use serde::{Deserialize, Serialize}; +/// Maximum length of a string in a certificate (mostly for the certificate name fields). +pub const CERTIFICATE_MAX_STRING_LENGTH: u32 = ztcore::ZT_CERTIFICATE_MAX_STRING_LENGTH; + +/// Certificate local trust bit field flag: this certificate self-signs a root CA. +pub const CERTIFICATE_LOCAL_TRUST_FLAG_ROOT_CA: u32 = ztcore::ZT_CERTIFICATE_LOCAL_TRUST_FLAG_ROOT_CA; + +/// Certificate local trust bit field flag: this certificate specifies a set of ZeroTier roots. +pub const CERTIFICATE_LOCAL_TRUST_FLAG_ZEROTIER_ROOT_SET: u32 = ztcore::ZT_CERTIFICATE_LOCAL_TRUST_FLAG_ZEROTIER_ROOT_SET; + +/// Length of a NIST P-384 unique ID (public key). +pub const CERTIFICATE_UNIQUE_ID_TYPE_NIST_P_384_SIZE: u32 = ztcore::ZT_CERTIFICATE_UNIQUE_ID_TYPE_NIST_P_384_SIZE; + +/// Length of a private key corresponding to a NIST P-384 unique ID. +pub const CERTIFICATE_UNIQUE_ID_TYPE_NIST_P_384_PRIVATE_SIZE: u32 = ztcore::ZT_CERTIFICATE_UNIQUE_ID_TYPE_NIST_P_384_PRIVATE_SIZE; + +/// Type of certificate subject unique ID +#[derive(FromPrimitive,ToPrimitive)] +pub enum CertificateUniqueIdType { + NistP384 = ztcore::ZT_CertificateUniqueIdType_ZT_CERTIFICATE_UNIQUE_ID_TYPE_NIST_P_384 as isize +} + +impl CertificateUniqueIdType { + pub fn new_from_string(s: &str) -> Result { + if s.to_ascii_lowercase() == "nistp384" { + return Ok(CertificateUniqueIdType::NistP384); + } + return Err(ResultCode::ErrorBadParameter); + } +} + +impl ToString for CertificateUniqueIdType { + fn to_string(&self) -> String { + match *self { + CertificateUniqueIdType::NistP384 => String::from("NistP384") + } + } +} + +impl serde::Serialize for CertificateUniqueIdType { + fn serialize(&self, serializer: S) -> Result where S: serde::Serializer { + serializer.serialize_str(self.to_string().as_str()) + } +} + +struct CertificateUniqueIdTypeVisitor; + +impl<'de> serde::de::Visitor<'de> for CertificateUniqueIdTypeVisitor { + type Value = CertificateUniqueIdType; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("CertificateUniqueIdType value in string form") + } + + fn visit_str(self, s: &str) -> Result where E: serde::de::Error { + let id = CertificateUniqueIdType::new_from_string(s); + if id.is_err() { + return Err(serde::de::Error::invalid_value(serde::de::Unexpected::Str(s), &self)); + } + return Ok(id.ok().unwrap() as Self::Value); + } +} + +impl<'de> serde::Deserialize<'de> for CertificateUniqueIdType { + fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de> { + deserializer.deserialize_str(CertificateUniqueIdTypeVisitor) + } +} + +/// Reasons a certificate may be rejected. +#[derive(FromPrimitive,ToPrimitive)] +pub enum CertificateError { + None = ztcore::ZT_CertificateError_ZT_CERTIFICATE_ERROR_NONE as isize, + HaveNewerCert = ztcore::ZT_CertificateError_ZT_CERTIFICATE_ERROR_HAVE_NEWER_CERT as isize, + InvalidFormat = ztcore::ZT_CertificateError_ZT_CERTIFICATE_ERROR_INVALID_FORMAT as isize, + InvalidIdentity = ztcore::ZT_CertificateError_ZT_CERTIFICATE_ERROR_INVALID_IDENTITY as isize, + InvalidPrimarySignature = ztcore::ZT_CertificateError_ZT_CERTIFICATE_ERROR_INVALID_PRIMARY_SIGNATURE as isize, + InvalidChain = ztcore::ZT_CertificateError_ZT_CERTIFICATE_ERROR_INVALID_CHAIN as isize, + InvalidComponentSignature = ztcore::ZT_CertificateError_ZT_CERTIFICATE_ERROR_INVALID_COMPONENT_SIGNATURE as isize, + InvalidUniqueIdProof = ztcore::ZT_CertificateError_ZT_CERTIFICATE_ERROR_INVALID_UNIQUE_ID_PROOF as isize, + MissingRequiredFields = ztcore::ZT_CertificateError_ZT_CERTIFICATE_ERROR_MISSING_REQUIRED_FIELDS as isize, + OutOfValidTimeWindow = ztcore::ZT_CertificateError_ZT_CERTIFICATE_ERROR_OUT_OF_VALID_TIME_WINDOW as isize, +} + +impl ToString for CertificateError { + fn to_string(&self) -> String { + String::from( + match self { + CertificateError::None => "None", + CertificateError::HaveNewerCert => "HaveNewerCert", + CertificateError::InvalidFormat => "InvalidFormat", + CertificateError::InvalidIdentity => "InavlidIdentity", + CertificateError::InvalidPrimarySignature => "InvalidPrimarySignature", + CertificateError::InvalidChain => "InvalidChain", + CertificateError::InvalidComponentSignature => "InvalidComponentSignature", + CertificateError::InvalidUniqueIdProof => "InvalidUniqueIdProof", + CertificateError::MissingRequiredFields => "MissingRequiredFields", + CertificateError::OutOfValidTimeWindow => "OutOfValidTimeWindow" + } + ) + } +} + +#[allow(non_snake_case)] +#[derive(Serialize, Deserialize)] pub struct CertificateName { - pub serial_no: String, - pub common_name: String, + pub serialNo: String, + pub commonName: String, pub country: String, pub organization: String, pub unit: String, pub locality: String, pub province: String, - pub street_address: String, - pub postal_code: String, + pub streetAddress: String, + pub postalCode: String, pub email: String, pub url: String, pub host: String } +#[derive(Serialize, Deserialize)] pub struct CertificateNetwork { pub id: NetworkId, pub controller: Fingerprint } +#[derive(Serialize, Deserialize)] pub struct CertificateIdentity { pub identity: Identity, pub locator: Locator } +#[derive(Serialize, Deserialize)] +pub struct CertificateSubjectUniqueIdSecret { + pub public: Box<[u8]>, + pub private: Box<[u8]>, + pub type_: CertificateUniqueIdType +} + +#[allow(non_snake_case)] +#[derive(Serialize, Deserialize)] pub struct CertificateSubject { pub timestamp: i64, pub identities: Box<[CertificateIdentity]>, pub networks: Box<[CertificateNetwork]>, - pub certificates: Box<[[u8; 48]]>, - pub update_urls: Box<[String]>, + pub certificates: Box<[Box<[u8]>]>, + pub updateURLs: Box<[String]>, pub name: CertificateName, - pub unique_id: Box<[u8]>, - pub unique_id_proof_signature: Box<[u8]> + pub uniqueId: Box<[u8]>, + pub uniqueIdProofSignature: Box<[u8]> } +#[allow(non_snake_case)] +#[derive(Serialize, Deserialize)] pub struct Certificate { - pub serial_no: [u8; 48], + pub serialNo: Box<[u8]>, pub flags: u64, pub timestamp: i64, pub validity: [i64; 2], pub subject: CertificateSubject, pub issuer: Identity, - pub issuer_name: CertificateName, - pub extended_attributes: Box<[u8]>, - pub max_path_length: u32, - pub crl: Box<[u8; 48]>, + pub issuerName: CertificateName, + pub extendedAttributes: Box<[u8]>, + pub maxPathLength: u32, + pub crl: Box<[Box<[u8]>]>, pub signature: Box<[u8]> } @@ -58,15 +176,15 @@ impl CertificateName { pub(crate) fn new_from_capi(cn: &ztcore::ZT_Certificate_Name) -> CertificateName { unsafe { return CertificateName { - serial_no: String::from(CStr::from_ptr(cn.serialNo.as_ptr()).to_str().unwrap()), - common_name: String::from(CStr::from_ptr(cn.commonName.as_ptr()).to_str().unwrap()), + serialNo: String::from(CStr::from_ptr(cn.serialNo.as_ptr()).to_str().unwrap()), + commonName: String::from(CStr::from_ptr(cn.commonName.as_ptr()).to_str().unwrap()), country: String::from(CStr::from_ptr(cn.country.as_ptr()).to_str().unwrap()), organization: String::from(CStr::from_ptr(cn.organization.as_ptr()).to_str().unwrap()), unit: String::from(CStr::from_ptr(cn.unit.as_ptr()).to_str().unwrap()), locality: String::from(CStr::from_ptr(cn.locality.as_ptr()).to_str().unwrap()), province: String::from(CStr::from_ptr(cn.province.as_ptr()).to_str().unwrap()), - street_address: String::from(CStr::from_ptr(cn.streetAddress.as_ptr()).to_str().unwrap()), - postal_code: String::from(CStr::from_ptr(cn.postalCode.as_ptr()).to_str().unwrap()), + streetAddress: String::from(CStr::from_ptr(cn.streetAddress.as_ptr()).to_str().unwrap()), + postalCode: String::from(CStr::from_ptr(cn.postalCode.as_ptr()).to_str().unwrap()), email: String::from(CStr::from_ptr(cn.email.as_ptr()).to_str().unwrap()), url: String::from(CStr::from_ptr(cn.url.as_ptr()).to_str().unwrap()), host: String::from(CStr::from_ptr(cn.host.as_ptr()).to_str().unwrap()) @@ -78,9 +196,9 @@ impl CertificateName { impl CertificateNetwork { pub(crate) fn new_from_capi(cn: &ztcore::ZT_Certificate_Network) -> CertificateNetwork { CertificateNetwork{ - id: cn.id as NetworkId, + id: NetworkId(cn.id), controller: Fingerprint{ - address: cn.controller.address as Address, + address: Address(cn.controller.address), hash: cn.controller.hash } } @@ -112,11 +230,11 @@ impl CertificateSubject { } let ccertificates: &[*const u8] = std::slice::from_raw_parts(cs.certificates, cs.certificateCount as usize); - let mut certificates: Vec<[u8; 48]> = Vec::new(); + let mut certificates: Vec> = Vec::new(); let mut ctmp: [u8; 48] = [0; 48]; for i in ccertificates.iter() { copy_nonoverlapping(*i, ctmp.as_mut_ptr(), 48); - certificates.push(ctmp.clone()); + certificates.push(Box::from(ctmp)); } let cupdate_urls: &[*const c_char] = std::slice::from_raw_parts(cs.updateURLs, cs.updateURLCount as usize); @@ -130,14 +248,79 @@ impl CertificateSubject { identities: identities.into_boxed_slice(), networks: networks.into_boxed_slice(), certificates: certificates.into_boxed_slice(), - update_urls: update_urls.into_boxed_slice(), + updateURLs: update_urls.into_boxed_slice(), name: CertificateName::new_from_capi(&cs.name), - unique_id: Box::from(std::slice::from_raw_parts(cs.uniqueId, cs.uniqueIdSize as usize).clone()), - unique_id_proof_signature: Box::from(std::slice::from_raw_parts(cs.uniqueIdProofSignature, cs.uniqueIdProofSignatureSize as usize).clone()) + uniqueId: Box::from(std::slice::from_raw_parts(cs.uniqueId, cs.uniqueIdSize as usize).clone()), + uniqueIdProofSignature: Box::from(std::slice::from_raw_parts(cs.uniqueIdProofSignature, cs.uniqueIdProofSignatureSize as usize).clone()) } } } } impl Certificate { + pub(crate) fn new_from_capi(c: &ztcore::ZT_Certificate) -> Certificate { + unsafe { + let cextended_attributes: &[u8] = std::slice::from_raw_parts(c.extendedAttributes, c.extendedAttributesSize as usize); + let mut extended_attributes: Vec = Vec::new(); + extended_attributes.extend(cextended_attributes.iter()); + + let ccrl: &[*const u8] = std::slice::from_raw_parts(c.crl, c.crlCount as usize); + let mut crl: Vec> = Vec::new(); + let mut ctmp: [u8; 48] = [0; 48]; + for i in ccrl.iter() { + copy_nonoverlapping(*i, ctmp.as_mut_ptr(), 48); + crl.push(Box::from(ctmp)); + } + + let csignature: &[u8] = std::slice::from_raw_parts(c.signature, c.signatureSize as usize); + let mut signature: Vec = Vec::new(); + signature.extend(csignature.iter()); + + return Certificate{ + serialNo: Box::from(c.serialNo), + flags: c.flags, + timestamp: c.timestamp, + validity: c.validity, + subject: CertificateSubject::new_from_capi(&c.subject), + issuer: Identity::new_from_capi(c.issuer, false), + issuerName: CertificateName::new_from_capi(&c.issuerName), + extendedAttributes: extended_attributes.into_boxed_slice(), + maxPathLength: c.maxPathLength as u32, + crl: crl.into_boxed_slice(), + signature: signature.into_boxed_slice() + } + } + } } + +impl CertificateSubjectUniqueIdSecret { + pub fn new(t: CertificateUniqueIdType) -> Self { + unsafe { + let mut unique_id: Vec = Vec::new(); + let mut unique_id_private: Vec = Vec::new(); + unique_id.resize(128, 0); + unique_id_private.resize(128, 0); + let mut unique_id_size: c_int = 128; + let mut unique_id_private_size: c_int = 128; + let ct: ztcore::ZT_CertificateUniqueIdType = num_traits::ToPrimitive::to_u32(&t).unwrap(); + if ztcore::ZT_Certificate_newSubjectUniqueId(ct, unique_id.as_mut_ptr() as *mut c_void, &mut unique_id_size as *mut c_int, unique_id_private.as_mut_ptr() as *mut c_void, &mut unique_id_private_size as *mut c_int) != 0 { + panic!("fatal internal error: ZT_Certificate_newSubjectUniqueId failed."); + } + unique_id.resize(unique_id_size as usize, 0); + unique_id_private.resize(unique_id_private_size as usize, 0); + return CertificateSubjectUniqueIdSecret{ + public: unique_id.into_boxed_slice(), + private: unique_id_private.into_boxed_slice(), + type_: num_traits::FromPrimitive::from_u32(ct as u32).unwrap() + }; + } + } +} + + +implement_json_serializable!(CertificateName); +implement_json_serializable!(CertificateNetwork); +implement_json_serializable!(CertificateIdentity); +implement_json_serializable!(CertificateSubject); +implement_json_serializable!(Certificate); +implement_json_serializable!(CertificateSubjectUniqueIdSecret); diff --git a/rust-zerotier-core/src/endpoint.rs b/rust-zerotier-core/src/endpoint.rs index fbebc9d8b..aece9a6ec 100644 --- a/rust-zerotier-core/src/endpoint.rs +++ b/rust-zerotier-core/src/endpoint.rs @@ -6,11 +6,21 @@ use std::ffi::CStr; use std::mem::MaybeUninit; pub struct Endpoint { - pub ep_type: EndpointType, + pub type_: EndpointType, intl: ztcore::ZT_Endpoint } impl Endpoint { + #[inline] + pub(crate) fn new_from_capi(ep: *const ztcore::ZT_Endpoint) -> Endpoint { + unsafe { + return Endpoint{ + type_: EndpointType::from_u32((*ep).type_ as u32).unwrap(), + intl: *ep + }; + } + } + pub fn new_from_string(s: &str) -> Result { unsafe { let mut cep: MaybeUninit = MaybeUninit::uninit(); @@ -18,22 +28,13 @@ impl Endpoint { if ec == 0 { let epi = cep.assume_init(); return Ok(Endpoint{ - ep_type: EndpointType::from_u32(epi.type_ as u32).unwrap(), + type_: EndpointType::from_u32(epi.type_ as u32).unwrap(), intl: epi }); } return Err(ResultCode::from_i32(ec).unwrap()); } } - - pub(crate) fn new_from_capi(ep: *const ztcore::ZT_Endpoint) -> Endpoint { - unsafe { - return Endpoint{ - ep_type: EndpointType::from_u32((*ep).type_ as u32).unwrap(), - intl: *ep - }; - } - } } impl ToString for Endpoint { @@ -47,3 +48,33 @@ impl ToString for Endpoint { } } } + +impl serde::Serialize for Endpoint { + fn serialize(&self, serializer: S) -> Result where S: serde::Serializer { + serializer.serialize_str(self.to_string().as_str()) + } +} + +struct EndpointVisitor; + +impl<'de> serde::de::Visitor<'de> for EndpointVisitor { + type Value = Endpoint; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("Endpoint value in string form") + } + + fn visit_str(self, s: &str) -> Result where E: serde::de::Error { + let id = Endpoint::new_from_string(s); + if id.is_err() { + return Err(serde::de::Error::invalid_value(serde::de::Unexpected::Str(s), &self)); + } + return Ok(id.ok().unwrap() as Self::Value); + } +} + +impl<'de> serde::Deserialize<'de> for Endpoint { + fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de> { + deserializer.deserialize_str(EndpointVisitor) + } +} diff --git a/rust-zerotier-core/src/fingerprint.rs b/rust-zerotier-core/src/fingerprint.rs index 4bd1390b2..0bfc0f78a 100644 --- a/rust-zerotier-core/src/fingerprint.rs +++ b/rust-zerotier-core/src/fingerprint.rs @@ -16,7 +16,7 @@ impl Fingerprint { if ztcore::ZT_Fingerprint_fromString(cfp.as_mut_ptr(), s.as_ptr() as *const c_char) != 0 { let fp = cfp.assume_init(); return Ok(Fingerprint{ - address: fp.address as Address, + address: Address(fp.address), hash: fp.hash }); } @@ -30,7 +30,7 @@ impl ToString for Fingerprint { let mut buf: [u8; 256] = [0; 256]; unsafe { if ztcore::ZT_Fingerprint_toString(&ztcore::ZT_Fingerprint { - address: self.address, + address: self.address.0, hash: self.hash }, buf.as_mut_ptr() as *mut c_char, buf.len() as c_int).is_null() { return String::from("(invalid)"); @@ -39,3 +39,33 @@ impl ToString for Fingerprint { } } } + +impl serde::Serialize for Fingerprint { + fn serialize(&self, serializer: S) -> Result where S: serde::Serializer { + serializer.serialize_str(self.to_string().as_str()) + } +} + +struct FingerprintVisitor; + +impl<'de> serde::de::Visitor<'de> for FingerprintVisitor { + type Value = Fingerprint; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("ZeroTier Fingerprint in string format") + } + + fn visit_str(self, s: &str) -> Result where E: serde::de::Error { + let id = Fingerprint::new_from_string(s); + if id.is_err() { + return Err(serde::de::Error::invalid_value(serde::de::Unexpected::Str(s), &self)); + } + return Ok(id.ok().unwrap() as Self::Value); + } +} + +impl<'de> serde::Deserialize<'de> for Fingerprint { + fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de> { + deserializer.deserialize_str(FingerprintVisitor) + } +} diff --git a/rust-zerotier-core/src/identity.rs b/rust-zerotier-core/src/identity.rs index f9a5250e1..09d7ee81f 100644 --- a/rust-zerotier-core/src/identity.rs +++ b/rust-zerotier-core/src/identity.rs @@ -4,11 +4,17 @@ use std::os::raw::*; use std::ffi::CStr; use num_traits::{ToPrimitive, FromPrimitive}; +#[derive(FromPrimitive,ToPrimitive)] +pub enum IdentityType { + Curve25519 = ztcore::ZT_IdentityType_ZT_IDENTITY_TYPE_C25519 as isize, + NistP384 = ztcore::ZT_IdentityType_ZT_IDENTITY_TYPE_P384 as isize, +} + pub struct Identity { - pub id_type: IdentityType, + pub type_: IdentityType, pub address: Address, capi: *const ztcore::ZT_Identity, - requires_delete: bool + requires_delete: bool, } impl Identity { @@ -16,16 +22,18 @@ impl Identity { unsafe { let idt = ztcore::ZT_Identity_type(id); let a = ztcore::ZT_Identity_address(id); - return Identity{ - id_type: FromPrimitive::from_u32(idt as u32).unwrap(), - address: a as Address, + return Identity { + type_: FromPrimitive::from_u32(idt as u32).unwrap(), + address: Address(a), capi: id, - requires_delete: requires_delete + requires_delete: requires_delete, }; } } - pub fn generate(id_type: IdentityType) -> Result { + /// Generate a new identity. + /// This is time consuming due to one time proof of work. It can take several seconds. + pub fn new_generate(id_type: IdentityType) -> Result { unsafe { let id = ztcore::ZT_Identity_new(id_type.to_u32().unwrap()); if id.is_null() { @@ -35,7 +43,8 @@ impl Identity { } } - pub fn new_from_string(s: String) -> Result { + /// Construct from a string representation of this identity. + pub fn new_from_string(s: &str) -> Result { unsafe { let id = ztcore::ZT_Identity_fromString(s.as_ptr() as *const c_char); if id.is_null() { @@ -45,7 +54,7 @@ impl Identity { } } - pub fn to_string(&self, include_private: bool) -> String { + fn intl_to_string(&self, include_private: bool) -> String { let mut buf: [u8; 2048] = [0; 2048]; unsafe { if ztcore::ZT_Identity_toString(self.capi, buf.as_mut_ptr() as *mut c_char, buf.len() as c_int, if include_private { 1 } else { 0 }).is_null() { @@ -55,6 +64,14 @@ impl Identity { } } + /// Convert to a string and include the private key if present. + /// If the private key is not present this is the same as to_string(). + #[inline] + pub fn to_secret_string(&self) -> String { + self.intl_to_string(true) + } + + /// Validate this identity, which can be slightly time consuming in some cases (20-40ms). pub fn validate(&self) -> bool { unsafe { if ztcore::ZT_Identity_validate(self.capi) != 0 { @@ -64,6 +81,7 @@ impl Identity { false } + /// Returns true if this Identity includes its corresponding private key. pub fn has_private(&self) -> bool { unsafe { if ztcore::ZT_Identity_hasPrivate(self.capi) != 0 { @@ -73,16 +91,18 @@ impl Identity { false } + /// Obtain the full fingerprint of this identity, which includes a SHA384 hash of the public key. pub fn fingerprint(&self) -> Fingerprint { unsafe { let cfp = ztcore::ZT_Identity_fingerprint(self.capi); return Fingerprint { - address: (*cfp).address, - hash: (*cfp).hash - } + address: Address((*cfp).address), + hash: (*cfp).hash, + }; } } + /// Sign some data with this identity. pub fn sign(&self, data: &[u8]) -> Result, ResultCode> { unsafe { let mut sig: Vec = vec!(0; 128); @@ -95,6 +115,7 @@ impl Identity { } } + /// Verify a signature by this identity. pub fn verify(&self, data: &[u8], signature: &[u8]) -> bool { if signature.len() == 0 { return false; @@ -110,7 +131,7 @@ impl Identity { impl Drop for Identity { fn drop(&mut self) { - if self.requires_delete && !self.capi.is_null() { + if self.requires_delete { unsafe { ztcore::ZT_Identity_delete(self.capi); } @@ -119,13 +140,38 @@ impl Drop for Identity { } impl ToString for Identity { + #[inline] fn to_string(&self) -> String { - self.to_string(false) + self.intl_to_string(false) } } -impl From for String { - fn from(id: Identity) -> String { - id.to_string(false) +impl serde::Serialize for Identity { + fn serialize(&self, serializer: S) -> Result where S: serde::Serializer { + serializer.serialize_str(self.intl_to_string(false).as_str()) + } +} + +struct IdentityVisitor; + +impl<'de> serde::de::Visitor<'de> for IdentityVisitor { + type Value = Identity; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("ZeroTier Identity in string format") + } + + fn visit_str(self, s: &str) -> Result where E: serde::de::Error { + let id = Identity::new_from_string(s); + if id.is_err() { + return Err(serde::de::Error::invalid_value(serde::de::Unexpected::Str(s), &self)); + } + return Ok(id.ok().unwrap() as Self::Value); + } +} + +impl<'de> serde::Deserialize<'de> for Identity { + fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de> { + deserializer.deserialize_str(IdentityVisitor) } } diff --git a/rust-zerotier-core/src/lib.rs b/rust-zerotier-core/src/lib.rs index 0a3b9a6b4..e2f6c4302 100644 --- a/rust-zerotier-core/src/lib.rs +++ b/rust-zerotier-core/src/lib.rs @@ -7,16 +7,18 @@ mod certificate; mod networkid; mod locator; -pub use identity::Identity; +pub use identity::*; pub use address::*; -pub use fingerprint::Fingerprint; -pub use endpoint::Endpoint; +pub use fingerprint::*; +pub use endpoint::*; pub use networkid::*; -pub use locator::Locator; +pub use locator::*; +pub use certificate::*; use bindings::capi as ztcore; use num_derive::FromPrimitive; use num_derive::ToPrimitive; +use std::os::raw::c_int; pub const DEFAULT_PORT: u16 = ztcore::ZT_DEFAULT_PORT as u16; @@ -45,38 +47,6 @@ pub mod RulePacketCharacteristics { pub const TcpFlagFIN: u64 = crate::bindings::capi::ZT_RULE_PACKET_CHARACTERISTICS_TCP_FIN as u64; } -#[derive(FromPrimitive,ToPrimitive)] -pub enum IdentityType { - Curve25519 = ztcore::ZT_IdentityType_ZT_IDENTITY_TYPE_C25519 as isize, - NistP384 = ztcore::ZT_IdentityType_ZT_IDENTITY_TYPE_P384 as isize, -} - -pub const CERTIFICATE_MAX_STRING_LENGTH: u32 = ztcore::ZT_CERTIFICATE_MAX_STRING_LENGTH; -pub const CERTIFICATE_LOCAL_TRUST_FLAG_ROOT_CA: u32 = ztcore::ZT_CERTIFICATE_LOCAL_TRUST_FLAG_ROOT_CA; -pub const CERTIFICATE_LOCAL_TRUST_FLAG_ZEROTIER_ROOT_SET: u32 = ztcore::ZT_CERTIFICATE_LOCAL_TRUST_FLAG_ZEROTIER_ROOT_SET; - -pub const CERTIFICATE_UNIQUE_ID_TYPE_NIST_P_384_SIZE: u32 = ztcore::ZT_CERTIFICATE_UNIQUE_ID_TYPE_NIST_P_384_SIZE; -pub const CERTIFICATE_UNIQUE_ID_TYPE_NIST_P_384_PRIVATE_SIZE: u32 = ztcore::ZT_CERTIFICATE_UNIQUE_ID_TYPE_NIST_P_384_PRIVATE_SIZE; - -#[derive(FromPrimitive,ToPrimitive)] -pub enum CertificateUniqueIdType { - NistP384 = ztcore::ZT_CertificateUniqueIdType_ZT_CERTIFICATE_UNIQUE_ID_TYPE_NIST_P_384 as isize -} - -#[derive(FromPrimitive,ToPrimitive)] -pub enum CertificateError { - None = ztcore::ZT_CertificateError_ZT_CERTIFICATE_ERROR_NONE as isize, - HaveNewerCert = ztcore::ZT_CertificateError_ZT_CERTIFICATE_ERROR_HAVE_NEWER_CERT as isize, - InvalidFormat = ztcore::ZT_CertificateError_ZT_CERTIFICATE_ERROR_INVALID_FORMAT as isize, - InvalidIdentity = ztcore::ZT_CertificateError_ZT_CERTIFICATE_ERROR_INVALID_IDENTITY as isize, - InvalidPrimarySignature = ztcore::ZT_CertificateError_ZT_CERTIFICATE_ERROR_INVALID_PRIMARY_SIGNATURE as isize, - InvalidChain = ztcore::ZT_CertificateError_ZT_CERTIFICATE_ERROR_INVALID_CHAIN as isize, - InvalidComponentSignature = ztcore::ZT_CertificateError_ZT_CERTIFICATE_ERROR_INVALID_COMPONENT_SIGNATURE as isize, - InvalidUniqueIdProof = ztcore::ZT_CertificateError_ZT_CERTIFICATE_ERROR_INVALID_UNIQUE_ID_PROOF as isize, - MissingRequiredFields = ztcore::ZT_CertificateError_ZT_CERTIFICATE_ERROR_MISSING_REQUIRED_FIELDS as isize, - OutOfValidTimeWindow = ztcore::ZT_CertificateError_ZT_CERTIFICATE_ERROR_OUT_OF_VALID_TIME_WINDOW as isize, -} - #[derive(FromPrimitive,ToPrimitive)] pub enum CredentialType { Null = ztcore::ZT_CredentialType_ZT_CREDENTIAL_TYPE_NULL as isize, @@ -242,6 +212,40 @@ pub enum StateObjectType { Certificate = ztcore::ZT_StateObjectType_ZT_STATE_OBJECT_CERT as isize } +pub fn version() -> (u32, u32, u32, u32) { + let mut major: c_int = 0; + let mut minor: c_int = 0; + let mut revision: c_int = 0; + let mut build: c_int = 0; + unsafe { + ztcore::ZT_version(&mut major as *mut c_int, &mut minor as *mut c_int, &mut revision as *mut c_int, &mut build as *mut c_int); + } + (major as u32, minor as u32, revision as u32, build as u32) +} + +#[macro_export] +macro_rules! implement_json_serializable { + ($struct_name:ident) => { + impl $struct_name { + pub fn new_from_json(json: &str) -> Result<$struct_name, String> { + let r: serde_json::error::Result<$struct_name> = serde_json::from_str(json); + if r.is_err() { + let e = r.err(); + if e.is_none() { + return Err(String::from("unknown error")); + } + return Err(e.unwrap().to_string()); + } + Ok(r.unwrap()) + } + + pub fn to_json(&self) -> String { + serde_json::to_string_pretty(self).unwrap() + } + } + }; +} + #[cfg(test)] mod tests { #[test] diff --git a/rust-zerotier-core/src/locator.rs b/rust-zerotier-core/src/locator.rs index 14bdbb252..7c985e6fe 100644 --- a/rust-zerotier-core/src/locator.rs +++ b/rust-zerotier-core/src/locator.rs @@ -9,6 +9,7 @@ pub struct Locator { } impl Locator { + #[inline] pub(crate) fn new_from_capi(l: *const ztcore::ZT_Locator, requires_delete: bool) -> Locator { Locator{ capi: l, @@ -66,3 +67,33 @@ impl ToString for Locator { } } } + +impl serde::Serialize for Locator { + fn serialize(&self, serializer: S) -> Result where S: serde::Serializer { + serializer.serialize_str(self.to_string().as_str()) + } +} + +struct LocatorVisitor; + +impl<'de> serde::de::Visitor<'de> for LocatorVisitor { + type Value = Locator; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("Locator value in string form") + } + + fn visit_str(self, s: &str) -> Result where E: serde::de::Error { + let id = Locator::new_from_string(s); + if id.is_err() { + return Err(serde::de::Error::invalid_value(serde::de::Unexpected::Str(s), &self)); + } + return Ok(id.ok().unwrap() as Self::Value); + } +} + +impl<'de> serde::Deserialize<'de> for Locator { + fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de> { + deserializer.deserialize_str(LocatorVisitor) + } +} diff --git a/rust-zerotier-core/src/networkid.rs b/rust-zerotier-core/src/networkid.rs index 50bb61ab9..ab44314ac 100644 --- a/rust-zerotier-core/src/networkid.rs +++ b/rust-zerotier-core/src/networkid.rs @@ -1,9 +1,41 @@ -pub type NetworkId = u64; +pub struct NetworkId(pub u64); -pub fn network_id_to_string(n: NetworkId) -> String { - format!("{:0>16x}", n as u64) +impl NetworkId { + #[inline] + pub fn new_from_string(s: &str) -> NetworkId { + return NetworkId(u64::from_str_radix(s, 16).unwrap_or(0)); + } } -pub fn network_id_from_string(s: &str) -> NetworkId { - return u64::from_str_radix(s, 16).unwrap_or(0) as NetworkId; +impl ToString for NetworkId { + #[inline] + fn to_string(&self) -> String { + format!("{:0>16x}", self.0) + } +} + +impl serde::Serialize for NetworkId { + fn serialize(&self, serializer: S) -> Result where S: serde::Serializer { + serializer.serialize_str(self.to_string().as_str()) + } +} + +struct NetworkIdVisitor; + +impl<'de> serde::de::Visitor<'de> for NetworkIdVisitor { + type Value = NetworkId; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("ZeroTier Address in string format") + } + + fn visit_str(self, s: &str) -> Result where E: serde::de::Error { + Ok(NetworkId::new_from_string(s)) + } +} + +impl<'de> serde::Deserialize<'de> for NetworkId { + fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de> { + deserializer.deserialize_str(NetworkIdVisitor) + } }