prs_lib/crypto/
mod.rs

1//! Crypto interface.
2//!
3//! This module provides an interface to all cryptography features that are used in prs.
4//!
5//! It supports multiple cryptography protocols (e.g. GPG) and multiple backends (e.g. GPGME,
6//! GnuPG). The list of supported protocols and backends may be extended in the future.
7
8pub mod backend;
9pub mod proto;
10pub mod recipients;
11pub mod store;
12pub mod util;
13
14use std::collections::HashMap;
15use std::fmt;
16use std::fs;
17use std::path::Path;
18
19use anyhow::Result;
20use thiserror::Error;
21
22use crate::{Ciphertext, Plaintext, Recipients};
23
24/// Crypto protocol.
25///
26/// This list contains all protocols supported by the prs project. This does not mean that all
27/// protocols are supported at runtime in a given build.
28#[non_exhaustive]
29#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
30pub enum Proto {
31    /// GPG crypto.
32    Gpg,
33}
34
35impl Proto {
36    /// Get the protocol display name.
37    pub fn name(&self) -> &str {
38        match self {
39            Self::Gpg => "GPG",
40        }
41    }
42}
43
44/// Crypto configuration.
45///
46/// Allows configuring extra properties for contexts globally.
47pub struct Config {
48    /// Protocol used.
49    pub proto: Proto,
50
51    /// Use TTY for password input with GPG.
52    pub gpg_tty: bool,
53
54    /// Whether to show verbose output.
55    pub verbose: bool,
56}
57
58impl Config {
59    /// Construct config with given protocol.
60    pub fn from(proto: Proto) -> Self {
61        Self {
62            proto,
63            gpg_tty: false,
64            verbose: false,
65        }
66    }
67}
68
69/// Represents a key.
70///
71/// The key type may be any of the supported crypto proto types.
72#[derive(Clone, PartialEq)]
73#[non_exhaustive]
74pub enum Key {
75    /// An GPG key.
76    #[cfg(feature = "_crypto-gpg")]
77    Gpg(proto::gpg::Key),
78}
79
80impl Key {
81    /// Get key protocol type.
82    pub fn proto(&self) -> Proto {
83        match self {
84            #[cfg(feature = "_crypto-gpg")]
85            Key::Gpg(_) => Proto::Gpg,
86        }
87    }
88
89    /// Key fingerprint.
90    pub fn fingerprint(&self, short: bool) -> String {
91        match self {
92            #[cfg(feature = "_crypto-gpg")]
93            Key::Gpg(key) => key.fingerprint(short),
94        }
95    }
96
97    /// Display string for user.
98    pub fn display(&self) -> String {
99        match self {
100            #[cfg(feature = "_crypto-gpg")]
101            Key::Gpg(key) => key.display_user(),
102        }
103    }
104}
105
106impl fmt::Display for Key {
107    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
108        write!(
109            f,
110            "[{}] {} - {}",
111            self.proto().name(),
112            self.fingerprint(true),
113            self.display(),
114        )
115    }
116}
117
118/// Get crypto context for given proto type at runtime.
119///
120/// This selects a compatible crypto context at runtime.
121///
122/// # Errors
123///
124/// Errors if no compatible crypto context is available for the selected protocol because no
125/// backend is providing it. Also errors if creating the context fails.
126pub fn context(config: &Config) -> Result<Context, Err> {
127    // Select proper crypto backend
128    match config.proto {
129        #[allow(unreachable_code)]
130        Proto::Gpg => {
131            #[cfg(feature = "backend-gpgme")]
132            return Ok(Context::from(Box::new(
133                backend::gpgme::context::context(config).map_err(|err| Err::Context(err.into()))?,
134            )));
135            #[cfg(feature = "backend-gnupg-bin")]
136            return Ok(Context::from(Box::new(
137                backend::gnupg_bin::context::context(config)
138                    .map_err(|err| Err::Context(err.into()))?,
139            )));
140            #[cfg(feature = "backend-rpgpie")]
141            return Ok(Context::from(Box::new(
142                backend::rpgpie::context::context(config)
143                    .map_err(|err| Err::Context(err.into()))?,
144            )));
145        }
146    }
147
148    #[allow(unreachable_code)]
149    Err(Err::Unsupported(config.proto))
150}
151
152/// Generic context.
153pub struct Context {
154    /// Inner context.
155    context: Box<dyn IsContext>,
156}
157
158impl Context {
159    pub fn from(context: Box<dyn IsContext>) -> Self {
160        Self { context }
161    }
162}
163
164impl IsContext for Context {
165    fn encrypt(&mut self, recipients: &Recipients, plaintext: Plaintext) -> Result<Ciphertext> {
166        self.context.encrypt(recipients, plaintext)
167    }
168
169    fn decrypt(&mut self, ciphertext: Ciphertext) -> Result<Plaintext> {
170        self.context.decrypt(ciphertext)
171    }
172
173    fn can_decrypt(&mut self, ciphertext: Ciphertext) -> Result<bool> {
174        self.context.can_decrypt(ciphertext)
175    }
176
177    fn keys_public(&mut self) -> Result<Vec<Key>> {
178        self.context.keys_public()
179    }
180
181    fn keys_private(&mut self) -> Result<Vec<Key>> {
182        self.context.keys_private()
183    }
184
185    fn import_key(&mut self, key: &[u8]) -> Result<()> {
186        self.context.import_key(key)
187    }
188
189    fn export_key(&mut self, key: Key) -> Result<Vec<u8>> {
190        self.context.export_key(key)
191    }
192
193    fn supports_proto(&self, proto: Proto) -> bool {
194        self.context.supports_proto(proto)
195    }
196}
197
198/// Defines generic crypto context.
199///
200/// Implemented on backend specific cryptography contexcts, makes using it possible through a
201/// single simple interface.
202pub trait IsContext {
203    /// Encrypt plaintext for recipients.
204    fn encrypt(&mut self, recipients: &Recipients, plaintext: Plaintext) -> Result<Ciphertext>;
205
206    /// Encrypt plaintext and write it to the file.
207    fn encrypt_file(
208        &mut self,
209        recipients: &Recipients,
210        plaintext: Plaintext,
211        path: &Path,
212    ) -> Result<()> {
213        fs::write(path, self.encrypt(recipients, plaintext)?.unsecure_ref())
214            .map_err(|err| Err::WriteFile(err).into())
215    }
216
217    /// Decrypt ciphertext.
218    fn decrypt(&mut self, ciphertext: Ciphertext) -> Result<Plaintext>;
219
220    /// Decrypt ciphertext from file.
221    fn decrypt_file(&mut self, path: &Path) -> Result<Plaintext> {
222        self.decrypt(fs::read(path).map_err(Err::ReadFile)?.into())
223    }
224
225    /// Check whether we can decrypt ciphertext.
226    fn can_decrypt(&mut self, ciphertext: Ciphertext) -> Result<bool>;
227
228    /// Check whether we can decrypt ciphertext from file.
229    fn can_decrypt_file(&mut self, path: &Path) -> Result<bool> {
230        self.can_decrypt(fs::read(path).map_err(Err::ReadFile)?.into())
231    }
232
233    /// Obtain all public keys from keychain.
234    fn keys_public(&mut self) -> Result<Vec<Key>>;
235
236    /// Obtain all public keys from keychain.
237    fn keys_private(&mut self) -> Result<Vec<Key>>;
238
239    /// Obtain a public key from keychain for fingerprint.
240    fn get_public_key(&mut self, fingerprint: &str) -> Result<Key> {
241        self.keys_public()?
242            .into_iter()
243            .find(|key| util::fingerprints_equal(key.fingerprint(false), fingerprint))
244            .ok_or_else(|| Err::UnknownFingerprint.into())
245    }
246
247    /// Find public keys from keychain for fingerprints.
248    ///
249    /// Skips fingerprints no key is found for.
250    fn find_public_keys(&mut self, fingerprints: &[&str]) -> Result<Vec<Key>> {
251        let keys = self.keys_public()?;
252        Ok(fingerprints
253            .iter()
254            .filter_map(|fingerprint| {
255                keys.iter()
256                    .find(|key| util::fingerprints_equal(key.fingerprint(false), fingerprint))
257                    .cloned()
258            })
259            .collect())
260    }
261
262    /// Import the given key from bytes into keychain.
263    fn import_key(&mut self, key: &[u8]) -> Result<()>;
264
265    /// Import the given key from a file into keychain.
266    fn import_key_file(&mut self, path: &Path) -> Result<()> {
267        self.import_key(&fs::read(path).map_err(Err::ReadFile)?)
268    }
269
270    /// Export the given key from the keychain as bytes.
271    fn export_key(&mut self, key: Key) -> Result<Vec<u8>>;
272
273    /// Export the given key from the keychain to a file.
274    fn export_key_file(&mut self, key: Key, path: &Path) -> Result<()> {
275        fs::write(path, self.export_key(key)?).map_err(|err| Err::WriteFile(err).into())
276    }
277
278    /// Check whether this context supports the given protocol.
279    fn supports_proto(&self, proto: Proto) -> bool;
280}
281
282/// A pool of proto contexts.
283///
284/// Makes using multiple contexts easy, by caching contexts by protocol type and initializing them
285/// on demand.
286pub struct ContextPool {
287    /// All loaded contexts.
288    contexts: HashMap<Proto, Context>,
289}
290
291impl ContextPool {
292    /// Create new empty pool.
293    pub fn empty() -> Self {
294        Self {
295            contexts: HashMap::new(),
296        }
297    }
298
299    /// Get mutable context for given proto.
300    ///
301    /// This will initialize the context if no context is loaded for the given proto yet. This
302    /// may error..
303    pub fn get_mut<'a>(&'a mut self, config: &'a Config) -> Result<&'a mut Context> {
304        Ok(self
305            .contexts
306            .entry(config.proto)
307            .or_insert(context(config)?))
308    }
309}
310
311/// Crypto error.
312#[derive(Debug, Error)]
313pub enum Err {
314    #[error("failed to obtain GPG cryptography context")]
315    Context(#[source] anyhow::Error),
316
317    #[error("failed to built context, protocol not supportd: {:?}", _0)]
318    Unsupported(Proto),
319
320    #[error("failed to write to file")]
321    WriteFile(#[source] std::io::Error),
322
323    #[error("failed to read from file")]
324    ReadFile(#[source] std::io::Error),
325
326    #[error("fingerprint does not match public key in keychain")]
327    UnknownFingerprint,
328}
329
330/// Prelude for common crypto traits.
331pub mod prelude {
332    pub use super::{IsContext, store::StoreRecipients};
333}