ZeroTierOne/rust-zerotier-core/src/certificate.rs

805 lines
34 KiB
Rust

/*
* Copyright (c)2013-2021 ZeroTier, Inc.
*
* Use of this software is governed by the Business Source License included
* in the LICENSE.TXT file in the project's root directory.
*
* Change Date: 2026-01-01
*
* On the date above, in accordance with the Business Source License, use
* of this software will be governed by version 2.0 of the Apache License.
*/
/****/
use std::ffi::CString;
use std::hash::{Hash, Hasher};
use std::mem::{zeroed, MaybeUninit};
use std::os::raw::{c_char, c_uint, c_void};
use std::pin::Pin;
use std::ptr::{copy_nonoverlapping, null, null_mut, read_unaligned, write_bytes};
use num_derive::{FromPrimitive, ToPrimitive};
#[allow(unused_imports)]
use num_traits::{FromPrimitive, ToPrimitive};
use serde::{Deserialize, Serialize};
use crate::*;
use crate::capi as ztcore;
/// Maximum length of a string in a certificate (mostly for the certificate name fields).
pub const CERTIFICATE_MAX_STRING_LENGTH: isize = ztcore::ZT_CERTIFICATE_MAX_STRING_LENGTH as isize;
/// 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;
pub const CERTIFICATE_USAGE_DIGITAL_SIGNATURE: u64 = ztcore::ZT_CERTIFICATE_USAGE_DIGITAL_SIGNATURE as u64;
pub const CERTIFICATE_USAGE_NON_REPUDIATION: u64 = ztcore::ZT_CERTIFICATE_USAGE_NON_REPUDIATION as u64;
pub const CERTIFICATE_USAGE_KEY_ENCIPHERMENT: u64 = ztcore::ZT_CERTIFICATE_USAGE_KEY_ENCIPHERMENT as u64;
pub const CERTIFICATE_USAGE_DATA_ENCIPHERMENT: u64 = ztcore::ZT_CERTIFICATE_USAGE_DATA_ENCIPHERMENT as u64;
pub const CERTIFICATE_USAGE_KEY_AGREEMENT: u64 = ztcore::ZT_CERTIFICATE_USAGE_KEY_AGREEMENT as u64;
pub const CERTIFICATE_USAGE_CERTIFICATE_SIGNING: u64 = ztcore::ZT_CERTIFICATE_USAGE_CERTIFICATE_SIGNING as u64;
pub const CERTIFICATE_USAGE_CRL_SIGNING: u64 = ztcore::ZT_CERTIFICATE_USAGE_CRL_SIGNING as u64;
pub const CERTIFICATE_USAGE_EXECUTABLE_SIGNATURE: u64 = ztcore::ZT_CERTIFICATE_USAGE_EXECUTABLE_SIGNATURE as u64;
pub const CERTIFICATE_USAGE_TIMESTAMPING: u64 = ztcore::ZT_CERTIFICATE_USAGE_TIMESTAMPING as u64;
/// All certificate usage flags and their corresponding canonical abbreviations.
pub const ALL_CERTIFICATE_USAGE_FLAGS: [(u64, &'static str); 9] = [
(CERTIFICATE_USAGE_DIGITAL_SIGNATURE, "ds"),
(CERTIFICATE_USAGE_NON_REPUDIATION, "nr"),
(CERTIFICATE_USAGE_KEY_ENCIPHERMENT, "ke"),
(CERTIFICATE_USAGE_DATA_ENCIPHERMENT, "de"),
(CERTIFICATE_USAGE_KEY_AGREEMENT, "ka"),
(CERTIFICATE_USAGE_CERTIFICATE_SIGNING, "cs"),
(CERTIFICATE_USAGE_CRL_SIGNING, "crl"),
(CERTIFICATE_USAGE_EXECUTABLE_SIGNATURE, "es"),
(CERTIFICATE_USAGE_TIMESTAMPING, "ts"),
];
#[inline(always)]
fn vec_to_array<const L: usize>(v: &Vec<u8>) -> [u8; L] {
unsafe {
let mut a: MaybeUninit<[u8; L]> = MaybeUninit::uninit();
copy_nonoverlapping(v.as_ptr(), a.as_mut_ptr().cast::<u8>(), v.len().min(L));
if v.len() < L {
write_bytes(a.as_mut_ptr().cast::<u8>(), 0, L - v.len());
}
a.assume_init()
}
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#[derive(FromPrimitive, ToPrimitive, PartialEq, Eq, Clone, Copy)]
pub enum CertificatePublicKeyAlgorithm {
None = ztcore::ZT_CertificatePublicKeyAlgorithm_ZT_CERTIFICATE_PUBLIC_KEY_ALGORITHM_NONE as isize,
ECDSANistP384 = ztcore::ZT_CertificatePublicKeyAlgorithm_ZT_CERTIFICATE_PUBLIC_KEY_ALGORITHM_ECDSA_NIST_P_384 as isize,
}
impl From<i32> for CertificatePublicKeyAlgorithm {
#[inline(always)]
fn from(n: i32) -> CertificatePublicKeyAlgorithm {
CertificatePublicKeyAlgorithm::from_i32(n).unwrap_or(CertificatePublicKeyAlgorithm::None)
}
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#[derive(PartialEq, Eq, PartialOrd, Ord, Clone)]
pub struct CertificateSerialNo(pub [u8; 48]);
impl CertificateSerialNo {
pub const SIZE: usize = 48;
pub const SIZE_HEX: usize = 96;
#[inline(always)]
pub fn new() -> CertificateSerialNo { CertificateSerialNo([0_u8; 48]) }
/// Create a new certificate serial from a hex string, returning None if invalid.
/// The from() alternative for converting from a string returns an a nil (all zero) serial on error.
pub fn new_from_string(s: &str) -> Option<CertificateSerialNo> {
hex::decode(s).map_or(None, |b| {
if b.len() == Self::SIZE {
Some(CertificateSerialNo(vec_to_array::<48>(&b)))
} else {
None
}
})
}
/// Returns true if serial is all zeroes.
#[inline(always)]
pub fn is_nil(&self) -> bool {
is_all_zeroes(self.0)
}
}
impl Hash for CertificateSerialNo {
#[inline(always)]
fn hash<H: Hasher>(&self, state: &mut H) {
// Serials are SHA384 hashes, so we can just use the first 8 bytes as-is.
state.write_u64(unsafe { read_unaligned::<u64>(self.0.as_ptr().cast()) })
}
}
impl<S: AsRef<str>> From<S> for CertificateSerialNo {
#[inline(always)]
fn from(s: S) -> CertificateSerialNo {
Self::new_from_string(s.as_ref()).unwrap_or_else(|| CertificateSerialNo::new())
}
}
impl ToString for CertificateSerialNo {
#[inline(always)]
fn to_string(&self) -> String { hex::encode(self.0) }
}
impl serde::Serialize for CertificateSerialNo {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: serde::Serializer { serializer.serialize_str(self.to_string().as_str()) }
}
struct CertificateSerialNoVisitor;
impl<'de> serde::de::Visitor<'de> for CertificateSerialNoVisitor {
type Value = CertificateSerialNo;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { formatter.write_str("object") }
fn visit_str<E>(self, s: &str) -> Result<Self::Value, E> where E: serde::de::Error { Self::Value::new_from_string(s).map_or_else(|| { Err(serde::de::Error::invalid_value(serde::de::Unexpected::Str(s), &self)) },|serial| { Ok(serial as Self::Value) }) }
}
impl<'de> serde::Deserialize<'de> for CertificateSerialNo {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: serde::Deserializer<'de> { deserializer.deserialize_str(CertificateSerialNoVisitor) }
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#[derive(FromPrimitive, ToPrimitive, PartialEq, Eq, Clone, Copy)]
pub enum CertificateError {
None = ztcore::ZT_CertificateError_ZT_CERTIFICATE_ERROR_NONE 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,
Revoked = ztcore::ZT_CertificateError_ZT_CERTIFICATE_ERROR_REVOKED as isize,
}
impl CertificateError {
pub fn to_str(&self) -> &'static str {
match self {
CertificateError::None => "None",
CertificateError::InvalidFormat => "InvalidFormat",
CertificateError::InvalidIdentity => "InvalidIdentity",
CertificateError::InvalidPrimarySignature => "InvalidPrimarySignature",
CertificateError::InvalidChain => "InvalidChain",
CertificateError::InvalidComponentSignature => "InvalidComponentSignature",
CertificateError::InvalidUniqueIdProof => "InvalidUniqueIdProof",
CertificateError::MissingRequiredFields => "MissingRequiredFields",
CertificateError::OutOfValidTimeWindow => "OutOfValidTimeWindow",
CertificateError::Revoked => "Revoked",
}
}
}
impl ToString for CertificateError {
#[inline(always)]
fn to_string(&self) -> String {
String::from(self.to_str())
}
}
impl<S: AsRef<str>> From<S> for CertificateError {
fn from(s: S) -> CertificateError {
match s.as_ref().to_ascii_lowercase().as_str() {
"invalidformat" => CertificateError::InvalidFormat,
"invalididentity" => CertificateError::InvalidIdentity,
"invalidprimarysignature" => CertificateError::InvalidPrimarySignature,
"invalidchain" => CertificateError::InvalidChain,
"invalidcomponentsignature" => CertificateError::InvalidComponentSignature,
"invaliduniqueidproof" => CertificateError::InvalidUniqueIdProof,
"missingrequiredfields" => CertificateError::MissingRequiredFields,
"outofvalidtimewindow" => CertificateError::OutOfValidTimeWindow,
"revoked" => CertificateError::Revoked,
_ => CertificateError::None
}
}
}
impl serde::Serialize for CertificateError {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: serde::Serializer { serializer.serialize_str(self.to_string().as_str()) }
}
struct CertificateErrorVisitor;
impl<'de> serde::de::Visitor<'de> for CertificateErrorVisitor {
type Value = CertificateError;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { formatter.write_str("object") }
fn visit_str<E>(self, s: &str) -> Result<Self::Value, E> where E: serde::de::Error { return Ok(CertificateError::from(s)); }
}
impl<'de> serde::Deserialize<'de> for CertificateError {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: serde::Deserializer<'de> { deserializer.deserialize_str(CertificateErrorVisitor) }
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#[derive(Serialize, Deserialize, PartialEq, Eq, Clone)]
pub struct CertificateName {
#[serde(rename = "serialNo")]
pub serial_no: String,
#[serde(rename = "commonName")]
pub common_name: String,
pub country: String,
pub organization: String,
pub unit: String,
pub locality: String,
pub province: String,
#[serde(rename = "streetAddress")]
pub street_address: String,
#[serde(rename = "postalCode")]
pub postal_code: String,
pub email: String,
pub url: String,
pub host: String,
}
impl CertificateName {
pub fn new() -> CertificateName {
CertificateName {
serial_no: String::new(),
common_name: String::new(),
country: String::new(),
organization: String::new(),
unit: String::new(),
locality: String::new(),
province: String::new(),
street_address: String::new(),
postal_code: String::new(),
email: String::new(),
url: String::new(),
host: String::new(),
}
}
pub(crate) unsafe fn new_from_capi(cn: &ztcore::ZT_Certificate_Name) -> CertificateName {
return CertificateName {
serial_no: cstr_to_string(cn.serialNo.as_ptr(), CERTIFICATE_MAX_STRING_LENGTH - 1),
common_name: cstr_to_string(cn.commonName.as_ptr(), CERTIFICATE_MAX_STRING_LENGTH - 1),
country: cstr_to_string(cn.country.as_ptr(), CERTIFICATE_MAX_STRING_LENGTH - 1),
organization: cstr_to_string(cn.organization.as_ptr(), CERTIFICATE_MAX_STRING_LENGTH - 1),
unit: cstr_to_string(cn.unit.as_ptr(), CERTIFICATE_MAX_STRING_LENGTH - 1),
locality: cstr_to_string(cn.locality.as_ptr(), CERTIFICATE_MAX_STRING_LENGTH - 1),
province: cstr_to_string(cn.province.as_ptr(), CERTIFICATE_MAX_STRING_LENGTH - 1),
street_address: cstr_to_string(cn.streetAddress.as_ptr(), CERTIFICATE_MAX_STRING_LENGTH - 1),
postal_code: cstr_to_string(cn.postalCode.as_ptr(), CERTIFICATE_MAX_STRING_LENGTH - 1),
email: cstr_to_string(cn.email.as_ptr(), CERTIFICATE_MAX_STRING_LENGTH - 1),
url: cstr_to_string(cn.url.as_ptr(), CERTIFICATE_MAX_STRING_LENGTH - 1),
host: cstr_to_string(cn.host.as_ptr(), CERTIFICATE_MAX_STRING_LENGTH - 1),
};
}
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;
}
pub(crate) unsafe fn to_capi(&self) -> ztcore::ZT_Certificate_Name {
let mut cn: ztcore::ZT_Certificate_Name = zeroed();
Self::str_to_cert_cstr(&self.serial_no, &mut cn.serialNo);
Self::str_to_cert_cstr(&self.common_name, &mut cn.commonName);
Self::str_to_cert_cstr(&self.country, &mut cn.country);
Self::str_to_cert_cstr(&self.organization, &mut cn.organization);
Self::str_to_cert_cstr(&self.unit, &mut cn.unit);
Self::str_to_cert_cstr(&self.locality, &mut cn.locality);
Self::str_to_cert_cstr(&self.province, &mut cn.province);
Self::str_to_cert_cstr(&self.street_address, &mut cn.streetAddress);
Self::str_to_cert_cstr(&self.postal_code, &mut cn.postalCode);
Self::str_to_cert_cstr(&self.email, &mut cn.email);
Self::str_to_cert_cstr(&self.url, &mut cn.url);
Self::str_to_cert_cstr(&self.host, &mut cn.host);
return cn;
}
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#[derive(Serialize, Deserialize, PartialEq, Eq, Clone)]
pub struct CertificateNetwork {
pub id: NetworkId,
pub controller: Option<Fingerprint>,
}
impl CertificateNetwork {
pub(crate) fn new_from_capi(cn: &ztcore::ZT_Certificate_Network) -> CertificateNetwork {
if is_all_zeroes(cn.controller.hash) {
CertificateNetwork {
id: NetworkId(cn.id),
controller: None,
}
} else {
CertificateNetwork {
id: NetworkId(cn.id),
controller: Some(Fingerprint {
address: Address(cn.controller.address),
hash: cn.controller.hash,
}),
}
}
}
pub(crate) fn to_capi(&self) -> ztcore::ZT_Certificate_Network {
self.controller.as_ref().map_or_else(|| {
ztcore::ZT_Certificate_Network {
id: self.id.0,
controller: ztcore::ZT_Fingerprint {
address: 0,
hash: [0_u8; 48],
}
}
}, |controller| {
ztcore::ZT_Certificate_Network {
id: self.id.0,
controller: ztcore::ZT_Fingerprint {
address: controller.address.0,
hash: controller.hash,
},
}
})
}
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#[derive(Serialize, Deserialize, PartialEq, Eq, Clone)]
pub struct CertificateIdentity {
pub identity: Identity,
pub locator: Option<Locator>,
}
impl CertificateIdentity {
pub(crate) unsafe fn new_from_capi(ci: &ztcore::ZT_Certificate_Identity) -> Option<CertificateIdentity> {
if ci.identity.is_null() {
return None;
}
Some(CertificateIdentity {
identity: Identity::new_from_capi(ci.identity, false).clone(),
locator: if ci.locator.is_null() { None } else { Some(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: if self.locator.is_some() { self.locator.as_ref().unwrap().capi } else { null() },
}
}
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#[derive(Serialize, Deserialize, PartialEq, Eq, Clone)]
pub struct CertificateSubject {
pub timestamp: i64,
pub identities: Vec<CertificateIdentity>,
pub networks: Vec<CertificateNetwork>,
#[serde(rename = "updateURLs")]
pub update_urls: Vec<String>,
pub name: CertificateName,
#[serde(with = "Base64URLSafeNoPad")]
#[serde(rename = "uniqueId")]
pub unique_id: Vec<u8>,
#[serde(with = "Base64URLSafeNoPad")]
#[serde(rename = "uniqueIdSignature")]
pub unique_id_signature: Vec<u8>,
}
#[allow(unused)]
pub(crate) struct CertificateSubjectCAPIContainer {
pub(crate) subject: ztcore::ZT_Certificate_Subject,
subject_identities: Option<Pin<Box<[ztcore::ZT_Certificate_Identity]>>>,
subject_networks: Option<Pin<Box<[ztcore::ZT_Certificate_Network]>>>,
subject_urls: Option<Pin<Box<[*const c_char]>>>,
subject_urls_strs: Option<Pin<Box<[CString]>>>,
}
impl CertificateSubject {
pub fn new() -> CertificateSubject {
CertificateSubject {
timestamp: 0,
identities: Vec::new(),
networks: Vec::new(),
update_urls: Vec::new(),
name: CertificateName::new(),
unique_id: Vec::new(),
unique_id_signature: Vec::new(),
}
}
pub(crate) unsafe fn new_from_capi(cs: &ztcore::ZT_Certificate_Subject) -> CertificateSubject {
let mut identities: Vec<CertificateIdentity> = Vec::new();
if !cs.identities.is_null() && cs.identityCount > 0 {
let cidentities: &[ztcore::ZT_Certificate_Identity] = std::slice::from_raw_parts(cs.identities, cs.identityCount as usize);
for i in cidentities.iter() {
let ci = CertificateIdentity::new_from_capi(i);
if ci.is_some() {
identities.push(ci.unwrap());
}
}
}
let mut networks: Vec<CertificateNetwork> = Vec::new();
if !cs.networks.is_null() && cs.networkCount > 0 {
let cnetworks: &[ztcore::ZT_Certificate_Network] = std::slice::from_raw_parts(cs.networks, cs.networkCount as usize);
for i in cnetworks.iter() {
networks.push(CertificateNetwork::new_from_capi(i));
}
}
let mut update_urls: Vec<String> = Vec::new();
if !cs.updateURLs.is_null() && cs.updateURLCount > 0 {
let cupdate_urls: &[*const c_char] = std::slice::from_raw_parts(cs.updateURLs, cs.updateURLCount as usize);
for i in cupdate_urls.iter() {
update_urls.push(cstr_to_string(*i, CERTIFICATE_MAX_STRING_LENGTH - 1));
}
}
return CertificateSubject {
timestamp: cs.timestamp,
identities,
networks,
update_urls,
name: CertificateName::new_from_capi(&cs.name),
unique_id: cs.uniqueId[0..(cs.uniqueIdSize as usize)].to_vec(),
unique_id_signature: cs.uniqueIdSignature[0..cs.uniqueIdSignatureSize as usize].to_vec(),
};
}
pub(crate) unsafe fn to_capi(&self) -> CertificateSubjectCAPIContainer {
let mut identity_count: c_uint = 0;
let mut network_count: c_uint = 0;
let mut update_url_count: c_uint = 0;
let mut capi_identities = if self.identities.is_empty() {
None
} else {
let mut capi_identities: Vec<ztcore::ZT_Certificate_Identity> = Vec::new();
capi_identities.reserve(self.identities.len());
for i in self.identities.iter() {
capi_identities.push((*i).to_capi());
}
identity_count = capi_identities.len() as c_uint;
Some(Pin::from(capi_identities.into_boxed_slice()))
};
let mut capi_networks = if self.networks.is_empty() {
None
} else {
let mut capi_networks: Vec<ztcore::ZT_Certificate_Network> = Vec::new();
capi_networks.reserve(self.networks.len());
for i in self.networks.iter() {
capi_networks.push((*i).to_capi());
}
network_count = capi_networks.len() as c_uint;
Some(Pin::from(capi_networks.into_boxed_slice()))
};
let (mut capi_urls, capi_urls_strs) = if self.update_urls.is_empty() {
(None, None)
} else {
let mut capi_urls_strs: Vec<CString> = Vec::new();
let mut capi_urls: Vec<*const c_char> = Vec::new();
capi_urls_strs.reserve(self.update_urls.len());
capi_urls.reserve(self.update_urls.len());
for i in self.update_urls.iter() {
let cs = CString::new((*i).as_str());
if cs.is_ok() {
capi_urls_strs.push(cs.unwrap());
}
}
let capi_urls_strs = Pin::from(capi_urls_strs.into_boxed_slice());
for i in capi_urls_strs.iter() {
capi_urls.push((*i).as_ptr());
}
update_url_count = capi_urls.len() as c_uint;
(Some(Pin::from(capi_urls.into_boxed_slice())), Some(capi_urls_strs))
};
CertificateSubjectCAPIContainer {
subject: ztcore::ZT_Certificate_Subject {
timestamp: self.timestamp,
identities: capi_identities.as_mut().map_or(null_mut(), |v| v.as_mut_ptr()),
networks: capi_networks.as_mut().map_or(null_mut(), |v| v.as_mut_ptr()),
updateURLs: capi_urls.as_mut().map_or(null_mut(), |v| v.as_mut_ptr()),
identityCount: identity_count,
networkCount: network_count,
updateURLCount: update_url_count,
name: self.name.to_capi(),
uniqueId: vec_to_array(&self.unique_id),
uniqueIdSignature: vec_to_array(&self.unique_id_signature),
uniqueIdSize: self.unique_id.len() as c_uint,
uniqueIdSignatureSize: self.unique_id_signature.len() as c_uint,
},
subject_identities: capi_identities,
subject_networks: capi_networks,
subject_urls: capi_urls,
subject_urls_strs: capi_urls_strs,
}
}
/// Create a new certificate signing request.
/// A CSR is a Certificate containing only the subject (with optional unique ID and signature)
/// and its private key. Other fields must be filled in by the owner of the signing certificate.
pub fn new_csr(&self, certificate_public_key: &[u8], subject_unique_id_private_key: Option<&[u8]>) -> Result<Vec<u8>, ResultCode> {
let mut csr: Vec<u8> = Vec::new();
csr.resize(65536, 0);
let mut csr_size: c_int = 65536;
let (uid, uid_size) = subject_unique_id_private_key.map_or((null::<u8>(), 0 as c_int), |b| (b.as_ptr(), b.len() as c_int));
let r = unsafe {
let s = self.to_capi();
ztcore::ZT_Certificate_newCSR(&s.subject, certificate_public_key.as_ptr().cast(), certificate_public_key.len() as c_int, uid.cast(), uid_size, csr.as_mut_ptr().cast(), &mut csr_size)
};
if r == 0 {
csr.resize(csr_size as usize, 0);
csr.shrink_to_fit();
Ok(csr)
} else {
Err(ResultCode::from_i32(r as i32).unwrap_or(ResultCode::ErrorInternalNonFatal))
}
}
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#[derive(Serialize, Deserialize, PartialEq, Eq, Clone)]
pub struct Certificate {
#[serde(rename = "serialNo")]
pub serial_no: CertificateSerialNo,
#[serde(rename = "usageFlags")]
pub usage_flags: u64,
pub timestamp: i64,
pub validity: [i64; 2],
pub subject: CertificateSubject,
pub issuer: CertificateSerialNo,
#[serde(rename = "issuerPublicKey")]
pub issuer_public_key: Vec<u8>,
#[serde(rename = "publicKey")]
pub public_key: Vec<u8>,
#[serde(rename = "extendedAttributes")]
pub extended_attributes: Vec<u8>,
#[serde(with = "Base64URLSafeNoPad")]
pub signature: Vec<u8>,
#[serde(rename = "maxPathLength")]
pub max_path_length: u32,
}
#[allow(unused)]
pub(crate) struct CertificateCAPIContainer {
pub(crate) certificate: ztcore::ZT_Certificate,
subject_container: CertificateSubjectCAPIContainer,
}
impl Certificate {
/// Create a new public/private key pair for use in certificate signing or subject unique IDs.
/// This returns a pair of (public, private) or an error. The first byte of both the
/// public and private are the type.
pub fn new_key_pair(alg: CertificatePublicKeyAlgorithm) -> Result<(Vec<u8>, Vec<u8>), ResultCode> {
let mut public_key = [0_u8; ztcore::ZT_CERTIFICATE_MAX_PUBLIC_KEY_SIZE as usize];
let mut private_key = [0_u8; ztcore::ZT_CERTIFICATE_MAX_PRIVATE_KEY_SIZE as usize];
let mut public_key_size: c_int = 0;
let mut private_key_size: c_int = 0;
let r = unsafe { ztcore::ZT_Certificate_newKeyPair(alg.to_i32().unwrap() as ztcore::ZT_CertificatePublicKeyAlgorithm, public_key.as_mut_ptr(), &mut public_key_size, private_key.as_mut_ptr(), &mut private_key_size) };
if r == 0 {
if public_key_size > 0 && private_key_size > 0 {
Ok((public_key[0..public_key_size as usize].to_vec(), private_key[0..private_key_size as usize].to_vec()))
} else {
Err(ResultCode::ErrorInternalNonFatal)
}
} else {
Err(ResultCode::from_i32(r as i32).unwrap_or(ResultCode::ErrorBadParameter))
}
}
/// Create an empty certificate structure.
pub fn new() -> Certificate {
Certificate {
serial_no: CertificateSerialNo::new(),
usage_flags: 0,
timestamp: 0,
validity: [0, i64::MAX],
subject: CertificateSubject::new(),
issuer: CertificateSerialNo::new(),
issuer_public_key: Vec::new(),
public_key: Vec::new(),
extended_attributes: Vec::new(),
max_path_length: 0,
signature: Vec::new(),
}
}
pub(crate) unsafe fn new_from_capi(c: &ztcore::ZT_Certificate) -> Certificate {
return Certificate {
serial_no: CertificateSerialNo(c.serialNo),
usage_flags: c.usageFlags,
timestamp: c.timestamp,
validity: c.validity,
subject: CertificateSubject::new_from_capi(&c.subject),
issuer: CertificateSerialNo(c.issuer),
issuer_public_key: c.issuerPublicKey[0..(c.issuerPublicKeySize as usize)].to_vec(),
public_key: c.publicKey[0..(c.publicKeySize as usize)].to_vec(),
extended_attributes: Vec::from(std::slice::from_raw_parts(c.extendedAttributes, c.extendedAttributesSize as usize)),
max_path_length: c.maxPathLength as u32,
signature: c.signature[0..(c.signatureSize as usize)].to_vec(),
};
}
pub(crate) unsafe fn to_capi(&self) -> CertificateCAPIContainer {
let subject = self.subject.to_capi();
CertificateCAPIContainer {
certificate: ztcore::ZT_Certificate {
serialNo: self.serial_no.0,
usageFlags: self.usage_flags,
timestamp: self.timestamp,
validity: self.validity,
subject: subject.subject,
issuer: self.issuer.0,
issuerPublicKey: vec_to_array(&self.issuer_public_key),
publicKey: vec_to_array(&self.public_key),
issuerPublicKeySize: self.issuer_public_key.len() as c_uint,
publicKeySize: self.public_key.len() as c_uint,
extendedAttributes: self.extended_attributes.as_ptr(),
extendedAttributesSize: self.extended_attributes.len() as c_uint,
maxPathLength: self.max_path_length as c_uint,
signature: vec_to_array(&self.signature),
signatureSize: self.signature.len() as c_uint,
},
subject_container: subject,
}
}
pub fn new_from_bytes(b: &[u8], verify: bool) -> Result<Certificate, CertificateError> {
let mut capi_cert: *const ztcore::ZT_Certificate = null_mut();
let capi_verify: c_int = if verify { 1 } else { 0 };
let result = unsafe { 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_i32(result as i32).unwrap_or(CertificateError::InvalidFormat));
}
if capi_cert.is_null() {
return Err(CertificateError::InvalidFormat);
}
unsafe {
let cert = Certificate::new_from_capi(&*capi_cert);
ztcore::ZT_Certificate_delete(capi_cert);
return Ok(cert);
}
}
pub fn to_bytes(&self) -> Result<Box<[u8]>, ResultCode> {
let mut cert: Vec<u8> = Vec::new();
cert.resize(16384, 0);
let mut cert_size: c_int = 16384;
unsafe {
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());
}
/// Sign this certificate, returning new signed certificate.
pub fn sign(&self, issuer: &CertificateSerialNo, issuer_private_key: &[u8]) -> Result<Certificate, ResultCode> {
let signed_cert = unsafe {
let c = self.to_capi();
ztcore::ZT_Certificate_sign(&c.certificate, issuer.0.as_ptr(), issuer_private_key.as_ptr().cast(), issuer_private_key.len() as c_int)
};
if signed_cert.is_null() {
Err(ResultCode::ErrorBadParameter)
} else {
let signed_cert2 = unsafe { Certificate::new_from_capi(&*signed_cert) };
unsafe { ztcore::ZT_Certificate_delete(signed_cert) };
Ok(signed_cert2)
}
}
/// Verify certificate structure and signatures.
/// This does not verify the full certificate chain, just what can be verified
/// by looking at the certificate itself.
pub fn verify(&self, clock: i64) -> CertificateError {
unsafe {
let capi = self.to_capi();
return CertificateError::from_i32(ztcore::ZT_Certificate_verify(&capi.certificate as *const ztcore::ZT_Certificate, clock) as i32).unwrap_or(CertificateError::InvalidFormat);
}
}
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#[cfg(test)]
mod tests {
use crate::*;
#[test]
fn generate_key_pair() {
let (pubk, privk) = Certificate::new_key_pair(CertificatePublicKeyAlgorithm::ECDSANistP384).ok().unwrap();
println!("key pair public: {}", hex::encode(pubk).as_str());
println!("key pair private: {}", hex::encode(privk).as_str());
}
#[test]
fn cert() {
let (issuer_pubk, issuer_privk) = Certificate::new_key_pair(CertificatePublicKeyAlgorithm::ECDSANistP384).ok().unwrap();
let (pubk, _) = Certificate::new_key_pair(CertificatePublicKeyAlgorithm::ECDSANistP384).ok().unwrap();
let (_, unique_id_private) = Certificate::new_key_pair(CertificatePublicKeyAlgorithm::ECDSANistP384).ok().unwrap();
let id0 = Identity::new_generate(IdentityType::Curve25519).ok().unwrap();
let mut cert = Certificate{
serial_no: CertificateSerialNo::new(),
usage_flags: 1,
timestamp: 2,
validity: [ 1,10 ],
subject: CertificateSubject::new(),
issuer: CertificateSerialNo::new(),
issuer_public_key: issuer_pubk,
public_key: pubk.clone(),
extended_attributes: Vec::new(),
max_path_length: 123,
signature: Vec::new()
};
cert.serial_no.0[1] = 99;
cert.issuer.0[1] = 199;
cert.subject.timestamp = 5;
cert.subject.identities.push(CertificateIdentity{
identity: id0.clone(),
locator: None
});
cert.subject.networks.push(CertificateNetwork{
id: NetworkId(0xdeadbeef),
controller: Some(id0.fingerprint())
});
cert.subject.update_urls.push(String::from("http://foo.bar"));
cert.subject.name = CertificateName{
serial_no: String::from("12345"),
common_name: String::from("foo"),
country: String::from("bar"),
organization: String::from("baz"),
unit: String::from("asdf"),
locality: String::from("qwerty"),
province: String::from("province"),
street_address: String::from("street address"),
postal_code: String::from("postal code"),
email: String::from("nobody@nowhere.org"),
url: String::from("https://www.zerotier.com/"),
host: String::from("zerotier.com")
};
unsafe {
let cert_capi = cert.to_capi();
let cert2 = Certificate::new_from_capi(&cert_capi.certificate);
assert!(cert == cert2);
}
let csr = cert.subject.new_csr(pubk.as_ref(), Some(unique_id_private.as_ref()));
assert!(csr.is_ok());
let csr = csr.ok().unwrap();
let csr_decoded = Certificate::new_from_bytes(csr.as_ref(), false);
assert!(csr_decoded.is_ok());
let mut csr_decoded = csr_decoded.ok().unwrap();
csr_decoded.validity = cert.validity;
let cert_signed = csr_decoded.sign(&cert.issuer, issuer_privk.as_ref());
assert!(cert_signed.is_ok());
let cert_signed = cert_signed.ok().unwrap();
assert!(cert_signed.verify(-1) == CertificateError::None);
assert!(cert_signed.verify(5) == CertificateError::None);
assert!(cert_signed.verify(15) != CertificateError::None);
}
}