email/account/config/pgp/
native.rs1use 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#[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 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 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}