use crate::commands::command::Cmd;
use crate::error::KeyToolError;
use clap::Args;
use openssl::asn1::Asn1Time;
use openssl::bn::BigNum;
use openssl::hash::MessageDigest;
use openssl::pkcs12::Pkcs12;
use openssl::pkey::PKey;
use openssl::rsa::Rsa;
use openssl::x509::{X509Builder, X509NameBuilder};
use std::fs::File;
use std::io::Write;
#[derive(Args, Debug, Clone)]
pub struct GenKeyPairCmd {
#[arg(long)]
pub alias: String,
#[arg(long)]
pub keyalg: Option<String>,
#[arg(long)]
pub keysize: Option<u32>,
#[arg(long)]
pub sigalg: Option<String>,
#[arg(long)]
pub validity: Option<u32>,
#[arg(long)]
pub storetype: Option<String>,
#[arg(long)]
pub keystore: Option<String>,
#[arg(long)]
pub storepass: Option<String>,
#[arg(long)]
pub keypass: Option<String>,
#[arg(long)]
pub dname: Option<String>,
#[arg(long)]
pub ext: Option<String>,
#[arg(long)]
pub providername: Option<String>,
}
impl Cmd for GenKeyPairCmd {
fn run(&self) -> Result<(), KeyToolError> {
let keyalg = self.keyalg.as_deref().unwrap_or("RSA");
let keysize = self.keysize.unwrap_or(2048);
let sigalg = self.sigalg.as_deref().unwrap_or("SHA256withRSA");
let validity = self.validity.unwrap_or(90);
let storetype = self.storetype.as_deref().unwrap_or("PKCS12");
let keystore_path = self
.keystore
.as_ref()
.ok_or_else(|| KeyToolError::new("Missing --keystore argument"))?;
let storepass = self.storepass.as_deref().unwrap_or("");
let _keypass = self.keypass.as_deref().unwrap_or(storepass);
let dname = self.dname.as_deref().unwrap_or("CN=Unknown");
if keyalg.to_uppercase() != "RSA" {
return Err(KeyToolError::new(
"Currently only RSA algorithm is supported",
));
}
let rsa = Rsa::generate(keysize)?;
let pkey = PKey::from_rsa(rsa)?;
let mut name_builder = X509NameBuilder::new()?;
for part in dname.split(',') {
let kv: Vec<&str> = part.trim().splitn(2, '=').collect();
if kv.len() == 2 {
let (k, v) = (kv[0].to_uppercase(), kv[1]);
match k.as_str() {
"CN" => name_builder.append_entry_by_text("CN", v)?,
"O" => name_builder.append_entry_by_text("O", v)?,
"OU" => name_builder.append_entry_by_text("OU", v)?,
"L" => name_builder.append_entry_by_text("L", v)?,
"ST" => name_builder.append_entry_by_text("ST", v)?,
"C" => name_builder.append_entry_by_text("C", v)?,
_ => (), }
}
}
let name = name_builder.build();
let mut builder = X509Builder::new()?;
builder.set_version(2)?;
let serial_number = {
let mut bn = BigNum::new()?;
bn.rand(64, openssl::bn::MsbOption::MAYBE_ZERO, false)?;
bn.to_asn1_integer()?
};
builder.set_serial_number(&serial_number)?;
builder.set_subject_name(&name)?;
builder.set_issuer_name(&name)?;
builder.set_pubkey(&pkey)?;
let not_before = Asn1Time::days_from_now(0)?;
let not_after = Asn1Time::days_from_now(validity)?;
builder.set_not_before(¬_before)?;
builder.set_not_after(¬_after)?;
let md = match sigalg.to_uppercase().as_str() {
"SHA256WITHRSA" => MessageDigest::sha256(),
"SHA1WITHRSA" => MessageDigest::sha1(),
_ => MessageDigest::sha256(),
};
builder.sign(&pkey, md)?;
let cert = builder.build();
if storetype.to_uppercase() == "PKCS12" {
let friendly_name = self.alias.as_str();
let mut pkcs12_builder = Pkcs12::builder();
let pkcs12 = pkcs12_builder
.name(friendly_name)
.pkey(&pkey)
.cert(&cert)
.build2(storepass)
.map_err(|e| KeyToolError::new(format!("Failed to generate PKCS12: {}", e)))?;
let der = pkcs12.to_der()?;
let mut file = File::create(keystore_path)?;
file.write_all(&der)?;
println!(
"Successfully generated PKCS12 keystore file: {}",
keystore_path
);
return Ok(());
}
Err(KeyToolError::new(format!(
"Unsupported keystore type: {}, only PKCS12 is supported currently",
storetype
)))
}
}