feat: make a lot of changes
This commit is contained in:
10
Cargo.toml
10
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
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4"
|
||||||
clap = "2.33.3"
|
clap = "2.33"
|
||||||
rust_util = "0.6"
|
rust_util = "0.6"
|
||||||
acme-lib = "0.8.1"
|
acme-lib = "0.8"
|
||||||
tide = "0.16.0"
|
tide = "0.16"
|
||||||
async-std = { version = "1.8.0", features = ["attributes"] }
|
async-std = { version = "1.8", features = ["attributes"] }
|
||||||
|
|||||||
7
src/logo.txt
Normal file
7
src/logo.txt
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
|
||||||
|
█████╗ ██████╗███╗ ███╗███████╗ ██████╗██╗ ██╗███████╗███╗ ██╗████████╗
|
||||||
|
██╔══██╗██╔════╝████╗ ████║██╔════╝ ██╔════╝██║ ██║██╔════╝████╗ ██║╚══██╔══╝
|
||||||
|
███████║██║ ██╔████╔██║█████╗ █████╗ ██║ ██║ ██║█████╗ ██╔██╗ ██║ ██║
|
||||||
|
██╔══██║██║ ██║╚██╔╝██║██╔══╝ ╚════╝ ██║ ██║ ██║██╔══╝ ██║╚██╗██║ ██║
|
||||||
|
██║ ██║╚██████╗██║ ╚═╝ ██║███████╗ ╚██████╗███████╗██║███████╗██║ ╚████║ ██║
|
||||||
|
╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝╚══════╝ ╚═════╝╚══════╝╚═╝╚══════╝╚═╝ ╚═══╝ ╚═╝
|
||||||
186
src/main.rs
186
src/main.rs
@@ -4,7 +4,7 @@ extern crate lazy_static;
|
|||||||
extern crate rust_util;
|
extern crate rust_util;
|
||||||
|
|
||||||
use rust_util::XResult;
|
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 acme_lib::persist::FilePersist;
|
||||||
use clap::{App, Arg};
|
use clap::{App, Arg};
|
||||||
use std::sync::{Arc, RwLock};
|
use std::sync::{Arc, RwLock};
|
||||||
@@ -12,6 +12,7 @@ use std::collections::BTreeMap;
|
|||||||
use tide::Request;
|
use tide::Request;
|
||||||
use std::process::exit;
|
use std::process::exit;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
use async_std::channel::Sender;
|
||||||
|
|
||||||
const NAME: &str = env!("CARGO_PKG_NAME");
|
const NAME: &str = env!("CARGO_PKG_NAME");
|
||||||
const VERSION: &str = env!("CARGO_PKG_VERSION");
|
const VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||||
@@ -21,8 +22,17 @@ lazy_static! {
|
|||||||
static ref TOKEN_MAP: Arc<RwLock<BTreeMap<String, String>>> = Arc::new(RwLock::new(BTreeMap::new()));
|
static ref TOKEN_MAP: Arc<RwLock<BTreeMap<String, String>>> = Arc::new(RwLock::new(BTreeMap::new()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum Algo {
|
||||||
|
Ec256,
|
||||||
|
Ec384,
|
||||||
|
Rsa(u32),
|
||||||
|
}
|
||||||
|
|
||||||
#[async_std::main]
|
#[async_std::main]
|
||||||
async fn main() -> tide::Result<()> {
|
async fn main() -> tide::Result<()> {
|
||||||
|
println!("{}", include_str!("logo.txt"));
|
||||||
|
|
||||||
let matches = App::new(NAME)
|
let matches = App::new(NAME)
|
||||||
.version(VERSION)
|
.version(VERSION)
|
||||||
.about(DESCRIPTION)
|
.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("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("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("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();
|
.get_matches();
|
||||||
|
|
||||||
if matches.is_present("version") {
|
if matches.is_present("version") {
|
||||||
information!("{} v{}", NAME, VERSION);
|
information!("{} v{}", NAME, VERSION);
|
||||||
return Ok(());
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
let email = match matches.value_of("email") {
|
let email = matches.value_of("email").unwrap_or_else(|| {
|
||||||
Some(email) => email,
|
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 => {
|
None => {
|
||||||
failure!("Email is not assigned.");
|
failure!("Port is not assigned.");
|
||||||
return Ok(());
|
exit(1);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let _type = matches.value_of("type").expect("Failed to get type");
|
let timeout: u64 = match matches.value_of("timeout") {
|
||||||
let port: u16 = matches.value_of("port").expect("Failed to get port").parse().expect("Failed to parse port");
|
Some(p) => p.parse().unwrap_or_else(|e| {
|
||||||
|
failure!("Parse timeout: {}, failed: {}", p, e);
|
||||||
let domains_val = match matches.values_of("domain") {
|
exit(1);
|
||||||
Some(domain_val) => domain_val,
|
}),
|
||||||
None => {
|
None => {
|
||||||
failure!("Domains is not assigned.");
|
failure!("Timeout is not assigned.");
|
||||||
return Ok(());
|
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::<Vec<_>>();
|
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);
|
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::<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);
|
||||||
|
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<i32>, port: u16) {
|
||||||
async_std::task::spawn(async move {
|
async_std::task::spawn(async move {
|
||||||
information!("Listen at 0.0.0.0:{}", port);
|
information!("Listen at 0.0.0.0:{}", port);
|
||||||
let mut app = tide::new();
|
let mut app = tide::new();
|
||||||
@@ -67,76 +167,26 @@ async fn main() -> tide::Result<()> {
|
|||||||
let token = match req.param("token") {
|
let token = match req.param("token") {
|
||||||
Ok(token) => token,
|
Ok(token) => token,
|
||||||
Err(e) => {
|
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());
|
return Ok("400 - bad request".to_string());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let auth_token = { TOKEN_MAP.read().unwrap().get(token).cloned() };
|
let auth_token = { TOKEN_MAP.read().unwrap().get(token).cloned() };
|
||||||
match auth_token {
|
match auth_token {
|
||||||
Some(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)
|
Ok(auth_token)
|
||||||
}
|
}
|
||||||
None => {
|
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())
|
Ok("404 - not found".to_string())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
s.send(1).await.ok();
|
s.send(1).await.ok();
|
||||||
if let Err(e) = app.listen(&format!("0.0.0.0:{}", port)).await {
|
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);
|
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(())
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user