feat: cert config

This commit is contained in:
2021-05-05 00:14:16 +08:00
parent 885207adf1
commit 94ce425e37
3 changed files with 204 additions and 68 deletions

View File

@@ -5,14 +5,20 @@ use std::fs;
use std::fs::File;
use std::io::Read;
use acme_lib::DirectoryUrl;
use std::path::PathBuf;
use std::str::FromStr;
use crate::x509;
use crate::x509::{X509PublicKeyAlgo, X509EcPublicKeyAlgo, X509Certificate};
use std::time::{SystemTime, Duration};
const CERT_NAME: &str = "cert.pem";
const KEY_NAME: &str = "key.pem";
pub const CERT_NAME: &str = "cert.pem";
pub const KEY_NAME: &str = "key.pem";
#[derive(Debug, Clone, Copy)]
pub enum AcmeAlgo {
Ec256,
Ec384,
Ec521,
Rsa(u32),
}
@@ -31,6 +37,7 @@ impl AcmeAlgo {
match s {
"ec256" => Ok(AcmeAlgo::Ec256),
"ec384" => Ok(AcmeAlgo::Ec384),
"ec521" => Ok(AcmeAlgo::Ec521),
"rsa2048" => Ok(AcmeAlgo::Rsa(2048)),
"rsa3072" => Ok(AcmeAlgo::Rsa(3072)),
"rsa4096" => Ok(AcmeAlgo::Rsa(4096)),
@@ -79,22 +86,7 @@ pub struct AcmeConfig {
pub dir: String,
pub auth_timeout: Option<u64>,
pub csr_timeout: Option<u64>,
pub concurrent: Option<u32>, // ?
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CertConfigItem {
pub path: String,
pub algo: Option<String>,
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>,
pub concurrent: Option<u32>,
}
impl AcmeConfig {
@@ -111,10 +103,94 @@ impl AcmeConfig {
}
}
#[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 cert_items = vec![];
let valid_days_secs = valid_days as i64 * 24 * 3600;
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) {
information!("Certificate: {} is valid: {} days", item.path,
(x509_certificate.certificate_not_after - secs_from_unix_epoch)/valid_days_secs
)
} else {
cert_items.push(item2);
}
}
Ok(None) => {
if fs::read_dir(&item.path).is_err() {
information!("Create path: {}", item.path);
fs::create_dir_all(&item.path).ok();
}
cert_items.push(item2);
}
Err(e) => warning!("Certificate: {}, parse error: {}", item.path, e),
}
}
Self { 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<()> {
// TODO
Ok(())
pub fn fill_dns_names(&mut self) -> XResult<Option<X509Certificate>> {
if self.common_name.is_none() || self.dns_names.is_none() {
if self.path.is_empty() {
return simple_error!("Cert config item common name and path both empty");
}
let path_buff = opt_result!(PathBuf::from_str(&self.path), "Path: {}, failed: {}", self.path);
let cert_path_buff = path_buff.join(CERT_NAME);
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());
self.algo = None;
self.public_key_algo = Some(x509_certificate.public_key_algo.clone());
Ok(Some(x509_certificate))
} else {
if self.path.is_empty() {
return simple_error!("Cert config item path is empty");
}
if self.public_key_algo.is_none() {
self.public_key_algo = match &self.algo {
None => Some(X509PublicKeyAlgo::Rsa(2048)),
Some(algo) => match AcmeAlgo::parse(&algo) {
Ok(AcmeAlgo::Rsa(bit_length)) => Some(X509PublicKeyAlgo::Rsa(bit_length)),
Ok(AcmeAlgo::Ec256) => Some(X509PublicKeyAlgo::EcKey(X509EcPublicKeyAlgo::Secp256r1)),
Ok(AcmeAlgo::Ec384) => Some(X509PublicKeyAlgo::EcKey(X509EcPublicKeyAlgo::Secp384r1)),
Ok(AcmeAlgo::Ec521) => Some(X509PublicKeyAlgo::EcKey(X509EcPublicKeyAlgo::Secp521r1)),
Err(_) => return simple_error!("Unknown algo: {}", algo),
},
};
}
Ok(None)
}
}
}

View File

