arti/subcommands/
keys.rs

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