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#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
10pub enum KeyType {
11 Public,
13 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#[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 #[clap(aliases = ["min", "1"])]
59 #[default]
60 Minimum = 0,
61 #[clap(alias = "2")]
63 Standard = 1,
64 #[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 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#[derive(Clone, Debug)]
88pub struct GpgKey {
89 inner: Key,
91 pub detail: KeyDetail,
93}
94
95impl GpgKey {
96 pub fn new(key: Key, detail: KeyDetail) -> Self {
98 Self { inner: key, detail }
99 }
100
101 pub fn get_id(&self) -> String {
103 self.inner
104 .id()
105 .map_or(String::from("[?]"), |v| format!("0x{v}"))
106 }
107
108 pub fn get_fingerprint(&self) -> String {
110 self.inner
111 .fingerprint()
112 .map_or(String::from("[?]"), |v| v.to_string())
113 }
114
115 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 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 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 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 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}