use clap::{Args, Subcommand};
use serde::Serialize;
use zad::error::Result;
use zad::permissions::signing;
use zad::permissions::trust::{TrustStore, trust_store_path};
#[derive(Debug, Args)]
pub struct SigningArgs {
#[command(subcommand)]
pub action: SigningAction,
}
#[derive(Debug, Subcommand)]
pub enum SigningAction {
Init(InitArgs),
Show(ShowArgs),
}
#[derive(Debug, Args)]
pub struct InitArgs {
#[arg(long)]
pub force: bool,
#[arg(long)]
pub json: bool,
}
#[derive(Debug, Args)]
pub struct ShowArgs {
#[arg(long)]
pub json: bool,
}
pub fn run(args: SigningArgs) -> Result<()> {
match args.action {
SigningAction::Init(a) => run_init(a),
SigningAction::Show(a) => run_show(a),
}
}
#[derive(Debug, Serialize)]
struct InitOut {
command: &'static str,
fingerprint: String,
public_key: String,
rotated: bool,
trust_store_path: String,
public_key_cache_path: String,
}
fn run_init(args: InitArgs) -> Result<()> {
let existed = signing::load_from_keychain()?.is_some();
let key = if args.force {
signing::rotate_keychain_key()?
} else {
signing::load_or_create_from_keychain()?
};
signing::write_public_key_cache(&key)?;
if args.force {
let path = trust_store_path()?;
if path.exists() {
std::fs::remove_file(&path).map_err(|e| zad::error::ZadError::Io {
path: path.clone(),
source: e,
})?;
}
}
let store = TrustStore::default();
store.save(&key)?;
let out = InitOut {
command: "signing.init",
fingerprint: key.fingerprint(),
public_key: key.public_key_b64(),
rotated: args.force && existed,
trust_store_path: trust_store_path()?.display().to_string(),
public_key_cache_path: signing::public_key_cache_path()?.display().to_string(),
};
if args.json {
println!("{}", serde_json::to_string_pretty(&out).unwrap());
} else {
if out.rotated {
println!("Rotated signing key (fingerprint: {}).", out.fingerprint);
println!(
" trust store reset: every permissions file must be re-signed via `zad <service> permissions sign`."
);
} else if existed {
println!(
"Signing key already initialized (fingerprint: {}).",
out.fingerprint
);
} else {
println!(
"Initialized signing key (fingerprint: {}).",
out.fingerprint
);
}
println!(" trust store : {}", out.trust_store_path);
println!(" public-key cache: {}", out.public_key_cache_path);
}
Ok(())
}
#[derive(Debug, Serialize)]
struct ShowOut {
command: &'static str,
initialized: bool,
fingerprint: Option<String>,
public_key: Option<String>,
trust_store_path: String,
public_key_cache_path: String,
}
fn run_show(args: ShowArgs) -> Result<()> {
let key = signing::load_from_keychain()?;
let out = ShowOut {
command: "signing.show",
initialized: key.is_some(),
fingerprint: key.as_ref().map(|k| k.fingerprint()),
public_key: key.as_ref().map(|k| k.public_key_b64()),
trust_store_path: trust_store_path()?.display().to_string(),
public_key_cache_path: signing::public_key_cache_path()?.display().to_string(),
};
if args.json {
println!("{}", serde_json::to_string_pretty(&out).unwrap());
} else if out.initialized {
println!("Signing key: {}", out.fingerprint.as_deref().unwrap_or(""));
println!(" trust store : {}", out.trust_store_path);
println!(" public-key cache: {}", out.public_key_cache_path);
} else {
println!("No signing key initialized.");
println!(" bootstrap with: zad signing init");
}
Ok(())
}