use std::fs::Metadata; use rust_util::{opt_result, util_time, XResult}; use rust_util::util_time::get_millis; use serde::{Deserialize, Serialize}; use crate::{compress, crypto_simple}; use crate::consts::SALT_META; use crate::crypto_cryptor::{Cryptor, KeyNonce}; use crate::util::{encode_base64, get_user_agent}; // Compatible with 1.0 is removed from v0.6.0 // pub const TINY_ENCRYPT_VERSION_10: &str = "1.0"; pub const TINY_ENCRYPT_VERSION_11: &str = "1.1"; /// Specification: [Tiny Encrypt Spec V1.1](https://github.com/OpenWebStandard/tiny-encrypt-format-spec/blob/main/TinyEncryptSpecv1.1.md) #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct TinyEncryptMeta { pub version: String, pub created: u64, pub user_agent: String, #[serde(skip_serializing_if = "Option::is_none")] pub comment: Option, #[serde(skip_serializing_if = "Option::is_none")] pub encrypted_comment: Option, #[serde(skip_serializing_if = "Option::is_none")] pub encrypted_meta: Option, // --------------------------------------- #[serde(skip_serializing_if = "Option::is_none")] pub pgp_envelop: Option, #[serde(skip_serializing_if = "Option::is_none")] pub pgp_fingerprint: Option, #[serde(skip_serializing_if = "Option::is_none")] pub age_envelop: Option, #[serde(skip_serializing_if = "Option::is_none")] pub age_recipient: Option, #[serde(skip_serializing_if = "Option::is_none")] pub ecdh_envelop: Option, #[serde(skip_serializing_if = "Option::is_none")] pub ecdh_point: Option, #[serde(skip_serializing_if = "Option::is_none")] pub envelop: Option, // --------------------------------------- pub envelops: Option>, #[serde(skip_serializing_if = "Option::is_none")] pub encryption_algorithm: Option, pub nonce: String, pub file_length: u64, pub file_last_modified: u64, pub file_edit_count: Option, pub compress: bool, } #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct TinyEncryptEnvelop { pub r#type: TinyEncryptEnvelopType, pub kid: String, #[serde(skip_serializing_if = "Option::is_none")] pub desc: Option, pub encrypted_key: String, } /// NOTICE: Kms and Age is not being supported in the future #[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, PartialOrd)] pub enum TinyEncryptEnvelopType { // OpenPGP RSA #[serde(rename = "pgp")] Pgp, // OpenPGP X25519 #[serde(rename = "pgp-x25519")] PgpX25519, // Static X25519 (less secure) #[serde(rename = "static-x25519")] StaticX25519, // Key P256 (Private key in Secure Enclave) #[serde(rename = "key-p256")] KeyP256, // Age, tiny-encrypt-rs is not supported #[serde(rename = "age")] Age, // ECDH P256 #[serde(rename = "ecdh")] Ecdh, // ECDH P384 #[serde(rename = "ecdh-p384")] EcdhP384, // KMS, tiny-encrypt-rs is not supported #[serde(rename = "kms")] Kms, } impl TinyEncryptEnvelopType { pub fn get_upper_name(&self) -> String { self.get_name().to_uppercase() } pub fn get_name(&self) -> &'static str { match self { TinyEncryptEnvelopType::Pgp => "pgp", TinyEncryptEnvelopType::PgpX25519 => "pgp-x25519", TinyEncryptEnvelopType::StaticX25519 => "static-x25519", TinyEncryptEnvelopType::KeyP256 => "key-p256", TinyEncryptEnvelopType::Age => "age", TinyEncryptEnvelopType::Ecdh => "ecdh", TinyEncryptEnvelopType::EcdhP384 => "ecdh-p384", TinyEncryptEnvelopType::Kms => "kms", } } pub fn auto_select(&self) -> bool { match self { TinyEncryptEnvelopType::Pgp => false, TinyEncryptEnvelopType::PgpX25519 => false, TinyEncryptEnvelopType::StaticX25519 => true, TinyEncryptEnvelopType::KeyP256 => true, TinyEncryptEnvelopType::Age => false, TinyEncryptEnvelopType::Ecdh => false, TinyEncryptEnvelopType::EcdhP384 => false, TinyEncryptEnvelopType::Kms => true, } } } #[derive(Clone, Debug, Serialize, Deserialize)] pub struct EncEncryptedMeta { #[serde(skip_serializing_if = "Option::is_none")] pub filename: Option, #[serde(skip_serializing_if = "Option::is_none")] pub c_time: Option, #[serde(skip_serializing_if = "Option::is_none")] pub m_time: Option, } impl EncEncryptedMeta { pub fn unseal(crypto: Cryptor, key_nonce: &KeyNonce, message: &[u8]) -> XResult { let mut decrypted = opt_result!(crypto_simple::try_decrypt_with_salt( crypto, key_nonce, SALT_META, message), "Decrypt encrypted meta failed: {}"); decrypted = opt_result!(compress::decompress(&decrypted), "Depress encrypted meta failed: {}"); let meta = opt_result!( serde_json::from_slice::(&decrypted), "Parse encrypted meta failed: {}"); Ok(meta) } pub fn seal(&self, crypto: Cryptor, key_nonce: &KeyNonce) -> XResult> { let mut encrypted_meta_json = serde_json::to_vec(self).unwrap(); encrypted_meta_json = opt_result!( compress::compress_default(&encrypted_meta_json), "Compress encrypted meta failed: {}"); let encrypted = opt_result!(crypto_simple::encrypt_with_salt( crypto, key_nonce, SALT_META, encrypted_meta_json.as_slice()), "Encrypt encrypted meta failed: {}"); Ok(encrypted) } } pub struct EncMetadata { pub comment: Option, pub encrypted_comment: Option, pub encrypted_meta: Option, pub compress: bool, } impl TinyEncryptMeta { pub fn new(metadata: &Metadata, enc_metadata: &EncMetadata, cryptor: Cryptor, nonce: &[u8], envelops: Vec) -> Self { TinyEncryptMeta { version: TINY_ENCRYPT_VERSION_11.to_string(), created: util_time::get_current_millis() as u64, user_agent: get_user_agent(), comment: enc_metadata.comment.to_owned(), encrypted_comment: enc_metadata.encrypted_comment.to_owned(), encrypted_meta: enc_metadata.encrypted_meta.to_owned(), pgp_envelop: None, pgp_fingerprint: None, age_envelop: None, age_recipient: None, ecdh_envelop: None, ecdh_point: None, envelop: None, envelops: Some(envelops), encryption_algorithm: Some(cryptor.get_name()), nonce: encode_base64(nonce), file_length: metadata.len(), file_last_modified: match metadata.modified() { Ok(modified) => get_millis(&modified) as u64, Err(_) => 0, }, file_edit_count: None, compress: enc_metadata.compress, } } pub fn normalize(&mut self) { if self.envelops.is_none() { self.envelops = Some(vec![]); } self.normalize_envelop(); self.normalize_pgp_envelop(); self.normalize_age_envelop(); self.normalize_ecdh_envelop(); } fn normalize_envelop(&mut self) { if let (Some(envelop), Some(envelops)) = (&self.envelop, &mut self.envelops) { envelops.push(TinyEncryptEnvelop { r#type: TinyEncryptEnvelopType::Kms, kid: "".to_string(), desc: None, encrypted_key: envelop.into(), }); self.envelop = None; } } fn normalize_pgp_envelop(&mut self) { if let (Some(pgp_envelop), Some(pgp_fingerprint), Some(envelops)) = (&self.pgp_envelop, &self.pgp_fingerprint, &mut self.envelops) { envelops.push(TinyEncryptEnvelop { r#type: TinyEncryptEnvelopType::Pgp, kid: pgp_fingerprint.into(), desc: None, encrypted_key: pgp_envelop.into(), }); self.pgp_envelop = None; self.pgp_fingerprint = None; } } fn normalize_age_envelop(&mut self) { if let (Some(age_envelop), Some(age_recipient), Some(envelops)) = (&self.age_envelop, &self.age_recipient, &mut self.envelops) { envelops.push(TinyEncryptEnvelop { r#type: TinyEncryptEnvelopType::Age, kid: age_recipient.into(), desc: None, encrypted_key: age_envelop.into(), }); self.age_envelop = None; self.age_recipient = None; } } fn normalize_ecdh_envelop(&mut self) { if let (Some(ecdh_envelop), Some(ecdh_point), Some(envelops)) = (&self.ecdh_envelop, &self.ecdh_point, &mut self.envelops) { envelops.push(TinyEncryptEnvelop { r#type: TinyEncryptEnvelopType::Ecdh, kid: ecdh_point.into(), desc: None, encrypted_key: ecdh_envelop.into(), }); self.ecdh_envelop = None; self.ecdh_point = None; } } }