1use crate::Session;
7use crate::accounts::prompt_password;
8use clap::Subcommand;
9use sioul_core::pgp;
10use sioul_core::securitykey::{self, Purpose, Touch};
11use sioul_sync::{keys, secret};
12use std::path::PathBuf;
13
14#[derive(Subcommand)]
15pub(crate) enum PgpCommand {
16 List,
18 Make { address: String },
22 Import { file: PathBuf },
24 Export { fingerprint: String },
26 Lookup { address: String },
28 Cards,
31}
32
33pub(crate) fn run(s: &Session, command: PgpCommand) -> Result<(), String> {
34 match command {
35 PgpCommand::List => {
36 for key in pgp::summaries() {
37 let mine = if key.own { "*" } else { " " };
38 println!("{mine} {} · {} · {}{}", key.fingerprint, crate::one_line(&key.names.join(", ")), key.created, if key.expires.is_empty() { String::new() } else { format!(" → {}", key.expires) });
39 }
40 Ok(())
41 }
42 PgpCommand::Make { address } => {
43 let name = s.config.accounts.iter().find(|a| a.address.as_deref() == Some(address.as_str())).and_then(|a| a.name.clone()).unwrap_or_default();
44 let passphrase = match secret::test_passphrase() {
45 Some(fixed) => fixed,
46 None => pgp::new_passphrase()?,
47 };
48 let made = pgp::generate(&name, &address, &passphrase)?;
49 secret::save_pgp_passphrase(&made.fingerprint, &passphrase).map_err(|e| e.sentence(&s.tr, &address))?;
50 println!("{}", made.fingerprint);
51 println!("{}", pgp::revocation_path(&made.fingerprint).display());
52 Ok(())
53 }
54 PgpCommand::Import { file } => {
55 let bytes = std::fs::read(&file).map_err(|e| format!("{}: {e}", file.display()))?;
56 let had: Vec<String> = pgp::summaries().into_iter().filter(|k| k.own).map(|k| k.fingerprint).collect();
58 for key in pgp::import(&bytes)? {
59 if key.own {
60 let passphrase = prompt_password(s, &crate::one_line(&key.names.join(", ")))?;
61 if !pgp::opens(&key.fingerprint, &passphrase) {
62 if !had.contains(&key.fingerprint) {
63 let _ = pgp::remove(&key.fingerprint);
64 }
65 return Err(s.tr.text("pgp-wrong-passphrase", None));
66 }
67 secret::save_pgp_passphrase(&key.fingerprint, &passphrase).map_err(|e| e.sentence(&s.tr, &key.fingerprint))?;
68 }
69 println!("{} · {}", key.fingerprint, crate::one_line(&key.names.join(", ")));
70 }
71 Ok(())
72 }
73 PgpCommand::Export { fingerprint } => {
74 print!("{}", pgp::export_public(&fingerprint).ok_or_else(|| s.tr.text("pgp-no-key", None))?);
75 Ok(())
76 }
77 PgpCommand::Cards => {
78 let cards = securitykey::cards(sioul_sync::securitykey::readers()).map_err(|e| e.sentence(&s.tr, Purpose::Read))?;
79 let known = securitykey::known_cards();
80 let touch = |touch: Touch| {
81 let policy = match touch {
82 Touch::Off => "off",
83 Touch::On => "on",
84 Touch::Fixed => "fixed",
85 Touch::Cached => "cached",
86 Touch::CachedFixed => "cached-fixed",
87 Touch::Unknown => "unknown",
88 };
89 s.say("seckey-cli-touch", &[("policy", policy.to_string())])
90 };
91 for card in cards {
92 let mine = if known.iter().any(|k| k.ident.eq_ignore_ascii_case(&card.ident)) { "*" } else { " " };
94 println!("{mine} {} · {} · {}", card.ident, card.label(&s.tr), crate::one_line(&card.holder));
95 for (id, slot) in [("seckey-cli-signs", &card.sign), ("seckey-cli-decrypts", &card.decrypt)] {
96 if let Some(slot) = slot {
97 let date = securitykey::full_date(&s.tr, i64::from(slot.created));
98 println!(" {}", s.say(id, &[("fingerprint", slot.fingerprint.clone()), ("algorithm", slot.algorithm.clone()), ("date", date), ("touch", touch(slot.touch))]));
99 }
100 }
101 println!(" {}", s.say("seckey-tries-left", &[("n", card.pin_tries.to_string())]));
102 if card.pin_each_signature {
103 println!(" {}", s.tr.text("seckey-pin-each", None));
104 }
105 println!(" {}", s.say("seckey-cli-codes", &[("reset", card.reset_tries.to_string()), ("admin", card.admin_tries.to_string())]));
106 if !card.url.is_empty() {
107 println!(" {}", crate::one_line(&card.url));
108 }
109 println!(" {}", crate::one_line(&card.reader));
110 }
111 Ok(())
112 }
113 PgpCommand::Lookup { address } => {
114 let found = keys::lookup(&address).map_err(|e| e.sentence(&s.tr, &address))?;
115 if found.is_empty() {
116 println!("{}", s.say("pgp-lookup-some", &[("missing", address)]));
117 }
118 for key in found {
119 println!("{} · {}", key.fingerprint, crate::one_line(&key.names.join(", ")));
120 }
121 Ok(())
122 }
123 }
124}