use clap::{Args, Parser, Subcommand};
use std::path::PathBuf;
use crate::asym::cli::AssymCommand;
use crate::error::{AppError, Result};
#[derive(Debug, Parser)]
#[command(
name = "fcrypt",
version,
about = "Encrypt files with passwords or post-quantum recipient keys.",
long_about = None,
after_help = "Start here:\n fcrypt encrypt report.pdf\n fcrypt identity create alice\n fcrypt encrypt report.pdf --recipient ./alice_recipient_default.pub\n\nFull reference:\n fcrypt help-all"
)]
pub struct Cli {
#[arg(long, short = 'q', global = true, conflicts_with = "json")]
pub quiet: bool,
#[arg(long, global = true, conflicts_with = "quiet")]
pub json: bool,
#[arg(long, global = true)]
pub no_progress: bool,
#[command(subcommand)]
pub command: Command,
}
#[derive(Debug, Subcommand)]
pub enum Command {
#[command(visible_alias = "encode")]
Encrypt(EncryptArgs),
#[command(visible_alias = "decode")]
Decrypt(DecryptArgs),
Sign(SignArgs),
Verify(VerifyArgs),
Identity(IdentityArgs),
Password(PasswordArgs),
Phrase(PhraseArgs),
#[command(name = "asym", alias = "assym")]
Asym {
#[command(subcommand)]
command: AssymCommand,
},
#[command(hide = true)]
Keygen(KeygenArgs),
HelpAll,
}
#[derive(Debug, Args)]
pub struct EncryptArgs {
#[arg(value_name = "INPUT", required_unless_present = "input_flag")]
pub input: Option<PathBuf>,
#[arg(
long = "input",
short = 'i',
value_name = "FILE",
conflicts_with = "input"
)]
pub input_flag: Option<PathBuf>,
#[arg(long, short = 'o', value_name = "FILE")]
pub output: Option<PathBuf>,
#[arg(
long = "recipient",
short = 'r',
visible_alias = "recipient-public",
value_name = "FILE",
conflicts_with_all = ["new_identity", "password_file"]
)]
pub recipient: Option<PathBuf>,
#[arg(
long = "new-identity",
value_name = "NAME",
conflicts_with_all = ["recipient", "password_file"]
)]
pub new_identity: Option<String>,
#[arg(
long = "keys-dir",
short = 'k',
value_name = "DIR",
conflicts_with = "password_file"
)]
pub keys_dir: Option<PathBuf>,
#[arg(long, short = 's', conflicts_with = "password_file")]
pub sign: bool,
#[arg(
long = "sign-key",
short = 'S',
value_name = "FILE",
conflicts_with = "password_file"
)]
pub sign_key: Option<PathBuf>,
#[arg(long = "password-file", value_name = "FILE", conflicts_with_all = ["recipient", "new_identity", "keys_dir", "sign", "sign_key"])]
pub password_file: Option<PathBuf>,
#[arg(long, short = 'f')]
pub force: bool,
}
impl EncryptArgs {
pub fn input_path(&self) -> Result<PathBuf> {
resolve_input(self.input.as_ref(), self.input_flag.as_ref())
}
pub fn uses_pqc(&self) -> bool {
self.recipient.is_some()
|| self.new_identity.is_some()
|| self.keys_dir.is_some()
|| self.sign
|| self.sign_key.is_some()
}
}
#[derive(Debug, Args)]
pub struct DecryptArgs {
#[arg(value_name = "INPUT", required_unless_present = "input_flag")]
pub input: Option<PathBuf>,
#[arg(long = "input", value_name = "FILE", conflicts_with = "input")]
pub input_flag: Option<PathBuf>,
#[arg(long, short = 'o', value_name = "FILE")]
pub output: Option<PathBuf>,
#[arg(
long,
short = 'i',
value_name = "FILE",
conflicts_with = "password_file"
)]
pub identity: Option<PathBuf>,
#[arg(
long = "keys-dir",
short = 'k',
value_name = "DIR",
conflicts_with = "password_file"
)]
pub keys_dir: Option<PathBuf>,
#[arg(
long,
short = 'v',
value_name = "FILE",
conflicts_with = "password_file"
)]
pub verify: Option<PathBuf>,
#[arg(long = "require-signature", short = 'R', hide = true)]
pub require_signature: bool,
#[arg(long = "password-file", value_name = "FILE", conflicts_with_all = ["identity", "keys_dir", "verify", "require_signature"])]
pub password_file: Option<PathBuf>,
#[arg(long, short = 'f')]
pub force: bool,
}
impl DecryptArgs {
pub fn input_path(&self) -> Result<PathBuf> {
resolve_input(self.input.as_ref(), self.input_flag.as_ref())
}
pub fn uses_pqc(&self) -> bool {
self.identity.is_some()
|| self.keys_dir.is_some()
|| self.verify.is_some()
|| self.require_signature
}
}
#[derive(Debug, Args)]
pub struct SignArgs {
#[arg(value_name = "INPUT")]
pub input: PathBuf,
#[arg(long, short = 'k', value_name = "FILE")]
pub key: PathBuf,
#[arg(long, short = 'o', value_name = "FILE")]
pub output: Option<PathBuf>,
#[arg(long, short = 'f')]
pub force: bool,
}
#[derive(Debug, Args)]
pub struct VerifyArgs {
#[arg(value_name = "INPUT")]
pub input: PathBuf,
#[arg(long, short = 'k', value_name = "FILE")]
pub key: PathBuf,
}
#[derive(Debug, Args)]
pub struct IdentityArgs {
#[command(subcommand)]
pub command: IdentityCommand,
}
#[derive(Debug, Subcommand)]
pub enum IdentityCommand {
Create {
#[arg(value_name = "NAME")]
name: String,
#[arg(
long = "keys-dir",
short = 'k',
value_name = "DIR",
default_value = "."
)]
keys_dir: PathBuf,
#[arg(long = "lifetime-days", value_name = "DAYS")]
lifetime_days: Option<u64>,
#[arg(long, short = 'f')]
force: bool,
},
List {
#[arg(
long = "keys-dir",
short = 'k',
value_name = "DIR",
default_value = "."
)]
keys_dir: PathBuf,
},
Inspect {
#[arg(value_name = "KEY")]
key: PathBuf,
},
}
#[derive(Debug, Args)]
pub struct PasswordArgs {
#[command(subcommand)]
pub command: PasswordCommand,
}
#[derive(Debug, Subcommand)]
pub enum PasswordCommand {
Generate {
#[arg(long, short = 'l', value_name = "N")]
length: usize,
#[arg(long = "base64", short = 'b')]
base64: bool,
#[arg(long, short = 'c')]
compatible: bool,
},
}
#[derive(Debug, Args)]
pub struct PhraseArgs {
#[command(subcommand)]
pub command: PhraseCommand,
}
#[derive(Debug, Subcommand)]
pub enum PhraseCommand {
Generate {
#[arg(long, short = 'w', value_name = "N")]
words: usize,
#[arg(
long = "separator",
short = 's',
value_name = "SEP",
default_value = "-"
)]
separator: String,
},
}
#[derive(Debug, Args)]
pub struct KeygenArgs {
#[arg(value_name = "N")]
pub length: Option<usize>,
#[command(subcommand)]
pub command: Option<KeygenCommand>,
}
#[derive(Debug, Subcommand)]
pub enum KeygenCommand {
Phrase {
#[arg(value_name = "N")]
word_count: usize,
#[arg(long = "sep", short = 's', value_name = "SEP", default_value = "-")]
separator: String,
},
Pair {
#[arg(value_name = "NAME")]
name: String,
#[arg(value_name = "LIFETIME_DAYS")]
lifetime_days: Option<u64>,
},
}
fn resolve_input(positional: Option<&PathBuf>, flagged: Option<&PathBuf>) -> Result<PathBuf> {
match (positional, flagged) {
(Some(input), None) | (None, Some(input)) => Ok(input.clone()),
_ => Err(AppError::InvalidArgument(
"exactly one input path must be provided".to_string(),
)),
}
}