use std::collections::HashMap;
use crate::db::{RocksDb, cf};
use crate::models::{TlsCertMetadata, TlsProfile};
use crate::tls_envelope::TlsEnvelope;
use blueprint_std::Rng;
use prost::Message;
use tracing::error;
use x509_parser::prelude::{FromDer, X509Certificate};
#[derive(Clone, Debug)]
pub struct TlsAssetManager {
db: RocksDb,
tls_envelope: TlsEnvelope,
}
impl TlsAssetManager {
pub fn new(db: RocksDb, tls_envelope: TlsEnvelope) -> Self {
Self { db, tls_envelope }
}
pub fn store_tls_profile(
&self,
service_id: u64,
profile: TlsProfile,
) -> Result<(), crate::Error> {
let key = format!("tls_profile:{service_id}");
let value = profile.encode_to_vec();
let cf_handle = self.db.cf_handle(cf::TLS_ASSETS_CF).unwrap();
self.db.put_cf(&cf_handle, key.as_bytes(), &value)?;
Ok(())
}
pub fn get_tls_profile(&self, service_id: u64) -> Result<Option<TlsProfile>, crate::Error> {
let key = format!("tls_profile:{service_id}");
let cf_handle = self.db.cf_handle(cf::TLS_ASSETS_CF).unwrap();
match self.db.get_cf(&cf_handle, key.as_bytes())? {
Some(value) => {
let profile = TlsProfile::decode(&value[..])?;
Ok(Some(profile))
}
None => Ok(None),
}
}
pub fn delete_tls_profile(&self, service_id: u64) -> Result<(), crate::Error> {
let key = format!("tls_profile:{service_id}");
let cf_handle = self.db.cf_handle(cf::TLS_ASSETS_CF).unwrap();
self.db.delete_cf(&cf_handle, key.as_bytes())?;
Ok(())
}
pub fn store_cert_metadata(
&self,
cert_id: &str,
metadata: TlsCertMetadata,
) -> Result<(), crate::Error> {
let key = format!("cert_metadata:{cert_id}");
let value = metadata.encode_to_vec();
let cf_handle = self.db.cf_handle(cf::TLS_CERT_METADATA_CF).unwrap();
self.db.put_cf(&cf_handle, key.as_bytes(), &value)?;
Ok(())
}
pub fn get_cert_metadata(
&self,
cert_id: &str,
) -> Result<Option<TlsCertMetadata>, crate::Error> {
let key = format!("cert_metadata:{cert_id}");
let cf_handle = self.db.cf_handle(cf::TLS_CERT_METADATA_CF).unwrap();
match self.db.get_cf(&cf_handle, key.as_bytes())? {
Some(value) => {
let metadata = TlsCertMetadata::decode(&value[..])?;
Ok(Some(metadata))
}
None => Ok(None),
}
}
pub fn list_cert_metadata(
&self,
service_id: u64,
) -> Result<Vec<TlsCertMetadata>, crate::Error> {
let prefix = format!("cert_metadata:service:{service_id}:");
let mut result = Vec::new();
let cf_handle = self.db.cf_handle(cf::TLS_CERT_METADATA_CF).unwrap();
for item in self.db.prefix_iterator_cf(&cf_handle, prefix.as_bytes()) {
let (_key, value) = item?;
let metadata = TlsCertMetadata::decode(&value[..])?;
result.push(metadata);
}
Ok(result)
}
pub fn store_encrypted_cert(
&self,
cert_id: &str,
cert_data: &[u8],
) -> Result<(), crate::Error> {
let key = format!("cert_data:{cert_id}");
let encrypted_data = self.tls_envelope.encrypt(cert_data)?;
let cf_handle = self.db.cf_handle(cf::TLS_ASSETS_CF).unwrap();
self.db
.put_cf(&cf_handle, key.as_bytes(), &encrypted_data)?;
Ok(())
}
pub fn get_encrypted_cert(&self, cert_id: &str) -> Result<Option<Vec<u8>>, crate::Error> {
let key = format!("cert_data:{cert_id}");
let cf_handle = self.db.cf_handle(cf::TLS_ASSETS_CF).unwrap();
match self.db.get_cf(&cf_handle, key.as_bytes())? {
Some(encrypted_data) => {
let cert_data = self.tls_envelope.decrypt(&encrypted_data)?;
Ok(Some(cert_data))
}
None => Ok(None),
}
}
pub fn store_encrypted_private_key(
&self,
key_id: &str,
key_data: &[u8],
) -> Result<(), crate::Error> {
let key = format!("private_key_data:{key_id}");
let encrypted_data = self.tls_envelope.encrypt(key_data)?;
let cf_handle = self.db.cf_handle(cf::TLS_ASSETS_CF).unwrap();
self.db
.put_cf(&cf_handle, key.as_bytes(), &encrypted_data)?;
Ok(())
}
pub fn get_encrypted_private_key(&self, key_id: &str) -> Result<Option<Vec<u8>>, crate::Error> {
let key = format!("private_key_data:{key_id}");
let cf_handle = self.db.cf_handle(cf::TLS_ASSETS_CF).unwrap();
match self.db.get_cf(&cf_handle, key.as_bytes())? {
Some(encrypted_data) => {
let key_data = self.tls_envelope.decrypt(&encrypted_data)?;
Ok(Some(key_data))
}
None => Ok(None),
}
}
pub fn store_encrypted_ca_bundle(
&self,
bundle_id: &str,
bundle_data: &[u8],
) -> Result<(), crate::Error> {
let key = format!("ca_bundle_data:{bundle_id}");
let encrypted_data = self.tls_envelope.encrypt(bundle_data)?;
let cf_handle = self.db.cf_handle(cf::TLS_ASSETS_CF).unwrap();
self.db
.put_cf(&cf_handle, key.as_bytes(), &encrypted_data)?;
Ok(())
}
pub fn get_encrypted_ca_bundle(
&self,
bundle_id: &str,
) -> Result<Option<Vec<u8>>, crate::Error> {
let key = format!("ca_bundle_data:{bundle_id}");
let cf_handle = self.db.cf_handle(cf::TLS_ASSETS_CF).unwrap();
match self.db.get_cf(&cf_handle, key.as_bytes())? {
Some(encrypted_data) => {
let bundle_data = self.tls_envelope.decrypt(&encrypted_data)?;
Ok(Some(bundle_data))
}
None => Ok(None),
}
}
pub fn log_cert_issuance(&self, log_entry: &str) -> Result<(), crate::Error> {
let timestamp = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_secs();
let key = format!("issuance_log:{timestamp}");
let value = log_entry.as_bytes();
let cf_handle = self.db.cf_handle(cf::TLS_ISSUANCE_LOG_CF).unwrap();
self.db.put_cf(&cf_handle, key.as_bytes(), value)?;
Ok(())
}
pub fn get_issuance_logs(
&self,
start_time: u64,
end_time: u64,
) -> Result<Vec<String>, crate::Error> {
let mut result = Vec::new();
let cf_handle = self.db.cf_handle(cf::TLS_ISSUANCE_LOG_CF).unwrap();
for item in self.db.prefix_iterator_cf(&cf_handle, b"issuance_log:") {
let (key, value) = item?;
let timestamp_str = String::from_utf8_lossy(&key[13..]); if let Ok(timestamp) = timestamp_str.parse::<u64>() {
if timestamp >= start_time && timestamp <= end_time {
result.push(String::from_utf8_lossy(&value).to_string());
}
}
}
Ok(result)
}
pub fn generate_cert_id(&self, service_id: u64) -> String {
use blueprint_std::BlueprintRng;
let mut rng = BlueprintRng::new();
let random_bytes: [u8; 16] = rng.r#gen();
format!("cert_{}_{}", service_id, hex::encode(random_bytes))
}
pub fn generate_key_id(&self, service_id: u64) -> String {
use blueprint_std::BlueprintRng;
let mut rng = BlueprintRng::new();
let random_bytes: [u8; 16] = rng.r#gen();
format!("key_{}_{}", service_id, hex::encode(random_bytes))
}
pub fn validate_certificate_chain(
&self,
cert_data: &[u8],
ca_bundle_data: &[u8],
) -> Result<bool, crate::Error> {
if cert_data.is_empty() || ca_bundle_data.is_empty() {
return Ok(false);
}
let leaf_cert_der = Self::extract_certificates(cert_data).map_err(|e| {
crate::Error::Io(std::io::Error::other(format!(
"Failed to parse certificate: {e}"
)))
})?;
let leaf_der = leaf_cert_der
.first()
.ok_or_else(|| crate::Error::Io(std::io::Error::other("Certificate data missing")))?;
let (_, leaf_cert) = X509Certificate::from_der(leaf_der).map_err(|e| {
crate::Error::Io(std::io::Error::other(format!(
"Invalid certificate DER: {e}"
)))
})?;
let ca_ders = Self::extract_certificates(ca_bundle_data).map_err(|e| {
crate::Error::Io(std::io::Error::other(format!(
"Failed to parse CA bundle: {e}"
)))
})?;
for ca_der in ca_ders {
match X509Certificate::from_der(&ca_der) {
Ok((_, ca_cert)) => {
if leaf_cert
.verify_signature(Some(ca_cert.public_key()))
.is_ok()
{
return Ok(true);
}
}
Err(err) => {
error!("Failed to parse CA certificate for validation: {err}");
}
}
}
Ok(false)
}
pub fn is_certificate_expired(&self, not_after: u64) -> bool {
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_secs();
now > not_after
}
pub fn is_certificate_not_yet_valid(&self, not_before: u64) -> bool {
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_secs();
now < not_before
}
pub fn get_all_tls_profiles(&self) -> Result<HashMap<u64, TlsProfile>, crate::Error> {
let mut result = HashMap::new();
let cf_handle = self.db.cf_handle(cf::TLS_ASSETS_CF).unwrap();
for item in self.db.prefix_iterator_cf(&cf_handle, b"tls_profile:") {
let (key, value) = item?;
let service_id_str = String::from_utf8_lossy(&key[11..]); if let Ok(service_id) = service_id_str.parse::<u64>() {
let profile = TlsProfile::decode(&value[..])?;
result.insert(service_id, profile);
}
}
Ok(result)
}
fn extract_certificates(input: &[u8]) -> Result<Vec<Vec<u8>>, String> {
if input.is_empty() {
return Ok(Vec::new());
}
if let Ok(pem_str) = std::str::from_utf8(input) {
if let Ok(blocks) = pem::parse_many(pem_str) {
let mut ders = Vec::new();
for block in blocks {
if block.tag() == "CERTIFICATE" {
ders.push(block.contents().to_vec());
}
}
if !ders.is_empty() {
return Ok(ders);
}
}
}
Ok(vec![input.to_vec()])
}
pub fn cleanup_expired_certificates(&self) -> Result<u32, crate::Error> {
let mut cleaned_count = 0;
let metadata_cf_handle = self.db.cf_handle(cf::TLS_CERT_METADATA_CF).unwrap();
let assets_cf_handle = self.db.cf_handle(cf::TLS_ASSETS_CF).unwrap();
let mut errors = Vec::new();
for item in self
.db
.prefix_iterator_cf(&metadata_cf_handle, b"cert_metadata:")
{
let (key, value) = item?;
if let Ok(metadata) = TlsCertMetadata::decode(&value[..]) {
if self.is_certificate_expired(metadata.expires_at) {
self.db.delete_cf(&metadata_cf_handle, &key)?;
let cert_key = format!("cert_data:{}", metadata.cert_id);
if let Err(err) = self.db.delete_cf(&assets_cf_handle, cert_key.as_bytes()) {
error!(
certificate_id = %metadata.cert_id,
error = %err,
"failed to delete encrypted certificate data during cleanup"
);
errors.push(format!("cert_data:{}: {}", metadata.cert_id, err));
}
let key_key = format!("private_key_data:{}", metadata.cert_id);
if let Err(err) = self.db.delete_cf(&assets_cf_handle, key_key.as_bytes()) {
error!(
certificate_id = %metadata.cert_id,
error = %err,
"failed to delete encrypted private key during cleanup"
);
errors.push(format!("private_key_data:{}: {}", metadata.cert_id, err));
}
cleaned_count += 1;
}
}
}
if errors.is_empty() {
Ok(cleaned_count)
} else {
Err(crate::Error::Tls(format!(
"cleaned {cleaned_count} expired certificates but encountered errors: {}",
errors.join("; ")
)))
}
}
}