Skip to content

Commit 718939a

Browse files
committed
feat: v1.4.0, support PIV RSA key
1 parent 3b361eb commit 718939a

13 files changed

Lines changed: 177 additions & 105 deletions

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "tiny-encrypt"
3-
version = "1.3.0"
3+
version = "1.4.0"
44
edition = "2021"
55
license = "MIT"
66
description = "A simple and tiny file encrypt tool"
@@ -9,7 +9,7 @@ repository = "https://git.hatter.ink/hatter/tiny-encrypt-rs"
99
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
1010

1111
[features]
12-
default = ["decrypt", "macos", "secure-enclave"]
12+
default = ["decrypt", "macos", "smartcard", "secure-enclave"]
1313
decrypt = ["smartcard"]
1414
smartcard = ["openpgp-card", "openpgp-card-pcsc", "yubikey"]
1515
macos = ["security-framework"]

README.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,27 +15,31 @@ Specification: [Tiny Encrypt Spec V1.1](https://github.com/OpenWebStandard/tiny-
1515
Repository address: https://git.hatter.ink/hatter/tiny-encrypt-rs mirror https://github.com/jht5945/tiny-encrypt-rs
1616

1717
Set default encryption algorithm:
18+
1819
```shell
1920
export TINY_ENCRYPT_DEFAULT_ALGORITHM='AES' # or CHACHA20
2021
```
2122

2223
Compile only encrypt:
24+
2325
```shell
2426
cargo build --release --no-default-features
2527
```
2628

2729
Edit encrypted file:
30+
2831
```shell
2932
tiny-encrypt decrypt --edit-file sample.txt.tinyenc
3033
```
34+
3135
Read environment `EDITOR` or `SECURE_EDITOR` to edit file, `SECURE_EDITOR` write encrypted file to temp file.
3236

3337
Secure editor command format:
38+
3439
```shell
3540
$SECURE_EDITOR <temp-file-name> "aes-256-gcm" <temp-key-hex> <temp-nonce-hex>
3641
```
3742

38-
3943
<br>
4044

4145
Encrypt config `~/.tinyencrypt/config-rs.json`:
@@ -78,6 +82,7 @@ Supported PKI encryption types:
7882
| piv-p256 | ECDH(secp256r1) | PIV Slot (Previous `ecdh`) |
7983
| piv-p384 | ECDH(secp384r1) | PIV Slot (Previous `ecdh-p384`) |
8084
| key-p256 | ECDH(secp256r1) | Key Stored in Secure Enclave |
85+
| piv-rsa | PKCS1-v1.5 | PIV Slot |
8186

8287
Smart Card(Yubikey) protected ECDH Encryption description:
8388

src/cmd_config.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ impl Ord for ConfigProfile {
3030

3131
impl PartialOrd for ConfigProfile {
3232
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
33-
self.profiles.partial_cmp(&other.profiles)
33+
Some(self.cmp(other))
3434
}
3535
}
3636

@@ -98,7 +98,6 @@ fn process_kid(kid: &str) -> String {
9898
fn config_profiles(cmd_version: &CmdConfig, config: &TinyEncryptConfig) -> XResult<()> {
9999
let mut reverse_map = HashMap::new();
100100
for (p, v) in &config.profiles {
101-
let p = p;
102101
let mut v2 = v.clone();
103102
v2.sort();
104103
let vs = v2.join(",");

src/cmd_decrypt.rs

Lines changed: 47 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,12 @@ use crate::consts::{
2828
};
2929
use crate::crypto_cryptor::{Cryptor, KeyNonce};
3030
use crate::spec::{EncEncryptedMeta, TinyEncryptEnvelop, TinyEncryptEnvelopType, TinyEncryptMeta};
31-
use crate::util::SecVec;
31+
use crate::util::{decode_base64, SecVec};
3232
use crate::util_digest::DigestWrite;
33-
#[cfg(feature = "macos")]
34-
use crate::util_keychainstatic;
3533
#[cfg(feature = "secure-enclave")]
3634
use crate::util_keychainkey;
35+
#[cfg(feature = "macos")]
36+
use crate::util_keychainstatic;
3737
use crate::util_progress::Progress;
3838
use crate::wrap_key::WrapKey;
3939

@@ -432,21 +432,22 @@ pub fn try_decrypt_key(config: &Option<TinyEncryptConfig>,
432432
pin: &Option<String>,
433433
slot: &Option<String>) -> XResult<Vec<u8>> {
434434
match envelop.r#type {
435-
TinyEncryptEnvelopType::PgpRsa => try_decrypt_key_pgp(envelop, pin),
435+
TinyEncryptEnvelopType::PgpRsa => try_decrypt_key_pgp_rsa(envelop, pin),
436436
TinyEncryptEnvelopType::PgpX25519 => try_decrypt_key_ecdh_pgp_x25519(envelop, pin),
437437
#[cfg(feature = "macos")]
438438
TinyEncryptEnvelopType::StaticX25519 => try_decrypt_key_ecdh_static_x25519(config, envelop),
439-
TinyEncryptEnvelopType::PivP256 | TinyEncryptEnvelopType::PivP384 => try_decrypt_key_ecdh(config, envelop, pin, slot),
439+
TinyEncryptEnvelopType::PivP256 | TinyEncryptEnvelopType::PivP384 => try_decrypt_piv_key_ecdh(config, envelop, pin, slot),
440440
#[cfg(feature = "secure-enclave")]
441441
TinyEncryptEnvelopType::KeyP256 => try_decrypt_se_key_ecdh(config, envelop),
442+
TinyEncryptEnvelopType::PivRsa => try_decrypt_piv_key_rsa(config, envelop, pin, slot),
442443
unknown_type => simple_error!("Unknown or unsupported type: {}", unknown_type.get_name()),
443444
}
444445
}
445446

446-
fn try_decrypt_key_ecdh(config: &Option<TinyEncryptConfig>,
447-
envelop: &TinyEncryptEnvelop,
448-
pin: &Option<String>,
449-
slot: &Option<String>) -> XResult<Vec<u8>> {
447+
fn try_decrypt_piv_key_ecdh(config: &Option<TinyEncryptConfig>,
448+
envelop: &TinyEncryptEnvelop,
449+
pin: &Option<String>,
450+
slot: &Option<String>) -> XResult<Vec<u8>> {
450451
let wrap_key = WrapKey::parse(&envelop.encrypted_key)?;
451452
let (cryptor, algo_id) = match wrap_key.header.enc.as_str() {
452453
ENC_AES256_GCM_P256 => (Cryptor::Aes256Gcm, AlgorithmId::EccP256),
@@ -483,6 +484,42 @@ fn try_decrypt_key_ecdh(config: &Option<TinyEncryptConfig>,
483484
Ok(decrypted_key)
484485
}
485486

487+
fn try_decrypt_piv_key_rsa(config: &Option<TinyEncryptConfig>,
488+
envelop: &TinyEncryptEnvelop,
489+
pin: &Option<String>,
490+
slot: &Option<String>) -> XResult<Vec<u8>> {
491+
let encrypted_key_bytes = opt_result!(decode_base64(&envelop.encrypted_key), "Decode encrypt key failed: {}");
492+
493+
let slot = util_piv::read_piv_slot(config, &envelop.kid, slot)?;
494+
let pin = util::read_pin(pin);
495+
496+
let mut yk = opt_result!(YubiKey::open(), "YubiKey not found: {}");
497+
let slot_id = util_piv::get_slot_id(&slot)?;
498+
opt_result!(yk.verify_pin(pin.as_bytes()), "YubiKey verify pin failed: {}");
499+
500+
let key = opt_result!(decrypt_data(
501+
&mut yk,
502+
&encrypted_key_bytes,
503+
AlgorithmId::Rsa2048,
504+
slot_id,
505+
), "Decrypt via PIV card failed: {}");
506+
let key_bytes = key.as_slice();
507+
if !key_bytes.starts_with(&[0x00, 0x02]) {
508+
return simple_error!("RSA decrypted in error format: {}", hex::encode(key_bytes));
509+
}
510+
let after_2nd_0_bytes = key_bytes.iter()
511+
.skip(1)
512+
.skip_while(|b| **b != 0x00)
513+
.skip(1)
514+
.copied()
515+
.collect::<Vec<_>>();
516+
517+
information!(">>>>>>>> {:?}", &after_2nd_0_bytes);
518+
util::zeroize(pin);
519+
util::zeroize(key);
520+
Ok(after_2nd_0_bytes)
521+
}
522+
486523
#[cfg(feature = "secure-enclave")]
487524
fn try_decrypt_se_key_ecdh(config: &Option<TinyEncryptConfig>,
488525
envelop: &TinyEncryptEnvelop) -> XResult<Vec<u8>> {
@@ -571,7 +608,7 @@ fn try_decrypt_key_ecdh_static_x25519(config: &Option<TinyEncryptConfig>, envelo
571608
Ok(decrypted_key)
572609
}
573610

574-
fn try_decrypt_key_pgp(envelop: &TinyEncryptEnvelop, pin: &Option<String>) -> XResult<Vec<u8>> {
611+
fn try_decrypt_key_pgp_rsa(envelop: &TinyEncryptEnvelop, pin: &Option<String>) -> XResult<Vec<u8>> {
575612
let mut pgp = util_pgp::get_openpgp()?;
576613
let mut trans = opt_result!(pgp.transaction(), "Connect OpenPGP card failed: {}");
577614

src/cmd_encrypt.rs

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use rsa::Pkcs1v15Encrypt;
1010
use rust_util::{debugging, failure, iff, information, opt_result, simple_error, success, util_size, XResult};
1111
use rust_util::util_time::UnixEpochTime;
1212

13-
use crate::{crypto_cryptor, crypto_simple, util, util_enc_file, util_env, util_p256, util_p384, util_x25519};
13+
use crate::{crypto_cryptor, crypto_simple, util, util_enc_file, util_env};
1414
use crate::compress::GzStreamEncoder;
1515
use crate::config::{TinyEncryptConfig, TinyEncryptConfigEnvelop};
1616
use crate::consts::{
@@ -24,6 +24,7 @@ use crate::spec::{
2424
EncEncryptedMeta, EncMetadata,
2525
TinyEncryptEnvelop, TinyEncryptEnvelopType, TinyEncryptMeta,
2626
};
27+
use crate::util_ecdh::{ecdh_p256, ecdh_p384, ecdh_x25519};
2728
use crate::util_progress::Progress;
2829
use crate::wrap_key::{WrapKey, WrapKeyHeader};
2930

@@ -265,14 +266,14 @@ fn encrypt_envelops(cryptor: Cryptor, key: &[u8], envelops: &[&TinyEncryptConfig
265266
let mut encrypted_envelops = vec![];
266267
for envelop in envelops {
267268
match envelop.r#type {
268-
TinyEncryptEnvelopType::PgpRsa => {
269-
encrypted_envelops.push(encrypt_envelop_pgp(key, envelop)?);
269+
TinyEncryptEnvelopType::PgpRsa | TinyEncryptEnvelopType::PivRsa => {
270+
encrypted_envelops.push(encrypt_envelop_rsa(key, envelop)?);
270271
}
271272
TinyEncryptEnvelopType::PgpX25519 | TinyEncryptEnvelopType::StaticX25519 => {
272273
encrypted_envelops.push(encrypt_envelop_ecdh_x25519(cryptor, key, envelop)?);
273274
}
274275
TinyEncryptEnvelopType::PivP256 | TinyEncryptEnvelopType::KeyP256 => {
275-
encrypted_envelops.push(encrypt_envelop_ecdh(cryptor, key, envelop)?);
276+
encrypted_envelops.push(encrypt_envelop_ecdh_p256(cryptor, key, envelop)?);
276277
}
277278
TinyEncryptEnvelopType::PivP384 => {
278279
encrypted_envelops.push(encrypt_envelop_ecdh_p384(cryptor, key, envelop)?);
@@ -283,9 +284,9 @@ fn encrypt_envelops(cryptor: Cryptor, key: &[u8], envelops: &[&TinyEncryptConfig
283284
Ok(encrypted_envelops)
284285
}
285286

286-
fn encrypt_envelop_ecdh(cryptor: Cryptor, key: &[u8], envelop: &TinyEncryptConfigEnvelop) -> XResult<TinyEncryptEnvelop> {
287+
fn encrypt_envelop_ecdh_p256(cryptor: Cryptor, key: &[u8], envelop: &TinyEncryptConfigEnvelop) -> XResult<TinyEncryptEnvelop> {
287288
let public_key_point_hex = &envelop.public_part;
288-
let (shared_secret, ephemeral_spki) = util_p256::compute_p256_shared_secret(public_key_point_hex)?;
289+
let (shared_secret, ephemeral_spki) = ecdh_p256::compute_p256_shared_secret(public_key_point_hex)?;
289290
let enc_type = match cryptor {
290291
Cryptor::Aes256Gcm => ENC_AES256_GCM_P256,
291292
Cryptor::ChaCha20Poly1305 => ENC_CHACHA20_POLY1305_P256,
@@ -295,7 +296,7 @@ fn encrypt_envelop_ecdh(cryptor: Cryptor, key: &[u8], envelop: &TinyEncryptConfi
295296

296297
fn encrypt_envelop_ecdh_p384(cryptor: Cryptor, key: &[u8], envelop: &TinyEncryptConfigEnvelop) -> XResult<TinyEncryptEnvelop> {
297298
let public_key_point_hex = &envelop.public_part;
298-
let (shared_secret, ephemeral_spki) = util_p384::compute_p384_shared_secret(public_key_point_hex)?;
299+
let (shared_secret, ephemeral_spki) = ecdh_p384::compute_p384_shared_secret(public_key_point_hex)?;
299300
let enc_type = match cryptor {
300301
Cryptor::Aes256Gcm => ENC_AES256_GCM_P384,
301302
Cryptor::ChaCha20Poly1305 => ENC_CHACHA20_POLY1305_P384,
@@ -305,7 +306,7 @@ fn encrypt_envelop_ecdh_p384(cryptor: Cryptor, key: &[u8], envelop: &TinyEncrypt
305306

306307
fn encrypt_envelop_ecdh_x25519(cryptor: Cryptor, key: &[u8], envelop: &TinyEncryptConfigEnvelop) -> XResult<TinyEncryptEnvelop> {
307308
let public_key_point_hex = &envelop.public_part;
308-
let (shared_secret, ephemeral_spki) = util_x25519::compute_x25519_shared_secret(public_key_point_hex)?;
309+
let (shared_secret, ephemeral_spki) = ecdh_x25519::compute_x25519_shared_secret(public_key_point_hex)?;
309310
let enc_type = match cryptor {
310311
Cryptor::Aes256Gcm => ENC_AES256_GCM_X25519,
311312
Cryptor::ChaCha20Poly1305 => ENC_CHACHA20_POLY1305_X25519,
@@ -341,10 +342,10 @@ fn encrypt_envelop_shared_secret(cryptor: Cryptor,
341342
})
342343
}
343344

344-
fn encrypt_envelop_pgp(key: &[u8], envelop: &TinyEncryptConfigEnvelop) -> XResult<TinyEncryptEnvelop> {
345-
let pgp_public_key = opt_result!(crypto_rsa::parse_spki(&envelop.public_part), "Parse PGP public key failed: {}");
345+
fn encrypt_envelop_rsa(key: &[u8], envelop: &TinyEncryptConfigEnvelop) -> XResult<TinyEncryptEnvelop> {
346+
let pgp_public_key = opt_result!(crypto_rsa::parse_spki(&envelop.public_part), "Parse RSA public key failed: {}");
346347
let mut rng = rand::thread_rng();
347-
let encrypted_key = opt_result!(pgp_public_key.encrypt(&mut rng, Pkcs1v15Encrypt, key), "PGP public key encrypt failed: {}");
348+
let encrypted_key = opt_result!(pgp_public_key.encrypt(&mut rng, Pkcs1v15Encrypt, key), "RSA public key encrypt failed: {}");
348349
Ok(TinyEncryptEnvelop {
349350
r#type: envelop.r#type,
350351
kid: envelop.kid.clone(),

src/cmd_initpiv.rs

Lines changed: 22 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,13 @@ use x509_parser::prelude::FromDer;
77
use x509_parser::public_key::RSAPublicKey;
88
use yubikey::Certificate;
99
use yubikey::Key;
10-
use yubikey::piv::{AlgorithmId, RetiredSlotId, SlotId};
10+
use yubikey::piv::{AlgorithmId, SlotId};
1111
use yubikey::YubiKey;
1212

1313
use crate::config::TinyEncryptConfigEnvelop;
1414
use crate::spec::TinyEncryptEnvelopType;
15+
use crate::{util, util_piv};
16+
use crate::util_digest::sha256_digest;
1517

1618
#[derive(Debug, Args)]
1719
pub struct CmdInitPiv {
@@ -28,7 +30,7 @@ const ECC_P384: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.3.132.0.34");
2830

2931
pub fn init_piv(cmd_init_piv: CmdInitPiv) -> XResult<()> {
3032
let mut yk = opt_result!(YubiKey::open(), "YubiKey not found: {}");
31-
let slot_id = get_slot_id(&cmd_init_piv.slot)?;
33+
let slot_id = util_piv::get_slot_id(&cmd_init_piv.slot)?;
3234
let slot_id_hex = to_slot_hex(&slot_id);
3335
let keys = opt_result!(Key::list(&mut yk), "List keys failed: {}");
3436

@@ -71,8 +73,23 @@ pub fn init_piv(cmd_init_piv: CmdInitPiv) -> XResult<()> {
7173

7274
information!("Config envelop:\n{}", serde_json::to_string_pretty(&config_envelop).unwrap());
7375
}
76+
AlgorithmId::Rsa2048 => {
77+
let spki = opt_result!(cert.subject_public_key_info.to_der(), "Generate SPKI DER failed: {}");
78+
let config_envelop = TinyEncryptConfigEnvelop {
79+
r#type: TinyEncryptEnvelopType::PivRsa,
80+
sid: Some(format!("piv-{}-rsa2048", &slot_id_hex)),
81+
kid: format!("piv:{}", hex::encode(sha256_digest(&spki))),
82+
desc: Some(format!("PIV --slot {}", &slot_id_hex)),
83+
args: Some(vec![
84+
slot_id_hex.clone()
85+
]),
86+
public_part: util::to_pem(&spki, "PUBLIC KEY"),
87+
};
88+
89+
information!("Config envelop:\n{}", serde_json::to_string_pretty(&config_envelop).unwrap());
90+
}
7491
_ => {
75-
failure!("Only support P256 or P384, actual: {:?}", algorithm_id);
92+
failure!("Only support P256, P384 or RSA2048, actual: {:?}", algorithm_id);
7693
}
7794
}
7895
}
@@ -91,7 +108,7 @@ fn get_algorithm_id(public_key_info: &SubjectPublicKeyInfoOwned) -> XResult<Algo
91108
let rsa_public_key = opt_result!(
92109
RSAPublicKey::from_der(public_key_info.subject_public_key.raw_bytes()), "Parse public key failed: {}");
93110
let starts_with_0 = rsa_public_key.1.modulus.starts_with(&[0]);
94-
let public_key_bits = (rsa_public_key.1.modulus.len() - if starts_with_0 { 1 } else { 0 }) * 8;
111+
let public_key_bits = (rsa_public_key.1.modulus.len() - iff!(starts_with_0, 1, 0)) * 8;
95112
if public_key_bits == 1024 {
96113
return Ok(AlgorithmId::Rsa1024);
97114
}
@@ -117,41 +134,10 @@ fn get_algorithm_id(public_key_info: &SubjectPublicKeyInfoOwned) -> XResult<Algo
117134
}
118135

119136
fn slot_equals(slot_id: &SlotId, slot: &str) -> bool {
120-
get_slot_id(slot).map(|sid| &sid == slot_id).unwrap_or(false)
137+
util_piv::get_slot_id(slot).map(|sid| &sid == slot_id).unwrap_or(false)
121138
}
122139

123140
fn to_slot_hex(slot: &SlotId) -> String {
124141
let slot_id: u8 = (*slot).into();
125142
format!("{:x}", slot_id)
126143
}
127-
128-
fn get_slot_id(slot: &str) -> XResult<SlotId> {
129-
let slot_lower = slot.to_lowercase();
130-
Ok(match slot_lower.as_str() {
131-
"9a" | "auth" | "authentication" => SlotId::Authentication,
132-
"9c" | "sign" | "signature" => SlotId::Signature,
133-
"9d" | "keym" | "keymanagement" => SlotId::KeyManagement,
134-
"9e" | "card" | "cardauthentication" => SlotId::CardAuthentication,
135-
"r1" | "82" => SlotId::Retired(RetiredSlotId::R1),
136-
"r2" | "83" => SlotId::Retired(RetiredSlotId::R2),
137-
"r3" | "84" => SlotId::Retired(RetiredSlotId::R3),
138-
"r4" | "85" => SlotId::Retired(RetiredSlotId::R4),
139-
"r5" | "86" => SlotId::Retired(RetiredSlotId::R5),
140-
"r6" | "87" => SlotId::Retired(RetiredSlotId::R6),
141-
"r7" | "88" => SlotId::Retired(RetiredSlotId::R7),
142-
"r8" | "89" => SlotId::Retired(RetiredSlotId::R8),
143-
"r9" | "8a" => SlotId::Retired(RetiredSlotId::R9),
144-
"r10" | "8b" => SlotId::Retired(RetiredSlotId::R10),
145-
"r11" | "8c" => SlotId::Retired(RetiredSlotId::R11),
146-
"r12" | "8d" => SlotId::Retired(RetiredSlotId::R12),
147-
"r13" | "8e" => SlotId::Retired(RetiredSlotId::R13),
148-
"r14" | "8f" => SlotId::Retired(RetiredSlotId::R14),
149-
"r15" | "90" => SlotId::Retired(RetiredSlotId::R15),
150-
"r16" | "91" => SlotId::Retired(RetiredSlotId::R16),
151-
"r17" | "92" => SlotId::Retired(RetiredSlotId::R17),
152-
"r18" | "93" => SlotId::Retired(RetiredSlotId::R18),
153-
"r19" | "94" => SlotId::Retired(RetiredSlotId::R19),
154-
"r20" | "95" => SlotId::Retired(RetiredSlotId::R20),
155-
_ => return simple_error!("Unknown slot: {}", slot),
156-
})
157-
}

src/config.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ impl TinyEncryptConfig {
5656
pub fn load(file: &str) -> XResult<Self> {
5757
let resolved_file = resolve_file_path(file);
5858
let config_contents = opt_result!(
59-
fs::read_to_string(&resolved_file), "Read config file: {}, failed: {}", file
59+
fs::read_to_string(resolved_file), "Read config file: {}, failed: {}", file
6060
);
6161
let mut config: TinyEncryptConfig = opt_result!(
6262
serde_json::from_str(&config_contents),"Parse config file: {}, failed: {}", file);

src/lib.rs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,11 @@ mod util;
3535
mod util_env;
3636
mod util_digest;
3737
mod util_progress;
38-
#[cfg(feature = "decrypt")]
38+
#[cfg(feature = "smartcard")]
3939
mod util_piv;
40-
#[cfg(feature = "decrypt")]
40+
#[cfg(feature = "smartcard")]
4141
mod util_pgp;
42-
mod util_p256;
43-
mod util_p384;
44-
mod util_x25519;
42+
mod util_ecdh;
4543
mod compress;
4644
mod config;
4745
mod spec;

0 commit comments

Comments
 (0)