From f86d704b3c638867e78718add25a762c44a06962 Mon Sep 17 00:00:00 2001 From: Galen Guyer Date: Sun, 27 Mar 2022 23:26:43 -0400 Subject: generate ca rsa key if not present --- .gitignore | 1 + Cargo.lock | 199 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 4 +- src/cli.rs | 69 +++++++++++++++++++++ src/lib.rs | 18 ++++++ src/main.rs | 131 +++++++++++++++++---------------------- src/path.rs | 27 +++++++++ 7 files changed, 371 insertions(+), 78 deletions(-) create mode 100644 src/cli.rs create mode 100644 src/lib.rs create mode 100644 src/path.rs diff --git a/.gitignore b/.gitignore index ea8c4bf..fedaa2b 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ /target +.env diff --git a/Cargo.lock b/Cargo.lock index 6418cb3..925d5c0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -45,13 +45,49 @@ checksum = "d8c93436c21e4698bacadf42917db28b23017027a4deccb35dbe47a7e7840123" dependencies = [ "atty", "bitflags", + "clap_derive", "indexmap", + "lazy_static", "os_str_bytes", "strsim", "termcolor", "textwrap", ] +[[package]] +name = "clap_derive" +version = "3.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da95d038ede1a964ce99f49cbe27a7fb538d1da595e4b4f70b8c8f338d17bf16" +dependencies = [ + "heck", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "dirs-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" +dependencies = [ + "cfg-if", + "dirs-sys-next", +] + +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + [[package]] name = "foreign-types" version = "0.3.2" @@ -67,12 +103,25 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" +[[package]] +name = "getrandom" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d39cd93900197114fa1fcb7ae84ca742095eed9442088988ae74fa744e930e77" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + [[package]] name = "hancock" version = "0.1.0" dependencies = [ "clap", "openssl", + "path-absolutize", + "shellexpand", ] [[package]] @@ -81,6 +130,12 @@ version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" +[[package]] +name = "heck" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" + [[package]] name = "hermit-abi" version = "0.1.19" @@ -100,6 +155,12 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + [[package]] name = "libc" version = "0.2.121" @@ -154,18 +215,118 @@ dependencies = [ "memchr", ] +[[package]] +name = "path-absolutize" +version = "3.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a2a79d7c1c4eab523515c4561459b10516d6e7014aa76edc3ea05680d5c5d2d" +dependencies = [ + "path-dedot", +] + +[[package]] +name = "path-dedot" +version = "3.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f326e2a3331685a5e3d4633bb9836bd92126e08037cb512252f3612f616a0b28" +dependencies = [ + "once_cell", +] + [[package]] name = "pkg-config" version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "58893f751c9b0412871a09abd62ecd2a00298c6c83befa223ef98c52aef40cbe" +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quote" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4af2ec4714533fcdf07e886f17025ace8b997b9ce51204ee69b6da831c3da57" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "redox_syscall" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae183fc1b06c149f0c1793e1eb447c8b04bfe46d48e9e48bfb8d2d7ed64ecf0" +dependencies = [ + "bitflags", +] + +[[package]] +name = "redox_users" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7776223e2696f1aa4c6b0170e83212f47296a00424305117d013dfe86fb0fe55" +dependencies = [ + "getrandom", + "redox_syscall", + "thiserror", +] + +[[package]] +name = "shellexpand" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83bdb7831b2d85ddf4a7b148aa19d0587eddbe8671a436b7bd1182eaad0f2829" +dependencies = [ + "dirs-next", +] + [[package]] name = "strsim" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +[[package]] +name = "syn" +version = "1.0.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea297be220d52398dcc07ce15a209fce436d361735ac1db700cab3b6cdfb9f54" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + [[package]] name = "termcolor" version = "1.1.3" @@ -181,12 +342,50 @@ version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb" +[[package]] +name = "thiserror" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-xid" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" + [[package]] name = "vcpkg" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "wasi" +version = "0.10.2+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" + [[package]] name = "winapi" version = "0.3.9" diff --git a/Cargo.toml b/Cargo.toml index 4ea50a4..2c0e2b9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,9 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -clap = "3.1.6" +clap = { version = "3.1.6", features = ["cargo", "derive", "env"] } openssl = "0.10.38" +path-absolutize = "3.0.12" +shellexpand = "2.1.0" [features] diff --git a/src/cli.rs b/src/cli.rs new file mode 100644 index 0000000..3f0f9a0 --- /dev/null +++ b/src/cli.rs @@ -0,0 +1,69 @@ +use clap::{Args, Parser, Subcommand}; + +#[derive(Parser, Debug)] +#[clap(author, version, about, long_about = None)] +#[clap(propagate_version = true)] +pub struct Cli { + #[clap(subcommand)] + pub command: Commands, +} + +#[derive(Subcommand, Debug)] +pub enum Commands { + Init(Init), +} + +#[derive(Args, Debug)] +pub struct Init { + /// Base directory to store certificates + #[clap(long, default_value = "~/.hancock", env = "CA_BASE_DIR")] + pub base_dir: String, + + /// Algorithm to generate private keys ('RSA' or 'ECDSA') + #[clap(long, default_value = "RSA", validator = validate_key_type)] + pub key_type: String, + + /// Length to use when generating an RSA key. Ignored for ECDSA + #[clap(long, default_value_t = 4096)] + pub key_length: u32, + + /// Lifetime in days of the generated certificate + #[clap(long, default_value_t = 365 * 10)] + pub lifetime: u32, + + #[clap(long)] + pub common_name: Option, + + #[clap(long)] + pub country: Option, + + #[clap(long)] + pub state: Option, + + #[clap(long)] + pub locality: Option, + + #[clap(long)] + pub organization: Option, + + #[clap(long)] + pub organizational_unit: Option, + + #[clap(long, env = "CA_PASSWORD")] + pub password: Option, + + #[clap(long)] + pub no_password: bool, +} + +fn validate_key_type(input: &str) -> Result<(), String> { + let input = input.to_string().to_uppercase(); + if input == "RSA" || input == "ECDSA" { + Ok(()) + } else { + Err(format!( + "{} is not a valid key type ['rsa', 'ecdsa']", + input + )) + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..2c421ea --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,18 @@ +use std::str::FromStr; + +pub mod path; + +#[derive(Clone, Copy)] +pub enum KeyType { + Ecdsa, + Rsa(u32), +} + +impl ToString for KeyType { + fn to_string(&self) -> String { + match self { + KeyType::Rsa(_) => String::from("rsa"), + KeyType::Ecdsa => String::from("ecdsa"), + } + } +} diff --git a/src/main.rs b/src/main.rs index ebea249..f1f80b8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,74 +1,74 @@ -#![feature(test)] - -extern crate test; +use clap::Parser; use openssl::asn1::Asn1Time; use openssl::bn::{BigNum, MsbOption}; use openssl::ec::{EcGroup, EcKey}; use openssl::hash::MessageDigest; use openssl::nid::Nid; -use openssl::pkey::PKey; +use openssl::pkey::{Id, PKey, Private, self}; use openssl::rsa::Rsa; use openssl::x509::extension::{BasicConstraints, KeyUsage}; use openssl::x509::*; -fn main() { - rsa(2048_u32); - ecdsa(); -} - -fn ecdsa() { - let ec = EcKey::generate(&EcGroup::from_curve_name(Nid::SECP384R1).unwrap()).unwrap(); - // println!("{}", String::from_utf8(ec.private_key_to_pem().unwrap()).unwrap()); - let pkey = PKey::from_ec_key(ec).unwrap(); - - let mut x509_name = X509Name::builder().unwrap(); - x509_name - .append_entry_by_nid(Nid::COMMONNAME, "ligma.dev") - .unwrap(); - let x509_name = x509_name.build(); - - let mut x509_builder = X509::builder().unwrap(); - x509_builder.set_version(2).unwrap(); - x509_builder.set_issuer_name(&x509_name).unwrap(); - x509_builder.set_subject_name(&x509_name).unwrap(); - - x509_builder - .set_not_before(&Asn1Time::days_from_now(0).unwrap()) - .unwrap(); - x509_builder - .set_not_after(&Asn1Time::days_from_now(365).unwrap()) - .unwrap(); - - x509_builder.set_pubkey(&pkey).unwrap(); +use hancock::KeyType; +use hancock::path; - let mut serial = BigNum::new().unwrap(); - serial.rand(128, MsbOption::MAYBE_ZERO, false).unwrap(); - x509_builder - .set_serial_number(&serial.to_asn1_integer().unwrap()) - .unwrap(); +use std::path::Path; +use std::fs::{read, write}; - let basic_constraints = BasicConstraints::new().critical().ca().build().unwrap(); - x509_builder.append_extension(basic_constraints).unwrap(); - let key_usage = KeyUsage::new() - .digital_signature() - .key_encipherment() - .build() - .unwrap(); - x509_builder.append_extension(key_usage).unwrap(); +mod cli; +use crate::cli::*; - x509_builder.sign(&pkey, MessageDigest::sha256()).unwrap(); +fn main() { + let cli = dbg!(Cli::parse()); + + match cli.command { + Commands::Init(args) => { + let base_dir = path::base_dir(&args.base_dir); + + let key_type = match args.key_type.to_uppercase().as_str() { + "RSA" => KeyType::Rsa(args.key_length), + "ECDSA" => KeyType::Ecdsa, + _ => panic!("key_type not ECDSA or RSA after validation. This should never happen"), + }; + + let pkey_path = path::ca_pkey(&base_dir, key_type); + + let pkey = match Path::new(&pkey_path).exists() { + true => { + PKey::private_key_from_pem(&read(&pkey_path).unwrap()).unwrap() + }, + false => { + let pkey = generate_pkey(key_type); + save_pkey(&pkey_path, &pkey); + pkey + } + }; + } + } - let x509 = x509_builder.build(); + // let key_type = KeyType::Ecdsa; + // let pkey = generate_pkey(key_type); + // let root_cert = generate_root_cert(pkey); +} - println!("{}", String::from_utf8(x509.to_pem().unwrap()).unwrap()); +fn generate_pkey(key_type: KeyType) -> PKey { + match key_type { + KeyType::Ecdsa => PKey::from_ec_key( + EcKey::generate(&EcGroup::from_curve_name(Nid::SECP384R1).unwrap()).unwrap(), + ) + .unwrap(), + KeyType::Rsa(bits) => PKey::from_rsa(Rsa::generate(bits).unwrap()).unwrap(), + } } -pub fn rsa(key_size: u32) { - let rsa = Rsa::generate(key_size).unwrap(); - let pkey = PKey::from_rsa(rsa).unwrap(); - // println!("{}", String::from_utf8(rsa.private_key_to_pem().unwrap()).unwrap()); +fn save_pkey(path: &str, key: &PKey) { + println!("{}", path); + path::ensure_dir(path); + write(path, key.private_key_to_pem_pkcs8().unwrap()).unwrap(); +} +fn generate_root_cert(pkey: PKey) -> X509 { let mut x509_name = X509Name::builder().unwrap(); x509_name .append_entry_by_nid(Nid::COMMONNAME, "ligma.dev") @@ -106,28 +106,5 @@ pub fn rsa(key_size: u32) { x509_builder.sign(&pkey, MessageDigest::sha256()).unwrap(); - let x509 = x509_builder.build(); - - println!("{}", String::from_utf8(x509.to_pem().unwrap()).unwrap()); -} - -#[cfg(test)] -mod tests { - use super::*; - use test::Bencher; - - #[bench] - fn bench_rsa_2048(b: &mut Bencher) { - b.iter(|| rsa(2048_u32)); - } - - #[bench] - fn bench_rsa_4096(b: &mut Bencher) { - b.iter(|| rsa(4096_u32)); - } - - #[bench] - fn bench_ecdsa(b: &mut Bencher) { - b.iter(|| ecdsa()); - } + x509_builder.build() } diff --git a/src/path.rs b/src/path.rs new file mode 100644 index 0000000..c7e5f29 --- /dev/null +++ b/src/path.rs @@ -0,0 +1,27 @@ +use crate::KeyType; +use path_absolutize::*; +use shellexpand; +use std::path::Path; +use std::fs::create_dir_all; + +pub fn ca_pkey(base_dir: &str, key_type: KeyType) -> String { + format!("{}/authority.{}.pem", base_dir, key_type.to_string()) +} + +pub fn base_dir(raw_base: &str) -> String { + Path::new(&shellexpand::tilde(&raw_base).to_string()) + .absolutize() + .unwrap() + .to_str() + .unwrap() + .to_string() +} + +pub fn ensure_dir(path: &str) { + let dir = match Path::new(path).is_dir() { + true => path, + false => Path::new(path).parent().unwrap_or(Path::new("/")).to_str().unwrap_or("/"), + }; + + create_dir_all(dir).unwrap(); +} -- cgit v1.2.3