@@ -5,7 +5,7 @@ extern crate rust_util;
mod config;
mod x509;
mod simple_thread_pool;
// mod simple_thread_pool;
use rust_util::XResult;
use acme_lib::{DirectoryUrl, Directory};
@@ -23,6 +23,7 @@ use async_std::channel;
use async_std::channel::Sender;
use config::AcmeAlgo;
use config::AcmeMode;
use crate::config::{CertConfig, CERT_NAME, KEY_NAME};
const NAME: &str = env!("CARGO_PKG_NAME");
const VERSION: &str = env!("CARGO_PKG_VERSION");
@@ -40,7 +41,7 @@ struct AcmeRequest<'a> {
alt_names: &'a [&'a str],
algo: AcmeAlgo,
mode: AcmeMode,
dir: &'a str,
account_dir: &'a str,
timeout: u64,
key_file: Option<String>,
cert_file: Option<String>,
@@ -64,6 +65,8 @@ async fn main() -> tide::Result<()> {
.arg(Arg::with_name("timeout").long("timeout").takes_value(true).default_value("5000").help("Timeout (ms)"))
.arg(Arg::with_name("mode").short("m").long("mode").takes_value(true).default_value("prod").help("Mode"))
.arg(Arg::with_name("dir").long("dir").takes_value(true).default_value("acme_dir").help("Account key dir"))
.arg(Arg::with_name("config").short("c").long("config").takes_value(true).help("Acme config"))
.arg(Arg::with_name("cert-config").long("cert-config").takes_value(true).help("Cert config"))
.get_matches();
if matches.is_present("version") {
@@ -120,7 +123,7 @@ async fn main() -> tide::Result<()> {
exit(1);
}
};
let dir = matches.value_of("dir").unwrap_or("acme_dir");
let account_dir = matches.value_of("dir").unwrap_or("acme_dir");
let domains_val = matches.values_of("domain").unwrap_or_else(|| {
failure!("Domains is not assigned.");
@@ -132,33 +135,72 @@ async fn main() -> tide::Result<()> {
r.recv().await.ok();
task::sleep(Duration::from_millis(500)).await;
let domains: Vec<&str> = domains_val.collect::<Vec<_>>();
let primary_name = domains[0];
let alt_names: Vec<&str> = domains.into_iter().skip(1).collect();
information!("Domains, main: {}, alt: {:?}", primary_name, alt_names);
let acme_request = AcmeRequest {
contract_email: email,
primary_name,
alt_names: &alt_names,
algo,
mode,
dir,
timeout,
..Default::default()
};
if let Err(e) = request_domains(acme_request) {
failure!("Request certificate by acme failed: {}", e);
exit(1);
// TODO ......
let config = matches.value_of("config");
let cert_config = matches.value_of("cert-config");
match cert_config {
None => { // cert config is not assigned
let domains: Vec<&str> = domains_val.collect::<Vec<_>>();
let primary_name = domains[0];
let alt_names: Vec<&str> = domains.into_iter().skip(1).collect();
information!("Domains, main: {}, alt: {:?}", primary_name, alt_names);
let acme_request = AcmeRequest {
contract_email: email,
primary_name,
alt_names: &alt_names,
algo,
mode,
account_dir,
timeout,
..Default::default()
};
if let Err(e) = request_domains(acme_request) {
failure!("Request certificate by acme failed: {}", e);
exit(1);
}
}
Some(cert_config) => { // cert config is assigned
let cert_config = {
CertConfig::load(cert_config).unwrap_or_else(|e| {
failure!("Load cert config: {}, failed: {}", cert_config, e);
exit(1);
})
};
let filtered_cert_config = cert_config.filter_cert_config_items(30);
for item in &filtered_cert_config.cert_items {
if let (Some(common_name), Some(dns_names)) = (&item.common_name, &item.dns_names) {
information!("Domains, main: {}, alt: {:?}", common_name, dns_names);
let alt_names: Vec<&str> = dns_names.iter().map(|n| n.as_str()).collect();
let acme_request = AcmeRequest {
contract_email: email,
primary_name: common_name,
alt_names: &alt_names,
algo,
mode,
account_dir,
timeout,
cert_file: Some(format!("{}/{}", item.path, CERT_NAME)),
key_file: Some(format!("{}/{}", item.path, KEY_NAME)),
};
if let Err(e) = request_domains(acme_request) {
failure!("Request certificate: {}, by acme failed: {}", item.path, e);
}
}
}
}
}
Ok(())
}
fn request_domains(acme_request: AcmeRequest) -> XResult<()> {
information!("Acme mode: {:?}", acme_request.mode);
let url = acme_request.mode.directory_url();
information!("Acme dir: {}", acme_request.dir);
fs::create_dir_all(acme_request.dir).ok();
let persist = FilePersist::new(acme_request.dir);
information!("Acme dir: {}", acme_request.account_dir);
fs::create_dir_all(acme_request.account_dir).ok();
let persist = FilePersist::new(acme_request.account_dir);
let dir = opt_result!(Directory::from_url(persist, url), "Create directory from url failed: {}");
let acc = opt_result!(dir.account(acme_request.contract_email), "Directory set account failed: {}");
let mut ord_new = opt_result!( acc.new_order(acme_request.primary_name, acme_request.alt_names), "Create order failed: {}");
@@ -193,14 +235,25 @@ fn request_domains(acme_request: AcmeRequest) -> XResult<()> {
let pkey_pri = match acme_request.algo {
AcmeAlgo::Ec256 => create_p256_key(),
AcmeAlgo::Ec384 => create_p384_key(),
AcmeAlgo::Ec521 => return simple_error!("Algo ec521 is not supported"),
AcmeAlgo::Rsa(bits) => create_rsa_key(bits),
};
let ord_cert = opt_result!( ord_csr.finalize_pkey(pkey_pri, acme_request.timeout), "Submit CSR failed: {}");
let cert = opt_result!( ord_cert.download_and_save_cert(), "Download and save certificate failed: {}");
information!("Certificate key: {}", cert.private_key());
information!("Certificate pem: {}", cert.certificate());
if let (Some(cert_file), Some(key_file)) = (&acme_request.cert_file, &acme_request.key_file) {
if let Err(e) = fs::write(cert_file, cert.certificate()) {
failure!("Write file: {}, failed: {}", cert_file, e);
}
if let Err(e) = fs::write(key_file, cert.private_key()) {
failure!("Write file: {}, failed: {}", key_file, e);
}
} else {
information!("Certificate key: {}", cert.private_key());
information!("Certificate pem: {}", cert.certificate());
}
Ok(())
}

View File

@@ -1,3 +1,4 @@
use serde::{Deserialize, Serialize};
use x509_parser::parse_x509_certificate;
use x509_parser::pem::parse_x509_pem;
use x509_parser::extensions::{ParsedExtension, GeneralName};
@@ -22,23 +23,23 @@ lazy_static! {
static ref OID_SECP521R1: Oid<'static> = Oid::from_str("1.3.132.0.35").unwrap();
}
#[derive(Debug, Clone, Copy)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X509IssuerAlgo {
RsaWithSha256,
EcdsaWithSha256,
}
#[derive(Debug, Clone)]
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum X509EcPublicKeyAlgo {
Secp256r1,
Secp384r1,
Secp521r1,
}
#[derive(Debug, Clone)]
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum X509PublicKeyAlgo {
EcKey(X509EcPublicKeyAlgo),
Rsa,
Rsa(u32),
}
impl X509PublicKeyAlgo {
@@ -62,48 +63,41 @@ impl X509PublicKeyAlgo {
};
Ok(Self::EcKey(ec_public_key_algo))
} else if public_key_algo_oid == &*OID_RSA_PUBLIC_KEY {
// TODO ..
println!(":::-> {:?}", public_key_info.subject_public_key);
let d = parse_der(public_key_info.subject_public_key.data);
println!("{:?}", d);
println!("{:?}", d.as_ref().unwrap().1.content);
if let BerObjectContent::Sequence(seq) = &d.as_ref().unwrap().1.content {
let public_key_data = parse_der(public_key_info.subject_public_key.data);
if let BerObjectContent::Sequence(seq) = &public_key_data.as_ref().unwrap().1.content {
let mut rsa_n_len = 0;
if let BerObjectContent::Integer(n) = seq[0].content {
rsa_n_len = n.len() - (if n[0] == 0 { 1 } else { 0 });
}
let rsa_bit_length = || -> Option<i32> {
let get_rsa_bit_length = || -> Option<i32> {
for bit_len in &[1024, 2048, 3072, 4096] {
if i32::abs(bit_len - (rsa_n_len as i32 * 8)) <= 16 {
if i32::abs(bit_len - (rsa_n_len as i32 * 8)) <= 32 {
return Some(*bit_len);
}
}
None
};
if let Some(rsa_bit_length) = get_rsa_bit_length() {
return Ok(Self::Rsa(rsa_bit_length as u32));
}
}
Ok(Self::Rsa)
simple_error!("Parse cert: {}, not valid rsa public key", pem_id)
} else {
simple_error!("Parse cert: {}, unknown public key algo oid: {}", pem_id, public_key_algo_oid)
}
}
}
#[derive(Debug, Clone)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct X509Certificate {
pub issuer_algo: X509IssuerAlgo,
pub common_name: String,
pub alt_names: Vec<String>,
pub public_key_algo: X509PublicKeyAlgo,
pub certificate_not_after: i64,
}
#[test]
fn test() {
let p = include_str!("sample_cert.pem");
let c = parse_x500("aa", p);
println!("{:?}", c);
}
pub fn parse_x500(pem_id: &str, pem: &str) -> XResult<X509Certificate> {
pub fn parse_x509(pem_id: &str, pem: &str) -> XResult<X509Certificate> {
let (_, der) = opt_result!(parse_x509_pem(pem.as_bytes()), "Parse pem: {} to der failed: {}", pem_id);
let (_, cert) = opt_result!(parse_x509_certificate(der.contents.as_slice()), "Parse cert: {} failed: {}", pem_id);
@@ -138,10 +132,23 @@ pub fn parse_x500(pem_id: &str, pem: &str) -> XResult<X509Certificate> {
}
let public_key_algo = X509PublicKeyAlgo::parse(pem_id, &cert.tbs_certificate.subject_pki)?;
let certificate_not_after = cert.tbs_certificate.validity.not_after.timestamp();
Ok(X509Certificate {
issuer_algo,
common_name,
alt_names,
public_key_algo,
certificate_not_after,
})
}
#[test]
fn test_sample_cert() {
let cert_pem = include_str!("sample_cert.pem");
let x509_certificate = parse_x509("test", cert_pem).unwrap();
assert_eq!(X509IssuerAlgo::RsaWithSha256, x509_certificate.issuer_algo);
assert_eq!("zigstack.org", x509_certificate.common_name.as_str());
assert_eq!(vec!["www.zigstack.org", "zigstack.org"], x509_certificate.alt_names);
assert_eq!(X509PublicKeyAlgo::Rsa(2048), x509_certificate.public_key_algo);
}