email/account/config/pgp/
native.rs

1use std::io;
2
3use keyring::KeyringEntry;
4use mml::pgp::{NativePgpPublicKeysResolver, NativePgpSecretKey, Pgp, PgpNative};
5use secret::Secret;
6use shellexpand_utils::shellexpand_path;
7use tokio::fs;
8use tracing::debug;
9
10#[doc(inline)]
11pub use super::{Error, Result};
12
13/// The native PGP configuration.
14///
15/// This configuration is based on the [`pgp`] crate, which provides a
16/// native Rust implementation of the PGP standard.
17#[derive(Clone, Debug, Eq, PartialEq)]
18#[cfg_attr(
19    feature = "derive",
20    derive(serde::Serialize, serde::Deserialize),
21    serde(rename_all = "kebab-case")
22)]
23pub struct PgpNativeConfig {
24    pub secret_key: NativePgpSecretKey,
25    pub secret_key_passphrase: Secret,
26    pub wkd: bool,
27    pub key_servers: Vec<String>,
28}
29
30impl PgpNativeConfig {
31    pub fn default_wkd() -> bool {
32        true
33    }
34
35    pub fn default_key_servers() -> Vec<String> {
36        vec![
37            String::from("hkps://keys.openpgp.org"),
38            String::from("hkps://keys.mailvelope.com"),
39        ]
40    }
41
42    /// Deletes secret and public keys.
43    pub async fn reset(&self) -> Result<()> {
44        match &self.secret_key {
45            NativePgpSecretKey::None => (),
46            NativePgpSecretKey::Raw(..) => (),
47            NativePgpSecretKey::Path(path) => {
48                let path = shellexpand_path(path);
49                if path.is_file() {
50                    fs::remove_file(&path)
51                        .await
52                        .map_err(|err| Error::DeletePgpKeyAtPathError(err, path.clone()))?;
53                } else {
54                    debug!("cannot delete pgp key file at {path:?}: file not found");
55                }
56            }
57            #[cfg(feature = "keyring")]
58            NativePgpSecretKey::Keyring(entry) => entry
59                .delete_secret()
60                .await
61                .map_err(Error::DeletePgpKeyFromKeyringError)?,
62        };
63
64        Ok(())
65    }
66
67    /// Generates secret and public keys then stores them.
68    pub async fn configure(
69        &self,
70        email: impl ToString,
71        passwd: impl Fn() -> io::Result<String>,
72    ) -> Result<()> {
73        let email = email.to_string();
74        let passwd = passwd().map_err(Error::GetPgpSecretKeyPasswdError)?;
75
76        let (skey, pkey) = pgp::gen_key_pair(email.clone(), passwd)
77            .await
78            .map_err(|err| Error::GeneratePgpKeyPairError(err, email.clone()))?;
79        let skey = skey
80            .to_armored_string(None)
81            .map_err(Error::ExportSecretKeyToArmoredStringError)?;
82        let pkey = pkey
83            .to_armored_string(None)
84            .map_err(Error::ExportPublicKeyToArmoredStringError)?;
85
86        match &self.secret_key {
87            NativePgpSecretKey::None => (),
88            NativePgpSecretKey::Raw(_) => (),
89            NativePgpSecretKey::Path(skey_path) => {
90                let skey_path = shellexpand_path(skey_path);
91                fs::write(&skey_path, skey)
92                    .await
93                    .map_err(|err| Error::WriteSecretKeyFileError(err, skey_path.clone()))?;
94
95                let pkey_path = skey_path.with_extension("pub");
96                fs::write(&pkey_path, pkey)
97                    .await
98                    .map_err(|err| Error::WritePublicKeyFileError(err, pkey_path))?;
99            }
100            NativePgpSecretKey::Keyring(skey_entry) => {
101                let pkey_entry = KeyringEntry::try_new(skey_entry.key.clone() + "-pub")
102                    .map_err(Error::GetPublicKeyFromKeyringError)?;
103
104                skey_entry
105                    .set_secret(skey)
106                    .await
107                    .map_err(Error::SetSecretKeyToKeyringError)?;
108                pkey_entry
109                    .set_secret(pkey)
110                    .await
111                    .map_err(Error::SetPublicKeyToKeyringError)?;
112            }
113        }
114
115        Ok(())
116    }
117}
118
119impl Default for PgpNativeConfig {
120    fn default() -> Self {
121        Self {
122            secret_key: Default::default(),
123            secret_key_passphrase: Default::default(),
124            wkd: Self::default_wkd(),
125            key_servers: Self::default_key_servers(),
126        }
127    }
128}
129
130impl From<PgpNativeConfig> for Pgp {
131    fn from(config: PgpNativeConfig) -> Self {
132        let public_keys_resolvers = {
133            let mut resolvers = vec![];
134
135            if config.wkd {
136                resolvers.push(NativePgpPublicKeysResolver::Wkd)
137            }
138
139            resolvers.push(NativePgpPublicKeysResolver::KeyServers(config.key_servers));
140
141            resolvers
142        };
143
144        Pgp::Native(PgpNative {
145            secret_key: config.secret_key,
146            secret_key_passphrase: config.secret_key_passphrase,
147            public_keys_resolvers,
148        })
149    }
150}