feat: v1.4.0, support PIV RSA key
This commit is contained in:
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -1700,7 +1700,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tiny-encrypt"
|
||||
version = "1.3.1"
|
||||
version = "1.4.0"
|
||||
dependencies = [
|
||||
"aes-gcm-stream",
|
||||
"base64",
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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:
|
||||
|
||||
|
||||
@@ -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(",");
|
||||
|
||||
@@ -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>> {
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
13
src/util.rs
13
src/util.rs
@@ -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)?)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user