From a68f5e0e00906db457e1e18456b7f08989c6bf0c Mon Sep 17 00:00:00 2001 From: Hatter Jiang Date: Tue, 10 Oct 2023 22:28:05 +0800 Subject: [PATCH] feat: v0.2.4, add config subcommand --- Cargo.lock | 74 +++++++++++++++++++++++++++++++++++++++++++++- Cargo.toml | 3 +- README.md | 13 ++++---- src/cmd_config.rs | 66 +++++++++++++++++++++++++++++++++++++++++ src/cmd_decrypt.rs | 26 ++++++++++------ src/main.rs | 7 +++++ src/util_piv.rs | 33 +++++++++++++++++++++ 7 files changed, 204 insertions(+), 18 deletions(-) create mode 100644 src/cmd_config.rs create mode 100644 src/util_piv.rs diff --git a/Cargo.lock b/Cargo.lock index 64e6461..ce05aeb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -231,6 +231,12 @@ version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" +[[package]] +name = "bytecount" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad152d03a2c813c80bb94fedbf3a3f02b28f793e39e7c214c8a0bcc196343de7" + [[package]] name = "byteorder" version = "1.5.0" @@ -1289,6 +1295,17 @@ dependencies = [ "sha2", ] +[[package]] +name = "papergrid" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2ccbe15f2b6db62f9a9871642746427e297b0ceb85f9a7f1ee5ff47d184d0c8" +dependencies = [ + "bytecount", + "fnv", + "unicode-width", +] + [[package]] name = "pbkdf2" version = "0.12.2" @@ -1405,6 +1422,30 @@ dependencies = [ "elliptic-curve", ] +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + [[package]] name = "proc-macro2" version = "1.0.68" @@ -1979,6 +2020,30 @@ dependencies = [ "libc", ] +[[package]] +name = "tabled" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfe9c3632da101aba5131ed63f9eed38665f8b3c68703a6bb18124835c1a5d22" +dependencies = [ + "papergrid", + "tabled_derive", + "unicode-width", +] + +[[package]] +name = "tabled_derive" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99f688a08b54f4f02f0a3c382aefdb7884d3d69609f785bd253dc033243e3fe4" +dependencies = [ + "heck", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "tempfile" version = "3.8.0" @@ -2063,7 +2128,7 @@ dependencies = [ [[package]] name = "tiny-encrypt" -version = "0.2.3" +version = "0.2.4" dependencies = [ "aes-gcm-stream", "base64", @@ -2084,6 +2149,7 @@ dependencies = [ "serde_json", "sha256", "simpledateformat", + "tabled", "x25519-dalek", "x509-parser", "yubikey", @@ -2214,6 +2280,12 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-width" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" + [[package]] name = "unicode-xid" version = "0.2.4" diff --git a/Cargo.toml b/Cargo.toml index f92b3e9..85cfa4c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tiny-encrypt" -version = "0.2.3" +version = "0.2.4" edition = "2021" license = "MIT" description = "A simple and tiny file encrypt tool" @@ -27,6 +27,7 @@ serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" sha256 = "1.4" simpledateformat = "0.1" +tabled = "0.14" x25519-dalek = "2.0" x509-parser = "0.15" yubikey = { version = "0.8", features = ["untested"] } diff --git a/README.md b/README.md index 7ed395e..6d3f86d 100644 --- a/README.md +++ b/README.md @@ -46,13 +46,12 @@ Encrypt config `~/.tinyencrypt/config-rs.json`: Supported PKI encryption types: -| Type | Description | -|------------|------------------------| -| pgp | OpenPGP RSA PKCS1-v1.5 | -| pgp-x25519 | ECDH X25519 | -| ecdh | ECDH P256 | -| ecdh-p384 | ECDH P384 | - +| Type | Algorithm | Description | +|------------|-----------------|------------------------| +| pgp | PKCS1-v1.5 | OpenPGP Encryption Key | +| pgp-x25519 | ECDH(X25519) | OpenPGP Encryption Key | +| ecdh | ECDH(secp256r1) | PIV Slot | +| ecdh-p384 | ECDH(secp384r1) | PIV Slot | Smart Card(Yubikey) protected ECDH Encryption description: diff --git a/src/cmd_config.rs b/src/cmd_config.rs new file mode 100644 index 0000000..443efff --- /dev/null +++ b/src/cmd_config.rs @@ -0,0 +1,66 @@ +use std::collections::HashMap; + +use clap::Args; +use rust_util::XResult; +use tabled::{Table, Tabled}; +use tabled::settings::Style; + +use crate::config::TinyEncryptConfig; +use crate::util::TINY_ENC_CONFIG_FILE; + +#[derive(Tabled)] +struct ConfigProfile { + profiles: String, + keys: String, +} + +#[derive(Debug, Args)] +pub struct CmdConfig {} + +pub fn config(_cmd_version: CmdConfig) -> XResult<()> { + let config = TinyEncryptConfig::load(TINY_ENC_CONFIG_FILE)?; + + let mut reverse_map = HashMap::new(); + for (p, v) in &config.profiles { + let p = p; + 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::>().join(", "); + 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 desc = envelop.desc.as_ref() + .map(|desc| format!(", Desc: {}", desc)) + .unwrap_or_else(|| "".to_string()); + ks.push(format!("{}{}", envelop.kid, desc)); + } + } + } + config_profiles.push(ConfigProfile { + profiles: pp, + keys: ks.join("\n"), + }); + } + + let mut table = Table::new(config_profiles); + table.with(Style::modern()); + println!("{}", table.to_string()); + + Ok(()) +} \ No newline at end of file diff --git a/src/cmd_decrypt.rs b/src/cmd_decrypt.rs index 4d30952..173be13 100644 --- a/src/cmd_decrypt.rs +++ b/src/cmd_decrypt.rs @@ -2,25 +2,30 @@ use std::{fs, io}; use std::fs::File; use std::io::{Read, Write}; use std::path::PathBuf; -use std::str::FromStr; use std::time::Instant; use clap::Args; use openpgp_card::{OpenPgp, OpenPgpTransaction}; use openpgp_card::crypto_data::Cryptogram; -use rust_util::{debugging, failure, iff, information, opt_result, simple_error, success, util_msg, util_term, warning, XResult}; +use rust_util::{ + debugging, failure, iff, information, opt_result, simple_error, success, + util_msg, util_term, warning, XResult, +}; use x509_parser::prelude::FromDer; use x509_parser::x509::SubjectPublicKeyInfo; -use yubikey::piv::{AlgorithmId, decrypt_data, RetiredSlotId, SlotId}; +use yubikey::piv::{AlgorithmId, decrypt_data}; use yubikey::YubiKey; use zeroize::Zeroize; -use crate::{card, file, util}; +use crate::{card, file, util, util_piv}; use crate::compress::GzStreamDecoder; use crate::config::TinyEncryptConfig; use crate::crypto_aes::aes_gcm_decrypt; use crate::spec::{TinyEncryptEnvelop, TinyEncryptEnvelopType, TinyEncryptMeta}; -use crate::util::{ENC_AES256_GCM_P256, ENC_AES256_GCM_P384, ENC_AES256_GCM_X25519, TINY_ENC_CONFIG_FILE, TINY_ENC_FILE_EXT}; +use crate::util::{ + ENC_AES256_GCM_P256, ENC_AES256_GCM_P384, ENC_AES256_GCM_X25519, + TINY_ENC_CONFIG_FILE, TINY_ENC_FILE_EXT, +}; use crate::wrap_key::WrapKey; #[derive(Debug, Args)] @@ -194,10 +199,11 @@ fn try_decrypt_key_ecdh(config: &Option, let epk_bytes = subject_public_key_info.subject_public_key.as_ref(); let mut yk = opt_result!(YubiKey::open(), "YubiKey not found: {}"); - let retired_slot_id = opt_result!(RetiredSlotId::from_str(&slot), "Slot not found: {}"); - let slot_id = SlotId::Retired(retired_slot_id); + let slot_id = util_piv::get_slot_id(&slot)?; opt_result!(yk.verify_pin(pin.as_bytes()), "YubiKey verify pin failed: {}"); - let algo_id = iff!(expected_enc_type == ENC_AES256_GCM_P256, AlgorithmId::EccP256, AlgorithmId::EccP384); + let algo_id = iff!( + expected_enc_type == ENC_AES256_GCM_P256, AlgorithmId::EccP256, AlgorithmId::EccP384 + ); let shared_secret = opt_result!(decrypt_data( &mut yk, &epk_bytes, @@ -322,7 +328,9 @@ fn select_envelop(meta: &TinyEncryptMeta) -> XResult<&TinyEncryptEnvelop> { envelops.iter().enumerate().for_each(|(i, envelop)| { let kid = iff!(envelop.kid.is_empty(), "".into(), format!(", Kid: {}", envelop.kid)); - let desc = envelop.desc.as_ref().map(|desc| format!(", Desc: {}", desc)).unwrap_or_else(|| "".to_string()); + let desc = envelop.desc.as_ref() + .map(|desc| format!(", Desc: {}", desc)) + .unwrap_or_else(|| "".to_string()); println!("#{} {}{}{}", i + 1, envelop.r#type.get_upper_name(), kid, diff --git a/src/main.rs b/src/main.rs index 15523dd..4edee69 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,6 +2,7 @@ extern crate core; use clap::{Parser, Subcommand}; use rust_util::XResult; +use crate::cmd_config::CmdConfig; use crate::cmd_decrypt::CmdDecrypt; use crate::cmd_encrypt::CmdEncrypt; @@ -9,6 +10,7 @@ use crate::cmd_info::CmdInfo; use crate::cmd_version::CmdVersion; mod util; +mod util_piv; mod util_ecdh; mod util_p384; mod util_x25519; @@ -21,6 +23,7 @@ mod wrap_key; mod file; mod card; mod cmd_version; +mod cmd_config; mod cmd_info; mod cmd_decrypt; mod cmd_encrypt; @@ -47,6 +50,9 @@ enum Commands { /// Show version #[command(short_flag = 'v')] Version(CmdVersion), + /// Show Config + #[command(short_flag = 'c')] + Config(CmdConfig), } fn main() -> XResult<()> { @@ -56,5 +62,6 @@ fn main() -> XResult<()> { Commands::Decrypt(cmd_decrypt) => cmd_decrypt::decrypt(cmd_decrypt), Commands::Info(cmd_info) => cmd_info::info(cmd_info), Commands::Version(cmd_version) => cmd_version::version(cmd_version), + Commands::Config(cmd_config) => cmd_config::config(cmd_config), } } \ No newline at end of file diff --git a/src/util_piv.rs b/src/util_piv.rs new file mode 100644 index 0000000..a69067d --- /dev/null +++ b/src/util_piv.rs @@ -0,0 +1,33 @@ +use rust_util::{simple_error, XResult}; +use yubikey::piv::{RetiredSlotId, SlotId}; + +pub fn get_slot_id(slot: &str) -> XResult { + let slot_lower = slot.to_lowercase(); + Ok(match slot_lower.as_str() { + "9a" | "auth" | "authentication" => SlotId::Authentication, + "9c" | "sign" | "signature" => SlotId::Signature, + "9d" | "keym" | "keymanagement" => SlotId::KeyManagement, + "9e" | "card" | "cardauthentication" => SlotId::CardAuthentication, + "r1" | "82" => SlotId::Retired(RetiredSlotId::R1), + "r2" | "83" => SlotId::Retired(RetiredSlotId::R2), + "r3" | "84" => SlotId::Retired(RetiredSlotId::R3), + "r4" | "85" => SlotId::Retired(RetiredSlotId::R4), + "r5" | "86" => SlotId::Retired(RetiredSlotId::R5), + "r6" | "87" => SlotId::Retired(RetiredSlotId::R6), + "r7" | "88" => SlotId::Retired(RetiredSlotId::R7), + "r8" | "89" => SlotId::Retired(RetiredSlotId::R8), + "r9" | "8a" => SlotId::Retired(RetiredSlotId::R9), + "r10" | "8b" => SlotId::Retired(RetiredSlotId::R10), + "r11" | "8c" => SlotId::Retired(RetiredSlotId::R11), + "r12" | "8d" => SlotId::Retired(RetiredSlotId::R12), + "r13" | "8e" => SlotId::Retired(RetiredSlotId::R13), + "r14" | "8f" => SlotId::Retired(RetiredSlotId::R14), + "r15" | "90" => SlotId::Retired(RetiredSlotId::R15), + "r16" | "91" => SlotId::Retired(RetiredSlotId::R16), + "r17" | "92" => SlotId::Retired(RetiredSlotId::R17), + "r18" | "93" => SlotId::Retired(RetiredSlotId::R18), + "r19" | "94" => SlotId::Retired(RetiredSlotId::R19), + "r20" | "95" => SlotId::Retired(RetiredSlotId::R20), + _ => return simple_error!("Unknown slot: {}", slot), + }) +} \ No newline at end of file