tiny-encrypt 1.9.20

A simple and tiny file encrypt tool
use std::cmp::Ordering;
use std::collections::HashMap;

use clap::Args;
use rust_util::{iff, information, warning, XResult};
use tabled::{Table, Tabled};
use tabled::settings::Style;

use crate::config::TinyEncryptConfig;
use crate::temporary_key::serialize_config_envelop;
use crate::util_envelop;

#[derive(Tabled, Eq)]
struct ConfigProfile {
    profiles: String,
    keys: String,
}

impl PartialEq<Self> for ConfigProfile {
    fn eq(&self, other: &Self) -> bool {
        self.profiles.eq(&other.profiles)
    }
}

impl Ord for ConfigProfile {
    fn cmp(&self, other: &Self) -> Ordering {
        self.profiles.cmp(&other.profiles)
    }
}

impl PartialOrd for ConfigProfile {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

#[derive(Tabled)]
pub struct ConfigEnvelop {
    pub r#type: String,
    pub sid: String,
    pub kid: String,
    pub desc: String,
    pub args: String,
}

#[derive(Debug, Args)]
pub struct CmdConfig {
    /// Show KID
    #[arg(long)]
    pub show_kid: bool,
    /// JSON output
    #[arg(long)]
    pub json: bool,
    /// Temporary key output
    #[arg(long)]
    pub temporary_key: bool,
    /// Hide __all__
    #[arg(long)]
    pub hide_all: bool,
    /// Encryption profile (use default when --key-filter is assigned)
    #[arg(long, short = 'p')]
    pub profile: Option<String>,
    /// Encryption key filter (key_id or type:TYPE(e.g. type:piv-p256, type:piv-p384, type:pgp-*), multiple joined by ',', ALL for all)
    #[arg(long, short = 'k')]
    pub key_filter: Option<String>,
}

pub fn config(cmd_config: CmdConfig) -> XResult<()> {
    let config = TinyEncryptConfig::load_default(&None)?;

    if cmd_config.json {
        let mut config = config;
        config.includes = None;
        if let Some(profiles) = &mut config.profiles {
            profiles.remove("__all__");
        }
        println!("{}", serde_json::to_string_pretty(&config)?);
        return Ok(());
    }

    if cmd_config.temporary_key {
        let envelops = if cmd_config.profile.is_some() || cmd_config.key_filter.is_some() {
            config.find_envelops(&cmd_config.profile, &cmd_config.key_filter)?
        } else {
            config.find_envelops(&None, &None)?
        };
        for envelop in envelops {
            let k = serialize_config_envelop(envelop);
            println!("{}", k);
        }
        return Ok(());
    }

    if cmd_config.profile.is_some() || cmd_config.key_filter.is_some() {
        return config_key_filter(&cmd_config, &config);
    }
    config_profiles(&cmd_config, &config)
}

fn config_key_filter(cmd_version: &CmdConfig, config: &TinyEncryptConfig) -> XResult<()> {
    let envelops = config.find_envelops(&cmd_version.profile, &cmd_version.key_filter)?;
    if envelops.is_empty() { warning!("Found no envelops"); }
    information!("Found {} envelops", envelops.len());
    let mut config_envelops = vec![];
    for envelop in envelops {
        let hardware_security_mark = match envelop.r#type.is_hardware_security() {
            None => " ?",
            Some(hardware_security) => iff!(hardware_security, " *", "")
        };
        config_envelops.push(ConfigEnvelop {
            r#type: format!("{}{}", envelop.r#type.get_name(), hardware_security_mark),
            sid: strip_field(&envelop.sid.as_ref().map(ToString::to_string).unwrap_or_else(|| "-".to_string()), 25),
            kid: strip_field(&envelop.kid, 40),
            desc: strip_field(&envelop.desc.as_ref().map(ToString::to_string).unwrap_or_else(|| "-".to_string()), 40),
            args: strip_field(&envelop.args.as_ref().map(|a| format!("[{}]", a.join(", "))).unwrap_or_else(|| "-".to_string()), 20),
        });
    }
    let mut table = Table::new(config_envelops);
    table.with(Style::sharp());
    println!("{}", table);
    println!("> Type with * is hardware security");
    Ok(())
}

fn strip_field(kid: &str, max_len: usize) -> String {
    if kid.len() <= max_len {
        kid.to_string()
    } else {
        kid.chars().enumerate()
            .filter(|(i, _c)| *i < max_len)
            .map(|(i, c)| iff!(i >= (max_len - 3), '.', c)).collect()
    }
}

fn config_profiles(cmd_config: &CmdConfig, config: &TinyEncryptConfig) -> XResult<()> {
    let mut reverse_map = HashMap::new();
    if let Some(profiles) = &config.profiles {
        for (p, v) in profiles {
            let mut v2 = v.clone();
            v2.sort();
            let vs = v2.join(",");
            match reverse_map.get_mut(&vs) {
                None => { reverse_map.insert(vs, vec![(p, v)]); }
                Some(vec) => { vec.push((p, v)); }
            }
        }
    }

    let mut config_profiles = vec![];
    for pvs in reverse_map.values() {
        let mut ps: Vec<_> = pvs.iter().map(|pv| pv.0).collect();
        ps.sort();
        let pp = ps.iter().map(|s| s.to_string()).collect::<Vec<_>>().join(", ");
        if cmd_config.hide_all && pp == "__all__" {
            continue;
        }
        let kids = pvs[0].1;
        let mut ks = Vec::with_capacity(kids.len());
        for kid in kids {
            match config.find_by_kid(kid) {
                None => {
                    ks.push(format!("[ERROR] Key not found: {}", kid));
                }
                Some(envelop) => {
                    let kid = if cmd_config.show_kid {
                        format!("Kid: {}", envelop.kid)
                    } else {
                        envelop.sid.as_ref()
                            .map(|sid| format!("Sid: {}", sid))
                            .unwrap_or_else(|| format!("Kid: {}", envelop.kid))
                    };
                    let desc = envelop.desc.as_ref()
                        .map(|desc| format!(", Desc: {}", desc))
                        .unwrap_or_else(|| "".to_string());
                    ks.push(format!(
                        "{}, {}{}",
                        util_envelop::with_width_type(envelop.r#type.get_name()), kid, desc
                    ));
                }
            }
        }
        config_profiles.push(ConfigProfile {
            profiles: pp,
            keys: ks.join("\n"),
        });
    }
    config_profiles.sort();

    let mut table = Table::new(config_profiles);
    table.with(Style::modern());
    println!("{}", table);

    Ok(())
}