feat: v1.4.0, support PIV RSA key

This commit is contained in:
2023-12-10 09:58:08 +08:00
parent b15b9a5b32
commit d0218ee233
11 changed files with 90 additions and 15 deletions

2
Cargo.lock generated
View File

@@ -1700,7 +1700,7 @@ dependencies = [
[[package]]
name = "tiny-encrypt"
version = "1.3.1"
version = "1.4.0"
dependencies = [
"aes-gcm-stream",
"base64",

View File

@@ -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"

View File

@@ -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`:
@@ -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:

View File

@@ -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))
}
}
@@ -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(",");

View File

@@ -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<TinyEncryptConfig>,
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<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>> {

View File

@@ -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<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(),

View File

@@ -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);
}
}
}

View File

@@ -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);

View File

@@ -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,
}
}

View File

@@ -109,6 +109,19 @@ pub fn simple_kdf(input: &[u8]) -> Vec<u8> {
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<Vec<u8>> {
Ok(general_purpose::STANDARD.decode(input)?)
}

View File

@@ -11,7 +11,7 @@ pub fn format_envelop(envelop: &TinyEncryptEnvelop, config: &Option<TinyEncryptC
let envelop_desc = get_envelop_desc(envelop, &config_envelop);
let desc = envelop_desc.as_ref()
.map(|desc| format!(", Desc: {}", desc))
.unwrap_or_else(|| "".to_string());
.unwrap_or_default();
format!("{}{}{}", with_width_type(&envelop.r#type.get_upper_name()), envelop_kid, desc)
}