From d0218ee233f6461e9a5fcf9195132005b34adf4b Mon Sep 17 00:00:00 2001 From: Hatter Jiang Date: Sun, 10 Dec 2023 09:58:08 +0800 Subject: [PATCH] feat: v1.4.0, support PIV RSA key --- Cargo.lock | 2 +- Cargo.toml | 2 +- README.md | 7 ++++++- src/cmd_config.rs | 3 +-- src/cmd_decrypt.rs | 39 ++++++++++++++++++++++++++++++++++++++- src/cmd_encrypt.rs | 10 +++++----- src/cmd_initpiv.rs | 20 ++++++++++++++++++-- src/config.rs | 2 +- src/spec.rs | 5 +++++ src/util.rs | 13 +++++++++++++ src/util_envelop.rs | 2 +- 11 files changed, 90 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f0c1f47..3bd6c2c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1700,7 +1700,7 @@ dependencies = [ [[package]] name = "tiny-encrypt" -version = "1.3.1" +version = "1.4.0" dependencies = [ "aes-gcm-stream", "base64", diff --git a/Cargo.toml b/Cargo.toml index e608a94..6911166 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tiny-encrypt" -version = "1.3.1" +version = "1.4.0" edition = "2021" license = "MIT" description = "A simple and tiny file encrypt tool" diff --git a/README.md b/README.md index a0bda12..ac6ce31 100644 --- a/README.md +++ b/README.md @@ -15,27 +15,31 @@ Specification: [Tiny Encrypt Spec V1.1](https://github.com/OpenWebStandard/tiny- Repository address: https://git.hatter.ink/hatter/tiny-encrypt-rs mirror https://github.com/jht5945/tiny-encrypt-rs Set default encryption algorithm: + ```shell export TINY_ENCRYPT_DEFAULT_ALGORITHM='AES' # or CHACHA20 ``` Compile only encrypt: + ```shell cargo build --release --no-default-features ``` Edit encrypted file: + ```shell tiny-encrypt decrypt --edit-file sample.txt.tinyenc ``` + Read environment `EDITOR` or `SECURE_EDITOR` to edit file, `SECURE_EDITOR` write encrypted file to temp file. Secure editor command format: + ```shell $SECURE_EDITOR "aes-256-gcm" ``` -
Encrypt config `~/.tinyencrypt/config-rs.json`: @@ -78,6 +82,7 @@ Supported PKI encryption types: | piv-p256 | ECDH(secp256r1) | PIV Slot (Previous `ecdh`) | | piv-p384 | ECDH(secp384r1) | PIV Slot (Previous `ecdh-p384`) | | key-p256 | ECDH(secp256r1) | Key Stored in Secure Enclave | +| piv-rsa | PKCS1-v1.5 | PIV Slot | Smart Card(Yubikey) protected ECDH Encryption description: diff --git a/src/cmd_config.rs b/src/cmd_config.rs index 46350a4..d054e82 100644 --- a/src/cmd_config.rs +++ b/src/cmd_config.rs @@ -30,7 +30,7 @@ impl Ord for ConfigProfile { impl PartialOrd for ConfigProfile { fn partial_cmp(&self, other: &Self) -> Option { - self.profiles.partial_cmp(&other.profiles) + Some(self.cmp(other)) } } @@ -98,7 +98,6 @@ fn process_kid(kid: &str) -> String { fn config_profiles(cmd_version: &CmdConfig, config: &TinyEncryptConfig) -> XResult<()> { 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(","); diff --git a/src/cmd_decrypt.rs b/src/cmd_decrypt.rs index 212b74b..e7788d3 100644 --- a/src/cmd_decrypt.rs +++ b/src/cmd_decrypt.rs @@ -28,7 +28,7 @@ use crate::consts::{ }; use crate::crypto_cryptor::{Cryptor, KeyNonce}; use crate::spec::{EncEncryptedMeta, TinyEncryptEnvelop, TinyEncryptEnvelopType, TinyEncryptMeta}; -use crate::util::SecVec; +use crate::util::{decode_base64, SecVec}; use crate::util_digest::DigestWrite; #[cfg(feature = "secure-enclave")] use crate::util_keychainkey; @@ -439,6 +439,7 @@ pub fn try_decrypt_key(config: &Option, TinyEncryptEnvelopType::PivP256 | TinyEncryptEnvelopType::PivP384 => try_decrypt_piv_key_ecdh(config, envelop, pin, slot), #[cfg(feature = "secure-enclave")] TinyEncryptEnvelopType::KeyP256 => try_decrypt_se_key_ecdh(config, envelop), + TinyEncryptEnvelopType::PivRsa => try_decrypt_piv_key_rsa(config, envelop, pin, slot), unknown_type => simple_error!("Unknown or unsupported type: {}", unknown_type.get_name()), } } @@ -483,6 +484,42 @@ fn try_decrypt_piv_key_ecdh(config: &Option, Ok(decrypted_key) } +fn try_decrypt_piv_key_rsa(config: &Option, + envelop: &TinyEncryptEnvelop, + pin: &Option, + slot: &Option) -> XResult> { + let encrypted_key_bytes = opt_result!(decode_base64(&envelop.encrypted_key), "Decode encrypt key failed: {}"); + + let slot = util_piv::read_piv_slot(config, &envelop.kid, slot)?; + let pin = util::read_pin(pin); + + let mut yk = opt_result!(YubiKey::open(), "YubiKey not found: {}"); + let slot_id = util_piv::get_slot_id(&slot)?; + opt_result!(yk.verify_pin(pin.as_bytes()), "YubiKey verify pin failed: {}"); + + let key = opt_result!(decrypt_data( + &mut yk, + &encrypted_key_bytes, + AlgorithmId::Rsa2048, + slot_id, + ), "Decrypt via PIV card failed: {}"); + let key_bytes = key.as_slice(); + if !key_bytes.starts_with(&[0x00, 0x02]) { + return simple_error!("RSA decrypted in error format: {}", hex::encode(key_bytes)); + } + let after_2nd_0_bytes = key_bytes.iter() + .skip(1) + .skip_while(|b| **b != 0x00) + .skip(1) + .copied() + .collect::>(); + + information!(">>>>>>>> {:?}", &after_2nd_0_bytes); + util::zeroize(pin); + util::zeroize(key); + Ok(after_2nd_0_bytes) +} + #[cfg(feature = "secure-enclave")] fn try_decrypt_se_key_ecdh(config: &Option, envelop: &TinyEncryptEnvelop) -> XResult> { diff --git a/src/cmd_encrypt.rs b/src/cmd_encrypt.rs index 637735f..d44d390 100644 --- a/src/cmd_encrypt.rs +++ b/src/cmd_encrypt.rs @@ -266,8 +266,8 @@ fn encrypt_envelops(cryptor: Cryptor, key: &[u8], envelops: &[&TinyEncryptConfig let mut encrypted_envelops = vec![]; for envelop in envelops { match envelop.r#type { - TinyEncryptEnvelopType::PgpRsa => { - encrypted_envelops.push(encrypt_envelop_pgp_rsa(key, envelop)?); + TinyEncryptEnvelopType::PgpRsa | TinyEncryptEnvelopType::PivRsa => { + encrypted_envelops.push(encrypt_envelop_rsa(key, envelop)?); } TinyEncryptEnvelopType::PgpX25519 | TinyEncryptEnvelopType::StaticX25519 => { encrypted_envelops.push(encrypt_envelop_ecdh_x25519(cryptor, key, envelop)?); @@ -342,10 +342,10 @@ fn encrypt_envelop_shared_secret(cryptor: Cryptor, }) } -fn encrypt_envelop_pgp_rsa(key: &[u8], envelop: &TinyEncryptConfigEnvelop) -> XResult { - let pgp_public_key = opt_result!(crypto_rsa::parse_spki(&envelop.public_part), "Parse PGP public key failed: {}"); +fn encrypt_envelop_rsa(key: &[u8], envelop: &TinyEncryptConfigEnvelop) -> XResult { + let pgp_public_key = opt_result!(crypto_rsa::parse_spki(&envelop.public_part), "Parse RSA public key failed: {}"); let mut rng = rand::thread_rng(); - let encrypted_key = opt_result!(pgp_public_key.encrypt(&mut rng, Pkcs1v15Encrypt, key), "PGP public key encrypt failed: {}"); + let encrypted_key = opt_result!(pgp_public_key.encrypt(&mut rng, Pkcs1v15Encrypt, key), "RSA public key encrypt failed: {}"); Ok(TinyEncryptEnvelop { r#type: envelop.r#type, kid: envelop.kid.clone(), diff --git a/src/cmd_initpiv.rs b/src/cmd_initpiv.rs index 501ce13..a7657e1 100644 --- a/src/cmd_initpiv.rs +++ b/src/cmd_initpiv.rs @@ -12,7 +12,8 @@ use yubikey::YubiKey; use crate::config::TinyEncryptConfigEnvelop; use crate::spec::TinyEncryptEnvelopType; -use crate::util_piv; +use crate::{util, util_piv}; +use crate::util_digest::sha256_digest; #[derive(Debug, Args)] pub struct CmdInitPiv { @@ -72,8 +73,23 @@ pub fn init_piv(cmd_init_piv: CmdInitPiv) -> XResult<()> { information!("Config envelop:\n{}", serde_json::to_string_pretty(&config_envelop).unwrap()); } + AlgorithmId::Rsa2048 => { + let spki = opt_result!(cert.subject_public_key_info.to_der(), "Generate SPKI DER failed: {}"); + let config_envelop = TinyEncryptConfigEnvelop { + r#type: TinyEncryptEnvelopType::PivRsa, + sid: Some(format!("piv-{}-rsa2048", &slot_id_hex)), + kid: format!("piv:{}", hex::encode(sha256_digest(&spki))), + desc: Some(format!("PIV --slot {}", &slot_id_hex)), + args: Some(vec![ + slot_id_hex.clone() + ]), + public_part: util::to_pem(&spki, "PUBLIC KEY"), + }; + + information!("Config envelop:\n{}", serde_json::to_string_pretty(&config_envelop).unwrap()); + } _ => { - failure!("Only support P256 or P384, actual: {:?}", algorithm_id); + failure!("Only support P256, P384 or RSA2048, actual: {:?}", algorithm_id); } } } diff --git a/src/config.rs b/src/config.rs index 1b19529..d81f77c 100644 --- a/src/config.rs +++ b/src/config.rs @@ -56,7 +56,7 @@ impl TinyEncryptConfig { pub fn load(file: &str) -> XResult { let resolved_file = resolve_file_path(file); let config_contents = opt_result!( - fs::read_to_string(&resolved_file), "Read config file: {}, failed: {}", file + fs::read_to_string(resolved_file), "Read config file: {}, failed: {}", file ); let mut config: TinyEncryptConfig = opt_result!( serde_json::from_str(&config_contents),"Parse config file: {}, failed: {}", file); diff --git a/src/spec.rs b/src/spec.rs index 02f141d..f841312 100644 --- a/src/spec.rs +++ b/src/spec.rs @@ -85,6 +85,9 @@ pub enum TinyEncryptEnvelopType { // PIV ECDH P384 #[serde(rename = "piv-p384", alias = "ecdh-p384")] PivP384, + // PIV RSA + #[serde(rename = "piv-rsa")] + PivRsa, // KMS, tiny-encrypt-rs is not supported #[serde(rename = "kms")] Kms, @@ -104,6 +107,7 @@ impl TinyEncryptEnvelopType { TinyEncryptEnvelopType::Age => "age", TinyEncryptEnvelopType::PivP256 => "piv-p256", TinyEncryptEnvelopType::PivP384 => "piv-p384", + TinyEncryptEnvelopType::PivRsa => "piv-rsa", TinyEncryptEnvelopType::Kms => "kms", } } @@ -117,6 +121,7 @@ impl TinyEncryptEnvelopType { TinyEncryptEnvelopType::Age => false, TinyEncryptEnvelopType::PivP256 => false, TinyEncryptEnvelopType::PivP384 => false, + TinyEncryptEnvelopType::PivRsa => false, TinyEncryptEnvelopType::Kms => true, } } diff --git a/src/util.rs b/src/util.rs index eaff6cb..d741e50 100644 --- a/src/util.rs +++ b/src/util.rs @@ -109,6 +109,19 @@ pub fn simple_kdf(input: &[u8]) -> Vec { input } +pub fn to_pem(bs: &[u8], name: &str) -> String { + let bs_base64 = encode_base64(bs); + let mut pem = String::with_capacity(bs.len() + 64); + pem.push_str(&format!("-----BEGIN {}-----", name)); + for (i, c) in bs_base64.chars().enumerate() { + if i % 64 == 0 { pem.push('\n'); } + pem.push(c); + } + if !pem.ends_with('\n') { pem.push('\n'); } + pem.push_str(&format!("-----END {}-----", name)); + pem +} + pub fn decode_base64(input: &str) -> XResult> { Ok(general_purpose::STANDARD.decode(input)?) } diff --git a/src/util_envelop.rs b/src/util_envelop.rs index cd2088a..2cf5c0c 100644 --- a/src/util_envelop.rs +++ b/src/util_envelop.rs @@ -11,7 +11,7 @@ pub fn format_envelop(envelop: &TinyEncryptEnvelop, config: &Option