1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
use anyhow::{Context, Result};
use clap::{Args, Subcommand};
use dialoguer::{theme::ColorfulTheme, Confirm, Password};
use p256::ecdsa::SigningKey;
use rand_core::OsRng;
use warg_client::keyring::Keyring;
use warg_client::Config;
use warg_crypto::signing::PrivateKey;

use super::CommonOptions;

/// Manage signing keys for interacting with a registry.
#[derive(Args)]
pub struct KeyCommand {
    /// The subcommand to execute.
    #[clap(subcommand)]
    pub command: KeySubcommand,
}

impl KeyCommand {
    /// Executes the command.
    pub async fn exec(self) -> Result<()> {
        match self.command {
            KeySubcommand::New(cmd) => cmd.exec().await,
            KeySubcommand::Info(cmd) => cmd.exec().await,
            KeySubcommand::Set(cmd) => cmd.exec().await,
            KeySubcommand::Delete(cmd) => cmd.exec().await,
        }
    }
}

/// The subcommand to execute.
#[derive(Subcommand)]
pub enum KeySubcommand {
    /// Creates a new signing key for a registry in the local keyring.
    New(KeyNewCommand),
    /// Shows information about the signing key for a registry in the local keyring.
    Info(KeyInfoCommand),
    /// Sets the signing key for a registry in the local keyring.
    Set(KeySetCommand),
    /// Deletes the signing key for a registry from the local keyring.
    Delete(KeyDeleteCommand),
}

/// Creates a new signing key for a registry in the local keyring.
#[derive(Args)]
pub struct KeyNewCommand {
    /// The common command options.
    #[clap(flatten)]
    pub common: CommonOptions,
}

impl KeyNewCommand {
    /// Executes the command.
    pub async fn exec(self) -> Result<()> {
        let config = &mut self.common.read_config()?;
        let key = SigningKey::random(&mut OsRng).into();
        if let Some(ref reg) = self.common.registry {
            config.keys.insert(reg.to_string());
        } else {
            config.keys.insert("default".to_string());
        }
        Keyring::from_config(config)?.set_signing_key(
            self.common.registry.as_deref(),
            &key,
            &mut config.keys,
            config.home_url.as_deref(),
        )?;
        config.write_to_file(&Config::default_config_path()?)?;
        let public_key = key.public_key();
        println!("Key ID: {}", public_key.fingerprint());
        println!("Public Key: {public_key}");
        Ok(())
    }
}

/// Shows information about the signing key for a registry in the local keyring.
#[derive(Args)]
pub struct KeyInfoCommand {
    /// The common command options.
    #[clap(flatten)]
    pub common: CommonOptions,
}

impl KeyInfoCommand {
    /// Executes the command.
    pub async fn exec(self) -> Result<()> {
        let config = &self.common.read_config()?;
        let private_key = Keyring::from_config(config)?.get_signing_key(
            self.common.registry.as_deref(),
            &config.keys,
            config.home_url.as_deref(),
        )?;
        let public_key = private_key.public_key();
        println!("Key ID: {}", public_key.fingerprint());
        println!("Public Key: {public_key}");
        Ok(())
    }
}

/// Sets the signing key for a registry in the local keyring.
#[derive(Args)]
pub struct KeySetCommand {
    /// The common command options.
    #[clap(flatten)]
    pub common: CommonOptions,
}

impl KeySetCommand {
    /// Executes the command.
    pub async fn exec(self) -> Result<()> {
        let key_str = Password::with_theme(&ColorfulTheme::default())
            .with_prompt("input signing key (expected format is `<alg>:<base64>`): ")
            .interact()
            .context("failed to read signing key")?;
        let key =
            PrivateKey::decode(key_str).context("signing key is not in the correct format")?;
        let config = &mut self.common.read_config()?;

        Keyring::from_config(config)?.set_signing_key(
            self.common.registry.as_deref(),
            &key,
            &mut config.keys,
            config.home_url.as_deref(),
        )?;
        config.write_to_file(&Config::default_config_path()?)?;

        println!("signing key was set successfully");

        Ok(())
    }
}

/// Deletes the signing key for a registry from the local keyring.
#[derive(Args)]
pub struct KeyDeleteCommand {
    /// The common command options.
    #[clap(flatten)]
    pub common: CommonOptions,
}

impl KeyDeleteCommand {
    /// Executes the command.
    pub async fn exec(self) -> Result<()> {
        let config = &mut self.common.read_config()?;

        if Confirm::with_theme(&ColorfulTheme::default())
            .with_prompt("are you sure you want to delete your signing key")
            .interact()?
        {
            Keyring::from_config(config)?.delete_signing_key(
                self.common.registry.as_deref(),
                &config.keys,
                config.home_url.as_deref(),
            )?;
            let keys = &mut config.keys;
            if let Some(registry_url) = self.common.registry {
                keys.swap_remove(&registry_url);
            } else {
                keys.swap_remove("default");
            }
            config.write_to_file(&Config::default_config_path()?)?;
            println!("signing key was deleted successfully",);
        } else if let Some(url) = self.common.registry {
            println!(
                "skipping deletion of signing key for registry `{url}`",
                url = url
            );
        } else {
            println!("skipping deletion of signing key");
        }

        Ok(())
    }
}