summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGalen Guyer <galen@galenguyer.com>2022-07-18 20:54:36 -0400
committerGalen Guyer <galen@galenguyer.com>2022-07-18 20:54:36 -0400
commit56d131c935422ac9b281c89b03836787535dc60f (patch)
treecffde3cd0211ec6a1394b5c4be3ea8eb6d8b604c
parent657934b1f408127799174984cc42932ac7bc1f83 (diff)
Add renew commandv1.4.0
-rw-r--r--Cargo.lock2
-rw-r--r--Cargo.toml2
-rw-r--r--src/cli.rs19
-rw-r--r--src/lib/cert.rs1
-rw-r--r--src/lib/ops.rs147
5 files changed, 127 insertions, 44 deletions
diff --git a/Cargo.lock b/Cargo.lock
index babeef7..47c45b5 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -146,7 +146,7 @@ dependencies = [
[[package]]
name = "hancock"
-version = "1.3.0"
+version = "1.4.0"
dependencies = [
"clap",
"dirs",
diff --git a/Cargo.toml b/Cargo.toml
index ae5fdbf..7369bb1 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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"
diff --git a/src/cli.rs b/src/cli.rs
index 7e137a8..964f8cb 100644
--- a/src/cli.rs
+++ b/src/cli.rs
@@ -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
+}