sheesy_vault/
error.rs

1use crate::util::write_at;
2use crate::util::FingerprintUserId;
3use failure;
4use failure::Fail;
5use gpgme;
6use mktemp::Temp;
7use serde_yaml;
8use std::fmt;
9use std::fs::File;
10use std::io;
11use std::path::{Path, PathBuf};
12
13#[derive(Debug, Fail)]
14#[fail(display = "The content was not encrypted for you.")]
15pub struct DecryptionError {
16    #[cause]
17    pub cause: gpgme::Error,
18}
19
20impl DecryptionError {
21    pub fn caused_by(err: gpgme::Error, alternative_text: &'static str) -> failure::Error {
22        if err.code() == gpgme::Error::NO_SECKEY.code() {
23            failure::Error::from(DecryptionError { cause: err })
24        } else {
25            err.context(alternative_text).into()
26        }
27    }
28}
29
30#[derive(Debug, Fail)]
31pub struct EncryptionError {
32    pub msg: String,
33    pub offending_recipients: Vec<String>,
34}
35
36impl fmt::Display for EncryptionError {
37    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
38        write!(f, "{}", self.msg)?;
39        for info in &self.offending_recipients {
40            write!(f, "\n{}", info)?;
41        }
42        Ok(())
43    }
44}
45
46fn find_offending_keys(ctx: &mut gpgme::Context, keys: &[gpgme::Key]) -> Result<Vec<String>, failure::Error> {
47    let mut output = Vec::new();
48    let mut obuf = Vec::<u8>::new();
49    let temp = Temp::new_file()?;
50    let temp_path = temp.to_path_buf();
51    {
52        let _ibuf = write_at(&temp_path)?;
53    }
54    let mut ibuf = File::open(&temp_path)?;
55    for key in keys {
56        if let Err(err) = ctx.encrypt(Some(key), &mut ibuf, &mut obuf) {
57            output.push(format!(
58                "Could not encrypt for recipient {} with error: {}",
59                FingerprintUserId(key),
60                err
61            ));
62        }
63    }
64    Ok(output)
65}
66
67impl EncryptionError {
68    pub fn caused_by(
69        err: gpgme::Error,
70        alternative_text: String,
71        ctx: &mut gpgme::Context,
72        keys: &[gpgme::Key],
73    ) -> failure::Error {
74        failure::Error::from(EncryptionError {
75            msg: if err.code() == gpgme::Error::UNUSABLE_PUBKEY.code() {
76                "At least one recipient you try to encrypt for is untrusted. \
77                 Consider (locally) signing the key with `gpg --sign-key <recipient>` \
78                 or ultimately trusting them."
79                    .into()
80            } else {
81                alternative_text
82            },
83            offending_recipients: match find_offending_keys(ctx, keys) {
84                Ok(v) => v,
85                Err(e) => return e,
86            },
87        })
88    }
89}
90
91#[derive(Debug, Fail)]
92pub enum VaultError {
93    ConfigurationFileExists(PathBuf),
94    Validation(failure::Error),
95    PartitionUnsupported,
96    ReadFile {
97        #[cause]
98        cause: io::Error,
99        path: PathBuf,
100    },
101    WriteFile {
102        #[cause]
103        cause: io::Error,
104        path: PathBuf,
105    },
106    Deserialization {
107        #[cause]
108        cause: serde_yaml::Error,
109        path: PathBuf,
110    },
111    Serialization {
112        #[cause]
113        cause: serde_yaml::Error,
114        path: PathBuf,
115    },
116}
117
118impl fmt::Display for VaultError {
119    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
120        use self::VaultError::*;
121        match *self {
122            Validation(ref err) => writeln!(f, "{}", err),
123            PartitionUnsupported => f.write_str("Cannot perform this operation on a partition"),
124            ConfigurationFileExists(ref path) => writeln!(
125                f,
126                "Cannot overwrite vault configuration file as it already exists at '{}'",
127                path.display()
128            ),
129            Serialization { ref path, .. } => writeln!(
130                f,
131                "Failed to serialize vault configuration file at '{}'",
132                path.display()
133            ),
134            Deserialization { ref path, .. } => writeln!(
135                f,
136                "Failed to deserialize vault configuration file at '{}'",
137                path.display()
138            ),
139            WriteFile { ref path, .. } => {
140                writeln!(f, "Failed to write vault configuration file at '{}'", path.display())
141            }
142            ReadFile { ref path, .. } => writeln!(f, "Failed to read vault configuration file at '{}'", path.display()),
143        }
144    }
145}
146
147pub enum IOMode {
148    Read,
149    Write,
150}
151
152impl VaultError {
153    pub fn from_io_err(cause: io::Error, path: &Path, mode: &IOMode) -> Self {
154        match *mode {
155            IOMode::Write => VaultError::WriteFile {
156                cause,
157                path: path.to_owned(),
158            },
159            IOMode::Read => VaultError::ReadFile {
160                cause,
161                path: path.to_owned(),
162            },
163        }
164    }
165}
166
167pub trait FailExt: Fail {
168    fn first_cause_of<T: Fail>(&self) -> Option<&T>;
169}
170
171impl<F> FailExt for F
172where
173    F: Fail,
174{
175    fn first_cause_of<T: Fail>(&self) -> Option<&T> {
176        self.causes().filter_map(|c| c.downcast_ref::<T>()).next()
177    }
178}
179
180// https://github.com/withoutboats/failure/pull/124
181pub fn first_cause_of_type<T: Fail>(root: &failure::Error) -> Option<&T> {
182    root.iter_chain().filter_map(|c| c.downcast_ref::<T>()).next()
183}