diff options
author | Galen Guyer <galen@galenguyer.com> | 2022-07-18 20:54:36 -0400 |
---|---|---|
committer | Galen Guyer <galen@galenguyer.com> | 2022-07-18 20:54:36 -0400 |
commit | 56d131c935422ac9b281c89b03836787535dc60f (patch) | |
tree | cffde3cd0211ec6a1394b5c4be3ea8eb6d8b604c | |
parent | 657934b1f408127799174984cc42932ac7bc1f83 (diff) |
Add renew commandv1.4.0
-rw-r--r-- | Cargo.lock | 2 | ||||
-rw-r--r-- | Cargo.toml | 2 | ||||
-rw-r--r-- | src/cli.rs | 19 | ||||
-rw-r--r-- | src/lib/cert.rs | 1 | ||||
-rw-r--r-- | src/lib/ops.rs | 147 |
5 files changed, 127 insertions, 44 deletions
@@ -146,7 +146,7 @@ dependencies = [ [[package]] name = "hancock" -version = "1.3.0" +version = "1.4.0" dependencies = [ "clap", "dirs", @@ -1,7 +1,7 @@ [package] name = "hancock" authors = ["Galen Guyer <galen@galenguyer.com>"] -version = "1.3.0" +version = "1.4.0" edition = "2021" license = "MIT" @@ -1,5 +1,5 @@ -use std::path::Path; use clap::{Parser, Subcommand}; +use std::path::Path; use hancock::ops::*; @@ -16,15 +16,25 @@ pub enum Commands { Init(Init), Issue(Issue), List(List), + Renew(Renew), } fn main() { let env_file = match Path::new(".env").exists() { true => Some(String::from(".env")), false => { - match Path::new(&dirs::home_dir().unwrap()).join(".hancock.conf").exists() { - true => Some(Path::new(&dirs::home_dir().unwrap()).join(".hancock.conf").to_str().unwrap().to_owned()), - false => None + match Path::new(&dirs::home_dir().unwrap()) + .join(".hancock.conf") + .exists() + { + true => Some( + Path::new(&dirs::home_dir().unwrap()) + .join(".hancock.conf") + .to_str() + .unwrap() + .to_owned(), + ), + false => None, } } }; @@ -42,5 +52,6 @@ fn main() { Commands::Init(args) => init(args), Commands::Issue(args) => issue(args), Commands::List(args) => list(args), + Commands::Renew(args) => renew(args), } } diff --git a/src/lib/cert.rs b/src/lib/cert.rs index 14ac9fd..007bfe6 100644 --- a/src/lib/cert.rs +++ b/src/lib/cert.rs @@ -95,7 +95,6 @@ pub fn generate_cert( } pub fn save_cert(path: &str, cert: &X509) { - println!("{}", path); path::ensure_dir(path); write(path, cert.to_pem().unwrap()).unwrap(); } diff --git a/src/lib/ops.rs b/src/lib/ops.rs index 27d874b..43fcede 100644 --- a/src/lib/ops.rs +++ b/src/lib/ops.rs @@ -115,6 +115,22 @@ pub struct List { pub base_dir: String, } +#[derive(Args, Debug)] +#[clap(about = "Renew a certificate or all if no Common Name is specified")] +pub struct Renew { + /// Base directory to store certificates + #[clap(long, default_value = "~/.hancock", env = "CA_BASE_DIR")] + pub base_dir: String, + + /// Certificate CommonName + #[clap(long, short = 'n')] + pub common_name: Option<String>, + + /// Password for private key + #[clap(long, short = 'p', env = "CA_PASSWORD")] + pub password: Option<String>, +} + pub fn init(args: Init) { let base_dir = path::base_dir(&args.base_dir); @@ -226,55 +242,104 @@ pub fn list(args: List) { let name = name.unwrap(); if name.file_type().unwrap().is_dir() { let name = format!("{}", name.file_name().to_string_lossy()); - let rsa_crt_path = path::cert_crt(&base_dir, &name, KeyType::Rsa(0)); - if Path::new(&rsa_crt_path).is_file() { - let crt = cert::read_cert(&rsa_crt_path); - println!("{}", cert_info(crt)); + for key_type in [KeyType::Rsa(0), KeyType::Ecdsa] { + let crt_path = path::cert_crt(&base_dir, &name, key_type); + if Path::new(&crt_path).is_file() { + let crt = cert::read_cert(&crt_path); + println!("{}", cert_info(crt)); + } } - - let ecda_crt_path = path::cert_crt(&base_dir, &name, KeyType::Ecdsa); - if Path::new(&ecda_crt_path).is_file() { - let crt = cert::read_cert(&ecda_crt_path); - println!("{}", cert_info(crt)); - } } } } -fn cert_info(crt: openssl::x509::X509) -> String { - let now = Asn1Time::days_from_now(0).unwrap(); +pub fn renew(args: Renew) { + let base_dir = path::base_dir(&args.base_dir); - let get_cn = |cert: &openssl::x509::X509| -> String { - let cn = cert.subject_name().entries_by_nid(Nid::COMMONNAME); - for entry in cn { - return format!("{}", entry.data().as_utf8().unwrap()); + let rsa_ca_crt_path = path::ca_crt(&base_dir, KeyType::Rsa(0)); + if Path::new(&rsa_ca_crt_path).is_file() { + let crt = cert::read_cert(&rsa_ca_crt_path); + println!("{}", cert_info(crt)); + } + + let ecda_ca_crt_path = path::ca_crt(&base_dir, KeyType::Ecdsa); + if Path::new(&ecda_ca_crt_path).is_file() { + let crt = cert::read_cert(&ecda_ca_crt_path); + println!("{}", cert_info(crt)); + } + + for name in fs::read_dir(&base_dir).unwrap() { + let name = name.unwrap(); + if name.file_type().unwrap().is_dir() { + let name = format!("{}", name.file_name().to_string_lossy()); + for key_type in [KeyType::Rsa(0), KeyType::Ecdsa] { + let crt_path = path::cert_crt(&base_dir, &name, key_type); + if Path::new(&crt_path).is_file() { + let crt = cert::read_cert(&crt_path); + let now = Asn1Time::days_from_now(0).unwrap(); + let original_lifetime = crt.not_before().diff(crt.not_after()).unwrap().days; + + if now.diff(crt.not_after()).unwrap().days < 30 { + // TODO: handle expirations in the past + println!( + "{} expires in {} days, renewing for {} days", + get_cn(&crt).unwrap_or_else(|| String::from("Unknown CN")), + now.diff(crt.not_after()).unwrap().days, + original_lifetime + ); + + let ca_pkey_path = path::ca_pkey(&base_dir, key_type); + + let ca_pkey = match Path::new(&ca_pkey_path).exists() { + true => pkey::read_pkey(&ca_pkey_path, args.password.clone()), + false => { + panic!("No private key for type {} found", key_type.to_string()); + } + }; + + let ca_cert_path = path::ca_crt(&base_dir, key_type); + let ca_cert = cert::read_cert(&ca_cert_path); + + let x509_req = req::read_req(&path::cert_csr( + &base_dir, + &get_cn(&crt).unwrap(), + key_type, + )); + let cert = cert::generate_cert( + original_lifetime as u32, + &x509_req, + &ca_cert, + &ca_pkey, + ); + cert::save_cert( + &path::cert_crt(&base_dir, &get_cn(&crt).unwrap(), key_type), + &cert, + ); + } + } + } } - String::from("Unknown CN") - }; + } +} - let cn = get_cn(&crt); +fn cert_info(crt: openssl::x509::X509) -> String { + let now = Asn1Time::days_from_now(0).unwrap(); + + let cn = get_cn(&crt).unwrap_or_else(|| String::from("Unknown CN")); let orig = crt.not_before().diff(crt.not_after()).unwrap().days; let ex = match now.compare(crt.not_after()).unwrap() { - Ordering::Greater => { - match now.diff(crt.not_after()).unwrap().days { - 1 => { - String::from("1 day ago") - } - d @ _ => { - format!("{} days ago", d) - } + Ordering::Greater => match now.diff(crt.not_after()).unwrap().days { + 1 => String::from("1 day ago"), + d => { + format!("{} days ago", d) } - } - Ordering::Less => { - match now.diff(crt.not_after()).unwrap().days { - 1 => { - String::from("in 1 day") - } - d @ _ => { - format!("in {} days", d) - } + }, + Ordering::Less => match now.diff(crt.not_after()).unwrap().days { + 1 => String::from("in 1 day"), + d => { + format!("in {} days", d) } - } + }, Ordering::Equal => String::from("right now"), }; format!("{cn} - expires {ex} (originally {orig} days)") @@ -291,3 +356,11 @@ fn validate_key_type(input: &str) -> Result<(), String> { )) } } + +fn get_cn(crt: &openssl::x509::X509) -> Option<String> { + let mut cn = crt.subject_name().entries_by_nid(Nid::COMMONNAME); + if let Some(entry) = cn.next() { + return Some(format!("{}", entry.data().as_utf8().unwrap())); + } + None +} |