arti/subcommands/
keys.rs

1//! The `key` subcommand.
2
3use std::str::FromStr;
4
5use anyhow::Result;
6
7use arti_client::{InertTorClient, TorClient, TorClientConfig};
8use clap::{ArgMatches, Args, FromArgMatches, Parser, Subcommand};
9use tor_key_forge::KeystoreItemType;
10use tor_keymgr::{KeyMgr, KeyPath, KeystoreEntryResult, KeystoreId, UnrecognizedEntryError};
11use tor_rtcompat::Runtime;
12
13/// Length of a line, used for formatting
14// TODO: use COLUMNS instead of an arbitrary LINE_LEN
15const LINE_LEN: usize = 80;
16
17/// The `keys` subcommands the arti CLI will be augmented with.
18#[derive(Debug, Parser)]
19pub(crate) enum KeysSubcommands {
20    /// Return the identity key for the specified service.
21    #[command(subcommand)]
22    Keys(KeysSubcommand),
23}
24
25#[derive(Subcommand, Debug, Clone)]
26pub(crate) enum KeysSubcommand {
27    /// List keys and certificates.
28    List(ListArgs),
29
30    /// List keystores.
31    ListKeystores,
32}
33
34/// The arguments of the [`List`](KeysSubcommand::List) subcommand.
35#[derive(Debug, Clone, Args)]
36pub(crate) struct ListArgs {
37    /// Identifier of the keystore.
38    /// If omitted, keys and certificates
39    /// from all the keystores will be returned.
40    #[arg(short, long)]
41    keystore_id: Option<String>,
42}
43
44/// Run the `keys` subcommand.
45pub(crate) fn run<R: Runtime>(
46    runtime: R,
47    keys_matches: &ArgMatches,
48    config: &TorClientConfig,
49) -> Result<()> {
50    let subcommand =
51        KeysSubcommand::from_arg_matches(keys_matches).expect("Could not parse keys subcommand");
52    let client = TorClient::with_runtime(runtime)
53        .config(config.clone())
54        .create_inert()?;
55
56    match subcommand {
57        KeysSubcommand::List(args) => run_list_keys(&args, &client),
58        KeysSubcommand::ListKeystores => run_list_keystores(&client),
59    }
60}
61
62/// Print information about a keystore entry.
63fn display_entry(path: &KeyPath, ty: &KeystoreItemType, keymgr: &KeyMgr) {
64    match keymgr.describe(path) {
65        Ok(entry) => {
66            println!(" Role: {}", entry.role());
67            println!(" Summary: {}", entry.summary());
68            println!(" KeystoreItemType: {:?}", ty);
69            let extra_info = entry.extra_info();
70            println!(" Extra info:");
71            for (key, value) in extra_info {
72                println!(" - {key}: {value}");
73            }
74        }
75        Err(err) => {
76            println!(" {}", err);
77        }
78    }
79    println!("\n {}\n", "-".repeat(LINE_LEN));
80}
81
82/// Print information about an unrecognized keystore entry.
83fn display_unrecognized_entry(entry: &UnrecognizedEntryError) {
84    println!(" Unrecognized entry");
85    #[allow(clippy::single_match)]
86    match entry.entry() {
87        tor_keymgr::UnrecognizedEntryId::Path(p) => {
88            println!(" Location: {}", p.to_string_lossy());
89            println!(" Error: {}", entry.error());
90        }
91        // NOTE: For the time being Arti only supports
92        // on-disk keystores, but more supported medium
93        // will be added.
94        other => {
95            panic!("Unhandled enum variant: {:?}", other);
96        }
97    }
98    println!("\n {}\n", "-".repeat(LINE_LEN));
99}
100
101/// Run the `keys list` subcommand.
102fn run_list_keys(args: &ListArgs, client: &InertTorClient) -> Result<()> {
103    let keymgr = client.keymgr()?;
104    // TODO: in the future we could group entries by their type
105    // (recognized, unrecognized and unrecognized path).
106    // That way we don't need to print "Unrecognized path",
107    // "Unrecognized" entry etc. for each unrecognized entry.
108    match &args.keystore_id {
109        Some(s) => {
110            let id = KeystoreId::from_str(s)?;
111            let entries = keymgr.list_by_id(&id)?.into_iter().map(|res| match res {
112                Ok((path, ty)) => Ok((path, ty, &id)),
113                Err(unrecognized_entry) => Err(unrecognized_entry),
114            });
115            let empty_err_msg = format!("Currently there are no entries in the keystore {}.", s);
116            display_keystore_entries(entries, keymgr, &empty_err_msg);
117        }
118        None => {
119            let entries = keymgr.list()?.into_iter().map(|res| match res {
120                Ok(entry) => Ok((
121                    entry.key_path().to_owned(),
122                    entry.key_type().to_owned(),
123                    entry.keystore_id(),
124                )),
125                Err(unrecognized_entry) => Err(unrecognized_entry),
126            });
127            display_keystore_entries(
128                entries,
129                keymgr,
130                "Currently there are no entries in any of the keystores.",
131            );
132        }
133    }
134    Ok(())
135}
136
137/// Run `key list-keystores` subcommand.
138fn run_list_keystores(client: &InertTorClient) -> Result<()> {
139    let keymgr = client.keymgr()?;
140    let entries = keymgr.list_keystores();
141
142    if entries.is_empty() {
143        println!("Currently there are no keystores available.");
144    } else {
145        println!(" Keystores:\n");
146        for entry in entries {
147            // TODO: We need something similar to [`KeyPathInfo`](tor_keymgr::KeyPathInfo)
148            // for `KeystoreId`
149            println!(" - {:?}\n", entry.as_ref());
150        }
151    }
152
153    Ok(())
154}
155
156/// Helper function of `run_list_keys`, reduces cognitive complexity.
157fn display_keystore_entries<'a>(
158    entries: impl std::iter::ExactSizeIterator<
159        Item = KeystoreEntryResult<(KeyPath, KeystoreItemType, &'a KeystoreId)>,
160    >,
161    keymgr: &KeyMgr,
162    empty_err_msg: &str,
163) {
164    if entries.len() == 0 {
165        println!("{}", empty_err_msg);
166        return;
167    }
168    println!(" ===== Keytore entries =====\n\n");
169    for entry in entries {
170        match entry {
171            Ok((path, ty, keystore_id)) => {
172                println!(" Keystore ID: {}", keystore_id.as_ref());
173                display_entry(&path, &ty, keymgr);
174            }
175            Err(entry) => {
176                display_unrecognized_entry(&entry);
177            }
178        }
179    }
180}