From 993f10ba7152637455f7e214a0f54ffcdad213b0 Mon Sep 17 00:00:00 2001 From: Hatter Jiang Date: Sat, 9 Dec 2023 21:56:00 +0800 Subject: [PATCH] feat: v1.3.0, support init-piv --- Cargo.lock | 3 +- Cargo.toml | 6 +- justfile | 2 + src/cmd_decrypt.rs | 2 +- src/cmd_encrypt.rs | 2 +- src/cmd_initkeychain.rs | 3 +- src/cmd_initpiv.rs | 157 ++++++++++++++++++++++++++++++++++++++++ src/cmd_version.rs | 2 + src/lib.rs | 8 +- src/main.rs | 10 ++- src/spec.rs | 9 +-- 11 files changed, 190 insertions(+), 14 deletions(-) create mode 100644 src/cmd_initpiv.rs diff --git a/Cargo.lock b/Cargo.lock index 7151661..80a96c1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1700,7 +1700,7 @@ dependencies = [ [[package]] name = "tiny-encrypt" -version = "1.2.2" +version = "1.3.0" dependencies = [ "aes-gcm-stream", "base64", @@ -1724,6 +1724,7 @@ dependencies = [ "serde", "serde_json", "simpledateformat", + "spki", "swift-rs", "tabled", "x25519-dalek", diff --git a/Cargo.toml b/Cargo.toml index fd49e9f..130f2b7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tiny-encrypt" -version = "1.2.2" +version = "1.3.0" edition = "2021" license = "MIT" description = "A simple and tiny file encrypt tool" @@ -10,7 +10,8 @@ repository = "https://git.hatter.ink/hatter/tiny-encrypt-rs" [features] default = ["decrypt", "macos", "secure-enclave"] -decrypt = ["openpgp-card", "openpgp-card-pcsc", "yubikey"] +decrypt = ["smartcard"] +smartcard = ["openpgp-card", "openpgp-card-pcsc", "yubikey"] macos = ["security-framework"] secure-enclave = ["macos", "swift-rs"] @@ -44,6 +45,7 @@ x509-parser = "0.15" yubikey = { version = "0.8", features = ["untested"], optional = true } zeroize = "1.7" swift-rs = { path = "swift-rs", optional = true } +spki = "0.7.3" [build-dependencies] swift-rs = { path = "swift-rs", features = ["build"], optional = true } diff --git a/justfile b/justfile index bb77663..bf01673 100644 --- a/justfile +++ b/justfile @@ -12,6 +12,8 @@ build-no-features: # Try build all try-build-all: cargo build --no-default-features + cargo build --no-default-features --features smartcard cargo build --no-default-features --features decrypt cargo build --no-default-features --features macos cargo build --no-default-features --features secure-enclave + cargo build diff --git a/src/cmd_decrypt.rs b/src/cmd_decrypt.rs index da02f60..979b712 100644 --- a/src/cmd_decrypt.rs +++ b/src/cmd_decrypt.rs @@ -436,7 +436,7 @@ pub fn try_decrypt_key(config: &Option, TinyEncryptEnvelopType::PgpX25519 => try_decrypt_key_ecdh_pgp_x25519(envelop, pin), #[cfg(feature = "macos")] TinyEncryptEnvelopType::StaticX25519 => try_decrypt_key_ecdh_static_x25519(config, envelop), - TinyEncryptEnvelopType::PivP256 | TinyEncryptEnvelopType::EcdhP384 => try_decrypt_key_ecdh(config, envelop, pin, slot), + TinyEncryptEnvelopType::PivP256 | TinyEncryptEnvelopType::PivP384 => try_decrypt_key_ecdh(config, envelop, pin, slot), #[cfg(feature = "secure-enclave")] TinyEncryptEnvelopType::KeyP256 => try_decrypt_se_key_ecdh(config, envelop), unknown_type => simple_error!("Unknown or unsupported type: {}", unknown_type.get_name()), diff --git a/src/cmd_encrypt.rs b/src/cmd_encrypt.rs index 62e9973..f54a31a 100644 --- a/src/cmd_encrypt.rs +++ b/src/cmd_encrypt.rs @@ -274,7 +274,7 @@ fn encrypt_envelops(cryptor: Cryptor, key: &[u8], envelops: &[&TinyEncryptConfig TinyEncryptEnvelopType::PivP256 | TinyEncryptEnvelopType::KeyP256 => { encrypted_envelops.push(encrypt_envelop_ecdh(cryptor, key, envelop)?); } - TinyEncryptEnvelopType::EcdhP384 => { + TinyEncryptEnvelopType::PivP384 => { encrypted_envelops.push(encrypt_envelop_ecdh_p384(cryptor, key, envelop)?); } _ => return simple_error!("Not supported type: {:?}", envelop.r#type), diff --git a/src/cmd_initkeychain.rs b/src/cmd_initkeychain.rs index 5cb66e6..1f43396 100644 --- a/src/cmd_initkeychain.rs +++ b/src/cmd_initkeychain.rs @@ -24,10 +24,9 @@ pub struct CmdInitKeychain { pub key_name: Option, } -#[allow(dead_code)] const DEFAULT_SERVICE_NAME: &str = "tiny-encrypt"; -pub fn keychain_key(cmd_init_keychain: CmdInitKeychain) -> XResult<()> { +pub fn init_keychain(cmd_init_keychain: CmdInitKeychain) -> XResult<()> { if cmd_init_keychain.secure_enclave { #[cfg(feature = "secure-enclave")] return keychain_key_se(cmd_init_keychain); diff --git a/src/cmd_initpiv.rs b/src/cmd_initpiv.rs new file mode 100644 index 0000000..f58bf19 --- /dev/null +++ b/src/cmd_initpiv.rs @@ -0,0 +1,157 @@ +use clap::Args; +use p256::pkcs8::der::Decode; +use rust_util::{failure, iff, information, opt_result, simple_error, warning, XResult}; +use spki::{ObjectIdentifier, SubjectPublicKeyInfoOwned}; +use spki::der::Encode; +use x509_parser::prelude::FromDer; +use x509_parser::public_key::RSAPublicKey; +use yubikey::Certificate; +use yubikey::Key; +use yubikey::piv::{AlgorithmId, RetiredSlotId, SlotId}; +use yubikey::YubiKey; + +use crate::config::TinyEncryptConfigEnvelop; +use crate::spec::TinyEncryptEnvelopType; + +#[derive(Debug, Args)] +pub struct CmdInitPiv { + /// PIV slot + #[arg(long, short = 's')] + pub slot: String, +} + +const RSA: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.2.840.113549.1.1.1"); +const ECC: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.2.840.10045.2.1"); + +const ECC_P256: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.2.840.10045.3.1.7"); +const ECC_P384: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.3.132.0.34"); + +pub fn init_piv(cmd_init_piv: CmdInitPiv) -> XResult<()> { + let mut yk = opt_result!(YubiKey::open(), "YubiKey not found: {}"); + let slot_id = get_slot_id(&cmd_init_piv.slot)?; + let slot_id_hex = to_slot_hex(&slot_id); + let keys = opt_result!(Key::list(&mut yk), "List keys failed: {}"); + + let find_key = || { + for k in &keys { + let key_slot_str = format!("{:x}", Into::::into(k.slot())); + if slot_equals(&slot_id, &key_slot_str) { + return Some(k); + } + } + None + }; + let key = match find_key() { + None => { + warning!("Key not found."); + return Ok(()); + } + Some(key) => key, + }; + let cert = &key.certificate().cert.tbs_certificate; + if let Ok(algorithm_id) = get_algorithm_id_by_certificate(key.certificate()) { + let public_key_bit_string = &cert.subject_public_key_info.subject_public_key; + match algorithm_id { + AlgorithmId::EccP256 | AlgorithmId::EccP384 => { + let pk_point_hex = public_key_bit_string.raw_bytes(); + let public_key_point_hex = hex::encode(pk_point_hex); + let compressed_public_key_point_hex = format!("02{}", hex::encode(&pk_point_hex[1..(pk_point_hex.len() / 2) + 1])); + + let is_p256 = algorithm_id == AlgorithmId::EccP256; + let config_envelop = TinyEncryptConfigEnvelop { + r#type: iff!(is_p256, TinyEncryptEnvelopType::PivP256, TinyEncryptEnvelopType::PivP384), + sid: Some(format!("piv-{}-ecdh-{}", &slot_id_hex, iff!(is_p256, "p256", "p384"))), + kid: compressed_public_key_point_hex.clone(), + desc: Some(format!("PIV --slot {}", &slot_id_hex)), + args: Some(vec![ + slot_id_hex.clone() + ]), + public_part: public_key_point_hex, + }; + + information!("Config envelop:\n{}", serde_json::to_string_pretty(&config_envelop).unwrap()); + } + _ => { + failure!("Only support P256 or P384, actual: {:?}", algorithm_id); + } + } + } + + Ok(()) +} + + +fn get_algorithm_id_by_certificate(certificate: &Certificate) -> XResult { + let tbs_certificate = &certificate.cert.tbs_certificate; + get_algorithm_id(&tbs_certificate.subject_public_key_info) +} + +fn get_algorithm_id(public_key_info: &SubjectPublicKeyInfoOwned) -> XResult { + if public_key_info.algorithm.oid == RSA { + let rsa_public_key = opt_result!( + RSAPublicKey::from_der(public_key_info.subject_public_key.raw_bytes()), "Parse public key failed: {}"); + let starts_with_0 = rsa_public_key.1.modulus.starts_with(&[0]); + let public_key_bits = (rsa_public_key.1.modulus.len() - if starts_with_0 { 1 } else { 0 }) * 8; + if public_key_bits == 1024 { + return Ok(AlgorithmId::Rsa1024); + } + if public_key_bits == 2048 { + return Ok(AlgorithmId::Rsa2048); + } + return simple_error!("Unknown rsa bits: {}", public_key_bits); + } + if public_key_info.algorithm.oid == ECC { + if let Some(any) = &public_key_info.algorithm.parameters { + let any_parameter_der = opt_result!(any.to_der(), "Bad any parameter: {}"); + let any_parameter_oid = opt_result!(ObjectIdentifier::from_der(&any_parameter_der), "Bad any parameter der: {}"); + if any_parameter_oid == ECC_P256 { + return Ok(AlgorithmId::EccP256); + } + if any_parameter_oid == ECC_P384 { + return Ok(AlgorithmId::EccP384); + } + return simple_error!("Unknown any parameter oid: {}", any_parameter_oid); + } + } + simple_error!("Unknown algorithm: {}", public_key_info.algorithm.oid) +} + +fn slot_equals(slot_id: &SlotId, slot: &str) -> bool { + get_slot_id(slot).map(|sid| &sid == slot_id).unwrap_or(false) +} + +fn to_slot_hex(slot: &SlotId) -> String { + let slot_id: u8 = (*slot).into(); + format!("{:x}", slot_id) +} + +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), + }) +} diff --git a/src/cmd_version.rs b/src/cmd_version.rs index 353e73a..c0618dc 100644 --- a/src/cmd_version.rs +++ b/src/cmd_version.rs @@ -14,6 +14,8 @@ pub fn version(_cmd_version: CmdVersion) -> XResult<()> { features.push("decrypt".to_string()); #[cfg(feature = "macos")] features.push("macos".to_string()); + #[cfg(feature = "smartcard")] + features.push("smartcard".to_string()); #[cfg(feature = "secure-enclave")] features.push(format!("secure-enclave{}", iff!(util_keychainkey::is_support_se(), "*", ""))); if features.is_empty() { features.push("-".to_string()); } diff --git a/src/lib.rs b/src/lib.rs index b12fe80..fc5fd0c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,7 +22,11 @@ pub use cmd_info::info_single; #[cfg(feature = "macos")] pub use cmd_initkeychain::CmdInitKeychain; #[cfg(feature = "macos")] -pub use cmd_initkeychain::keychain_key; +pub use cmd_initkeychain::init_keychain; +#[cfg(feature = "smartcard")] +pub use cmd_initpiv::CmdInitPiv; +#[cfg(feature = "smartcard")] +pub use cmd_initpiv::init_piv; pub use cmd_version::CmdVersion; pub use cmd_version::version; @@ -57,6 +61,8 @@ mod cmd_encrypt; mod cmd_directdecrypt; #[cfg(feature = "macos")] mod cmd_initkeychain; +#[cfg(feature = "smartcard")] +mod cmd_initpiv; #[cfg(feature = "macos")] mod util_keychainstatic; #[cfg(feature = "decrypt")] diff --git a/src/main.rs b/src/main.rs index f2f4ed1..4120f28 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,6 +10,8 @@ use tiny_encrypt::CmdDecrypt; use tiny_encrypt::CmdExecEnv; #[cfg(feature = "macos")] use tiny_encrypt::CmdInitKeychain; +#[cfg(feature = "smartcard")] +use tiny_encrypt::CmdInitPiv; #[derive(Debug, Parser)] #[command(name = "tiny-encrypt-rs")] @@ -38,6 +40,10 @@ enum Commands { /// Init Keychain (Secure Enclave or Static) #[command(arg_required_else_help = true, short_flag = 'K')] InitKeychain(CmdInitKeychain), + #[cfg(feature = "smartcard")] + /// Init PIV + #[command(arg_required_else_help = true, short_flag = 'P')] + InitPiv(CmdInitPiv), #[cfg(feature = "decrypt")] /// Execute environment #[command(arg_required_else_help = true, short_flag = 'X')] @@ -59,7 +65,9 @@ fn main() -> XResult<()> { Commands::DirectDecrypt(cmd_direct_decrypt) => tiny_encrypt::direct_decrypt(cmd_direct_decrypt), Commands::Info(cmd_info) => tiny_encrypt::info(cmd_info), #[cfg(feature = "macos")] - Commands::InitKeychain(cmd_keychain_key) => tiny_encrypt::keychain_key(cmd_keychain_key), + Commands::InitKeychain(cmd_keychain_key) => tiny_encrypt::init_keychain(cmd_keychain_key), + #[cfg(feature = "smartcard")] + Commands::InitPiv(cmd_init_piv) => tiny_encrypt::init_piv(cmd_init_piv), #[cfg(feature = "decrypt")] Commands::ExecEnv(cmd_exec_env) => tiny_encrypt::exec_env(cmd_exec_env), Commands::Version(cmd_version) => tiny_encrypt::version(cmd_version), diff --git a/src/spec.rs b/src/spec.rs index ea55f8b..02f141d 100644 --- a/src/spec.rs +++ b/src/spec.rs @@ -1,8 +1,7 @@ -use std::fs::Metadata; - use rust_util::{opt_result, util_time, XResult}; use rust_util::util_time::get_millis; use serde::{Deserialize, Serialize}; +use std::fs::Metadata; use crate::{compress, crypto_simple}; use crate::consts::SALT_META; @@ -85,7 +84,7 @@ pub enum TinyEncryptEnvelopType { PivP256, // PIV ECDH P384 #[serde(rename = "piv-p384", alias = "ecdh-p384")] - EcdhP384, + PivP384, // KMS, tiny-encrypt-rs is not supported #[serde(rename = "kms")] Kms, @@ -104,7 +103,7 @@ impl TinyEncryptEnvelopType { TinyEncryptEnvelopType::KeyP256 => "key-p256", TinyEncryptEnvelopType::Age => "age", TinyEncryptEnvelopType::PivP256 => "piv-p256", - TinyEncryptEnvelopType::EcdhP384 => "piv-p384", + TinyEncryptEnvelopType::PivP384 => "piv-p384", TinyEncryptEnvelopType::Kms => "kms", } } @@ -117,7 +116,7 @@ impl TinyEncryptEnvelopType { TinyEncryptEnvelopType::KeyP256 => true, TinyEncryptEnvelopType::Age => false, TinyEncryptEnvelopType::PivP256 => false, - TinyEncryptEnvelopType::EcdhP384 => false, + TinyEncryptEnvelopType::PivP384 => false, TinyEncryptEnvelopType::Kms => true, } }