diff --git a/Cargo.toml b/Cargo.toml index 94fb808..5b0586e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,5 +7,5 @@ edition = "2024" anyhow = "1.0.100" clap = { version = "4.5.53", features = ["derive"] } pem = "3.0.6" -rcgen = { version = "0.14.6", features = ["fips"] } +rcgen = { version = "0.14.6", features = ["fips", "x509-parser"] } time = "0.3.44" diff --git a/src/bin/mk_ca.rs b/src/bin/mk_ca.rs index 47d2293..d115913 100644 --- a/src/bin/mk_ca.rs +++ b/src/bin/mk_ca.rs @@ -61,6 +61,10 @@ fn main() -> Result<()> { format!("{}/key.pem", &args.output), key_pair.serialize_pem().as_bytes(), )?; + fs::write( + format!("{}/public.pem", &args.output), + key_pair.public_key_pem().as_bytes(), + )?; Ok(()) } diff --git a/src/bin/mk_cert.rs b/src/bin/mk_cert.rs new file mode 100644 index 0000000..95fb957 --- /dev/null +++ b/src/bin/mk_cert.rs @@ -0,0 +1,125 @@ +#![feature(duration_constructors)] +use anyhow::Result; +use clap::Parser; +use rcgen::{ + CertificateParams, DistinguishedName, DnType, ExtendedKeyUsagePurpose, + Issuer, KeyPair, KeyUsagePurpose, SanType, +}; +use rcgen::string::Ia5String; +use std::fs; +use std::path::PathBuf; +use time::{Duration, OffsetDateTime}; + +/// Generate a Certificate Authority with some opinionated +/// options selected. +#[derive(Parser, Debug)] +#[command(version, about, long_about = None)] +struct Args { + /// Common name for this cert. + #[arg(long)] + common_name: String, + + /// Email address to assign to cert. + #[arg(long)] + email_address: Option, + + /// Domain address to assign to cert. + #[arg(long)] + domain_name: Option, + + /// Set to make this cert valid for client authentication. + #[arg(long)] + client_auth: bool, + + /// Set to make this cert valid for server authentication. + #[arg(long)] + server_auth: bool, + + /// Days for CA to valid for. + #[arg(long, default_value = "365")] + valid_length: i64, + + /// Path to CA folder. + #[arg(long)] + ca_path: String, + + /// Output directory for artifacts. + #[arg(long, short)] + output: String, +} + +fn main() -> Result<()> { + let args = Args::parse(); + + if (args.client_auth && args.server_auth) || (!args.client_auth && !args.server_auth) { + return Err(anyhow::Error::msg( + "Must set one and only one of client or server auth.", + )); + } + + // Set up our identity. + let mut params: CertificateParams = Default::default(); + let earliest_date = OffsetDateTime::now_utc(); + let latest_date = OffsetDateTime::now_utc() + .checked_add(Duration::days(args.valid_length)) + .ok_or(anyhow::Error::msg("Could not get date."))?; + params.not_before = earliest_date; + params.not_after = latest_date; + params.distinguished_name = DistinguishedName::new(); + params + .distinguished_name + .push(DnType::CommonName, args.common_name); + if args.email_address.is_some() { + let email_address = Ia5String::try_from(args.email_address.unwrap())?; + params + .subject_alt_names + .push(SanType::Rfc822Name(email_address)); + } + if args.domain_name.is_some() { + let domain_name = Ia5String::try_from(args.domain_name.unwrap())?; + params + .subject_alt_names + .push(SanType::DnsName(domain_name)); + } + + // Set up our purposes. + params.key_usages.push(KeyUsagePurpose::DigitalSignature); + if args.client_auth { + params + .extended_key_usages + .push(ExtendedKeyUsagePurpose::ClientAuth); + } + if args.server_auth { + params + .extended_key_usages + .push(ExtendedKeyUsagePurpose::ServerAuth); + } + + // Get the ca key pair and cert. + let ca_keys_file = PathBuf::from(format!("{}/key.pem", &args.ca_path)); + let ca_cert_file = PathBuf::from(format!("{}/cert.pem", &args.ca_path)); + + let ca_keys_pem = fs::read_to_string(&ca_keys_file)?; + let ca_cert_pem = fs::read_to_string(&ca_cert_file)?; + + let ca_key_pair = KeyPair::from_pem(&ca_keys_pem)?; + let signer = Issuer::from_ca_cert_pem(&ca_cert_pem, ca_key_pair)?; + + let key_pair = KeyPair::generate()?; + let cert = params.signed_by(&key_pair, &signer)?; + + let pem_serialized = cert.pem(); + + println!("Saving artifacts."); + fs::create_dir_all(&args.output)?; + fs::write( + format!("{}/cert.pem", &args.output), + pem_serialized.as_bytes(), + )?; + fs::write( + format!("{}/key.pem", &args.output), + key_pair.serialize_pem().as_bytes(), + )?; + + Ok(()) +}