envio 0.7.0

A secure command-line tool for managing environment variables
pub mod gpg;
pub mod none;
pub mod passphrase;
pub mod symmetric;

pub use gpg::GPG;
pub use none::NONE;
pub use passphrase::PASSPHRASE;
pub use symmetric::SYMMETRIC;

use dyn_clone::DynClone;
use serde::{Deserialize, Serialize};
use serde_with::{base64::Base64, serde_as};
use std::any::Any;
use strum_macros::{AsRefStr, EnumIter, EnumString};
use zeroize::Zeroizing;

use crate::{env::EnvMap, error::Result};

#[derive(Copy, Clone, PartialEq, Serialize, Deserialize, EnumIter, EnumString, AsRefStr)]
#[serde(rename_all = "lowercase")]
#[strum(ascii_case_insensitive, serialize_all = "lowercase")]
pub enum CipherKind {
    NONE,
    PASSPHRASE,
    SYMMETRIC,
    GPG,
}

impl std::fmt::Display for CipherKind {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.as_ref())
    }
}

#[serde_as]
#[derive(Serialize, Deserialize)]
#[serde(untagged)]
pub enum EncryptedContent {
    Bytes(#[serde_as(as = "Base64")] Vec<u8>),
    Json(serde_json::Value),
}

impl EncryptedContent {
    pub fn as_bytes(&self) -> Result<Vec<u8>> {
        match self {
            EncryptedContent::Bytes(b) => Ok(b.clone()),
            EncryptedContent::Json(value) => Ok(serde_json::to_vec(value)?),
        }
    }
}

pub trait Cipher: Any + Send + DynClone {
    fn encrypt(&mut self, envs: &EnvMap) -> Result<EncryptedContent>;
    fn decrypt(&self, encrypted_data: &EncryptedContent) -> Result<EnvMap>;
    fn kind(&self) -> CipherKind;

    fn export_metadata(&self) -> Option<serde_json::Value> {
        None
    }

    #[allow(unused)]
    fn import_metadata(&mut self, data: serde_json::Value) -> Result<()> {
        Ok(())
    }

    fn as_any(&self) -> &dyn Any;
    fn as_any_mut(&mut self) -> &mut dyn Any;
}

impl Clone for Box<dyn Cipher> {
    fn clone(&self) -> Box<dyn Cipher> {
        dyn_clone::clone_box(self.as_ref())
    }
}

pub fn create_cipher(
    cipher_kind: CipherKind,
    key: Option<Zeroizing<String>>,
) -> Result<Box<dyn Cipher>> {
    match cipher_kind {
        CipherKind::NONE => Ok(Box::new(NONE)),
        CipherKind::PASSPHRASE => Ok(Box::new(PASSPHRASE::new(key.unwrap_or_default()))),
        CipherKind::SYMMETRIC => Ok(Box::new(SYMMETRIC::new(key.unwrap_or_default()))),
        CipherKind::GPG => Ok(Box::new(GPG::new(key.unwrap_or_default().to_string()))),
    }
}