keytool 0.1.0

A command-line tool for managing certificates, similar to Java keytool.
Documentation
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;

/**
cargo run -v -list -keystore mykeystore.p12 -storetype PKCS12 -storepass mypassword
cargo run -- list --keystore /Users/liulipeng/Downloads/mykeystore.p12 --storepass mypassword --verbose
**/

#[derive(Args, Debug, Clone)]
pub struct ListCmd {
    /// Path to keystore file (required)
    #[arg(long)]
    pub keystore: String,

    /// Keystore password (default empty)
    #[arg(long)]
    pub storepass: Option<String>,

    /// Verbose output
    #[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(())
    }
}

/// 把 X509NameRef 转成字符串表示,类似 "CN=Test,O=Org,..."
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(", ")
}