zoi/cmd/
pgp.rs

1use crate::pkg;
2use anyhow::{Result, anyhow};
3use clap::{ArgGroup, Parser, Subcommand};
4use std::path::Path;
5
6#[derive(Parser, Debug)]
7#[command(long_about = "Manages PGP keys for package signature verification.")]
8pub struct PgpCommand {
9    #[command(subcommand)]
10    pub command: PgpCommands,
11}
12
13#[derive(Subcommand, Debug)]
14pub enum PgpCommands {
15    /// Add a PGP key from a file, URL, or a keyserver
16    Add(AddKey),
17    /// Remove a PGP key
18    #[command(alias = "rm")]
19    Remove(RemoveKey),
20    /// List all imported PGP keys
21    #[command(alias = "ls")]
22    List,
23    /// Search for a PGP key by user ID or fingerprint
24    Search(SearchKey),
25    /// Show the public key of a stored PGP key
26    Show(ShowKey),
27    /// Verify a file's detached signature
28    Verify(VerifySig),
29}
30
31#[derive(Parser, Debug)]
32#[command(group(
33    ArgGroup::new("source")
34        .required(true)
35        .args(["path", "fingerprint", "url"]),
36))]
37pub struct AddKey {
38    /// Path to the PGP key file (.asc)
39    #[arg(long)]
40    pub path: Option<String>,
41
42    /// Fingerprint of the PGP key to fetch from keys.openpgp.org
43    #[arg(long)]
44    pub fingerprint: Option<String>,
45
46    /// URL of the PGP key to import
47    #[arg(long)]
48    pub url: Option<String>,
49
50    /// Name to associate with the key (defaults to filename if adding from path/url)
51    #[arg(long)]
52    pub name: Option<String>,
53}
54
55#[derive(Parser, Debug)]
56#[command(group(
57    ArgGroup::new("key_id")
58        .required(true)
59        .args(["name", "fingerprint"]),
60))]
61pub struct RemoveKey {
62    /// Name of the key to remove
63    pub name: Option<String>,
64
65    /// Fingerprint of the key to remove
66    #[arg(long)]
67    pub fingerprint: Option<String>,
68}
69
70#[derive(Parser, Debug)]
71pub struct SearchKey {
72    /// The user ID (name, email) or fingerprint to search for
73    #[arg(required = true)]
74    pub term: String,
75}
76
77#[derive(Parser, Debug)]
78pub struct ShowKey {
79    /// The name of the key to show
80    #[arg(required = true)]
81    pub name: String,
82}
83
84#[derive(Parser, Debug)]
85pub struct VerifySig {
86    /// Path to the file to verify
87    #[arg(long)]
88    pub file: String,
89
90    /// Path to the detached signature file
91    #[arg(long)]
92    pub sig: String,
93
94    /// Name of the key in the local store to use for verification
95    #[arg(long)]
96    pub key: String,
97}
98
99pub fn run(args: PgpCommand) -> Result<()> {
100    match args.command {
101        PgpCommands::Add(add_args) => {
102            if let Some(path) = add_args.path {
103                pkg::pgp::add_key_from_path(&path, add_args.name.as_deref())?;
104            } else if let Some(fingerprint) = add_args.fingerprint {
105                if let Some(name) = add_args.name {
106                    pkg::pgp::add_key_from_fingerprint(&fingerprint, &name)?;
107                } else {
108                    return Err(anyhow!(
109                        "A name must be provided when adding a key by fingerprint."
110                    ));
111                }
112            } else if let Some(url) = add_args.url {
113                let name = if let Some(n) = add_args.name {
114                    n
115                } else {
116                    Path::new(&url)
117                        .file_stem()
118                        .and_then(|s| s.to_str())
119                        .ok_or(anyhow!("Could not derive name from URL"))?
120                        .to_string()
121                };
122                pkg::pgp::add_key_from_url(&url, &name)?;
123            }
124        }
125        PgpCommands::Remove(remove_args) => {
126            if let Some(name) = remove_args.name {
127                pkg::pgp::remove_key_by_name(&name)?;
128            } else if let Some(fingerprint) = remove_args.fingerprint {
129                pkg::pgp::remove_key_by_fingerprint(&fingerprint)?;
130            }
131        }
132        PgpCommands::List => {
133            pkg::pgp::list_keys()?;
134        }
135        PgpCommands::Search(search_args) => {
136            pkg::pgp::search_keys(&search_args.term)?;
137        }
138        PgpCommands::Show(show_args) => {
139            pkg::pgp::show_key(&show_args.name)?;
140        }
141        PgpCommands::Verify(verify_args) => {
142            pkg::pgp::cli_verify_signature(&verify_args.file, &verify_args.sig, &verify_args.key)?;
143        }
144    }
145    Ok(())
146}