diff --git a/Cargo.lock b/Cargo.lock index 7187f4f..c9f72d9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7,8 +7,10 @@ dependencies = [ "acme-lib", "async-std", "clap", + "deser-hjson", "lazy_static", "rust_util", + "serde", "tide", ] @@ -644,6 +646,15 @@ version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57" +[[package]] +name = "deser-hjson" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d995b60ff81bc6af01a98f0bf5db70a7418a1ac8bd74ada633968f388139da5e" +dependencies = [ + "serde", +] + [[package]] name = "digest" version = "0.9.0" diff --git a/Cargo.toml b/Cargo.toml index 8effffb..d28d640 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,3 +14,5 @@ rust_util = "0.6" acme-lib = "0.8" tide = "0.16" async-std = { version = "1.8", features = ["attributes"] } +serde = { version = "1.0", features = ["derive"] } +deser-hjson = "0.1" diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..24c3b47 --- /dev/null +++ b/src/config.rs @@ -0,0 +1,113 @@ +use serde::{Deserialize, Serialize}; +use rust_util::util_file; +use rust_util::XResult; +use std::fs; +use std::fs::File; +use std::io::Read; +use acme_lib::DirectoryUrl; + +const CERT_NAME: &str = "cert.pem"; +const KEY_NAME: &str = "key.pem"; + +#[derive(Debug, Clone, Copy)] +pub enum AcmeAlgo { + Ec256, + Ec384, + Rsa(u32), +} + +impl AcmeAlgo { + pub fn parse(s: &str) -> XResult { + match s { + "ec256" => Ok(AcmeAlgo::Ec256), + "ec384" => Ok(AcmeAlgo::Ec384), + "rsa2048" => Ok(AcmeAlgo::Rsa(2048)), + "rsa3072" => Ok(AcmeAlgo::Rsa(3072)), + "rsa4096" => Ok(AcmeAlgo::Rsa(4096)), + _ => simple_error!("Unknown algo: {}", s), + } + } +} + +#[derive(Debug, Clone, Copy)] +pub enum AcmeMode { + Prod, + Test, +} + +impl AcmeMode { + pub fn parse(s: &str) -> XResult { + 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 AcmeConfig { + pub email: String, + pub dir: String, + pub auth_timeout: Option, + pub csr_timeout: Option, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CertConfigItem { + pub path: String, + pub algo: Option, + pub dns_names: Option>, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CertConfig { + pub cert_items: Vec, +} + +impl AcmeConfig { + pub fn get_auth_timeout(&self) -> u64 { + self.auth_timeout.unwrap_or(5_000) + } + + pub fn get_csr_timeout(&self) -> u64 { + self.csr_timeout.unwrap_or(5_000) + } +} + +impl CertConfigItem { + pub fn fill_dns_names(&mut self) -> XResult<()> { + // TODO + Ok(()) + } +} + +pub fn load_acme_config(file: Option<&str>, load_default: bool) -> XResult { + if let Some(file) = file { + let s = opt_result!(util_file::read_file_content(file), "Read file: {}, failed: {}", file); + return Ok(opt_result!(deser_hjson::from_str(&s), "Parse acme config file: {}, failed: {}", file)); + } + + if load_default { + let default_config = util_file::read_config(None, &[ + "~/acme_config.json".to_string(), + "/etc/acme_config.json".to_string(), + ]); + if let Some(default_config) = default_config { + let s = opt_result!(fs::read_to_string(default_config.clone()), "Read file: {:?}, failed: {}", default_config); + return Ok(opt_result!(deser_hjson::from_str(&s), "Parse acme config file: {:?}, failed: {}", default_config)); + } + } + + simple_error!("Acme config file not found!") +} diff --git a/src/main.rs b/src/main.rs index 786e35e..d017e72 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,17 +3,22 @@ extern crate lazy_static; #[macro_use] extern crate rust_util; +mod config; + use rust_util::XResult; use acme_lib::{DirectoryUrl, Directory}; use acme_lib::{create_p384_key, create_p256_key, create_rsa_key}; use acme_lib::persist::FilePersist; use clap::{App, Arg}; +use std::fs; use std::sync::RwLock; use std::collections::BTreeMap; use tide::Request; use std::process::exit; use std::time::Duration; use async_std::channel::Sender; +use config::AcmeAlgo; +use config::AcmeMode; const NAME: &str = env!("CARGO_PKG_NAME"); const VERSION: &str = env!("CARGO_PKG_VERSION"); @@ -24,26 +29,13 @@ lazy_static! { static ref TOKEN_MAP: RwLock> = RwLock::new(BTreeMap::new()); } -#[derive(Debug, Clone, Copy)] -enum Algo { - Ec256, - Ec384, - Rsa(u32), -} - -#[derive(Debug, Clone, Copy)] -enum Mode { - Prod, - Test, -} - #[derive(Debug)] struct AcmeRequest<'a> { contract_email: &'a str, primary_name: &'a str, alt_names: &'a [&'a str], - algo: Algo, - mode: Mode, + algo: AcmeAlgo, + mode: AcmeMode, dir: &'a str, timeout: u64, } @@ -103,21 +95,22 @@ async fn main() -> tide::Result<()> { } }; let algo = match matches.value_of("algo") { - Some("ec256") => Algo::Ec256, - Some("ec384") => Algo::Ec384, - Some("rsa2048") => Algo::Rsa(2048), - Some("rsa3072") => Algo::Rsa(3072), - Some("rsa4096") => Algo::Rsa(4096), + Some(a) => AcmeAlgo::parse(a).unwrap_or_else(|e| { + failure!("{}", e); + exit(1); + }), _ => { - failure!("Algo is not assigned, or wrong, should be: ec256, ec384, rsa2048, rsa3073 or rsa4096."); + failure!("Algo is not assigned, should be: ec256, ec384, rsa2048, rsa3073 or rsa4096."); exit(1); } }; let mode = match matches.value_of("mode") { - Some("prod") => Mode::Prod, - Some("test") => Mode::Test, + Some(m) => AcmeMode::parse(m).unwrap_or_else(|e| { + failure!("{}", e); + exit(1); + }), _ => { - failure!("Mode is not assigned, or wrong, should be: prod or test"); + failure!("AcmeMode is not assigned, should be: prod or test"); exit(1); } }; @@ -155,12 +148,9 @@ async fn main() -> tide::Result<()> { fn request_domains(acme_request: AcmeRequest) -> XResult<()> { information!("Acme mode: {:?}", acme_request.mode); - let url = match acme_request.mode { - Mode::Prod => DirectoryUrl::LetsEncrypt, - Mode::Test => DirectoryUrl::LetsEncryptStaging, - }; + let url = acme_request.mode.directory_url(); information!("Acme dir: {}", acme_request.dir); - std::fs::create_dir(acme_request.dir).ok(); + fs::create_dir_all(acme_request.dir).ok(); let persist = FilePersist::new(acme_request.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: {}"); @@ -194,16 +184,16 @@ fn request_domains(acme_request: AcmeRequest) -> XResult<()> { information!("Generate private key, type: {:?}", acme_request.algo); let pkey_pri = match acme_request.algo { - Algo::Ec256 => create_p256_key(), - Algo::Ec384 => create_p384_key(), - Algo::Rsa(bits) => create_rsa_key(bits), + AcmeAlgo::Ec256 => create_p256_key(), + AcmeAlgo::Ec384 => create_p384_key(), + AcmeAlgo::Rsa(bits) => create_rsa_key(bits), }; - information!("Created private key: {:?}", pkey_pri); 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!("Created certificate: {:?}", cert); + information!("Certificate key: {}", cert.private_key()); + information!("Certificate pem: {}", cert.certificate()); Ok(()) }