diff --git a/core/Certificate.hpp b/core/Certificate.hpp index d1962aea9..750aa8767 100644 --- a/core/Certificate.hpp +++ b/core/Certificate.hpp @@ -91,7 +91,7 @@ public: * @param controller Network controller's full fingerprint * @return Pointer to C struct */ - ZT_Certificate_Network *addSubjectNetwork(const uint64_t id, const ZT_Fingerprint &controller); + ZT_Certificate_Network *addSubjectNetwork(uint64_t id, const ZT_Fingerprint &controller); /** * Add a subject certificate (by its serial number) diff --git a/core/zerotier.h b/core/zerotier.h index 51023f63a..b6379d6c4 100644 --- a/core/zerotier.h +++ b/core/zerotier.h @@ -2802,6 +2802,10 @@ ZT_SDK_API int ZT_Certificate_newCSR( /** * Sign a CSR to generate a complete certificate. * + * Note that the signer should fill out timestamp, validity, and other + * certificate fields before signing. Things outside the subject are + * filled in (or can be modified) by the signer. + * * @param cert Certificate to sign * @param signer Signer identity (must contain secret key) * @param signedCert Signed certificate buffer (recommended size: 16384 bytes) diff --git a/rust-zerotier-core/src/certificate.rs b/rust-zerotier-core/src/certificate.rs index 8df9d390c..bc61c14f0 100644 --- a/rust-zerotier-core/src/certificate.rs +++ b/rust-zerotier-core/src/certificate.rs @@ -1,9 +1,11 @@ -use std::ffi::CStr; -use std::intrinsics::write_bytes; +use std::cell::Cell; +use std::ffi::{CStr, CString}; use std::mem::{MaybeUninit, zeroed}; -use std::os::raw::{c_char, c_void}; -use std::ptr::copy_nonoverlapping; +use std::os::raw::{c_char, c_uint, c_void}; +use std::ptr::{copy_nonoverlapping, null, null_mut}; +use std::sync::Mutex; +use num_traits::FromPrimitive; use serde::{Deserialize, Serialize}; use crate::*; @@ -95,7 +97,7 @@ impl<'de> serde::Deserialize<'de> for CertificateSerialNo { ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /// Type of certificate subject unique ID -#[derive(FromPrimitive,ToPrimitive)] +#[derive(FromPrimitive, ToPrimitive)] pub enum CertificateUniqueIdType { NistP384 = ztcore::ZT_CertificateUniqueIdType_ZT_CERTIFICATE_UNIQUE_ID_TYPE_NIST_P_384 as isize } @@ -149,8 +151,39 @@ impl<'de> serde::Deserialize<'de> for CertificateUniqueIdType { ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#[derive(Serialize, Deserialize)] +pub struct CertificateSubjectUniqueIdSecret { + pub public: Vec, + pub private: Vec, + pub type_: CertificateUniqueIdType, +} + +impl CertificateSubjectUniqueIdSecret { + pub fn new(t: CertificateUniqueIdType) -> Self { + unsafe { + let mut unique_id: [u8; 128] = zeroed(); + let mut unique_id_private: [u8; 128] = zeroed(); + let mut unique_id_size: c_int = unique_id.len() as c_int; + let mut unique_id_private_size: c_int = unique_id_private.len() as c_int; + 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."); + } + return CertificateSubjectUniqueIdSecret { + public: Vec::from(&unique_id[0..unique_id_size as usize]), + private: Vec::from(&unique_id_private[0..unique_id_private_size as usize]), + type_: num_traits::FromPrimitive::from_u32(ct as u32).unwrap(), + }; + } + } +} + +implement_to_from_json!(CertificateSubjectUniqueIdSecret); + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// Reasons a certificate may be rejected. -#[derive(FromPrimitive,ToPrimitive)] +#[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, @@ -171,7 +204,7 @@ impl ToString for CertificateError { CertificateError::None => "None", CertificateError::HaveNewerCert => "HaveNewerCert", CertificateError::InvalidFormat => "InvalidFormat", - CertificateError::InvalidIdentity => "InavlidIdentity", + CertificateError::InvalidIdentity => "InvalidIdentity", CertificateError::InvalidPrimarySignature => "InvalidPrimarySignature", CertificateError::InvalidChain => "InvalidChain", CertificateError::InvalidComponentSignature => "InvalidComponentSignature", @@ -183,6 +216,8 @@ impl ToString for CertificateError { } } +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + #[allow(non_snake_case)] #[derive(Serialize, Deserialize)] pub struct CertificateName { @@ -197,55 +232,22 @@ pub struct CertificateName { pub postalCode: String, pub email: String, pub url: String, - pub host: 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: Vec, - pub private: Vec, - pub type_: CertificateUniqueIdType -} - -#[allow(non_snake_case)] -#[derive(Serialize, Deserialize)] -pub struct CertificateSubject { - pub timestamp: i64, - pub identities: Vec, - pub networks: Vec, - pub certificates: Vec, - pub updateURLs: Vec, - pub name: CertificateName, - pub uniqueId: Vec, - pub uniqueIdProofSignature: Vec -} - -#[allow(non_snake_case)] -#[derive(Serialize, Deserialize)] -pub struct Certificate { - pub serialNo: CertificateSerialNo, - pub flags: u64, - pub timestamp: i64, - pub validity: [i64; 2], - pub subject: CertificateSubject, - pub issuer: Identity, - pub issuerName: CertificateName, - pub extendedAttributes: Vec, - pub maxPathLength: u32, - pub crl: Vec, - pub signature: Vec +unsafe fn str_to_cert_cstr(s: &String, cs: &mut [c_char; 128]) { + let mut l = s.len(); + if l == 0 { + cs[0] = 0; + return; + } + if l > 126 { + l = 126; + } + unsafe { + copy_nonoverlapping(s.as_ptr(), cs.as_mut_ptr() as *mut u8, l); + } + cs[l + 1] = 0; } impl CertificateName { @@ -263,31 +265,113 @@ impl CertificateName { 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()) + host: String::from(CStr::from_ptr(cn.host.as_ptr()).to_str().unwrap()), }; } } + + pub(crate) unsafe fn to_capi(&self) -> ztcore::ZT_Certificate_Name { + unsafe { + let mut cn: ztcore::ZT_Certificate_Name = zeroed(); + str_to_cert_cstr(&self.serialNo, &mut cn.serialNo); + str_to_cert_cstr(&self.commonName, &mut cn.commonName); + str_to_cert_cstr(&self.country, &mut cn.country); + str_to_cert_cstr(&self.organization, &mut cn.organization); + str_to_cert_cstr(&self.unit, &mut cn.unit); + str_to_cert_cstr(&self.locality, &mut cn.locality); + str_to_cert_cstr(&self.province, &mut cn.province); + str_to_cert_cstr(&self.streetAddress, &mut cn.streetAddress); + str_to_cert_cstr(&self.postalCode, &mut cn.postalCode); + str_to_cert_cstr(&self.email, &mut cn.email); + str_to_cert_cstr(&self.url, &mut cn.url); + str_to_cert_cstr(&self.host, &mut cn.host); + return cn; + } + } +} + +implement_to_from_json!(CertificateName); + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#[derive(Serialize, Deserialize)] +pub struct CertificateNetwork { + pub id: NetworkId, + pub controller: Fingerprint, } impl CertificateNetwork { pub(crate) fn new_from_capi(cn: &ztcore::ZT_Certificate_Network) -> CertificateNetwork { - CertificateNetwork{ + CertificateNetwork { id: NetworkId(cn.id), - controller: Fingerprint{ + controller: Fingerprint { address: Address(cn.controller.address), - hash: cn.controller.hash - } + hash: cn.controller.hash, + }, + } + } + + pub(crate) fn to_capi(&self) -> ztcore::ZT_Certificate_Network { + ztcore::ZT_Certificate_Network { + id: self.id.0, + controller: ztcore::ZT_Fingerprint { + address: self.controller.address.0, + hash: self.controller.hash, + }, } } } +implement_to_from_json!(CertificateNetwork); + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#[derive(Serialize, Deserialize)] +pub struct CertificateIdentity { + pub identity: Identity, + pub locator: Locator, +} + impl CertificateIdentity { pub(crate) fn new_from_capi(ci: &ztcore::ZT_Certificate_Identity) -> CertificateIdentity { - CertificateIdentity{ + CertificateIdentity { identity: Identity::new_from_capi(ci.identity, false).clone(), - locator: Locator::new_from_capi(ci.locator, false).clone() + locator: Locator::new_from_capi(ci.locator, false).clone(), } } + + pub(crate) unsafe fn to_capi(&self) -> ztcore::ZT_Certificate_Identity { + ztcore::ZT_Certificate_Identity { + identity: self.identity.capi, + locator: self.locator.capi, + } + } +} + +implement_to_from_json!(CertificateIdentity); + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#[allow(non_snake_case)] +#[derive(Serialize, Deserialize)] +pub struct CertificateSubject { + pub timestamp: i64, + pub identities: Vec, + pub networks: Vec, + pub certificates: Vec, + pub updateURLs: Vec, + pub name: CertificateName, + pub uniqueId: Vec, + pub uniqueIdProofSignature: Vec, +} + +pub(crate) struct CertificateSubjectCAPIContainer { + pub(crate) subject: ztcore::ZT_Certificate_Subject, + subject_identities: Vec, + subject_networks: Vec, + subject_certificates: Vec<*const u8>, + subject_urls: Vec<*const c_char>, + subject_urls_strs: Vec, } impl CertificateSubject { @@ -319,7 +403,7 @@ impl CertificateSubject { update_urls.push(CStr::from_ptr(*i).to_str().unwrap().to_string()); } - return CertificateSubject{ + return CertificateSubject { timestamp: cs.timestamp, identities: identities, networks: networks, @@ -327,10 +411,123 @@ impl CertificateSubject { updateURLs: update_urls, name: CertificateName::new_from_capi(&cs.name), uniqueId: Vec::from(std::slice::from_raw_parts(cs.uniqueId, cs.uniqueIdSize as usize)), - uniqueIdProofSignature: Vec::from(std::slice::from_raw_parts(cs.uniqueIdProofSignature, cs.uniqueIdProofSignatureSize as usize)) - } + uniqueIdProofSignature: Vec::from(std::slice::from_raw_parts(cs.uniqueIdProofSignature, cs.uniqueIdProofSignatureSize as usize)), + }; } } + + pub(crate) unsafe fn to_capi(&self) -> CertificateSubjectCAPIContainer { + unsafe { + let mut capi_identities: Vec = Vec::new(); + let mut capi_networks: Vec = Vec::new(); + let mut capi_certificates: Vec<*const u8> = Vec::new(); + let mut capi_urls: Vec<*const c_char> = Vec::new(); + let mut capi_urls_strs: Vec = Vec::new(); + + if !self.identities.is_empty() { + capi_identities.reserve(self.identities.len()); + for i in self.identities.iter() { + capi_identities.push((*i).to_capi()); + } + } + if !self.networks.is_empty() { + capi_networks.reserve(self.networks.len()); + for i in self.networks.iter() { + capi_networks.push((*i).to_capi()); + } + } + if !self.certificates.is_empty() { + capi_certificates.reserve(self.certificates.len()); + for i in self.certificates.iter() { + capi_certificates.push((*i).0.as_ptr()); + } + } + if !self.updateURLs.is_empty() { + capi_urls.reserve(self.updateURLs.len()); + for i in self.updateURLs.iter() { + let cs = CString::new((*i).as_str()); + if cs.is_ok() { + capi_urls_strs.push(cs.unwrap()); + capi_urls.push(capi_urls_strs.last().unwrap().as_ptr()); + } + } + } + + return CertificateSubjectCAPIContainer { + subject: ztcore::ZT_Certificate_Subject { + timestamp: self.timestamp, + identities: capi_identities.as_mut_ptr(), + networks: capi_networks.as_mut_ptr(), + certificates: capi_certificates.as_ptr(), + updateURLs: capi_urls.as_ptr(), + identityCount: capi_identities.len() as c_uint, + networkCount: capi_networks.len() as c_uint, + certificateCount: capi_certificates.len() as c_uint, + updateURLCount: capi_urls.len() as c_uint, + name: self.name.to_capi(), + uniqueId: self.uniqueId.as_ptr(), + uniqueIdProofSignature: self.uniqueIdProofSignature.as_ptr(), + uniqueIdSize: self.uniqueId.len() as c_uint, + uniqueIdProofSignatureSize: self.uniqueIdProofSignature.len() as c_uint, + }, + subject_identities: capi_identities, + subject_networks: capi_networks, + subject_certificates: capi_certificates, + subject_urls: capi_urls, + subject_urls_strs: capi_urls_strs, + }; + } + } + + pub fn new_csr(&self, uid: Option<&CertificateSubjectUniqueIdSecret>) -> Result, ResultCode> { + unsafe { + let capi = self.to_capi(); + + let mut csr: Vec = Vec::new(); + csr.resize(16384, 0); + let mut csr_size: c_int = 16384; + + if uid.is_some() { + let uid2 = uid.unwrap(); + if ztcore::ZT_Certificate_newCSR(&capi.subject as *const ztcore::ZT_Certificate_Subject, uid2.public.as_ptr() as *const c_void, uid2.public.len() as c_int, uid2.private.as_ptr() as *const c_void, uid2.private.len() as c_int, csr.as_mut_ptr() as *mut c_void, &mut csr_size as *mut c_int) != 0 { + return Err(ResultCode::ErrorBadParameter); + } + } else { + if ztcore::ZT_Certificate_newCSR(&capi.subject, null(), -1, null(), -1, csr.as_mut_ptr() as *mut c_void, &mut csr_size as *mut c_int) != 0 { + return Err(ResultCode::ErrorBadParameter); + } + } + csr.resize(csr_size as usize, 0); + + return Ok(csr); + } + } +} + +implement_to_from_json!(CertificateSubject); + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#[allow(non_snake_case)] +#[derive(Serialize, Deserialize)] +pub struct Certificate { + pub serialNo: CertificateSerialNo, + pub flags: u64, + pub timestamp: i64, + pub validity: [i64; 2], + pub subject: CertificateSubject, + pub issuer: Identity, + pub issuerName: CertificateName, + pub extendedAttributes: Vec, + pub maxPathLength: u32, + pub crl: Vec, + pub signature: Vec, +} + +pub(crate) struct CertificateCAPIContainer { + pub(crate) certificate: ztcore::ZT_Certificate, + certificate_crls: Vec<*const u8>, + subject_container: CertificateSubjectCAPIContainer, } impl Certificate { @@ -344,7 +541,7 @@ impl Certificate { crl.push(CertificateSerialNo(ctmp)); } - return Certificate{ + return Certificate { serialNo: CertificateSerialNo(c.serialNo), flags: c.flags, timestamp: c.timestamp, @@ -355,35 +552,100 @@ impl Certificate { extendedAttributes: Vec::from(std::slice::from_raw_parts(c.extendedAttributes, c.extendedAttributesSize as usize)), maxPathLength: c.maxPathLength as u32, crl: crl, - signature: Vec::from(std::slice::from_raw_parts(c.signature, c.signatureSize as usize)) - } - } - } -} - -impl CertificateSubjectUniqueIdSecret { - pub fn new(t: CertificateUniqueIdType) -> Self { - unsafe { - let mut unique_id: [u8; 128] = zeroed(); - let mut unique_id_private: [u8; 128] = zeroed(); - let mut unique_id_size: c_int = unique_id.len() as c_int; - let mut unique_id_private_size: c_int = unique_id_private.len() as c_int; - 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."); - } - return CertificateSubjectUniqueIdSecret{ - public: Vec::from(&unique_id[0..unique_id_size as usize]), - private: Vec::from(&unique_id_private[0..unique_id_private_size as usize]), - type_: num_traits::FromPrimitive::from_u32(ct as u32).unwrap() + signature: Vec::from(std::slice::from_raw_parts(c.signature, c.signatureSize as usize)), }; } } + + pub fn new_from_bytes(b: &[u8], verify: bool) -> Result { + unsafe { + let mut capi_cert: *const ztcore::ZT_Certificate = null_mut(); + let capi_verify: c_int = if verify { 1 } else { 0 }; + let result = ztcore::ZT_Certificate_decode(&mut capi_cert as *mut *const ztcore::ZT_Certificate, b.as_ptr() as *const c_void, b.len() as c_int, capi_verify); + if result != ztcore::ZT_CertificateError_ZT_CERTIFICATE_ERROR_NONE { + return Err(CertificateError::from_u32(result as u32).unwrap_or(CertificateError::InvalidFormat)); + } + if capi_cert.is_null() { + return Err(CertificateError::InvalidFormat); + } + let cert = Certificate::new_from_capi(&*capi_cert); + ztcore::ZT_Certificate_delete(capi_cert); + return Ok(cert); + } + } + + pub(crate) unsafe fn to_capi(&self) -> CertificateCAPIContainer { + unsafe { + let subject = self.subject.to_capi(); + + let mut capi_crls: Vec<*const u8> = Vec::new(); + capi_crls.reserve(self.crl.len()); + for i in self.crl.iter() { + capi_crls.push((*i).0.as_ptr()); + } + + return CertificateCAPIContainer { + certificate: ztcore::ZT_Certificate { + serialNo: self.serialNo.0, + flags: self.flags, + timestamp: self.timestamp, + validity: self.validity, + subject: subject.subject, + issuer: self.issuer.capi, + issuerName: self.issuerName.to_capi(), + extendedAttributes: self.extendedAttributes.as_ptr(), + extendedAttributesSize: self.extendedAttributes.len() as c_uint, + maxPathLength: self.maxPathLength as c_uint, + crl: capi_crls.as_ptr(), + crlCount: capi_crls.len() as c_uint, + signature: self.signature.as_ptr(), + signatureSize: self.signature.len() as c_uint, + }, + certificate_crls: capi_crls, + subject_container: subject, + }; + } + } + + pub fn to_bytes(&self) -> Result, ResultCode> { + unsafe { + let mut cert: Vec = Vec::new(); + cert.resize(16384, 0); + let mut cert_size: c_int = 16384; + let capi = self.to_capi(); + if ztcore::ZT_Certificate_encode(&capi.certificate as *const ztcore::ZT_Certificate, cert.as_mut_ptr() as *mut c_void, &mut cert_size) != 0 { + return Err(ResultCode::ErrorInternalNonFatal); + } + cert.resize(cert_size as usize, 0); + return Ok(cert.into_boxed_slice()); + } + } + + pub fn sign(&self, id: &Identity) -> Result, ResultCode> { + if !id.has_private() { + return Err(ResultCode::ErrorBadParameter); + } + unsafe { + let capi = self.to_capi(); + let mut signed_cert: Vec = Vec::new(); + signed_cert.resize(16384, 0); + let mut signed_cert_size: c_int = 16384; + if ztcore::ZT_Certificate_sign(&capi.certificate as *const ztcore::ZT_Certificate, id.capi, signed_cert.as_mut_ptr() as *mut c_void, &mut signed_cert_size) != 0 { + return Err(ResultCode::ErrorBadParameter); + } + signed_cert.resize(signed_cert_size as usize, 0); + return Ok(signed_cert); + } + } + + pub fn verify(&self) -> CertificateError { + unsafe { + let capi = self.to_capi(); + return CertificateError::from_u32(ztcore::ZT_Certificate_verify(&capi.certificate as *const ztcore::ZT_Certificate) as u32).unwrap_or(CertificateError::InvalidFormat); + } + } } -implement_to_from_json!(CertificateName); -implement_to_from_json!(CertificateNetwork); -implement_to_from_json!(CertificateIdentity); -implement_to_from_json!(CertificateSubject); implement_to_from_json!(Certificate); -implement_to_from_json!(CertificateSubjectUniqueIdSecret); + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/rust-zerotier-core/src/identity.rs b/rust-zerotier-core/src/identity.rs index 152560467..a75ef3775 100644 --- a/rust-zerotier-core/src/identity.rs +++ b/rust-zerotier-core/src/identity.rs @@ -13,7 +13,7 @@ pub enum IdentityType { pub struct Identity { pub type_: IdentityType, pub address: Address, - capi: *const ztcore::ZT_Identity, + pub(crate) capi: *const ztcore::ZT_Identity, requires_delete: bool, } diff --git a/rust-zerotier-core/src/locator.rs b/rust-zerotier-core/src/locator.rs index 8d5d30f4c..a0d7a0052 100644 --- a/rust-zerotier-core/src/locator.rs +++ b/rust-zerotier-core/src/locator.rs @@ -4,7 +4,7 @@ use std::os::raw::{c_char, c_int, c_uint}; use std::ffi::CStr; pub struct Locator { - capi: *const ztcore::ZT_Locator, + pub(crate) capi: *const ztcore::ZT_Locator, requires_delete: bool }