use std::path::PathBuf;
use age::{
secrecy::{ExposeSecret, SecretString},
x25519,
};
use clap::Parser;
use itertools::Itertools;
use miette::{Context as _, IntoDiagnostic as _, Result};
use tokio::{fs::File, io::AsyncWriteExt};
use crate::passphrases::{Passphrase, PassphraseArgs};
#[derive(Debug, Clone, Parser)]
#[clap(verbatim_doc_comment)]
pub struct KeygenArgs {
#[arg(short, long)]
pub output: Option<PathBuf>,
#[arg(long = "public", default_value = "identity.pub")]
pub public_path: PathBuf,
#[arg(long)]
pub plaintext: bool,
#[arg(short = 'R', long, conflicts_with = "plaintext")]
pub random_passphrase: bool,
#[command(flatten)]
#[allow(missing_docs, reason = "don't interfere with clap")]
pub key: PassphraseArgs,
}
pub async fn run(
KeygenArgs {
output,
public_path,
plaintext,
random_passphrase,
key,
}: KeygenArgs,
) -> Result<()> {
let secret = x25519::Identity::generate();
let public = secret.to_public();
let output = output.unwrap_or_else(|| {
if plaintext {
"identity.txt"
} else {
"identity.txt.age"
}
.into()
});
let identity = SecretString::from(format!(
"# created: {}\n# public key: {public}\n{}\n",
jiff::Timestamp::now(),
secret.to_string().expose_secret()
));
let identity = if plaintext {
identity.expose_secret().as_bytes().to_owned()
} else {
let key = if random_passphrase {
use rand::seq::SliceRandom;
let rng = &mut rand::thread_rng();
let words = diceware_wordlists::MINILOCK_WORDLIST.choose_multiple(rng, 6);
let phrase: String = Itertools::intersperse(words.map(|item| *item), "-").collect();
Passphrase::new(phrase.into())
} else {
key.require_with_confirmation().await?
};
age::encrypt(&key, identity.expose_secret().as_bytes()).into_diagnostic()?
};
File::create_new(&output)
.await
.into_diagnostic()
.wrap_err("opening the identity file")?
.write_all(&identity)
.await
.into_diagnostic()
.wrap_err("writing the identity")?;
println!("public key: {public}");
if public_path.to_string_lossy() != "-" {
File::create_new(&public_path)
.await
.into_diagnostic()
.wrap_err("opening the public key file")?
.write_all(public.to_string().as_bytes())
.await
.into_diagnostic()
.wrap_err("writing the public key")?;
}
Ok(())
}