133 lines
3.8 KiB
Rust
133 lines
3.8 KiB
Rust
/*
|
|
Copyright James Pace 2025
|
|
|
|
This Source Code Form is subject to the terms of the Mozilla Public
|
|
License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
*/
|
|
#![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(())
|
|
}
|