1use 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#[derive(Debug)]
14pub enum IdentityFileConvertError {
15 FailedToWriteOutput(io::Error),
18 #[cfg(feature = "plugin")]
21 #[cfg_attr(docsrs, doc(cfg(feature = "plugin")))]
22 IdentityFileContainsPlugin {
23 filename: Option<String>,
25 plugin_name: String,
27 },
28 NoIdentities {
31 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#[cfg(feature = "plugin")]
80#[cfg_attr(docsrs, doc(cfg(feature = "plugin")))]
81#[derive(Clone, Debug)]
82pub enum PluginError {
83 Identity {
85 binary_name: String,
87 message: String,
89 },
90 Recipient {
92 binary_name: String,
94 recipient: String,
96 message: String,
98 },
99 Other {
101 kind: String,
103 metadata: Vec<String>,
105 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#[derive(Debug)]
168pub enum EncryptError {
169 EncryptedIdentities(DecryptError),
171 IncompatibleRecipients {
173 l_labels: HashSet<String>,
175 r_labels: HashSet<String>,
177 },
178 InvalidRecipientLabels(HashSet<String>),
183 Io(io::Error),
185 #[cfg(feature = "plugin")]
187 #[cfg_attr(docsrs, doc(cfg(feature = "plugin")))]
188 MissingPlugin {
189 binary_name: String,
191 },
192 MissingRecipients,
194 MixedRecipientAndPassphrase,
198 #[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#[derive(Debug)]
315pub enum DecryptError {
316 DecryptionFailed,
318 ExcessiveWork {
320 required: u8,
322 target: u8,
324 },
325 InvalidHeader,
327 InvalidMac,
329 Io(io::Error),
331 KeyDecryptionFailed,
333 #[cfg(feature = "plugin")]
335 #[cfg_attr(docsrs, doc(cfg(feature = "plugin")))]
336 MissingPlugin {
337 binary_name: String,
339 },
340 NoMatchingKeys,
342 #[cfg(feature = "plugin")]
344 #[cfg_attr(docsrs, doc(cfg(feature = "plugin")))]
345 Plugin(Vec<PluginError>),
346 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}