use chrono::Utc;
use faststr::FastStr;
use rustls_pemfile::{certs, private_key};
use rustls_pki_types::{CertificateDer, PrivateKeyDer};
use x509_parser::{extensions::GeneralName, parse_x509_certificate};
use crate::{Error, Result};
#[derive(Debug)]
pub struct Cert {
pub fullchain: Vec<CertificateDer<'static>>,
pub key: PrivateKeyDer<'static>,
pub host_li: Vec<FastStr>,
pub expire: i64,
}
impl Cert {
pub fn new(fullchain: impl AsRef<str>, key: impl AsRef<str>) -> Result<Self> {
let mut cert_reader = std::io::Cursor::new(fullchain.as_ref().as_bytes());
let fullchain = certs(&mut cert_reader)
.collect::<std::result::Result<Vec<_>, _>>()
.map_err(|e| Error::CertParse(format!("证书解析失败: {e:?}")))?;
if fullchain.is_empty() {
return Err(Error::CertNotFound(
"exist cert str , but no cert".to_string(),
));
}
let mut key_reader = std::io::Cursor::new(key.as_ref().as_bytes());
let key = private_key(&mut key_reader)
.map_err(|e| Error::CertParse(format!("私钥解析失败: {e:?}")))?
.ok_or(Error::PrivateKeyNotFound)?;
let end_entity_cert = &fullchain[0];
let (_, x509) = parse_x509_certificate(end_entity_cert.as_ref())
.map_err(|e| Error::CertParse(format!("x509解析失败: {e}")))?;
let host_li = if let Ok(Some(san)) = x509.subject_alternative_name() {
san
.value
.general_names
.iter()
.filter_map(|name| match name {
GeneralName::DNSName(s) => Some(FastStr::from(s.to_string())),
_ => None,
})
.collect::<Vec<FastStr>>()
} else {
x509
.subject()
.iter_common_name()
.filter_map(|cn| {
cn.attr_value()
.as_str()
.ok()
.map(|s| FastStr::from(s.to_string()))
})
.collect::<Vec<FastStr>>()
};
if host_li.is_empty() {
return Err(Error::CertParse("证书没有域名".to_string()));
}
let expire = x509.validity().not_after.timestamp();
Ok(Cert {
fullchain,
key,
host_li,
expire,
})
}
pub fn is_expired(&self, days: i64) -> bool {
self.expire < Utc::now().timestamp() + days * 24 * 60 * 60
}
}