summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGalen Guyer <galen@galenguyer.com>2022-03-27 23:26:43 -0400
committerGalen Guyer <galen@galenguyer.com>2022-03-27 23:26:43 -0400
commitf86d704b3c638867e78718add25a762c44a06962 (patch)
tree3fb18bba066dcd4e63f690b38f7b859cb4981e4f
parent1ffaa4a0b1283f053c18a410080bad04e0583ada (diff)
generate ca rsa key if not present
-rw-r--r--.gitignore1
-rw-r--r--Cargo.lock199
-rw-r--r--Cargo.toml4
-rw-r--r--src/cli.rs69
-rw-r--r--src/lib.rs18
-rw-r--r--src/main.rs131
-rw-r--r--src/path.rs27
7 files changed, 371 insertions, 78 deletions
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,7 +45,9 @@ checksum = "d8c93436c21e4698bacadf42917db28b23017027a4deccb35dbe47a7e7840123"
dependencies = [
"atty",
"bitflags",
+ "clap_derive",
"indexmap",
+ "lazy_static",
"os_str_bytes",
"strsim",
"termcolor",
@@ -53,6 +55,40 @@ dependencies = [
]
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -68,11 +104,24 @@ 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]]
@@ -82,6 +131,12 @@ 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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -101,6 +156,12 @@ dependencies = [
]
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -155,18 +216,118 @@ dependencies = [
]
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -182,12 +343,50 @@ 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"
source = "registry+https://github.com/rust-lang/crates.io-index"
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<String>,
+
+ #[clap(long)]
+ pub country: Option<String>,
+
+ #[clap(long)]
+ pub state: Option<String>,
+
+ #[clap(long)]
+ pub locality: Option<String>,
+
+ #[clap(long)]
+ pub organization: Option<String>,
+
+ #[clap(long)]
+ pub organizational_unit: Option<String>,
+
+ #[clap(long, env = "CA_PASSWORD")]
+ pub password: Option<String>,
+
+ #[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<Private> {
+ 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<Private>) {
+ println!("{}", path);
+ path::ensure_dir(path);
+ write(path, key.private_key_to_pem_pkcs8().unwrap()).unwrap();
+}
+fn generate_root_cert(pkey: PKey<Private>) -> 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();
+}