797 lines
28 KiB
Rust
Raw Normal View History

use std::sync::Arc;
2025-12-22 21:34:01 +00:00
use async_trait::async_trait;
use chrono::{Duration, Utc};
use serde::{Deserialize, Serialize};
use serde_json::{json, Value};
use super::Engine as SecretEngine;
use crate::core::SealMechanism;
use crate::crypto::KeyAlgorithm;
use crate::error::{Result, VaultError};
use crate::storage::StorageBackend;
/// Certificate metadata for storage
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CertificateMetadata {
pub name: String,
pub certificate_pem: String,
pub private_key_pem: Option<String>, // Only for root CA and issued certs
pub issued_at: String,
pub expires_at: String,
pub common_name: String,
pub subject_alt_names: Vec<String>,
pub key_algorithm: String,
pub revoked: bool,
pub serial_number: String,
}
/// Revocation entry for CRL
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RevocationEntry {
pub serial_number: String,
pub revoked_at: String,
pub reason: String,
}
/// PKI Secrets Engine for X.509 and PQC certificate management
2025-12-22 21:34:01 +00:00
pub struct PkiEngine {
storage: Arc<dyn StorageBackend>,
crypto: Arc<dyn crate::crypto::CryptoBackend>,
2025-12-22 21:34:01 +00:00
seal: Arc<tokio::sync::Mutex<SealMechanism>>,
mount_path: String,
root_ca_name: Arc<tokio::sync::Mutex<Option<String>>>,
revocations: Arc<tokio::sync::Mutex<Vec<RevocationEntry>>>,
}
impl PkiEngine {
/// Create a new PKI engine instance
pub fn new(
storage: Arc<dyn StorageBackend>,
crypto: Arc<dyn crate::crypto::CryptoBackend>,
2025-12-22 21:34:01 +00:00
seal: Arc<tokio::sync::Mutex<SealMechanism>>,
mount_path: String,
) -> Self {
Self {
storage,
crypto,
2025-12-22 21:34:01 +00:00
seal,
mount_path,
root_ca_name: Arc::new(tokio::sync::Mutex::new(None)),
revocations: Arc::new(tokio::sync::Mutex::new(Vec::new())),
}
}
/// Get storage key for certificate
fn cert_storage_key(&self, cert_name: &str) -> String {
format!("{}certs/{}", self.mount_path, cert_name)
}
/// Generate a self-signed root CA certificate
/// Supports classical (RSA/ECDSA via OpenSSL X.509) and post-quantum
/// (ML-DSA-65 via JSON)
2025-12-22 21:34:01 +00:00
pub async fn generate_root_ca(
&self,
name: &str,
key_type: KeyAlgorithm,
2025-12-22 21:34:01 +00:00
ttl_days: i64,
common_name: &str,
) -> Result<CertificateMetadata> {
// Delegate to PQC implementation if ML-DSA-65 requested
#[cfg(feature = "pqc")]
if key_type == KeyAlgorithm::MlDsa65 {
return self.generate_pqc_root_ca(name, ttl_days, common_name).await;
}
// Classical certificate generation with OpenSSL X.509
2025-12-22 21:34:01 +00:00
use openssl::asn1::Asn1Time;
use openssl::bn::BigNum;
use openssl::pkey::PKey;
use openssl::rsa::Rsa;
use openssl::x509::{X509Builder, X509Name};
// Generate RSA keypair (2048-bit)
let rsa = Rsa::generate(2048)
.map_err(|e| VaultError::crypto(format!("Failed to generate RSA key: {}", e)))?;
let pkey = PKey::from_rsa(rsa)
.map_err(|e| VaultError::crypto(format!("Failed to create PKey: {}", e)))?;
// Create X.509 certificate builder
let mut cert_builder = X509Builder::new()
.map_err(|e| VaultError::crypto(format!("Failed to create X509Builder: {}", e)))?;
// Set version (v3)
cert_builder
.set_version(2)
.map_err(|e| VaultError::crypto(format!("Failed to set version: {}", e)))?;
// Set serial number (use timestamp-based)
let serial = Utc::now().timestamp() as u32;
let mut serial_bn = BigNum::new()
.map_err(|e| VaultError::crypto(format!("Failed to create BigNum: {}", e)))?;
serial_bn
.add_word(serial)
.map_err(|e| VaultError::crypto(format!("Failed to add to BigNum: {}", e)))?;
let serial_asn1 = openssl::asn1::Asn1Integer::from_bn(&serial_bn).map_err(|e| {
VaultError::crypto(format!("Failed to convert BigNum to Asn1Integer: {}", e))
})?;
cert_builder
.set_serial_number(&serial_asn1)
.map_err(|e| VaultError::crypto(format!("Failed to set serial number: {}", e)))?;
// Set subject name
let mut subject = X509Name::builder()
.map_err(|e| VaultError::crypto(format!("Failed to create X509Name builder: {}", e)))?;
subject
.append_entry_by_text("CN", common_name)
.map_err(|e| VaultError::crypto(format!("Failed to set CN: {}", e)))?;
let subject_name = subject.build();
cert_builder
.set_subject_name(&subject_name)
.map_err(|e| VaultError::crypto(format!("Failed to set subject: {}", e)))?;
// Set issuer (self-signed, same as subject)
cert_builder
.set_issuer_name(&subject_name)
.map_err(|e| VaultError::crypto(format!("Failed to set issuer: {}", e)))?;
// Set validity period
let not_before = Asn1Time::days_from_now(0)
.map_err(|e| VaultError::crypto(format!("Failed to set not_before: {}", e)))?;
let not_after = Asn1Time::days_from_now(ttl_days as u32)
.map_err(|e| VaultError::crypto(format!("Failed to set not_after: {}", e)))?;
cert_builder
.set_not_before(&not_before)
.map_err(|e| VaultError::crypto(format!("Failed to set not_before: {}", e)))?;
cert_builder
.set_not_after(&not_after)
.map_err(|e| VaultError::crypto(format!("Failed to set not_after: {}", e)))?;
// Set public key
cert_builder
.set_pubkey(&pkey)
.map_err(|e| VaultError::crypto(format!("Failed to set pubkey: {}", e)))?;
// Self-sign the certificate
cert_builder
.sign(&pkey, openssl::hash::MessageDigest::sha256())
.map_err(|e| VaultError::crypto(format!("Failed to sign certificate: {}", e)))?;
let cert = cert_builder.build();
// Convert certificate to PEM
let cert_pem =
String::from_utf8(cert.to_pem().map_err(|e| {
VaultError::crypto(format!("Failed to convert cert to PEM: {}", e))
})?)
.map_err(|e| VaultError::crypto(format!("Failed to convert PEM to string: {}", e)))?;
// Convert private key to PEM
let privkey_pem = String::from_utf8(
pkey.private_key_to_pem_pkcs8()
.map_err(|e| VaultError::crypto(format!("Failed to convert key to PEM: {}", e)))?,
)
.map_err(|e| VaultError::crypto(format!("Failed to convert key PEM to string: {}", e)))?;
let now = Utc::now();
let expires_at = now + Duration::days(ttl_days);
let metadata = CertificateMetadata {
name: name.to_string(),
certificate_pem: cert_pem.clone(),
private_key_pem: Some(privkey_pem),
issued_at: now.to_rfc3339(),
expires_at: expires_at.to_rfc3339(),
common_name: common_name.to_string(),
subject_alt_names: vec![],
key_algorithm: "RSA-2048".to_string(),
revoked: false,
serial_number: serial.to_string(),
};
// Store certificate
let storage_key = self.cert_storage_key(name);
let metadata_json = serde_json::to_vec(&metadata)
.map_err(|e| VaultError::storage(format!("Failed to serialize metadata: {}", e)))?;
self.storage
.store_secret(
&storage_key,
&crate::storage::EncryptedData {
ciphertext: metadata_json,
nonce: vec![],
algorithm: "aes-256-gcm".to_string(),
},
)
.await
.map_err(|e| VaultError::storage(e.to_string()))?;
// Update root CA name
let mut root_ca = self.root_ca_name.lock().await;
*root_ca = Some(name.to_string());
Ok(metadata)
}
/// Generate a post-quantum root CA certificate with ML-DSA-65
/// Uses SecretumVault-specific JSON format (not X.509 PEM) since ML-DSA is
/// not yet in X.509 standard
#[cfg(feature = "pqc")]
async fn generate_pqc_root_ca(
&self,
name: &str,
ttl_days: i64,
common_name: &str,
) -> Result<CertificateMetadata> {
// Generate ML-DSA-65 keypair
let keypair = self
.crypto
.generate_keypair(KeyAlgorithm::MlDsa65)
.await
.map_err(|e| VaultError::crypto(format!("ML-DSA-65 key generation failed: {}", e)))?;
let now = Utc::now();
let expires_at = now + Duration::days(ttl_days);
let serial = now.timestamp() as u32;
use base64::Engine;
// Encode certificate as JSON (not X.509 since ML-DSA is not standardized yet)
let cert_json = json!({
"version": "SecretumVault-PQC-v1",
"algorithm": "ML-DSA-65",
"public_key": base64::engine::general_purpose::STANDARD.encode(&keypair.public_key.key_data),
"common_name": common_name,
"issued_at": now.to_rfc3339(),
"expires_at": expires_at.to_rfc3339(),
"serial_number": serial.to_string(),
"is_ca": true,
});
let cert_pem = format!(
"-----BEGIN SECRETUMVAULT PQC CERTIFICATE-----\n{}\n-----END SECRETUMVAULT PQC \
CERTIFICATE-----",
base64::engine::general_purpose::STANDARD.encode(cert_json.to_string().as_bytes())
);
// Encode private key
let privkey_pem = format!(
"-----BEGIN SECRETUMVAULT PQC PRIVATE KEY-----\n{}\n-----END SECRETUMVAULT PQC \
PRIVATE KEY-----",
base64::engine::general_purpose::STANDARD.encode(&keypair.private_key.key_data)
);
let metadata = CertificateMetadata {
name: name.to_string(),
certificate_pem: cert_pem,
private_key_pem: Some(privkey_pem),
issued_at: now.to_rfc3339(),
expires_at: expires_at.to_rfc3339(),
common_name: common_name.to_string(),
subject_alt_names: vec![],
key_algorithm: "ML-DSA-65".to_string(),
revoked: false,
serial_number: serial.to_string(),
};
// Store certificate
let storage_key = self.cert_storage_key(name);
let metadata_json = serde_json::to_vec(&metadata)
.map_err(|e| VaultError::storage(format!("Failed to serialize metadata: {}", e)))?;
self.storage
.store_secret(
&storage_key,
&crate::storage::EncryptedData {
ciphertext: metadata_json,
nonce: vec![],
algorithm: "aes-256-gcm".to_string(),
},
)
.await
.map_err(|e| VaultError::storage(e.to_string()))?;
2025-12-22 21:34:01 +00:00
// Update root CA name
let mut root_ca = self.root_ca_name.lock().await;
*root_ca = Some(name.to_string());
Ok(metadata)
}
/// Issue a certificate signed by the root CA
pub async fn issue_certificate(
&self,
name: &str,
common_name: &str,
subject_alt_names: Vec<String>,
ttl_days: i64,
) -> Result<CertificateMetadata> {
use openssl::asn1::Asn1Time;
use openssl::pkey::PKey;
use openssl::rsa::Rsa;
use openssl::x509::X509Builder;
// Get root CA
let root_ca_name = self.root_ca_name.lock().await;
let ca_name = root_ca_name
.as_ref()
.ok_or_else(|| VaultError::crypto("Root CA not configured".to_string()))?
.clone();
drop(root_ca_name);
let root_cert_key = self.cert_storage_key(&ca_name);
let root_cert_data = self
.storage
.get_secret(&root_cert_key)
.await
.map_err(|e| VaultError::storage(format!("Failed to get root CA: {}", e)))?;
let root_metadata: CertificateMetadata = serde_json::from_slice(&root_cert_data.ciphertext)
.map_err(|e| VaultError::storage(format!("Failed to parse root CA: {}", e)))?;
// Generate RSA keypair for the new certificate
let rsa = Rsa::generate(2048)
.map_err(|e| VaultError::crypto(format!("Failed to generate RSA key: {}", e)))?;
let pkey = PKey::from_rsa(rsa)
.map_err(|e| VaultError::crypto(format!("Failed to create PKey: {}", e)))?;
// Create certificate builder
use openssl::bn::BigNum;
use openssl::x509::X509Name;
let mut cert_builder = X509Builder::new()
.map_err(|e| VaultError::crypto(format!("Failed to create X509Builder: {}", e)))?;
// Set version (v3)
cert_builder
.set_version(2)
.map_err(|e| VaultError::crypto(format!("Failed to set version: {}", e)))?;
// Set serial number
let serial = Utc::now().timestamp() as u32;
let mut serial_bn = BigNum::new()
.map_err(|e| VaultError::crypto(format!("Failed to create BigNum: {}", e)))?;
serial_bn
.add_word(serial)
.map_err(|e| VaultError::crypto(format!("Failed to add to BigNum: {}", e)))?;
let serial_asn1 = openssl::asn1::Asn1Integer::from_bn(&serial_bn).map_err(|e| {
VaultError::crypto(format!("Failed to convert BigNum to Asn1Integer: {}", e))
})?;
cert_builder
.set_serial_number(&serial_asn1)
.map_err(|e| VaultError::crypto(format!("Failed to set serial number: {}", e)))?;
// Set subject
let mut subject = X509Name::builder()
.map_err(|e| VaultError::crypto(format!("Failed to create X509Name builder: {}", e)))?;
subject
.append_entry_by_text("CN", common_name)
.map_err(|e| VaultError::crypto(format!("Failed to set CN: {}", e)))?;
let subject_name = subject.build();
cert_builder
.set_subject_name(&subject_name)
.map_err(|e| VaultError::crypto(format!("Failed to set subject: {}", e)))?;
// Parse root CA certificate for issuer
let root_cert_pem = root_metadata.certificate_pem.as_bytes();
let root_x509 = openssl::x509::X509::from_pem(root_cert_pem)
.map_err(|e| VaultError::crypto(format!("Failed to parse root cert: {}", e)))?;
let issuer = root_x509.issuer_name();
cert_builder
.set_issuer_name(issuer)
.map_err(|e| VaultError::crypto(format!("Failed to set issuer: {}", e)))?;
// Set validity period
let not_before = Asn1Time::days_from_now(0)
.map_err(|e| VaultError::crypto(format!("Failed to set not_before: {}", e)))?;
let not_after = Asn1Time::days_from_now(ttl_days as u32)
.map_err(|e| VaultError::crypto(format!("Failed to set not_after: {}", e)))?;
cert_builder
.set_not_before(&not_before)
.map_err(|e| VaultError::crypto(format!("Failed to set not_before: {}", e)))?;
cert_builder
.set_not_after(&not_after)
.map_err(|e| VaultError::crypto(format!("Failed to set not_after: {}", e)))?;
// Set public key
cert_builder
.set_pubkey(&pkey)
.map_err(|e| VaultError::crypto(format!("Failed to set pubkey: {}", e)))?;
// Sign with root CA private key
let root_privkey_pem = root_metadata
.private_key_pem
.ok_or_else(|| VaultError::crypto("Root CA has no private key".to_string()))?;
let root_privkey =
openssl::pkey::PKey::private_key_from_pem(root_privkey_pem.as_bytes())
.map_err(|e| VaultError::crypto(format!("Failed to parse root CA key: {}", e)))?;
cert_builder
.sign(&root_privkey, openssl::hash::MessageDigest::sha256())
.map_err(|e| VaultError::crypto(format!("Failed to sign certificate: {}", e)))?;
let cert = cert_builder.build();
// Convert to PEM
let cert_pem =
String::from_utf8(cert.to_pem().map_err(|e| {
VaultError::crypto(format!("Failed to convert cert to PEM: {}", e))
})?)
.map_err(|e| VaultError::crypto(format!("Failed to convert PEM to string: {}", e)))?;
let privkey_pem = String::from_utf8(
pkey.private_key_to_pem_pkcs8()
.map_err(|e| VaultError::crypto(format!("Failed to convert key to PEM: {}", e)))?,
)
.map_err(|e| VaultError::crypto(format!("Failed to convert key PEM to string: {}", e)))?;
let now = Utc::now();
let expires_at = now + Duration::days(ttl_days);
let metadata = CertificateMetadata {
name: name.to_string(),
certificate_pem: cert_pem.clone(),
private_key_pem: Some(privkey_pem),
issued_at: now.to_rfc3339(),
expires_at: expires_at.to_rfc3339(),
common_name: common_name.to_string(),
subject_alt_names,
key_algorithm: "RSA-2048".to_string(),
revoked: false,
serial_number: serial.to_string(),
};
// Store certificate
let storage_key = self.cert_storage_key(name);
let metadata_json = serde_json::to_vec(&metadata)
.map_err(|e| VaultError::storage(format!("Failed to serialize metadata: {}", e)))?;
self.storage
.store_secret(
&storage_key,
&crate::storage::EncryptedData {
ciphertext: metadata_json,
nonce: vec![],
algorithm: "aes-256-gcm".to_string(),
},
)
.await
.map_err(|e| VaultError::storage(e.to_string()))?;
Ok(metadata)
}
/// Revoke a certificate
pub async fn revoke_certificate(&self, name: &str, reason: &str) -> Result<()> {
let storage_key = self.cert_storage_key(name);
// Get the certificate
let cert_data = self
.storage
.get_secret(&storage_key)
.await
.map_err(|e| VaultError::storage(format!("Certificate not found: {}", e)))?;
let mut metadata: CertificateMetadata = serde_json::from_slice(&cert_data.ciphertext)
.map_err(|e| VaultError::storage(format!("Failed to parse certificate: {}", e)))?;
// Mark as revoked
metadata.revoked = true;
// Update storage
let metadata_json = serde_json::to_vec(&metadata)
.map_err(|e| VaultError::storage(format!("Failed to serialize metadata: {}", e)))?;
self.storage
.store_secret(
&storage_key,
&crate::storage::EncryptedData {
ciphertext: metadata_json,
nonce: vec![],
algorithm: "aes-256-gcm".to_string(),
},
)
.await
.map_err(|e| VaultError::storage(e.to_string()))?;
// Add to revocation list
let mut revocations = self.revocations.lock().await;
revocations.push(RevocationEntry {
serial_number: metadata.serial_number.clone(),
revoked_at: Utc::now().to_rfc3339(),
reason: reason.to_string(),
});
Ok(())
}
/// Read certificate metadata
pub async fn read_certificate(&self, name: &str) -> Result<Option<CertificateMetadata>> {
let storage_key = self.cert_storage_key(name);
match self.storage.get_secret(&storage_key).await {
Ok(cert_data) => {
let metadata: CertificateMetadata = serde_json::from_slice(&cert_data.ciphertext)
.map_err(|e| {
VaultError::storage(format!("Failed to parse certificate: {}", e))
})?;
Ok(Some(metadata))
}
Err(_) => Ok(None),
}
}
}
#[async_trait]
impl SecretEngine for PkiEngine {
fn name(&self) -> &str {
"pki"
}
fn engine_type(&self) -> &str {
"pki"
}
async fn read(&self, path: &str) -> Result<Option<Value>> {
if let Some(cert_name) = path.strip_prefix("certs/") {
match self.read_certificate(cert_name).await? {
Some(cert) => Ok(Some(json!({
"name": cert.name,
"common_name": cert.common_name,
"certificate": cert.certificate_pem,
"issued_at": cert.issued_at,
"expires_at": cert.expires_at,
"serial_number": cert.serial_number,
"revoked": cert.revoked,
}))),
None => Ok(None),
}
} else {
Ok(None)
}
}
async fn write(&self, path: &str, data: &Value) -> Result<()> {
if let Some(cert_name) = path.strip_prefix("issue/") {
let common_name = data
.get("common_name")
.and_then(|v| v.as_str())
.ok_or_else(|| VaultError::storage("Missing common_name".to_string()))?;
let subject_alt_names: Vec<String> = data
.get("subject_alt_names")
.and_then(|v| v.as_array())
.map(|arr| {
arr.iter()
.filter_map(|v| v.as_str().map(|s| s.to_string()))
.collect()
})
.unwrap_or_default();
let ttl_days = data.get("ttl_days").and_then(|v| v.as_u64()).unwrap_or(365) as i64;
let _cert = self
.issue_certificate(cert_name, common_name, subject_alt_names, ttl_days)
.await?;
} else if let Some(ca_name) = path.strip_prefix("root/") {
let common_name = data
.get("common_name")
.and_then(|v| v.as_str())
.ok_or_else(|| VaultError::storage("Missing common_name".to_string()))?;
let ttl_days = data
.get("ttl_days")
.and_then(|v| v.as_u64())
.unwrap_or(3650) as i64;
let _cert = self
.generate_root_ca(
ca_name,
crate::crypto::KeyAlgorithm::Rsa2048,
ttl_days,
common_name,
)
.await?;
}
Ok(())
}
async fn delete(&self, path: &str) -> Result<()> {
if let Some(cert_name) = path.strip_prefix("certs/") {
let reason = "Manual revocation".to_string();
self.revoke_certificate(cert_name, &reason).await?;
}
Ok(())
}
async fn list(&self, prefix: &str) -> Result<Vec<String>> {
// List all certificates with given prefix from storage
let storage_prefix = format!("{}certs/", self.mount_path);
let all_certs = self
.storage
.list_secrets(&storage_prefix)
.await
.map_err(|e| VaultError::storage(e.to_string()))?;
let filtered: Vec<String> = all_certs
.iter()
.filter(|cert| cert.starts_with(prefix))
.map(|cert| cert.to_string())
.collect();
Ok(filtered)
}
async fn health_check(&self) -> Result<()> {
self.storage
.health_check()
.await
.map_err(|e| VaultError::storage(e.to_string()))?;
let seal = self.seal.lock().await;
if seal.is_sealed() {
return Err(VaultError::crypto("Vault is sealed".to_string()));
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use tempfile::TempDir;
2025-12-22 21:34:01 +00:00
use super::*;
use crate::config::{FilesystemStorageConfig, SealConfig, ShamirSealConfig, StorageConfig};
use crate::crypto::CryptoRegistry;
use crate::storage::StorageRegistry;
async fn setup_engine() -> Result<(PkiEngine, TempDir)> {
let temp_dir = TempDir::new().map_err(|e| VaultError::storage(e.to_string()))?;
let fs_config = FilesystemStorageConfig {
path: temp_dir.path().to_path_buf(),
};
let storage_config = StorageConfig {
backend: "filesystem".to_string(),
filesystem: fs_config,
surrealdb: Default::default(),
etcd: Default::default(),
postgresql: Default::default(),
};
let storage = StorageRegistry::create(&storage_config).await?;
let crypto = CryptoRegistry::create("openssl", &Default::default())?;
let seal_config = SealConfig {
seal_type: "shamir".to_string(),
shamir: ShamirSealConfig {
threshold: 2,
shares: 3,
},
auto_unseal: Default::default(),
};
let mut seal = crate::core::SealMechanism::new(&seal_config)?;
let _init_result = seal.init(crypto.as_ref(), storage.as_ref()).await?;
let seal_arc = Arc::new(tokio::sync::Mutex::new(seal));
let engine = PkiEngine::new(storage, crypto.clone(), seal_arc, "pki/".to_string());
Ok((engine, temp_dir))
}
#[tokio::test]
async fn test_pki_engine_creation() -> Result<()> {
let (_engine, _temp) = setup_engine().await?;
Ok(())
}
#[tokio::test]
async fn test_generate_root_ca() -> Result<()> {
let (engine, _temp) = setup_engine().await?;
let cert = engine
.generate_root_ca("root-ca", KeyAlgorithm::Rsa2048, 3650, "example.com")
.await?;
assert_eq!(cert.name, "root-ca");
assert_eq!(cert.common_name, "example.com");
assert!(cert.certificate_pem.contains("BEGIN CERTIFICATE"));
assert!(cert.private_key_pem.is_some());
Ok(())
}
#[tokio::test]
async fn test_issue_certificate() -> Result<()> {
let (engine, _temp) = setup_engine().await?;
// Generate root CA first
let _root_cert = engine
.generate_root_ca("root-ca", KeyAlgorithm::Rsa2048, 3650, "example.com")
.await?;
// Issue a certificate
let cert = engine
.issue_certificate(
"server-cert",
"server.example.com",
vec!["www.example.com".to_string()],
365,
)
.await?;
assert_eq!(cert.name, "server-cert");
assert_eq!(cert.common_name, "server.example.com");
assert_eq!(cert.subject_alt_names, vec!["www.example.com"]);
assert!(cert.certificate_pem.contains("BEGIN CERTIFICATE"));
assert!(!cert.revoked);
Ok(())
}
#[tokio::test]
async fn test_revoke_certificate() -> Result<()> {
let (engine, _temp) = setup_engine().await?;
// Generate root CA and issue cert
let _root_cert = engine
.generate_root_ca("root-ca", KeyAlgorithm::Rsa2048, 3650, "example.com")
.await?;
let _cert = engine
.issue_certificate("server-cert", "server.example.com", vec![], 365)
.await?;
// Revoke the certificate
engine
.revoke_certificate("server-cert", "Test revocation")
.await?;
// Read it back and verify revocation
let revoked = engine.read_certificate("server-cert").await?;
assert!(revoked.is_some());
assert!(revoked.unwrap().revoked);
Ok(())
}
#[tokio::test]
async fn test_read_certificate() -> Result<()> {
let (engine, _temp) = setup_engine().await?;
let root = engine
.generate_root_ca("root-ca", KeyAlgorithm::Rsa2048, 3650, "example.com")
.await?;
let read_result = engine.read_certificate("root-ca").await?;
assert!(read_result.is_some());
assert_eq!(read_result.unwrap().name, root.name);
Ok(())
}
#[tokio::test]
async fn test_pki_health_check() -> Result<()> {
let (engine, _temp) = setup_engine().await?;
engine.health_check().await?;
Ok(())
}
}