bit-twiddler 0.2.0

Cross-platform developer toolbox: bit manipulation, hashing, YAML/JSON/SQL, QR, Markdown, cron, and 40+ more tools — Tauri v2, no Node.js
use digest::Digest;
use serde::Serialize;
use sha1::Sha1;
use sha2::Sha256;
use x509_parser::prelude::*;

#[derive(Serialize)]
pub struct CertInfo {
    pub subject: String,
    pub issuer: String,
    pub serial: String,
    pub not_before: String,
    pub not_after: String,
    pub is_expired: bool,
    pub san: Vec<String>,
    pub key_algorithm: String,
    pub sig_algorithm: String,
    pub fingerprint_sha256: String,
    pub fingerprint_sha1: String,
    pub is_ca: bool,
}

fn colon_hex(bytes: &[u8]) -> String {
    bytes
        .iter()
        .map(|b| format!("{b:02X}"))
        .collect::<Vec<_>>()
        .join(":")
}

fn oid_name(oid: &oid_registry::Oid) -> String {
    x509_parser::objects::oid_registry()
        .get(oid)
        .map(|entry| entry.sn().to_string())
        .unwrap_or_else(|| oid.to_string())
}

#[tauri::command]
pub fn inspect_certificate(pem: String) -> Result<CertInfo, String> {
    let (_, pem_block) = parse_x509_pem(pem.as_bytes()).map_err(|e| e.to_string())?;
    let cert = pem_block.parse_x509().map_err(|e| e.to_string())?;

    let san = cert
        .subject_alternative_name()
        .ok()
        .flatten()
        .map(|ext| {
            ext.value
                .general_names
                .iter()
                .map(|name| match name {
                    GeneralName::DNSName(s) => s.to_string(),
                    GeneralName::IPAddress(ip) => format!("{ip:?}"),
                    GeneralName::RFC822Name(s) => s.to_string(),
                    GeneralName::URI(s) => s.to_string(),
                    other => format!("{other:?}"),
                })
                .collect()
        })
        .unwrap_or_default();

    let key_algorithm = match cert.public_key().parsed() {
        Ok(pk) => {
            let kind = match &pk {
                x509_parser::public_key::PublicKey::RSA(_) => "RSA",
                x509_parser::public_key::PublicKey::EC(_) => "EC",
                x509_parser::public_key::PublicKey::DSA(_) => "DSA",
                _ => "Unknown",
            };
            let bits = pk.key_size();
            if bits > 0 {
                format!("{kind} {bits} bits")
            } else {
                kind.to_string()
            }
        }
        Err(_) => oid_name(&cert.public_key().algorithm.algorithm),
    };

    let raw = cert.as_raw();

    Ok(CertInfo {
        subject: cert.subject().to_string(),
        issuer: cert.issuer().to_string(),
        serial: cert.raw_serial_as_string(),
        not_before: cert.validity().not_before.to_string(),
        not_after: cert.validity().not_after.to_string(),
        is_expired: !cert.validity().is_valid(),
        san,
        key_algorithm,
        sig_algorithm: oid_name(&cert.signature_algorithm.algorithm),
        fingerprint_sha256: colon_hex(&Sha256::digest(raw)),
        fingerprint_sha1: colon_hex(&Sha1::digest(raw)),
        is_ca: cert.is_ca(),
    })
}