use crate::error::{ZitiError, ZitiResult};
use crate::identity::config::Config;
use rustls::RootCertStore;
use rustls::pki_types::{CertificateDer, PrivateKeyDer};
use rustls_pemfile::{certs, private_key};
use std::io::BufReader;
use std::path::Path;
use tokio::fs;
#[derive(Debug)]
pub struct Credentials {
pub certificate_chain: Vec<CertificateDer<'static>>,
pub private_key: PrivateKeyDer<'static>,
pub ca_store: RootCertStore,
}
impl Clone for Credentials {
fn clone(&self) -> Self {
Self {
certificate_chain: self.certificate_chain.clone(),
private_key: self.private_key.clone_key(),
ca_store: self.ca_store.clone(),
}
}
}
impl Credentials {
pub fn new(
certificate_chain: Vec<CertificateDer<'static>>,
private_key: PrivateKeyDer<'static>,
ca_store: RootCertStore,
) -> Self {
Self {
certificate_chain,
private_key,
ca_store,
}
}
pub async fn load_from_config(config: &Config) -> ZitiResult<Self> {
let cert_chain = Self::load_certificates(&config.cert_path()).await?;
let private_key = Self::load_private_key(&config.key_path()).await?;
let ca_store = Self::load_ca_certificates(&config.ca_path()).await?;
Ok(Self::new(cert_chain, private_key, ca_store))
}
async fn load_certificates(cert_path: &Path) -> ZitiResult<Vec<CertificateDer<'static>>> {
let cert_file = fs::File::open(cert_path).await.map_err(|e| {
ZitiError::ConfigError(format!(
"Failed to open certificate file {:?}: {}",
cert_path, e
))
})?;
let cert_file = cert_file.into_std().await;
let mut cert_reader = BufReader::new(cert_file);
let cert_chain = certs(&mut cert_reader)
.collect::<Result<Vec<_>, _>>()
.map_err(|e| {
ZitiError::ConfigError(format!(
"Failed to parse certificate file {:?}: {}",
cert_path, e
))
})?;
if cert_chain.is_empty() {
return Err(ZitiError::ConfigError(format!(
"No certificates found in file {:?}",
cert_path
)));
}
Ok(cert_chain)
}
async fn load_private_key(key_path: &Path) -> ZitiResult<PrivateKeyDer<'static>> {
let key_file = fs::File::open(key_path).await.map_err(|e| {
ZitiError::ConfigError(format!(
"Failed to open private key file {:?}: {}",
key_path, e
))
})?;
let key_file = key_file.into_std().await;
let mut key_reader = BufReader::new(key_file);
private_key(&mut key_reader)
.map_err(|e| {
ZitiError::ConfigError(format!(
"Failed to parse private key file {:?}: {}",
key_path, e
))
})?
.ok_or_else(|| {
ZitiError::ConfigError(format!("No private keys found in file {:?}", key_path))
})
}
async fn load_ca_certificates(ca_path: &Path) -> ZitiResult<RootCertStore> {
let ca_file = fs::File::open(ca_path).await.map_err(|e| {
ZitiError::ConfigError(format!(
"Failed to open CA certificate file {:?}: {}",
ca_path, e
))
})?;
let ca_file = ca_file.into_std().await;
let mut ca_reader = BufReader::new(ca_file);
let ca_certs = certs(&mut ca_reader)
.collect::<Result<Vec<_>, _>>()
.map_err(|e| {
ZitiError::ConfigError(format!(
"Failed to parse CA certificate file {:?}: {}",
ca_path, e
))
})?;
if ca_certs.is_empty() {
return Err(ZitiError::ConfigError(format!(
"No CA certificates found in file {:?}",
ca_path
)));
}
let mut ca_store = RootCertStore::empty();
for cert in ca_certs {
ca_store.add(cert).map_err(|e| {
ZitiError::ConfigError(format!("Failed to add CA certificate: {}", e))
})?;
}
Ok(ca_store)
}
}
#[derive(Debug)]
pub struct Identity {
pub id: String,
pub certificate_chain: Vec<CertificateDer<'static>>,
pub private_key: PrivateKeyDer<'static>,
pub ca_store: RootCertStore,
}
impl Clone for Identity {
fn clone(&self) -> Self {
Self {
id: self.id.clone(),
certificate_chain: self.certificate_chain.clone(),
private_key: self.private_key.clone_key(),
ca_store: self.ca_store.clone(),
}
}
}
impl Identity {
pub fn new(
id: String,
certificate_chain: Vec<CertificateDer<'static>>,
private_key: PrivateKeyDer<'static>,
ca_store: RootCertStore,
) -> Self {
Self {
id,
certificate_chain,
private_key,
ca_store,
}
}
pub fn from_config_and_credentials(config: &Config, credentials: Credentials) -> Self {
Self {
id: config.id.clone(),
certificate_chain: credentials.certificate_chain,
private_key: credentials.private_key,
ca_store: credentials.ca_store,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use rustls::RootCertStore;
use rustls::pki_types::{CertificateDer, PrivateKeyDer, PrivatePkcs8KeyDer};
fn create_mock_certificate() -> CertificateDer<'static> {
CertificateDer::from(vec![0u8; 32]) }
fn create_mock_private_key() -> PrivateKeyDer<'static> {
PrivateKeyDer::Pkcs8(PrivatePkcs8KeyDer::from(vec![0u8; 32])) }
fn create_mock_ca_store() -> RootCertStore {
RootCertStore::empty()
}
#[test]
fn test_credentials_new() {
let cert_chain = vec![create_mock_certificate()];
let private_key = create_mock_private_key();
let ca_store = create_mock_ca_store();
let credentials = Credentials::new(cert_chain.clone(), private_key.clone_key(), ca_store);
assert_eq!(credentials.certificate_chain.len(), 1);
assert_eq!(credentials.private_key.secret_der(), private_key.secret_der());
}
#[test]
fn test_credentials_clone() {
let cert_chain = vec![create_mock_certificate()];
let private_key = create_mock_private_key();
let ca_store = create_mock_ca_store();
let original = Credentials::new(cert_chain, private_key, ca_store);
let cloned = original.clone();
assert_eq!(cloned.certificate_chain.len(), original.certificate_chain.len());
assert_eq!(cloned.private_key.secret_der(), original.private_key.secret_der());
}
#[test]
fn test_identity_new() {
let id = "test-identity".to_string();
let cert_chain = vec![create_mock_certificate()];
let private_key = create_mock_private_key();
let ca_store = create_mock_ca_store();
let identity = Identity::new(id.clone(), cert_chain.clone(), private_key.clone_key(), ca_store);
assert_eq!(identity.id, id);
assert_eq!(identity.certificate_chain.len(), 1);
assert_eq!(identity.private_key.secret_der(), private_key.secret_der());
}
#[test]
fn test_identity_clone() {
let id = "test-identity".to_string();
let cert_chain = vec![create_mock_certificate()];
let private_key = create_mock_private_key();
let ca_store = create_mock_ca_store();
let original = Identity::new(id, cert_chain, private_key, ca_store);
let cloned = original.clone();
assert_eq!(cloned.id, original.id);
assert_eq!(cloned.certificate_chain.len(), original.certificate_chain.len());
assert_eq!(cloned.private_key.secret_der(), original.private_key.secret_der());
}
#[test]
fn test_identity_from_config_and_credentials() {
let config = Config::new(
"https://controller.example.com".to_string(),
"config-id".to_string(),
"cert.pem".to_string(),
"key.pem".to_string(),
"ca.pem".to_string(),
);
let cert_chain = vec![create_mock_certificate()];
let private_key = create_mock_private_key();
let ca_store = create_mock_ca_store();
let credentials = Credentials::new(cert_chain.clone(), private_key.clone_key(), ca_store);
let identity = Identity::from_config_and_credentials(&config, credentials);
assert_eq!(identity.id, "config-id");
assert_eq!(identity.certificate_chain.len(), 1);
assert_eq!(identity.private_key.secret_der(), private_key.secret_der());
}
#[test]
fn test_credentials_with_multiple_certificates() {
let cert_chain = vec![
create_mock_certificate(),
create_mock_certificate(),
create_mock_certificate(),
];
let private_key = create_mock_private_key();
let ca_store = create_mock_ca_store();
let credentials = Credentials::new(cert_chain, private_key, ca_store);
assert_eq!(credentials.certificate_chain.len(), 3);
}
#[test]
fn test_identity_with_empty_certificate_chain() {
let id = "test-identity".to_string();
let cert_chain = vec![]; let private_key = create_mock_private_key();
let ca_store = create_mock_ca_store();
let identity = Identity::new(id.clone(), cert_chain, private_key, ca_store);
assert_eq!(identity.id, id);
assert_eq!(identity.certificate_chain.len(), 0);
}
}