Skip to main content

gpg_tui/gpg/
key.rs

1use crate::gpg::handler;
2use clap::ValueEnum;
3use gpgme::{Key, SignatureNotation, Subkey, UserId, UserIdSignature};
4use serde::{Deserialize, Serialize};
5use std::fmt::{Display, Formatter, Result as FmtResult};
6use std::str::FromStr;
7
8/// Type of the key.
9#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
10pub enum KeyType {
11	/// Public key.
12	Public,
13	/// Secret (private) key.
14	Secret,
15}
16
17impl Display for KeyType {
18	fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
19		write!(
20			f,
21			"{}",
22			match self {
23				Self::Public => "pub",
24				Self::Secret => "sec",
25			}
26		)
27	}
28}
29
30impl FromStr for KeyType {
31	type Err = ();
32	fn from_str(s: &str) -> Result<Self, Self::Err> {
33		for key_type in &[Self::Public, Self::Secret] {
34			if key_type.to_string().matches(&s).count() >= 1 {
35				return Ok(*key_type);
36			}
37		}
38		Err(())
39	}
40}
41
42/// Level of detail to show for key.
43#[derive(
44	Clone,
45	Copy,
46	Debug,
47	Default,
48	PartialEq,
49	Eq,
50	Serialize,
51	Deserialize,
52	ValueEnum,
53)]
54#[serde(rename_all = "snake_case")]
55#[clap(rename_all = "snake_case")]
56pub enum KeyDetail {
57	/// Show only the primary key and user ID.
58	#[clap(aliases = ["min", "1"])]
59	#[default]
60	Minimum = 0,
61	/// Show all subkeys and user IDs.
62	#[clap(alias = "2")]
63	Standard = 1,
64	/// Show signatures.
65	#[clap(alias = "3")]
66	Full = 2,
67}
68
69impl Display for KeyDetail {
70	fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
71		write!(f, "{}", format!("{self:?}").to_lowercase())
72	}
73}
74
75impl KeyDetail {
76	/// Increases the level of detail.
77	pub fn increase(&mut self) {
78		*self = match *self as i8 + 1 {
79			1 => KeyDetail::Standard,
80			2 => KeyDetail::Full,
81			_ => KeyDetail::Minimum,
82		}
83	}
84}
85
86/// Representation of a key.
87#[derive(Clone, Debug)]
88pub struct GpgKey {
89	/// GPGME Key type.
90	inner: Key,
91	/// Level of detail to show about key information.
92	pub detail: KeyDetail,
93}
94
95impl GpgKey {
96	/// Constructs a new instance of `GpgKey`.
97	pub fn new(key: Key, detail: KeyDetail) -> Self {
98		Self { inner: key, detail }
99	}
100
101	/// Returns the key ID with '0x' prefix.
102	pub fn get_id(&self) -> String {
103		self.inner
104			.id()
105			.map_or(String::from("[?]"), |v| format!("0x{v}"))
106	}
107
108	/// Returns the key fingerprint.
109	pub fn get_fingerprint(&self) -> String {
110		self.inner
111			.fingerprint()
112			.map_or(String::from("[?]"), |v| v.to_string())
113	}
114
115	/// Returns the primary user of the key.
116	pub fn get_user_id(&self) -> String {
117		match self.inner.user_ids().next() {
118			Some(user) => {
119				user.id().map_or(String::from("[?]"), |v| v.to_string())
120			}
121			None => String::from("[?]"),
122		}
123	}
124
125	/// Returns information about the subkeys.
126	pub fn get_subkey_info(
127		&self,
128		default_key: Option<&str>,
129		truncate: bool,
130	) -> Vec<String> {
131		let mut key_info = Vec::new();
132		let subkeys = self.inner.subkeys().collect::<Vec<Subkey>>();
133		for (i, subkey) in subkeys.iter().enumerate() {
134			key_info.push(format!(
135				"[{}]{}{}/{}",
136				handler::get_subkey_flags(*subkey),
137				if default_key.map(|v| v.trim_start_matches("0x"))
138					== subkey.id().ok()
139				{
140					"*"
141				} else {
142					" "
143				},
144				if let Ok(algorithm_name) = subkey.algorithm_name() {
145					if algorithm_name.is_empty() {
146						String::from("unrecog")
147					} else {
148						format!("{:-<7}", algorithm_name)
149							.chars()
150							.take(7)
151							.collect()
152					}
153				} else {
154					String::from("unknown")
155				},
156				if truncate {
157					subkey.id()
158				} else {
159					subkey.fingerprint()
160				}
161				.unwrap_or("[?]"),
162			));
163			if self.detail == KeyDetail::Minimum {
164				break;
165			}
166			key_info.push(format!(
167				"{}      └─{}",
168				if i != subkeys.len() - 1 { "|" } else { " " },
169				handler::get_subkey_time(
170					*subkey,
171					if truncate { "%Y" } else { "%F" }
172				)
173			));
174		}
175		key_info
176	}
177
178	/// Returns information about the users of the key.
179	pub fn get_user_info(&self, truncate: bool) -> Vec<String> {
180		let mut user_info = Vec::new();
181		let user_ids = self.inner.user_ids().collect::<Vec<UserId>>();
182		for (i, user) in user_ids.iter().enumerate() {
183			user_info.push(format!(
184				"{}[{}] {}",
185				if i == 0 {
186					""
187				} else if i == user_ids.len() - 1 {
188					" └─"
189				} else {
190					" ├─"
191				},
192				user.validity(),
193				if truncate { user.email() } else { user.id() }
194					.unwrap_or("[?]")
195			));
196			if self.detail == KeyDetail::Minimum {
197				break;
198			}
199			if self.detail == KeyDetail::Full {
200				user_info.extend(self.get_user_signatures(
201					user,
202					user_ids.len(),
203					i,
204					truncate,
205				));
206			}
207		}
208		user_info
209	}
210
211	/// Returns the signature information of an user.
212	fn get_user_signatures(
213		&self,
214		user: &UserId,
215		user_count: usize,
216		user_index: usize,
217		truncate: bool,
218	) -> Vec<String> {
219		let mut user_signatures = Vec::new();
220		let signatures = user.signatures().collect::<Vec<UserIdSignature>>();
221		for (i, sig) in signatures.iter().enumerate() {
222			let notations = sig.notations().collect::<Vec<SignatureNotation>>();
223			let padding = if user_count == 1 {
224				" "
225			} else if user_index == user_count - 1 {
226				"    "
227			} else if user_index == 0 {
228				"│"
229			} else {
230				"│   "
231			};
232			user_signatures.push(format!(
233				" {}  {}[{:x}] {} {}",
234				padding,
235				if i == signatures.len() - 1 {
236					"└─"
237				} else {
238					"├─"
239				},
240				sig.cert_class(),
241				if sig.signer_key_id() == self.inner.id() {
242					String::from("selfsig")
243				} else if truncate {
244					sig.signer_key_id().unwrap_or("[?]").to_string()
245				} else {
246					let user_id = sig.signer_user_id().unwrap_or("[-]");
247					format!(
248						"{} {}",
249						sig.signer_key_id().unwrap_or("[?]"),
250						if user_id.is_empty() { "[?]" } else { user_id }
251					)
252				},
253				handler::get_signature_time(
254					*sig,
255					if truncate { "%Y" } else { "%F" }
256				)
257			));
258			if !notations.is_empty() {
259				user_signatures.extend(self.get_signature_notations(
260					notations,
261					format!(" {padding}  "),
262					signatures.len(),
263					i,
264				));
265			}
266		}
267		user_signatures
268	}
269
270	/// Returns the notations of the given signature.
271	fn get_signature_notations(
272		&self,
273		notations: Vec<SignatureNotation>,
274		padding: String,
275		sig_count: usize,
276		sig_index: usize,
277	) -> Vec<String> {
278		notations
279			.iter()
280			.enumerate()
281			.map(|(i, notation)| {
282				format!(
283					"{}{}  {}[{}] {}={}",
284					padding,
285					if sig_index == sig_count - 1 {
286						" "
287					} else {
288						"│"
289					},
290					if i == notations.len() - 1 {
291						"└─"
292					} else {
293						"├─"
294					},
295					if notation.is_critical() {
296						"!"
297					} else if notation.is_human_readable() {
298						"h"
299					} else {
300						"?"
301					},
302					notation.name().unwrap_or("?"),
303					notation.value().unwrap_or("?"),
304				)
305			})
306			.collect()
307	}
308}
309
310#[cfg(feature = "gpg-tests")]
311#[cfg(test)]
312mod tests {
313	use super::*;
314	use crate::args::Args;
315	use crate::gpg::config::GpgConfig;
316	use crate::gpg::context::GpgContext;
317	use anyhow::Result;
318	use pretty_assertions::assert_eq;
319	#[test]
320	fn test_gpg_key() -> Result<()> {
321		let args = Args::default();
322		let config = GpgConfig::new(&args)?;
323		let mut context = GpgContext::new(config)?;
324		let mut keys =
325			context.get_keys(KeyType::Public, None, KeyDetail::default())?;
326		let key = &mut keys[0];
327		key.detail.increase();
328		assert_eq!(KeyDetail::Standard, key.detail);
329		assert_eq!(Ok(key.detail), KeyDetail::from_str("standard", true));
330		key.detail.increase();
331		assert_eq!(KeyDetail::Full, key.detail);
332		assert_eq!("full", key.detail.to_string());
333		assert!(key
334			.get_subkey_info(Some(""), true)
335			.join("\n")
336			.contains(&key.get_id().replace("0x", "")));
337		assert!(key
338			.get_subkey_info(Some(""), false)
339			.join("\n")
340			.contains(&key.get_fingerprint()));
341		assert!(key
342			.get_user_info(false)
343			.join("\n")
344			.contains(&key.get_user_id()));
345		Ok(())
346	}
347}