1pub 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#[non_exhaustive]
29#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
30pub enum Proto {
31 Gpg,
33}
34
35impl Proto {
36 pub fn name(&self) -> &str {
38 match self {
39 Self::Gpg => "GPG",
40 }
41 }
42}
43
44pub struct Config {
48 pub proto: Proto,
50
51 pub gpg_tty: bool,
53
54 pub verbose: bool,
56}
57
58impl Config {
59 pub fn from(proto: Proto) -> Self {
61 Self {
62 proto,
63 gpg_tty: false,
64 verbose: false,
65 }
66 }
67}
68
69#[derive(Clone, PartialEq)]
73#[non_exhaustive]
74pub enum Key {
75 #[cfg(feature = "_crypto-gpg")]
77 Gpg(proto::gpg::Key),
78}
79
80impl Key {
81 pub fn proto(&self) -> Proto {
83 match self {
84 #[cfg(feature = "_crypto-gpg")]
85 Key::Gpg(_) => Proto::Gpg,
86 }
87 }
88
89 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 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
118pub fn context(config: &Config) -> Result<Context, Err> {
127 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
152pub struct Context {
154 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
198pub trait IsContext {
203 fn encrypt(&mut self, recipients: &Recipients, plaintext: Plaintext) -> Result<Ciphertext>;
205
206 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 fn decrypt(&mut self, ciphertext: Ciphertext) -> Result<Plaintext>;
219
220 fn decrypt_file(&mut self, path: &Path) -> Result<Plaintext> {
222 self.decrypt(fs::read(path).map_err(Err::ReadFile)?.into())
223 }
224
225 fn can_decrypt(&mut self, ciphertext: Ciphertext) -> Result<bool>;
227
228 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 fn keys_public(&mut self) -> Result<Vec<Key>>;
235
236 fn keys_private(&mut self) -> Result<Vec<Key>>;
238
239 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 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 fn import_key(&mut self, key: &[u8]) -> Result<()>;
264
265 fn import_key_file(&mut self, path: &Path) -> Result<()> {
267 self.import_key(&fs::read(path).map_err(Err::ReadFile)?)
268 }
269
270 fn export_key(&mut self, key: Key) -> Result<Vec<u8>>;
272
273 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 fn supports_proto(&self, proto: Proto) -> bool;
280}
281
282pub struct ContextPool {
287 contexts: HashMap<Proto, Context>,
289}
290
291impl ContextPool {
292 pub fn empty() -> Self {
294 Self {
295 contexts: HashMap::new(),
296 }
297 }
298
299 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#[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
330pub mod prelude {
332 pub use super::{IsContext, store::StoreRecipients};
333}