From cd3c2503657d5c59fcd63aac0a0d4a1d6de59c12 Mon Sep 17 00:00:00 2001 From: Hatter Jiang Date: Fri, 30 Apr 2021 23:47:10 +0800 Subject: [PATCH] feat: make a lot of changes --- Cargo.toml | 10 +-- src/logo.txt | 7 ++ src/main.rs | 186 ++++++++++++++++++++++++++++++++------------------- 3 files changed, 130 insertions(+), 73 deletions(-) create mode 100644 src/logo.txt diff --git a/Cargo.toml b/Cargo.toml index ac66ae4..77cd887 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,9 +8,9 @@ description = "Acme auto challenge client" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -lazy_static = "1.4.0" -clap = "2.33.3" +lazy_static = "1.4" +clap = "2.33" rust_util = "0.6" -acme-lib = "0.8.1" -tide = "0.16.0" -async-std = { version = "1.8.0", features = ["attributes"] } +acme-lib = "0.8" +tide = "0.16" +async-std = { version = "1.8", features = ["attributes"] } diff --git a/src/logo.txt b/src/logo.txt new file mode 100644 index 0000000..86494d9 --- /dev/null +++ b/src/logo.txt @@ -0,0 +1,7 @@ + + █████╗ ██████╗███╗ ███╗███████╗ ██████╗██╗ ██╗███████╗███╗ ██╗████████╗ +██╔══██╗██╔════╝████╗ ████║██╔════╝ ██╔════╝██║ ██║██╔════╝████╗ ██║╚══██╔══╝ +███████║██║ ██╔████╔██║█████╗ █████╗ ██║ ██║ ██║█████╗ ██╔██╗ ██║ ██║ +██╔══██║██║ ██║╚██╔╝██║██╔══╝ ╚════╝ ██║ ██║ ██║██╔══╝ ██║╚██╗██║ ██║ +██║ ██║╚██████╗██║ ╚═╝ ██║███████╗ ╚██████╗███████╗██║███████╗██║ ╚████║ ██║ +╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝╚══════╝ ╚═════╝╚══════╝╚═╝╚══════╝╚═╝ ╚═══╝ ╚═╝ diff --git a/src/main.rs b/src/main.rs index 27037f9..103925e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,7 +4,7 @@ extern crate lazy_static; extern crate rust_util; use rust_util::XResult; -use acme_lib::{DirectoryUrl, Directory, create_p384_key}; +use acme_lib::{DirectoryUrl, Directory, create_p384_key, create_p256_key, create_rsa_key}; use acme_lib::persist::FilePersist; use clap::{App, Arg}; use std::sync::{Arc, RwLock}; @@ -12,6 +12,7 @@ use std::collections::BTreeMap; use tide::Request; use std::process::exit; use std::time::Duration; +use async_std::channel::Sender; const NAME: &str = env!("CARGO_PKG_NAME"); const VERSION: &str = env!("CARGO_PKG_VERSION"); @@ -21,8 +22,17 @@ lazy_static! { static ref TOKEN_MAP: Arc>> = Arc::new(RwLock::new(BTreeMap::new())); } +#[derive(Debug)] +enum Algo { + Ec256, + Ec384, + Rsa(u32), +} + #[async_std::main] async fn main() -> tide::Result<()> { + println!("{}", include_str!("logo.txt")); + let matches = App::new(NAME) .version(VERSION) .about(DESCRIPTION) @@ -32,34 +42,124 @@ async fn main() -> tide::Result<()> { .arg(Arg::with_name("port").short("p").long("port").default_value("80").takes_value(true).help("Http port")) .arg(Arg::with_name("domain").short("d").long("domain").multiple(true).takes_value(true).help("Domains")) .arg(Arg::with_name("email").long("email").takes_value(true).help("Contract email")) + .arg(Arg::with_name("algo").short("a").long("algo").takes_value(true).default_value("ec384").help("Pki algo")) + .arg(Arg::with_name("timeout").long("timeout").takes_value(true).default_value("5000").help("Timeout (ms)")) .get_matches(); if matches.is_present("version") { information!("{} v{}", NAME, VERSION); - return Ok(()); + exit(1); } - let email = match matches.value_of("email") { - Some(email) => email, + let email = matches.value_of("email").unwrap_or_else(|| { + failure!("Email is not assigned."); + exit(1); + }); + + if let None = matches.value_of("type") { + failure!("Type is not assigned."); + exit(1); + } + let port: u16 = match matches.value_of("port") { + Some(p) => p.parse().unwrap_or_else(|e| { + failure!("Parse port: {}, failed: {}", p, e); + exit(1); + }), None => { - failure!("Email is not assigned."); - return Ok(()); + failure!("Port is not assigned."); + exit(1); } }; - let _type = matches.value_of("type").expect("Failed to get type"); - let port: u16 = matches.value_of("port").expect("Failed to get port").parse().expect("Failed to parse port"); - - let domains_val = match matches.values_of("domain") { - Some(domain_val) => domain_val, + let timeout: u64 = match matches.value_of("timeout") { + Some(p) => p.parse().unwrap_or_else(|e| { + failure!("Parse timeout: {}, failed: {}", p, e); + exit(1); + }), None => { - failure!("Domains is not assigned."); - return Ok(()); + failure!("Timeout is not assigned."); + exit(1); + } + }; + 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), + _ => { + failure!("Algo is not assigned, or wrong, should be: ec256, ec384, rsa2048, rsa3073 or rsa4096."); + exit(1); } }; - let domains: Vec<&str> = domains_val.collect::>(); + let domains_val = matches.values_of("domain").unwrap_or_else(|| { + failure!("Domains is not assigned."); + exit(1); + }); let (s, r) = async_std::channel::bounded(1); + startup_http_server(s, port); + r.recv().await.ok(); + async_std::task::sleep(Duration::from_millis(500)).await; + + let domains: Vec<&str> = domains_val.collect::>(); + let primary_name = domains[0]; + let alt_names: Vec<&str> = domains.into_iter().skip(1).collect(); + information!("Domains, main: {}, alt: {:?}", primary_name, alt_names); + if let Err(e) = request_domains(email, primary_name, &alt_names, algo, timeout) { + failure!("Request certificate by acme failed: {}", e); + exit(1); + } + Ok(()) +} + +fn request_domains(contract_email: &str, primary_name: &str, alt_names: &[&str], algo: Algo, timeout: u64) -> XResult<()> { + let url = DirectoryUrl::LetsEncrypt; + std::fs::create_dir("__temp_dir").ok(); + let persist = FilePersist::new("__temp_dir"); + let dir = opt_result!(Directory::from_url(persist, url), "Create directory from url failed: {}"); + let acc = opt_result!(dir.account(contract_email), "Directory set account failed: {}"); + let mut ord_new = opt_result!( acc.new_order(primary_name, alt_names), "Create order failed: {}"); + + let ord_csr = loop { + if let Some(ord_csr) = ord_new.confirm_validations() { + break ord_csr; + } + + let auths = opt_result!(ord_new.authorizations(), "Order auth failed: {}"); + for auth in &auths { + let chall = auth.http_challenge(); + let token = chall.http_token(); + // let path = format!(".well-known/acme-challenge/{}", token); + let proof = chall.http_proof(); + + { + information!("Add acme challenge: {} -> {}",token, proof); + TOKEN_MAP.write().unwrap().insert(token.to_string(), proof); + } + opt_result!(chall.validate(timeout), "Validate challenge failed: {}"); + } + + opt_result!( ord_new.refresh(), "Refresh order failed: {}"); + }; + + information!("Generate private key, type: {:?}", algo); + let pkey_pri = match algo { + Algo::Ec256 => create_p256_key(), + Algo::Ec384 => create_p384_key(), + Algo::Rsa(bits) => create_rsa_key(bits), + }; + information!("Created private key: {:?}", pkey_pri); + + let ord_cert = opt_result!( ord_csr.finalize_pkey(pkey_pri, timeout), "Finalize private key failed: {}"); + let cert = opt_result!( ord_cert.download_and_save_cert(), "Download and save certificate failed: {}"); + + information!("Created certificate: {:?}", cert); + + Ok(()) +} + +fn startup_http_server(s: Sender, port: u16) { async_std::task::spawn(async move { information!("Listen at 0.0.0.0:{}", port); let mut app = tide::new(); @@ -67,76 +167,26 @@ async fn main() -> tide::Result<()> { let token = match req.param("token") { Ok(token) => token, Err(e) => { - warning!("Cannot get token from url, error: {}", e); + warning!("Cannot get token from url, query: {:?}, error: {}", req.url().query(), e); return Ok("400 - bad request".to_string()); } }; let auth_token = { TOKEN_MAP.read().unwrap().get(token).cloned() }; match auth_token { Some(auth_token) => { - information!("Request acme challenge: {} -> {}", token, auth_token); + information!("Request acme challenge: {} -> {}, peer: {:?}", token, auth_token, req.peer_addr()); Ok(auth_token) } None => { - warning!("Request acme challenge not found: {}", token); + warning!("Request acme challenge not found: {}, peer: {:?}", token, req.peer_addr()); Ok("404 - not found".to_string()) } } }); s.send(1).await.ok(); if let Err(e) = app.listen(&format!("0.0.0.0:{}", port)).await { - failure!("Failed to listen 0.0.0.0:{}, error: {}", port, e); + failure!("Failed to listen 0.0.0.0:{}, program will exit, error: {}", port, e); exit(1); } }); - - r.recv().await.ok(); - async_std::task::sleep(Duration::from_secs(1)).await; - let primary_name = domains[0]; - let alt_names: Vec<&str> = domains.into_iter().skip(1).collect(); - information!("Domains, main: {}, alt: {:?}", primary_name, alt_names); - request_domains(email, primary_name, &alt_names).expect("Request domain failed"); - - Ok(()) -} - - -fn request_domains(contract_email: &str, primary_name: &str, alt_names: &[&str]) -> XResult<()> { - let url = DirectoryUrl::LetsEncrypt; - std::fs::create_dir("__temp_dir").ok(); - let persist = FilePersist::new("__temp_dir"); - let dir = Directory::from_url(persist, url)?; - let acc = dir.account(contract_email)?; - let mut ord_new = acc.new_order(primary_name, alt_names)?; - - let ord_csr = loop { - if let Some(ord_csr) = ord_new.confirm_validations() { - break ord_csr; - } - - let auths = ord_new.authorizations()?; - for auth in &auths { - let chall = auth.http_challenge(); - let token = chall.http_token(); - // let path = format!(".well-known/acme-challenge/{}", token); - let proof = chall.http_proof(); - - information!("Add acme challenge: {} -> {}",token, proof); - TOKEN_MAP.write().unwrap().insert(token.to_string(), proof); - - chall.validate(5000)?; - } - - ord_new.refresh()?; - }; - - let pkey_pri = create_p384_key(); - println!("{:?}", pkey_pri); - - let ord_cert = ord_csr.finalize_pkey(pkey_pri, 5000)?; - let cert = ord_cert.download_and_save_cert()?; - - println!("{:?}", cert); - - Ok(()) }