Skip to main content

mneme/crypto/
engine.rs

1use std::io::{Read, Write};
2
3/// Motor de encriptación/desencriptación.
4#[derive(Debug)]
5pub struct CryptoEngine {
6    recipients: Vec<crate::crypto::keys::RecipientKey>,
7    identity: Option<crate::crypto::keys::IdentityKey>,
8}
9
10impl CryptoEngine {
11    /// Crea un nuevo motor con los recipients dados.
12    pub fn new(recipients: Vec<crate::crypto::keys::RecipientKey>) -> Self {
13        Self {
14            recipients,
15            identity: None,
16        }
17    }
18
19    /// Encripta bytes para todos los recipients. Retorna ciphertext como Vec<u8>.
20    pub fn encrypt(&self, plaintext: &[u8]) -> crate::error::Result<Vec<u8>> {
21        if self.recipients.is_empty() {
22            return Err(crate::error::MnemeError::NoRecipientsConfigured);
23        }
24
25        let mut age_recipients: Vec<Box<dyn age::Recipient + Send>> = Vec::new();
26
27        for key in &self.recipients {
28            match key {
29                crate::crypto::keys::RecipientKey::Age(s) => {
30                    let recipient: age::x25519::Recipient = s.parse().map_err(|e: &str| {
31                        crate::error::MnemeError::Config(format!("invalid age key: {}", e))
32                    })?;
33                    age_recipients.push(Box::new(recipient));
34                }
35                crate::crypto::keys::RecipientKey::Ssh(s) => {
36                    let recipient: age::ssh::Recipient =
37                        s.parse().map_err(|e: age::ssh::ParseRecipientKeyError| {
38                            crate::error::MnemeError::Config(format!("invalid ssh key: {:?}", e))
39                        })?;
40                    age_recipients.push(Box::new(recipient));
41                }
42            }
43        }
44
45        let encryptor = age::Encryptor::with_recipients(age_recipients)
46            .ok_or(crate::error::MnemeError::NoRecipientsConfigured)?;
47
48        let mut ciphertext = vec![];
49        let mut writer = encryptor
50            .wrap_output(&mut ciphertext)
51            .map_err(|e| crate::error::MnemeError::Config(format!("wrap output error: {}", e)))?;
52        writer.write_all(plaintext)?;
53        writer
54            .finish()
55            .map_err(|e| crate::error::MnemeError::Config(format!("finish error: {}", e)))?;
56
57        Ok(ciphertext)
58    }
59
60    /// Encripta un string, retorna ciphertext en hex.
61    pub fn encrypt_str(&self, plaintext: &str) -> crate::error::Result<String> {
62        let ciphertext = self.encrypt(plaintext.as_bytes())?;
63        Ok(hex::encode(ciphertext))
64    }
65
66    /// Desencripta ciphertext hex.
67    pub fn decrypt_str(&mut self, ciphertext_hex: &str) -> crate::error::Result<String> {
68        let ciphertext = hex::decode(ciphertext_hex)
69            .map_err(|e| crate::error::MnemeError::Config(format!("hex decode error: {}", e)))?;
70
71        let identity = self
72            .identity
73            .as_ref()
74            .ok_or(crate::error::MnemeError::IdentityNotLoaded)?;
75
76        let decrypted = match identity {
77            crate::crypto::keys::IdentityKey::Ssh(path) => {
78                let key_content = std::fs::read_to_string(path)?;
79                let identity = age::ssh::Identity::from_buffer(
80                    std::io::BufReader::new(key_content.as_bytes()),
81                    Some(path.display().to_string()),
82                )
83                .map_err(|_e| crate::error::MnemeError::DecryptionFailed)?;
84
85                let decryptor = match age::Decryptor::new(ciphertext.as_slice())
86                    .map_err(|_| crate::error::MnemeError::DecryptionFailed)?
87                {
88                    age::Decryptor::Recipients(d) => d,
89                    _ => return Err(crate::error::MnemeError::DecryptionFailed),
90                };
91
92                let mut reader = decryptor
93                    .decrypt(std::iter::once(&identity as &dyn age::Identity))
94                    .map_err(|_| crate::error::MnemeError::DecryptionFailed)?;
95                let mut output = String::new();
96                reader.read_to_string(&mut output)?;
97                output
98            }
99            crate::crypto::keys::IdentityKey::Age(path) => {
100                let key_content = std::fs::read_to_string(path)?;
101                let identity: age::x25519::Identity = key_content
102                    .trim()
103                    .parse()
104                    .map_err(|_e: &str| crate::error::MnemeError::DecryptionFailed)?;
105
106                let decryptor = match age::Decryptor::new(ciphertext.as_slice())
107                    .map_err(|_| crate::error::MnemeError::DecryptionFailed)?
108                {
109                    age::Decryptor::Recipients(d) => d,
110                    _ => return Err(crate::error::MnemeError::DecryptionFailed),
111                };
112
113                let mut reader = decryptor
114                    .decrypt(std::iter::once(&identity as &dyn age::Identity))
115                    .map_err(|_| crate::error::MnemeError::DecryptionFailed)?;
116                let mut output = String::new();
117                reader.read_to_string(&mut output)?;
118                output
119            }
120        };
121
122        Ok(decrypted)
123    }
124
125    /// Verifica si puede desencriptar.
126    pub fn can_decrypt(&self) -> bool {
127        self.identity.is_some()
128    }
129
130    /// Carga la identidad de desencriptación.
131    pub fn load_identity(&mut self) -> crate::error::Result<()> {
132        let identity = crate::crypto::keys::IdentityKey::detect()?;
133        self.identity = Some(identity);
134        Ok(())
135    }
136
137    /// Carga identidad desde path específico.
138    pub fn load_identity_from_path(
139        &mut self,
140        path: &std::path::PathBuf,
141    ) -> crate::error::Result<()> {
142        let identity = crate::crypto::keys::IdentityKey::from_path(path)?;
143        self.identity = Some(identity);
144        Ok(())
145    }
146
147    /// Retorna true si hay al menos un recipient configurado.
148    pub fn has_recipients(&self) -> bool {
149        !self.recipients.is_empty()
150    }
151
152    /// Retorna el alias/descripción de la clave usada.
153    pub fn encrypted_for_label(&self) -> String {
154        self.recipients
155            .iter()
156            .map(|r| r.key_type().to_string())
157            .collect::<Vec<_>>()
158            .join(",")
159    }
160}