anubis_age/
error.rs

1//! Error type.
2
3use std::collections::HashSet;
4use std::fmt;
5use std::io;
6
7use crate::{wfl, wlnfl};
8
9#[cfg(feature = "plugin")]
10use anubis_core::format::Stanza;
11
12/// Errors returned when converting an identity file to a recipients file.
13#[derive(Debug)]
14pub enum IdentityFileConvertError {
15    /// An I/O error occurred while writing out a recipient corresponding to an identity
16    /// in this file.
17    FailedToWriteOutput(io::Error),
18    /// The identity file contains a plugin identity, which can be converted to a
19    /// recipient for encryption purposes, but not for writing a recipients file.
20    #[cfg(feature = "plugin")]
21    #[cfg_attr(docsrs, doc(cfg(feature = "plugin")))]
22    IdentityFileContainsPlugin {
23        /// The given identity file.
24        filename: Option<String>,
25        /// The name of the plugin.
26        plugin_name: String,
27    },
28    /// The identity file contains no identities, and thus cannot be used to produce a
29    /// recipients file.
30    NoIdentities {
31        /// The given identity file.
32        filename: Option<String>,
33    },
34}
35
36impl fmt::Display for IdentityFileConvertError {
37    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
38        match self {
39            IdentityFileConvertError::FailedToWriteOutput(e) => {
40                wfl!(f, "err-failed-to-write-output", err = e.to_string())
41            }
42            #[cfg(feature = "plugin")]
43            IdentityFileConvertError::IdentityFileContainsPlugin {
44                filename,
45                plugin_name,
46            } => {
47                wlnfl!(
48                    f,
49                    "err-identity-file-contains-plugin",
50                    filename = filename.as_deref().unwrap_or_default(),
51                    plugin_name = plugin_name.as_str(),
52                )?;
53                wfl!(
54                    f,
55                    "rec-identity-file-contains-plugin",
56                    plugin_name = plugin_name.as_str(),
57                )
58            }
59            IdentityFileConvertError::NoIdentities { filename } => match filename {
60                Some(filename) => {
61                    wfl!(f, "err-no-identities-in-file", filename = filename.as_str())
62                }
63                None => wfl!(f, "err-no-identities-in-stdin"),
64            },
65        }
66    }
67}
68
69impl std::error::Error for IdentityFileConvertError {
70    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
71        match self {
72            IdentityFileConvertError::FailedToWriteOutput(e) => Some(e),
73            _ => None,
74        }
75    }
76}
77
78/// Errors returned by a plugin.
79#[cfg(feature = "plugin")]
80#[cfg_attr(docsrs, doc(cfg(feature = "plugin")))]
81#[derive(Clone, Debug)]
82pub enum PluginError {
83    /// An error caused by a specific identity.
84    Identity {
85        /// The plugin's binary name.
86        binary_name: String,
87        /// The error message.
88        message: String,
89    },
90    /// An error caused by a specific recipient.
91    Recipient {
92        /// The plugin's binary name.
93        binary_name: String,
94        /// The recipient.
95        recipient: String,
96        /// The error message.
97        message: String,
98    },
99    /// Some other error we don't know about.
100    Other {
101        /// The error kind.
102        kind: String,
103        /// Any metadata associated with the error.
104        metadata: Vec<String>,
105        /// The error message.
106        message: String,
107    },
108}
109
110#[cfg(feature = "plugin")]
111impl From<Stanza> for PluginError {
112    fn from(mut s: Stanza) -> Self {
113        assert!(s.tag == "error");
114        let kind = s.args.remove(0);
115        PluginError::Other {
116            kind,
117            metadata: s.args,
118            message: String::from_utf8_lossy(&s.body).to_string(),
119        }
120    }
121}
122
123#[cfg(feature = "plugin")]
124impl fmt::Display for PluginError {
125    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
126        match self {
127            PluginError::Identity {
128                binary_name,
129                message,
130            } => wfl!(
131                f,
132                "err-plugin-identity",
133                plugin_name = binary_name.as_str(),
134                message = message.as_str(),
135            ),
136            PluginError::Recipient {
137                binary_name,
138                recipient,
139                message,
140            } => wfl!(
141                f,
142                "err-plugin-recipient",
143                plugin_name = binary_name.as_str(),
144                recipient = recipient.as_str(),
145                message = message.as_str(),
146            ),
147            PluginError::Other {
148                kind,
149                metadata,
150                message,
151            } => {
152                write!(f, "({}", kind)?;
153                for d in metadata {
154                    write!(f, " {}", d)?;
155                }
156                write!(f, ")")?;
157                if !message.is_empty() {
158                    write!(f, " {}", message)?;
159                }
160                Ok(())
161            }
162        }
163    }
164}
165
166/// The various errors that can be returned during the encryption process.
167#[derive(Debug)]
168pub enum EncryptError {
169    /// An error occured while decrypting passphrase-encrypted identities.
170    EncryptedIdentities(DecryptError),
171    /// The encryptor was given recipients that declare themselves incompatible.
172    IncompatibleRecipients {
173        /// The set of labels from the first recipient provided to the encryptor.
174        l_labels: HashSet<String>,
175        /// The set of labels from the first non-matching recipient.
176        r_labels: HashSet<String>,
177    },
178    /// One or more of the labels from the first recipient provided to the encryptor are
179    /// invalid.
180    ///
181    /// Labels must be valid age "arbitrary string"s (`1*VCHAR` in ABNF).
182    InvalidRecipientLabels(HashSet<String>),
183    /// An I/O error occurred during encryption.
184    Io(io::Error),
185    /// A required plugin could not be found.
186    #[cfg(feature = "plugin")]
187    #[cfg_attr(docsrs, doc(cfg(feature = "plugin")))]
188    MissingPlugin {
189        /// The plugin's binary name.
190        binary_name: String,
191    },
192    /// The encryptor was not given any recipients.
193    MissingRecipients,
194    /// [`scrypt::Recipient`] was mixed with other recipient types.
195    ///
196    /// [`scrypt::Recipient`]: crate::scrypt::Recipient
197    MixedRecipientAndPassphrase,
198    /// Errors from a plugin.
199    #[cfg(feature = "plugin")]
200    #[cfg_attr(docsrs, doc(cfg(feature = "plugin")))]
201    Plugin(Vec<PluginError>),
202}
203
204impl From<io::Error> for EncryptError {
205    fn from(e: io::Error) -> Self {
206        EncryptError::Io(e)
207    }
208}
209
210impl Clone for EncryptError {
211    fn clone(&self) -> Self {
212        match self {
213            Self::EncryptedIdentities(e) => Self::EncryptedIdentities(e.clone()),
214            Self::IncompatibleRecipients { l_labels, r_labels } => Self::IncompatibleRecipients {
215                l_labels: l_labels.clone(),
216                r_labels: r_labels.clone(),
217            },
218            Self::InvalidRecipientLabels(labels) => Self::InvalidRecipientLabels(labels.clone()),
219            Self::Io(e) => Self::Io(io::Error::new(e.kind(), e.to_string())),
220            #[cfg(feature = "plugin")]
221            Self::MissingPlugin { binary_name } => Self::MissingPlugin {
222                binary_name: binary_name.clone(),
223            },
224            Self::MissingRecipients => Self::MissingRecipients,
225            Self::MixedRecipientAndPassphrase => Self::MixedRecipientAndPassphrase,
226            #[cfg(feature = "plugin")]
227            Self::Plugin(e) => Self::Plugin(e.clone()),
228        }
229    }
230}
231
232fn print_labels(labels: &HashSet<String>) -> String {
233    let mut s = String::new();
234    for (i, label) in labels.iter().enumerate() {
235        s.push_str(label);
236        if i != 0 {
237            s.push_str(", ");
238        }
239    }
240    s
241}
242
243impl fmt::Display for EncryptError {
244    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
245        match self {
246            EncryptError::EncryptedIdentities(e) => e.fmt(f),
247            EncryptError::IncompatibleRecipients { l_labels, r_labels } => {
248                match (l_labels.is_empty(), r_labels.is_empty()) {
249                    (true, true) => unreachable!("labels are compatible"),
250                    (false, true) => {
251                        wfl!(
252                            f,
253                            "err-incompatible-recipients-oneway",
254                            labels = print_labels(l_labels),
255                        )
256                    }
257                    (true, false) => {
258                        wfl!(
259                            f,
260                            "err-incompatible-recipients-oneway",
261                            labels = print_labels(r_labels),
262                        )
263                    }
264                    (false, false) => wfl!(
265                        f,
266                        "err-incompatible-recipients-twoway",
267                        left = print_labels(l_labels),
268                        right = print_labels(r_labels),
269                    ),
270                }
271            }
272            EncryptError::InvalidRecipientLabels(labels) => wfl!(
273                f,
274                "err-invalid-recipient-labels",
275                labels = print_labels(labels),
276            ),
277            EncryptError::Io(e) => e.fmt(f),
278            #[cfg(feature = "plugin")]
279            EncryptError::MissingPlugin { binary_name } => {
280                wlnfl!(f, "err-missing-plugin", plugin_name = binary_name.as_str())?;
281                wfl!(f, "rec-missing-plugin")
282            }
283            EncryptError::MissingRecipients => wfl!(f, "err-missing-recipients"),
284            EncryptError::MixedRecipientAndPassphrase => {
285                wfl!(f, "err-mixed-recipient-passphrase")
286            }
287            #[cfg(feature = "plugin")]
288            EncryptError::Plugin(errors) => match &errors[..] {
289                [] => unreachable!(),
290                [e] => write!(f, "{}", e),
291                _ => {
292                    wlnfl!(f, "err-plugin-multiple")?;
293                    for e in errors {
294                        writeln!(f, "- {}", e)?;
295                    }
296                    Ok(())
297                }
298            },
299        }
300    }
301}
302
303impl std::error::Error for EncryptError {
304    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
305        match self {
306            EncryptError::EncryptedIdentities(inner) => Some(inner),
307            EncryptError::Io(inner) => Some(inner),
308            _ => None,
309        }
310    }
311}
312
313/// The various errors that can be returned during the decryption process.
314#[derive(Debug)]
315pub enum DecryptError {
316    /// The age file failed to decrypt.
317    DecryptionFailed,
318    /// The age file used an excessive work factor for passphrase encryption.
319    ExcessiveWork {
320        /// The work factor required to decrypt.
321        required: u8,
322        /// The target work factor for this device (around 1 second of work).
323        target: u8,
324    },
325    /// The age header was invalid.
326    InvalidHeader,
327    /// The MAC in the age header was invalid.
328    InvalidMac,
329    /// An I/O error occurred during decryption.
330    Io(io::Error),
331    /// Failed to decrypt an encrypted key.
332    KeyDecryptionFailed,
333    /// A required plugin could not be found.
334    #[cfg(feature = "plugin")]
335    #[cfg_attr(docsrs, doc(cfg(feature = "plugin")))]
336    MissingPlugin {
337        /// The plugin's binary name.
338        binary_name: String,
339    },
340    /// None of the provided keys could be used to decrypt the age file.
341    NoMatchingKeys,
342    /// Errors from a plugin.
343    #[cfg(feature = "plugin")]
344    #[cfg_attr(docsrs, doc(cfg(feature = "plugin")))]
345    Plugin(Vec<PluginError>),
346    /// An unknown age format, probably from a newer version.
347    UnknownFormat,
348}
349
350impl Clone for DecryptError {
351    fn clone(&self) -> Self {
352        match self {
353            Self::DecryptionFailed => Self::DecryptionFailed,
354            Self::ExcessiveWork { required, target } => Self::ExcessiveWork {
355                required: *required,
356                target: *target,
357            },
358            Self::InvalidHeader => Self::InvalidHeader,
359            Self::InvalidMac => Self::InvalidMac,
360            Self::Io(e) => Self::Io(io::Error::new(e.kind(), e.to_string())),
361            Self::KeyDecryptionFailed => Self::KeyDecryptionFailed,
362            #[cfg(feature = "plugin")]
363            Self::MissingPlugin { binary_name } => Self::MissingPlugin {
364                binary_name: binary_name.clone(),
365            },
366            Self::NoMatchingKeys => Self::NoMatchingKeys,
367            #[cfg(feature = "plugin")]
368            Self::Plugin(e) => Self::Plugin(e.clone()),
369            Self::UnknownFormat => Self::UnknownFormat,
370        }
371    }
372}
373
374impl fmt::Display for DecryptError {
375    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
376        match self {
377            DecryptError::DecryptionFailed => wfl!(f, "err-decryption-failed"),
378            DecryptError::ExcessiveWork { required, target } => {
379                wlnfl!(f, "err-excessive-work")?;
380                wfl!(
381                    f,
382                    "rec-excessive-work",
383                    duration = (1 << (required - target)),
384                )
385            }
386            DecryptError::InvalidHeader => wfl!(f, "err-header-invalid"),
387            DecryptError::InvalidMac => wfl!(f, "err-header-mac-invalid"),
388            DecryptError::Io(e) => e.fmt(f),
389            DecryptError::KeyDecryptionFailed => wfl!(f, "err-key-decryption"),
390            #[cfg(feature = "plugin")]
391            DecryptError::MissingPlugin { binary_name } => {
392                wlnfl!(f, "err-missing-plugin", plugin_name = binary_name.as_str())?;
393                wfl!(f, "rec-missing-plugin")
394            }
395            DecryptError::NoMatchingKeys => wfl!(f, "err-no-matching-keys"),
396            #[cfg(feature = "plugin")]
397            DecryptError::Plugin(errors) => match &errors[..] {
398                [] => unreachable!(),
399                [e] => write!(f, "{}", e),
400                _ => {
401                    wlnfl!(f, "err-plugin-multiple")?;
402                    for e in errors {
403                        writeln!(f, "- {}", e)?;
404                    }
405                    Ok(())
406                }
407            },
408            DecryptError::UnknownFormat => {
409                wlnfl!(f, "err-unknown-format")?;
410                wfl!(f, "rec-unknown-format")
411            }
412        }
413    }
414}
415
416impl From<chacha20poly1305::aead::Error> for DecryptError {
417    fn from(_: chacha20poly1305::aead::Error) -> Self {
418        DecryptError::DecryptionFailed
419    }
420}
421
422impl From<io::Error> for DecryptError {
423    fn from(e: io::Error) -> Self {
424        DecryptError::Io(e)
425    }
426}
427
428impl From<hmac::digest::MacError> for DecryptError {
429    fn from(_: hmac::digest::MacError) -> Self {
430        DecryptError::InvalidMac
431    }
432}
433
434#[cfg(feature = "ssh")]
435#[cfg_attr(docsrs, doc(cfg(feature = "ssh")))]
436impl From<rsa::errors::Error> for DecryptError {
437    fn from(_: rsa::errors::Error) -> Self {
438        DecryptError::DecryptionFailed
439    }
440}
441
442impl std::error::Error for DecryptError {
443    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
444        match self {
445            DecryptError::Io(inner) => Some(inner),
446            _ => None,
447        }
448    }
449}