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;
#[derive(Args)]
pub struct KeyCommand {
#[clap(subcommand)]
pub command: KeySubcommand,
}
impl KeyCommand {
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,
}
}
}
#[derive(Subcommand)]
pub enum KeySubcommand {
New(KeyNewCommand),
Info(KeyInfoCommand),
Set(KeySetCommand),
Delete(KeyDeleteCommand),
}
#[derive(Args)]
pub struct KeyNewCommand {
#[clap(flatten)]
pub common: CommonOptions,
}
impl KeyNewCommand {
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(())
}
}
#[derive(Args)]
pub struct KeyInfoCommand {
#[clap(flatten)]
pub common: CommonOptions,
}
impl KeyInfoCommand {
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(())
}
}
#[derive(Args)]
pub struct KeySetCommand {
#[clap(flatten)]
pub common: CommonOptions,
}
impl KeySetCommand {
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(())
}
}
#[derive(Args)]
pub struct KeyDeleteCommand {
#[clap(flatten)]
pub common: CommonOptions,
}
impl KeyDeleteCommand {
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(®istry_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(())
}
}