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]]
|
[[package]]
|
||||||
name = "tiny-encrypt"
|
name = "tiny-encrypt"
|
||||||
version = "1.3.1"
|
version = "1.4.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aes-gcm-stream",
|
"aes-gcm-stream",
|
||||||
"base64",
|
"base64",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "tiny-encrypt"
|
name = "tiny-encrypt"
|
||||||
version = "1.3.1"
|
version = "1.4.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
description = "A simple and tiny file encrypt tool"
|
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
|
Repository address: https://git.hatter.ink/hatter/tiny-encrypt-rs mirror https://github.com/jht5945/tiny-encrypt-rs
|
||||||
|
|
||||||
Set default encryption algorithm:
|
Set default encryption algorithm:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
export TINY_ENCRYPT_DEFAULT_ALGORITHM='AES' # or CHACHA20
|
export TINY_ENCRYPT_DEFAULT_ALGORITHM='AES' # or CHACHA20
|
||||||
```
|
```
|
||||||
|
|
||||||
Compile only encrypt:
|
Compile only encrypt:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
cargo build --release --no-default-features
|
cargo build --release --no-default-features
|
||||||
```
|
```
|
||||||
|
|
||||||
Edit encrypted file:
|
Edit encrypted file:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
tiny-encrypt decrypt --edit-file sample.txt.tinyenc
|
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.
|
Read environment `EDITOR` or `SECURE_EDITOR` to edit file, `SECURE_EDITOR` write encrypted file to temp file.
|
||||||
|
|
||||||
Secure editor command format:
|
Secure editor command format:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
$SECURE_EDITOR <temp-file-name> "aes-256-gcm" <temp-key-hex> <temp-nonce-hex>
|
$SECURE_EDITOR <temp-file-name> "aes-256-gcm" <temp-key-hex> <temp-nonce-hex>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
Encrypt config `~/.tinyencrypt/config-rs.json`:
|
Encrypt config `~/.tinyencrypt/config-rs.json`:
|
||||||
@@ -78,6 +82,7 @@ Supported PKI encryption types:
|
|||||||
| piv-p256 | ECDH(secp256r1) | PIV Slot (Previous `ecdh`) |
|
| piv-p256 | ECDH(secp256r1) | PIV Slot (Previous `ecdh`) |
|
||||||
| piv-p384 | ECDH(secp384r1) | PIV Slot (Previous `ecdh-p384`) |
|
| piv-p384 | ECDH(secp384r1) | PIV Slot (Previous `ecdh-p384`) |
|
||||||
| key-p256 | ECDH(secp256r1) | Key Stored in Secure Enclave |
|
| key-p256 | ECDH(secp256r1) | Key Stored in Secure Enclave |
|
||||||
|
| piv-rsa | PKCS1-v1.5 | PIV Slot |
|
||||||
|
|
||||||
Smart Card(Yubikey) protected ECDH Encryption description:
|
Smart Card(Yubikey) protected ECDH Encryption description:
|
||||||
|
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ impl Ord for ConfigProfile {
|
|||||||
|
|
||||||
impl PartialOrd for ConfigProfile {
|
impl PartialOrd for ConfigProfile {
|
||||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
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<()> {
|
fn config_profiles(cmd_version: &CmdConfig, config: &TinyEncryptConfig) -> XResult<()> {
|
||||||
let mut reverse_map = HashMap::new();
|
let mut reverse_map = HashMap::new();
|
||||||
for (p, v) in &config.profiles {
|
for (p, v) in &config.profiles {
|
||||||
let p = p;
|
|
||||||
let mut v2 = v.clone();
|
let mut v2 = v.clone();
|
||||||
v2.sort();
|
v2.sort();
|
||||||
let vs = v2.join(",");
|
let vs = v2.join(",");
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ use crate::consts::{
|
|||||||
};
|
};
|
||||||
use crate::crypto_cryptor::{Cryptor, KeyNonce};
|
use crate::crypto_cryptor::{Cryptor, KeyNonce};
|
||||||
use crate::spec::{EncEncryptedMeta, TinyEncryptEnvelop, TinyEncryptEnvelopType, TinyEncryptMeta};
|
use crate::spec::{EncEncryptedMeta, TinyEncryptEnvelop, TinyEncryptEnvelopType, TinyEncryptMeta};
|
||||||
use crate::util::SecVec;
|
use crate::util::{decode_base64, SecVec};
|
||||||
use crate::util_digest::DigestWrite;
|
use crate::util_digest::DigestWrite;
|
||||||
#[cfg(feature = "secure-enclave")]
|
#[cfg(feature = "secure-enclave")]
|
||||||
use crate::util_keychainkey;
|
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),
|
TinyEncryptEnvelopType::PivP256 | TinyEncryptEnvelopType::PivP384 => try_decrypt_piv_key_ecdh(config, envelop, pin, slot),
|
||||||
#[cfg(feature = "secure-enclave")]
|
#[cfg(feature = "secure-enclave")]
|
||||||
TinyEncryptEnvelopType::KeyP256 => try_decrypt_se_key_ecdh(config, envelop),
|
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()),
|
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)
|
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")]
|
#[cfg(feature = "secure-enclave")]
|
||||||
fn try_decrypt_se_key_ecdh(config: &Option<TinyEncryptConfig>,
|
fn try_decrypt_se_key_ecdh(config: &Option<TinyEncryptConfig>,
|
||||||
envelop: &TinyEncryptEnvelop) -> XResult<Vec<u8>> {
|
envelop: &TinyEncryptEnvelop) -> XResult<Vec<u8>> {
|
||||||
|
|||||||
@@ -266,8 +266,8 @@ fn encrypt_envelops(cryptor: Cryptor, key: &[u8], envelops: &[&TinyEncryptConfig
|
|||||||
let mut encrypted_envelops = vec![];
|
let mut encrypted_envelops = vec![];
|
||||||
for envelop in envelops {
|
for envelop in envelops {
|
||||||
match envelop.r#type {
|
match envelop.r#type {
|
||||||
TinyEncryptEnvelopType::PgpRsa => {
|
TinyEncryptEnvelopType::PgpRsa | TinyEncryptEnvelopType::PivRsa => {
|
||||||
encrypted_envelops.push(encrypt_envelop_pgp_rsa(key, envelop)?);
|
encrypted_envelops.push(encrypt_envelop_rsa(key, envelop)?);
|
||||||
}
|
}
|
||||||
TinyEncryptEnvelopType::PgpX25519 | TinyEncryptEnvelopType::StaticX25519 => {
|
TinyEncryptEnvelopType::PgpX25519 | TinyEncryptEnvelopType::StaticX25519 => {
|
||||||
encrypted_envelops.push(encrypt_envelop_ecdh_x25519(cryptor, key, envelop)?);
|
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> {
|
fn encrypt_envelop_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: {}");
|
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 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 {
|
Ok(TinyEncryptEnvelop {
|
||||||
r#type: envelop.r#type,
|
r#type: envelop.r#type,
|
||||||
kid: envelop.kid.clone(),
|
kid: envelop.kid.clone(),
|
||||||
|
|||||||
@@ -12,7 +12,8 @@ use yubikey::YubiKey;
|
|||||||
|
|
||||||
use crate::config::TinyEncryptConfigEnvelop;
|
use crate::config::TinyEncryptConfigEnvelop;
|
||||||
use crate::spec::TinyEncryptEnvelopType;
|
use crate::spec::TinyEncryptEnvelopType;
|
||||||
use crate::util_piv;
|
use crate::{util, util_piv};
|
||||||
|
use crate::util_digest::sha256_digest;
|
||||||
|
|
||||||
#[derive(Debug, Args)]
|
#[derive(Debug, Args)]
|
||||||
pub struct CmdInitPiv {
|
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());
|
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> {
|
pub fn load(file: &str) -> XResult<Self> {
|
||||||
let resolved_file = resolve_file_path(file);
|
let resolved_file = resolve_file_path(file);
|
||||||
let config_contents = opt_result!(
|
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!(
|
let mut config: TinyEncryptConfig = opt_result!(
|
||||||
serde_json::from_str(&config_contents),"Parse config file: {}, failed: {}", file);
|
serde_json::from_str(&config_contents),"Parse config file: {}, failed: {}", file);
|
||||||
|
|||||||
@@ -85,6 +85,9 @@ pub enum TinyEncryptEnvelopType {
|
|||||||
// PIV ECDH P384
|
// PIV ECDH P384
|
||||||
#[serde(rename = "piv-p384", alias = "ecdh-p384")]
|
#[serde(rename = "piv-p384", alias = "ecdh-p384")]
|
||||||
PivP384,
|
PivP384,
|
||||||
|
// PIV RSA
|
||||||
|
#[serde(rename = "piv-rsa")]
|
||||||
|
PivRsa,
|
||||||
// KMS, tiny-encrypt-rs is not supported
|
// KMS, tiny-encrypt-rs is not supported
|
||||||
#[serde(rename = "kms")]
|
#[serde(rename = "kms")]
|
||||||
Kms,
|
Kms,
|
||||||
@@ -104,6 +107,7 @@ impl TinyEncryptEnvelopType {
|
|||||||
TinyEncryptEnvelopType::Age => "age",
|
TinyEncryptEnvelopType::Age => "age",
|
||||||
TinyEncryptEnvelopType::PivP256 => "piv-p256",
|
TinyEncryptEnvelopType::PivP256 => "piv-p256",
|
||||||
TinyEncryptEnvelopType::PivP384 => "piv-p384",
|
TinyEncryptEnvelopType::PivP384 => "piv-p384",
|
||||||
|
TinyEncryptEnvelopType::PivRsa => "piv-rsa",
|
||||||
TinyEncryptEnvelopType::Kms => "kms",
|
TinyEncryptEnvelopType::Kms => "kms",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -117,6 +121,7 @@ impl TinyEncryptEnvelopType {
|
|||||||
TinyEncryptEnvelopType::Age => false,
|
TinyEncryptEnvelopType::Age => false,
|
||||||
TinyEncryptEnvelopType::PivP256 => false,
|
TinyEncryptEnvelopType::PivP256 => false,
|
||||||
TinyEncryptEnvelopType::PivP384 => false,
|
TinyEncryptEnvelopType::PivP384 => false,
|
||||||
|
TinyEncryptEnvelopType::PivRsa => false,
|
||||||
TinyEncryptEnvelopType::Kms => true,
|
TinyEncryptEnvelopType::Kms => true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
13
src/util.rs
13
src/util.rs
@@ -109,6 +109,19 @@ pub fn simple_kdf(input: &[u8]) -> Vec<u8> {
|
|||||||
input
|
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>> {
|
pub fn decode_base64(input: &str) -> XResult<Vec<u8>> {
|
||||||
Ok(general_purpose::STANDARD.decode(input)?)
|
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 envelop_desc = get_envelop_desc(envelop, &config_envelop);
|
||||||
let desc = envelop_desc.as_ref()
|
let desc = envelop_desc.as_ref()
|
||||||
.map(|desc| format!(", Desc: {}", desc))
|
.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)
|
format!("{}{}{}", with_width_type(&envelop.r#type.get_upper_name()), envelop_kid, desc)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user