Skip to content

Commit

Permalink
feat: v1.4.0, support PIV RSA key
Browse files Browse the repository at this point in the history
  • Loading branch information
jht5945 committed Dec 10, 2023
1 parent 3b361eb commit 718939a
Show file tree
Hide file tree
Showing 13 changed files with 177 additions and 105 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "tiny-encrypt"
version = "1.3.0"
version = "1.4.0"
edition = "2021"
license = "MIT"
description = "A simple and tiny file encrypt tool"
Expand All @@ -9,7 +9,7 @@ repository = "https://git.hatter.ink/hatter/tiny-encrypt-rs"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[features]
default = ["decrypt", "macos", "secure-enclave"]
default = ["decrypt", "macos", "smartcard", "secure-enclave"]
decrypt = ["smartcard"]
smartcard = ["openpgp-card", "openpgp-card-pcsc", "yubikey"]
macos = ["security-framework"]
Expand Down
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <temp-file-name> "aes-256-gcm" <temp-key-hex> <temp-nonce-hex>
```


<br>

Encrypt config `~/.tinyencrypt/config-rs.json`:
Expand Down Expand Up @@ -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:

Expand Down
3 changes: 1 addition & 2 deletions src/cmd_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ impl Ord for ConfigProfile {

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

Expand Down Expand Up @@ -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(",");
Expand Down
57 changes: 47 additions & 10 deletions src/cmd_decrypt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,12 @@ 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 = "macos")]
use crate::util_keychainstatic;
#[cfg(feature = "secure-enclave")]
use crate::util_keychainkey;
#[cfg(feature = "macos")]
use crate::util_keychainstatic;
use crate::util_progress::Progress;
use crate::wrap_key::WrapKey;

Expand Down Expand Up @@ -432,21 +432,22 @@ pub fn try_decrypt_key(config: &Option<TinyEncryptConfig>,
pin: &Option<String>,
slot: &Option<String>) -> XResult<Vec<u8>> {
match envelop.r#type {
TinyEncryptEnvelopType::PgpRsa => try_decrypt_key_pgp(envelop, pin),
TinyEncryptEnvelopType::PgpRsa => try_decrypt_key_pgp_rsa(envelop, pin),
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::PivP384 => try_decrypt_key_ecdh(config, envelop, pin, slot),
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()),
}
}

fn try_decrypt_key_ecdh(config: &Option<TinyEncryptConfig>,
envelop: &TinyEncryptEnvelop,
pin: &Option<String>,
slot: &Option<String>) -> XResult<Vec<u8>> {
fn try_decrypt_piv_key_ecdh(config: &Option<TinyEncryptConfig>,
envelop: &TinyEncryptEnvelop,
pin: &Option<String>,
slot: &Option<String>) -> XResult<Vec<u8>> {
let wrap_key = WrapKey::parse(&envelop.encrypted_key)?;
let (cryptor, algo_id) = match wrap_key.header.enc.as_str() {
ENC_AES256_GCM_P256 => (Cryptor::Aes256Gcm, AlgorithmId::EccP256),
Expand Down Expand Up @@ -483,6 +484,42 @@ fn try_decrypt_key_ecdh(config: &Option<TinyEncryptConfig>,
Ok(decrypted_key)
}

fn try_decrypt_piv_key_rsa(config: &Option<TinyEncryptConfig>,
envelop: &TinyEncryptEnvelop,
pin: &Option<String>,
slot: &Option<String>) -> XResult<Vec<u8>> {
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::<Vec<_>>();

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<TinyEncryptConfig>,
envelop: &TinyEncryptEnvelop) -> XResult<Vec<u8>> {
Expand Down Expand Up @@ -571,7 +608,7 @@ fn try_decrypt_key_ecdh_static_x25519(config: &Option<TinyEncryptConfig>, envelo
Ok(decrypted_key)
}

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

Expand Down
23 changes: 12 additions & 11 deletions src/cmd_encrypt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use rsa::Pkcs1v15Encrypt;
use rust_util::{debugging, failure, iff, information, opt_result, simple_error, success, util_size, XResult};
use rust_util::util_time::UnixEpochTime;

use crate::{crypto_cryptor, crypto_simple, util, util_enc_file, util_env, util_p256, util_p384, util_x25519};
use crate::{crypto_cryptor, crypto_simple, util, util_enc_file, util_env};
use crate::compress::GzStreamEncoder;
use crate::config::{TinyEncryptConfig, TinyEncryptConfigEnvelop};
use crate::consts::{
Expand All @@ -24,6 +24,7 @@ use crate::spec::{
EncEncryptedMeta, EncMetadata,
TinyEncryptEnvelop, TinyEncryptEnvelopType, TinyEncryptMeta,
};
use crate::util_ecdh::{ecdh_p256, ecdh_p384, ecdh_x25519};
use crate::util_progress::Progress;
use crate::wrap_key::{WrapKey, WrapKeyHeader};

Expand Down Expand Up @@ -265,14 +266,14 @@ 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(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)?);
}
TinyEncryptEnvelopType::PivP256 | TinyEncryptEnvelopType::KeyP256 => {
encrypted_envelops.push(encrypt_envelop_ecdh(cryptor, key, envelop)?);
encrypted_envelops.push(encrypt_envelop_ecdh_p256(cryptor, key, envelop)?);
}
TinyEncryptEnvelopType::PivP384 => {
encrypted_envelops.push(encrypt_envelop_ecdh_p384(cryptor, key, envelop)?);
Expand All @@ -283,9 +284,9 @@ fn encrypt_envelops(cryptor: Cryptor, key: &[u8], envelops: &[&TinyEncryptConfig
Ok(encrypted_envelops)
}

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

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

fn encrypt_envelop_ecdh_x25519(cryptor: Cryptor, key: &[u8], envelop: &TinyEncryptConfigEnvelop) -> XResult<TinyEncryptEnvelop> {
let public_key_point_hex = &envelop.public_part;
let (shared_secret, ephemeral_spki) = util_x25519::compute_x25519_shared_secret(public_key_point_hex)?;
let (shared_secret, ephemeral_spki) = ecdh_x25519::compute_x25519_shared_secret(public_key_point_hex)?;
let enc_type = match cryptor {
Cryptor::Aes256Gcm => ENC_AES256_GCM_X25519,
Cryptor::ChaCha20Poly1305 => ENC_CHACHA20_POLY1305_X25519,
Expand Down Expand Up @@ -341,10 +342,10 @@ fn encrypt_envelop_shared_secret(cryptor: Cryptor,
})
}

fn encrypt_envelop_pgp(key: &[u8], envelop: &TinyEncryptConfigEnvelop) -> XResult<TinyEncryptEnvelop> {
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<TinyEncryptEnvelop> {
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(),
Expand Down
58 changes: 22 additions & 36 deletions src/cmd_initpiv.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@ 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::piv::{AlgorithmId, SlotId};
use yubikey::YubiKey;

use crate::config::TinyEncryptConfigEnvelop;
use crate::spec::TinyEncryptEnvelopType;
use crate::{util, util_piv};
use crate::util_digest::sha256_digest;

#[derive(Debug, Args)]
pub struct CmdInitPiv {
Expand All @@ -28,7 +30,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 = util_piv::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: {}");

Expand Down Expand Up @@ -71,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);
}
}
}
Expand All @@ -91,7 +108,7 @@ fn get_algorithm_id(public_key_info: &SubjectPublicKeyInfoOwned) -> XResult<Algo
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;
let public_key_bits = (rsa_public_key.1.modulus.len() - iff!(starts_with_0, 1, 0)) * 8;
if public_key_bits == 1024 {
return Ok(AlgorithmId::Rsa1024);
}
Expand All @@ -117,41 +134,10 @@ fn get_algorithm_id(public_key_info: &SubjectPublicKeyInfoOwned) -> XResult<Algo
}

fn slot_equals(slot_id: &SlotId, slot: &str) -> bool {
get_slot_id(slot).map(|sid| &sid == slot_id).unwrap_or(false)
util_piv::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<SlotId> {
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),
})
}
2 changes: 1 addition & 1 deletion src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ impl TinyEncryptConfig {
pub fn load(file: &str) -> XResult<Self> {
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);
Expand Down
8 changes: 3 additions & 5 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,11 @@ mod util;
mod util_env;
mod util_digest;
mod util_progress;
#[cfg(feature = "decrypt")]
#[cfg(feature = "smartcard")]
mod util_piv;
#[cfg(feature = "decrypt")]
#[cfg(feature = "smartcard")]
mod util_pgp;
mod util_p256;
mod util_p384;
mod util_x25519;
mod util_ecdh;
mod compress;
mod config;
mod spec;
Expand Down
Loading

0 comments on commit 718939a

Please sign in to comment.