warg_cli/commands/
key.rs

1use anyhow::{Context, Result};
2use clap::{Args, Subcommand};
3use dialoguer::{theme::ColorfulTheme, Confirm, Password};
4use p256::ecdsa::SigningKey;
5use rand_core::OsRng;
6use warg_client::keyring::Keyring;
7use warg_client::Config;
8use warg_crypto::signing::PrivateKey;
9
10use super::CommonOptions;
11
12/// Manage signing keys for interacting with a registry.
13#[derive(Args)]
14pub struct KeyCommand {
15    /// The subcommand to execute.
16    #[clap(subcommand)]
17    pub command: KeySubcommand,
18}
19
20impl KeyCommand {
21    /// Executes the command.
22    pub async fn exec(self) -> Result<()> {
23        match self.command {
24            KeySubcommand::New(cmd) => cmd.exec().await,
25            KeySubcommand::Info(cmd) => cmd.exec().await,
26            KeySubcommand::Set(cmd) => cmd.exec().await,
27            KeySubcommand::Delete(cmd) => cmd.exec().await,
28        }
29    }
30}
31
32/// The subcommand to execute.
33#[derive(Subcommand)]
34pub enum KeySubcommand {
35    /// Creates a new signing key for a registry in the local keyring.
36    New(KeyNewCommand),
37    /// Shows information about the signing key for a registry in the local keyring.
38    Info(KeyInfoCommand),
39    /// Sets the signing key for a registry in the local keyring.
40    Set(KeySetCommand),
41    /// Deletes the signing key for a registry from the local keyring.
42    Delete(KeyDeleteCommand),
43}
44
45/// Creates a new signing key for a registry in the local keyring.
46#[derive(Args)]
47pub struct KeyNewCommand {
48    /// The common command options.
49    #[clap(flatten)]
50    pub common: CommonOptions,
51}
52
53impl KeyNewCommand {
54    /// Executes the command.
55    pub async fn exec(self) -> Result<()> {
56        let config = &mut self.common.read_config()?;
57        let key = SigningKey::random(&mut OsRng).into();
58        if let Some(ref reg) = self.common.registry {
59            config.keys.insert(reg.to_string());
60        } else {
61            config.keys.insert("default".to_string());
62        }
63        Keyring::from_config(config)?.set_signing_key(
64            self.common.registry.as_deref(),
65            &key,
66            &mut config.keys,
67            config.home_url.as_deref(),
68        )?;
69        config.write_to_file(&Config::default_config_path()?)?;
70        let public_key = key.public_key();
71        println!("Key ID: {}", public_key.fingerprint());
72        println!("Public Key: {public_key}");
73        Ok(())
74    }
75}
76
77/// Shows information about the signing key for a registry in the local keyring.
78#[derive(Args)]
79pub struct KeyInfoCommand {
80    /// The common command options.
81    #[clap(flatten)]
82    pub common: CommonOptions,
83}
84
85impl KeyInfoCommand {
86    /// Executes the command.
87    pub async fn exec(self) -> Result<()> {
88        let config = &self.common.read_config()?;
89        let private_key = Keyring::from_config(config)?.get_signing_key(
90            self.common.registry.as_deref(),
91            &config.keys,
92            config.home_url.as_deref(),
93        )?;
94        let public_key = private_key.public_key();
95        println!("Key ID: {}", public_key.fingerprint());
96        println!("Public Key: {public_key}");
97        Ok(())
98    }
99}
100
101/// Sets the signing key for a registry in the local keyring.
102#[derive(Args)]
103pub struct KeySetCommand {
104    /// The common command options.
105    #[clap(flatten)]
106    pub common: CommonOptions,
107}
108
109impl KeySetCommand {
110    /// Executes the command.
111    pub async fn exec(self) -> Result<()> {
112        let key_str = Password::with_theme(&ColorfulTheme::default())
113            .with_prompt("input signing key (expected format is `<alg>:<base64>`): ")
114            .interact()
115            .context("failed to read signing key")?;
116        let key =
117            PrivateKey::decode(key_str).context("signing key is not in the correct format")?;
118        let config = &mut self.common.read_config()?;
119
120        Keyring::from_config(config)?.set_signing_key(
121            self.common.registry.as_deref(),
122            &key,
123            &mut config.keys,
124            config.home_url.as_deref(),
125        )?;
126        config.write_to_file(&Config::default_config_path()?)?;
127
128        println!("signing key was set successfully");
129
130        Ok(())
131    }
132}
133
134/// Deletes the signing key for a registry from the local keyring.
135#[derive(Args)]
136pub struct KeyDeleteCommand {
137    /// The common command options.
138    #[clap(flatten)]
139    pub common: CommonOptions,
140}
141
142impl KeyDeleteCommand {
143    /// Executes the command.
144    pub async fn exec(self) -> Result<()> {
145        let config = &mut self.common.read_config()?;
146
147        if Confirm::with_theme(&ColorfulTheme::default())
148            .with_prompt("are you sure you want to delete your signing key")
149            .interact()?
150        {
151            Keyring::from_config(config)?.delete_signing_key(
152                self.common.registry.as_deref(),
153                &config.keys,
154                config.home_url.as_deref(),
155            )?;
156            let keys = &mut config.keys;
157            if let Some(registry_url) = self.common.registry {
158                keys.swap_remove(&registry_url);
159            } else {
160                keys.swap_remove("default");
161            }
162            config.write_to_file(&Config::default_config_path()?)?;
163            println!("signing key was deleted successfully",);
164        } else if let Some(url) = self.common.registry {
165            println!(
166                "skipping deletion of signing key for registry `{url}`",
167                url = url
168            );
169        } else {
170            println!("skipping deletion of signing key");
171        }
172
173        Ok(())
174    }
175}