use crate::commands::command::Cmd;
use crate::error::KeyToolError;
use clap::Args;
use openssl::pkcs12::Pkcs12;
use openssl::x509::X509NameRef;
use std::fs;
use std::path::Path;
#[derive(Args, Debug, Clone)]
pub struct ListCmd {
#[arg(long)]
pub keystore: String,
#[arg(long)]
pub storepass: Option<String>,
#[arg(long)]
pub verbose: bool,
}
impl Cmd for ListCmd {
fn run(&self) -> Result<(), KeyToolError> {
let storepass = self.storepass.as_deref().unwrap_or("");
let keystore_path = Path::new(&self.keystore);
let der = fs::read(keystore_path)
.map_err(|e| KeyToolError::new(format!("Failed to read keystore file: {}", e)))?;
let pkcs12 = Pkcs12::from_der(&der)
.map_err(|e| KeyToolError::new(format!("Failed to parse PKCS12 keystore: {}", e)))?;
let parsed = pkcs12.parse2(storepass).map_err(|e| {
KeyToolError::new(format!("Failed to parse PKCS12 with password: {}", e))
})?;
println!("Keystore type: PKCS12");
println!("Keystore file: {}", self.keystore);
println!();
println!("Your keystore contains 1 entry");
println!();
println!("Entry type: PrivateKeyEntry");
let cert = &parsed.cert;
if let Some(cert) = cert {
println!(
"Certificate Subject: {}",
x509_name_to_string(cert.subject_name())
);
if self.verbose {
println!("\nCertificate details:");
println!(" Subject: {}", x509_name_to_string(cert.subject_name()));
println!(" Issuer: {}", x509_name_to_string(cert.issuer_name()));
let serial_hex = match cert.serial_number().to_bn() {
Ok(bn) => match bn.to_hex_str() {
Ok(s) => s.to_string(),
Err(_) => "<error>".to_string(),
},
Err(_) => "<error>".to_string(),
};
println!(" Serial Number: {}", serial_hex);
println!(" Not Before: {}", cert.not_before());
println!(" Not After: {}", cert.not_after());
match cert.digest(openssl::hash::MessageDigest::sha256()) {
Ok(digest) => {
let fingerprint = digest
.iter()
.map(|b| format!("{:02X}", b))
.collect::<Vec<_>>()
.join(":");
println!(" SHA-256 Fingerprint: {}", fingerprint);
}
Err(_) => println!(" SHA-256 Fingerprint: <error>"),
}
}
} else {
println!("No certificate found in the keystore.");
}
Ok(())
}
}
fn x509_name_to_string(name: &X509NameRef) -> String {
let mut parts = Vec::new();
for entry in name.entries() {
let key = entry.object().nid().short_name().unwrap_or("");
let val = match entry.data().as_utf8() {
Ok(s) => s.to_string(),
Err(_) => "<invalid utf8>".to_string(),
};
parts.push(format!("{}={}", key, val));
}
parts.join(", ")
}