Files
tiny-encrypt-rs/src/spec.rs
2023-10-13 21:54:26 +08:00

221 lines
7.8 KiB
Rust

use std::fs::Metadata;
use flate2::Compression;
use rust_util::{opt_result, util_time, XResult};
use rust_util::util_time::get_millis;
use serde::{Deserialize, Serialize};
use crate::{compress, crypto_aes};
use crate::consts::{SALT_META, TINY_ENC_AES_GCM};
use crate::util::{encode_base64, get_user_agent};
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<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub encrypted_comment: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub encrypted_meta: Option<String>,
// ---------------------------------------
#[serde(skip_serializing_if = "Option::is_none")]
pub pgp_envelop: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub pgp_fingerprint: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub age_envelop: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub age_recipient: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub ecdh_envelop: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub ecdh_point: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub envelop: Option<String>,
// ---------------------------------------
pub envelops: Option<Vec<TinyEncryptEnvelop>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub encryption_algorithm: Option<String>,
pub nonce: String,
pub file_length: u64,
pub file_last_modified: u64,
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<String>,
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 {
#[serde(rename = "pgp")]
Pgp,
#[serde(rename = "pgp-x25519")]
PgpX25519,
#[serde(rename = "age")]
Age,
#[serde(rename = "ecdh")]
Ecdh,
#[serde(rename = "ecdh-p384")]
EcdhP384,
#[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::Age => "age",
TinyEncryptEnvelopType::Ecdh => "ecdh",
TinyEncryptEnvelopType::EcdhP384 => "ecdh-p384",
TinyEncryptEnvelopType::Kms => "kms",
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct EncEncryptedMeta {
#[serde(skip_serializing_if = "Option::is_none")]
pub filename: Option<String>,
}
impl EncEncryptedMeta {
pub fn unseal(key: &[u8], nonce: &[u8], message: &[u8]) -> XResult<Self> {
let mut decrypted = opt_result!(crypto_aes::try_aes_gcm_decrypt_with_salt(
key, nonce, SALT_META, message), "Decrypt failed: {}");
decrypted = opt_result!(compress::decompress(&decrypted), "Decode faield: {}");
let meta = opt_result!(
serde_json::from_slice::<Self>(&decrypted), "Parse failed: {}");
Ok(meta)
}
pub fn seal(&self, key: &[u8], nonce: &[u8]) -> XResult<Vec<u8>> {
let mut encrypted_meta_json = serde_json::to_vec(self).unwrap();
encrypted_meta_json = opt_result!(
compress::compress(Compression::default(), &encrypted_meta_json), "Compress failed: {}");
let encrypted = opt_result!(crypto_aes::aes_gcm_encrypt_with_salt(
key, nonce, SALT_META, encrypted_meta_json.as_slice()), "Encrypt failed: {}");
Ok(encrypted)
}
}
pub struct EncMetadata {
pub comment: Option<String>,
pub encrypted_comment: Option<String>,
pub encrypted_meta: Option<String>,
pub compress: bool,
}
impl TinyEncryptMeta {
pub fn new(metadata: &Metadata, enc_metadata: &EncMetadata, nonce: &[u8], envelops: Vec<TinyEncryptEnvelop>) -> 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(TINY_ENC_AES_GCM.to_string()),
nonce: encode_base64(nonce),
file_length: metadata.len(),
file_last_modified: match metadata.modified() {
Ok(modified) => get_millis(&modified) as u64,
Err(_) => 0,
},
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;
}
}
}