Add cert generation.

This commit is contained in:
James Pace 2025-12-13 19:19:56 -05:00
parent 76d606a58f
commit ef8690bcd9
3 changed files with 130 additions and 1 deletions

View File

@ -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"

View File

@ -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(())
}

125
src/bin/mk_cert.rs Normal file
View File

@ -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<String>,
/// Domain address to assign to cert.
#[arg(long)]
domain_name: Option<String>,
/// 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(())
}