Files
acme-client-rs/src/config.rs
2021-05-05 11:42:33 +08:00

170 lines
7.1 KiB
Rust

use serde::{Deserialize, Serialize};
use rust_util::XResult;
use std::fs;
use acme_lib::DirectoryUrl;
use std::path::PathBuf;
use std::str::FromStr;
use crate::x509;
use crate::x509::{X509PublicKeyAlgo, X509Certificate};
use std::time::SystemTime;
pub const CERT_NAME: &str = "cert.pem";
pub const KEY_NAME: &str = "key.pem";
#[derive(Debug, Clone, Copy)]
pub enum AcmeMode {
Prod,
Test,
}
impl Default for AcmeMode {
fn default() -> Self {
Self::Prod
}
}
impl AcmeMode {
pub fn parse(s: &str) -> XResult<AcmeMode> {
match s {
"prod" => Ok(AcmeMode::Prod),
"test" => Ok(AcmeMode::Test),
_ => simple_error!("Unknown mode: {}", s),
}
}
pub fn directory_url(&self) -> DirectoryUrl {
match self {
AcmeMode::Prod => DirectoryUrl::LetsEncrypt,
AcmeMode::Test => DirectoryUrl::LetsEncryptStaging,
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CertConfigItem {
pub path: String,
pub algo: Option<String>,
pub public_key_algo: Option<X509PublicKeyAlgo>,
pub common_name: Option<String>,
pub dns_names: Option<Vec<String>>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CertConfig {
pub cert_items: Vec<CertConfigItem>,
}
impl CertConfig {
pub fn filter_cert_config_items(self, valid_days: i32) -> Self {
let mut filtered_cert_items = vec![];
let secs_per_day = 24 * 3600;
let valid_days_secs = valid_days as i64 * secs_per_day;
let secs_from_unix_epoch = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs() as i64;
for item in &self.cert_items {
let mut item2 = item.clone();
match item2.fill_dns_names() {
Ok(Some(x509_certificate)) => {
if x509_certificate.certificate_not_after >= (valid_days_secs + secs_from_unix_epoch) {
success!("Certificate: {}, common name: {}, dns names: {:?}, is valid: {} days", item.path,
x509_certificate.common_name,
x509_certificate.alt_names,
(x509_certificate.certificate_not_after - secs_from_unix_epoch) / secs_per_day
);
} else {
warning!("Certificate: {}, common name: {}, dns names: {:?}, is valid: {} days", item.path,
x509_certificate.common_name,
x509_certificate.alt_names,
(x509_certificate.certificate_not_after - secs_from_unix_epoch) / secs_per_day
);
filtered_cert_items.push(item2);
}
}
Ok(None) => {
if fs::read_dir(&item.path).is_err() {
information!("Create certificate path: {}", item.path);
fs::create_dir_all(&item.path).ok();
}
filtered_cert_items.push(item2);
}
Err(e) => warning!("Certificate: {}, parse error: {}", item.path, e),
}
}
Self { cert_items: filtered_cert_items }
}
pub fn load(config_fn: &str) -> XResult<Self> {
let config_content = opt_result!(fs::read_to_string(config_fn), "Load config: {}, failed: {}", config_fn);
let config: CertConfig = opt_result!(deser_hjson::from_str(&config_content), "Parse config: {}, failed: {}", config_fn);
Ok(config)
}
}
impl CertConfigItem {
pub fn fill_dns_names(&mut self) -> XResult<Option<X509Certificate>> {
if self.path.is_empty() {
return simple_error!("Cert config item path is empty");
}
let path_buff = opt_result!(PathBuf::from_str(&self.path), "Path: {}, failed: {}", self.path);
let cert_path_buff = path_buff.join(CERT_NAME);
if self.common_name.is_none() && self.dns_names.is_none() {
let pem = opt_result!(fs::read_to_string(cert_path_buff.clone()), "Read file: {:?}, failed: {}", cert_path_buff);
let x509_certificate = opt_result!(x509::parse_x509(&format!("{}/{}", self.path, CERT_NAME), &pem), "Parse x509: {}/{}, faield: {}", self.path, CERT_NAME);
self.common_name = Some(x509_certificate.common_name.clone());
self.dns_names = Some(x509_certificate.alt_names.clone());
if let Some(pos) = x509_certificate.alt_names.iter().position(|n| n == &x509_certificate.common_name) {
if let Some(dns_names) = &mut self.dns_names {
dns_names.remove(pos);
}
}
self.algo = None;
self.public_key_algo = Some(x509_certificate.public_key_algo.clone());
Ok(Some(x509_certificate))
} else {
if self.common_name.is_none() {
if let Some(dns_names) = &mut self.dns_names {
self.common_name = Some(dns_names.remove(0));
}
}
if self.public_key_algo.is_none() {
self.public_key_algo = match &self.algo {
None => Some(X509PublicKeyAlgo::Rsa(2048)),
Some(algo) => match X509PublicKeyAlgo::from_str(&algo) {
Ok(algo) => Some(algo),
Err(_) => return simple_error!("Unknown algo: {}", algo),
},
};
}
if cert_path_buff.exists() {
let pem = opt_result!(fs::read_to_string(cert_path_buff.clone()), "Read file: {:?}, failed: {}", cert_path_buff);
let x509_certificate = opt_result!(x509::parse_x509(&format!("{}/{}", self.path, CERT_NAME), &pem), "Parse x509: {}/{}, faield: {}", self.path, CERT_NAME);
if let Some(common_name) = &self.common_name {
if common_name != &x509_certificate.common_name {
warning!("Cert: {}, common name mis-match: {} vs {}", self.path, common_name, x509_certificate.common_name);
return Ok(None); // request for new cert
}
}
if let Some(dns_names) = &self.dns_names {
let mut sorted_dns_names = dns_names.clone();
sorted_dns_names.sort();
let mut cert_sorted_dns_names = x509_certificate.alt_names.clone();
if let Some(pos) = cert_sorted_dns_names.iter().position(|n| n == self.common_name.as_ref().unwrap()) {
cert_sorted_dns_names.remove(pos);
}
cert_sorted_dns_names.sort();
if sorted_dns_names != cert_sorted_dns_names {
warning!("Cert: {}, dns names mis-match: {:?} vs {:?}", self.path, sorted_dns_names, cert_sorted_dns_names);
return Ok(None); // request for new cert
}
}
Ok(Some(x509_certificate))
} else {
Ok(None)
}
}
}
}