1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
//! # PGP GPG module
//!
//! This module contains the PGP backend based on GPG.

use gpgme::{Context, Protocol};
use log::{debug, trace, warn};
use std::path::PathBuf;
use thiserror::Error;

use crate::Result;

/// Errors dedicated to GPG.
#[derive(Debug, Error)]
pub enum Error {
    #[error("cannot get gpg context")]
    GetContextError(#[source] gpgme::Error),
    #[error("cannot get gpg home dir path from {0}")]
    GetHomeDirPathError(PathBuf),
    #[error("cannot set gpg home dir at {1}")]
    SetHomeDirError(#[source] gpgme::Error, PathBuf),
    #[error("cannot encrypt data using gpg")]
    EncryptError(#[source] gpgme::Error),
    #[error("cannot decrypt data using gpg")]
    DecryptError(#[source] gpgme::Error),
    #[error("cannot sign data using gpg")]
    SignError(#[source] gpgme::Error),
    #[error("cannot verify data using gpg")]
    VerifyError(#[source] gpgme::Error),
}

/// The GPG PGP backend.
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct Gpg {
    /// The GPG home directory.
    ///
    /// Defaults to GPG default home directory (~/.gpg).
    pub home_dir: Option<PathBuf>,
}

impl Gpg {
    pub fn get_context(&self) -> Result<Context> {
        let mut ctx = Context::from_protocol(Protocol::OpenPgp).map_err(Error::GetContextError)?;

        if let Some(path) = &self.home_dir {
            let home_dir = path
                .as_path()
                .to_str()
                .ok_or_else(|| Error::GetHomeDirPathError(path.clone()))?;

            ctx.set_engine_home_dir(home_dir)
                .map_err(|err| Error::SetHomeDirError(err, path.clone()))?;
        }

        Ok(ctx)
    }

    /// Encrypts the given plain bytes using the given recipients.
    pub async fn encrypt(
        &self,
        emails: impl IntoIterator<Item = String>,
        plain_bytes: Vec<u8>,
    ) -> Result<Vec<u8>> {
        let mut ctx = self.get_context()?;

        // TODO: make it really async
        let mut keys = Vec::new();
        for ref email in emails {
            match ctx.locate_key(email) {
                Ok(key) => {
                    debug!("found public key for {email} for encryption");
                    trace!("{key:#?}");
                    keys.push(key);
                }
                Err(err) => {
                    warn!("cannot locate gpg key for {email}: {err}");
                    debug!("cannot locate gpg key for {email}: {err}");
                }
            }
        }

        let mut encrypted_bytes = Vec::new();
        let res = ctx
            .encrypt(keys.iter(), plain_bytes, &mut encrypted_bytes)
            .map_err(Error::EncryptError)?;
        trace!("encrypt result: {res:#?}");

        let recipients_count = res.invalid_recipients().count();
        if recipients_count > 0 {
            warn!("skipping {recipients_count} recipients from gpg encryption");
            debug!("invalid recipients: {:#?}", res.invalid_recipients());
        }

        Ok(encrypted_bytes)
    }

    /// Decrypts the given encrypted bytes.
    pub async fn decrypt(&self, mut encrypted_bytes: Vec<u8>) -> Result<Vec<u8>> {
        let mut ctx = self.get_context()?;

        let mut plain_bytes = Vec::new();
        let res = ctx
            .decrypt(&mut encrypted_bytes, &mut plain_bytes)
            .map_err(Error::DecryptError)?;
        trace!("decrypt result: {res:#?}");

        Ok(plain_bytes)
    }

    /// Signs the given plain bytes.
    pub async fn sign(&self, mut plain_bytes: Vec<u8>) -> Result<Vec<u8>> {
        let mut ctx = self.get_context()?;

        let mut signed_bytes = Vec::new();
        let res = ctx
            .sign_clear(&mut plain_bytes, &mut signed_bytes)
            .map_err(Error::SignError)?;
        trace!("sign result: {res:#?}");

        Ok(signed_bytes)
    }

    /// Verifies the given signed bytes as well as the signature_bytes.
    pub async fn verify(&self, signature_bytes: Vec<u8>, signed_bytes: Vec<u8>) -> Result<()> {
        let mut ctx = self.get_context()?;

        let res = ctx
            .verify_opaque(signature_bytes, signed_bytes)
            .map_err(Error::SignError)?;
        trace!("verify result: {res:#?}");

        Ok(())
    }
}