From a64649451c69b34f316bbe5c68a934d26ea70307 Mon Sep 17 00:00:00 2001 From: Hatter Jiang Date: Tue, 10 Oct 2023 00:59:59 +0800 Subject: [PATCH] feat: v0.2.3, supports p384 --- Cargo.lock | 3 ++- Cargo.toml | 47 +++++++++++++++++++++++----------------------- src/cmd_decrypt.rs | 15 +++++++++------ src/cmd_encrypt.rs | 14 ++++++++++++-- src/main.rs | 1 + src/spec.rs | 3 +++ src/util.rs | 1 + src/util_p384.rs | 19 +++++++++++++++++++ 8 files changed, 71 insertions(+), 32 deletions(-) create mode 100644 src/util_p384.rs diff --git a/Cargo.lock b/Cargo.lock index c44e720..64e6461 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2063,7 +2063,7 @@ dependencies = [ [[package]] name = "tiny-encrypt" -version = "0.2.2" +version = "0.2.3" dependencies = [ "aes-gcm-stream", "base64", @@ -2074,6 +2074,7 @@ dependencies = [ "openpgp-card", "openpgp-card-pcsc", "p256", + "p384", "rand", "reqwest", "rpassword", diff --git a/Cargo.toml b/Cargo.toml index 2bd63f1..f92b3e9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tiny-encrypt" -version = "0.2.2" +version = "0.2.3" edition = "2021" license = "MIT" description = "A simple and tiny file encrypt tool" @@ -8,28 +8,29 @@ description = "A simple and tiny file encrypt tool" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -aes-gcm-stream = "0.2.0" -base64 = "0.21.0" -chrono = "0.4.23" -clap = { version = "4.1.4", features = ["derive"] } -flate2 = "1.0.27" -hex = "0.4.3" -openpgp-card = "0.3.7" -openpgp-card-pcsc = "0.3.0" -p256 = { version = "0.13.2", features = ["pem", "ecdh", "pkcs8"] } -rand = "0.8.5" -reqwest = { version = "0.11.14", features = ["blocking", "rustls", "rustls-tls"] } -rpassword = "7.2.0" -rsa = { version = "0.9.2", features = ["pem"] } -rust_util = "0.6.42" -serde = { version = "1.0.152", features = ["derive"] } -serde_json = "1.0.93" -sha256 = "1.4.0" -simpledateformat = "0.1.4" -x25519-dalek = "2.0.0" -x509-parser = "0.15.1" -yubikey = { version = "0.8.0", features = ["untested"] } -zeroize = "1.6.0" +aes-gcm-stream = "0.2" +base64 = "0.21" +chrono = "0.4" +clap = { version = "4.1", features = ["derive"] } +flate2 = "1.0" +hex = "0.4" +openpgp-card = "0.3" +openpgp-card-pcsc = "0.3" +p256 = { version = "0.13", features = ["pem", "ecdh", "pkcs8"] } +p384 = { version = "0.13", features = ["pem", "ecdh", "pkcs8"] } +rand = "0.8" +reqwest = { version = "0.11", features = ["blocking", "rustls", "rustls-tls"] } +rpassword = "7.2" +rsa = { version = "0.9", features = ["pem"] } +rust_util = "0.6" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +sha256 = "1.4" +simpledateformat = "0.1" +x25519-dalek = "2.0" +x509-parser = "0.15" +yubikey = { version = "0.8", features = ["untested"] } +zeroize = "1.6" [profile.release] codegen-units = 1 diff --git a/src/cmd_decrypt.rs b/src/cmd_decrypt.rs index 059367f..4d30952 100644 --- a/src/cmd_decrypt.rs +++ b/src/cmd_decrypt.rs @@ -11,8 +11,8 @@ use openpgp_card::crypto_data::Cryptogram; use rust_util::{debugging, failure, iff, information, opt_result, simple_error, success, util_msg, util_term, warning, XResult}; use x509_parser::prelude::FromDer; use x509_parser::x509::SubjectPublicKeyInfo; -use yubikey::YubiKey; use yubikey::piv::{AlgorithmId, decrypt_data, RetiredSlotId, SlotId}; +use yubikey::YubiKey; use zeroize::Zeroize; use crate::{card, file, util}; @@ -20,7 +20,7 @@ use crate::compress::GzStreamDecoder; use crate::config::TinyEncryptConfig; use crate::crypto_aes::aes_gcm_decrypt; use crate::spec::{TinyEncryptEnvelop, TinyEncryptEnvelopType, TinyEncryptMeta}; -use crate::util::{ENC_AES256_GCM_P256, ENC_AES256_GCM_X25519, TINY_ENC_CONFIG_FILE, TINY_ENC_FILE_EXT}; +use crate::util::{ENC_AES256_GCM_P256, ENC_AES256_GCM_P384, ENC_AES256_GCM_X25519, TINY_ENC_CONFIG_FILE, TINY_ENC_FILE_EXT}; use crate::wrap_key::WrapKey; #[derive(Debug, Args)] @@ -168,7 +168,8 @@ fn try_decrypt_key(config: &Option, match envelop.r#type { TinyEncryptEnvelopType::Pgp => try_decrypt_key_pgp(envelop, pin), TinyEncryptEnvelopType::PgpX25519 => try_decrypt_key_ecdh_pgp_x25519(envelop, pin), - TinyEncryptEnvelopType::Ecdh => try_decrypt_key_ecdh(config, envelop, pin, slot), + TinyEncryptEnvelopType::Ecdh => try_decrypt_key_ecdh(config, envelop, pin, ENC_AES256_GCM_P256, slot), + TinyEncryptEnvelopType::EcdhP384 => try_decrypt_key_ecdh(config, envelop, pin, ENC_AES256_GCM_P384, slot), unknown_type => { return simple_error!("Unknown or not supported type: {}", unknown_type.get_name()); } @@ -178,10 +179,11 @@ fn try_decrypt_key(config: &Option, fn try_decrypt_key_ecdh(config: &Option, envelop: &TinyEncryptEnvelop, pin: &Option, + expected_enc_type: &str, slot: &Option) -> XResult> { let wrap_key = WrapKey::parse(&envelop.encrypted_key)?; - if wrap_key.header.enc.as_str() != ENC_AES256_GCM_P256 { - return simple_error!("Unsupported header requires: {}, actual: {}", ENC_AES256_GCM_P256, &wrap_key.header.enc); + if wrap_key.header.enc.as_str() != expected_enc_type { + return simple_error!("Unsupported header requires: {}, actual: {}", expected_enc_type, &wrap_key.header.enc); } let e_pub_key = &wrap_key.header.e_pub_key; let e_pub_key_bytes = opt_result!(util::decode_base64_url_no_pad(e_pub_key), "Invalid envelop: {}"); @@ -195,10 +197,11 @@ fn try_decrypt_key_ecdh(config: &Option, let retired_slot_id = opt_result!(RetiredSlotId::from_str(&slot), "Slot not found: {}"); let slot_id = SlotId::Retired(retired_slot_id); opt_result!(yk.verify_pin(pin.as_bytes()), "YubiKey verify pin failed: {}"); + let algo_id = iff!(expected_enc_type == ENC_AES256_GCM_P256, AlgorithmId::EccP256, AlgorithmId::EccP384); let shared_secret = opt_result!(decrypt_data( &mut yk, &epk_bytes, - AlgorithmId::EccP256, + algo_id, slot_id, ), "Decrypt via PIV card failed: {}"); let key = util::simple_kdf(shared_secret.as_slice()); diff --git a/src/cmd_encrypt.rs b/src/cmd_encrypt.rs index 3af58be..bf1b1b2 100644 --- a/src/cmd_encrypt.rs +++ b/src/cmd_encrypt.rs @@ -10,13 +10,13 @@ use rsa::Pkcs1v15Encrypt; use rust_util::{debugging, failure, information, opt_result, simple_error, success, util_msg, warning, XResult}; use zeroize::Zeroize; -use crate::{util, util_ecdh, util_x25519}; +use crate::{util, util_ecdh, util_p384, util_x25519}; use crate::compress::GzStreamEncoder; use crate::config::{TinyEncryptConfig, TinyEncryptConfigEnvelop}; use crate::crypto_aes::aes_gcm_encrypt; use crate::crypto_rsa::parse_spki; use crate::spec::{EncMetadata, TINY_ENCRYPT_VERSION_10, TinyEncryptEnvelop, TinyEncryptEnvelopType, TinyEncryptMeta}; -use crate::util::{ENC_AES256_GCM_P256, ENC_AES256_GCM_X25519, TINY_ENC_CONFIG_FILE}; +use crate::util::{ENC_AES256_GCM_P256, ENC_AES256_GCM_P384, ENC_AES256_GCM_X25519, TINY_ENC_CONFIG_FILE}; use crate::wrap_key::{WrapKey, WrapKeyHeader}; #[derive(Debug, Args)] @@ -239,6 +239,9 @@ fn encrypt_envelops(key: &[u8], envelops: &[&TinyEncryptConfigEnvelop]) -> XResu TinyEncryptEnvelopType::Ecdh => { encrypted_envelops.push(encrypt_envelop_ecdh(key, envelop)?); } + TinyEncryptEnvelopType::EcdhP384 => { + encrypted_envelops.push(encrypt_envelop_ecdh_p384(key, envelop)?); + } _ => return simple_error!("Not supported type: {:?}", envelop.r#type), } } @@ -252,6 +255,13 @@ fn encrypt_envelop_ecdh(key: &[u8], envelop: &TinyEncryptConfigEnvelop) -> XResu encrypt_envelop_shared_secret(key, &shared_secret, &ephemeral_spki, ENC_AES256_GCM_P256, envelop) } +fn encrypt_envelop_ecdh_p384(key: &[u8], envelop: &TinyEncryptConfigEnvelop) -> XResult { + let public_key_point_hex = &envelop.public_part; + let (shared_secret, ephemeral_spki) = util_p384::compute_p384_shared_secret(public_key_point_hex)?; + + encrypt_envelop_shared_secret(key, &shared_secret, &ephemeral_spki, ENC_AES256_GCM_P384, envelop) +} + fn encrypt_envelop_ecdh_x25519(key: &[u8], envelop: &TinyEncryptConfigEnvelop) -> XResult { let public_key_point_hex = &envelop.public_part; let (shared_secret, ephemeral_spki) = util_x25519::compute_x25519_shared_secret(public_key_point_hex)?; diff --git a/src/main.rs b/src/main.rs index 1120269..15523dd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,6 +10,7 @@ use crate::cmd_version::CmdVersion; mod util; mod util_ecdh; +mod util_p384; mod util_x25519; mod compress; mod config; diff --git a/src/spec.rs b/src/spec.rs index 54de156..8ccb927 100644 --- a/src/spec.rs +++ b/src/spec.rs @@ -56,6 +56,8 @@ pub enum TinyEncryptEnvelopType { Age, #[serde(rename = "ecdh")] Ecdh, + #[serde(rename = "ecdh-p384")] + EcdhP384, #[serde(rename = "kms")] Kms, } @@ -70,6 +72,7 @@ impl TinyEncryptEnvelopType { TinyEncryptEnvelopType::PgpX25519 => "pgp-x25519", TinyEncryptEnvelopType::Age => "age", TinyEncryptEnvelopType::Ecdh => "ecdh", + TinyEncryptEnvelopType::EcdhP384 => "ecdh-p384", TinyEncryptEnvelopType::Kms => "kms", } } diff --git a/src/util.rs b/src/util.rs index 2dd3db0..7e9fd27 100644 --- a/src/util.rs +++ b/src/util.rs @@ -9,6 +9,7 @@ use rust_util::{simple_error, warning, XResult}; use zeroize::Zeroize; pub const ENC_AES256_GCM_P256: &str = "aes256-gcm-p256"; +pub const ENC_AES256_GCM_P384: &str = "aes256-gcm-p384"; pub const ENC_AES256_GCM_X25519: &str = "aes256-gcm-x25519"; pub const TINY_ENC_FILE_EXT: &str = ".tinyenc"; pub const TINY_ENC_CONFIG_FILE: &str = "~/.tinyencrypt/config-rs.json"; diff --git a/src/util_p384.rs b/src/util_p384.rs new file mode 100644 index 0000000..73b0209 --- /dev/null +++ b/src/util_p384.rs @@ -0,0 +1,19 @@ +use p384::ecdh::EphemeralSecret; +use rand::rngs::OsRng; +use rust_util::{opt_result, XResult}; + +use p384::pkcs8::EncodePublicKey; +use p384::{EncodedPoint, PublicKey}; +use p384::elliptic_curve::sec1::FromEncodedPoint; + +pub fn compute_p384_shared_secret(public_key_point_hex: &str) -> XResult<(Vec, Vec)> { + let public_key_point_bytes = opt_result!(hex::decode(public_key_point_hex), "Parse public key point hex failed: {}"); + let encoded_point = opt_result!(EncodedPoint::from_bytes(&public_key_point_bytes), "Parse public key point failed: {}"); + let public_key = PublicKey::from_encoded_point(&encoded_point).unwrap(); + + let esk = EphemeralSecret::random(&mut OsRng); + let epk = esk.public_key(); + let shared_secret = esk.diffie_hellman(&public_key); + let epk_public_key_der = opt_result!(epk.to_public_key_der(), "Convert epk to SPKI failed: {}"); + Ok((shared_secret.raw_secret_bytes().as_slice().to_vec(), epk_public_key_der.to_vec())) +} \ No newline at end of file